diff --git a/src/renderer/components/ui.tsx b/src/renderer/components/ui.tsx deleted file mode 100644 index 642d30dc..00000000 --- a/src/renderer/components/ui.tsx +++ /dev/null @@ -1,5 +0,0 @@ -// Barrel re-export — all consumers of "../components/ui" continue to work unchanged. -export { Badge, SectionButton, Panel, Field, Input, TextArea, Select, ActionButton } from "./ui/primitives"; -export { StatusPill, InlineNotice, ConfirmDialog, EmptyState } from "./ui/feedback"; -export { Modal, ListFrame, ListRow } from "./ui/layout"; -export { useFocusTrap, useReducedMotion, ErrorBoundary } from "./ui/hooks"; diff --git a/src/renderer/components/ui/index.ts b/src/renderer/components/ui/index.ts index 866fc9d0..b419a648 100644 --- a/src/renderer/components/ui/index.ts +++ b/src/renderer/components/ui/index.ts @@ -1,4 +1,4 @@ -export { Badge, SectionButton, Panel, Field, Input, TextArea, Select, ActionButton } from "./primitives"; +export { Badge, SectionButton, Panel, Field, Input, TextArea, Select, ActionButton, Spinner, Eyebrow, Card } from "./primitives"; export { StatusPill, InlineNotice, ConfirmDialog, EmptyState } from "./feedback"; export { Modal, ListFrame, ListRow } from "./layout"; export { useFocusTrap, useReducedMotion, ErrorBoundary } from "./hooks"; diff --git a/src/renderer/components/ui/primitives.tsx b/src/renderer/components/ui/primitives.tsx index b763f3bb..715c8a80 100644 --- a/src/renderer/components/ui/primitives.tsx +++ b/src/renderer/components/ui/primitives.tsx @@ -1,4 +1,76 @@ -import { type ElementType } from "react"; +import { type ElementType, type ReactNode as ReactNodeType } from "react"; + +/* ------------------------------------------------------------------ */ +/* Spinner */ +/* ------------------------------------------------------------------ */ + +export function Spinner({ size = 16, className = "" }: { size?: number; className?: string }) { + return ( + + ); +} + +/* ------------------------------------------------------------------ */ +/* Eyebrow */ +/* ------------------------------------------------------------------ */ + +export function Eyebrow({ + children, + tone = "muted", + className = "", +}: { + children: ReactNodeType; + tone?: "muted" | "primary" | "warning" | "destructive" | "success"; + className?: string; +}) { + const toneClass = + tone === "primary" + ? "text-primary" + : tone === "warning" + ? "text-warning" + : tone === "destructive" + ? "text-destructive" + : tone === "success" + ? "text-success" + : "text-muted-foreground"; + return ( +
+ {children} +
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Card */ +/* ------------------------------------------------------------------ */ + +export function Card({ + children, + className = "", + padded = true, + as: Tag = "div", +}: { + children: ReactNodeType; + className?: string; + padded?: boolean; + as?: "div" | "section" | "article"; +}) { + const padding = padded ? "p-4" : ""; + return ( + + {children} + + ); +} /* ------------------------------------------------------------------ */ /* Badge */ @@ -290,13 +362,19 @@ export function ActionButton({ tone = "default", loading = false, disabled = false, + iconOnly = false, + type = "button", + ariaLabel, }: { label: string; - onClick: () => void; + onClick?: () => void; icon?: ElementType; tone?: "default" | "accent" | "danger"; loading?: boolean; disabled?: boolean; + iconOnly?: boolean; + type?: "button" | "submit" | "reset"; + ariaLabel?: string; }) { const isDisabled = loading || disabled; const toneClass = @@ -306,22 +384,26 @@ export function ActionButton({ ? "border-transparent bg-destructive text-primary-foreground shadow-[0_1px_3px_rgba(220,53,69,0.3)] hover:brightness-90" : "border-border bg-card text-foreground hover:bg-muted"; + const sizing = iconOnly + ? "h-10 w-10 p-0 justify-center" + : "gap-2 px-4 py-2.5"; + return ( ); }