refactor(web): kit consistency — focusRing, PageTitle, Badge, size-4, icon buttons (#66)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 23:49:35 +02:00
parent 900f85f8ac
commit 74cde67a54
8 changed files with 18 additions and 13 deletions
+2 -1
View File
@@ -7,6 +7,7 @@ import { useConfig } from "../config/config-context";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { PageTitle } from "@/components/ui/page-title";
/** Accept only a single-leading-slash local path; reject protocol-relative /** Accept only a single-leading-slash local path; reject protocol-relative
* ("//host") and absolute URLs to avoid an open redirect. */ * ("//host") and absolute URLs to avoid an open redirect. */
@@ -46,7 +47,7 @@ export function LoginPage() {
return ( return (
<div className="flex min-h-screen items-center justify-center p-4"> <div className="flex min-h-screen items-center justify-center p-4">
<form onSubmit={onSubmit} className="w-full max-w-sm space-y-4"> <form onSubmit={onSubmit} className="w-full max-w-sm space-y-4">
<h1 className="text-2xl font-semibold">{app_name}</h1> <PageTitle>{app_name}</PageTitle>
{sessionExpired && ( {sessionExpired && (
<p className="text-sm text-muted-foreground">{t("auth.sessionExpired")}</p> <p className="text-sm text-muted-foreground">{t("auth.sessionExpired")}</p>
)} )}
+2 -2
View File
@@ -96,9 +96,9 @@ export function FieldList({
> >
<span className="font-medium">{labelText(def.labels, lang)}</span> <span className="font-medium">{labelText(def.labels, lang)}</span>
<span className="text-xs text-muted-foreground">{def.key}</span> <span className="text-xs text-muted-foreground">{def.key}</span>
<span className="rounded-md bg-muted px-1.5 py-0.5 text-xs text-muted-foreground"> <Badge variant="secondary">
{t(`fields.types.${def.data_type}`)} {t(`fields.types.${def.data_type}`)}
</span> </Badge>
{def.required && ( {def.required && (
<span <span
className="text-xs text-destructive" className="text-xs text-destructive"
+2 -1
View File
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
import { X } from "lucide-react"; import { X } from "lucide-react";
import { Drawer, DrawerClose, DrawerContent } from "@/components/ui/drawer"; import { Drawer, DrawerClose, DrawerContent } from "@/components/ui/drawer";
import { Button } from "@/components/ui/button";
/** /**
* Narrow-viewport object detail: the nested <Outlet/> inside a Base UI Drawer that * Narrow-viewport object detail: the nested <Outlet/> inside a Base UI Drawer that
@@ -30,7 +31,7 @@ export function ObjectDetailDrawer({
<div className="flex justify-end border-b p-2"> <div className="flex justify-end border-b p-2">
<DrawerClose <DrawerClose
aria-label={t("actions.closeDetail")} aria-label={t("actions.closeDetail")}
className="rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground" render={<Button variant="ghost" size="icon-sm" />}
> >
<X className="size-4" aria-hidden="true" /> <X className="size-4" aria-hidden="true" />
</DrawerClose> </DrawerClose>
+5 -4
View File
@@ -7,6 +7,7 @@ import { ObjectsTable } from "./objects-table";
import { useMediaQuery } from "../lib/use-media-query"; import { useMediaQuery } from "../lib/use-media-query";
import { useDocumentTitle } from "../lib/use-document-title"; import { useDocumentTitle } from "../lib/use-document-title";
import { useBreadcrumb } from "../shell/use-breadcrumb"; import { useBreadcrumb } from "../shell/use-breadcrumb";
import { Button } from "@/components/ui/button";
import { PageTitle } from "@/components/ui/page-title"; import { PageTitle } from "@/components/ui/page-title";
const ObjectDetailDrawer = lazy(() => const ObjectDetailDrawer = lazy(() =>
@@ -47,14 +48,14 @@ export function ObjectsPage() {
{open && ( {open && (
<div className="flex h-full flex-col overflow-hidden border-l"> <div className="flex h-full flex-col overflow-hidden border-l">
<div className="flex justify-end border-b p-2"> <div className="flex justify-end border-b p-2">
<button <Button
type="button" variant="ghost"
size="icon-sm"
onClick={closeDetail} onClick={closeDetail}
aria-label={t("actions.closeDetail")} aria-label={t("actions.closeDetail")}
className="rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground"
> >
<X className="size-4" aria-hidden="true" /> <X className="size-4" aria-hidden="true" />
</button> </Button>
</div> </div>
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
<Outlet /> <Outlet />
+1 -1
View File
@@ -20,7 +20,7 @@ export function HeaderSearch() {
<form onSubmit={onSubmit} className="hidden sm:block"> <form onSubmit={onSubmit} className="hidden sm:block">
<div className="relative"> <div className="relative">
<Search <Search
className="pointer-events-none absolute top-1/2 left-2 h-4 w-4 -translate-y-1/2 text-muted-foreground" className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 text-muted-foreground"
aria-hidden aria-hidden
/> />
<Input <Input
+4 -2
View File
@@ -13,6 +13,7 @@ import {
import type { LucideIcon } from "lucide-react"; import type { LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { focusRing } from "../lib/focus-ring";
import { Tooltip } from "@/components/ui/tooltip"; import { Tooltip } from "@/components/ui/tooltip";
import { useMediaQuery } from "@/lib/use-media-query"; import { useMediaQuery } from "@/lib/use-media-query";
import { useConfig } from "../config/config-context"; import { useConfig } from "../config/config-context";
@@ -43,7 +44,7 @@ function navLinkClass(collapsed: boolean) {
return ({ isActive }: { isActive: boolean }) => return ({ isActive }: { isActive: boolean }) =>
cn( cn(
"flex items-center gap-2 rounded-md px-2 py-1 outline-none", "flex items-center gap-2 rounded-md px-2 py-1 outline-none",
"focus-visible:ring-3 focus-visible:ring-ring/50", focusRing,
collapsed && "justify-center", collapsed && "justify-center",
isActive && "bg-accent font-medium", isActive && "bg-accent font-medium",
); );
@@ -85,7 +86,8 @@ export function Sidebar() {
title={t(collapsed ? "nav.expandSidebar" : "nav.collapseSidebar")} title={t(collapsed ? "nav.expandSidebar" : "nav.collapseSidebar")}
className={cn( className={cn(
"flex items-center justify-center rounded-md p-1 outline-none", "flex items-center justify-center rounded-md p-1 outline-none",
"hover:bg-accent focus-visible:ring-3 focus-visible:ring-ring/50", "hover:bg-accent",
focusRing,
"disabled:pointer-events-none disabled:opacity-50", "disabled:pointer-events-none disabled:opacity-50",
)} )}
> >
+1 -1
View File
@@ -36,7 +36,7 @@ export function ThemeSwitch() {
: "text-muted-foreground hover:text-foreground", : "text-muted-foreground hover:text-foreground",
)} )}
> >
<Icon className="h-4 w-4" aria-hidden /> <Icon className="size-4" aria-hidden />
</button> </button>
); );
})} })}
+1 -1
View File
@@ -24,7 +24,7 @@ export function UserMenu() {
<MenuTrigger <MenuTrigger
render={ render={
<Button variant="ghost" size="sm" className="max-w-44"> <Button variant="ghost" size="sm" className="max-w-44">
<CircleUser className="h-4 w-4" aria-hidden /> <CircleUser className="size-4" aria-hidden />
<span className="truncate">{me.email}</span> <span className="truncate">{me.email}</span>
</Button> </Button>
} }