@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,157 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
5
|
+
import { X } from "lucide-react";
|
|
6
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
7
|
+
import { cn } from "../lib/utils";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Drawer / Sheet — side-pushed panel for secondary tasks.
|
|
11
|
+
*
|
|
12
|
+
* Use a Drawer (not a Dialog) for:
|
|
13
|
+
* - Editing a row inline
|
|
14
|
+
* - Viewing details without losing place in the list
|
|
15
|
+
* - Mobile filter sheets
|
|
16
|
+
* - Audit timelines, side-by-side comparisons
|
|
17
|
+
*
|
|
18
|
+
* Per design.md §3.17, drawers replace modal stacks — never nest a
|
|
19
|
+
* dialog inside a drawer or vice versa.
|
|
20
|
+
*
|
|
21
|
+
* Backed by Radix Dialog under the hood, so focus trap, escape close,
|
|
22
|
+
* and aria semantics work out of the box.
|
|
23
|
+
*/
|
|
24
|
+
export const Drawer = DialogPrimitive.Root;
|
|
25
|
+
export const DrawerTrigger = DialogPrimitive.Trigger;
|
|
26
|
+
export const DrawerClose = DialogPrimitive.Close;
|
|
27
|
+
export const DrawerPortal = DialogPrimitive.Portal;
|
|
28
|
+
|
|
29
|
+
export const DrawerOverlay = React.forwardRef<
|
|
30
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
31
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
32
|
+
>(({ className, ...props }, ref) => (
|
|
33
|
+
<DialogPrimitive.Overlay
|
|
34
|
+
ref={ref}
|
|
35
|
+
className={cn(
|
|
36
|
+
"fixed inset-0 z-50 bg-black/40 backdrop-blur-[4px]",
|
|
37
|
+
"data-[state=open]:animate-fade-in",
|
|
38
|
+
className
|
|
39
|
+
)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
));
|
|
43
|
+
DrawerOverlay.displayName = "DrawerOverlay";
|
|
44
|
+
|
|
45
|
+
export const drawerVariants = cva(
|
|
46
|
+
"fixed z-50 flex flex-col bg-background border-border-strong shadow-md outline-none transition-transform",
|
|
47
|
+
{
|
|
48
|
+
variants: {
|
|
49
|
+
side: {
|
|
50
|
+
top: "inset-x-0 top-0 border-b max-h-[80vh] data-[state=open]:animate-fade-in",
|
|
51
|
+
bottom:
|
|
52
|
+
"inset-x-0 bottom-0 border-t max-h-[80vh] data-[state=open]:animate-fade-in",
|
|
53
|
+
left: "inset-y-0 left-0 h-full w-80 border-r data-[state=open]:animate-fade-in",
|
|
54
|
+
right:
|
|
55
|
+
"inset-y-0 right-0 h-full w-96 border-l data-[state=open]:animate-fade-in",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
defaultVariants: { side: "right" },
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
export interface DrawerContentProps
|
|
63
|
+
extends
|
|
64
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,
|
|
65
|
+
VariantProps<typeof drawerVariants> {}
|
|
66
|
+
|
|
67
|
+
export const DrawerContent = React.forwardRef<
|
|
68
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
69
|
+
DrawerContentProps
|
|
70
|
+
>(({ side = "right", className, children, ...props }, ref) => (
|
|
71
|
+
<DrawerPortal>
|
|
72
|
+
<DrawerOverlay />
|
|
73
|
+
<DialogPrimitive.Content
|
|
74
|
+
ref={ref}
|
|
75
|
+
className={cn(drawerVariants({ side }), className)}
|
|
76
|
+
{...props}
|
|
77
|
+
>
|
|
78
|
+
{children}
|
|
79
|
+
<DialogPrimitive.Close
|
|
80
|
+
aria-label="Close drawer"
|
|
81
|
+
className="absolute right-3 top-3 inline-flex h-6 w-6 items-center justify-center rounded-sm text-text-muted transition-colors hover:bg-bg-subtle hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring pointer-coarse:after:absolute pointer-coarse:after:-inset-2.5"
|
|
82
|
+
>
|
|
83
|
+
<X className="h-4 w-4" strokeWidth={1.75} />
|
|
84
|
+
</DialogPrimitive.Close>
|
|
85
|
+
</DialogPrimitive.Content>
|
|
86
|
+
</DrawerPortal>
|
|
87
|
+
));
|
|
88
|
+
DrawerContent.displayName = "DrawerContent";
|
|
89
|
+
|
|
90
|
+
export function DrawerHeader({
|
|
91
|
+
className,
|
|
92
|
+
...props
|
|
93
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
94
|
+
return (
|
|
95
|
+
<div
|
|
96
|
+
className={cn("px-5 pt-4 pb-3 border-b border-border", className)}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const DrawerTitle = React.forwardRef<
|
|
103
|
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
104
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
105
|
+
>(({ className, ...props }, ref) => (
|
|
106
|
+
<DialogPrimitive.Title
|
|
107
|
+
ref={ref}
|
|
108
|
+
className={cn(
|
|
109
|
+
"text-md font-semibold tracking-[-0.01em] text-foreground",
|
|
110
|
+
className
|
|
111
|
+
)}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
));
|
|
115
|
+
DrawerTitle.displayName = "DrawerTitle";
|
|
116
|
+
|
|
117
|
+
export const DrawerDescription = React.forwardRef<
|
|
118
|
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
119
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
120
|
+
>(({ className, ...props }, ref) => (
|
|
121
|
+
<DialogPrimitive.Description
|
|
122
|
+
ref={ref}
|
|
123
|
+
className={cn("text-xs text-text-muted mt-0.5", className)}
|
|
124
|
+
{...props}
|
|
125
|
+
/>
|
|
126
|
+
));
|
|
127
|
+
DrawerDescription.displayName = "DrawerDescription";
|
|
128
|
+
|
|
129
|
+
export function DrawerBody({
|
|
130
|
+
className,
|
|
131
|
+
...props
|
|
132
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
133
|
+
return (
|
|
134
|
+
<div
|
|
135
|
+
className={cn(
|
|
136
|
+
"px-5 py-4 text-sm text-foreground overflow-y-auto flex-1",
|
|
137
|
+
className
|
|
138
|
+
)}
|
|
139
|
+
{...props}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function DrawerFooter({
|
|
145
|
+
className,
|
|
146
|
+
...props
|
|
147
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
148
|
+
return (
|
|
149
|
+
<div
|
|
150
|
+
className={cn(
|
|
151
|
+
"flex items-center justify-end gap-2 px-5 py-3 border-t border-border bg-bg-subtle/50",
|
|
152
|
+
className
|
|
153
|
+
)}
|
|
154
|
+
{...props}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
5
|
+
import { Check, ChevronRight, Circle } from "lucide-react";
|
|
6
|
+
import { cn } from "../lib/utils";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* DropdownMenu — for actions menus (kebab on table rows, user avatar
|
|
10
|
+
* menu in topbar). NOT for selecting a value — use Select for that.
|
|
11
|
+
*
|
|
12
|
+
* <DropdownMenu>
|
|
13
|
+
* <DropdownMenuTrigger asChild><Button>Actions</Button></DropdownMenuTrigger>
|
|
14
|
+
* <DropdownMenuContent>
|
|
15
|
+
* <DropdownMenuLabel>Game</DropdownMenuLabel>
|
|
16
|
+
* <DropdownMenuItem>View roster</DropdownMenuItem>
|
|
17
|
+
* <DropdownMenuItem>Broadcast…</DropdownMenuItem>
|
|
18
|
+
* <DropdownMenuSeparator />
|
|
19
|
+
* <DropdownMenuItem className="text-danger">Cancel game…</DropdownMenuItem>
|
|
20
|
+
* </DropdownMenuContent>
|
|
21
|
+
* </DropdownMenu>
|
|
22
|
+
*/
|
|
23
|
+
export const DropdownMenu = DropdownMenuPrimitive.Root;
|
|
24
|
+
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
25
|
+
export const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
|
26
|
+
export const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
|
27
|
+
export const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
|
28
|
+
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
|
29
|
+
|
|
30
|
+
export const DropdownMenuContent = React.forwardRef<
|
|
31
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
32
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
33
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
34
|
+
<DropdownMenuPrimitive.Portal>
|
|
35
|
+
<DropdownMenuPrimitive.Content
|
|
36
|
+
ref={ref}
|
|
37
|
+
sideOffset={sideOffset}
|
|
38
|
+
className={cn(
|
|
39
|
+
"z-50 min-w-[8rem] overflow-hidden rounded border border-border-strong bg-background text-foreground shadow-md p-1",
|
|
40
|
+
"data-[state=open]:animate-fade-in",
|
|
41
|
+
className
|
|
42
|
+
)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
</DropdownMenuPrimitive.Portal>
|
|
46
|
+
));
|
|
47
|
+
DropdownMenuContent.displayName = "DropdownMenuContent";
|
|
48
|
+
|
|
49
|
+
export const DropdownMenuItem = React.forwardRef<
|
|
50
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
51
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
|
52
|
+
inset?: boolean;
|
|
53
|
+
}
|
|
54
|
+
>(({ className, inset, ...props }, ref) => (
|
|
55
|
+
<DropdownMenuPrimitive.Item
|
|
56
|
+
ref={ref}
|
|
57
|
+
className={cn(
|
|
58
|
+
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 pointer-coarse:min-h-11 text-sm outline-none transition-colors",
|
|
59
|
+
"focus:bg-bg-subtle focus:text-foreground",
|
|
60
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
61
|
+
inset && "pl-8",
|
|
62
|
+
className
|
|
63
|
+
)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
));
|
|
67
|
+
DropdownMenuItem.displayName = "DropdownMenuItem";
|
|
68
|
+
|
|
69
|
+
export const DropdownMenuCheckboxItem = React.forwardRef<
|
|
70
|
+
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
|
71
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
|
72
|
+
>(({ className, children, checked, ...props }, ref) => (
|
|
73
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
74
|
+
ref={ref}
|
|
75
|
+
className={cn(
|
|
76
|
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pointer-coarse:min-h-11 pl-8 pr-2 text-sm outline-none transition-colors",
|
|
77
|
+
"focus:bg-bg-subtle focus:text-foreground",
|
|
78
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
79
|
+
className
|
|
80
|
+
)}
|
|
81
|
+
checked={checked}
|
|
82
|
+
{...props}
|
|
83
|
+
>
|
|
84
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
85
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
86
|
+
<Check className="h-3 w-3" strokeWidth={2.5} />
|
|
87
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
88
|
+
</span>
|
|
89
|
+
{children}
|
|
90
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
91
|
+
));
|
|
92
|
+
DropdownMenuCheckboxItem.displayName = "DropdownMenuCheckboxItem";
|
|
93
|
+
|
|
94
|
+
export const DropdownMenuRadioItem = React.forwardRef<
|
|
95
|
+
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
|
96
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
|
97
|
+
>(({ className, children, ...props }, ref) => (
|
|
98
|
+
<DropdownMenuPrimitive.RadioItem
|
|
99
|
+
ref={ref}
|
|
100
|
+
className={cn(
|
|
101
|
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pointer-coarse:min-h-11 pl-8 pr-2 text-sm outline-none transition-colors",
|
|
102
|
+
"focus:bg-bg-subtle focus:text-foreground",
|
|
103
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
104
|
+
className
|
|
105
|
+
)}
|
|
106
|
+
{...props}
|
|
107
|
+
>
|
|
108
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
109
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
110
|
+
<Circle className="h-1.5 w-1.5 fill-current" strokeWidth={0} />
|
|
111
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
112
|
+
</span>
|
|
113
|
+
{children}
|
|
114
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
115
|
+
));
|
|
116
|
+
DropdownMenuRadioItem.displayName = "DropdownMenuRadioItem";
|
|
117
|
+
|
|
118
|
+
export const DropdownMenuLabel = React.forwardRef<
|
|
119
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
|
120
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
|
121
|
+
inset?: boolean;
|
|
122
|
+
}
|
|
123
|
+
>(({ className, inset, ...props }, ref) => (
|
|
124
|
+
<DropdownMenuPrimitive.Label
|
|
125
|
+
ref={ref}
|
|
126
|
+
className={cn(
|
|
127
|
+
"px-2 py-1.5 text-[10px] font-semibold uppercase tracking-[0.06em] text-text-muted",
|
|
128
|
+
inset && "pl-8",
|
|
129
|
+
className
|
|
130
|
+
)}
|
|
131
|
+
{...props}
|
|
132
|
+
/>
|
|
133
|
+
));
|
|
134
|
+
DropdownMenuLabel.displayName = "DropdownMenuLabel";
|
|
135
|
+
|
|
136
|
+
export const DropdownMenuSeparator = React.forwardRef<
|
|
137
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
138
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
139
|
+
>(({ className, ...props }, ref) => (
|
|
140
|
+
<DropdownMenuPrimitive.Separator
|
|
141
|
+
ref={ref}
|
|
142
|
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
143
|
+
{...props}
|
|
144
|
+
/>
|
|
145
|
+
));
|
|
146
|
+
DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
|
|
147
|
+
|
|
148
|
+
export function DropdownMenuShortcut({
|
|
149
|
+
className,
|
|
150
|
+
...props
|
|
151
|
+
}: React.HTMLAttributes<HTMLSpanElement>) {
|
|
152
|
+
return (
|
|
153
|
+
<span
|
|
154
|
+
className={cn(
|
|
155
|
+
"ml-auto text-[10px] font-mono tracking-widest text-text-subtle tabular",
|
|
156
|
+
className
|
|
157
|
+
)}
|
|
158
|
+
{...props}
|
|
159
|
+
/>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const DropdownMenuSubTrigger = React.forwardRef<
|
|
164
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
|
165
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
166
|
+
inset?: boolean;
|
|
167
|
+
}
|
|
168
|
+
>(({ className, inset, children, ...props }, ref) => (
|
|
169
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
170
|
+
ref={ref}
|
|
171
|
+
className={cn(
|
|
172
|
+
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 pointer-coarse:min-h-11 text-sm outline-none transition-colors",
|
|
173
|
+
"focus:bg-bg-subtle focus:text-foreground",
|
|
174
|
+
"data-[state=open]:bg-bg-subtle",
|
|
175
|
+
inset && "pl-8",
|
|
176
|
+
className
|
|
177
|
+
)}
|
|
178
|
+
{...props}
|
|
179
|
+
>
|
|
180
|
+
{children}
|
|
181
|
+
<ChevronRight className="ml-auto h-3 w-3" strokeWidth={1.75} />
|
|
182
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
183
|
+
));
|
|
184
|
+
DropdownMenuSubTrigger.displayName = "DropdownMenuSubTrigger";
|
|
185
|
+
|
|
186
|
+
export const DropdownMenuSubContent = React.forwardRef<
|
|
187
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
|
188
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
|
189
|
+
>(({ className, ...props }, ref) => (
|
|
190
|
+
<DropdownMenuPrimitive.SubContent
|
|
191
|
+
ref={ref}
|
|
192
|
+
className={cn(
|
|
193
|
+
"z-50 min-w-[8rem] overflow-hidden rounded border border-border-strong bg-background text-foreground shadow-md p-1",
|
|
194
|
+
"data-[state=open]:animate-fade-in",
|
|
195
|
+
className
|
|
196
|
+
)}
|
|
197
|
+
{...props}
|
|
198
|
+
/>
|
|
199
|
+
));
|
|
200
|
+
DropdownMenuSubContent.displayName = "DropdownMenuSubContent";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../lib/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* EmptyState — per design.md §10
|
|
6
|
+
* - Centered, single line text-sm muted
|
|
7
|
+
* - Optional small CTA
|
|
8
|
+
* - NO illustrations. A subtle Lucide icon at most.
|
|
9
|
+
*/
|
|
10
|
+
export interface EmptyStateProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
11
|
+
icon?: React.ReactNode;
|
|
12
|
+
title: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
action?: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function EmptyState({
|
|
18
|
+
icon,
|
|
19
|
+
title,
|
|
20
|
+
description,
|
|
21
|
+
action,
|
|
22
|
+
className,
|
|
23
|
+
...props
|
|
24
|
+
}: EmptyStateProps) {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
role="status"
|
|
28
|
+
className={cn(
|
|
29
|
+
"flex flex-col items-center justify-center gap-2 px-6 py-10 text-center",
|
|
30
|
+
className
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
{icon && (
|
|
35
|
+
<div className="text-text-subtle mb-1" aria-hidden="true">
|
|
36
|
+
{icon}
|
|
37
|
+
</div>
|
|
38
|
+
)}
|
|
39
|
+
<div className="text-sm font-medium text-foreground">{title}</div>
|
|
40
|
+
{description && (
|
|
41
|
+
<div className="text-xs text-text-muted max-w-xs">{description}</div>
|
|
42
|
+
)}
|
|
43
|
+
{action && <div className="mt-2">{action}</div>}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* FormField — composition wrapper that auto-wires Label + control +
|
|
8
|
+
* helper/error text + the right aria attributes.
|
|
9
|
+
*
|
|
10
|
+
* <FormField label="Email" required helper="We'll send confirmations here.">
|
|
11
|
+
* <Input type="email" autoComplete="email" />
|
|
12
|
+
* </FormField>
|
|
13
|
+
*
|
|
14
|
+
* <FormField label="Card" required error="Card number is too short.">
|
|
15
|
+
* <Input />
|
|
16
|
+
* </FormField>
|
|
17
|
+
*
|
|
18
|
+
* The child input is cloned with: `id`, `aria-describedby`, `aria-invalid`,
|
|
19
|
+
* so consumers don't have to remember the a11y plumbing.
|
|
20
|
+
*/
|
|
21
|
+
export interface FormFieldProps {
|
|
22
|
+
/** Optional explicit id — autogenerated if omitted. */
|
|
23
|
+
id?: string;
|
|
24
|
+
label?: React.ReactNode;
|
|
25
|
+
required?: boolean;
|
|
26
|
+
helper?: React.ReactNode;
|
|
27
|
+
error?: React.ReactNode;
|
|
28
|
+
/** Hide label visually but keep for screen readers */
|
|
29
|
+
hideLabel?: boolean;
|
|
30
|
+
/** Inline layout — label sits on the left of the control */
|
|
31
|
+
inline?: boolean;
|
|
32
|
+
className?: string;
|
|
33
|
+
children: React.ReactNode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function FormField({
|
|
37
|
+
id,
|
|
38
|
+
label,
|
|
39
|
+
required,
|
|
40
|
+
helper,
|
|
41
|
+
error,
|
|
42
|
+
hideLabel,
|
|
43
|
+
inline,
|
|
44
|
+
className,
|
|
45
|
+
children,
|
|
46
|
+
}: FormFieldProps) {
|
|
47
|
+
const reactId = React.useId();
|
|
48
|
+
const fieldId = id ?? reactId;
|
|
49
|
+
const helperId = `${fieldId}-helper`;
|
|
50
|
+
const errorId = `${fieldId}-error`;
|
|
51
|
+
|
|
52
|
+
const describedBy =
|
|
53
|
+
[error ? errorId : null, helper && !error ? helperId : null]
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.join(" ") || undefined;
|
|
56
|
+
|
|
57
|
+
// Clone first valid child so we can inject id + aria props.
|
|
58
|
+
const child = React.Children.map(children, (c) => {
|
|
59
|
+
if (!React.isValidElement(c)) return c;
|
|
60
|
+
const el = c as React.ReactElement<{
|
|
61
|
+
id?: string;
|
|
62
|
+
"aria-describedby"?: string;
|
|
63
|
+
"aria-invalid"?: boolean;
|
|
64
|
+
}>;
|
|
65
|
+
return React.cloneElement(el, {
|
|
66
|
+
id: el.props.id ?? fieldId,
|
|
67
|
+
"aria-describedby": el.props["aria-describedby"] ?? describedBy,
|
|
68
|
+
"aria-invalid": el.props["aria-invalid"] ?? (error ? true : undefined),
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const labelEl = label && (
|
|
73
|
+
<label
|
|
74
|
+
htmlFor={fieldId}
|
|
75
|
+
className={cn(
|
|
76
|
+
"block text-xs font-medium uppercase tracking-[0.04em] text-text-muted",
|
|
77
|
+
hideLabel && "sr-only"
|
|
78
|
+
)}
|
|
79
|
+
>
|
|
80
|
+
{label}
|
|
81
|
+
{required && (
|
|
82
|
+
<span aria-hidden="true" className="ml-0.5 text-danger">
|
|
83
|
+
*
|
|
84
|
+
</span>
|
|
85
|
+
)}
|
|
86
|
+
</label>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const messages =
|
|
90
|
+
error || helper ? (
|
|
91
|
+
<p
|
|
92
|
+
id={error ? errorId : helperId}
|
|
93
|
+
className={cn("text-xs", error ? "text-danger" : "text-text-muted")}
|
|
94
|
+
>
|
|
95
|
+
{error ?? helper}
|
|
96
|
+
</p>
|
|
97
|
+
) : null;
|
|
98
|
+
|
|
99
|
+
if (inline) {
|
|
100
|
+
return (
|
|
101
|
+
<div className={cn("flex items-start gap-3", className)}>
|
|
102
|
+
{labelEl && <div className="pt-1.5 w-32 shrink-0">{labelEl}</div>}
|
|
103
|
+
<div className="flex-1 min-w-0 space-y-1">
|
|
104
|
+
{child}
|
|
105
|
+
{messages}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className={cn("space-y-1.5", className)}>
|
|
113
|
+
{labelEl}
|
|
114
|
+
{child}
|
|
115
|
+
{messages}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../lib/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Input — per design.md §10
|
|
6
|
+
* Height 32px, padding 0 10px, 1px border-strong, 2px radius.
|
|
7
|
+
* Focus: 2px ring in the theme ring color (no border color change).
|
|
8
|
+
* Error: pass `aria-invalid` true → border + ring switch to danger.
|
|
9
|
+
*/
|
|
10
|
+
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
|
11
|
+
|
|
12
|
+
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
13
|
+
({ className, type = "text", ...props }, ref) => (
|
|
14
|
+
<input
|
|
15
|
+
ref={ref}
|
|
16
|
+
type={type}
|
|
17
|
+
className={cn(
|
|
18
|
+
"flex h-8 pointer-coarse:h-11 w-full rounded-sm border border-border-strong bg-background px-2.5 text-sm text-foreground transition-colors duration-150 ease-out",
|
|
19
|
+
"placeholder:text-text-subtle",
|
|
20
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
21
|
+
"disabled:cursor-not-allowed disabled:bg-bg-muted disabled:opacity-60",
|
|
22
|
+
"aria-[invalid=true]:border-danger aria-[invalid=true]:focus-visible:ring-danger",
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
)
|
|
28
|
+
);
|
|
29
|
+
Input.displayName = "Input";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
5
|
+
import { cn } from "../lib/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Label — per design.md §10
|
|
9
|
+
* Uppercase, tracking-wide, text-xs, muted color.
|
|
10
|
+
* Always pair with its input via `htmlFor`.
|
|
11
|
+
*/
|
|
12
|
+
export const Label = React.forwardRef<
|
|
13
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
14
|
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & {
|
|
15
|
+
required?: boolean;
|
|
16
|
+
}
|
|
17
|
+
>(({ className, children, required, ...props }, ref) => (
|
|
18
|
+
<LabelPrimitive.Root
|
|
19
|
+
ref={ref}
|
|
20
|
+
className={cn(
|
|
21
|
+
"block text-xs font-medium uppercase tracking-[0.04em] text-text-muted",
|
|
22
|
+
"peer-disabled:cursor-not-allowed peer-disabled:opacity-60",
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
>
|
|
27
|
+
{children}
|
|
28
|
+
{required && (
|
|
29
|
+
<span aria-hidden="true" className="ml-0.5 text-danger">
|
|
30
|
+
*
|
|
31
|
+
</span>
|
|
32
|
+
)}
|
|
33
|
+
</LabelPrimitive.Root>
|
|
34
|
+
));
|
|
35
|
+
Label.displayName = "Label";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../lib/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* LivePill — per design.md §10 & §12
|
|
6
|
+
* The ONE continuously animated element in the system.
|
|
7
|
+
* Renders a lime pill with a pulsing dot and "LIVE" label.
|
|
8
|
+
*
|
|
9
|
+
* Accessibility:
|
|
10
|
+
* - aria-live="polite" so SR users hear "Live" without interrupting
|
|
11
|
+
* - The pulse animation honors prefers-reduced-motion via globals.css
|
|
12
|
+
*/
|
|
13
|
+
export function LivePill({
|
|
14
|
+
className,
|
|
15
|
+
label = "Live",
|
|
16
|
+
...props
|
|
17
|
+
}: React.HTMLAttributes<HTMLSpanElement> & { label?: string }) {
|
|
18
|
+
return (
|
|
19
|
+
<span
|
|
20
|
+
role="status"
|
|
21
|
+
aria-live="polite"
|
|
22
|
+
className={cn(
|
|
23
|
+
"inline-flex items-center gap-1.5 rounded-full bg-brand-lime px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.06em] text-brand-lime-foreground",
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
<span
|
|
29
|
+
className="block h-1.5 w-1.5 rounded-full bg-brand-lime-foreground animate-live-pulse"
|
|
30
|
+
aria-hidden="true"
|
|
31
|
+
/>
|
|
32
|
+
{label}
|
|
33
|
+
</span>
|
|
34
|
+
);
|
|
35
|
+
}
|