From 4de92823102e092688d20fb6837755bb9794a267 Mon Sep 17 00:00:00 2001 From: Collin Barrett <6483057+collinbarrett@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:38:51 -0500 Subject: [PATCH] feat(web): add Languages column --- web/package-lock.json | Bin 336196 -> 337740 bytes web/package.json | 1 + .../{columns.ts => columns.tsx} | 13 ++++ web/src/components/filterlist-table/index.tsx | 8 ++- .../filterlist-table/language-badges.tsx | 33 ++++++++++ .../filterlist-table/pagination.tsx | 1 - web/src/components/theme-provider.tsx | 1 - web/src/components/theme-toggle.tsx | 1 - web/src/components/ui/badge.tsx | 45 +++++++++++++ web/src/components/ui/button.tsx | 1 - web/src/components/ui/dropdown-menu.tsx | 1 - web/src/components/ui/table.tsx | 2 - web/src/components/ui/tooltip.tsx | 60 ++++++++++++++++++ web/src/services/get-languages.ts | 6 +- 14 files changed, 161 insertions(+), 12 deletions(-) rename web/src/components/filterlist-table/{columns.ts => columns.tsx} (61%) create mode 100644 web/src/components/filterlist-table/language-badges.tsx create mode 100644 web/src/components/ui/badge.tsx create mode 100644 web/src/components/ui/tooltip.tsx diff --git a/web/package-lock.json b/web/package-lock.json index 2daadc24df7d31cf66ec69857e4adf6b840d2a7b..3a52fbc2c6e810f88247faffc1b6f911b4d6ce7a 100644 GIT binary patch delta 507 zcmX?dNaV~pkqw!Q(-jUe^0SxZ=jW7U7EFJT$0F98!MHtxkumBDOmy1A0^RT!=vk!s{o;v19{ zsPCE@o}Fb>SeYGV7F_BYW|A4KpKq8HhBa*rC;J%VdUkX zR8m?Ps%;uI{or{P)#(?`Gloo`aD`E1`u}x|?$cve0$Hki7?r2rxxlC~{o)?R4buZI zGqO+LwU<$Sdi@PX!Ra*@8M&rUzQ8EWUzS;1nwXPQshg3Rl9HOY{oEPGD~!_{Rx`3p zf3TO4fBV&|jBSF`8}2f)@S!T5u4ut1G2LN3Gt2b+%ZyH%sIqzRU_cWG21kP@FgOaT zyb2ACN?a2YJu?cklBx=vOUv~AD+|0L^_>C|%lz}xaHGT? zhg4;mxhI*L7l#MEqAB-1Nr#nb6aZNXnVPa<- aFxRYAVB%~ykYfU3W*}zSZXm}h!wCSAgm%&Z 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 {