@dtsports/design-system 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/components/accordion.d.ts +19 -0
- package/dist/components/accordion.d.ts.map +1 -0
- package/dist/components/accordion.js +26 -0
- package/dist/components/accordion.js.map +1 -0
- package/dist/components/alert.d.ts +26 -0
- package/dist/components/alert.d.ts.map +1 -0
- package/dist/components/alert.js.map +1 -0
- package/dist/components/app-shell.d.ts +21 -0
- package/dist/components/app-shell.d.ts.map +1 -0
- package/dist/components/app-shell.js +25 -0
- package/dist/components/app-shell.js.map +1 -0
- package/dist/components/aspect-ratio.d.ts +16 -0
- package/dist/components/aspect-ratio.d.ts.map +1 -0
- package/dist/components/aspect-ratio.js +17 -0
- package/dist/components/aspect-ratio.js.map +1 -0
- package/dist/components/avatar-group.d.ts +31 -0
- package/dist/components/avatar-group.d.ts.map +1 -0
- package/dist/components/avatar.d.ts +15 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.js +20 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge.d.ts +27 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/badge.js +51 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/breadcrumb.js.map +1 -0
- package/dist/components/button.d.ts +33 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.js +58 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/calendar.d.ts +15 -0
- package/dist/components/calendar.d.ts.map +1 -0
- package/dist/components/calendar.js +33 -0
- package/dist/components/calendar.js.map +1 -0
- package/dist/components/capacity-bar.d.ts +32 -0
- package/dist/components/capacity-bar.d.ts.map +1 -0
- package/dist/components/capacity-bar.js +29 -0
- package/dist/components/capacity-bar.js.map +1 -0
- package/dist/components/card.d.ts +26 -0
- package/dist/components/card.d.ts.map +1 -0
- package/dist/components/card.js +20 -0
- package/dist/components/checkbox.d.ts +11 -0
- package/dist/components/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox.js +19 -0
- package/dist/components/chip.d.ts +23 -0
- package/dist/components/chip.d.ts.map +1 -0
- package/dist/components/chip.js +32 -0
- package/dist/components/chip.js.map +1 -0
- package/dist/components/collapsible.d.ts +16 -0
- package/dist/components/collapsible.js +22 -0
- package/dist/components/collapsible.js.map +1 -0
- package/dist/components/combobox.d.ts +38 -0
- package/dist/components/combobox.d.ts.map +1 -0
- package/dist/components/combobox.js +17 -0
- package/dist/components/combobox.js.map +1 -0
- package/dist/components/confirm-action.d.ts +36 -0
- package/dist/components/confirm-action.d.ts.map +1 -0
- package/dist/components/confirm-action.js +21 -0
- package/dist/components/confirm-action.js.map +1 -0
- package/dist/components/container.js.map +1 -0
- package/dist/components/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker.js +22 -0
- package/dist/components/date-picker.js.map +1 -0
- package/dist/components/dialog.d.ts +28 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/drawer.js +55 -0
- package/dist/components/drawer.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +39 -0
- package/dist/components/dropdown-menu.d.ts.map +1 -0
- package/dist/components/dropdown-menu.js +47 -0
- package/dist/components/empty-state.d.ts +15 -0
- package/dist/components/empty-state.d.ts.map +1 -0
- package/dist/components/empty-state.js +6 -0
- package/dist/components/form-field.d.ts +32 -0
- package/dist/components/form-field.d.ts.map +1 -0
- package/dist/components/form-field.js +31 -0
- package/dist/components/form-field.js.map +1 -0
- package/dist/components/input.d.ts +10 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +6 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/label.d.ts +11 -0
- package/dist/components/label.d.ts.map +1 -0
- package/dist/components/label.js +13 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/live-pill.d.ts +14 -0
- package/dist/components/live-pill.js +15 -0
- package/dist/components/live-pill.js.map +1 -0
- package/dist/components/metric-tile.d.ts +23 -0
- package/dist/components/metric-tile.d.ts.map +1 -0
- package/dist/components/money-breakdown.d.ts +36 -0
- package/dist/components/money-breakdown.d.ts.map +1 -0
- package/dist/components/money-breakdown.js +12 -0
- package/dist/components/money-breakdown.js.map +1 -0
- package/dist/components/number-input.d.ts +16 -0
- package/dist/components/number-input.d.ts.map +1 -0
- package/dist/components/number-input.js +30 -0
- package/dist/components/number-input.js.map +1 -0
- package/dist/components/pagination.d.ts +27 -0
- package/dist/components/pagination.d.ts.map +1 -0
- package/dist/components/pagination.js +52 -0
- package/dist/components/pagination.js.map +1 -0
- package/dist/components/popover.d.ts +24 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.js +28 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/progress.d.ts +13 -0
- package/dist/components/progress.d.ts.map +1 -0
- package/dist/components/progress.js +17 -0
- package/dist/components/radio-group.d.ts +10 -0
- package/dist/components/radio-group.d.ts.map +1 -0
- package/dist/components/radio-group.js +17 -0
- package/dist/components/radio-group.js.map +1 -0
- package/dist/components/scroll-area.d.ts.map +1 -0
- package/dist/components/scroll-area.js +22 -0
- package/dist/components/scroll-area.js.map +1 -0
- package/dist/components/select.d.ts +27 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.js +40 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/separator.d.ts +10 -0
- package/dist/components/separator.d.ts.map +1 -0
- package/dist/components/separator.js +14 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sidebar.d.ts.map +1 -0
- package/dist/components/sidebar.js +48 -0
- package/dist/components/sidebar.js.map +1 -0
- package/dist/components/skeleton.d.ts +23 -0
- package/dist/components/skeleton.js +25 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/slider.d.ts +14 -0
- package/dist/components/slider.d.ts.map +1 -0
- package/dist/components/slider.js +30 -0
- package/dist/components/slider.js.map +1 -0
- package/dist/components/spec.d.ts +35 -0
- package/dist/components/spec.js +31 -0
- package/dist/components/spec.js.map +1 -0
- package/dist/components/spinner.d.ts +20 -0
- package/dist/components/spinner.d.ts.map +1 -0
- package/dist/components/spinner.js +18 -0
- package/dist/components/spinner.js.map +1 -0
- package/dist/components/stack.d.ts +48 -0
- package/dist/components/stack.d.ts.map +1 -0
- package/dist/components/stack.js +43 -0
- package/dist/components/stack.js.map +1 -0
- package/dist/components/status-pill.js +34 -0
- package/dist/components/status-pill.js.map +1 -0
- package/dist/components/switch.d.ts +9 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.js +15 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/table.d.ts +31 -0
- package/dist/components/table.d.ts.map +1 -0
- package/dist/components/tabs.d.ts +22 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.js +28 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/textarea.d.ts +8 -0
- package/dist/components/textarea.d.ts.map +1 -0
- package/dist/components/textarea.js +6 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/theme-provider.d.ts +35 -0
- package/dist/components/theme-provider.d.ts.map +1 -0
- package/dist/components/theme-provider.js.map +1 -0
- package/dist/components/theme-toggle.d.ts +10 -0
- package/dist/components/theme-toggle.d.ts.map +1 -0
- package/dist/components/theme-toggle.js.map +1 -0
- package/dist/components/three-state.d.ts +39 -0
- package/dist/components/three-state.d.ts.map +1 -0
- package/dist/components/three-state.js +24 -0
- package/dist/components/three-state.js.map +1 -0
- package/dist/components/timestamp.d.ts +24 -0
- package/dist/components/timestamp.js +64 -0
- package/dist/components/timestamp.js.map +1 -0
- package/dist/components/toaster.d.ts +27 -0
- package/dist/components/toaster.d.ts.map +1 -0
- package/dist/components/toaster.js +21 -0
- package/dist/components/toaster.js.map +1 -0
- package/dist/components/tooltip.d.ts +28 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/components/top-bar.d.ts +13 -0
- package/dist/components/top-bar.d.ts.map +1 -0
- package/dist/components/top-bar.js +16 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/utils.d.ts +7 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +10 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/marketing/footer.d.ts +31 -0
- package/dist/marketing/footer.d.ts.map +1 -0
- package/dist/marketing/footer.js +9 -0
- package/dist/marketing/footer.js.map +1 -0
- package/dist/marketing/hero.d.ts +29 -0
- package/dist/marketing/hero.d.ts.map +1 -0
- package/dist/marketing/hero.js +7 -0
- package/dist/marketing/hero.js.map +1 -0
- package/dist/marketing/index.d.ts +21 -0
- package/dist/marketing/index.d.ts.map +1 -0
- package/dist/marketing/index.js +21 -0
- package/dist/marketing/index.js.map +1 -0
- package/dist/marketing/logo-cloud.d.ts +15 -0
- package/dist/marketing/logo-cloud.d.ts.map +1 -0
- package/dist/marketing/logo-cloud.js +6 -0
- package/dist/marketing/logo-cloud.js.map +1 -0
- package/dist/marketing/nav.d.ts +33 -0
- package/dist/marketing/nav.d.ts.map +1 -0
- package/dist/marketing/nav.js +14 -0
- package/dist/marketing/nav.js.map +1 -0
- package/dist/marketing/prose.d.ts +12 -0
- package/dist/marketing/prose.d.ts.map +1 -0
- package/dist/marketing/prose.js +18 -0
- package/dist/marketing/prose.js.map +1 -0
- package/dist/marketing/section.d.ts +22 -0
- package/dist/marketing/section.d.ts.map +1 -0
- package/dist/marketing/section.js +7 -0
- package/dist/marketing/section.js.map +1 -0
- package/dist/showcase/components/code-block.d.ts +11 -0
- package/dist/showcase/components/code-block.d.ts.map +1 -0
- package/dist/showcase/components/code-block.js +20 -0
- package/dist/showcase/components/code-block.js.map +1 -0
- package/dist/showcase/components/example.d.ts +16 -0
- package/dist/showcase/components/example.d.ts.map +1 -0
- package/dist/showcase/components/example.js +14 -0
- package/dist/showcase/components/example.js.map +1 -0
- package/dist/showcase/components/section.d.ts +21 -0
- package/dist/showcase/components/section.d.ts.map +1 -0
- package/dist/showcase/components/section.js +10 -0
- package/dist/showcase/components/section.js.map +1 -0
- package/dist/showcase/components/swatch.d.ts +17 -0
- package/dist/showcase/components/swatch.d.ts.map +1 -0
- package/dist/showcase/components/swatch.js +6 -0
- package/dist/showcase/components/swatch.js.map +1 -0
- package/dist/showcase/index.d.ts +17 -0
- package/dist/showcase/index.d.ts.map +1 -0
- package/dist/showcase/index.js +17 -0
- package/dist/showcase/index.js.map +1 -0
- package/dist/showcase/page.d.ts +2 -0
- package/dist/showcase/page.d.ts.map +1 -0
- package/dist/showcase/page.js +1085 -0
- package/dist/showcase/page.js.map +1 -0
- package/dist/styles/generated/tokens.d.ts +131 -0
- package/dist/styles/generated/tokens.d.ts.map +1 -0
- package/dist/styles/generated/tokens.js +107 -0
- package/dist/styles/generated/tokens.js.map +1 -0
- package/dist/styles/globals.css +131 -0
- package/dist/styles/tailwind.preset.d.ts +181 -0
- package/dist/styles/tailwind.preset.d.ts.map +1 -0
- package/dist/styles/tailwind.preset.js +114 -0
- package/dist/styles/tailwind.preset.js.map +1 -0
- package/dist/tokens/index.d.ts +176 -0
- package/dist/tokens/index.d.ts.map +1 -0
- package/dist/tokens/index.js +139 -0
- package/dist/tokens/index.js.map +1 -0
- package/dist/tokens/tokens.json +154 -0
- package/package.json +124 -0
- package/src/components/accordion.tsx +76 -0
- package/src/components/alert.tsx +113 -0
- package/src/components/app-shell.tsx +43 -0
- package/src/components/aspect-ratio.tsx +18 -0
- package/src/components/avatar-group.tsx +73 -0
- package/src/components/avatar.tsx +81 -0
- package/src/components/badge.tsx +77 -0
- package/src/components/breadcrumb.tsx +121 -0
- package/src/components/button.tsx +83 -0
- package/src/components/calendar.tsx +85 -0
- package/src/components/capacity-bar.tsx +80 -0
- package/src/components/card.tsx +104 -0
- package/src/components/checkbox.tsx +46 -0
- package/src/components/chip.tsx +74 -0
- package/src/components/collapsible.tsx +54 -0
- package/src/components/combobox.tsx +140 -0
- package/src/components/confirm-action.tsx +111 -0
- package/src/components/container.tsx +39 -0
- package/src/components/date-picker.tsx +98 -0
- package/src/components/dialog.tsx +174 -0
- package/src/components/drawer.tsx +157 -0
- package/src/components/dropdown-menu.tsx +200 -0
- package/src/components/empty-state.tsx +46 -0
- package/src/components/form-field.tsx +118 -0
- package/src/components/input.tsx +29 -0
- package/src/components/label.tsx +35 -0
- package/src/components/live-pill.tsx +35 -0
- package/src/components/metric-tile.tsx +83 -0
- package/src/components/money-breakdown.tsx +92 -0
- package/src/components/number-input.tsx +113 -0
- package/src/components/pagination.tsx +157 -0
- package/src/components/popover.tsx +46 -0
- package/src/components/progress.tsx +34 -0
- package/src/components/radio-group.tsx +46 -0
- package/src/components/scroll-area.tsx +55 -0
- package/src/components/select.tsx +169 -0
- package/src/components/separator.tsx +34 -0
- package/src/components/sidebar.tsx +168 -0
- package/src/components/skeleton.tsx +85 -0
- package/src/components/slider.tsx +58 -0
- package/src/components/spec.tsx +59 -0
- package/src/components/spinner.tsx +36 -0
- package/src/components/stack.tsx +80 -0
- package/src/components/status-pill.tsx +100 -0
- package/src/components/switch.tsx +37 -0
- package/src/components/table.tsx +129 -0
- package/src/components/tabs.tsx +72 -0
- package/src/components/textarea.tsx +27 -0
- package/src/components/theme-provider.tsx +131 -0
- package/src/components/theme-toggle.tsx +34 -0
- package/src/components/three-state.tsx +90 -0
- package/src/components/timestamp.tsx +107 -0
- package/src/components/toaster.tsx +55 -0
- package/src/components/tooltip.tsx +49 -0
- package/src/components/top-bar.tsx +29 -0
- package/src/index.ts +221 -0
- package/src/lib/utils.ts +10 -0
- package/src/marketing/footer.tsx +93 -0
- package/src/marketing/hero.tsx +92 -0
- package/src/marketing/index.ts +24 -0
- package/src/marketing/logo-cloud.tsx +35 -0
- package/src/marketing/nav.tsx +122 -0
- package/src/marketing/prose.tsx +45 -0
- package/src/marketing/section.tsx +77 -0
- package/src/showcase/components/code-block.tsx +66 -0
- package/src/showcase/components/example.tsx +47 -0
- package/src/showcase/components/section.tsx +70 -0
- package/src/showcase/components/swatch.tsx +52 -0
- package/src/showcase/index.tsx +16 -0
- package/src/showcase/page.tsx +4216 -0
- package/src/styles/generated/tokens.ts +119 -0
- package/src/styles/globals.css +131 -0
- package/src/styles/tailwind.preset.ts +134 -0
- package/src/test/a11y.test.tsx +638 -0
- package/src/test/marketing-a11y.test.tsx +138 -0
- package/src/test/setup.ts +47 -0
- package/src/tokens/index.ts +154 -0
- package/src/tokens/tokens.json +154 -0
|
@@ -0,0 +1,1085 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { AlertTriangle, CalendarDays, CalendarPlus, CheckCircle2, ChevronDown, CircleHelp, Edit, Inbox, Info, LayoutDashboard, MapPin, MoreHorizontal, Repeat, Settings, Trash2, Trophy, Users, XCircle, Zap, } from "lucide-react";
|
|
5
|
+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AspectRatio, Calendar, Collapsible, CollapsibleContent, CollapsibleTrigger, Combobox, DatePicker, SkeletonList, SkeletonTable, Slider, Toaster, toast, Avatar, AvatarGroup, Badge, Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, CapacityBar, Chip, Container, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, ConfirmAction, Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, EmptyState, FormField, Input, Label, LivePill, MetricTile, MoneyBreakdown, NumberInput, Pagination, Popover, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, ScrollArea, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, SidebarBody, SidebarFooter, SidebarHeader, SidebarItem, SidebarSection, Skeleton, Spec, SpecList, Spinner, Stack, StatusPill, Switch, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ThemeToggle, ThreeState, Timestamp, Tooltip, TooltipContent, TooltipTrigger, TopBar, } from "../index.js";
|
|
6
|
+
// Showcase-only helpers (live in the app, not the design system).
|
|
7
|
+
import { Footer, Hero, LogoCloud, MarketingNav, Prose } from "../marketing/index.js";
|
|
8
|
+
import { Swatch } from "./components/swatch.js";
|
|
9
|
+
import { DocSection, DocSubsection } from "./components/section.js";
|
|
10
|
+
import { CodeBlock } from "./components/code-block.js";
|
|
11
|
+
import { Example, PreviewCode } from "./components/example.js";
|
|
12
|
+
const NAV_GROUPS = [
|
|
13
|
+
{
|
|
14
|
+
id: "group-principles",
|
|
15
|
+
label: "Principles",
|
|
16
|
+
items: [
|
|
17
|
+
{ id: "principles-visual", label: "Visual principles" },
|
|
18
|
+
{ id: "principles-ux", label: "UX principles" },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "group-foundations",
|
|
23
|
+
label: "Foundations",
|
|
24
|
+
items: [
|
|
25
|
+
{ id: "colors", label: "Colors" },
|
|
26
|
+
{ id: "typography", label: "Typography" },
|
|
27
|
+
{ id: "spacing", label: "Spacing" },
|
|
28
|
+
{ id: "radius", label: "Radius" },
|
|
29
|
+
{ id: "elevation", label: "Elevation" },
|
|
30
|
+
{ id: "icons", label: "Iconography" },
|
|
31
|
+
{ id: "motion", label: "Motion" },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "group-components",
|
|
36
|
+
label: "Components",
|
|
37
|
+
items: [
|
|
38
|
+
{ id: "buttons", label: "Buttons" },
|
|
39
|
+
{ id: "inputs", label: "Inputs" },
|
|
40
|
+
{ id: "checkbox", label: "Checkbox" },
|
|
41
|
+
{ id: "radio", label: "Radio group" },
|
|
42
|
+
{ id: "switch", label: "Switch" },
|
|
43
|
+
{ id: "select", label: "Select" },
|
|
44
|
+
{ id: "combobox", label: "Combobox" },
|
|
45
|
+
{ id: "textarea", label: "Textarea" },
|
|
46
|
+
{ id: "number-input", label: "Number input" },
|
|
47
|
+
{ id: "slider", label: "Slider" },
|
|
48
|
+
{ id: "date-picker", label: "Date picker" },
|
|
49
|
+
{ id: "form-field", label: "Form field" },
|
|
50
|
+
{ id: "alert", label: "Alert" },
|
|
51
|
+
{ id: "badges", label: "Badges" },
|
|
52
|
+
{ id: "chip", label: "Chip" },
|
|
53
|
+
{ id: "status", label: "Status pill" },
|
|
54
|
+
{ id: "cards", label: "Cards" },
|
|
55
|
+
{ id: "metrics", label: "Metric tiles" },
|
|
56
|
+
{ id: "spec", label: "Spec list" },
|
|
57
|
+
{ id: "capacity", label: "Capacity bar" },
|
|
58
|
+
{ id: "tables", label: "Tables" },
|
|
59
|
+
{ id: "pagination", label: "Pagination" },
|
|
60
|
+
{ id: "tabs", label: "Tabs" },
|
|
61
|
+
{ id: "accordion", label: "Accordion" },
|
|
62
|
+
{ id: "collapsible", label: "Collapsible" },
|
|
63
|
+
{ id: "dialog", label: "Dialog" },
|
|
64
|
+
{ id: "drawer", label: "Drawer" },
|
|
65
|
+
{ id: "popover", label: "Popover" },
|
|
66
|
+
{ id: "dropdown", label: "Dropdown menu" },
|
|
67
|
+
{ id: "tooltip", label: "Tooltip" },
|
|
68
|
+
{ id: "separator", label: "Separator" },
|
|
69
|
+
{ id: "toast", label: "Toasts" },
|
|
70
|
+
{ id: "empty", label: "Empty states" },
|
|
71
|
+
{ id: "avatars", label: "Avatars" },
|
|
72
|
+
{ id: "avatar-group", label: "Avatar group" },
|
|
73
|
+
{ id: "skeleton", label: "Skeleton" },
|
|
74
|
+
{ id: "spinner", label: "Spinner" },
|
|
75
|
+
{ id: "progress", label: "Progress" },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "group-layout",
|
|
80
|
+
label: "Layout",
|
|
81
|
+
items: [
|
|
82
|
+
{ id: "app-shell", label: "App shell" },
|
|
83
|
+
{ id: "sidebar-comp", label: "Sidebar" },
|
|
84
|
+
{ id: "top-bar", label: "Top bar" },
|
|
85
|
+
{ id: "breadcrumb", label: "Breadcrumb" },
|
|
86
|
+
{ id: "container", label: "Container" },
|
|
87
|
+
{ id: "stack", label: "Stack" },
|
|
88
|
+
{ id: "aspect-ratio", label: "Aspect ratio" },
|
|
89
|
+
{ id: "scroll-area", label: "Scroll area" },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "group-patterns",
|
|
94
|
+
label: "Patterns",
|
|
95
|
+
items: [{ id: "patterns", label: "UX patterns" }],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "group-marketing",
|
|
99
|
+
label: "Marketing",
|
|
100
|
+
items: [{ id: "marketing", label: "Marketing tier" }],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: "group-reference",
|
|
104
|
+
label: "Reference",
|
|
105
|
+
items: [
|
|
106
|
+
{ id: "a11y", label: "Accessibility" },
|
|
107
|
+
{ id: "usage", label: "Using the system" },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
export default function DesignSystemPage() {
|
|
112
|
+
return (_jsxs("div", { className: "flex min-h-screen", children: [_jsxs("aside", { "aria-label": "Design system contents", className: "hidden lg:flex flex-col w-56 shrink-0 border-r border-border bg-bg-subtle/40 sticky top-0 h-screen", children: [_jsxs("div", { className: "px-5 py-4 border-b border-border", children: [_jsx("div", { className: "text-xs font-medium uppercase tracking-[0.04em] text-text-muted", children: "DT Sports" }), _jsx("div", { className: "text-md font-semibold tracking-[-0.01em] mt-0.5", children: "Design System" })] }), _jsx("nav", { className: "flex-1 overflow-y-auto scrollbar-thin py-3 px-2", children: NAV_GROUPS.map((group, gi) => (_jsxs("div", { className: gi === 0 ? "" : "mt-4", children: [_jsx("div", { className: "px-2.5 pb-1 text-[10px] font-semibold uppercase tracking-[0.06em] text-text-subtle", children: group.label }), _jsx("ul", { className: "space-y-0.5", children: group.items.map((item) => (_jsx("li", { children: _jsx("a", { href: `#${item.id}`, className: "block rounded-sm px-2.5 py-1.5 text-sm text-text-muted hover:bg-bg-muted hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: item.label }) }, item.id))) })] }, group.id))) })] }), _jsxs("main", { id: "main", className: "flex-1 min-w-0", children: [_jsx("header", { className: "sticky top-0 z-20 bg-background/95 backdrop-blur border-b border-border", children: _jsxs("div", { className: "flex h-12 items-center justify-between px-6 lg:px-8", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm text-text-muted", children: [_jsx("span", { children: "Design System" }), _jsx("span", { className: "text-text-subtle", children: "/" }), _jsx("span", { className: "text-foreground font-medium", children: "v0.1" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "secondary", size: "sm", asChild: true, children: _jsx("a", { href: "/admin", children: "View admin demo \u2192" }) }), _jsx(Badge, { variant: "forest", size: "sm", children: "Exo 2 \u00B7 14px base" }), _jsx(ThemeToggle, {})] })] }) }), _jsxs("div", { className: "px-6 lg:px-8 pt-10 pb-6 max-w-5xl", children: [_jsx("h1", { className: "text-3xl font-bold tracking-[-0.01em]", children: "DT Sports \u2014 Design System" }), _jsx("p", { className: "mt-2 text-base text-text-muted max-w-2xl", children: "A dense, data-first UI for hosts and players. Sharp corners, small spacing, sports-turf color. Built for tables and dashboards. This page is the living source of truth \u2014 every section below is the real component, rendered live." }), _jsxs("div", { className: "mt-4 flex flex-wrap gap-2", children: [_jsx(Badge, { variant: "lime", size: "sm", className: "rounded", children: "#B4D334" }), _jsx(Badge, { variant: "forest", size: "sm", className: "rounded", children: "#176E3E" }), _jsx(Badge, { variant: "outline", size: "sm", className: "rounded", children: "Exo 2" }), _jsx(Badge, { variant: "outline", size: "sm", className: "rounded", children: "4px base spacing" }), _jsx(Badge, { variant: "outline", size: "sm", className: "rounded", children: "2\u20136px corners" }), _jsx(Badge, { variant: "outline", size: "sm", className: "rounded", children: "WCAG AA" })] })] }), _jsxs("div", { className: "px-6 lg:px-8 max-w-5xl pb-24", children: [_jsx(GroupHeader, { id: "group-principles", label: "Principles", description: "The rules that decide every design call. Visual = what to draw. UX = what to do.", first: true }), _jsx(VisualPrinciplesSection, {}), _jsx(UXPrinciplesSection, {}), _jsx(GroupHeader, { id: "group-foundations", label: "Foundations", description: "Design tokens \u2014 colors, type, spacing, radius, elevation, icons, motion. Everything else composes from these." }), _jsx(ColorsSection, {}), _jsx(TypographySection, {}), _jsx(SpacingSection, {}), _jsx(RadiusSection, {}), _jsx(ElevationSection, {}), _jsx(IconsSection, {}), _jsx(MotionSection, {}), _jsx(GroupHeader, { id: "group-components", label: "Components", description: "The building blocks. Each is variant-driven via cva(), accepts className for composition, and ships with a11y baked in." }), _jsx(ButtonsSection, {}), _jsx(InputsSection, {}), _jsx(CheckboxSection, {}), _jsx(RadioSection, {}), _jsx(SwitchSection, {}), _jsx(SelectSection, {}), _jsx(ComboboxSection, {}), _jsx(TextareaSection, {}), _jsx(NumberInputSection, {}), _jsx(SliderSection, {}), _jsx(DatePickerSection, {}), _jsx(FormFieldSection, {}), _jsx(AlertSection, {}), _jsx(BadgesSection, {}), _jsx(ChipSection, {}), _jsx(StatusSection, {}), _jsx(CardsSection, {}), _jsx(MetricsSection, {}), _jsx(SpecSection, {}), _jsx(CapacitySection, {}), _jsx(TablesSection, {}), _jsx(PaginationSection, {}), _jsx(TabsSection, {}), _jsx(AccordionSection, {}), _jsx(CollapsibleSection, {}), _jsx(DialogSection, {}), _jsx(DrawerSection, {}), _jsx(PopoverSection, {}), _jsx(DropdownSection, {}), _jsx(TooltipSection, {}), _jsx(SeparatorSection, {}), _jsx(ToastSection, {}), _jsx(EmptyStateSection, {}), _jsx(AvatarsSection, {}), _jsx(AvatarGroupSection, {}), _jsx(SkeletonSection, {}), _jsx(SpinnerSection, {}), _jsx(ProgressSection, {}), _jsx(GroupHeader, { id: "group-layout", label: "Layout", description: "Page-level building blocks. AppShell + Sidebar + TopBar compose admin chrome; Container + Stack handle content padding and gaps; AspectRatio + ScrollArea handle bounded regions; Breadcrumb is the standard nav crumb trail." }), _jsx(AppShellSection, {}), _jsx(SidebarComponentSection, {}), _jsx(TopBarSection, {}), _jsx(BreadcrumbSection, {}), _jsx(ContainerSection, {}), _jsx(StackSection, {}), _jsx(AspectRatioSection, {}), _jsx(ScrollAreaSection, {}), _jsx(GroupHeader, { id: "group-patterns", label: "Patterns", description: "Composed UX behaviors. Reach for these to honor the \u00A73 UX principles without thinking about it." }), _jsx(UXPatternsSection, {}), _jsx(GroupHeader, { id: "group-marketing", label: "Marketing", description: "The website tier \u2014 fluid display type, generous spacing, page-level primitives. Import from @dtsports/design-system/marketing." }), _jsx(MarketingTierSection, {}), _jsx(GroupHeader, { id: "group-reference", label: "Reference", description: "How the system is wired up + how to consume it." }), _jsx(AccessibilitySection, {}), _jsx(UsageSection, {})] })] }), _jsx(Toaster, {})] }));
|
|
113
|
+
}
|
|
114
|
+
/* ─────────────────────────────────────────────────────────────────────── */
|
|
115
|
+
/* Group headers */
|
|
116
|
+
/* ─────────────────────────────────────────────────────────────────────── */
|
|
117
|
+
function GroupHeader({ id, label, description, first = false, }) {
|
|
118
|
+
return (_jsxs("section", { id: id, "aria-label": label, className: first ? "scroll-mt-16" : "scroll-mt-16 pt-12", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "text-xs font-semibold uppercase tracking-[0.08em] text-brand-forest", children: label }), _jsx("div", { className: "flex-1 h-px bg-brand-forest/30", "aria-hidden": "true" })] }), description && (_jsx("p", { className: "mt-2 text-sm text-text-muted max-w-2xl", children: description }))] }));
|
|
119
|
+
}
|
|
120
|
+
/* ─────────────────────────────────────────────────────────────────────── */
|
|
121
|
+
/* Sections */
|
|
122
|
+
/* ─────────────────────────────────────────────────────────────────────── */
|
|
123
|
+
function VisualPrinciplesSection() {
|
|
124
|
+
const principles = [
|
|
125
|
+
[
|
|
126
|
+
"Density over decoration",
|
|
127
|
+
"13–14px body. 4px base. Rows over cards. Show more, click less.",
|
|
128
|
+
],
|
|
129
|
+
[
|
|
130
|
+
"Borders not shadows",
|
|
131
|
+
"1px borders separate. Shadows reserved for floating layers.",
|
|
132
|
+
],
|
|
133
|
+
["Brand as accent", "Lime + forest punctuate. Most chrome stays neutral."],
|
|
134
|
+
[
|
|
135
|
+
"One CTA per view",
|
|
136
|
+
"Lime is the loudest. Use it once for the action that matters.",
|
|
137
|
+
],
|
|
138
|
+
["Numbers first-class", "Tabular figures, right-aligned, mono for IDs."],
|
|
139
|
+
[
|
|
140
|
+
"Status is iconic + colored",
|
|
141
|
+
"Color never alone. Always pair with text or icon.",
|
|
142
|
+
],
|
|
143
|
+
["Live > polished", "Skeletons over spinners. Real data fast."],
|
|
144
|
+
[
|
|
145
|
+
"Predictable motion",
|
|
146
|
+
"150ms ease-out, opacity + small translate. No springs.",
|
|
147
|
+
],
|
|
148
|
+
];
|
|
149
|
+
return (_jsx(DocSection, { id: "principles-visual", title: "Visual principles", description: "What to draw. The eight rules behind the look + feel.", children: _jsx("ul", { className: "grid gap-3 md:grid-cols-2", children: principles.map(([title, desc]) => (_jsxs("li", { className: "rounded border border-border bg-background p-4", children: [_jsx("div", { className: "text-sm font-semibold", children: title }), _jsx("div", { className: "text-xs text-text-muted mt-1", children: desc })] }, title))) }) }));
|
|
150
|
+
}
|
|
151
|
+
function UXPrinciplesSection() {
|
|
152
|
+
const principles = [
|
|
153
|
+
{
|
|
154
|
+
n: "3.1",
|
|
155
|
+
title: "Two audiences, two mental models",
|
|
156
|
+
body: "Player optimizes for speed-to-confidence; operator optimizes for scan + act. Every page can name which audience it serves.",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
n: "3.2",
|
|
160
|
+
title: "Information hierarchy per surface",
|
|
161
|
+
body: "Pick 3–4 signals that drive the decision. Demote the rest.",
|
|
162
|
+
example: "Game card: time · venue · price · spots. Period.",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
n: "3.3",
|
|
166
|
+
title: "Progressive disclosure",
|
|
167
|
+
body: "Show the minimum to decide. Reveal details on demand — except at the moment of commitment, where consequences are inline.",
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
n: "3.4",
|
|
171
|
+
title: "Latency budgets",
|
|
172
|
+
body: "Browse < 300ms. Detail < 500ms. Skeletons over 200ms. Refunds are async with explicit timeline.",
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
n: "3.5",
|
|
176
|
+
title: "Optimistic vs pessimistic UI",
|
|
177
|
+
body: "Reversible no-money actions go optimistic. Money / emails / audits go pessimistic.",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
n: "3.6",
|
|
181
|
+
title: "Error recovery",
|
|
182
|
+
body: "What happened · why in plain English · what to do next. Never 'Something went wrong.'",
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
n: "3.7",
|
|
186
|
+
title: "Forgiveness + undo",
|
|
187
|
+
body: "5s undo toast for cheap reversals. Confirm dialog with concrete consequences for destructive actions.",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
n: "3.8",
|
|
191
|
+
title: "Smart defaults",
|
|
192
|
+
body: "Don't ask for things they almost always want. Skill = Mixed. Confirmation email = on. Credit = auto-apply.",
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
n: "3.9",
|
|
196
|
+
title: "State persistence",
|
|
197
|
+
body: "Filters in URL params. Drafts auto-save. Tables remember sort and column visibility per user.",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
n: "3.10",
|
|
201
|
+
title: "Affordance + feedback",
|
|
202
|
+
body: "Visible hover on every interactive surface. Outcome ≤ 150ms via toast / dialog / inline. No silent success or failure.",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
n: "3.11",
|
|
206
|
+
title: "Confirmation discipline",
|
|
207
|
+
body: "Confirm only destructive / irreversible. Show concrete consequences: '8 players will be refunded $14 each.'",
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
n: "3.12",
|
|
211
|
+
title: "Live data discipline",
|
|
212
|
+
body: "Spots + price poll every 30s. Quote locked for 5 min at checkout. Never yank state mid-action.",
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
n: "3.13",
|
|
216
|
+
title: "Mobile-first interactions",
|
|
217
|
+
body: "32px+ tap targets. No hover-only reveals. Sticky bottom CTA on narrow screens.",
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
n: "3.14",
|
|
221
|
+
title: "Empty / loading / full",
|
|
222
|
+
body: "Every list designs all three states (plus error). Skeletons echo the final shape.",
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
n: "3.15",
|
|
226
|
+
title: "Money UX",
|
|
227
|
+
body: "Mandatory pre-charge breakdown. Receipt verbatim matches. No surprise debits.",
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
n: "3.16",
|
|
231
|
+
title: "Time UX",
|
|
232
|
+
body: "Future events in venue tz; tooltip reveals user tz. Within 24h, show ticker.",
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
n: "3.17",
|
|
236
|
+
title: "One screen, one job",
|
|
237
|
+
body: "If a page feels like an admin AND a profile, split it. Drawers for secondary tasks.",
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
n: "3.18",
|
|
241
|
+
title: "Onboarding in the flow",
|
|
242
|
+
body: "First booking IS the onboarding. No tour modal. Inline tooltips at first touch only.",
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
n: "3.19",
|
|
246
|
+
title: "Voice consistency",
|
|
247
|
+
body: "UI, toasts, emails, SMS, support all sound like one person. Direct, operational, no hype.",
|
|
248
|
+
},
|
|
249
|
+
];
|
|
250
|
+
return (_jsxs(DocSection, { id: "principles-ux", title: "UX principles", description: "How the surface behaves. These shape every interaction \u2014 flows, defaults, errors, feedback, money, time.", children: [_jsx("ul", { className: "grid gap-3 md:grid-cols-2", children: principles.map(({ n, title, body, example }) => (_jsxs("li", { className: "rounded border border-border bg-background p-4 flex flex-col", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsxs("span", { className: "text-[10px] font-mono text-text-subtle tabular", children: ["\u00A7", n] }), _jsx("h4", { className: "text-sm font-semibold", children: title })] }), _jsx("p", { className: "mt-1.5 text-xs text-text-muted", children: body }), example && (_jsxs("p", { className: "mt-2 text-xs text-text-subtle border-t border-border pt-2", children: [_jsx("span", { className: "font-medium text-foreground", children: "Example. " }), example] }))] }, n))) }), _jsxs("p", { className: "text-xs text-text-muted", children: ["See ", _jsx("code", { className: "font-mono", children: "design.md \u00A73" }), " for the full treatment with do/don't tables."] })] }));
|
|
251
|
+
}
|
|
252
|
+
function ColorsSection() {
|
|
253
|
+
return (_jsxs(DocSection, { id: "colors", title: "Colors", description: "Brand + neutrals + semantic. Verified for WCAG AA contrast. White-on-lime fails AA \u2014 never do it.", children: [_jsx(DocSubsection, { title: "Brand", children: _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsx(Swatch, { token: "brand-lime", bg: "bg-brand-lime", value: "#B4D334", textSample: "black", contrast: "black 11.2:1 \u2713 AAA" }), _jsx(Swatch, { token: "brand-forest", bg: "bg-brand-forest", value: "#176E3E", textSample: "white", contrast: "white 6.9:1 \u2713 AA" }), _jsx(Swatch, { token: "foreground", bg: "bg-foreground", value: "#0A0A0A", textSample: "white", contrast: "white 19:1 \u2713 AAA" }), _jsx(Swatch, { token: "background", bg: "bg-background border-border-strong", value: "#FFFFFF", textSample: "black", contrast: "black 19:1 \u2713 AAA" })] }) }), _jsx(DocSubsection, { title: "Neutrals", children: _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsx(Swatch, { token: "bg-subtle", bg: "bg-bg-subtle", value: "hsl(0 0% 98%)" }), _jsx(Swatch, { token: "bg-muted", bg: "bg-bg-muted", value: "hsl(240 5% 96%)" }), _jsx(Swatch, { token: "border", bg: "bg-border", value: "hsl(0 0% 90%)" }), _jsx(Swatch, { token: "border-strong", bg: "bg-border-strong", value: "hsl(0 0% 83%)" }), _jsx(Swatch, { token: "text-muted", bg: "bg-text-muted", value: "hsl(0 0% 32%)", textSample: "white" }), _jsx(Swatch, { token: "text-subtle", bg: "bg-text-subtle", value: "hsl(0 0% 45%)", textSample: "white" })] }) }), _jsx(DocSubsection, { title: "Semantic", description: "Use sparingly \u2014 these signal state, not decoration.", children: _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsx(Swatch, { token: "success", bg: "bg-success", value: "#16A34A", textSample: "white" }), _jsx(Swatch, { token: "warning", bg: "bg-warning", value: "#F59E0B", textSample: "black" }), _jsx(Swatch, { token: "danger", bg: "bg-danger", value: "#DC2626", textSample: "white" }), _jsx(Swatch, { token: "info", bg: "bg-info", value: "#2563EB", textSample: "white" })] }) }), _jsx(DocSubsection, { title: "Critical contrast rules", description: "Use this checklist whenever you place text on a colored background.", children: _jsx("div", { className: "rounded border border-border bg-background overflow-hidden", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-bg-subtle border-b border-border", children: _jsxs("tr", { children: [_jsx("th", { className: "text-left px-3 py-2 text-xs font-medium uppercase tracking-[0.04em] text-text-muted", children: "Pair" }), _jsx("th", { className: "text-left px-3 py-2 text-xs font-medium uppercase tracking-[0.04em] text-text-muted", children: "Ratio" }), _jsx("th", { className: "text-left px-3 py-2 text-xs font-medium uppercase tracking-[0.04em] text-text-muted", children: "Verdict" })] }) }), _jsxs("tbody", { children: [_jsxs("tr", { className: "border-b border-border", children: [_jsx("td", { className: "px-3 py-2", children: "Foreground on background" }), _jsx("td", { className: "px-3 py-2 tabular", children: "19:1" }), _jsx("td", { className: "px-3 py-2 text-brand-forest font-medium", children: "AAA \u2713" })] }), _jsxs("tr", { className: "border-b border-border", children: [_jsx("td", { className: "px-3 py-2", children: "Brand-forest on white" }), _jsx("td", { className: "px-3 py-2 tabular", children: "6.1:1" }), _jsx("td", { className: "px-3 py-2 text-brand-forest font-medium", children: "AA \u2713" })] }), _jsxs("tr", { className: "border-b border-border", children: [_jsx("td", { className: "px-3 py-2", children: "White on brand-forest" }), _jsx("td", { className: "px-3 py-2 tabular", children: "6.9:1" }), _jsx("td", { className: "px-3 py-2 text-brand-forest font-medium", children: "AA \u2713" })] }), _jsxs("tr", { className: "border-b border-border", children: [_jsx("td", { className: "px-3 py-2", children: "Black on brand-lime" }), _jsx("td", { className: "px-3 py-2 tabular", children: "11.2:1" }), _jsx("td", { className: "px-3 py-2 text-brand-forest font-medium", children: "AAA \u2713" })] }), _jsxs("tr", { children: [_jsx("td", { className: "px-3 py-2 font-medium", children: "White on brand-lime" }), _jsx("td", { className: "px-3 py-2 tabular", children: "1.5:1" }), _jsx("td", { className: "px-3 py-2 text-danger font-medium", children: "FAIL \u2014 never use" })] })] })] }) }) })] }));
|
|
254
|
+
}
|
|
255
|
+
function TypographySection() {
|
|
256
|
+
const scale = [
|
|
257
|
+
["text-xs", "11 / 16", "Table cells (compact), badges, microcopy"],
|
|
258
|
+
["text-sm", "13 / 18", "DEFAULT body text, inputs, buttons"],
|
|
259
|
+
["text-base", "14 / 20", "Paragraphs, card body"],
|
|
260
|
+
["text-md", "15 / 22", "Section headings in cards"],
|
|
261
|
+
["text-lg", "17 / 24", "Subsection titles"],
|
|
262
|
+
["text-xl", "20 / 28", "Page headings"],
|
|
263
|
+
["text-2xl", "24 / 32", "Metric values"],
|
|
264
|
+
["text-3xl", "30 / 36", "Hero — rare"],
|
|
265
|
+
];
|
|
266
|
+
return (_jsxs(DocSection, { id: "typography", title: "Typography", description: "Exo 2. Geometric, slightly sporty, dense at small sizes. Mono fallback for IDs and code.", children: [_jsx(DocSubsection, { title: "Scale", children: _jsx("div", { className: "rounded border border-border bg-background divide-y divide-border", children: scale.map(([size, lh, use]) => (_jsxs("div", { className: "flex items-baseline gap-4 px-4 py-3", children: [_jsx("div", { className: "w-28 shrink-0 text-xs font-mono text-text-muted", children: size }), _jsx("div", { className: `${size} font-medium flex-1`, children: "The quick brown fox 0123" }), _jsx("div", { className: "hidden md:block text-xs text-text-subtle tabular w-16 text-right", children: lh }), _jsx("div", { className: "hidden lg:block text-xs text-text-muted w-72", children: use })] }, size))) }) }), _jsx(DocSubsection, { title: "Weights", children: _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-base font-normal", children: "Regular 400 \u2014 body text" }), _jsx("p", { className: "text-base font-medium", children: "Medium 500 \u2014 emphasis" }), _jsx("p", { className: "text-base font-semibold", children: "Semibold 600 \u2014 headings" }), _jsx("p", { className: "text-base font-bold", children: "Bold 700 \u2014 rare, page titles" })] }) }), _jsx(DocSubsection, { title: "Numerals (tabular)", description: "Add the .tabular utility to every cell, price, count, or aligned number.", children: _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "rounded border border-border bg-background p-4", children: [_jsx("div", { className: "text-xs font-medium uppercase tracking-[0.04em] text-text-muted mb-2", children: "Without (default)" }), _jsx("pre", { className: "text-base", children: "100\n111\n222\n330" })] }), _jsxs("div", { className: "rounded border border-border bg-background p-4", children: [_jsx("div", { className: "text-xs font-medium uppercase tracking-[0.04em] text-text-muted mb-2", children: "With .tabular" }), _jsx("pre", { className: "text-base tabular", children: "100\n111\n222\n330" })] })] }) })] }));
|
|
267
|
+
}
|
|
268
|
+
function SpacingSection() {
|
|
269
|
+
const tokens = [
|
|
270
|
+
["0.5", "2px"],
|
|
271
|
+
["1", "4px"],
|
|
272
|
+
["1.5", "6px"],
|
|
273
|
+
["2", "8px"],
|
|
274
|
+
["2.5", "10px"],
|
|
275
|
+
["3", "12px"],
|
|
276
|
+
["4", "16px"],
|
|
277
|
+
["5", "20px"],
|
|
278
|
+
["6", "24px"],
|
|
279
|
+
["8", "32px"],
|
|
280
|
+
["10", "40px"],
|
|
281
|
+
["12", "48px"],
|
|
282
|
+
];
|
|
283
|
+
return (_jsx(DocSection, { id: "spacing", title: "Spacing", description: "4px base. Compact. Default gutters 12\u201316px, card padding 16\u201320px, page padding 32px.", children: _jsx("div", { className: "rounded border border-border bg-background p-4 space-y-1.5", children: tokens.map(([token, px]) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "w-14 text-xs font-mono text-text-muted", children: token }), _jsx("div", { className: "w-14 text-xs font-mono text-text-subtle tabular", children: px }), _jsx("div", { className: "h-3 bg-brand-forest rounded-sm", style: { width: px } })] }, token))) }) }));
|
|
284
|
+
}
|
|
285
|
+
function RadiusSection() {
|
|
286
|
+
const tokens = [
|
|
287
|
+
["none", "0", "rounded-none"],
|
|
288
|
+
["sm", "2px", "rounded-sm"],
|
|
289
|
+
["DEFAULT", "4px", "rounded"],
|
|
290
|
+
["md", "6px", "rounded-md"],
|
|
291
|
+
["full", "9999px", "rounded-full"],
|
|
292
|
+
];
|
|
293
|
+
return (_jsx(DocSection, { id: "radius", title: "Corner radius", description: "Minimal \u2014 sharp, modern. No rounded-xl or rounded-2xl anywhere.", children: _jsx("div", { className: "grid grid-cols-2 md:grid-cols-5 gap-4", children: tokens.map(([name, px, cls]) => (_jsxs("div", { className: "flex flex-col items-start gap-2", children: [_jsx("div", { className: `h-16 w-full bg-brand-forest ${cls}`, "aria-hidden": "true" }), _jsxs("div", { className: "text-xs", children: [_jsx("div", { className: "font-medium", children: name }), _jsx("div", { className: "text-text-muted font-mono tabular", children: px }), _jsx("div", { className: "text-text-subtle font-mono", children: cls })] })] }, name))) }) }));
|
|
294
|
+
}
|
|
295
|
+
function ElevationSection() {
|
|
296
|
+
return (_jsx(DocSection, { id: "elevation", title: "Elevation", description: "Borders first. Shadows only for floating layers.", children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-4", children: [_jsx("div", { className: "bg-bg-subtle p-8 rounded", children: _jsx("div", { className: "h-20 rounded border border-border bg-background flex items-center justify-center text-xs text-text-muted", children: "shadow-none (default)" }) }), _jsx("div", { className: "bg-bg-subtle p-8 rounded", children: _jsx("div", { className: "h-20 rounded border border-border bg-background shadow-sm flex items-center justify-center text-xs text-text-muted", children: "shadow-sm (popovers)" }) }), _jsx("div", { className: "bg-bg-subtle p-8 rounded", children: _jsx("div", { className: "h-20 rounded border border-border-strong bg-background shadow-md flex items-center justify-center text-xs text-text-muted", children: "shadow-md (modals)" }) }), _jsx("div", { className: "bg-bg-subtle p-8 rounded", children: _jsx("div", { className: "h-20 rounded border border-border-strong bg-background shadow-lg flex items-center justify-center text-xs text-text-muted", children: "shadow-lg (marketing)" }) })] }) }));
|
|
297
|
+
}
|
|
298
|
+
function IconsSection() {
|
|
299
|
+
const icons = [
|
|
300
|
+
{ Icon: CalendarPlus, name: "CalendarPlus" },
|
|
301
|
+
{ Icon: MapPin, name: "MapPin" },
|
|
302
|
+
{ Icon: Zap, name: "Zap" },
|
|
303
|
+
{ Icon: CheckCircle2, name: "CheckCircle2" },
|
|
304
|
+
{ Icon: AlertTriangle, name: "AlertTriangle" },
|
|
305
|
+
{ Icon: XCircle, name: "XCircle" },
|
|
306
|
+
{ Icon: Info, name: "Info" },
|
|
307
|
+
{ Icon: CircleHelp, name: "CircleHelp" },
|
|
308
|
+
];
|
|
309
|
+
return (_jsxs(DocSection, { id: "icons", title: "Iconography", description: "Lucide icons, stroke 1.75, currentColor. 14\u201316px inline, 18\u201320px standalone.", children: [_jsx("div", { className: "grid grid-cols-4 md:grid-cols-8 gap-3", children: icons.map(({ Icon, name }) => (_jsxs("div", { className: "flex flex-col items-center gap-1.5 rounded border border-border bg-background py-3", children: [_jsx(Icon, { className: "h-4 w-4", strokeWidth: 1.75 }), _jsx("div", { className: "text-[10px] font-mono text-text-muted", children: name })] }, name))) }), _jsx(CodeBlock, { language: "tsx", code: `import { CalendarPlus } from "lucide-react";\n\n<CalendarPlus className="h-4 w-4" strokeWidth={1.75} />` })] }));
|
|
310
|
+
}
|
|
311
|
+
function ButtonsSection() {
|
|
312
|
+
return (_jsxs(DocSection, { id: "buttons", title: "Buttons", description: "One accent (lime) per view. Forest is the default primary. 32px tap target on default size.", children: [_jsx(DocSubsection, { title: "Variants", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "accent", children: "Book now" }), _jsx(Button, { variant: "primary", children: "Confirm" }), _jsx(Button, { variant: "secondary", children: "Cancel" }), _jsx(Button, { variant: "ghost", children: "More" }), _jsx(Button, { variant: "destructive", children: "Cancel game" }), _jsx(Button, { variant: "link", children: "View receipt" })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<Button variant="accent">Book now</Button>
|
|
313
|
+
<Button variant="primary">Confirm</Button>
|
|
314
|
+
<Button variant="secondary">Cancel</Button>
|
|
315
|
+
<Button variant="ghost">More</Button>
|
|
316
|
+
<Button variant="destructive">Cancel game</Button>
|
|
317
|
+
<Button variant="link">View receipt</Button>` }) }) }), _jsx(DocSubsection, { title: "Sizes", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "flex flex-wrap items-end gap-2", children: [_jsx(Button, { size: "xs", children: "XS" }), _jsx(Button, { size: "sm", children: "SM" }), _jsx(Button, { size: "default", children: "Default" }), _jsx(Button, { size: "lg", children: "LG" }), _jsx(Button, { size: "icon", "aria-label": "Delete", children: _jsx(Trash2, { className: "h-4 w-4", strokeWidth: 1.75 }) })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<Button size="xs">XS</Button>
|
|
318
|
+
<Button size="sm">SM</Button>
|
|
319
|
+
<Button size="default">Default</Button>
|
|
320
|
+
<Button size="lg">LG</Button>
|
|
321
|
+
|
|
322
|
+
{/* Icon-only buttons MUST have aria-label */}
|
|
323
|
+
<Button size="icon" aria-label="Delete">
|
|
324
|
+
<Trash2 className="h-4 w-4" />
|
|
325
|
+
</Button>` }) }) }), _jsx(DocSubsection, { title: "With icon + state", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsxs(Button, { variant: "accent", children: [_jsx(Zap, { className: "h-4 w-4", strokeWidth: 1.75 }), "Go Live"] }), _jsx(Button, { variant: "primary", disabled: true, children: "Disabled" })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<Button variant="accent">
|
|
326
|
+
<Zap className="h-4 w-4" /> Go Live
|
|
327
|
+
</Button>
|
|
328
|
+
<Button disabled>Disabled</Button>` }) }) })] }));
|
|
329
|
+
}
|
|
330
|
+
function InputsSection() {
|
|
331
|
+
return (_jsx(DocSection, { id: "inputs", title: "Inputs & Forms", description: "Labels above inputs. Required marker is a danger asterisk. Helper text below input; replaced by error text on validation failure.", children: _jsx(Example, { children: _jsxs("form", { className: "grid gap-4 max-w-md", onSubmit: (e) => {
|
|
332
|
+
e.preventDefault();
|
|
333
|
+
toast.success("Form submitted (demo).");
|
|
334
|
+
}, noValidate: true, children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "email", required: true, children: "Email" }), _jsx(Input, { id: "email", type: "email", placeholder: "you@dtsports.tv", autoComplete: "email", required: true, "aria-describedby": "email-helper" }), _jsx("p", { id: "email-helper", className: "text-xs text-text-muted", children: "We'll send booking confirmations here." })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "phone", children: "Mobile" }), _jsx(Input, { id: "phone", type: "tel", placeholder: "+1 555 123 4567", autoComplete: "tel" })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "bad", required: true, children: "Card number" }), _jsx(Input, { id: "bad", type: "text", defaultValue: "4242 4242 0000", "aria-invalid": "true", "aria-describedby": "bad-error" }), _jsx("p", { id: "bad-error", className: "text-xs text-danger", children: "Card number is invalid." })] }), _jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [_jsx(Button, { variant: "ghost", type: "button", children: "Cancel" }), _jsx(Button, { variant: "accent", type: "submit", children: "Pay $14.00" })] })] }) }) }));
|
|
335
|
+
}
|
|
336
|
+
function BadgesSection() {
|
|
337
|
+
return (_jsx(DocSection, { id: "badges", title: "Badges", description: "Uppercase pills used as status markers. Always paired with text. Square shape by default; pill shape reserved for game status.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Badge, { children: "Neutral" }), _jsx(Badge, { variant: "outline", children: "Outline" }), _jsx(Badge, { variant: "forest", children: "Forest" }), _jsx(Badge, { variant: "lime", children: "Lime" }), _jsx(Badge, { variant: "success", children: "Success" }), _jsx(Badge, { variant: "warning", children: "Warning" }), _jsx(Badge, { variant: "danger", children: "Danger" }), _jsx(Badge, { variant: "info", children: "Info" })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<Badge>Neutral</Badge>
|
|
338
|
+
<Badge variant="outline">Outline</Badge>
|
|
339
|
+
<Badge variant="forest">Forest</Badge>
|
|
340
|
+
<Badge variant="lime">Lime</Badge>
|
|
341
|
+
<Badge variant="success">Success</Badge>
|
|
342
|
+
<Badge variant="warning">Warning</Badge>
|
|
343
|
+
<Badge variant="danger">Danger</Badge>
|
|
344
|
+
<Badge variant="info">Info</Badge>` }) }) }));
|
|
345
|
+
}
|
|
346
|
+
function StatusSection() {
|
|
347
|
+
const statuses = [
|
|
348
|
+
"draft",
|
|
349
|
+
"scheduled",
|
|
350
|
+
"filling",
|
|
351
|
+
"sold_out",
|
|
352
|
+
"live",
|
|
353
|
+
"completed",
|
|
354
|
+
"cancelled",
|
|
355
|
+
];
|
|
356
|
+
return (_jsx(DocSection, { id: "status", title: "Status pill", description: "One canonical visualization for the game-status vocabulary. The LIVE state is the only continuously animated element.", children: _jsx(PreviewCode, { preview: _jsx("div", { className: "flex flex-wrap gap-2", children: statuses.map((s) => (_jsx(StatusPill, { status: s }, s))) }), code: _jsx(CodeBlock, { language: "tsx", code: `<StatusPill status="draft" />
|
|
357
|
+
<StatusPill status="scheduled" />
|
|
358
|
+
<StatusPill status="filling" />
|
|
359
|
+
<StatusPill status="sold_out" />
|
|
360
|
+
<StatusPill status="live" />
|
|
361
|
+
<StatusPill status="completed" />
|
|
362
|
+
<StatusPill status="cancelled" />` }) }) }));
|
|
363
|
+
}
|
|
364
|
+
function CardsSection() {
|
|
365
|
+
return (_jsx(DocSection, { id: "cards", title: "Cards", description: "1px border, 4px radius, no shadow at rest. Compose Header / Content / Footer.", children: _jsxs(Card, { className: "max-w-md", children: [_jsxs(CardHeader, { children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Astoria Turf \u00B7 Field A" }), _jsx(CardDescription, { children: "5v5 \u00B7 Saturday, 6:00 pm" })] }), _jsx(StatusPill, { status: "filling" })] }), _jsx(CardContent, { children: _jsxs("div", { className: "grid grid-cols-3 gap-3 text-sm", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs uppercase tracking-[0.04em] text-text-muted", children: "Price" }), _jsx("div", { className: "tabular font-semibold mt-0.5", children: "$14.00" })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs uppercase tracking-[0.04em] text-text-muted", children: "Spots" }), _jsx("div", { className: "tabular font-semibold mt-0.5", children: "8 / 10" })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs uppercase tracking-[0.04em] text-text-muted", children: "Skill" }), _jsx("div", { className: "font-semibold mt-0.5", children: "Mixed" })] })] }) }), _jsxs(CardFooter, { children: [_jsx(Button, { variant: "ghost", size: "sm", children: "Details" }), _jsx(Button, { variant: "accent", size: "sm", children: "Book now" })] })] }) }));
|
|
366
|
+
}
|
|
367
|
+
function MetricsSection() {
|
|
368
|
+
return (_jsxs(DocSection, { id: "metrics", title: "Metric tiles", description: "Three lines exactly: label / value / delta. Use 3\u20134 across in dashboard rows.", children: [_jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-3", children: [_jsx(MetricTile, { label: "This week", value: "$1,840", delta: 12 }), _jsx(MetricTile, { label: "Bookings", value: "124", delta: -3 }), _jsx(MetricTile, { label: "Fill rate", value: "73%", delta: 0 }), _jsx(MetricTile, { label: "Refunds", value: "4", delta: 50, invertDelta: true })] }), _jsx(CodeBlock, { language: "tsx", code: `<MetricTile label="This week" value="$1,840" delta={12} />
|
|
369
|
+
<MetricTile label="Bookings" value="124" delta={-3} />
|
|
370
|
+
<MetricTile label="Fill rate" value="73%" delta={0} />
|
|
371
|
+
<MetricTile label="Refunds" value="4" delta={50} invertDelta />` })] }));
|
|
372
|
+
}
|
|
373
|
+
function TablesSection() {
|
|
374
|
+
const rows = [
|
|
375
|
+
{
|
|
376
|
+
date: "Sat May 22",
|
|
377
|
+
time: "6:00 pm",
|
|
378
|
+
venue: "Astoria Turf · A",
|
|
379
|
+
format: "5v5",
|
|
380
|
+
price: 14,
|
|
381
|
+
filled: 8,
|
|
382
|
+
capacity: 10,
|
|
383
|
+
status: "filling",
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
date: "Sat May 22",
|
|
387
|
+
time: "7:00 pm",
|
|
388
|
+
venue: "McCarren · Court 1",
|
|
389
|
+
format: "3v3",
|
|
390
|
+
price: 18,
|
|
391
|
+
filled: 6,
|
|
392
|
+
capacity: 6,
|
|
393
|
+
status: "sold_out",
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
date: "Sat May 22",
|
|
397
|
+
time: "8:00 pm",
|
|
398
|
+
venue: "Astoria Turf · B",
|
|
399
|
+
format: "5v5",
|
|
400
|
+
price: 12,
|
|
401
|
+
filled: 3,
|
|
402
|
+
capacity: 10,
|
|
403
|
+
status: "scheduled",
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
date: "Sat May 22",
|
|
407
|
+
time: "9:00 pm",
|
|
408
|
+
venue: "Astoria Turf · A",
|
|
409
|
+
format: "5v5",
|
|
410
|
+
price: 16,
|
|
411
|
+
filled: 10,
|
|
412
|
+
capacity: 10,
|
|
413
|
+
status: "live",
|
|
414
|
+
},
|
|
415
|
+
];
|
|
416
|
+
return (_jsx(DocSection, { id: "tables", title: "Tables", description: "The centerpiece. 36px row height, numeric columns right-aligned with tabular figures. Use this shape for every list of structured data.", children: _jsx("div", { className: "rounded border border-border bg-background overflow-hidden", children: _jsxs(Table, { children: [_jsx(TableCaption, { className: "sr-only", children: "Upcoming pickup games for Saturday, May 22." }), _jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Date" }), _jsx(TableHead, { children: "Time" }), _jsx(TableHead, { children: "Venue" }), _jsx(TableHead, { children: "Format" }), _jsx(TableHead, { className: "text-right", children: "Price" }), _jsx(TableHead, { className: "text-right", children: "Spots" }), _jsx(TableHead, { children: "Status" })] }) }), _jsx(TableBody, { children: rows.map((r, i) => (_jsxs(TableRow, { interactive: true, selected: i === 1, children: [_jsx(TableCell, { children: r.date }), _jsx(TableCell, { children: r.time }), _jsx(TableCell, { className: "font-medium", children: r.venue }), _jsx(TableCell, { children: r.format }), _jsxs(TableCell, { className: "text-right tabular", children: ["$", r.price.toFixed(2)] }), _jsxs(TableCell, { className: "text-right tabular", children: [r.filled, " / ", r.capacity] }), _jsx(TableCell, { children: _jsx(StatusPill, { status: r.status }) })] }, i))) })] }) }) }));
|
|
417
|
+
}
|
|
418
|
+
function DialogSection() {
|
|
419
|
+
return (_jsx(DocSection, { id: "dialog", title: "Dialog", description: "Focus-trapped, escape-to-close, returns focus to trigger. Accent CTA on the right; secondary on the left.", children: _jsxs(Dialog, { children: [_jsx(DialogTrigger, { asChild: true, children: _jsx(Button, { variant: "destructive", children: "Cancel game\u2026" }) }), _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Cancel this game?" }), _jsx(DialogDescription, { children: "8 players will be refunded as credit and notified via email." })] }), _jsx(DialogBody, { children: _jsxs("p", { className: "text-sm text-text-muted", children: ["This action is logged and cannot be undone. Players will receive a credit of", " ", _jsx("span", { className: "tabular font-medium text-foreground", children: "$14.00" }), " ", "each, applied automatically to their next booking."] }) }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", children: "Keep game" }), _jsx(Button, { variant: "destructive", children: "Cancel + refund all" })] })] })] }) }));
|
|
420
|
+
}
|
|
421
|
+
function ToastSection() {
|
|
422
|
+
return (_jsx(DocSection, { id: "toast", title: "Toasts", description: "Top-right, 4s auto-dismiss (persistent for danger). Status communicated via icon + color, never color alone.", children: _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "secondary", onClick: () => toast.success("Booking confirmed.", {
|
|
423
|
+
description: "Saturday, May 22 · 6:00 pm at Astoria Turf",
|
|
424
|
+
icon: (_jsx(CheckCircle2, { className: "h-4 w-4 text-success", strokeWidth: 1.75 })),
|
|
425
|
+
}), children: "Show success" }), _jsx(Button, { variant: "secondary", onClick: () => toast.warning("3 spots left.", {
|
|
426
|
+
description: "Saturday's 7 pm at McCarren is filling fast.",
|
|
427
|
+
icon: (_jsx(AlertTriangle, { className: "h-4 w-4 text-warning", strokeWidth: 1.75 })),
|
|
428
|
+
}), children: "Show warning" }), _jsx(Button, { variant: "secondary", onClick: () => toast.error("Payment declined.", {
|
|
429
|
+
description: "Try a different card or contact your bank.",
|
|
430
|
+
icon: (_jsx(XCircle, { className: "h-4 w-4 text-danger", strokeWidth: 1.75 })),
|
|
431
|
+
duration: 10000,
|
|
432
|
+
}), children: "Show danger" }), _jsx(Button, { variant: "secondary", onClick: () => toast("Stream goes live at 6:00 pm.", {
|
|
433
|
+
description: "Watch on DT Sports TV from any game page.",
|
|
434
|
+
icon: _jsx(Info, { className: "h-4 w-4 text-info", strokeWidth: 1.75 }),
|
|
435
|
+
}), children: "Show info" })] }) }));
|
|
436
|
+
}
|
|
437
|
+
function EmptyStateSection() {
|
|
438
|
+
return (_jsx(DocSection, { id: "empty", title: "Empty states", description: "Center-aligned text-sm. No illustrations. Subtle Lucide icon at most.", children: _jsx("div", { className: "rounded border border-border bg-background", children: _jsx(EmptyState, { icon: _jsx(Inbox, { className: "h-6 w-6", strokeWidth: 1.5 }), title: "No upcoming games", description: "Once you book or host a game, it will show up here.", action: _jsx(Button, { size: "sm", variant: "accent", children: "Browse games" }) }) }) }));
|
|
439
|
+
}
|
|
440
|
+
function AvatarsSection() {
|
|
441
|
+
return (_jsx(DocSection, { id: "avatars", title: "Avatars", description: "Fallback uses forest background with initials. Status dot indicates check-in state.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "flex items-end gap-3", children: [_jsx(Avatar, { name: "Alex Chen", size: "sm" }), _jsx(Avatar, { name: "Jordan Lee" }), _jsx(Avatar, { name: "Sam Patel", status: "checked-in" }), _jsx(Avatar, { name: "Maria Garcia", status: "booked" }), _jsx(Avatar, { name: "Danny Tran", size: "lg" })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<Avatar name="Alex Chen" size="sm" />
|
|
442
|
+
<Avatar name="Jordan Lee" />
|
|
443
|
+
<Avatar name="Sam Patel" status="checked-in" />
|
|
444
|
+
<Avatar name="Maria Garcia" status="booked" />
|
|
445
|
+
<Avatar name="Danny Tran" size="lg" />` }) }) }));
|
|
446
|
+
}
|
|
447
|
+
function SkeletonSection() {
|
|
448
|
+
return (_jsxs(DocSection, { id: "skeleton", title: "Skeleton", description: "Use over spinners. The placeholder echoes the final layout so the transition feels immediate.", children: [_jsxs("div", { className: "rounded border border-border bg-background p-5 max-w-md space-y-3", children: [_jsx(Skeleton, { className: "h-4 w-1/3" }), _jsx(Skeleton, { className: "h-3 w-2/3" }), _jsx(Skeleton, { className: "h-3 w-1/2" }), _jsxs("div", { className: "flex gap-2 pt-2", children: [_jsx(Skeleton, { className: "h-8 w-20" }), _jsx(Skeleton, { className: "h-8 w-20" })] })] }), _jsx(DocSubsection, { title: "Composites", description: "Pre-built placeholders for the two most common list shapes \u2014 match rows/columns to the real data so nothing jumps on load.", children: _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsxs("div", { className: "rounded border border-border bg-background p-4", children: [_jsxs("div", { className: "text-xs font-medium text-text-muted mb-3", children: ["SkeletonList rows=", "{3}"] }), _jsx(SkeletonList, { rows: 3 })] }), _jsxs("div", { className: "rounded border border-border bg-background p-4", children: [_jsxs("div", { className: "text-xs font-medium text-text-muted mb-3", children: ["SkeletonTable rows=", "{3}", " columns=", "{4}"] }), _jsx(SkeletonTable, { rows: 3, columns: 4 })] })] }) })] }));
|
|
449
|
+
}
|
|
450
|
+
function UXPatternsSection() {
|
|
451
|
+
return (_jsxs(DocSection, { id: "patterns", title: "UX patterns", description: "The principles above made concrete. These are the components you reach for to honor \u00A73 without thinking about it.", children: [_jsx(ThreeStatePatternDemo, {}), _jsx(MoneyBreakdownDemo, {}), _jsx(TimestampDemo, {}), _jsx(ConfirmActionDemo, {}), _jsx(UndoToastDemo, {})] }));
|
|
452
|
+
}
|
|
453
|
+
function ThreeStatePatternDemo() {
|
|
454
|
+
const [state, setState] = React.useState({
|
|
455
|
+
kind: "data",
|
|
456
|
+
data: ["Game A", "Game B"],
|
|
457
|
+
});
|
|
458
|
+
const states = [
|
|
459
|
+
{
|
|
460
|
+
label: "Loading",
|
|
461
|
+
value: { kind: "loading" },
|
|
462
|
+
},
|
|
463
|
+
{ label: "Empty", value: { kind: "empty" } },
|
|
464
|
+
{
|
|
465
|
+
label: "Error",
|
|
466
|
+
value: {
|
|
467
|
+
kind: "error",
|
|
468
|
+
error: new Error("Network unreachable. Check your connection."),
|
|
469
|
+
retry: () => setState({ kind: "loading" }),
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
label: "Full",
|
|
474
|
+
value: {
|
|
475
|
+
kind: "data",
|
|
476
|
+
data: ["Sat 6pm · Astoria", "Sat 7pm · McCarren"],
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
];
|
|
480
|
+
return (_jsxs(DocSubsection, { title: "Empty / loading / error / full", description: "Every list view uses this wrapper. Forces all four states to exist before merge. (\u00A73.14)", children: [_jsx("div", { className: "flex flex-wrap gap-2 mb-3", children: states.map(({ label, value }) => (_jsx(Button, { size: "sm", variant: state.kind === value.kind ? "primary" : "secondary", onClick: () => setState(value), "aria-pressed": state.kind === value.kind, children: label }, label))) }), _jsx("div", { className: "rounded border border-border bg-background min-h-[160px]", children: _jsx(ThreeState, { state: state, empty: {
|
|
481
|
+
icon: _jsx(Inbox, { className: "h-6 w-6", strokeWidth: 1.5 }),
|
|
482
|
+
title: "No games this week",
|
|
483
|
+
description: "Once you book or host one, it'll show up here.",
|
|
484
|
+
action: (_jsx(Button, { size: "sm", variant: "accent", children: "Browse games" })),
|
|
485
|
+
}, children: state.kind === "data" && (_jsx("ul", { className: "divide-y divide-border", children: state.data.map((row) => (_jsxs("li", { className: "flex items-center justify-between px-4 py-2.5 text-sm", children: [_jsx("span", { children: row }), _jsx(StatusPill, { status: "scheduled" })] }, row))) })) }) }), _jsx(CodeBlock, { language: "tsx", code: `<ThreeState
|
|
486
|
+
state={state}
|
|
487
|
+
empty={{ title: "No games this week", action: <Button>Browse</Button> }}
|
|
488
|
+
>
|
|
489
|
+
<RealList items={state.kind === "data" ? state.data : []} />
|
|
490
|
+
</ThreeState>` })] }));
|
|
491
|
+
}
|
|
492
|
+
function MoneyBreakdownDemo() {
|
|
493
|
+
return (_jsx(DocSubsection, { title: "Money breakdown", description: "Mandatory before any charge. Every line is named, signed, tabular-aligned. Receipt must match this verbatim. (\u00A73.15)", children: _jsx(PreviewCode, { preview: _jsx("div", { className: "w-full", children: _jsx(MoneyBreakdown, { lines: [
|
|
494
|
+
{ label: "Game fee", cents: 1400 },
|
|
495
|
+
{
|
|
496
|
+
label: "Credit applied",
|
|
497
|
+
cents: -600,
|
|
498
|
+
muted: true,
|
|
499
|
+
hint: "From cancelled game on May 18",
|
|
500
|
+
},
|
|
501
|
+
{ label: "Sales tax (8.875%)", cents: 71, muted: true },
|
|
502
|
+
], totalLabel: "Charged to card", totalCents: 871 }) }), code: _jsx(CodeBlock, { language: "tsx", code: `<MoneyBreakdown
|
|
503
|
+
lines={[
|
|
504
|
+
{ label: "Game fee", cents: 1400 },
|
|
505
|
+
{ label: "Credit applied", cents: -600, muted: true },
|
|
506
|
+
{ label: "Sales tax", cents: 71, muted: true },
|
|
507
|
+
]}
|
|
508
|
+
totalLabel="Charged to card"
|
|
509
|
+
totalCents={871}
|
|
510
|
+
/>` }) }) }));
|
|
511
|
+
}
|
|
512
|
+
function TimestampDemo() {
|
|
513
|
+
// Demo times are frozen at first render on purpose — recomputing "now"
|
|
514
|
+
// on every render would make the labels drift mid-session.
|
|
515
|
+
const [{ inThreeHours, fiveMinAgo, nextSaturday }] = React.useState(() => {
|
|
516
|
+
const now = Date.now();
|
|
517
|
+
const d = new Date(now);
|
|
518
|
+
d.setDate(d.getDate() + ((6 - d.getDay() + 7) % 7 || 7));
|
|
519
|
+
d.setHours(18, 0, 0, 0);
|
|
520
|
+
return {
|
|
521
|
+
inThreeHours: new Date(now + 3 * 3600 * 1000).toISOString(),
|
|
522
|
+
fiveMinAgo: new Date(now - 5 * 60 * 1000).toISOString(),
|
|
523
|
+
nextSaturday: d.toISOString(),
|
|
524
|
+
};
|
|
525
|
+
});
|
|
526
|
+
return (_jsx(DocSubsection, { title: "Timestamp", description: "Future events in venue tz with user-tz tooltip; within 24h show a ticker; past events relative within 7d. (\u00A73.16)", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "space-y-2 text-sm", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-text-muted text-xs w-32", children: "In 3 hours" }), _jsx(Timestamp, { iso: inThreeHours, venueTz: "America/New_York", relative: true })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-text-muted text-xs w-32", children: "5 min ago" }), _jsx(Timestamp, { iso: fiveMinAgo, relative: true })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-text-muted text-xs w-32", children: "Next Saturday" }), _jsx(Timestamp, { iso: nextSaturday, venueTz: "America/New_York" })] }), _jsx("p", { className: "text-xs text-text-subtle pt-2", children: "Hover the timestamps with a different user TZ \u2192 tooltip reveals \"Your time:\" alongside venue time." })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<Timestamp iso="2026-05-22T22:00:00Z" venueTz="America/New_York" relative />
|
|
527
|
+
<Timestamp iso="2026-05-22T17:55:00Z" relative />
|
|
528
|
+
<Timestamp iso="2026-05-29T22:00:00Z" venueTz="America/New_York" />` }) }) }));
|
|
529
|
+
}
|
|
530
|
+
function ConfirmActionDemo() {
|
|
531
|
+
return (_jsxs(DocSubsection, { title: "ConfirmAction", description: "One helper for every destructive / irreversible action. Requires concrete consequence text \u2014 abstract 'Are you sure?' is rejected by API. (\u00A73.11)", children: [_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(ConfirmAction, { title: "Cancel this game?", description: "This action is logged and cannot be undone.", consequence: _jsxs(_Fragment, { children: ["8 players will be refunded", " ", _jsx("span", { className: "tabular font-medium text-foreground", children: "$14.00" }), " ", "each as credit and notified via email."] }), confirmLabel: "Cancel + refund all", confirmVariant: "destructive", onConfirm: async () => {
|
|
532
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
533
|
+
toast.success("Game cancelled. 8 refunds issued as credit.");
|
|
534
|
+
}, children: _jsx(Button, { variant: "destructive", size: "sm", children: "Cancel game\u2026" }) }), _jsx(ConfirmAction, { title: "Refund this booking?", consequence: _jsxs(_Fragment, { children: ["Alex Chen will receive", " ", _jsx("span", { className: "tabular font-medium text-foreground", children: "$14.00" }), " ", "back to their card in 3\u20135 business days."] }), confirmLabel: "Refund $14.00", confirmVariant: "destructive", onConfirm: async () => {
|
|
535
|
+
await new Promise((r) => setTimeout(r, 600));
|
|
536
|
+
toast.success("Refund initiated.");
|
|
537
|
+
}, children: _jsx(Button, { variant: "secondary", size: "sm", children: "Refund booking\u2026" }) })] }), _jsx(CodeBlock, { language: "tsx", code: `<ConfirmAction
|
|
538
|
+
title="Cancel this game?"
|
|
539
|
+
consequence="8 players will be refunded $14.00 each as credit and emailed."
|
|
540
|
+
confirmLabel="Cancel + refund all"
|
|
541
|
+
confirmVariant="destructive"
|
|
542
|
+
onConfirm={async () => await cancelGame(id)}
|
|
543
|
+
>
|
|
544
|
+
<Button variant="destructive">Cancel game…</Button>
|
|
545
|
+
</ConfirmAction>` })] }));
|
|
546
|
+
}
|
|
547
|
+
function UndoToastDemo() {
|
|
548
|
+
return (_jsxs(DocSubsection, { title: "Undo toast", description: "The right pattern for cheap reversals (delete draft, dismiss notification). 5s window. Money actions don't get undo \u2014 they get ConfirmAction. (\u00A73.7)", children: [_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "secondary", size: "sm", onClick: () => {
|
|
549
|
+
const tid = toast("Draft deleted.", {
|
|
550
|
+
description: "Sat May 22 · 6:00 pm",
|
|
551
|
+
duration: 5000,
|
|
552
|
+
action: {
|
|
553
|
+
label: "Undo",
|
|
554
|
+
onClick: () => {
|
|
555
|
+
toast.success("Draft restored.");
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
return tid;
|
|
560
|
+
}, children: "Delete draft" }), _jsx(Button, { variant: "secondary", size: "sm", onClick: () => toast("Notification dismissed.", {
|
|
561
|
+
duration: 5000,
|
|
562
|
+
action: {
|
|
563
|
+
label: "Undo",
|
|
564
|
+
onClick: () => toast.success("Notification restored."),
|
|
565
|
+
},
|
|
566
|
+
}), children: "Dismiss notification" })] }), _jsx(CodeBlock, { language: "tsx", code: `toast("Draft deleted.", {
|
|
567
|
+
description: "Sat May 22 · 6:00 pm",
|
|
568
|
+
duration: 5000,
|
|
569
|
+
action: { label: "Undo", onClick: () => restoreDraft(id) },
|
|
570
|
+
});` })] }));
|
|
571
|
+
}
|
|
572
|
+
function MotionSection() {
|
|
573
|
+
return (_jsxs(DocSection, { id: "motion", title: "Motion", description: "150ms ease-out. No springs, no scale-on-hover, no parallax. The LIVE dot is the only continuously animated element.", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-6", children: [_jsx(LivePill, {}), _jsxs("span", { className: "text-sm text-text-muted", children: ["\u2190 Animation respects", " ", _jsx("code", { className: "font-mono", children: "prefers-reduced-motion" })] })] }), _jsx(CodeBlock, { language: "ts", code: `// tailwind.config.ts (excerpt)
|
|
574
|
+
keyframes: {
|
|
575
|
+
"live-pulse": {
|
|
576
|
+
"0%, 100%": { opacity: "1" },
|
|
577
|
+
"50%": { opacity: "0.4" },
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
animation: {
|
|
581
|
+
"live-pulse": "live-pulse 1.5s ease-in-out infinite",
|
|
582
|
+
},` })] }));
|
|
583
|
+
}
|
|
584
|
+
function AccessibilitySection() {
|
|
585
|
+
const checks = [
|
|
586
|
+
[
|
|
587
|
+
"Color contrast",
|
|
588
|
+
"All text ≥ WCAG AA; AAA where possible. White-on-lime is forbidden.",
|
|
589
|
+
],
|
|
590
|
+
[
|
|
591
|
+
"Focus rings",
|
|
592
|
+
":focus-visible across all interactive elements. 2px brand-forest @ 50%, 2px offset.",
|
|
593
|
+
],
|
|
594
|
+
[
|
|
595
|
+
"Tap targets",
|
|
596
|
+
"32px+ on default buttons / inputs. Smaller sizes only in dense toolbars.",
|
|
597
|
+
],
|
|
598
|
+
[
|
|
599
|
+
"Status semantics",
|
|
600
|
+
"Color never alone — every status badge has text and/or icon.",
|
|
601
|
+
],
|
|
602
|
+
[
|
|
603
|
+
"Reduced motion",
|
|
604
|
+
"globals.css disables animations + transitions when prefers-reduced-motion is set.",
|
|
605
|
+
],
|
|
606
|
+
[
|
|
607
|
+
"Form a11y",
|
|
608
|
+
"Every input has a Label with htmlFor. Errors use aria-invalid + aria-describedby.",
|
|
609
|
+
],
|
|
610
|
+
[
|
|
611
|
+
"Dialogs",
|
|
612
|
+
"Radix handles focus-trap, escape-to-close, focus return. Title + Description wired to aria.",
|
|
613
|
+
],
|
|
614
|
+
[
|
|
615
|
+
"Tables",
|
|
616
|
+
"<th scope='col'>. Sortable columns announce sort state via aria-sort.",
|
|
617
|
+
],
|
|
618
|
+
["Skip link", "First focusable element jumps to <main id='main'>."],
|
|
619
|
+
[
|
|
620
|
+
"Icons",
|
|
621
|
+
"Decorative icons get aria-hidden; meaningful icons get aria-label or accompanying text.",
|
|
622
|
+
],
|
|
623
|
+
];
|
|
624
|
+
return (_jsx(DocSection, { id: "a11y", title: "Accessibility", description: "Baked in \u2014 every primitive ships with the rules below. Verify with axe DevTools before merging UI changes.", children: _jsx("ul", { className: "rounded border border-border bg-background divide-y divide-border", children: checks.map(([label, body]) => (_jsxs("li", { className: "flex gap-3 px-4 py-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 mt-0.5 shrink-0 text-brand-forest", strokeWidth: 2 }), _jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: label }), _jsx("div", { className: "text-xs text-text-muted mt-0.5", children: body })] })] }, label))) }) }));
|
|
625
|
+
}
|
|
626
|
+
function UsageSection() {
|
|
627
|
+
return (_jsxs(DocSection, { id: "usage", title: "Using the system", description: "How to consume the components, extend variants, and respect the rules.", children: [_jsx(DocSubsection, { title: "Import paths", description: "Everything ships from one entry. The package is a workspace dep \u2014 no build step, source TS is consumed directly via Next's transpilePackages.", children: _jsx(CodeBlock, { language: "tsx", code: `import {
|
|
628
|
+
Button, Input, Label, Badge,
|
|
629
|
+
Card, CardHeader, CardTitle, CardContent, CardFooter,
|
|
630
|
+
Table, TableHeader, TableBody, TableRow, TableHead, TableCell,
|
|
631
|
+
Dialog, DialogContent, DialogHeader, DialogTitle, DialogBody, DialogFooter,
|
|
632
|
+
LivePill, StatusPill, MetricTile, EmptyState, Avatar, Skeleton,
|
|
633
|
+
Timestamp, MoneyBreakdown, ThreeState, ConfirmAction,
|
|
634
|
+
ThemeProvider, useTheme, ThemeToggle,
|
|
635
|
+
cn,
|
|
636
|
+
type GameStatus, type ListState,
|
|
637
|
+
} from "@dtsports/design-system";
|
|
638
|
+
|
|
639
|
+
import { toast } from "@dtsports/design-system";` }) }), _jsx(DocSubsection, { title: "Composing classes with cn()", description: "Use cn() to merge Tailwind classes so callers can override variants without duplication.", children: _jsx(CodeBlock, { language: "tsx", code: `import { cn } from "@dtsports/design-system";
|
|
640
|
+
|
|
641
|
+
export function MyCard({ className, ...props }) {
|
|
642
|
+
return (
|
|
643
|
+
<div
|
|
644
|
+
className={cn("rounded border border-border bg-background p-4", className)}
|
|
645
|
+
{...props}
|
|
646
|
+
/>
|
|
647
|
+
);
|
|
648
|
+
}` }) }), _jsx(DocSubsection, { title: "Dark mode", description: "The ThemeProvider toggles a `dark` class on <html>. All design tokens have a dark counterpart in globals.css. Components don't need any dark: classes \u2014 they inherit the variables.", children: _jsxs("div", { className: "flex items-center gap-3 text-sm text-text-muted", children: ["Try the toggle in the topbar (top right). ", _jsx(ThemeToggle, {})] }) }), _jsx(DocSubsection, { title: "Adding a new variant", description: "Edit the variant's cva() call in components/ui/<name>.tsx. Keep new variants minimal \u2014 if a use case fits an existing one, prefer that.", children: _jsx(CodeBlock, { language: "tsx", code: `// components/ui/button.tsx
|
|
649
|
+
const buttonVariants = cva(/* base classes */, {
|
|
650
|
+
variants: {
|
|
651
|
+
variant: {
|
|
652
|
+
// existing variants...
|
|
653
|
+
success: "bg-success text-white hover:bg-success/90", // new
|
|
654
|
+
},
|
|
655
|
+
},
|
|
656
|
+
});` }) }), _jsx(DocSubsection, { title: "Do / don't", description: "Quick reference. If in doubt, default to the left column.", children: _jsxs("div", { className: "grid md:grid-cols-2 gap-3", children: [_jsxs("div", { className: "rounded border border-border bg-background p-4", children: [_jsx("div", { className: "text-xs font-medium uppercase tracking-[0.04em] text-brand-forest mb-2", children: "Do" }), _jsxs("ul", { className: "text-sm text-foreground space-y-1.5", children: [_jsx("li", { children: "\u00B7 Default to text-sm (13px)" }), _jsx("li", { children: "\u00B7 Tables for any list of 3+ structured rows" }), _jsx("li", { children: "\u00B7 1px borders for separation" }), _jsx("li", { children: "\u00B7 Forest first, lime for the loudest CTA only" }), _jsx("li", { children: "\u00B7 Right-align numbers + add .tabular" }), _jsx("li", { children: "\u00B7 Skeletons over spinners" }), _jsx("li", { children: "\u00B7 Pair status colors with icons or labels" })] })] }), _jsxs("div", { className: "rounded border border-border bg-background p-4", children: [_jsx("div", { className: "text-xs font-medium uppercase tracking-[0.04em] text-danger mb-2", children: "Don't" }), _jsxs("ul", { className: "text-sm text-foreground space-y-1.5", children: [_jsx("li", { children: "\u00B7 rounded-xl / rounded-2xl anywhere" }), _jsx("li", { children: "\u00B7 White text on lime" }), _jsx("li", { children: "\u00B7 Multiple accent (lime) buttons on one screen" }), _jsx("li", { children: "\u00B7 Emojis in product UI" }), _jsx("li", { children: "\u00B7 Scale-on-hover or bouncy animations" }), _jsx("li", { children: "\u00B7 Tinted card backgrounds to indicate stacking" }), _jsx("li", { children: "\u00B7 Title Case in UI labels" })] })] })] }) })] }));
|
|
657
|
+
}
|
|
658
|
+
/* ─────────────────────────────────────────────────────────────────────── */
|
|
659
|
+
/* Batch A — Forms */
|
|
660
|
+
/* ─────────────────────────────────────────────────────────────────────── */
|
|
661
|
+
function CheckboxSection() {
|
|
662
|
+
const [marketing, setMarketing] = React.useState(false);
|
|
663
|
+
return (_jsx(DocSection, { id: "checkbox", title: "Checkbox", description: "Square 16px control. Brand-forest fill when checked. Supports `indeterminate` (e.g., partial selection in a tree). Always pair with a Label or use FormField.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: "ck-waiver", defaultChecked: true }), _jsx(Label, { htmlFor: "ck-waiver", className: "normal-case tracking-normal text-sm font-normal cursor-pointer", children: "I've read and accept the waiver" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: "ck-credit" }), _jsx(Label, { htmlFor: "ck-credit", className: "normal-case tracking-normal text-sm font-normal cursor-pointer", children: "Apply $6.00 credit at checkout" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: "ck-ind", checked: marketing, onCheckedChange: setMarketing }), _jsx(Label, { htmlFor: "ck-ind", className: "normal-case tracking-normal text-sm font-normal cursor-pointer", children: "Marketing emails (indeterminate when toggled)" }), _jsx(Button, { size: "xs", variant: "ghost", onClick: () => setMarketing(marketing === "indeterminate" ? false : "indeterminate"), children: "toggle" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: "ck-disabled", disabled: true, defaultChecked: true }), _jsx(Label, { htmlFor: "ck-disabled", className: "normal-case tracking-normal text-sm font-normal opacity-60", children: "Disabled (locked in)" })] })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<div className="flex items-center gap-2">
|
|
664
|
+
<Checkbox id="ck" defaultChecked />
|
|
665
|
+
<Label htmlFor="ck">I've read and accept the waiver</Label>
|
|
666
|
+
</div>
|
|
667
|
+
|
|
668
|
+
{/* Indeterminate state */}
|
|
669
|
+
<Checkbox checked="indeterminate" onCheckedChange={…} />` }) }) }));
|
|
670
|
+
}
|
|
671
|
+
function RadioSection() {
|
|
672
|
+
const [skill, setSkill] = React.useState("mixed");
|
|
673
|
+
return (_jsx(DocSection, { id: "radio", title: "Radio group", description: "Pick one from a small set. Keyboard nav (Arrow / Home / End) handled by Radix. For >5 options, prefer Select.", children: _jsx(PreviewCode, { preview: _jsx(RadioGroup, { value: skill, onValueChange: setSkill, children: [
|
|
674
|
+
["beginner", "Beginner", "Learning the basics"],
|
|
675
|
+
["intermediate", "Intermediate", "Comfortable with the rules"],
|
|
676
|
+
["mixed", "Mixed skill", "Open to anyone"],
|
|
677
|
+
["advanced", "Advanced", "Competitive only"],
|
|
678
|
+
].map(([value, title, desc]) => (_jsxs("div", { className: "flex items-start gap-2.5", children: [_jsx(RadioGroupItem, { value: value, id: `rg-${value}`, className: "mt-0.5" }), _jsxs(Label, { htmlFor: `rg-${value}`, className: "normal-case tracking-normal cursor-pointer", children: [_jsx("span", { className: "block text-sm font-medium text-foreground", children: title }), _jsx("span", { className: "block text-xs text-text-muted mt-0.5", children: desc })] })] }, value))) }), code: _jsx(CodeBlock, { language: "tsx", code: `<RadioGroup value={skill} onValueChange={setSkill}>
|
|
679
|
+
<div className="flex items-center gap-2">
|
|
680
|
+
<RadioGroupItem value="beginner" id="b" />
|
|
681
|
+
<Label htmlFor="b">Beginner</Label>
|
|
682
|
+
</div>
|
|
683
|
+
<div className="flex items-center gap-2">
|
|
684
|
+
<RadioGroupItem value="advanced" id="a" />
|
|
685
|
+
<Label htmlFor="a">Advanced</Label>
|
|
686
|
+
</div>
|
|
687
|
+
</RadioGroup>` }) }) }));
|
|
688
|
+
}
|
|
689
|
+
function SwitchSection() {
|
|
690
|
+
return (_jsx(DocSection, { id: "switch", title: "Switch", description: "For instantly-applied toggles (notifications, quiet hours, theme). Use Checkbox for form fields that need an explicit Save.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "space-y-3 max-w-md", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Label, { htmlFor: "sw-credits", className: "normal-case tracking-normal text-sm font-normal flex-1 cursor-pointer", children: "Auto-apply credits at checkout" }), _jsx(Switch, { id: "sw-credits", defaultChecked: true })] }), _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "flex-1", children: [_jsx(Label, { htmlFor: "sw-quiet", className: "normal-case tracking-normal text-sm font-normal cursor-pointer", children: "SMS quiet hours" }), _jsx("p", { className: "text-xs text-text-muted mt-0.5", children: "No SMS 9 pm \u2013 8 am local" })] }), _jsx(Switch, { id: "sw-quiet", defaultChecked: true })] }), _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Label, { htmlFor: "sw-disabled", className: "normal-case tracking-normal text-sm font-normal flex-1 opacity-60", children: "Push notifications (not yet enabled)" }), _jsx(Switch, { id: "sw-disabled", disabled: true })] })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<div className="flex items-center justify-between">
|
|
691
|
+
<Label htmlFor="sw-credits">Auto-apply credits</Label>
|
|
692
|
+
<Switch id="sw-credits" defaultChecked />
|
|
693
|
+
</div>` }) }) }));
|
|
694
|
+
}
|
|
695
|
+
function SelectSection() {
|
|
696
|
+
const [venue, setVenue] = React.useState("");
|
|
697
|
+
return (_jsx(DocSection, { id: "select", title: "Select", description: "Keyboard-accessible dropdown. Use for venue/field/sport pickers and other curated lists. Supports groups, labels, separators.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "max-w-xs", children: [_jsxs(Select, { value: venue, onValueChange: setVenue, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Pick a venue" }) }), _jsxs(SelectContent, { children: [_jsxs(SelectGroup, { children: [_jsx(SelectLabel, { children: "NYC" }), _jsx(SelectItem, { value: "astoria", children: "Astoria Turf" }), _jsx(SelectItem, { value: "mccarren", children: "McCarren Park" })] }), _jsx(SelectSeparator, {}), _jsxs(SelectGroup, { children: [_jsx(SelectLabel, { children: "Coming soon" }), _jsx(SelectItem, { value: "williamsburg", disabled: true, children: "Williamsburg Field" }), _jsx(SelectItem, { value: "bushwick", disabled: true, children: "Bushwick Park" })] })] })] }), venue && (_jsxs("p", { className: "mt-2 text-xs text-text-muted tabular", children: ["Selected: ", _jsx("span", { className: "font-mono", children: venue })] }))] }), code: _jsx(CodeBlock, { language: "tsx", code: `<Select value={venue} onValueChange={setVenue}>
|
|
698
|
+
<SelectTrigger>
|
|
699
|
+
<SelectValue placeholder="Pick a venue" />
|
|
700
|
+
</SelectTrigger>
|
|
701
|
+
<SelectContent>
|
|
702
|
+
<SelectGroup>
|
|
703
|
+
<SelectLabel>NYC</SelectLabel>
|
|
704
|
+
<SelectItem value="astoria">Astoria Turf</SelectItem>
|
|
705
|
+
<SelectItem value="mccarren">McCarren Park</SelectItem>
|
|
706
|
+
</SelectGroup>
|
|
707
|
+
</SelectContent>
|
|
708
|
+
</Select>` }) }) }));
|
|
709
|
+
}
|
|
710
|
+
function TextareaSection() {
|
|
711
|
+
return (_jsx(DocSection, { id: "textarea", title: "Textarea", description: "Multi-line input. Same focus / aria-invalid pattern as Input. Defaults to vertical resize only.", children: _jsx(PreviewCode, { preview: _jsx(Textarea, { defaultValue: `- Turf shoes or flats only — no metal cleats.
|
|
712
|
+
- Bring two shirt colors (dark / light).
|
|
713
|
+
- Arrive 10 minutes before start.`, placeholder: "House rules\u2026", className: "max-w-md", rows: 5 }), code: _jsx(CodeBlock, { language: "tsx", code: `<Textarea
|
|
714
|
+
placeholder="House rules…"
|
|
715
|
+
defaultValue="- Turf shoes only…"
|
|
716
|
+
rows={5}
|
|
717
|
+
/>
|
|
718
|
+
|
|
719
|
+
{/* Error state */}
|
|
720
|
+
<Textarea aria-invalid="true" />` }) }) }));
|
|
721
|
+
}
|
|
722
|
+
function NumberInputSection() {
|
|
723
|
+
const [capacity, setCapacity] = React.useState(10);
|
|
724
|
+
const [price, setPrice] = React.useState(14);
|
|
725
|
+
return (_jsx(DocSection, { id: "number-input", title: "Number input", description: "Stepper-style numeric input with +/\u2212 buttons. Use for capacity, min-to-run, price, quantity. Always set min/max for safety.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "flex items-end gap-4", children: [_jsxs("div", { children: [_jsx(Label, { htmlFor: "ni-cap", children: "Capacity" }), _jsx(NumberInput, { id: "ni-cap", value: capacity, onChange: setCapacity, min: 2, max: 22, step: 1, className: "mt-1.5" })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "ni-price", children: "Base price ($)" }), _jsx(NumberInput, { id: "ni-price", value: price, onChange: setPrice, min: 0, max: 100, step: 1, className: "mt-1.5" })] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
726
|
+
setCapacity(10);
|
|
727
|
+
setPrice(14);
|
|
728
|
+
}, children: "Reset" })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<NumberInput
|
|
729
|
+
value={capacity}
|
|
730
|
+
onChange={setCapacity}
|
|
731
|
+
min={2}
|
|
732
|
+
max={22}
|
|
733
|
+
step={1}
|
|
734
|
+
/>` }) }) }));
|
|
735
|
+
}
|
|
736
|
+
function FormFieldSection() {
|
|
737
|
+
return (_jsxs(DocSection, { id: "form-field", title: "Form field", description: "Composition wrapper. Auto-generates an id, links the Label, and injects aria-describedby / aria-invalid into the control. Switch between helper and error text via props.", children: [_jsx(Example, { children: _jsxs("form", { className: "grid gap-4 max-w-md", onSubmit: (e) => {
|
|
738
|
+
e.preventDefault();
|
|
739
|
+
toast.success("Form submitted (demo).");
|
|
740
|
+
}, noValidate: true, children: [_jsx(FormField, { label: "Email", required: true, helper: "We'll send booking confirmations here.", children: _jsx(Input, { type: "email", placeholder: "you@dtsports.tv", autoComplete: "email", required: true }) }), _jsx(FormField, { label: "Skill level", helper: "Helps us match you with the right games.", children: _jsxs(Select, { defaultValue: "mixed", children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "beginner", children: "Beginner" }), _jsx(SelectItem, { value: "intermediate", children: "Intermediate" }), _jsx(SelectItem, { value: "mixed", children: "Mixed" }), _jsx(SelectItem, { value: "advanced", children: "Advanced" })] })] }) }), _jsx(FormField, { label: "House rules", helper: "Markdown, one bullet per rule.", children: _jsx(Textarea, { rows: 4, defaultValue: "- Turf shoes only.\\n- Bring two shirt colors." }) }), _jsx(FormField, { label: "Card number", required: true, error: "Card number is too short.", children: _jsx(Input, { defaultValue: "4242 4242" }) }), _jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [_jsx(Button, { variant: "ghost", type: "button", children: "Cancel" }), _jsx(Button, { variant: "accent", type: "submit", children: "Submit" })] })] }) }), _jsx(CodeBlock, { language: "tsx", code: `<FormField
|
|
741
|
+
label="Email"
|
|
742
|
+
required
|
|
743
|
+
helper="We'll send booking confirmations here."
|
|
744
|
+
>
|
|
745
|
+
<Input type="email" autoComplete="email" />
|
|
746
|
+
</FormField>
|
|
747
|
+
|
|
748
|
+
{/* Error replaces helper, sets aria-invalid + aria-describedby */}
|
|
749
|
+
<FormField label="Card" required error="Card number is too short.">
|
|
750
|
+
<Input />
|
|
751
|
+
</FormField>` })] }));
|
|
752
|
+
}
|
|
753
|
+
function AlertSection() {
|
|
754
|
+
const [dismissed, setDismissed] = React.useState(false);
|
|
755
|
+
return (_jsxs(DocSection, { id: "alert", title: "Alert", description: "Inline status messages that stay put. Use Toast for transient feedback; use Alert when the message belongs inline. Danger variant uses role='alert' for SR interruption.", children: [_jsxs("div", { className: "space-y-3 max-w-2xl", children: [_jsx(Alert, { variant: "info", title: "DT Sports TV streams on YouTube.", children: "Paste any YouTube URL when creating a game \u2014 we embed it automatically." }), _jsx(Alert, { variant: "warning", title: "1 game below minimum to run", children: "Astoria \u00B7 Field B \u00B7 8 pm has 2/6. Promote from waitlist or cancel + refund before 6 pm." }), _jsx(Alert, { variant: "danger", title: "Payment failed", children: "Your bank declined the charge. Try a different card or contact your bank." }), _jsx(Alert, { variant: "success", title: "Booking confirmed.", children: "Saturday, May 25 at 6:00 pm at Astoria Turf \u00B7 Field A." }), !dismissed && (_jsx(Alert, { variant: "info", title: "Dismissible", dismissible: true, onDismiss: () => setDismissed(true), children: "Set `dismissible` and provide `onDismiss` for a close button." })), dismissed && (_jsx(Button, { size: "sm", variant: "ghost", onClick: () => setDismissed(false), children: "Restore dismissed alert" }))] }), _jsx(CodeBlock, { language: "tsx", code: `<Alert variant="warning" title="1 game below minimum">
|
|
756
|
+
Astoria Field B at 8 pm needs 4 more.
|
|
757
|
+
</Alert>
|
|
758
|
+
|
|
759
|
+
<Alert variant="info" dismissible onDismiss={() => …}>
|
|
760
|
+
…
|
|
761
|
+
</Alert>` })] }));
|
|
762
|
+
}
|
|
763
|
+
/* ─────────────────────────────────────────────────────────────────────── */
|
|
764
|
+
/* Mid-page additions */
|
|
765
|
+
/* ─────────────────────────────────────────────────────────────────────── */
|
|
766
|
+
function CapacitySection() {
|
|
767
|
+
return (_jsx(DocSection, { id: "capacity", title: "Capacity bar", description: "The spots-filled visualization used everywhere games appear. Three colors: forest (normal), warning (below minToRun), foreground (full). Includes role='progressbar' and a default aria-label.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "space-y-3 max-w-md", children: [_jsx(CapacityBar, { filled: 3, capacity: 10, minToRun: 6 }), _jsx(CapacityBar, { filled: 5, capacity: 10, minToRun: 6 }), _jsx(CapacityBar, { filled: 8, capacity: 10, minToRun: 6 }), _jsx(CapacityBar, { filled: 10, capacity: 10, minToRun: 6 }), _jsx("div", { className: "pt-2", children: _jsx(CapacityBar, { filled: 4, capacity: 10, minToRun: 6, size: "lg" }) })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<CapacityBar filled={8} capacity={10} minToRun={6} />
|
|
768
|
+
|
|
769
|
+
{/* Hide count */}
|
|
770
|
+
<CapacityBar filled={3} capacity={10} showCount={false} />
|
|
771
|
+
|
|
772
|
+
{/* Larger */}
|
|
773
|
+
<CapacityBar filled={4} capacity={10} size="lg" />` }) }) }));
|
|
774
|
+
}
|
|
775
|
+
function TabsSection() {
|
|
776
|
+
return (_jsxs(DocSection, { id: "tabs", title: "Tabs", description: "Underline-style horizontal tabs. Keyboard nav (\u2190/\u2192/Home/End) handled by Radix. Use for time-window filters or status filters in dashboards.", children: [_jsxs(Tabs, { defaultValue: "today", className: "w-full", children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "today", children: "Today" }), _jsxs(TabsTrigger, { value: "week", children: ["This week", _jsx(Badge, { variant: "neutral", size: "xs", children: "12" })] }), _jsx(TabsTrigger, { value: "past", children: "Past" }), _jsx(TabsTrigger, { value: "cancelled", disabled: true, children: "Cancelled" })] }), _jsx(TabsContent, { value: "today", children: _jsx("div", { className: "rounded border border-border bg-background p-4 text-sm text-text-muted", children: "6 games scheduled for today." }) }), _jsx(TabsContent, { value: "week", children: _jsx("div", { className: "rounded border border-border bg-background p-4 text-sm text-text-muted", children: "12 games this week across 2 venues." }) }), _jsx(TabsContent, { value: "past", children: _jsx("div", { className: "rounded border border-border bg-background p-4 text-sm text-text-muted", children: "Past games would render here." }) })] }), _jsx(CodeBlock, { language: "tsx", code: `<Tabs defaultValue="today">
|
|
777
|
+
<TabsList>
|
|
778
|
+
<TabsTrigger value="today">Today</TabsTrigger>
|
|
779
|
+
<TabsTrigger value="week">This week</TabsTrigger>
|
|
780
|
+
</TabsList>
|
|
781
|
+
<TabsContent value="today">…</TabsContent>
|
|
782
|
+
<TabsContent value="week">…</TabsContent>
|
|
783
|
+
</Tabs>` })] }));
|
|
784
|
+
}
|
|
785
|
+
function AccordionSection() {
|
|
786
|
+
return (_jsxs(DocSection, { id: "accordion", title: "Accordion", description: "Stacked expandable sections. `type='single' collapsible` for FAQ-style, `type='multiple'` for settings panels. The chevron rotates on open.", children: [_jsxs(Accordion, { type: "single", collapsible: true, className: "max-w-2xl rounded border border-border bg-background px-4", children: [_jsxs(AccordionItem, { value: "rules", children: [_jsx(AccordionTrigger, { children: "House rules \u2014 Astoria Turf" }), _jsx(AccordionContent, { children: _jsxs("ul", { className: "text-sm text-text-muted space-y-1.5", children: [_jsx("li", { children: "\u00B7 Turf shoes or flats only \u2014 no metal cleats." }), _jsx("li", { children: "\u00B7 Bring two shirt colors (dark / light)." }), _jsx("li", { children: "\u00B7 Arrive 10 minutes before start for check-in." }), _jsx("li", { children: "\u00B7 No spectators on the turf \u2014 sideline benches only." })] }) })] }), _jsxs(AccordionItem, { value: "policy", children: [_jsx(AccordionTrigger, { children: "Cancellation policy" }), _jsx(AccordionContent, { children: _jsx("p", { className: "text-sm text-text-muted", children: "Refunds are issued as DT Sports credit (instant). Cancel 24h+ for full credit, less than 2h for none. DT Sports cancellations are always 100%." }) })] }), _jsxs(AccordionItem, { value: "about", children: [_jsx(AccordionTrigger, { children: "About this format" }), _jsx(AccordionContent, { children: _jsx("p", { className: "text-sm text-text-muted", children: "5v5 turf soccer, 90 minutes, two halves. We balance teams on-site after warm-ups." }) })] })] }), _jsx(CodeBlock, { language: "tsx", code: `<Accordion type="single" collapsible>
|
|
787
|
+
<AccordionItem value="rules">
|
|
788
|
+
<AccordionTrigger>House rules</AccordionTrigger>
|
|
789
|
+
<AccordionContent>…</AccordionContent>
|
|
790
|
+
</AccordionItem>
|
|
791
|
+
</Accordion>` })] }));
|
|
792
|
+
}
|
|
793
|
+
function DrawerSection() {
|
|
794
|
+
return (_jsxs(DocSection, { id: "drawer", title: "Drawer", description: "Side-pushed panel for secondary tasks. Use instead of nested dialogs (per \u00A73.17). Default side is right; `side='bottom'` for mobile filter sheets.", children: [_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsxs(Drawer, { children: [_jsx(DrawerTrigger, { asChild: true, children: _jsxs(Button, { variant: "secondary", children: [_jsx(Edit, { className: "h-4 w-4", strokeWidth: 1.75 }), "Edit game (right)"] }) }), _jsxs(DrawerContent, { side: "right", children: [_jsxs(DrawerHeader, { children: [_jsx(DrawerTitle, { children: "Edit game" }), _jsx(DrawerDescription, { children: "Astoria Turf \u00B7 Field A \u00B7 Sat May 25, 6:00 pm" })] }), _jsx(DrawerBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(FormField, { label: "Capacity", children: _jsx(NumberInput, { defaultValue: 10, min: 2, max: 22 }) }), _jsx(FormField, { label: "Base price", children: _jsx(NumberInput, { defaultValue: 14, min: 0, max: 100 }) }), _jsx(FormField, { label: "Notes", helper: "Visible to players in confirmation email.", children: _jsx(Textarea, { rows: 3, placeholder: "Optional\u2026" }) })] }) }), _jsxs(DrawerFooter, { children: [_jsx(Button, { variant: "ghost", size: "sm", children: "Cancel" }), _jsx(Button, { variant: "primary", size: "sm", children: "Save changes" })] })] })] }), _jsxs(Drawer, { children: [_jsx(DrawerTrigger, { asChild: true, children: _jsx(Button, { variant: "secondary", children: "Filters (bottom)" }) }), _jsxs(DrawerContent, { side: "bottom", children: [_jsx(DrawerHeader, { children: _jsx(DrawerTitle, { children: "Filters" }) }), _jsx(DrawerBody, { children: _jsx("p", { className: "text-sm text-text-muted", children: "A bottom drawer is the right pattern for mobile filter sheets." }) })] })] })] }), _jsx(CodeBlock, { language: "tsx", code: `<Drawer>
|
|
795
|
+
<DrawerTrigger asChild><Button>Edit…</Button></DrawerTrigger>
|
|
796
|
+
<DrawerContent side="right">
|
|
797
|
+
<DrawerHeader>
|
|
798
|
+
<DrawerTitle>Edit game</DrawerTitle>
|
|
799
|
+
<DrawerDescription>Astoria Turf · Field A</DrawerDescription>
|
|
800
|
+
</DrawerHeader>
|
|
801
|
+
<DrawerBody>…</DrawerBody>
|
|
802
|
+
<DrawerFooter>
|
|
803
|
+
<Button variant="ghost">Cancel</Button>
|
|
804
|
+
<Button>Save</Button>
|
|
805
|
+
</DrawerFooter>
|
|
806
|
+
</DrawerContent>
|
|
807
|
+
</Drawer>` })] }));
|
|
808
|
+
}
|
|
809
|
+
function PopoverSection() {
|
|
810
|
+
return (_jsxs(DocSection, { id: "popover", title: "Popover", description: "Generic click-to-open floating panel. Use for date pickers, mini filter menus, inline 'quick edit' forms. For action menus, use DropdownMenu.", children: [_jsx("div", { className: "flex flex-wrap gap-2", children: _jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "secondary", size: "sm", children: [_jsx(Info, { className: "h-4 w-4", strokeWidth: 1.75 }), "Quick add player"] }) }), _jsx(PopoverContent, { children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("h4", { className: "text-sm font-semibold", children: "Walk-in player" }), _jsx("p", { className: "text-xs text-text-muted mt-0.5", children: "Add a player who showed up without booking." })] }), _jsx(FormField, { label: "Name", children: _jsx(Input, { placeholder: "Alex Chen" }) }), _jsx(FormField, { label: "Charge", children: _jsx(NumberInput, { defaultValue: 14, min: 0, max: 50 }) }), _jsx(Button, { size: "sm", variant: "accent", className: "w-full", children: "Add & charge" })] }) })] }) }), _jsx(CodeBlock, { language: "tsx", code: `<Popover>
|
|
811
|
+
<PopoverTrigger asChild><Button>Quick add</Button></PopoverTrigger>
|
|
812
|
+
<PopoverContent>
|
|
813
|
+
<h4>Walk-in player</h4>
|
|
814
|
+
…
|
|
815
|
+
</PopoverContent>
|
|
816
|
+
</Popover>` })] }));
|
|
817
|
+
}
|
|
818
|
+
function DropdownSection() {
|
|
819
|
+
const [autoApply, setAutoApply] = React.useState(true);
|
|
820
|
+
return (_jsxs(DocSection, { id: "dropdown", title: "Dropdown menu", description: "Actions menu (kebab on table rows, user avatar menu). Supports labels, separators, checkbox items, sub-menus, keyboard shortcuts. NOT for selecting a value \u2014 use Select for that.", children: [_jsxs("div", { className: "flex flex-wrap gap-3", children: [_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "More actions", children: _jsx(MoreHorizontal, { className: "h-4 w-4", strokeWidth: 1.75 }) }) }), _jsxs(DropdownMenuContent, { align: "start", children: [_jsx(DropdownMenuLabel, { children: "Game \u00B7 Astoria \u00B7 6 pm" }), _jsxs(DropdownMenuItem, { onSelect: () => toast("View roster."), children: [_jsx(Users, { className: "h-3.5 w-3.5", strokeWidth: 1.75 }), "View roster", _jsx(DropdownMenuShortcut, { children: "\u2318R" })] }), _jsxs(DropdownMenuItem, { onSelect: () => toast("Edit game."), children: [_jsx(Edit, { className: "h-3.5 w-3.5", strokeWidth: 1.75 }), "Edit", _jsx(DropdownMenuShortcut, { children: "\u2318E" })] }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuCheckboxItem, { checked: autoApply, onCheckedChange: (c) => setAutoApply(!!c), children: "Auto-promote waitlist" }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "text-danger focus:text-danger focus:bg-danger/10", onSelect: () => toast.error("Cancel-game flow not built yet."), children: [_jsx(Trash2, { className: "h-3.5 w-3.5", strokeWidth: 1.75 }), "Cancel game\u2026"] })] })] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "secondary", size: "sm", children: [_jsx(Settings, { className: "h-3.5 w-3.5", strokeWidth: 1.75 }), "Settings", _jsx(ChevronDown, { className: "h-3 w-3 opacity-60", strokeWidth: 1.75 })] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuLabel, { children: "Profile" }), _jsx(DropdownMenuItem, { children: "Edit profile" }), _jsx(DropdownMenuItem, { children: "Notification preferences" }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "text-danger focus:text-danger focus:bg-danger/10", children: ["Sign out", _jsx(DropdownMenuShortcut, { children: "\u2318Q" })] })] })] })] }), _jsx(CodeBlock, { language: "tsx", code: `<DropdownMenu>
|
|
821
|
+
<DropdownMenuTrigger asChild>
|
|
822
|
+
<Button size="icon"><MoreHorizontal /></Button>
|
|
823
|
+
</DropdownMenuTrigger>
|
|
824
|
+
<DropdownMenuContent>
|
|
825
|
+
<DropdownMenuLabel>Game</DropdownMenuLabel>
|
|
826
|
+
<DropdownMenuItem>View roster</DropdownMenuItem>
|
|
827
|
+
<DropdownMenuItem>Edit</DropdownMenuItem>
|
|
828
|
+
<DropdownMenuSeparator />
|
|
829
|
+
<DropdownMenuCheckboxItem checked={auto} onCheckedChange={setAuto}>
|
|
830
|
+
Auto-promote waitlist
|
|
831
|
+
</DropdownMenuCheckboxItem>
|
|
832
|
+
<DropdownMenuSeparator />
|
|
833
|
+
<DropdownMenuItem className="text-danger">Cancel…</DropdownMenuItem>
|
|
834
|
+
</DropdownMenuContent>
|
|
835
|
+
</DropdownMenu>` })] }));
|
|
836
|
+
}
|
|
837
|
+
function TooltipSection() {
|
|
838
|
+
return (_jsxs(DocSection, { id: "tooltip", title: "Tooltip", description: "Keyboard-accessible hover hints. Preferred over native title= (mouse-only). The TooltipProvider is already wired into ThemeProvider \u2014 no extra setup needed in your app.", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "View roster", children: _jsx(Users, { className: "h-4 w-4", strokeWidth: 1.75 }) }) }), _jsx(TooltipContent, { children: "View roster" })] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Edit game", children: _jsx(Edit, { className: "h-4 w-4", strokeWidth: 1.75 }) }) }), _jsx(TooltipContent, { children: "Edit game \u00B7 \u2318E" })] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Delete", children: _jsx(Trash2, { className: "h-4 w-4 text-danger", strokeWidth: 1.75 }) }) }), _jsx(TooltipContent, { children: "Delete (no undo)" })] }), _jsx("span", { className: "text-xs text-text-muted ml-3", children: "Tab through them \u2014 tooltips appear on keyboard focus, not just mouse." })] }), _jsx(CodeBlock, { language: "tsx", code: `<Tooltip>
|
|
839
|
+
<TooltipTrigger asChild>
|
|
840
|
+
<Button size="icon" aria-label="View roster">
|
|
841
|
+
<Users />
|
|
842
|
+
</Button>
|
|
843
|
+
</TooltipTrigger>
|
|
844
|
+
<TooltipContent>View roster</TooltipContent>
|
|
845
|
+
</Tooltip>` })] }));
|
|
846
|
+
}
|
|
847
|
+
function SeparatorSection() {
|
|
848
|
+
return (_jsxs(DocSection, { id: "separator", title: "Separator", description: "Thin divider (<hr> horizontal, vertical bar). Decorative by default; set decorative={false} when it semantically separates groups.", children: [_jsxs("div", { className: "rounded border border-border bg-background p-4 max-w-md", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-sm font-semibold", children: "Saturday's games" }), _jsx("p", { className: "text-xs text-text-muted", children: "3 venues \u00B7 6 games" })] }), _jsx(Separator, { className: "my-3" }), _jsxs("div", { className: "flex h-5 items-center gap-3 text-sm", children: [_jsx("div", { className: "tabular", children: "6:00 pm" }), _jsx(Separator, { orientation: "vertical" }), _jsx("div", { children: "Astoria \u00B7 Field A" }), _jsx(Separator, { orientation: "vertical" }), _jsx("div", { className: "tabular text-text-muted", children: "8/10" })] })] }), _jsx(CodeBlock, { language: "tsx", code: `<Separator /> {/* horizontal */}
|
|
849
|
+
<Separator orientation="vertical" />` })] }));
|
|
850
|
+
}
|
|
851
|
+
function SpinnerSection() {
|
|
852
|
+
return (_jsx(DocSection, { id: "spinner", title: "Spinner", description: "Tiny inline loader for buttons and per-row 'working' state. Prefer Skeleton for full-region loading (per \u00A73.14).", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Spinner, { size: "sm" }), _jsx(Spinner, {}), _jsx(Spinner, { size: "lg" }), _jsxs(Button, { variant: "primary", disabled: true, children: [_jsx(Spinner, { size: "sm" }), " Processing payment\u2026"] })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<Spinner size="sm" />
|
|
853
|
+
<Spinner />
|
|
854
|
+
<Spinner size="lg" />
|
|
855
|
+
|
|
856
|
+
{/* In a button */}
|
|
857
|
+
<Button disabled>
|
|
858
|
+
<Spinner size="sm" /> Processing…
|
|
859
|
+
</Button>` }) }) }));
|
|
860
|
+
}
|
|
861
|
+
function ProgressSection() {
|
|
862
|
+
const [value, setValue] = React.useState(33);
|
|
863
|
+
return (_jsx(DocSection, { id: "progress", title: "Progress", description: "For multi-step flows with a known percent (waiver \u2192 emergency contact \u2192 done; file uploads). For spots filled, use CapacityBar. For unknown duration, use Spinner or Skeleton.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "space-y-4 max-w-md", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between mb-1.5", children: [_jsx("span", { className: "text-xs text-text-muted", children: "Step 1 of 3 \u2014 Profile" }), _jsxs("span", { className: "text-xs tabular font-medium", children: [value, "%"] })] }), _jsx(Progress, { value: value })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: () => setValue(Math.max(0, value - 33)), children: "Back" }), _jsx(Button, { size: "sm", variant: "primary", onClick: () => setValue(Math.min(100, value + 33)), children: "Next" })] })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<Progress value={66} />
|
|
864
|
+
|
|
865
|
+
{/* With label + percent */}
|
|
866
|
+
<div className="flex justify-between text-xs">
|
|
867
|
+
<span>Step 2 of 3</span>
|
|
868
|
+
<span>66%</span>
|
|
869
|
+
</div>
|
|
870
|
+
<Progress value={66} />` }) }) }));
|
|
871
|
+
}
|
|
872
|
+
/* ─────────────────────────────────────────────────────────────────────── */
|
|
873
|
+
/* Batch C — Data + display additions */
|
|
874
|
+
/* ─────────────────────────────────────────────────────────────────────── */
|
|
875
|
+
function ChipSection() {
|
|
876
|
+
const [filters, setFilters] = React.useState([
|
|
877
|
+
"Astoria Turf",
|
|
878
|
+
"Sat 5/24",
|
|
879
|
+
"5v5",
|
|
880
|
+
]);
|
|
881
|
+
const remove = (label) => setFilters(filters.filter((f) => f !== label));
|
|
882
|
+
return (_jsx(DocSection, { id: "chip", title: "Chip", description: "Small removable token \u2014 use for active filter indicators, applied tags, selected options in a multi-select summary. Distinct from Badge: Chips are interactive.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [_jsx("span", { className: "text-xs text-text-muted mr-1", children: "Active filters:" }), filters.length === 0 && (_jsx("span", { className: "text-xs text-text-subtle italic", children: "no filters \u00B7 click below to add some back" })), filters.map((f) => (_jsx(Chip, { onRemove: () => remove(f), children: f }, f)))] }), _jsxs("div", { className: "flex flex-wrap gap-1.5", children: [_jsx(Chip, { variant: "accent", children: "accent" }), _jsx(Chip, { variant: "neutral", children: "neutral" }), _jsx(Chip, { variant: "outline", children: "outline" }), _jsx(Chip, { variant: "accent", onRemove: () => { }, children: "with \u00D7 button" })] }), filters.length < 3 && (_jsx(Button, { size: "xs", variant: "ghost", onClick: () => setFilters(["Astoria Turf", "Sat 5/24", "5v5"]), children: "Reset" }))] }), code: _jsx(CodeBlock, { language: "tsx", code: `<Chip onRemove={() => removeFilter("astoria")}>
|
|
883
|
+
Astoria Turf
|
|
884
|
+
</Chip>
|
|
885
|
+
|
|
886
|
+
<Chip variant="neutral">Soccer</Chip>
|
|
887
|
+
<Chip variant="outline">5v5</Chip>` }) }) }));
|
|
888
|
+
}
|
|
889
|
+
function SpecSection() {
|
|
890
|
+
return (_jsx(DocSection, { id: "spec", title: "Spec list", description: "Semantic key-value display (<dl> / <dt> / <dd>). Use for 'About this game' panels, settings summaries, receipts. Grid columns 1\u20134.", children: _jsx(PreviewCode, { preview: _jsxs(Card, { className: "max-w-2xl", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-sm", children: "About this game" }) }), _jsx(CardContent, { children: _jsxs(SpecList, { columns: 2, children: [_jsx(Spec, { label: "Sport", children: "Soccer" }), _jsx(Spec, { label: "Format", children: "5v5" }), _jsx(Spec, { label: "Skill level", children: "Mixed" }), _jsx(Spec, { label: "Capacity", children: _jsx("span", { className: "tabular", children: "10 players (6 min to run)" }) }), _jsx(Spec, { label: "Venue", children: "Astoria Turf \u00B7 Field A" }), _jsx(Spec, { label: "Address", children: "19-04 30th Dr, Astoria, NY" })] }) })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<SpecList columns={2}>
|
|
891
|
+
<Spec label="Format">5v5</Spec>
|
|
892
|
+
<Spec label="Capacity">10 players</Spec>
|
|
893
|
+
<Spec label="Start"><Timestamp iso={…} /></Spec>
|
|
894
|
+
</SpecList>` }) }) }));
|
|
895
|
+
}
|
|
896
|
+
function PaginationSection() {
|
|
897
|
+
const [page, setPage] = React.useState(1);
|
|
898
|
+
return (_jsxs(DocSection, { id: "pagination", title: "Pagination", description: "Use when total > 1. For URL-driven pagination per \u00A73.9, wire onPageChange to router.replace(?page=N) \u2014 that gives you a shareable + back-button-safe URL.", children: [_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-xs text-text-muted tabular", children: ["Page ", page, " of 28"] }), _jsx(Pagination, { currentPage: page, totalPages: 28, onPageChange: setPage })] }), _jsx("div", { className: "text-xs text-text-subtle", children: "Examples below for different total counts:" }), _jsxs("div", { className: "space-y-2", children: [_jsx(Pagination, { currentPage: 1, totalPages: 3, onPageChange: () => { } }), _jsx(Pagination, { currentPage: 1, totalPages: 10, onPageChange: () => { } }), _jsx(Pagination, { currentPage: 50, totalPages: 100, onPageChange: () => { } })] })] }), _jsx(CodeBlock, { language: "tsx", code: `<Pagination
|
|
899
|
+
currentPage={page}
|
|
900
|
+
totalPages={totalPages}
|
|
901
|
+
onPageChange={setPage}
|
|
902
|
+
/>
|
|
903
|
+
|
|
904
|
+
{/* URL-driven (recommended) */}
|
|
905
|
+
const setPage = (p: number) =>
|
|
906
|
+
router.replace(\`?page=\${p}\`, { scroll: false });` })] }));
|
|
907
|
+
}
|
|
908
|
+
function AvatarGroupSection() {
|
|
909
|
+
return (_jsx(DocSection, { id: "avatar-group", title: "Avatar group", description: "Stacked overlapping avatars with a +N overflow pill. Use for roster previews and 'N people booked' indicators.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "space-y-3", children: [_jsxs(AvatarGroup, { max: 4, label: "8 players booked", children: [_jsx(Avatar, { name: "Alex Chen" }), _jsx(Avatar, { name: "Jordan Lee" }), _jsx(Avatar, { name: "Sam Patel", status: "checked-in" }), _jsx(Avatar, { name: "Maria Garcia" }), _jsx(Avatar, { name: "Danny Tran" }), _jsx(Avatar, { name: "Priya Singh" }), _jsx(Avatar, { name: "Ethan Brooks" }), _jsx(Avatar, { name: "Nina Park" })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(AvatarGroup, { max: 3, size: "sm", children: [_jsx(Avatar, { name: "Alex Chen", size: "sm" }), _jsx(Avatar, { name: "Jordan Lee", size: "sm" }), _jsx(Avatar, { name: "Sam Patel", size: "sm" }), _jsx(Avatar, { name: "Maria Garcia", size: "sm" }), _jsx(Avatar, { name: "Danny Tran", size: "sm" })] }), _jsx("span", { className: "text-xs text-text-muted", children: "Compact size for table rows." })] })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<AvatarGroup max={4} label="8 players booked">
|
|
910
|
+
<Avatar name="Alex Chen" />
|
|
911
|
+
<Avatar name="Jordan Lee" />
|
|
912
|
+
<Avatar name="Sam Patel" status="checked-in" />
|
|
913
|
+
…
|
|
914
|
+
</AvatarGroup>` }) }) }));
|
|
915
|
+
}
|
|
916
|
+
/* ─────────────────────────────────────────────────────────────────────── */
|
|
917
|
+
/* Batch C — Layout */
|
|
918
|
+
/* ─────────────────────────────────────────────────────────────────────── */
|
|
919
|
+
function AppShellSection() {
|
|
920
|
+
return (_jsxs(DocSection, { id: "app-shell", title: "App shell", description: "The full-height flex frame. AppShell + AppMain compose any sidebar-and-content app. Use sticky TopBar inside AppMain so it pins on scroll. The /admin route uses exactly this pattern.", children: [_jsx(CodeBlock, { language: "tsx", code: `<AppShell>
|
|
921
|
+
<Sidebar>…</Sidebar>
|
|
922
|
+
<AppMain>
|
|
923
|
+
<TopBar>
|
|
924
|
+
<Breadcrumb>…</Breadcrumb>
|
|
925
|
+
</TopBar>
|
|
926
|
+
<main className="flex-1 px-6 py-6">
|
|
927
|
+
{children}
|
|
928
|
+
</main>
|
|
929
|
+
</AppMain>
|
|
930
|
+
</AppShell>` }), _jsxs("p", { className: "text-xs text-text-muted", children: ["Live in ", _jsx("code", { className: "font-mono", children: "/admin" }), " \u2014 the route uses AppShell, Sidebar, and TopBar primitives."] })] }));
|
|
931
|
+
}
|
|
932
|
+
function SidebarComponentSection() {
|
|
933
|
+
return (_jsxs(DocSection, { id: "sidebar-comp", title: "Sidebar", description: "Composable nav sidebar. Use Sidebar / SidebarHeader / SidebarBody / SidebarFooter for the shell; SidebarSection + SidebarItem for the nav. SidebarItem supports asChild for Next.js Link integration.", children: [_jsx(Example, { label: "Sidebar in a 280px frame", children: _jsxs("div", { className: "border border-border-strong rounded overflow-hidden h-[420px] flex bg-background", children: [_jsxs("div", { className: "w-56 shrink-0 bg-bg-subtle/40 border-r border-border flex flex-col", children: [_jsx(SidebarHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-6 w-6 rounded-sm bg-brand-forest grid place-items-center text-brand-forest-foreground font-bold text-xs tabular", "aria-hidden": "true", children: "DT" }), _jsxs("div", { children: [_jsx("div", { className: "text-[10px] font-medium uppercase tracking-[0.06em] text-text-subtle", children: "DT Sports" }), _jsx("div", { className: "text-sm font-semibold leading-none", children: "Admin" })] })] }) }), _jsxs(SidebarBody, { children: [_jsxs(SidebarSection, { label: "Today", children: [_jsx(SidebarItem, { href: "#today", active: true, icon: _jsx(LayoutDashboard, { className: "h-4 w-4", strokeWidth: 1.75 }), children: "Today" }), _jsx(SidebarItem, { href: "#week", icon: _jsx(CalendarDays, { className: "h-4 w-4", strokeWidth: 1.75 }), children: "This week" })] }), _jsxs(SidebarSection, { label: "Manage", children: [_jsx(SidebarItem, { href: "#games", icon: _jsx(Trophy, { className: "h-4 w-4", strokeWidth: 1.75 }), children: "Games" }), _jsx(SidebarItem, { href: "#series", icon: _jsx(Repeat, { className: "h-4 w-4", strokeWidth: 1.75 }), children: "Series" }), _jsx(SidebarItem, { href: "#players", icon: _jsx(Users, { className: "h-4 w-4", strokeWidth: 1.75 }), badge: _jsx(Badge, { variant: "warning", size: "xs", children: "4" }), children: "Players" })] })] }), _jsx(SidebarFooter, { children: _jsx("div", { className: "text-[10px] text-text-subtle font-mono tabular", children: "v0.1 \u00B7 main" }) })] }), _jsx("div", { className: "flex-1 grid place-items-center text-text-subtle text-xs", children: "(page content)" })] }) }), _jsx(CodeBlock, { language: "tsx", code: `<Sidebar>
|
|
934
|
+
<SidebarHeader><BrandMark /></SidebarHeader>
|
|
935
|
+
<SidebarBody>
|
|
936
|
+
<SidebarSection label="Today">
|
|
937
|
+
<SidebarItem asChild active>
|
|
938
|
+
<Link href="/admin">
|
|
939
|
+
<LayoutDashboard className="h-4 w-4" strokeWidth={1.75} />
|
|
940
|
+
<span className="flex-1 truncate">Today</span>
|
|
941
|
+
</Link>
|
|
942
|
+
</SidebarItem>
|
|
943
|
+
</SidebarSection>
|
|
944
|
+
</SidebarBody>
|
|
945
|
+
<SidebarFooter>v0.1</SidebarFooter>
|
|
946
|
+
</Sidebar>` })] }));
|
|
947
|
+
}
|
|
948
|
+
function TopBarSection() {
|
|
949
|
+
return (_jsxs(DocSection, { id: "top-bar", title: "Top bar", description: "Sticky 48px header. Renders the chrome; content layout is the consumer's. Common pattern: breadcrumb left, search center, actions right.", children: [_jsx(Example, { children: _jsxs("div", { className: "border border-border-strong rounded overflow-hidden bg-background", children: [_jsxs(TopBar, { className: "static", children: [_jsx(Breadcrumb, { children: _jsxs(BreadcrumbList, { children: [_jsx(BreadcrumbItem, { children: _jsx(BreadcrumbLink, { href: "#", children: "Admin" }) }), _jsx(BreadcrumbSeparator, {}), _jsx(BreadcrumbItem, { children: _jsx(BreadcrumbPage, { children: "Today" }) })] }) }), _jsx("div", { className: "flex-1" }), _jsx(Badge, { variant: "forest", size: "sm", children: "v0.1" }), _jsx(Button, { size: "sm", variant: "ghost", "aria-label": "Settings", children: _jsx(Settings, { className: "h-4 w-4", strokeWidth: 1.75 }) })] }), _jsx("div", { className: "px-6 py-8 text-text-subtle text-xs", children: "(page content here)" })] }) }), _jsx(CodeBlock, { language: "tsx", code: `<TopBar>
|
|
950
|
+
<Breadcrumb>…</Breadcrumb>
|
|
951
|
+
<div className="flex-1" />
|
|
952
|
+
<ThemeToggle />
|
|
953
|
+
<Avatar name="Danny Tran" size="sm" />
|
|
954
|
+
</TopBar>` })] }));
|
|
955
|
+
}
|
|
956
|
+
function BreadcrumbSection() {
|
|
957
|
+
return (_jsx(DocSection, { id: "breadcrumb", title: "Breadcrumb", description: "Semantic <nav aria-label='Breadcrumb'> with ordered list, links, separator, and a non-link page indicator for the current location.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "space-y-3", children: [_jsx(Breadcrumb, { children: _jsxs(BreadcrumbList, { children: [_jsx(BreadcrumbItem, { children: _jsx(BreadcrumbLink, { href: "#", children: "Admin" }) }), _jsx(BreadcrumbSeparator, {}), _jsx(BreadcrumbItem, { children: _jsx(BreadcrumbPage, { children: "Today" }) })] }) }), _jsx(Breadcrumb, { children: _jsxs(BreadcrumbList, { children: [_jsx(BreadcrumbItem, { children: _jsx(BreadcrumbLink, { href: "#", children: "Admin" }) }), _jsx(BreadcrumbSeparator, {}), _jsx(BreadcrumbItem, { children: _jsx(BreadcrumbLink, { href: "#", children: "Games" }) }), _jsx(BreadcrumbSeparator, {}), _jsx(BreadcrumbItem, { children: _jsx(BreadcrumbLink, { href: "#", children: "Astoria Turf" }) }), _jsx(BreadcrumbSeparator, {}), _jsx(BreadcrumbItem, { children: _jsx(BreadcrumbPage, { children: "Field A \u00B7 Sat 6 pm" }) })] }) })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<Breadcrumb>
|
|
958
|
+
<BreadcrumbList>
|
|
959
|
+
<BreadcrumbItem>
|
|
960
|
+
<BreadcrumbLink asChild>
|
|
961
|
+
<Link href="/admin">Admin</Link>
|
|
962
|
+
</BreadcrumbLink>
|
|
963
|
+
</BreadcrumbItem>
|
|
964
|
+
<BreadcrumbSeparator />
|
|
965
|
+
<BreadcrumbItem>
|
|
966
|
+
<BreadcrumbPage>Today</BreadcrumbPage>
|
|
967
|
+
</BreadcrumbItem>
|
|
968
|
+
</BreadcrumbList>
|
|
969
|
+
</Breadcrumb>` }) }) }));
|
|
970
|
+
}
|
|
971
|
+
function ContainerSection() {
|
|
972
|
+
return (_jsxs(DocSection, { id: "container", title: "Container", description: "Max-width page wrapper with consistent horizontal padding (24/32px responsive). Sizes: sm (768) \u00B7 default (1024) \u00B7 lg (1280) \u00B7 full.", children: [_jsx("div", { className: "space-y-3", children: ["sm", "default", "lg", "full"].map((size) => (_jsx("div", { className: "border border-border rounded", children: _jsx(Container, { size: size, className: "py-4", children: _jsxs("div", { className: "rounded bg-bg-subtle border border-border px-3 py-2 text-xs tabular text-text-muted", children: ["size=\"", size, "\" \u2014 wraps content at the right width"] }) }) }, size))) }), _jsx(CodeBlock, { language: "tsx", code: `<Container size="lg">
|
|
973
|
+
<h1>Page heading</h1>
|
|
974
|
+
…
|
|
975
|
+
</Container>` })] }));
|
|
976
|
+
}
|
|
977
|
+
function StackSection() {
|
|
978
|
+
return (_jsx(DocSection, { id: "stack", title: "Stack", description: "Flexbox with consistent gap. Documents intent (vs raw `flex flex-col gap-3`) and keeps gap tokens consistent. Both vertical and horizontal.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "space-y-4", children: [_jsxs(Stack, { gap: 2, children: [_jsx("div", { className: "rounded border border-border bg-background px-3 py-2 text-xs", children: "Vertical \u00B7 gap=2" }), _jsx("div", { className: "rounded border border-border bg-background px-3 py-2 text-xs", children: "Vertical \u00B7 gap=2" }), _jsx("div", { className: "rounded border border-border bg-background px-3 py-2 text-xs", children: "Vertical \u00B7 gap=2" })] }), _jsxs(Stack, { direction: "row", gap: 3, align: "center", justify: "between", children: [_jsx("span", { className: "text-sm", children: "Row \u00B7 align=center \u00B7 justify=between" }), _jsx(Button, { size: "sm", variant: "primary", children: "Action" })] }), _jsx(Stack, { direction: "row", gap: 2, wrap: true, children: Array.from({ length: 10 }).map((_, i) => (_jsxs(Chip, { variant: "neutral", children: ["Tag ", i + 1] }, i))) })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<Stack gap={2}>…</Stack>
|
|
979
|
+
|
|
980
|
+
<Stack direction="row" gap={3} align="center" justify="between">
|
|
981
|
+
<h2>Heading</h2>
|
|
982
|
+
<Button>Action</Button>
|
|
983
|
+
</Stack>
|
|
984
|
+
|
|
985
|
+
<Stack direction="row" gap={2} wrap>
|
|
986
|
+
{tags.map(t => <Chip key={t}>{t}</Chip>)}
|
|
987
|
+
</Stack>` }) }) }));
|
|
988
|
+
}
|
|
989
|
+
function AspectRatioSection() {
|
|
990
|
+
return (_jsx(DocSection, { id: "aspect-ratio", title: "Aspect ratio", description: "Locks a region to a given ratio without layout shift. Use for video embeds, image placeholders, banners. Pass `ratio={16 / 9}` etc.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "grid grid-cols-2 gap-3 max-w-md", children: [_jsx("div", { className: "rounded border border-border overflow-hidden", children: _jsx(AspectRatio, { ratio: 16 / 9, children: _jsx("div", { className: "h-full w-full bg-bg-subtle grid place-items-center text-xs text-text-muted", children: "16:9" }) }) }), _jsx("div", { className: "rounded border border-border overflow-hidden", children: _jsx(AspectRatio, { ratio: 1, children: _jsx("div", { className: "h-full w-full bg-bg-subtle grid place-items-center text-xs text-text-muted", children: "1:1" }) }) }), _jsx("div", { className: "rounded border border-border overflow-hidden col-span-2", children: _jsx(AspectRatio, { ratio: 21 / 9, children: _jsx("div", { className: "h-full w-full bg-bg-subtle grid place-items-center text-xs text-text-muted", children: "21:9" }) }) })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<AspectRatio ratio={16 / 9}>
|
|
991
|
+
<iframe src={url} className="h-full w-full" />
|
|
992
|
+
</AspectRatio>` }) }) }));
|
|
993
|
+
}
|
|
994
|
+
function ScrollAreaSection() {
|
|
995
|
+
return (_jsx(DocSection, { id: "scroll-area", title: "Scroll area", description: "Styled, consistent custom scrollbars across browsers. Wraps bounded content that needs to scroll. Don't use for full-page scroll \u2014 that's the browser's job.", children: _jsx(PreviewCode, { preview: _jsxs(ScrollArea, { className: "h-48 w-full max-w-sm rounded border border-border bg-background p-3", children: [_jsx("h4", { className: "text-sm font-semibold mb-2", children: "Roster \u2014 Astoria Turf" }), _jsx("ul", { className: "space-y-1.5 text-sm", children: [
|
|
996
|
+
"Alex Chen",
|
|
997
|
+
"Jordan Lee",
|
|
998
|
+
"Sam Patel",
|
|
999
|
+
"Maria Garcia",
|
|
1000
|
+
"Danny Tran",
|
|
1001
|
+
"Priya Singh",
|
|
1002
|
+
"Ethan Brooks",
|
|
1003
|
+
"Nina Park",
|
|
1004
|
+
"Oliver Stone",
|
|
1005
|
+
"Reese Wang",
|
|
1006
|
+
"Sophia Liu",
|
|
1007
|
+
"Tomás Vega",
|
|
1008
|
+
"Uma Patel",
|
|
1009
|
+
"Vince Holloway",
|
|
1010
|
+
"Wei Chen",
|
|
1011
|
+
"Xander Hayes",
|
|
1012
|
+
].map((name) => (_jsxs("li", { className: "flex items-center gap-2", children: [_jsx(Avatar, { name: name, size: "sm" }), _jsx("span", { children: name })] }, name))) })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<ScrollArea className="h-48 w-full rounded border">
|
|
1013
|
+
<RosterList players={…} />
|
|
1014
|
+
</ScrollArea>` }) }) }));
|
|
1015
|
+
}
|
|
1016
|
+
function ComboboxSection() {
|
|
1017
|
+
const [venue, setVenue] = React.useState(null);
|
|
1018
|
+
return (_jsx(DocSection, { id: "combobox", title: "Combobox", description: "Searchable single-select. Reach for this over Select once the list is long enough that typing beats scrolling (~10+ options).", children: _jsx(PreviewCode, { preview: _jsx("div", { className: "max-w-xs", children: _jsx(Combobox, { "aria-label": "Venue", placeholder: "Select venue\u2026", options: [
|
|
1019
|
+
{ value: "astoria", label: "Astoria Turf" },
|
|
1020
|
+
{ value: "mccarren", label: "McCarren Park" },
|
|
1021
|
+
{ value: "greenpoint", label: "Greenpoint Dome" },
|
|
1022
|
+
{ value: "redhook", label: "Red Hook Fields" },
|
|
1023
|
+
{ value: "bushwick", label: "Bushwick Park", disabled: true },
|
|
1024
|
+
], value: venue, onValueChange: setVenue }) }), code: _jsx(CodeBlock, { language: "tsx", code: `<Combobox
|
|
1025
|
+
aria-label="Venue"
|
|
1026
|
+
placeholder="Select venue…"
|
|
1027
|
+
options={venues.map(v => ({ value: v.id, label: v.name }))}
|
|
1028
|
+
value={venueId}
|
|
1029
|
+
onValueChange={setVenueId}
|
|
1030
|
+
/>` }) }) }));
|
|
1031
|
+
}
|
|
1032
|
+
function SliderSection() {
|
|
1033
|
+
const [price, setPrice] = React.useState([20]);
|
|
1034
|
+
return (_jsx(DocSection, { id: "slider", title: "Slider", description: "Range input for bounded numeric choices (max price, distance radius). Arrow keys step; PageUp/Down big-step; Home/End jump to the ends. 44px thumb targets on touch.", children: _jsx(PreviewCode, { preview: _jsxs("div", { className: "max-w-xs space-y-2", children: [_jsx(Slider, { "aria-label": "Max price", min: 0, max: 50, step: 5, value: price, onValueChange: setPrice }), _jsxs("p", { className: "text-xs text-text-muted tabular", children: ["Max price: $", price[0]] })] }), code: _jsx(CodeBlock, { language: "tsx", code: `<Slider aria-label="Max price" min={0} max={50} step={5}
|
|
1035
|
+
value={price} onValueChange={setPrice} />
|
|
1036
|
+
|
|
1037
|
+
{/* Two thumbs = range */}
|
|
1038
|
+
<Slider aria-label="Price range" defaultValue={[10, 30]} />` }) }) }));
|
|
1039
|
+
}
|
|
1040
|
+
function DatePickerSection() {
|
|
1041
|
+
const [date, setDate] = React.useState(undefined);
|
|
1042
|
+
return (_jsxs(DocSection, { id: "date-picker", title: "Date picker", description: "Single-date selection in a popover; closes on pick. The picked Date is midnight LOCAL time \u2014 convert against the venue tz before persisting (\u00A73.16). Compose Calendar directly for inline or range use.", children: [_jsx(PreviewCode, { preview: _jsx("div", { className: "max-w-xs", children: _jsx(DatePicker, { "aria-label": "Game date", value: date, onValueChange: setDate }) }), code: _jsx(CodeBlock, { language: "tsx", code: `<DatePicker
|
|
1043
|
+
aria-label="Game date"
|
|
1044
|
+
value={gameDate}
|
|
1045
|
+
onValueChange={setGameDate}
|
|
1046
|
+
disabledDates={{ before: new Date() }}
|
|
1047
|
+
/>
|
|
1048
|
+
|
|
1049
|
+
{/* Inline calendar */}
|
|
1050
|
+
<Calendar mode="single" selected={date} onSelect={setDate} />` }) }), _jsx(DocSubsection, { title: "Inline calendar", description: "The same Calendar component, embedded (availability views, embedded scheduling).", children: _jsx("div", { className: "rounded border border-border bg-background inline-block", children: _jsx(Calendar, { mode: "single", selected: date, onSelect: setDate }) }) })] }));
|
|
1051
|
+
}
|
|
1052
|
+
function CollapsibleSection() {
|
|
1053
|
+
return (_jsx(DocSection, { id: "collapsible", title: "Collapsible", description: "One independent disclosure. Accordion is for grouped sections with one-open-at-a-time semantics; Collapsible is a single 'show more'.", children: _jsx(PreviewCode, { preview: _jsx("div", { className: "max-w-md rounded border border-border bg-background p-4", children: _jsxs(Collapsible, { children: [_jsx(CollapsibleTrigger, { children: "Advanced options" }), _jsx(CollapsibleContent, { children: _jsx("div", { className: "space-y-2 text-sm text-text-muted", children: _jsx("p", { children: "Waitlist auto-promotion, visibility, and refund rules." }) }) })] }) }), code: _jsx(CodeBlock, { language: "tsx", code: `<Collapsible>
|
|
1054
|
+
<CollapsibleTrigger>Advanced options</CollapsibleTrigger>
|
|
1055
|
+
<CollapsibleContent>…</CollapsibleContent>
|
|
1056
|
+
</Collapsible>` }) }) }));
|
|
1057
|
+
}
|
|
1058
|
+
function MarketingTierSection() {
|
|
1059
|
+
return (_jsxs(DocSection, { id: "marketing", title: "Marketing tier", description: "Primitives for the marketing site, sharing the same token source as the app tier. Display type is fluid (clamp 32\u219296px); spacing extends to 160px; the brand gradient is bg-gradient-brand.", children: [_jsx(DocSubsection, { title: "Display typography", description: "text-display-sm / text-display / text-display-lg \u2014 fluid between phone and desktop. Resize the window to see them scale.", children: _jsxs("div", { className: "space-y-4 rounded border border-border bg-background p-6 overflow-hidden", children: [_jsx("div", { className: "text-display-lg font-bold", children: "Aa 96" }), _jsx("div", { className: "text-display font-bold", children: "Book a game 64" }), _jsx("div", { className: "text-display-sm font-bold", children: "Three taps to kickoff 44" })] }) }), _jsx(DocSubsection, { title: "Page composition", description: "MarketingNav \u00B7 Hero \u00B7 Section \u00B7 LogoCloud \u00B7 Footer \u2014 a full page in five components. All server-safe except the nav.", children: _jsxs("div", { className: "rounded border border-border overflow-hidden", children: [_jsx(MarketingNav, { brand: _jsxs("a", { href: "#marketing", "aria-label": "DT Sports home", className: "font-bold tracking-[-0.01em]", children: ["DT", _jsx("span", { className: "text-brand-forest", children: "Sports" })] }), links: [
|
|
1060
|
+
{ label: "Games", href: "#marketing" },
|
|
1061
|
+
{ label: "Venues", href: "#marketing" },
|
|
1062
|
+
{ label: "Pricing", href: "#marketing" },
|
|
1063
|
+
], cta: _jsx(Button, { variant: "accent", children: "Find a game" }), className: "relative" }), _jsx(Hero, { eyebrow: "Pickup soccer, organized", title: "Book a game in 30 seconds", description: "Real fields, fair teams, instant refunds. Every game money-back if it doesn't run.", actions: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "accent", size: "touch", children: "Find a game" }), _jsx(Button, { variant: "secondary", size: "touch", children: "Host with us" })] }), className: "py-16 md:py-16 lg:py-16" }), _jsxs(LogoCloud, { label: "Trusted by leagues across NYC", children: [_jsx("span", { className: "text-lg font-bold", "aria-label": "Astoria League", children: "ASTORIA" }), _jsx("span", { className: "text-lg font-bold", "aria-label": "McCarren FC", children: "McCARREN FC" }), _jsx("span", { className: "text-lg font-bold", "aria-label": "Greenpoint Dome", children: "GP DOME" })] }), _jsx(Footer, { brand: _jsx("span", { className: "font-bold", children: "DT Sports" }), tagline: "Pickup sports, organized.", columns: [
|
|
1064
|
+
{
|
|
1065
|
+
title: "Product",
|
|
1066
|
+
links: [
|
|
1067
|
+
{ label: "Games", href: "#marketing" },
|
|
1068
|
+
{ label: "Venues", href: "#marketing" },
|
|
1069
|
+
],
|
|
1070
|
+
},
|
|
1071
|
+
{
|
|
1072
|
+
title: "Company",
|
|
1073
|
+
links: [{ label: "About", href: "#marketing" }],
|
|
1074
|
+
},
|
|
1075
|
+
], legal: "\u00A9 2026 DT Sports" })] }) }), _jsx(DocSubsection, { title: "Prose", description: "Long-form content styling for blog/legal/CMS pages \u2014 no typography plugin needed.", children: _jsx("div", { className: "rounded border border-border bg-background p-6", children: _jsxs(Prose, { children: [_jsx("h2", { children: "Refund policy" }), _jsxs("p", { children: ["Cancel up to 2 hours before kickoff for a", " ", _jsx("a", { href: "#marketing", children: "full credit" }), ". Weather cancellations refund automatically."] }), _jsxs("ul", { children: [_jsx("li", { children: "Host cancellations refund to the original card." }), _jsx("li", { children: "Credits never expire." })] })] }) }) }), _jsx(CodeBlock, { language: "tsx", code: `import {
|
|
1076
|
+
MarketingNav, Hero, Section, Footer, LogoCloud, Prose,
|
|
1077
|
+
} from "@dtsports/design-system/marketing";
|
|
1078
|
+
|
|
1079
|
+
<Hero
|
|
1080
|
+
eyebrow="Pickup soccer, organized"
|
|
1081
|
+
title="Book a game in 30 seconds"
|
|
1082
|
+
actions={<Button variant="accent" size="touch">Find a game</Button>}
|
|
1083
|
+
/>` })] }));
|
|
1084
|
+
}
|
|
1085
|
+
//# sourceMappingURL=page.js.map
|