mirror of
https://github.com/collinbarrett/FilterLists.git
synced 2026-03-11 09:04:27 +00:00
feat(web): add Languages column
This commit is contained in:
parent
2185b38717
commit
4de9282310
14 changed files with 161 additions and 12 deletions
BIN
web/package-lock.json
generated
BIN
web/package-lock.json
generated
Binary file not shown.
|
|
@ -12,6 +12,7 @@
|
|||
"@azure/monitor-opentelemetry": "^1.11.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { FilterList } from "@/services/get-filterlists";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { FilterListsTableMeta } from ".";
|
||||
import { LanguageBadges } from "./language-badges";
|
||||
|
||||
const columnHelper = createColumnHelper<FilterList>();
|
||||
|
||||
|
|
@ -13,6 +14,18 @@ export const columns = [
|
|||
columnHelper.accessor((row) => row.description, {
|
||||
header: "Description",
|
||||
}),
|
||||
columnHelper.accessor((row) => row.languageIds, {
|
||||
header: "Languages",
|
||||
cell: (info) => {
|
||||
const meta = info.table.options.meta as FilterListsTableMeta;
|
||||
const languages =
|
||||
info
|
||||
.getValue()
|
||||
?.flatMap((id) => meta.languages.find((l) => l.id === id) || []) ??
|
||||
[];
|
||||
return <LanguageBadges languages={languages} />;
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor((row) => row.licenseId, {
|
||||
header: "License",
|
||||
cell: (info) => {
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {
|
||||
useReactTable,
|
||||
getCoreRowModel,
|
||||
|
|
@ -29,6 +28,7 @@ import { Syntax } from "@/services/get-syntaxes";
|
|||
import { Tag } from "@/services/get-tags";
|
||||
|
||||
export interface FilterListsTableMeta extends TableMeta<FilterList> {
|
||||
languages: Language[];
|
||||
licenses: License[];
|
||||
}
|
||||
|
||||
|
|
@ -46,6 +46,7 @@ interface FilterListTableProps {
|
|||
export function FilterListTable({
|
||||
columns,
|
||||
initialFilterLists,
|
||||
languages,
|
||||
licenses,
|
||||
}: FilterListTableProps) {
|
||||
const [data, setData] = useState<FilterList[]>(initialFilterLists);
|
||||
|
|
@ -67,7 +68,10 @@ export function FilterListTable({
|
|||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
meta: { licenses: licenses } as FilterListsTableMeta,
|
||||
meta: {
|
||||
languages,
|
||||
licenses,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
|||
33
web/src/components/filterlist-table/language-badges.tsx
Normal file
33
web/src/components/filterlist-table/language-badges.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
"use client";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Language } from "@/services/get-languages";
|
||||
|
||||
interface LanguageBadgesProps {
|
||||
languages: Language[];
|
||||
}
|
||||
|
||||
export function LanguageBadges({ languages }: LanguageBadgesProps) {
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<TooltipProvider>
|
||||
{languages.map((language) => (
|
||||
<Tooltip key={language.id}>
|
||||
<TooltipTrigger>
|
||||
<Badge variant="secondary">{language.iso6391}</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{language.name}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import * as React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Table } from "@tanstack/react-table";
|
||||
import { FilterList } from "@/services/get-filterlists";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||
|
||||
export function ThemeProvider({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
|
|
|
|||
45
web/src/components/ui/badge.tsx
Normal file
45
web/src/components/ui/badge.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
function Badge({
|
||||
className,
|
||||
variant,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"span"> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot : "span";
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="badge"
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants };
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
||||
|
|
|
|||
60
web/src/components/ui/tooltip.tsx
Normal file
60
web/src/components/ui/tooltip.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
"use client";
|
||||
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function TooltipProvider({
|
||||
delayDuration = 0,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
||||
return (
|
||||
<TooltipPrimitive.Provider
|
||||
data-slot="tooltip-provider"
|
||||
delayDuration={delayDuration}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Tooltip({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function TooltipTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function TooltipContent({
|
||||
className,
|
||||
sideOffset = 0,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
||||
return (
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
data-slot="tooltip-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||
|
|
@ -2,9 +2,9 @@ import { API_BASE_URL, DEFAULT_REVALIDATE_SECS } from "./constants";
|
|||
|
||||
export type Language = {
|
||||
id: number;
|
||||
iso6391: string | null;
|
||||
name: string | null;
|
||||
filterListIds: number[] | null;
|
||||
iso6391: string;
|
||||
name: string;
|
||||
filterListIds: number[]; // TODO: are these needed, or can we remove them from the API?
|
||||
};
|
||||
|
||||
export async function getLanguages(): Promise<Language[]> {
|
||||
|
|
|
|||
Loading…
Reference in a new issue