feat: full shadcn/ui component system with consistent typography
New components: - ui/button.tsx — Button with primary/accent/surface/ghost/destructive variants - ui/input.tsx — Input + Textarea with focus ring and CSS var theming - ui/label.tsx — Radix Label with peer-disabled support - ui/separator.tsx — Radix Separator - ui/tooltip.tsx — Radix Tooltip with themed content - ui/dialog.tsx — Radix Dialog replacing custom modal implementations - ui/table.tsx — Table/TableHeader/TableBody/TableRow/TableHead/TableCell - ui/typography.tsx — PageTitle/SectionHeading/Body/Muted/Caption/MonoCaption Wired throughout all components: - ConfirmDialog + FormModal migrated to Radix Dialog (focus trap, Escape, ARIA) - All raw <button> → Button, <input>/<textarea> → Input/Textarea - All repeated text patterns → typography components - All hardcoded hex/rgba strings → COLOR constants
This commit is contained in:
110
src/components/ui/typography.tsx
Normal file
110
src/components/ui/typography.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type AsChild<T extends React.ElementType> = {
|
||||
as?: T;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
type Props<T extends React.ElementType> = AsChild<T> &
|
||||
Omit<React.ComponentPropsWithoutRef<T>, keyof AsChild<T>>;
|
||||
|
||||
export function PageTitle<T extends React.ElementType = "h1">({
|
||||
as,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: Props<T>) {
|
||||
const Tag = (as ?? "h1") as React.ElementType;
|
||||
return (
|
||||
<Tag
|
||||
className={cn("text-xl font-semibold tracking-tight", className)}
|
||||
style={{ color: "var(--text-1)" }}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
export function SectionHeading<T extends React.ElementType = "h2">({
|
||||
as,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: Props<T>) {
|
||||
const Tag = (as ?? "h2") as React.ElementType;
|
||||
return (
|
||||
<Tag
|
||||
className={cn("text-sm font-medium mb-3", className)}
|
||||
style={{ color: "var(--text-1)" }}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
export function Body<T extends React.ElementType = "p">({
|
||||
as,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: Props<T>) {
|
||||
const Tag = (as ?? "p") as React.ElementType;
|
||||
return (
|
||||
<Tag
|
||||
className={cn("text-sm leading-relaxed", className)}
|
||||
style={{ color: "var(--text-2)" }}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
export function Muted<T extends React.ElementType = "p">({
|
||||
as,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: Props<T>) {
|
||||
const Tag = (as ?? "p") as React.ElementType;
|
||||
return (
|
||||
<Tag className={cn("text-sm", className)} style={{ color: "var(--text-3)" }} {...rest}>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
export function Caption<T extends React.ElementType = "span">({
|
||||
as,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: Props<T>) {
|
||||
const Tag = (as ?? "span") as React.ElementType;
|
||||
return (
|
||||
<Tag className={cn("text-xs", className)} style={{ color: "var(--text-4)" }} {...rest}>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
export function MonoCaption<T extends React.ElementType = "span">({
|
||||
as,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: Props<T>) {
|
||||
const Tag = (as ?? "span") as React.ElementType;
|
||||
return (
|
||||
<Tag
|
||||
className={cn("text-xs font-mono", className)}
|
||||
style={{ color: "var(--text-4)" }}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user