diff --git a/web/package-lock.json b/web/package-lock.json index 2daadc24d..3a52fbc2c 100644 Binary files a/web/package-lock.json and b/web/package-lock.json differ diff --git a/web/package.json b/web/package.json index e8cc39744..983072a92 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/src/components/filterlist-table/columns.ts b/web/src/components/filterlist-table/columns.tsx similarity index 61% rename from web/src/components/filterlist-table/columns.ts rename to web/src/components/filterlist-table/columns.tsx index 6d7cbec71..7fb61d2b0 100644 --- a/web/src/components/filterlist-table/columns.ts +++ b/web/src/components/filterlist-table/columns.tsx @@ -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(); @@ -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 ; + }, + }), columnHelper.accessor((row) => row.licenseId, { header: "License", cell: (info) => { diff --git a/web/src/components/filterlist-table/index.tsx b/web/src/components/filterlist-table/index.tsx index f24fdd5bc..3faca8c3c 100644 --- a/web/src/components/filterlist-table/index.tsx +++ b/web/src/components/filterlist-table/index.tsx @@ -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 { + languages: Language[]; licenses: License[]; } @@ -46,6 +46,7 @@ interface FilterListTableProps { export function FilterListTable({ columns, initialFilterLists, + languages, licenses, }: FilterListTableProps) { const [data, setData] = useState(initialFilterLists); @@ -67,7 +68,10 @@ export function FilterListTable({ columns, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), - meta: { licenses: licenses } as FilterListsTableMeta, + meta: { + languages, + licenses, + }, }); return ( diff --git a/web/src/components/filterlist-table/language-badges.tsx b/web/src/components/filterlist-table/language-badges.tsx new file mode 100644 index 000000000..93d1c72cc --- /dev/null +++ b/web/src/components/filterlist-table/language-badges.tsx @@ -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 ( +
+ + {languages.map((language) => ( + + + {language.iso6391} + + +

{language.name}

+
+
+ ))} +
+
+ ); +} diff --git a/web/src/components/filterlist-table/pagination.tsx b/web/src/components/filterlist-table/pagination.tsx index 67ec99c5f..b480e6a98 100644 --- a/web/src/components/filterlist-table/pagination.tsx +++ b/web/src/components/filterlist-table/pagination.tsx @@ -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"; diff --git a/web/src/components/theme-provider.tsx b/web/src/components/theme-provider.tsx index 189a2b1a1..f2df7ce74 100644 --- a/web/src/components/theme-provider.tsx +++ b/web/src/components/theme-provider.tsx @@ -1,6 +1,5 @@ "use client"; -import * as React from "react"; import { ThemeProvider as NextThemesProvider } from "next-themes"; export function ThemeProvider({ diff --git a/web/src/components/theme-toggle.tsx b/web/src/components/theme-toggle.tsx index 498143795..9448477c1 100644 --- a/web/src/components/theme-toggle.tsx +++ b/web/src/components/theme-toggle.tsx @@ -1,6 +1,5 @@ "use client"; -import * as React from "react"; import { Moon, Sun } from "lucide-react"; import { useTheme } from "next-themes"; diff --git a/web/src/components/ui/badge.tsx b/web/src/components/ui/badge.tsx new file mode 100644 index 000000000..04056d65e --- /dev/null +++ b/web/src/components/ui/badge.tsx @@ -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 & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span"; + + return ( + + ); +} + +export { Badge, badgeVariants }; diff --git a/web/src/components/ui/button.tsx b/web/src/components/ui/button.tsx index 2adaf00da..7a7318ef7 100644 --- a/web/src/components/ui/button.tsx +++ b/web/src/components/ui/button.tsx @@ -1,4 +1,3 @@ -import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; diff --git a/web/src/components/ui/dropdown-menu.tsx b/web/src/components/ui/dropdown-menu.tsx index 1fc1f4ee3..50fdb8d9d 100644 --- a/web/src/components/ui/dropdown-menu.tsx +++ b/web/src/components/ui/dropdown-menu.tsx @@ -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"; diff --git a/web/src/components/ui/table.tsx b/web/src/components/ui/table.tsx index 4b3c98ea4..5eba40859 100644 --- a/web/src/components/ui/table.tsx +++ b/web/src/components/ui/table.tsx @@ -1,7 +1,5 @@ "use client"; -import * as React from "react"; - import { cn } from "@/lib/utils"; function Table({ className, ...props }: React.ComponentProps<"table">) { diff --git a/web/src/components/ui/tooltip.tsx b/web/src/components/ui/tooltip.tsx new file mode 100644 index 000000000..b3bb135ea --- /dev/null +++ b/web/src/components/ui/tooltip.tsx @@ -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) { + return ( + + ); +} + +function Tooltip({ + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +function TooltipTrigger({ + ...props +}: React.ComponentProps) { + return ; +} + +function TooltipContent({ + className, + sideOffset = 0, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + ); +} + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/web/src/services/get-languages.ts b/web/src/services/get-languages.ts index f39b75b0b..25a6d2a6f 100644 --- a/web/src/services/get-languages.ts +++ b/web/src/services/get-languages.ts @@ -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 {