From 0dd561da30b795370ab742363dc6aea7c28718d8 Mon Sep 17 00:00:00 2001 From: Natalie Bridgers Date: Wed, 30 Sep 2026 17:36:29 -0500 Subject: [PATCH] Update web UI theme tokens and add recursive token validation test Signed-off-by: Natalie Bridgers --- js/web/src/components/dashboard/stream-info.tsx | 2 +- js/web/src/components/ui/dropdown-menu.tsx | 10 +++++----- js/web/src/components/ui/select.tsx | 2 +- js/web/src/styles.css | 1 - js/web/src/styles.test.ts | 14 ++++++++++++++ 5 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 js/web/src/styles.test.ts diff --git a/js/web/src/components/dashboard/stream-info.tsx b/js/web/src/components/dashboard/stream-info.tsx index c62da5613..c76abd67a 100644 --- a/js/web/src/components/dashboard/stream-info.tsx +++ b/js/web/src/components/dashboard/stream-info.tsx @@ -358,7 +358,7 @@ export function StreamInfoWidget({ store }: { store: LivestreamStore }) { diff --git a/js/web/src/components/ui/dropdown-menu.tsx b/js/web/src/components/ui/dropdown-menu.tsx index fd2cd08b0..587fe0844 100644 --- a/js/web/src/components/ui/dropdown-menu.tsx +++ b/js/web/src/components/ui/dropdown-menu.tsx @@ -92,7 +92,7 @@ function DropdownMenuItem({ data-inset={inset} data-variant={variant} className={cn( - "group/dropdown-menu-item data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 data-[variant=destructive]:data-highlighted:text-destructive dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 data-[variant=destructive]:*:[svg]:text-destructive relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "group/dropdown-menu-item data-highlighted:bg-muted data-highlighted:text-foreground not-data-[variant=destructive]:data-highlighted:**:text-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 data-[variant=destructive]:data-highlighted:text-destructive dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 data-[variant=destructive]:*:[svg]:text-destructive relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className, )} {...props} @@ -117,7 +117,7 @@ function DropdownMenuSubTrigger({ data-slot="dropdown-menu-sub-trigger" data-inset={inset} className={cn( - "data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "data-highlighted:bg-muted data-highlighted:text-foreground not-data-[variant=destructive]:data-highlighted:**:text-foreground data-popup-open:bg-muted data-popup-open:text-foreground data-open:bg-muted data-open:text-foreground flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className, )} {...props} @@ -166,7 +166,7 @@ function DropdownMenuCheckboxItem({ data-slot="dropdown-menu-checkbox-item" data-inset={inset} className={cn( - "data-highlighted:bg-accent data-highlighted:text-accent-foreground data-highlighted:**:text-accent-foreground relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "data-highlighted:bg-muted data-highlighted:text-foreground data-highlighted:**:text-foreground relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className, )} checked={checked} @@ -207,7 +207,7 @@ function DropdownMenuRadioItem({ data-slot="dropdown-menu-radio-item" data-inset={inset} className={cn( - "data-highlighted:bg-accent data-highlighted:text-accent-foreground data-highlighted:**:text-accent-foreground relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "data-highlighted:bg-muted data-highlighted:text-foreground data-highlighted:**:text-foreground relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className, )} {...props} @@ -246,7 +246,7 @@ function DropdownMenuShortcut({ { + const css = readFileSync("src/styles.css", "utf8"); + const aliases = [...css.matchAll(/(--[\w-]+):\s*var\((--[\w-]+)\)\s*;/g)]; + expect(aliases.length).toBeGreaterThan(0); + // A self-reference invalidates the token, leaving highlight backgrounds + // transparent even when the paired foreground color is applied. + const selfReferences = aliases + .filter(([, name, target]) => name === target) + .map(([, name]) => name); + expect(selfReferences).toEqual([]); +}); -- knotmirror 0.0.1