From c11fbfc5b1a7eca384fdcf8c60f6976fe37c8dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriele=20Vigan=C3=B2?= Date: Sun, 25 Oct 2026 23:06:53 +0000 Subject: [PATCH 1/2] fix(ui): replace browser defaults with themed controls --- src/components/account/access-panels.tsx | 40 +++--- src/components/account/account-cards.tsx | 26 ++-- src/components/account/mcp-panel.tsx | 25 ++-- src/components/account/secret-text.tsx | 41 +++--- src/components/account/sessions-panel.tsx | 25 ++-- src/components/copy-button.tsx | 45 +++--- src/components/oidc/form-fields.tsx | 167 +++++++++++----------- src/components/rbac/catalog-search.tsx | 34 +---- src/components/rbac/pick-list.tsx | 83 +++++------ src/components/ui/input.tsx | 2 +- src/components/ui/scroll-area.tsx | 54 +++++++ src/components/ui/search-input.tsx | 52 +++++++ src/components/ui/textarea.tsx | 2 +- src/components/ui/tooltip.tsx | 19 ++- src/components/users/traits.tsx | 52 +++---- src/components/users/user-roles.tsx | 34 +++-- src/routes/access/roles/index.tsx | 47 +++--- src/routes/users/$userId.tsx | 15 +- src/routes/users/index.tsx | 70 ++++----- src/styles.css | 59 ++++++++ 20 files changed, 548 insertions(+), 344 deletions(-) create mode 100644 src/components/ui/scroll-area.tsx create mode 100644 src/components/ui/search-input.tsx diff --git a/src/components/account/access-panels.tsx b/src/components/account/access-panels.tsx index ee49a63..9bf6c9c 100644 --- a/src/components/account/access-panels.tsx +++ b/src/components/account/access-panels.tsx @@ -1,3 +1,5 @@ +import { SearchInput } from "@/components/ui/search-input"; +import { Hint } from "@/components/ui/tooltip"; import { useState, type ReactNode } from "react"; import { cn } from "cn"; import { BadgeCheck, Fingerprint, Plus, Search, Trash2 } from "lucide-react"; @@ -73,20 +75,20 @@ export function GroupsPanel({ groups }: { groups: AccountDetails["groups"] }) { {groups.length ? ( ) : ( @@ -146,18 +148,14 @@ export function PermissionsPanel({ permissions }: { permissions: Permission[] }) className="pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground" aria-hidden="true" /> - setSearch(event.target.value)} - onKeyDown={(event) => { - if (event.key === "Escape") setSearch(""); - }} + onValueChange={setSearch} aria-label="Search permissions" placeholder="Search" autoComplete="off" spellCheck={false} - className="h-9 w-full rounded-md border bg-background pr-2.5 pl-8 text-base outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 sm:text-sm dark:bg-input/30" + className="h-9 w-full rounded-md border bg-background pl-8 text-base outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 sm:text-sm dark:bg-input/30" /> {matching.length ? ( diff --git a/src/components/account/account-cards.tsx b/src/components/account/account-cards.tsx index 8b309be..84ff258 100644 --- a/src/components/account/account-cards.tsx +++ b/src/components/account/account-cards.tsx @@ -1,3 +1,4 @@ +import { Hint } from "@/components/ui/tooltip"; import { useRouter } from "@tanstack/react-router"; import { useState, type ReactNode } from "react"; import { cn } from "cn"; @@ -67,17 +68,19 @@ function UnlinkAction({ onClick: () => void; }) { return ( - - - + + + + + ); } @@ -295,6 +298,7 @@ function StudentVerification({ onDone }: { onDone: () => void }) { return (
{ event.preventDefault(); diff --git a/src/components/account/mcp-panel.tsx b/src/components/account/mcp-panel.tsx index 5bcfdd9..256dd42 100644 --- a/src/components/account/mcp-panel.tsx +++ b/src/components/account/mcp-panel.tsx @@ -1,3 +1,4 @@ +import { Hint } from "@/components/ui/tooltip"; import { useState } from "react"; import { Bot, Unplug } from "lucide-react"; import type { AccountDetails } from "@/auth/account.functions"; @@ -54,17 +55,19 @@ export function McpPanel({ mcp, actions }: { mcp: Mcp; actions: AccountActions } Active

- + + + ); })} diff --git a/src/components/account/secret-text.tsx b/src/components/account/secret-text.tsx index d77dca1..23ddc5a 100644 --- a/src/components/account/secret-text.tsx +++ b/src/components/account/secret-text.tsx @@ -1,3 +1,4 @@ +import { Hint } from "@/components/ui/tooltip"; import { useEffect, useState } from "react"; import { cn } from "cn"; import { CopyButton } from "@/components/copy-button"; @@ -57,26 +58,28 @@ export function SecretText({ return ( - + + {truncate(revealed ? value : scramble(value), maxChars)} + + + ); diff --git a/src/components/account/sessions-panel.tsx b/src/components/account/sessions-panel.tsx index 78a887b..84b6c54 100644 --- a/src/components/account/sessions-panel.tsx +++ b/src/components/account/sessions-panel.tsx @@ -1,3 +1,4 @@ +import { Hint } from "@/components/ui/tooltip"; import { useState } from "react"; import { LogOut, Monitor, Smartphone } from "lucide-react"; import type { AccountDetails } from "@/auth/account.functions"; @@ -105,17 +106,19 @@ export function SessionsPanel({

{!entry.current && ( - + + + )} ); diff --git a/src/components/copy-button.tsx b/src/components/copy-button.tsx index 05646aa..01f6fb8 100644 --- a/src/components/copy-button.tsx +++ b/src/components/copy-button.tsx @@ -1,3 +1,4 @@ +import { Hint } from "@/components/ui/tooltip"; import { useEffect, useState } from "react"; import { Check, Copy } from "lucide-react"; import { Button } from "@/components/ui/button"; @@ -20,24 +21,30 @@ export function CopyButton({ return () => clearTimeout(timer); }, [copied]); return ( - + + + ); } diff --git a/src/components/oidc/form-fields.tsx b/src/components/oidc/form-fields.tsx index d9e72f4..3da7c0e 100644 --- a/src/components/oidc/form-fields.tsx +++ b/src/components/oidc/form-fields.tsx @@ -1,3 +1,4 @@ +import { Hint } from "@/components/ui/tooltip"; // Fields shared by the new-application wizard and the application settings page. import { useEffect, useId, useRef, useState, type KeyboardEvent, type ReactNode } from "react"; import { cn } from "cn"; @@ -136,27 +137,28 @@ export function LogoPreview({ url, dark }: { url: string; dark?: boolean }) { }, [url]); const broken = !!src && failed === src; return ( -
- {broken ? ( -
+ +
+ {broken ? ( +
+
); } @@ -535,36 +537,37 @@ export function IdentityScopeList({ const checkboxId = `${id}-${scope.id}`; return (
  • - +
  • ); })} @@ -742,37 +745,39 @@ export function ApiAccessTable({ {apiScopes .filter((scope) => scope.resource === resource.id) .map((scope) => ( - - - - {scope.label} - - {scope.id} - - - - {columns.map((column) => ( - - - - ))} - + + + + + {scope.label} + + {scope.id} + + + + {columns.map((column) => ( + + + + ))} + + ))} ))} diff --git a/src/components/rbac/catalog-search.tsx b/src/components/rbac/catalog-search.tsx index 2ea49df..1487aab 100644 --- a/src/components/rbac/catalog-search.tsx +++ b/src/components/rbac/catalog-search.tsx @@ -1,9 +1,8 @@ -import { useRef, type ReactNode } from "react"; -import { Search, X } from "lucide-react"; +import { type ReactNode } from "react"; +import { Search } from "lucide-react"; import { cn } from "cn"; -import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; +import { SearchInput } from "@/components/ui/search-input"; /** The search box on its own, for pages that lay out the rest of the toolbar themselves. */ export function SearchField({ @@ -19,40 +18,19 @@ export function SearchField({ className?: string; inputClassName?: string; }) { - const input = useRef(null); - return (
    ); } diff --git a/src/components/rbac/pick-list.tsx b/src/components/rbac/pick-list.tsx index 9bf9a55..1dc3900 100644 --- a/src/components/rbac/pick-list.tsx +++ b/src/components/rbac/pick-list.tsx @@ -4,6 +4,7 @@ import { cn } from "cn"; import { Search } from "lucide-react"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; +import { ScrollArea } from "@/components/ui/scroll-area"; export type PickOption = { key: string; @@ -72,50 +73,50 @@ export function PickList({ /> )} -
    - {visible.length === 0 ? ( -

    - {options.length === 0 ? emptyText : "Nothing matches that filter."} -

    - ) : ( - visible.map((option) => { - const checked = selected.includes(option.key); - return ( - + ); + }) + )} +
    + {error && (