diff --git a/docs/plans/2026-04-22-shadcn-components-stories-design.md b/docs/plans/2026-04-22-shadcn-components-stories-design.md new file mode 100644 index 0000000..16b038a --- /dev/null +++ b/docs/plans/2026-04-22-shadcn-components-stories-design.md @@ -0,0 +1,78 @@ +# Design: Install All shadcn Components with Tests & Stories + +**Date:** 2026-04-22 +**Status:** Approved + +## Goal + +Install all ~45 remaining shadcn components into `packages/ui`, write interaction tests for ~30 interactive components, and create Storybook stories for all components organized by MUI-style categories. + +## Component Installation + +Install into `packages/ui/src/components/ui/` using `npx shadcn add`. + +**Already installed (10):** accordion, alert, button, checkbox, input, label, radio-group, select, switch, textarea + +**To install (45):** alert-dialog, aspect-ratio, avatar, badge, breadcrumb, button-group, calendar, card, carousel, chart, collapsible, combobox, command, context-menu, dialog, direction, drawer, dropdown-menu, empty, field, form, hover-card, input-group, input-otp, item, kbd, menubar, native-select, navigation-menu, pagination, popover, progress, resizable, scroll-area, separator, sheet, sidebar, skeleton, slider, sonner, spinner, table, tabs, toggle, toggle-group, tooltip + +## MUI Category Mapping + +| Category | Components | +|---|---| +| **Inputs** | Button, ButtonGroup, Calendar, Checkbox, Combobox, Field, Form, Input, InputGroup, InputOTP, NativeSelect, RadioGroup, Select, Slider, Switch, Textarea, Toggle, ToggleGroup | +| **Data Display** | Avatar, Badge, Card, Carousel, Chart, Kbd, Table, Tooltip, Icon, Label, Type | +| **Feedback** | Alert, AlertDialog, Dialog, Drawer, Empty, Progress, Sheet, Skeleton, Sonner, Spinner | +| **Navigation** | Breadcrumb, ContextMenu, DropdownMenu, Menubar, NavigationMenu, Pagination, Sidebar, Tabs | +| **Surfaces** | Accordion, Collapsible, Command, HoverCard, Popover | +| **Layout** | AspectRatio, Direction, Item, Resizable, ScrollArea, Separator | +| **Custom** | IconButton, SectionTitle | + +## Stories + +All stories in `apps/storybook/src/stories/` with title format `Category/ComponentName`. + +Each story follows the existing pattern: +- `Meta` with `component`, `title: "Category/Name"`, `tags: ["autodocs"]` +- Default story + variant stories showing key props/states +- Interactive args where applicable + +**Story file structure:** +``` +apps/storybook/src/stories/ + Inputs/ # 18 stories + DataDisplay/ # 11 stories + Feedback/ # 10 stories + Navigation/ # 8 stories + Surfaces/ # 5 stories + Layout/ # 6 stories + Custom/ # 2 stories +``` + +## Testing + +Tests for ~30 interactive components using `@testing-library/react` + `@testing-library/user-event` + `vitest` with `happy-dom`. + +**Components to test:** +AlertDialog, Avatar, Breadcrumb, ButtonGroup, Calendar, Checkbox, Collapsible, Combobox, Command, ContextMenu, Dialog, Drawer, DropdownMenu, Form/Field, HoverCard, Input, InputOTP, Menubar, NavigationMenu, NativeSelect, Pagination, Popover, RadioGroup, Sheet, Slider, Switch, Tabs, Textarea, Toggle/ToggleGroup, Tooltip + +**Skipping tests for (visual/layout only):** Alert, AspectRatio, Badge, Card, Carousel, Chart, Direction, Empty, Item, Kbd, Label, Progress, Resizable, ScrollArea, Separator, Sidebar, Skeleton, Sonner, Spinner, Table + +## Dependencies to Add + +- `date-fns` + `react-day-picker` (Calendar) +- `recharts` (Chart) +- `react-hook-form` + `@hookform/resolvers` + `zod` (Form) +- `sonner` (Sonner/toast) +- `embla-carousel-react` (Carousel) +- `input-otp` (InputOTP) +- `react-resizable-panels` (Resizable) +- `vaul` (Drawer) +- Additional `@radix-ui/*` packages as needed + +## No Changes To + +- Build config (Vite, Turborepo) +- Tailwind setup (v4 auto-scan handles new components) +- Storybook config (story glob already matches) +- Existing components or tests +- Design tokens or styling approach (radix-nova, oklch) diff --git a/docs/plans/2026-04-22-shadcn-components-stories.md b/docs/plans/2026-04-22-shadcn-components-stories.md new file mode 100644 index 0000000..ca5196e --- /dev/null +++ b/docs/plans/2026-04-22-shadcn-components-stories.md @@ -0,0 +1,571 @@ +# shadcn Components, Tests & Stories Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Install all remaining shadcn components, write interaction tests for interactive components, and create Storybook stories for every component organized by MUI-style categories. + +**Architecture:** Components live in `packages/ui/src/components/ui/`, exported from `packages/ui/src/index.ts`. Stories live in `apps/storybook/src/stories//`. Tests co-locate with components as `.test.tsx`. Stories follow flat directory structure under category folders. + +**Tech Stack:** shadcn v4.3.0 (radix-nova style), Vitest + Testing Library, Storybook 10.3.5, Tailwind v4, Bun + +--- + +## Wave 1: Install All shadcn Components + +### Task 1: Install shadcn components (batch) + +**Why:** All 45 remaining components need to be installed. shadcn CLI handles file generation and dependency installation. + +**Step 1: Install all components in one batch** + +Run from `packages/ui/`: + +```bash +cd packages/ui && npx shadcn@4.3.0 add alert-dialog aspect-ratio avatar badge breadcrumb button-group calendar card carousel chart checkbox collapsible combobox command context-menu dialog direction drawer dropdown-menu empty field form hover-card input-group input-otp item kbd menubar native-select navigation-menu pagination popover progress resizable scroll-area separator sheet sidebar skeleton slider sonner spinner table tabs toggle toggle-group tooltip --overwrite +``` + +Note: Use `--overwrite` to update any already-installed components to latest radix-nova style. This may update existing files like `checkbox.tsx`. + +**Step 2: Verify all component files exist** + +```bash +ls packages/ui/src/components/ui/*.tsx | wc -l +``` + +Expected: ~55 files (10 existing + 45 new) + +**Step 3: Install any missing peer dependencies** + +Check the shadcn output for any peer dependency warnings and install them: + +```bash +cd packages/ui && bun install +``` + +**Step 4: Commit** + +```bash +git add packages/ui/src/components/ui/ packages/ui/package.json bun.lock +git commit -m "feat(ui): install all remaining shadcn components" +``` + +--- + +### Task 2: Export all new components from index.ts + +**Files:** +- Modify: `packages/ui/src/index.ts` + +**Step 1: Update index.ts with all exports** + +Read each new component file to identify its exports (component names, types, sub-components). Then update `packages/ui/src/index.ts` to export everything, grouped by MUI category comments. + +The export structure should follow this pattern (expand for every component): + +```typescript +// ─── Inputs ────────────────────────────────────────────── +export { Button, type ButtonProps, buttonVariants } from "./components/ui/button"; +export { ButtonGroup } from "./components/ui/button-group"; +export { Calendar } from "./components/ui/calendar"; +export { Checkbox } from "./components/ui/checkbox"; +// ... (read each file for exact export names) +export { Combobox } from "./components/ui/combobox"; +export { Field } from "./components/ui/field"; +export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "./components/ui/form"; +export { Input } from "./components/ui/input"; +export { InputGroup } from "./components/ui/input-group"; +export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "./components/ui/input-otp"; +export { NativeSelect } from "./components/ui/native-select"; +export { RadioGroup, RadioGroupItem } from "./components/ui/radio-group"; +export { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./components/ui/select"; +export { Slider } from "./components/ui/slider"; +export { Switch } from "./components/ui/switch"; +export { Textarea } from "./components/ui/textarea"; +export { Toggle, toggleVariants } from "./components/ui/toggle"; +export { ToggleGroup, ToggleGroupItem } from "./components/ui/toggle-group"; + +// ─── Data Display ──────────────────────────────────────── +export { Avatar, AvatarFallback, AvatarImage } from "./components/ui/avatar"; +export { Badge, badgeVariants } from "./components/ui/badge"; +export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./components/ui/card"; +export { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "./components/ui/carousel"; +export { Chart /* read file for exact exports */ } from "./components/ui/chart"; +export { Kbd } from "./components/ui/kbd"; +export { Label } from "./components/ui/label"; +export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "./components/ui/table"; +export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./components/ui/tooltip"; + +// ─── Feedback ──────────────────────────────────────────── +export { Alert, AlertAction, AlertDescription, AlertTitle } from "./components/ui/alert"; +export { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "./components/ui/alert-dialog"; +export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "./components/ui/dialog"; +export { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from "./components/ui/drawer"; +export { Empty } from "./components/ui/empty"; +export { Progress } from "./components/ui/progress"; +export { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "./components/ui/sheet"; +export { Skeleton } from "./components/ui/skeleton"; +export { Toaster } from "./components/ui/sonner"; +export { Spinner } from "./components/ui/spinner"; + +// ─── Navigation ────────────────────────────────────────── +export { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "./components/ui/breadcrumb"; +export { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from "./components/ui/context-menu"; +export { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./components/ui/dropdown-menu"; +export { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarTrigger } from "./components/ui/menubar"; +export { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger } from "./components/ui/navigation-menu"; +export { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from "./components/ui/pagination"; +export { Sidebar } from "./components/ui/sidebar"; +export { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs"; + +// ─── Surfaces ──────────────────────────────────────────── +export { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./components/ui/accordion"; +export { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./components/ui/collapsible"; +export { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "./components/ui/command"; +export { HoverCard, HoverCardContent, HoverCardTrigger } from "./components/ui/hover-card"; +export { Popover, PopoverContent, PopoverTrigger } from "./components/ui/popover"; + +// ─── Layout ────────────────────────────────────────────── +export { AspectRatio } from "./components/ui/aspect-ratio"; +export { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "./components/ui/resizable"; +export { ScrollArea, ScrollBar } from "./components/ui/scroll-area"; +export { Separator } from "./components/ui/separator"; + +// ─── Custom Figma Components ───────────────────────────── +export { ICONS, Icon, type IconProps, registerIcons, type StaticIconName, type StaticIconNameMap } from "./components/figma/icon"; +export { IconButton } from "./components/figma/icon-button"; +export { SectionTitle } from "./components/figma/section-title"; +export { Type } from "./components/figma/type"; + +// ─── Utilities ─────────────────────────────────────────── +export { cn } from "./lib/utils"; +``` + +**Important:** The exact export names above are approximations. Read each generated component file to get the actual exported names. shadcn components export different things depending on style. + +**Step 2: Verify TypeScript compiles** + +```bash +cd packages/ui && bunx tsc --noEmit +``` + +Expected: No errors + +**Step 3: Commit** + +```bash +git add packages/ui/src/index.ts +git commit -m "feat(ui): export all shadcn components from index.ts" +``` + +--- + +## Wave 2: Move Existing Stories to Category Folders + +### Task 3: Reorganize existing stories into MUI category folders + +**Files:** +- Move: `apps/storybook/src/stories/*.stories.tsx` → `apps/storybook/src/stories//` + +**Step 1: Create category directories** + +```bash +mkdir -p apps/storybook/src/stories/{Inputs,DataDisplay,Feedback,Navigation,Surfaces,Layout,Custom} +``` + +**Step 2: Move existing stories to their categories** + +```bash +# Inputs +mv apps/storybook/src/stories/Button.stories.tsx apps/storybook/src/stories/Inputs/ +mv apps/storybook/src/stories/Checkbox.stories.tsx apps/storybook/src/stories/Inputs/ +mv apps/storybook/src/stories/Input.stories.tsx apps/storybook/src/stories/Inputs/ +mv apps/storybook/src/stories/RadioGroup.stories.tsx apps/storybook/src/stories/Inputs/ +mv apps/storybook/src/stories/Select.stories.tsx apps/storybook/src/stories/Inputs/ +mv apps/storybook/src/stories/Switch.stories.tsx apps/storybook/src/stories/Inputs/ +mv apps/storybook/src/stories/Textarea.stories.tsx apps/storybook/src/stories/Inputs/ + +# Data Display +mv apps/storybook/src/stories/Icon.stories.tsx apps/storybook/src/stories/DataDisplay/ +mv apps/storybook/src/stories/Label.stories.tsx apps/storybook/src/stories/DataDisplay/ +mv apps/storybook/src/stories/Type.stories.tsx apps/storybook/src/stories/DataDisplay/ +mv apps/storybook/src/stories/Accordion.stories.tsx apps/storybook/src/stories/Surfaces/ + +# Feedback +mv apps/storybook/src/stories/Alert.stories.tsx apps/storybook/src/stories/Feedback/ + +# Custom +mv apps/storybook/src/stories/IconButton.stories.tsx apps/storybook/src/stories/Custom/ +mv apps/storybook/src/stories/SectionTitle.stories.tsx apps/storybook/src/stories/Custom/ +``` + +**Step 3: Verify storybook still discovers stories** + +The glob `../src/stories/**/*.stories.@(ts|tsx)` already handles nested directories. + +```bash +cd apps/storybook && bun run storybook --ci --smoke-test 2>&1 || echo "Smoke test not available, verify manually" +``` + +**Step 4: Commit** + +```bash +git add apps/storybook/src/stories/ +git commit -m "chore(storybook): move existing stories into MUI category folders" +``` + +--- + +## Wave 3: Write Stories for New Components + +Each task below creates stories for one MUI category. Stories follow the established pattern: + +```typescript +import { ComponentName } from "@repo/ui"; +import type { Meta, StoryObj } from "@storybook/react"; + +const meta = { + component: ComponentName, + title: "Category/ComponentName", + tags: ["autodocs"], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { /* args or render */ }; +``` + +### Task 4: Write Inputs stories (new components only) + +**Files to create** in `apps/storybook/src/stories/Inputs/`: +- `ButtonGroup.stories.tsx` +- `Calendar.stories.tsx` +- `Combobox.stories.tsx` +- `Field.stories.tsx` +- `Form.stories.tsx` +- `InputGroup.stories.tsx` +- `InputOTP.stories.tsx` +- `NativeSelect.stories.tsx` +- `Slider.stories.tsx` +- `Toggle.stories.tsx` +- `ToggleGroup.stories.tsx` + +Each story should demonstrate: +- Default state with minimal args +- Key variant/size stories +- Disabled state where applicable +- For complex components (Calendar, Combobox, Form): use `render` function with state management + +**Step 1:** Read each component file in `packages/ui/src/components/ui/` to understand its props, sub-components, and variants before writing the story. + +**Step 2:** Write each story file. + +**Step 3: Commit** + +```bash +git add apps/storybook/src/stories/Inputs/ +git commit -m "feat(storybook): add Inputs category stories for new shadcn components" +``` + +--- + +### Task 5: Write Data Display stories (new components only) + +**Files to create** in `apps/storybook/src/stories/DataDisplay/`: +- `Avatar.stories.tsx` +- `Badge.stories.tsx` +- `Card.stories.tsx` +- `Carousel.stories.tsx` +- `Chart.stories.tsx` +- `Kbd.stories.tsx` +- `Table.stories.tsx` +- `Tooltip.stories.tsx` + +Each story should demonstrate the component's primary use case plus variants. + +**Step 1:** Read each component file for props/exports. +**Step 2:** Write story files. +**Step 3: Commit** + +```bash +git add apps/storybook/src/stories/DataDisplay/ +git commit -m "feat(storybook): add Data Display category stories" +``` + +--- + +### Task 6: Write Feedback stories (new components only) + +**Files to create** in `apps/storybook/src/stories/Feedback/`: +- `AlertDialog.stories.tsx` +- `Dialog.stories.tsx` +- `Drawer.stories.tsx` +- `Empty.stories.tsx` +- `Progress.stories.tsx` +- `Sheet.stories.tsx` +- `Skeleton.stories.tsx` +- `Sonner.stories.tsx` +- `Spinner.stories.tsx` + +Dialog/Drawer/Sheet stories need a trigger button in a `render` function. + +**Step 1:** Read each component file. +**Step 2:** Write story files. +**Step 3: Commit** + +```bash +git add apps/storybook/src/stories/Feedback/ +git commit -m "feat(storybook): add Feedback category stories" +``` + +--- + +### Task 7: Write Navigation stories + +**Files to create** in `apps/storybook/src/stories/Navigation/`: +- `Breadcrumb.stories.tsx` +- `ContextMenu.stories.tsx` +- `DropdownMenu.stories.tsx` +- `Menubar.stories.tsx` +- `NavigationMenu.stories.tsx` +- `Pagination.stories.tsx` +- `Sidebar.stories.tsx` +- `Tabs.stories.tsx` + +Menu components need trigger elements in `render` functions. Sidebar needs a layout wrapper. + +**Step 1:** Read each component file. +**Step 2:** Write story files. +**Step 3: Commit** + +```bash +git add apps/storybook/src/stories/Navigation/ +git commit -m "feat(storybook): add Navigation category stories" +``` + +--- + +### Task 8: Write Surfaces stories (new components only) + +**Files to create** in `apps/storybook/src/stories/Surfaces/`: +- `Collapsible.stories.tsx` +- `Command.stories.tsx` +- `HoverCard.stories.tsx` +- `Popover.stories.tsx` + +Note: Accordion story already exists, just moved. + +**Step 1:** Read each component file. +**Step 2:** Write story files. +**Step 3: Commit** + +```bash +git add apps/storybook/src/stories/Surfaces/ +git commit -m "feat(storybook): add Surfaces category stories" +``` + +--- + +### Task 9: Write Layout stories + +**Files to create** in `apps/storybook/src/stories/Layout/`: +- `AspectRatio.stories.tsx` +- `Direction.stories.tsx` +- `Item.stories.tsx` +- `Resizable.stories.tsx` +- `ScrollArea.stories.tsx` +- `Separator.stories.tsx` + +**Step 1:** Read each component file. +**Step 2:** Write story files. +**Step 3: Commit** + +```bash +git add apps/storybook/src/stories/Layout/ +git commit -m "feat(storybook): add Layout category stories" +``` + +--- + +## Wave 4: Write Interaction Tests + +Tests co-locate with components in `packages/ui/src/components/ui/`. Follow existing pattern: + +```typescript +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; +import { ComponentName } from "./component-name"; + +describe("ComponentName interaction tests", () => { + it("specific behavior", async () => { + // ... + }); +}); +``` + +### Task 10: Write tests for existing untested components + +**Files to create** in `packages/ui/src/components/ui/`: +- `checkbox.test.tsx` — check/uncheck, disabled +- `input.test.tsx` — type text, disabled, placeholder +- `radio-group.test.tsx` — select option, keyboard +- `switch.test.tsx` — toggle on/off, disabled +- `textarea.test.tsx` — type text, disabled + +**Step 1:** Read each component to understand its DOM structure and props. +**Step 2:** Write test files. +**Step 3: Run tests** + +```bash +cd packages/ui && bun run test +``` + +Expected: All pass + +**Step 4: Commit** + +```bash +git add packages/ui/src/components/ui/*.test.tsx +git commit -m "test(ui): add interaction tests for existing untested components" +``` + +--- + +### Task 11: Write tests for dialog/overlay components + +**Files to create** in `packages/ui/src/components/ui/`: +- `alert-dialog.test.tsx` — open, confirm/cancel, close on escape +- `dialog.test.tsx` — open/close, overlay click, escape +- `drawer.test.tsx` — open/close +- `sheet.test.tsx` — open/close, side variants +- `popover.test.tsx` — open/close trigger +- `hover-card.test.tsx` — hover trigger, content display + +**Step 1:** Read each component. +**Step 2:** Write tests. These components need a trigger to open — render the full component tree. +**Step 3: Run tests** + +```bash +cd packages/ui && bun run test +``` + +**Step 4: Commit** + +```bash +git add packages/ui/src/components/ui/*.test.tsx +git commit -m "test(ui): add interaction tests for dialog/overlay components" +``` + +--- + +### Task 12: Write tests for menu/navigation components + +**Files to create** in `packages/ui/src/components/ui/`: +- `context-menu.test.tsx` — right-click trigger, item selection +- `dropdown-menu.test.tsx` — open/close, item selection +- `menubar.test.tsx` — open menus, keyboard nav +- `navigation-menu.test.tsx` — open/close submenus +- `tabs.test.tsx` — tab switching, keyboard +- `pagination.test.tsx` — page clicks + +**Step 1:** Read each component. +**Step 2:** Write tests. +**Step 3: Run tests and commit** + +```bash +cd packages/ui && bun run test +git add packages/ui/src/components/ui/*.test.tsx +git commit -m "test(ui): add interaction tests for menu/navigation components" +``` + +--- + +### Task 13: Write tests for input-family components + +**Files to create** in `packages/ui/src/components/ui/`: +- `slider.test.tsx` — keyboard step, min/max +- `toggle.test.tsx` — press/unpress +- `toggle-group.test.tsx` — single/multi select +- `combobox.test.tsx` — search, select +- `input-otp.test.tsx` — digit entry +- `calendar.test.tsx` — date selection, month navigation +- `native-select.test.tsx` — selection change +- `form.test.tsx` — validation, submit, error display +- `command.test.tsx` — search filtering, item selection + +**Step 1:** Read each component. +**Step 2:** Write tests. +**Step 3: Run tests and commit** + +```bash +cd packages/ui && bun run test +git add packages/ui/src/components/ui/*.test.tsx +git commit -m "test(ui): add interaction tests for input-family components" +``` + +--- + +### Task 14: Write tests for remaining interactive components + +**Files to create** in `packages/ui/src/components/ui/`: +- `avatar.test.tsx` — fallback rendering +- `breadcrumb.test.tsx` — render links +- `button-group.test.tsx` — render children +- `collapsible.test.tsx` — open/close +- `tooltip.test.tsx` — hover show/hide + +**Step 1:** Read each component. +**Step 2:** Write tests. +**Step 3: Run tests and commit** + +```bash +cd packages/ui && bun run test +git add packages/ui/src/components/ui/*.test.tsx +git commit -m "test(ui): add interaction tests for remaining components" +``` + +--- + +## Wave 5: Verification + +### Task 15: Run full test suite and verify storybook builds + +**Step 1: Run all tests** + +```bash +bun run test +``` + +Expected: All pass + +**Step 2: Run lint** + +```bash +bun run lint +``` + +Expected: No errors (fix any biome issues) + +**Step 3: Build storybook** + +```bash +turbo run build-storybook +``` + +Expected: Builds successfully + +**Step 4: Verify TypeScript** + +```bash +cd packages/ui && bunx tsc --noEmit +``` + +Expected: No errors + +**Step 5: Final commit if any fixes needed** + +```bash +git add -A +git commit -m "fix: resolve lint and type errors from component additions" +``` diff --git a/docs/plans/2026-04-23-ui3-theming-design.md b/docs/plans/2026-04-23-ui3-theming-design.md new file mode 100644 index 0000000..5709ebe --- /dev/null +++ b/docs/plans/2026-04-23-ui3-theming-design.md @@ -0,0 +1,174 @@ +# Figma UI3 Theming for shadcn Components + +**Date:** 2026-04-23 +**Status:** Approved +**Approach:** Token swap + data-slot CSS overrides (Approach B) + +## Goal + +Style all 45+ shadcn components to match Figma's UI3 design language. Both light and dark themes. Primarily through CSS variables and `data-slot` selectors in `styles.css` — avoid modifying component `.tsx` files. + +## Figma UI3 Design Language + +- **Brand blue** (#0C8CE9 / `oklch(0.62 0.19 245)`) +- **Inter font** (already configured) +- **Compact controls** — tight spacing, 32px input height +- **Rounded corners** — 8px base radius +- **Subtle depth** — inner shadows on inputs, soft drop shadows on popovers +- **Clean neutrals** — warm grays, not pure white/black +- **Blue-tinted hover** — accent backgrounds have a slight blue tint + +## 1. Color Token Changes + +### Light Theme (`:root`) + +| Token | Old (OKLCH) | New (OKLCH) | Rationale | +|-------|-------------|-------------|-----------| +| `--background` | `1 0 0` | `0.985 0 0` | Warm off-white like UI3 canvas | +| `--foreground` | `0.26 0 0` | `0.23 0 0` | Slightly darker text | +| `--primary` | `0.68 0.18 221` | `0.62 0.19 245` | Figma brand blue | +| `--primary-foreground` | `1 0 0` | `1 0 0` | No change | +| `--secondary` | `0.95 0 0` | `0.95 0 0` | No change | +| `--secondary-foreground` | `0.26 0 0` | `0.23 0 0` | Match foreground | +| `--muted` | `0.91 0 0` | `0.94 0 0` | Lighter inactive areas | +| `--muted-foreground` | `0.73 0 0` | `0.55 0 0` | Darker for readability | +| `--accent` | `0.95 0 0` | `0.93 0.01 245` | Blue-tinted hover | +| `--accent-foreground` | `0.26 0 0` | `0.23 0 0` | Match foreground | +| `--destructive` | `0.61 0.23 28` | `0.59 0.22 25` | Figma error red | +| `--border` | `0.91 0 0` | `0.90 0 0` | Subtle clean borders | +| `--input` | `0.91 0 0` | `0.90 0 0` | Match border | +| `--ring` | `0.68 0.18 221` | `0.62 0.19 245` | Match primary | +| `--radius` | `0.375rem` | `0.5rem` | UI3 uses 8px corners | +| `--card` | `1 0 0` | `1 0 0` | No change | +| `--card-foreground` | `0.26 0 0` | `0.23 0 0` | Match foreground | +| `--popover` | `1 0 0` | `1 0 0` | No change | +| `--popover-foreground` | `0.26 0 0` | `0.23 0 0` | Match foreground | + +### Dark Theme (`.dark`, `html.figma-dark`) + +| Token | Old (OKLCH) | New (OKLCH) | Rationale | +|-------|-------------|-------------|-----------| +| `--background` | `0.20 0 0` | `0.18 0 0` | Darker canvas | +| `--foreground` | `0.96 0 0` | `0.93 0 0` | Slightly softer white | +| `--primary` | `0.72 0.18 221` | `0.65 0.19 245` | UI3 blue in dark mode | +| `--primary-foreground` | `0.18 0 0` | `0.15 0 0` | Darker for contrast | +| `--secondary` | `0.30 0 0` | `0.26 0 0` | Darker secondary | +| `--secondary-foreground` | `0.96 0 0` | `0.93 0 0` | Match foreground | +| `--muted` | `0.30 0 0` | `0.25 0 0` | Darker muted | +| `--muted-foreground` | `0.75 0 0` | `0.60 0 0` | Better readability | +| `--accent` | `0.30 0 0` | `0.27 0.01 245` | Blue-tinted hover | +| `--accent-foreground` | `0.96 0 0` | `0.93 0 0` | Match foreground | +| `--destructive` | `0.66 0.23 28` | `0.62 0.22 25` | Consistent red | +| `--border` | `0.42 0 0` | `0.35 0 0` | Subtler borders | +| `--input` | `0.42 0 0` | `0.35 0 0` | Match border | +| `--ring` | `0.72 0.18 221` | `0.65 0.19 245` | Match primary | +| `--card` | `0.25 0 0` | `0.22 0 0` | Darker cards | +| `--card-foreground` | `0.96 0 0` | `0.93 0 0` | Match foreground | +| `--popover` | `0.25 0 0` | `0.22 0 0` | Match card | +| `--popover-foreground` | `0.96 0 0` | `0.93 0 0` | Match foreground | + +### New Tokens + +```css +--sidebar: oklch(0.97 0 0); /* light: panel bg */ +--sidebar-foreground: var(--foreground); +--sidebar-border: var(--border); +--chart-1: oklch(0.62 0.19 245); /* blue */ +--chart-2: oklch(0.65 0.18 160); /* teal */ +--chart-3: oklch(0.70 0.15 70); /* amber */ +--chart-4: oklch(0.60 0.20 310); /* purple */ +--chart-5: oklch(0.65 0.22 25); /* coral */ +``` + +## 2. Typography + +No font-size changes needed (already Figma-optimized 11-14px scale). + +Weight adjustments via `data-slot` CSS: +- Interactive labels (buttons, menu items, tabs, accordion triggers): `font-weight: 500` +- Body/descriptions: `font-weight: 400` (default, no change) +- Badges: `font-weight: 600` + +## 3. Component Overrides via `data-slot` CSS + +All overrides use `[data-slot="..."]` selectors. No `.tsx` modifications. + +### Buttons +- `font-weight: 500` +- Primary: subtle inner shadow `inset 0 1px 0 oklch(1 0 0 / 0.1)` +- Ghost hover: use blue-tinted `--accent` + +### Inputs & Textarea +- Inner shadow: `inset 0 1px 2px oklch(0 0 0 / 0.05)` +- Focus: stronger ring with `box-shadow` combining inner shadow + ring + +### Checkbox & Radio +- Crisper border (`1.5px`) +- Checked state: primary fill with white indicator + +### Switch +- Thumb: subtle drop shadow `0 1px 3px oklch(0 0 0 / 0.15)` + +### Select Trigger +- Match input styling (inner shadow, same height) + +### Menu Content (dropdown, context, menubar) +- Tighter padding: `py-1` on items +- `font-weight: 500` +- Backdrop blur on content +- Subtler, thinner separators + +### Dialogs, Sheets, Alert Dialogs +- Content: larger border-radius +- Softer, more spread shadow +- Overlay: slightly more transparent + +### Cards +- Ring border (`ring-1 ring-border`) instead of heavy shadow +- Clean flat look + +### Tabs +- Active trigger: bolder indicator +- List: subtle background + +### Progress +- Rounded ends on both track and indicator + +### Tooltip +- Smaller text, tighter padding +- Slight backdrop blur + +### Badge +- Tighter padding +- `font-weight: 600` + +### Skeleton +- Subtler pulse opacity range + +### Accordion +- Trigger: `font-weight: 500` + +### Sidebar +- Menu items: rounded hover with `--accent` +- Group labels: uppercase tracking + +### Breadcrumb +- Separator: muted color + +### Slider +- Track: slightly thicker +- Thumb: shadow for depth + +## 4. Constraints + +- Zero `.tsx` file modifications (target only `styles.css`) +- If a component can't be adequately styled via CSS alone, document it as a known limitation +- Both light and dark themes must be updated +- Existing tests must continue to pass (styling-only changes) + +## 5. Verification + +- `bun run test` — all tests pass (no behavior changes) +- `bun run lint` — no new warnings +- `bun run types` — no type errors +- Visual verification via Storybook (`bun run storybook`) diff --git a/docs/plans/2026-04-23-ui3-theming.md b/docs/plans/2026-04-23-ui3-theming.md new file mode 100644 index 0000000..654a328 --- /dev/null +++ b/docs/plans/2026-04-23-ui3-theming.md @@ -0,0 +1,668 @@ +# Figma UI3 Theming Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Restyle all shadcn components to match Figma UI3's design language via CSS-only changes in `styles.css`. + +**Architecture:** Update CSS custom property tokens for colors/radius/shadows, then add targeted `[data-slot]` selector overrides. Zero `.tsx` component file changes. Both light and dark themes. + +**Tech Stack:** Tailwind CSS v4, OKLCH color space, CSS custom properties, `data-slot` attribute selectors + +--- + +### Task 1: Update Light Theme Color Tokens + +**Files:** +- Modify: `packages/ui/src/styles.css:12-32` + +**Step 1: Replace the `:root` block with UI3 light theme tokens** + +```css +:root { + --background: oklch(0.985 0 0); + --foreground: oklch(0.23 0 0); + --primary: oklch(0.62 0.19 245); + --primary-foreground: oklch(1 0 0); + --secondary: oklch(0.95 0 0); + --secondary-foreground: oklch(0.23 0 0); + --muted: oklch(0.94 0 0); + --muted-foreground: oklch(0.55 0 0); + --accent: oklch(0.93 0.01 245); + --accent-foreground: oklch(0.23 0 0); + --destructive: oklch(0.59 0.22 25); + --border: oklch(0.90 0 0); + --input: oklch(0.90 0 0); + --ring: oklch(0.62 0.19 245); + --radius: 0.5rem; + --card: oklch(1 0 0); + --card-foreground: oklch(0.23 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.23 0 0); + --sidebar: oklch(0.97 0 0); + --sidebar-foreground: oklch(0.23 0 0); + --sidebar-border: oklch(0.90 0 0); + --chart-1: oklch(0.62 0.19 245); + --chart-2: oklch(0.65 0.18 160); + --chart-3: oklch(0.70 0.15 70); + --chart-4: oklch(0.60 0.20 310); + --chart-5: oklch(0.65 0.22 25); +} +``` + +**Step 2: Run tests to verify nothing breaks** + +Run: `bun run --filter @repo/ui test` +Expected: All tests pass (color tokens don't affect behavior) + +**Step 3: Commit** + +```bash +git add packages/ui/src/styles.css +git commit -m "style(ui): update light theme tokens to Figma UI3 palette" +``` + +--- + +### Task 2: Update Dark Theme Color Tokens + +**Files:** +- Modify: `packages/ui/src/styles.css:38-58` + +**Step 1: Replace the dark theme block with UI3 dark tokens** + +```css +.dark, +html.figma-dark { + --background: oklch(0.18 0 0); + --foreground: oklch(0.93 0 0); + --primary: oklch(0.65 0.19 245); + --primary-foreground: oklch(0.15 0 0); + --secondary: oklch(0.26 0 0); + --secondary-foreground: oklch(0.93 0 0); + --muted: oklch(0.25 0 0); + --muted-foreground: oklch(0.60 0 0); + --accent: oklch(0.27 0.01 245); + --accent-foreground: oklch(0.93 0 0); + --destructive: oklch(0.62 0.22 25); + --border: oklch(0.35 0 0); + --input: oklch(0.35 0 0); + --ring: oklch(0.65 0.19 245); + --card: oklch(0.22 0 0); + --card-foreground: oklch(0.93 0 0); + --popover: oklch(0.22 0 0); + --popover-foreground: oklch(0.93 0 0); + --sidebar: oklch(0.16 0 0); + --sidebar-foreground: oklch(0.93 0 0); + --sidebar-border: oklch(0.35 0 0); + --chart-1: oklch(0.65 0.19 245); + --chart-2: oklch(0.68 0.18 160); + --chart-3: oklch(0.73 0.15 70); + --chart-4: oklch(0.63 0.20 310); + --chart-5: oklch(0.68 0.22 25); +} +``` + +**Step 2: Run tests** + +Run: `bun run --filter @repo/ui test` +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add packages/ui/src/styles.css +git commit -m "style(ui): update dark theme tokens to Figma UI3 palette" +``` + +--- + +### Task 3: Add New Theme Tokens to Tailwind `@theme` Block + +**Files:** +- Modify: `packages/ui/src/styles.css:60-91` + +**Step 1: Add sidebar and chart color mappings to the `@theme inline` block** + +Add these lines inside the existing `@theme inline { ... }` block, after the `--color-popover-foreground` line: + +```css + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); +``` + +**Step 2: Run tests and type-check** + +Run: `bun run --filter @repo/ui test && bun run --filter @repo/ui types` +Expected: All pass + +**Step 3: Commit** + +```bash +git add packages/ui/src/styles.css +git commit -m "style(ui): add sidebar and chart tokens to Tailwind theme" +``` + +--- + +### Task 4: Add UI3 Component Overrides — Buttons + +**Files:** +- Modify: `packages/ui/src/styles.css` (append after `@theme inline` block) + +**Step 1: Add button overrides** + +Append to `styles.css`: + +```css +/* ============================================================ + Figma UI3 Component Overrides + Uses data-slot selectors to style shadcn components + without modifying .tsx files. + ============================================================ */ + +/* --- Buttons --- */ +[data-slot="button"] { + font-weight: 500; +} + +[data-slot="button"][data-variant="default"] { + box-shadow: inset 0 1px 0 oklch(1 0 0 / 0.1), 0 1px 2px oklch(0 0 0 / 0.05); +} + +[data-slot="button"][data-variant="outline"] { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.03); +} +``` + +**Step 2: Run tests** + +Run: `bun run --filter @repo/ui test` +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add packages/ui/src/styles.css +git commit -m "style(ui): add UI3 button overrides" +``` + +--- + +### Task 5: Add UI3 Component Overrides — Inputs, Textarea, Select + +**Files:** +- Modify: `packages/ui/src/styles.css` (append) + +**Step 1: Add input/textarea/select overrides** + +```css +/* --- Inputs & Textarea --- */ +[data-slot="input"], +[data-slot="textarea"] { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.05); +} + +[data-slot="input"]:focus, +[data-slot="textarea"]:focus { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.05), 0 0 0 3px oklch(from var(--ring) l c h / 0.2); +} + +/* --- Select --- */ +[data-slot="select-trigger"] { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.03); + font-weight: 500; +} + +[data-slot="select-content"] { + backdrop-filter: blur(8px); + box-shadow: 0 4px 16px oklch(0 0 0 / 0.08), 0 1px 4px oklch(0 0 0 / 0.04); +} + +[data-slot="select-item"] { + font-weight: 500; +} +``` + +**Step 2: Run tests** + +Run: `bun run --filter @repo/ui test` +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add packages/ui/src/styles.css +git commit -m "style(ui): add UI3 input, textarea, and select overrides" +``` + +--- + +### Task 6: Add UI3 Component Overrides — Checkbox, Radio, Switch + +**Files:** +- Modify: `packages/ui/src/styles.css` (append) + +**Step 1: Add checkbox/radio/switch overrides** + +```css +/* --- Checkbox --- */ +[data-slot="checkbox"] { + border-width: 1.5px; +} + +/* --- Radio --- */ +[data-slot="radio-group-item"] { + border-width: 1.5px; +} + +/* --- Switch --- */ +[data-slot="switch-thumb"] { + box-shadow: 0 1px 3px oklch(0 0 0 / 0.15), 0 1px 1px oklch(0 0 0 / 0.06); +} +``` + +**Step 2: Run tests** + +Run: `bun run --filter @repo/ui test` +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add packages/ui/src/styles.css +git commit -m "style(ui): add UI3 checkbox, radio, and switch overrides" +``` + +--- + +### Task 7: Add UI3 Component Overrides — Menus & Dropdowns + +**Files:** +- Modify: `packages/ui/src/styles.css` (append) + +**Step 1: Add menu overrides (covers dropdown, context, menubar)** + +```css +/* --- Menus (dropdown, context, menubar) --- */ +[data-slot="dropdown-menu-content"], +[data-slot="context-menu-content"], +[data-slot="menubar-content"], +[data-slot="dropdown-menu-sub-content"], +[data-slot="context-menu-sub-content"], +[data-slot="menubar-sub-content"] { + backdrop-filter: blur(8px); + box-shadow: 0 4px 16px oklch(0 0 0 / 0.08), 0 1px 4px oklch(0 0 0 / 0.04); +} + +[data-slot="dropdown-menu-item"], +[data-slot="context-menu-item"], +[data-slot="menubar-item"], +[data-slot="dropdown-menu-checkbox-item"], +[data-slot="context-menu-checkbox-item"], +[data-slot="menubar-checkbox-item"], +[data-slot="dropdown-menu-radio-item"], +[data-slot="context-menu-radio-item"], +[data-slot="menubar-radio-item"] { + font-weight: 500; + padding-block: 0.25rem; +} + +[data-slot="dropdown-menu-separator"], +[data-slot="context-menu-separator"], +[data-slot="menubar-separator"] { + opacity: 0.6; +} + +[data-slot="dropdown-menu-sub-trigger"], +[data-slot="context-menu-sub-trigger"], +[data-slot="menubar-sub-trigger"] { + font-weight: 500; +} +``` + +**Step 2: Run tests** + +Run: `bun run --filter @repo/ui test` +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add packages/ui/src/styles.css +git commit -m "style(ui): add UI3 menu and dropdown overrides" +``` + +--- + +### Task 8: Add UI3 Component Overrides — Dialogs, Sheets, Alert Dialogs + +**Files:** +- Modify: `packages/ui/src/styles.css` (append) + +**Step 1: Add dialog/sheet/alert-dialog overrides** + +```css +/* --- Dialogs & Sheets --- */ +[data-slot="dialog-content"], +[data-slot="alert-dialog-content"], +[data-slot="sheet-content"] { + box-shadow: 0 8px 32px oklch(0 0 0 / 0.12), 0 2px 8px oklch(0 0 0 / 0.06); +} + +[data-slot="dialog-overlay"], +[data-slot="alert-dialog-overlay"], +[data-slot="sheet-overlay"] { + background-color: oklch(0 0 0 / 0.08); + backdrop-filter: blur(2px); +} + +[data-slot="dialog-title"], +[data-slot="alert-dialog-title"], +[data-slot="sheet-title"], +[data-slot="drawer-title"] { + font-weight: 600; +} + +[data-slot="dialog-description"], +[data-slot="alert-dialog-description"], +[data-slot="sheet-description"], +[data-slot="drawer-description"] { + font-weight: 400; +} +``` + +**Step 2: Run tests** + +Run: `bun run --filter @repo/ui test` +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add packages/ui/src/styles.css +git commit -m "style(ui): add UI3 dialog, sheet, and alert-dialog overrides" +``` + +--- + +### Task 9: Add UI3 Component Overrides — Cards, Tabs, Accordion + +**Files:** +- Modify: `packages/ui/src/styles.css` (append) + +**Step 1: Add card/tabs/accordion overrides** + +```css +/* --- Cards --- */ +[data-slot="card"] { + box-shadow: 0 0 0 1px var(--border), 0 1px 2px oklch(0 0 0 / 0.03); +} + +[data-slot="card-title"] { + font-weight: 600; +} + +/* --- Tabs --- */ +[data-slot="tabs-list"] { + font-weight: 500; +} + +[data-slot="tabs-trigger"] { + font-weight: 500; +} + +/* --- Accordion --- */ +[data-slot="accordion-trigger"] { + font-weight: 500; +} +``` + +**Step 2: Run tests** + +Run: `bun run --filter @repo/ui test` +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add packages/ui/src/styles.css +git commit -m "style(ui): add UI3 card, tabs, and accordion overrides" +``` + +--- + +### Task 10: Add UI3 Component Overrides — Tooltip, Badge, Progress, Slider, Skeleton + +**Files:** +- Modify: `packages/ui/src/styles.css` (append) + +**Step 1: Add remaining component overrides** + +```css +/* --- Tooltip --- */ +[data-slot="tooltip-content"] { + backdrop-filter: blur(8px); + font-weight: 500; +} + +/* --- Badge --- */ +[data-slot="badge"] { + font-weight: 600; +} + +/* --- Progress --- */ +[data-slot="progress"] { + border-radius: 9999px; + overflow: hidden; +} + +[data-slot="progress-indicator"] { + border-radius: 9999px; +} + +/* --- Slider --- */ +[data-slot="slider-thumb"] { + box-shadow: 0 1px 3px oklch(0 0 0 / 0.15), 0 1px 1px oklch(0 0 0 / 0.06); +} + +[data-slot="slider-track"] { + height: 5px; +} + +/* --- Skeleton --- */ +@keyframes ui3-pulse { + 0%, 100% { opacity: 0.6; } + 50% { opacity: 0.3; } +} + +[data-slot="skeleton"] { + animation-name: ui3-pulse; + animation-duration: 2s; +} +``` + +**Step 2: Run tests** + +Run: `bun run --filter @repo/ui test` +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add packages/ui/src/styles.css +git commit -m "style(ui): add UI3 tooltip, badge, progress, slider, skeleton overrides" +``` + +--- + +### Task 11: Add UI3 Component Overrides — Navigation (Sidebar, Breadcrumb, Popover, Command) + +**Files:** +- Modify: `packages/ui/src/styles.css` (append) + +**Step 1: Add navigation/sidebar overrides** + +```css +/* --- Popover --- */ +[data-slot="popover-content"] { + backdrop-filter: blur(8px); + box-shadow: 0 4px 16px oklch(0 0 0 / 0.08), 0 1px 4px oklch(0 0 0 / 0.04); +} + +/* --- Command --- */ +[data-slot="command"] { + box-shadow: 0 4px 16px oklch(0 0 0 / 0.08); +} + +[data-slot="command-item"] { + font-weight: 500; +} + +/* --- Breadcrumb --- */ +[data-slot="breadcrumb-separator"] { + opacity: 0.5; +} + +[data-slot="breadcrumb-link"] { + font-weight: 500; +} + +/* --- Sidebar --- */ +[data-slot="sidebar-group-label"] { + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 600; +} + +[data-slot="sidebar-menu-button"] { + font-weight: 500; +} +``` + +**Step 2: Run tests** + +Run: `bun run --filter @repo/ui test` +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add packages/ui/src/styles.css +git commit -m "style(ui): add UI3 sidebar, breadcrumb, popover, command overrides" +``` + +--- + +### Task 12: Add Dark Mode Adjustments for Overrides + +**Files:** +- Modify: `packages/ui/src/styles.css` (append) + +**Step 1: Add dark-mode specific shadow/backdrop adjustments** + +```css +/* --- Dark Mode Override Adjustments --- */ +:where(.dark, html.figma-dark) { + [data-slot="button"][data-variant="default"] { + box-shadow: inset 0 1px 0 oklch(1 0 0 / 0.06), 0 1px 2px oklch(0 0 0 / 0.2); + } + + [data-slot="button"][data-variant="outline"] { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.1); + } + + [data-slot="input"], + [data-slot="textarea"] { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.15); + } + + [data-slot="input"]:focus, + [data-slot="textarea"]:focus { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.15), 0 0 0 3px oklch(from var(--ring) l c h / 0.25); + } + + [data-slot="select-trigger"] { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.1); + } + + [data-slot="card"] { + box-shadow: 0 0 0 1px var(--border), 0 1px 2px oklch(0 0 0 / 0.1); + } + + [data-slot="switch-thumb"] { + box-shadow: 0 1px 3px oklch(0 0 0 / 0.3), 0 1px 1px oklch(0 0 0 / 0.15); + } + + [data-slot="slider-thumb"] { + box-shadow: 0 1px 3px oklch(0 0 0 / 0.3), 0 1px 1px oklch(0 0 0 / 0.15); + } + + [data-slot="dialog-content"], + [data-slot="alert-dialog-content"], + [data-slot="sheet-content"] { + box-shadow: 0 8px 32px oklch(0 0 0 / 0.3), 0 2px 8px oklch(0 0 0 / 0.15); + } + + [data-slot="dialog-overlay"], + [data-slot="alert-dialog-overlay"], + [data-slot="sheet-overlay"] { + background-color: oklch(0 0 0 / 0.25); + } + + [data-slot="dropdown-menu-content"], + [data-slot="context-menu-content"], + [data-slot="menubar-content"], + [data-slot="dropdown-menu-sub-content"], + [data-slot="context-menu-sub-content"], + [data-slot="menubar-sub-content"], + [data-slot="select-content"], + [data-slot="popover-content"], + [data-slot="command"] { + box-shadow: 0 4px 16px oklch(0 0 0 / 0.2), 0 1px 4px oklch(0 0 0 / 0.1); + } +} +``` + +**Step 2: Run full test suite** + +Run: `bun run test` +Expected: All tests pass across all packages + +**Step 3: Commit** + +```bash +git add packages/ui/src/styles.css +git commit -m "style(ui): add dark mode adjustments for UI3 overrides" +``` + +--- + +### Task 13: Final Verification + +**Step 1: Run full CI checks** + +```bash +bun run lint && bun run types && bun run test +``` + +Expected: All pass with no new warnings/errors + +**Step 2: Visual verification** + +```bash +bun run storybook +``` + +Check components in both light and dark modes in the Storybook UI. + +**Step 3: Commit and push** + +```bash +git push +``` diff --git a/packages/ui/src/styles.css b/packages/ui/src/styles.css index 6391e6e..a249631 100644 --- a/packages/ui/src/styles.css +++ b/packages/ui/src/styles.css @@ -8,53 +8,66 @@ @custom-variant dark (&:where(.dark, .dark *, html.figma-dark, html.figma-dark *)); -/* Figma design tokens — derived from figma-plugin-ds color palette */ +/* Figma UI3 design tokens — light theme */ :root { - --background: oklch(1 0 0); - --foreground: oklch(0.26 0 0); - --primary: oklch(0.68 0.18 221); + --background: oklch(0.985 0 0); + --foreground: oklch(0.23 0 0); + --primary: oklch(0.62 0.19 245); --primary-foreground: oklch(1 0 0); --secondary: oklch(0.95 0 0); - --secondary-foreground: oklch(0.26 0 0); - --muted: oklch(0.91 0 0); - --muted-foreground: oklch(0.73 0 0); - --accent: oklch(0.95 0 0); - --accent-foreground: oklch(0.26 0 0); - --destructive: oklch(0.61 0.23 28); - --border: oklch(0.91 0 0); - --input: oklch(0.91 0 0); - --ring: oklch(0.68 0.18 221); - --radius: 0.375rem; + --secondary-foreground: oklch(0.23 0 0); + --muted: oklch(0.94 0 0); + --muted-foreground: oklch(0.55 0 0); + --accent: oklch(0.93 0.01 245); + --accent-foreground: oklch(0.23 0 0); + --destructive: oklch(0.59 0.22 25); + --border: oklch(0.90 0 0); + --input: oklch(0.90 0 0); + --ring: oklch(0.62 0.19 245); + --radius: 0.5rem; --card: oklch(1 0 0); - --card-foreground: oklch(0.26 0 0); + --card-foreground: oklch(0.23 0 0); --popover: oklch(1 0 0); - --popover-foreground: oklch(0.26 0 0); + --popover-foreground: oklch(0.23 0 0); + --sidebar: oklch(0.97 0 0); + --sidebar-foreground: oklch(0.23 0 0); + --sidebar-border: oklch(0.90 0 0); + --chart-1: oklch(0.62 0.19 245); + --chart-2: oklch(0.65 0.18 160); + --chart-3: oklch(0.70 0.15 70); + --chart-4: oklch(0.60 0.20 310); + --chart-5: oklch(0.65 0.22 25); } -/* Figma dark theme tokens — mirror of :root, activated by .dark (tests/Storybook) or html.figma-dark (Figma runtime). - Contrast revisited 2026-04-10 after human-verify checkpoint: original 0.30 border/input - values were indistinguishable from the 0.22 card background, making checkbox/input - borders invisible. Values now target Figma Desktop's native dark palette. */ +/* Figma UI3 design tokens — dark theme, activated by .dark (tests/Storybook) or html.figma-dark (Figma runtime). */ .dark, html.figma-dark { - --background: oklch(0.20 0 0); - --foreground: oklch(0.96 0 0); - --primary: oklch(0.72 0.18 221); - --primary-foreground: oklch(0.18 0 0); - --secondary: oklch(0.30 0 0); - --secondary-foreground: oklch(0.96 0 0); - --muted: oklch(0.30 0 0); - --muted-foreground: oklch(0.75 0 0); - --accent: oklch(0.30 0 0); - --accent-foreground: oklch(0.96 0 0); - --destructive: oklch(0.66 0.23 28); - --border: oklch(0.42 0 0); - --input: oklch(0.42 0 0); - --ring: oklch(0.72 0.18 221); - --card: oklch(0.25 0 0); - --card-foreground: oklch(0.96 0 0); - --popover: oklch(0.25 0 0); - --popover-foreground: oklch(0.96 0 0); + --background: oklch(0.18 0 0); + --foreground: oklch(0.93 0 0); + --primary: oklch(0.65 0.19 245); + --primary-foreground: oklch(0.15 0 0); + --secondary: oklch(0.26 0 0); + --secondary-foreground: oklch(0.93 0 0); + --muted: oklch(0.25 0 0); + --muted-foreground: oklch(0.60 0 0); + --accent: oklch(0.27 0.01 245); + --accent-foreground: oklch(0.93 0 0); + --destructive: oklch(0.62 0.22 25); + --border: oklch(0.35 0 0); + --input: oklch(0.35 0 0); + --ring: oklch(0.65 0.19 245); + --card: oklch(0.22 0 0); + --card-foreground: oklch(0.93 0 0); + --popover: oklch(0.22 0 0); + --popover-foreground: oklch(0.93 0 0); + --sidebar: oklch(0.16 0 0); + --sidebar-foreground: oklch(0.93 0 0); + --sidebar-border: oklch(0.35 0 0); + --chart-1: oklch(0.65 0.19 245); + --chart-2: oklch(0.68 0.18 160); + --chart-3: oklch(0.73 0.15 70); + --chart-4: oklch(0.63 0.20 310); + --chart-5: oklch(0.68 0.22 25); } @theme inline { @@ -76,6 +89,14 @@ html.figma-dark { --color-card-foreground: var(--card-foreground); --color-popover: var(--popover); --color-popover-foreground: var(--popover-foreground); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); @@ -89,3 +110,287 @@ html.figma-dark { --font-size-base: 13px; --font-size-lg: 14px; } + +/* ============================================================ + Figma UI3 Component Overrides + Uses data-slot selectors to style shadcn components + without modifying .tsx files. + ============================================================ */ + +/* --- Buttons --- */ +[data-slot="button"] { + font-weight: 500; +} + +[data-slot="button"][data-variant="default"] { + box-shadow: inset 0 1px 0 oklch(1 0 0 / 0.1), 0 1px 2px oklch(0 0 0 / 0.05); +} + +[data-slot="button"][data-variant="outline"] { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.03); +} + +/* --- Inputs & Textarea --- */ +[data-slot="input"], +[data-slot="textarea"] { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.05); +} + +[data-slot="input"]:focus, +[data-slot="textarea"]:focus { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.05), 0 0 0 3px oklch(from var(--ring) l c h / 0.2); +} + +/* --- Select --- */ +[data-slot="select-trigger"] { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.03); + font-weight: 500; +} + +[data-slot="select-content"] { + backdrop-filter: blur(8px); + box-shadow: 0 4px 16px oklch(0 0 0 / 0.08), 0 1px 4px oklch(0 0 0 / 0.04); +} + +[data-slot="select-item"] { + font-weight: 500; +} + +/* --- Checkbox --- */ +[data-slot="checkbox"] { + border-width: 1.5px; +} + +/* --- Radio --- */ +[data-slot="radio-group-item"] { + border-width: 1.5px; +} + +/* --- Switch --- */ +[data-slot="switch-thumb"] { + box-shadow: 0 1px 3px oklch(0 0 0 / 0.15), 0 1px 1px oklch(0 0 0 / 0.06); +} + +/* --- Menus (dropdown, context, menubar) --- */ +[data-slot="dropdown-menu-content"], +[data-slot="context-menu-content"], +[data-slot="menubar-content"], +[data-slot="dropdown-menu-sub-content"], +[data-slot="context-menu-sub-content"], +[data-slot="menubar-sub-content"] { + backdrop-filter: blur(8px); + box-shadow: 0 4px 16px oklch(0 0 0 / 0.08), 0 1px 4px oklch(0 0 0 / 0.04); +} + +[data-slot="dropdown-menu-item"], +[data-slot="context-menu-item"], +[data-slot="menubar-item"], +[data-slot="dropdown-menu-checkbox-item"], +[data-slot="context-menu-checkbox-item"], +[data-slot="menubar-checkbox-item"], +[data-slot="dropdown-menu-radio-item"], +[data-slot="context-menu-radio-item"], +[data-slot="menubar-radio-item"] { + font-weight: 500; + padding-block: 0.25rem; +} + +[data-slot="dropdown-menu-separator"], +[data-slot="context-menu-separator"], +[data-slot="menubar-separator"] { + opacity: 0.6; +} + +[data-slot="dropdown-menu-sub-trigger"], +[data-slot="context-menu-sub-trigger"], +[data-slot="menubar-sub-trigger"] { + font-weight: 500; +} + +/* --- Dialogs & Sheets --- */ +[data-slot="dialog-content"], +[data-slot="alert-dialog-content"], +[data-slot="sheet-content"] { + box-shadow: 0 8px 32px oklch(0 0 0 / 0.12), 0 2px 8px oklch(0 0 0 / 0.06); +} + +[data-slot="dialog-overlay"], +[data-slot="alert-dialog-overlay"], +[data-slot="sheet-overlay"] { + background-color: oklch(0 0 0 / 0.08); + backdrop-filter: blur(2px); +} + +[data-slot="dialog-title"], +[data-slot="alert-dialog-title"], +[data-slot="sheet-title"], +[data-slot="drawer-title"] { + font-weight: 600; +} + +[data-slot="dialog-description"], +[data-slot="alert-dialog-description"], +[data-slot="sheet-description"], +[data-slot="drawer-description"] { + font-weight: 400; +} + +/* --- Cards --- */ +[data-slot="card"] { + box-shadow: 0 0 0 1px var(--border), 0 1px 2px oklch(0 0 0 / 0.03); +} + +[data-slot="card-title"] { + font-weight: 600; +} + +/* --- Tabs --- */ +[data-slot="tabs-list"] { + font-weight: 500; +} + +[data-slot="tabs-trigger"] { + font-weight: 500; +} + +/* --- Accordion --- */ +[data-slot="accordion-trigger"] { + font-weight: 500; +} + +/* --- Tooltip --- */ +[data-slot="tooltip-content"] { + backdrop-filter: blur(8px); + font-weight: 500; +} + +/* --- Badge --- */ +[data-slot="badge"] { + font-weight: 600; +} + +/* --- Progress --- */ +[data-slot="progress"] { + border-radius: 9999px; + overflow: hidden; +} + +[data-slot="progress-indicator"] { + border-radius: 9999px; +} + +/* --- Slider --- */ +[data-slot="slider-thumb"] { + box-shadow: 0 1px 3px oklch(0 0 0 / 0.15), 0 1px 1px oklch(0 0 0 / 0.06); +} + +[data-slot="slider-track"] { + height: 5px; +} + +/* --- Skeleton --- */ +@keyframes ui3-pulse { + 0%, 100% { opacity: 0.6; } + 50% { opacity: 0.3; } +} + +[data-slot="skeleton"] { + animation-name: ui3-pulse; + animation-duration: 2s; +} + +/* --- Popover --- */ +[data-slot="popover-content"] { + backdrop-filter: blur(8px); + box-shadow: 0 4px 16px oklch(0 0 0 / 0.08), 0 1px 4px oklch(0 0 0 / 0.04); +} + +/* --- Command --- */ +[data-slot="command"] { + box-shadow: 0 4px 16px oklch(0 0 0 / 0.08); +} + +[data-slot="command-item"] { + font-weight: 500; +} + +/* --- Breadcrumb --- */ +[data-slot="breadcrumb-separator"] { + opacity: 0.5; +} + +[data-slot="breadcrumb-link"] { + font-weight: 500; +} + +/* --- Sidebar --- */ +[data-slot="sidebar-group-label"] { + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 600; +} + +[data-slot="sidebar-menu-button"] { + font-weight: 500; +} + +/* --- Dark Mode Override Adjustments --- */ +:where(.dark, html.figma-dark) { + [data-slot="button"][data-variant="default"] { + box-shadow: inset 0 1px 0 oklch(1 0 0 / 0.06), 0 1px 2px oklch(0 0 0 / 0.2); + } + + [data-slot="button"][data-variant="outline"] { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.1); + } + + [data-slot="input"], + [data-slot="textarea"] { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.15); + } + + [data-slot="input"]:focus, + [data-slot="textarea"]:focus { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.15), 0 0 0 3px oklch(from var(--ring) l c h / 0.25); + } + + [data-slot="select-trigger"] { + box-shadow: inset 0 1px 2px oklch(0 0 0 / 0.1); + } + + [data-slot="card"] { + box-shadow: 0 0 0 1px var(--border), 0 1px 2px oklch(0 0 0 / 0.1); + } + + [data-slot="switch-thumb"] { + box-shadow: 0 1px 3px oklch(0 0 0 / 0.3), 0 1px 1px oklch(0 0 0 / 0.15); + } + + [data-slot="slider-thumb"] { + box-shadow: 0 1px 3px oklch(0 0 0 / 0.3), 0 1px 1px oklch(0 0 0 / 0.15); + } + + [data-slot="dialog-content"], + [data-slot="alert-dialog-content"], + [data-slot="sheet-content"] { + box-shadow: 0 8px 32px oklch(0 0 0 / 0.3), 0 2px 8px oklch(0 0 0 / 0.15); + } + + [data-slot="dialog-overlay"], + [data-slot="alert-dialog-overlay"], + [data-slot="sheet-overlay"] { + background-color: oklch(0 0 0 / 0.25); + } + + [data-slot="dropdown-menu-content"], + [data-slot="context-menu-content"], + [data-slot="menubar-content"], + [data-slot="dropdown-menu-sub-content"], + [data-slot="context-menu-sub-content"], + [data-slot="menubar-sub-content"], + [data-slot="select-content"], + [data-slot="popover-content"], + [data-slot="command"] { + box-shadow: 0 4px 16px oklch(0 0 0 / 0.2), 0 1px 4px oklch(0 0 0 / 0.1); + } +}