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:
@@ -7,6 +7,7 @@ import { useConfig } from "../config/config-context";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { PageTitle } from "@/components/ui/page-title";
|
||||
|
||||
/** Accept only a single-leading-slash local path; reject protocol-relative
|
||||
* ("//host") and absolute URLs to avoid an open redirect. */
|
||||
@@ -46,7 +47,7 @@ export function LoginPage() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center p-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 && (
|
||||
<p className="text-sm text-muted-foreground">{t("auth.sessionExpired")}</p>
|
||||
)}
|
||||
|
||||
@@ -96,9 +96,9 @@ export function FieldList({
|
||||
>
|
||||
<span className="font-medium">{labelText(def.labels, lang)}</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}`)}
|
||||
</span>
|
||||
</Badge>
|
||||
{def.required && (
|
||||
<span
|
||||
className="text-xs text-destructive"
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
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
|
||||
@@ -30,7 +31,7 @@ export function ObjectDetailDrawer({
|
||||
<div className="flex justify-end border-b p-2">
|
||||
<DrawerClose
|
||||
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" />
|
||||
</DrawerClose>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ObjectsTable } from "./objects-table";
|
||||
import { useMediaQuery } from "../lib/use-media-query";
|
||||
import { useDocumentTitle } from "../lib/use-document-title";
|
||||
import { useBreadcrumb } from "../shell/use-breadcrumb";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PageTitle } from "@/components/ui/page-title";
|
||||
|
||||
const ObjectDetailDrawer = lazy(() =>
|
||||
@@ -47,14 +48,14 @@ export function ObjectsPage() {
|
||||
{open && (
|
||||
<div className="flex h-full flex-col overflow-hidden border-l">
|
||||
<div className="flex justify-end border-b p-2">
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={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" />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto">
|
||||
<Outlet />
|
||||
|
||||
@@ -20,7 +20,7 @@ export function HeaderSearch() {
|
||||
<form onSubmit={onSubmit} className="hidden sm:block">
|
||||
<div className="relative">
|
||||
<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
|
||||
/>
|
||||
<Input
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { focusRing } from "../lib/focus-ring";
|
||||
import { Tooltip } from "@/components/ui/tooltip";
|
||||
import { useMediaQuery } from "@/lib/use-media-query";
|
||||
import { useConfig } from "../config/config-context";
|
||||
@@ -43,7 +44,7 @@ function navLinkClass(collapsed: boolean) {
|
||||
return ({ isActive }: { isActive: boolean }) =>
|
||||
cn(
|
||||
"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",
|
||||
isActive && "bg-accent font-medium",
|
||||
);
|
||||
@@ -85,7 +86,8 @@ export function Sidebar() {
|
||||
title={t(collapsed ? "nav.expandSidebar" : "nav.collapseSidebar")}
|
||||
className={cn(
|
||||
"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",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -36,7 +36,7 @@ export function ThemeSwitch() {
|
||||
: "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
<Icon className="h-4 w-4" aria-hidden />
|
||||
<Icon className="size-4" aria-hidden />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -24,7 +24,7 @@ export function UserMenu() {
|
||||
<MenuTrigger
|
||||
render={
|
||||
<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>
|
||||
</Button>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user