feat(ui): topbar primitives — IconButton, SegmentedControl, Avatar
Adds the shared primitives the planner topbar redesign needs, on the design-system tokens: - IconButton — icon-only button (undo/redo), ghost/secondary variants, requires an accessible label. - SegmentedControl — generic single-select toggle (color mode Plain/Elevation/Surface), radiogroup semantics. - Avatar — colored initial circle for participants, per-user color with an accent fallback. Co-located jsdom unit tests for each (roles, aria-checked, onChange, disabled, color fallback). Extends the dev-only /dev/ui gallery with sections for all three. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c9ad475e59
commit
1bd5f18a33
8 changed files with 371 additions and 2 deletions
50
packages/ui/src/IconButton.tsx
Normal file
50
packages/ui/src/IconButton.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import type { ButtonHTMLAttributes } from "react";
|
||||
import { cn } from "./cn.ts";
|
||||
|
||||
export type IconButtonVariant = "ghost" | "secondary";
|
||||
export type IconButtonSize = "sm" | "md";
|
||||
|
||||
export interface IconButtonProps
|
||||
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "aria-label"> {
|
||||
/** Required accessible name — the button has no visible text. */
|
||||
label: string;
|
||||
variant?: IconButtonVariant;
|
||||
size?: IconButtonSize;
|
||||
}
|
||||
|
||||
const base =
|
||||
"inline-flex items-center justify-center rounded-md transition-colors " +
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent " +
|
||||
"focus-visible:ring-offset-1 focus-visible:ring-offset-bg " +
|
||||
"disabled:pointer-events-none disabled:opacity-30";
|
||||
|
||||
const variants: Record<IconButtonVariant, string> = {
|
||||
ghost: "text-text-md hover:bg-bg-subtle hover:text-text-hi",
|
||||
secondary:
|
||||
"border border-border bg-bg-raised text-text-hi hover:bg-bg-subtle",
|
||||
};
|
||||
|
||||
const sizes: Record<IconButtonSize, string> = {
|
||||
sm: "h-7 w-7",
|
||||
md: "h-9 w-9",
|
||||
};
|
||||
|
||||
export function IconButton({
|
||||
label,
|
||||
variant = "ghost",
|
||||
size = "md",
|
||||
className,
|
||||
type = "button",
|
||||
title,
|
||||
...props
|
||||
}: IconButtonProps) {
|
||||
return (
|
||||
<button
|
||||
type={type}
|
||||
aria-label={label}
|
||||
title={title ?? label}
|
||||
className={cn(base, variants[variant], sizes[size], className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue