@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.
Files changed (340) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/components/accordion.d.ts +19 -0
  4. package/dist/components/accordion.d.ts.map +1 -0
  5. package/dist/components/accordion.js +26 -0
  6. package/dist/components/accordion.js.map +1 -0
  7. package/dist/components/alert.d.ts +26 -0
  8. package/dist/components/alert.d.ts.map +1 -0
  9. package/dist/components/alert.js.map +1 -0
  10. package/dist/components/app-shell.d.ts +21 -0
  11. package/dist/components/app-shell.d.ts.map +1 -0
  12. package/dist/components/app-shell.js +25 -0
  13. package/dist/components/app-shell.js.map +1 -0
  14. package/dist/components/aspect-ratio.d.ts +16 -0
  15. package/dist/components/aspect-ratio.d.ts.map +1 -0
  16. package/dist/components/aspect-ratio.js +17 -0
  17. package/dist/components/aspect-ratio.js.map +1 -0
  18. package/dist/components/avatar-group.d.ts +31 -0
  19. package/dist/components/avatar-group.d.ts.map +1 -0
  20. package/dist/components/avatar.d.ts +15 -0
  21. package/dist/components/avatar.d.ts.map +1 -0
  22. package/dist/components/avatar.js +20 -0
  23. package/dist/components/avatar.js.map +1 -0
  24. package/dist/components/badge.d.ts +27 -0
  25. package/dist/components/badge.d.ts.map +1 -0
  26. package/dist/components/badge.js +51 -0
  27. package/dist/components/badge.js.map +1 -0
  28. package/dist/components/breadcrumb.js.map +1 -0
  29. package/dist/components/button.d.ts +33 -0
  30. package/dist/components/button.d.ts.map +1 -0
  31. package/dist/components/button.js +58 -0
  32. package/dist/components/button.js.map +1 -0
  33. package/dist/components/calendar.d.ts +15 -0
  34. package/dist/components/calendar.d.ts.map +1 -0
  35. package/dist/components/calendar.js +33 -0
  36. package/dist/components/calendar.js.map +1 -0
  37. package/dist/components/capacity-bar.d.ts +32 -0
  38. package/dist/components/capacity-bar.d.ts.map +1 -0
  39. package/dist/components/capacity-bar.js +29 -0
  40. package/dist/components/capacity-bar.js.map +1 -0
  41. package/dist/components/card.d.ts +26 -0
  42. package/dist/components/card.d.ts.map +1 -0
  43. package/dist/components/card.js +20 -0
  44. package/dist/components/checkbox.d.ts +11 -0
  45. package/dist/components/checkbox.d.ts.map +1 -0
  46. package/dist/components/checkbox.js +19 -0
  47. package/dist/components/chip.d.ts +23 -0
  48. package/dist/components/chip.d.ts.map +1 -0
  49. package/dist/components/chip.js +32 -0
  50. package/dist/components/chip.js.map +1 -0
  51. package/dist/components/collapsible.d.ts +16 -0
  52. package/dist/components/collapsible.js +22 -0
  53. package/dist/components/collapsible.js.map +1 -0
  54. package/dist/components/combobox.d.ts +38 -0
  55. package/dist/components/combobox.d.ts.map +1 -0
  56. package/dist/components/combobox.js +17 -0
  57. package/dist/components/combobox.js.map +1 -0
  58. package/dist/components/confirm-action.d.ts +36 -0
  59. package/dist/components/confirm-action.d.ts.map +1 -0
  60. package/dist/components/confirm-action.js +21 -0
  61. package/dist/components/confirm-action.js.map +1 -0
  62. package/dist/components/container.js.map +1 -0
  63. package/dist/components/date-picker.d.ts.map +1 -0
  64. package/dist/components/date-picker.js +22 -0
  65. package/dist/components/date-picker.js.map +1 -0
  66. package/dist/components/dialog.d.ts +28 -0
  67. package/dist/components/dialog.d.ts.map +1 -0
  68. package/dist/components/dialog.js.map +1 -0
  69. package/dist/components/drawer.js +55 -0
  70. package/dist/components/drawer.js.map +1 -0
  71. package/dist/components/dropdown-menu.d.ts +39 -0
  72. package/dist/components/dropdown-menu.d.ts.map +1 -0
  73. package/dist/components/dropdown-menu.js +47 -0
  74. package/dist/components/empty-state.d.ts +15 -0
  75. package/dist/components/empty-state.d.ts.map +1 -0
  76. package/dist/components/empty-state.js +6 -0
  77. package/dist/components/form-field.d.ts +32 -0
  78. package/dist/components/form-field.d.ts.map +1 -0
  79. package/dist/components/form-field.js +31 -0
  80. package/dist/components/form-field.js.map +1 -0
  81. package/dist/components/input.d.ts +10 -0
  82. package/dist/components/input.d.ts.map +1 -0
  83. package/dist/components/input.js +6 -0
  84. package/dist/components/input.js.map +1 -0
  85. package/dist/components/label.d.ts +11 -0
  86. package/dist/components/label.d.ts.map +1 -0
  87. package/dist/components/label.js +13 -0
  88. package/dist/components/label.js.map +1 -0
  89. package/dist/components/live-pill.d.ts +14 -0
  90. package/dist/components/live-pill.js +15 -0
  91. package/dist/components/live-pill.js.map +1 -0
  92. package/dist/components/metric-tile.d.ts +23 -0
  93. package/dist/components/metric-tile.d.ts.map +1 -0
  94. package/dist/components/money-breakdown.d.ts +36 -0
  95. package/dist/components/money-breakdown.d.ts.map +1 -0
  96. package/dist/components/money-breakdown.js +12 -0
  97. package/dist/components/money-breakdown.js.map +1 -0
  98. package/dist/components/number-input.d.ts +16 -0
  99. package/dist/components/number-input.d.ts.map +1 -0
  100. package/dist/components/number-input.js +30 -0
  101. package/dist/components/number-input.js.map +1 -0
  102. package/dist/components/pagination.d.ts +27 -0
  103. package/dist/components/pagination.d.ts.map +1 -0
  104. package/dist/components/pagination.js +52 -0
  105. package/dist/components/pagination.js.map +1 -0
  106. package/dist/components/popover.d.ts +24 -0
  107. package/dist/components/popover.d.ts.map +1 -0
  108. package/dist/components/popover.js +28 -0
  109. package/dist/components/popover.js.map +1 -0
  110. package/dist/components/progress.d.ts +13 -0
  111. package/dist/components/progress.d.ts.map +1 -0
  112. package/dist/components/progress.js +17 -0
  113. package/dist/components/radio-group.d.ts +10 -0
  114. package/dist/components/radio-group.d.ts.map +1 -0
  115. package/dist/components/radio-group.js +17 -0
  116. package/dist/components/radio-group.js.map +1 -0
  117. package/dist/components/scroll-area.d.ts.map +1 -0
  118. package/dist/components/scroll-area.js +22 -0
  119. package/dist/components/scroll-area.js.map +1 -0
  120. package/dist/components/select.d.ts +27 -0
  121. package/dist/components/select.d.ts.map +1 -0
  122. package/dist/components/select.js +40 -0
  123. package/dist/components/select.js.map +1 -0
  124. package/dist/components/separator.d.ts +10 -0
  125. package/dist/components/separator.d.ts.map +1 -0
  126. package/dist/components/separator.js +14 -0
  127. package/dist/components/separator.js.map +1 -0
  128. package/dist/components/sidebar.d.ts.map +1 -0
  129. package/dist/components/sidebar.js +48 -0
  130. package/dist/components/sidebar.js.map +1 -0
  131. package/dist/components/skeleton.d.ts +23 -0
  132. package/dist/components/skeleton.js +25 -0
  133. package/dist/components/skeleton.js.map +1 -0
  134. package/dist/components/slider.d.ts +14 -0
  135. package/dist/components/slider.d.ts.map +1 -0
  136. package/dist/components/slider.js +30 -0
  137. package/dist/components/slider.js.map +1 -0
  138. package/dist/components/spec.d.ts +35 -0
  139. package/dist/components/spec.js +31 -0
  140. package/dist/components/spec.js.map +1 -0
  141. package/dist/components/spinner.d.ts +20 -0
  142. package/dist/components/spinner.d.ts.map +1 -0
  143. package/dist/components/spinner.js +18 -0
  144. package/dist/components/spinner.js.map +1 -0
  145. package/dist/components/stack.d.ts +48 -0
  146. package/dist/components/stack.d.ts.map +1 -0
  147. package/dist/components/stack.js +43 -0
  148. package/dist/components/stack.js.map +1 -0
  149. package/dist/components/status-pill.js +34 -0
  150. package/dist/components/status-pill.js.map +1 -0
  151. package/dist/components/switch.d.ts +9 -0
  152. package/dist/components/switch.d.ts.map +1 -0
  153. package/dist/components/switch.js +15 -0
  154. package/dist/components/switch.js.map +1 -0
  155. package/dist/components/table.d.ts +31 -0
  156. package/dist/components/table.d.ts.map +1 -0
  157. package/dist/components/tabs.d.ts +22 -0
  158. package/dist/components/tabs.d.ts.map +1 -0
  159. package/dist/components/tabs.js +28 -0
  160. package/dist/components/tabs.js.map +1 -0
  161. package/dist/components/textarea.d.ts +8 -0
  162. package/dist/components/textarea.d.ts.map +1 -0
  163. package/dist/components/textarea.js +6 -0
  164. package/dist/components/textarea.js.map +1 -0
  165. package/dist/components/theme-provider.d.ts +35 -0
  166. package/dist/components/theme-provider.d.ts.map +1 -0
  167. package/dist/components/theme-provider.js.map +1 -0
  168. package/dist/components/theme-toggle.d.ts +10 -0
  169. package/dist/components/theme-toggle.d.ts.map +1 -0
  170. package/dist/components/theme-toggle.js.map +1 -0
  171. package/dist/components/three-state.d.ts +39 -0
  172. package/dist/components/three-state.d.ts.map +1 -0
  173. package/dist/components/three-state.js +24 -0
  174. package/dist/components/three-state.js.map +1 -0
  175. package/dist/components/timestamp.d.ts +24 -0
  176. package/dist/components/timestamp.js +64 -0
  177. package/dist/components/timestamp.js.map +1 -0
  178. package/dist/components/toaster.d.ts +27 -0
  179. package/dist/components/toaster.d.ts.map +1 -0
  180. package/dist/components/toaster.js +21 -0
  181. package/dist/components/toaster.js.map +1 -0
  182. package/dist/components/tooltip.d.ts +28 -0
  183. package/dist/components/tooltip.d.ts.map +1 -0
  184. package/dist/components/tooltip.js.map +1 -0
  185. package/dist/components/top-bar.d.ts +13 -0
  186. package/dist/components/top-bar.d.ts.map +1 -0
  187. package/dist/components/top-bar.js +16 -0
  188. package/dist/index.d.ts +76 -0
  189. package/dist/index.d.ts.map +1 -0
  190. package/dist/index.js +86 -0
  191. package/dist/index.js.map +1 -0
  192. package/dist/lib/utils.d.ts +7 -0
  193. package/dist/lib/utils.d.ts.map +1 -0
  194. package/dist/lib/utils.js +10 -0
  195. package/dist/lib/utils.js.map +1 -0
  196. package/dist/marketing/footer.d.ts +31 -0
  197. package/dist/marketing/footer.d.ts.map +1 -0
  198. package/dist/marketing/footer.js +9 -0
  199. package/dist/marketing/footer.js.map +1 -0
  200. package/dist/marketing/hero.d.ts +29 -0
  201. package/dist/marketing/hero.d.ts.map +1 -0
  202. package/dist/marketing/hero.js +7 -0
  203. package/dist/marketing/hero.js.map +1 -0
  204. package/dist/marketing/index.d.ts +21 -0
  205. package/dist/marketing/index.d.ts.map +1 -0
  206. package/dist/marketing/index.js +21 -0
  207. package/dist/marketing/index.js.map +1 -0
  208. package/dist/marketing/logo-cloud.d.ts +15 -0
  209. package/dist/marketing/logo-cloud.d.ts.map +1 -0
  210. package/dist/marketing/logo-cloud.js +6 -0
  211. package/dist/marketing/logo-cloud.js.map +1 -0
  212. package/dist/marketing/nav.d.ts +33 -0
  213. package/dist/marketing/nav.d.ts.map +1 -0
  214. package/dist/marketing/nav.js +14 -0
  215. package/dist/marketing/nav.js.map +1 -0
  216. package/dist/marketing/prose.d.ts +12 -0
  217. package/dist/marketing/prose.d.ts.map +1 -0
  218. package/dist/marketing/prose.js +18 -0
  219. package/dist/marketing/prose.js.map +1 -0
  220. package/dist/marketing/section.d.ts +22 -0
  221. package/dist/marketing/section.d.ts.map +1 -0
  222. package/dist/marketing/section.js +7 -0
  223. package/dist/marketing/section.js.map +1 -0
  224. package/dist/showcase/components/code-block.d.ts +11 -0
  225. package/dist/showcase/components/code-block.d.ts.map +1 -0
  226. package/dist/showcase/components/code-block.js +20 -0
  227. package/dist/showcase/components/code-block.js.map +1 -0
  228. package/dist/showcase/components/example.d.ts +16 -0
  229. package/dist/showcase/components/example.d.ts.map +1 -0
  230. package/dist/showcase/components/example.js +14 -0
  231. package/dist/showcase/components/example.js.map +1 -0
  232. package/dist/showcase/components/section.d.ts +21 -0
  233. package/dist/showcase/components/section.d.ts.map +1 -0
  234. package/dist/showcase/components/section.js +10 -0
  235. package/dist/showcase/components/section.js.map +1 -0
  236. package/dist/showcase/components/swatch.d.ts +17 -0
  237. package/dist/showcase/components/swatch.d.ts.map +1 -0
  238. package/dist/showcase/components/swatch.js +6 -0
  239. package/dist/showcase/components/swatch.js.map +1 -0
  240. package/dist/showcase/index.d.ts +17 -0
  241. package/dist/showcase/index.d.ts.map +1 -0
  242. package/dist/showcase/index.js +17 -0
  243. package/dist/showcase/index.js.map +1 -0
  244. package/dist/showcase/page.d.ts +2 -0
  245. package/dist/showcase/page.d.ts.map +1 -0
  246. package/dist/showcase/page.js +1085 -0
  247. package/dist/showcase/page.js.map +1 -0
  248. package/dist/styles/generated/tokens.d.ts +131 -0
  249. package/dist/styles/generated/tokens.d.ts.map +1 -0
  250. package/dist/styles/generated/tokens.js +107 -0
  251. package/dist/styles/generated/tokens.js.map +1 -0
  252. package/dist/styles/globals.css +131 -0
  253. package/dist/styles/tailwind.preset.d.ts +181 -0
  254. package/dist/styles/tailwind.preset.d.ts.map +1 -0
  255. package/dist/styles/tailwind.preset.js +114 -0
  256. package/dist/styles/tailwind.preset.js.map +1 -0
  257. package/dist/tokens/index.d.ts +176 -0
  258. package/dist/tokens/index.d.ts.map +1 -0
  259. package/dist/tokens/index.js +139 -0
  260. package/dist/tokens/index.js.map +1 -0
  261. package/dist/tokens/tokens.json +154 -0
  262. package/package.json +124 -0
  263. package/src/components/accordion.tsx +76 -0
  264. package/src/components/alert.tsx +113 -0
  265. package/src/components/app-shell.tsx +43 -0
  266. package/src/components/aspect-ratio.tsx +18 -0
  267. package/src/components/avatar-group.tsx +73 -0
  268. package/src/components/avatar.tsx +81 -0
  269. package/src/components/badge.tsx +77 -0
  270. package/src/components/breadcrumb.tsx +121 -0
  271. package/src/components/button.tsx +83 -0
  272. package/src/components/calendar.tsx +85 -0
  273. package/src/components/capacity-bar.tsx +80 -0
  274. package/src/components/card.tsx +104 -0
  275. package/src/components/checkbox.tsx +46 -0
  276. package/src/components/chip.tsx +74 -0
  277. package/src/components/collapsible.tsx +54 -0
  278. package/src/components/combobox.tsx +140 -0
  279. package/src/components/confirm-action.tsx +111 -0
  280. package/src/components/container.tsx +39 -0
  281. package/src/components/date-picker.tsx +98 -0
  282. package/src/components/dialog.tsx +174 -0
  283. package/src/components/drawer.tsx +157 -0
  284. package/src/components/dropdown-menu.tsx +200 -0
  285. package/src/components/empty-state.tsx +46 -0
  286. package/src/components/form-field.tsx +118 -0
  287. package/src/components/input.tsx +29 -0
  288. package/src/components/label.tsx +35 -0
  289. package/src/components/live-pill.tsx +35 -0
  290. package/src/components/metric-tile.tsx +83 -0
  291. package/src/components/money-breakdown.tsx +92 -0
  292. package/src/components/number-input.tsx +113 -0
  293. package/src/components/pagination.tsx +157 -0
  294. package/src/components/popover.tsx +46 -0
  295. package/src/components/progress.tsx +34 -0
  296. package/src/components/radio-group.tsx +46 -0
  297. package/src/components/scroll-area.tsx +55 -0
  298. package/src/components/select.tsx +169 -0
  299. package/src/components/separator.tsx +34 -0
  300. package/src/components/sidebar.tsx +168 -0
  301. package/src/components/skeleton.tsx +85 -0
  302. package/src/components/slider.tsx +58 -0
  303. package/src/components/spec.tsx +59 -0
  304. package/src/components/spinner.tsx +36 -0
  305. package/src/components/stack.tsx +80 -0
  306. package/src/components/status-pill.tsx +100 -0
  307. package/src/components/switch.tsx +37 -0
  308. package/src/components/table.tsx +129 -0
  309. package/src/components/tabs.tsx +72 -0
  310. package/src/components/textarea.tsx +27 -0
  311. package/src/components/theme-provider.tsx +131 -0
  312. package/src/components/theme-toggle.tsx +34 -0
  313. package/src/components/three-state.tsx +90 -0
  314. package/src/components/timestamp.tsx +107 -0
  315. package/src/components/toaster.tsx +55 -0
  316. package/src/components/tooltip.tsx +49 -0
  317. package/src/components/top-bar.tsx +29 -0
  318. package/src/index.ts +221 -0
  319. package/src/lib/utils.ts +10 -0
  320. package/src/marketing/footer.tsx +93 -0
  321. package/src/marketing/hero.tsx +92 -0
  322. package/src/marketing/index.ts +24 -0
  323. package/src/marketing/logo-cloud.tsx +35 -0
  324. package/src/marketing/nav.tsx +122 -0
  325. package/src/marketing/prose.tsx +45 -0
  326. package/src/marketing/section.tsx +77 -0
  327. package/src/showcase/components/code-block.tsx +66 -0
  328. package/src/showcase/components/example.tsx +47 -0
  329. package/src/showcase/components/section.tsx +70 -0
  330. package/src/showcase/components/swatch.tsx +52 -0
  331. package/src/showcase/index.tsx +16 -0
  332. package/src/showcase/page.tsx +4216 -0
  333. package/src/styles/generated/tokens.ts +119 -0
  334. package/src/styles/globals.css +131 -0
  335. package/src/styles/tailwind.preset.ts +134 -0
  336. package/src/test/a11y.test.tsx +638 -0
  337. package/src/test/marketing-a11y.test.tsx +138 -0
  338. package/src/test/setup.ts +47 -0
  339. package/src/tokens/index.ts +154 -0
  340. package/src/tokens/tokens.json +154 -0
@@ -0,0 +1,77 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import { cn } from "../lib/utils";
5
+
6
+ /**
7
+ * Badge — per design.md §10
8
+ * Small uppercase pills used as status markers. Always paired with text
9
+ * (and optionally an icon) so they are NOT color-only signals.
10
+ *
11
+ * Sizes:
12
+ * - xs (default): 20px tall, 6px horizontal padding
13
+ * - sm: 24px tall, 8px horizontal padding
14
+ *
15
+ * Shape:
16
+ * - rounded-sm (default): square-ish for inline cell badges
17
+ * - rounded-full: pill, reserved for game status (see StatusPill)
18
+ */
19
+ const badgeVariants = cva(
20
+ "inline-flex items-center gap-1 font-medium uppercase tracking-[0.04em] whitespace-nowrap",
21
+ {
22
+ variants: {
23
+ variant: {
24
+ neutral: "bg-bg-muted text-foreground",
25
+ outline: "border border-border-strong bg-transparent text-foreground",
26
+ forest: "bg-brand-forest text-brand-forest-foreground",
27
+ lime: "bg-brand-lime text-brand-lime-foreground",
28
+ success: "bg-success text-success-foreground",
29
+ warning: "bg-warning text-warning-foreground",
30
+ danger: "bg-danger text-danger-foreground",
31
+ info: "bg-info text-info-foreground",
32
+ "muted-outline":
33
+ "border border-border-strong bg-transparent text-text-muted",
34
+ },
35
+ size: {
36
+ xs: "h-5 px-1.5 text-[10px] leading-none",
37
+ sm: "h-6 px-2 text-xs leading-none",
38
+ },
39
+ shape: {
40
+ square: "rounded-sm",
41
+ pill: "rounded-full",
42
+ },
43
+ },
44
+ defaultVariants: {
45
+ variant: "neutral",
46
+ size: "xs",
47
+ shape: "square",
48
+ },
49
+ }
50
+ );
51
+
52
+ export interface BadgeProps
53
+ extends
54
+ React.HTMLAttributes<HTMLSpanElement>,
55
+ VariantProps<typeof badgeVariants> {
56
+ /** Render as the child element (e.g. a link) with badge styling. */
57
+ asChild?: boolean;
58
+ }
59
+
60
+ export function Badge({
61
+ className,
62
+ variant,
63
+ size,
64
+ shape,
65
+ asChild = false,
66
+ ...props
67
+ }: BadgeProps) {
68
+ const Comp = asChild ? Slot : "span";
69
+ return (
70
+ <Comp
71
+ className={cn(badgeVariants({ variant, size, shape }), className)}
72
+ {...props}
73
+ />
74
+ );
75
+ }
76
+
77
+ export { badgeVariants };
@@ -0,0 +1,121 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Slot } from "@radix-ui/react-slot";
5
+ import { ChevronRight } from "lucide-react";
6
+ import { cn } from "../lib/utils";
7
+
8
+ /**
9
+ * Breadcrumb — semantic nav for hierarchical location.
10
+ *
11
+ * <Breadcrumb>
12
+ * <BreadcrumbList>
13
+ * <BreadcrumbItem>
14
+ * <BreadcrumbLink href="/admin">Admin</BreadcrumbLink>
15
+ * </BreadcrumbItem>
16
+ * <BreadcrumbSeparator />
17
+ * <BreadcrumbItem>
18
+ * <BreadcrumbPage>Today</BreadcrumbPage>
19
+ * </BreadcrumbItem>
20
+ * </BreadcrumbList>
21
+ * </Breadcrumb>
22
+ *
23
+ * For Next.js Link, use `asChild` on BreadcrumbLink:
24
+ * <BreadcrumbLink asChild><Link href="/admin">Admin</Link></BreadcrumbLink>
25
+ */
26
+ export const Breadcrumb = React.forwardRef<
27
+ HTMLElement,
28
+ React.HTMLAttributes<HTMLElement>
29
+ >(({ className, ...props }, ref) => (
30
+ <nav
31
+ ref={ref}
32
+ aria-label="Breadcrumb"
33
+ className={cn("text-sm", className)}
34
+ {...props}
35
+ />
36
+ ));
37
+ Breadcrumb.displayName = "Breadcrumb";
38
+
39
+ export const BreadcrumbList = React.forwardRef<
40
+ HTMLOListElement,
41
+ React.OlHTMLAttributes<HTMLOListElement>
42
+ >(({ className, ...props }, ref) => (
43
+ <ol
44
+ ref={ref}
45
+ className={cn(
46
+ "flex flex-wrap items-center gap-1.5 text-text-muted",
47
+ className
48
+ )}
49
+ {...props}
50
+ />
51
+ ));
52
+ BreadcrumbList.displayName = "BreadcrumbList";
53
+
54
+ export const BreadcrumbItem = React.forwardRef<
55
+ HTMLLIElement,
56
+ React.LiHTMLAttributes<HTMLLIElement>
57
+ >(({ className, ...props }, ref) => (
58
+ <li
59
+ ref={ref}
60
+ className={cn("inline-flex items-center gap-1.5", className)}
61
+ {...props}
62
+ />
63
+ ));
64
+ BreadcrumbItem.displayName = "BreadcrumbItem";
65
+
66
+ export interface BreadcrumbLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
67
+ asChild?: boolean;
68
+ }
69
+
70
+ export const BreadcrumbLink = React.forwardRef<
71
+ HTMLAnchorElement,
72
+ BreadcrumbLinkProps
73
+ >(({ asChild, className, ...props }, ref) => {
74
+ const Comp = (asChild ? Slot : "a") as React.ElementType;
75
+ return (
76
+ <Comp
77
+ ref={ref}
78
+ className={cn(
79
+ "text-text-muted hover:text-foreground transition-colors",
80
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm",
81
+ className
82
+ )}
83
+ {...props}
84
+ />
85
+ );
86
+ });
87
+ BreadcrumbLink.displayName = "BreadcrumbLink";
88
+
89
+ export const BreadcrumbPage = React.forwardRef<
90
+ HTMLSpanElement,
91
+ React.HTMLAttributes<HTMLSpanElement>
92
+ >(({ className, ...props }, ref) => (
93
+ <span
94
+ ref={ref}
95
+ role="link"
96
+ aria-disabled="true"
97
+ aria-current="page"
98
+ className={cn("text-foreground font-medium", className)}
99
+ {...props}
100
+ />
101
+ ));
102
+ BreadcrumbPage.displayName = "BreadcrumbPage";
103
+
104
+ export const BreadcrumbSeparator = React.forwardRef<
105
+ HTMLLIElement,
106
+ React.LiHTMLAttributes<HTMLLIElement>
107
+ >(({ className, children, ...props }, ref) => (
108
+ <li
109
+ ref={ref}
110
+ role="presentation"
111
+ aria-hidden="true"
112
+ className={cn(
113
+ "[&>svg]:h-3.5 [&>svg]:w-3.5 text-text-subtle inline-flex",
114
+ className
115
+ )}
116
+ {...props}
117
+ >
118
+ {children ?? <ChevronRight strokeWidth={1.75} />}
119
+ </li>
120
+ ));
121
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
@@ -0,0 +1,83 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Slot } from "@radix-ui/react-slot";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+ import { cn } from "../lib/utils";
7
+
8
+ /**
9
+ * Button — per design.md §10
10
+ *
11
+ * Variants:
12
+ * - accent The single loudest CTA on the screen (Book, Pay, Go Live). Lime, black text.
13
+ * - primary Default primary action. Forest, white text.
14
+ * - secondary Outline button. Cancel, alt actions.
15
+ * - ghost Toolbar / table row actions.
16
+ * - destructive Cancel game, refund, delete.
17
+ * - link In-flow inline link button.
18
+ *
19
+ * Sizes: xs (24px) · sm (28px) · default (32px) · lg (36px) · icon (32px
20
+ * square) · touch (44px — prominent mobile CTAs)
21
+ *
22
+ * Accessibility:
23
+ * - Visible focus ring (2px theme ring color) via :focus-visible
24
+ * - On touch devices (pointer: coarse) every size grows to a 44px
25
+ * minimum hit target; visual density is preserved on desktop
26
+ * - Disabled state has aria-friendly opacity drop; not interactive
27
+ * - Icon-only buttons require aria-label
28
+ */
29
+ const buttonVariants = cva(
30
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 pointer-coarse:min-h-11",
31
+ {
32
+ variants: {
33
+ variant: {
34
+ accent:
35
+ "bg-brand-lime text-brand-lime-foreground hover:bg-brand-lime/90 active:bg-brand-lime/80",
36
+ primary:
37
+ "bg-brand-forest text-brand-forest-foreground hover:bg-brand-forest/90 active:bg-brand-forest/80",
38
+ secondary:
39
+ "border border-border-strong bg-background text-foreground hover:bg-bg-subtle active:bg-bg-muted",
40
+ ghost: "text-foreground hover:bg-bg-subtle active:bg-bg-muted",
41
+ destructive:
42
+ "bg-danger text-danger-foreground hover:bg-danger/90 active:bg-danger/80",
43
+ link: "text-brand-forest underline-offset-4 hover:underline px-0",
44
+ },
45
+ size: {
46
+ xs: "h-6 px-2 text-xs rounded-sm",
47
+ sm: "h-7 px-2.5 text-xs rounded-sm",
48
+ default: "h-8 px-3 text-sm rounded-sm",
49
+ lg: "h-9 px-4 text-sm rounded-sm",
50
+ icon: "h-8 w-8 rounded-sm pointer-coarse:min-w-11",
51
+ touch: "h-11 px-4 text-md rounded-sm",
52
+ },
53
+ },
54
+ defaultVariants: {
55
+ variant: "primary",
56
+ size: "default",
57
+ },
58
+ }
59
+ );
60
+
61
+ export interface ButtonProps
62
+ extends
63
+ React.ButtonHTMLAttributes<HTMLButtonElement>,
64
+ VariantProps<typeof buttonVariants> {
65
+ asChild?: boolean;
66
+ }
67
+
68
+ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
69
+ ({ className, variant, size, asChild = false, type, ...props }, ref) => {
70
+ const Comp = asChild ? Slot : "button";
71
+ return (
72
+ <Comp
73
+ ref={ref}
74
+ type={asChild ? undefined : (type ?? "button")}
75
+ className={cn(buttonVariants({ variant, size, className }))}
76
+ {...props}
77
+ />
78
+ );
79
+ }
80
+ );
81
+ Button.displayName = "Button";
82
+
83
+ export { buttonVariants };
@@ -0,0 +1,85 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ChevronLeft, ChevronRight } from "lucide-react";
5
+ import { DayPicker } from "react-day-picker";
6
+ import { cn } from "../lib/utils";
7
+
8
+ /**
9
+ * Calendar — themed react-day-picker. Usually consumed via DatePicker;
10
+ * use directly for inline calendars (availability views, embedded
11
+ * scheduling).
12
+ *
13
+ * <Calendar mode="single" selected={date} onSelect={setDate} />
14
+ *
15
+ * Keyboard: arrows move by day/week, PageUp/Down by month, Home/End to
16
+ * week edges, Enter selects (provided by react-day-picker).
17
+ */
18
+ export type CalendarProps = React.ComponentProps<typeof DayPicker>;
19
+
20
+ export function Calendar({ className, classNames, ...props }: CalendarProps) {
21
+ return (
22
+ <DayPicker
23
+ className={cn("p-3", className)}
24
+ classNames={{
25
+ root: "relative",
26
+ months: "flex flex-col gap-4",
27
+ month: "space-y-3",
28
+ month_caption: "flex items-center justify-center h-8",
29
+ caption_label: "text-sm font-semibold",
30
+ nav: "absolute inset-x-1 top-1 z-10 flex items-center justify-between",
31
+ button_previous: cn(
32
+ "inline-flex h-6 w-6 pointer-coarse:h-11 pointer-coarse:w-11 items-center justify-center rounded-sm text-text-muted",
33
+ "hover:bg-bg-subtle hover:text-foreground transition-colors",
34
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
35
+ "disabled:opacity-40 disabled:pointer-events-none"
36
+ ),
37
+ button_next: cn(
38
+ "inline-flex h-6 w-6 pointer-coarse:h-11 pointer-coarse:w-11 items-center justify-center rounded-sm text-text-muted",
39
+ "hover:bg-bg-subtle hover:text-foreground transition-colors",
40
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
41
+ "disabled:opacity-40 disabled:pointer-events-none"
42
+ ),
43
+ month_grid: "w-full border-collapse",
44
+ weekdays: "flex",
45
+ weekday:
46
+ "w-8 text-[10px] font-semibold uppercase tracking-[0.06em] text-text-subtle",
47
+ weeks: "space-y-0.5",
48
+ week: "flex",
49
+ day: "p-0",
50
+ day_button: cn(
51
+ "h-8 w-8 pointer-coarse:h-11 pointer-coarse:w-11 rounded-sm text-sm tabular transition-colors",
52
+ "hover:bg-bg-subtle",
53
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
54
+ "disabled:opacity-40 disabled:pointer-events-none"
55
+ ),
56
+ selected:
57
+ "[&>button]:bg-brand-forest [&>button]:text-brand-forest-foreground [&>button]:hover:bg-brand-forest",
58
+ today: "[&>button]:font-bold [&>button]:text-brand-forest",
59
+ outside: "[&>button]:text-text-subtle",
60
+ disabled: "[&>button]:opacity-40 [&>button]:pointer-events-none",
61
+ hidden: "invisible",
62
+ ...classNames,
63
+ }}
64
+ components={{
65
+ Chevron: ({ orientation, ...chevronProps }) =>
66
+ orientation === "left" ? (
67
+ <ChevronLeft
68
+ className="h-4 w-4"
69
+ strokeWidth={1.75}
70
+ aria-hidden="true"
71
+ {...chevronProps}
72
+ />
73
+ ) : (
74
+ <ChevronRight
75
+ className="h-4 w-4"
76
+ strokeWidth={1.75}
77
+ aria-hidden="true"
78
+ {...chevronProps}
79
+ />
80
+ ),
81
+ }}
82
+ {...props}
83
+ />
84
+ );
85
+ }
@@ -0,0 +1,80 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ /**
5
+ * CapacityBar — the spots-filled visualization used everywhere games
6
+ * appear (admin row, public card, game detail aside).
7
+ *
8
+ * <CapacityBar filled={8} capacity={10} minToRun={6} />
9
+ *
10
+ * Colors:
11
+ * - foreground when 100% full
12
+ * - warning when filled < minToRun (sub-minimum, needs action)
13
+ * - brand-forest otherwise
14
+ */
15
+ const HEIGHT_MAP = {
16
+ sm: "h-1",
17
+ default: "h-1.5",
18
+ lg: "h-2",
19
+ } as const;
20
+
21
+ export interface CapacityBarProps extends React.HTMLAttributes<HTMLDivElement> {
22
+ filled: number;
23
+ capacity: number;
24
+ /** Below this number, the bar tints warning */
25
+ minToRun?: number;
26
+ /** Show the "8/10" count text next to the bar */
27
+ showCount?: boolean;
28
+ /** Bar thickness */
29
+ size?: keyof typeof HEIGHT_MAP;
30
+ /** Override the accessible label (default: "8 of 10 spots filled") */
31
+ label?: string;
32
+ }
33
+
34
+ export function CapacityBar({
35
+ filled,
36
+ capacity,
37
+ minToRun,
38
+ showCount = true,
39
+ size = "default",
40
+ label,
41
+ className,
42
+ ...props
43
+ }: CapacityBarProps) {
44
+ const pct = capacity > 0 ? Math.round((filled / capacity) * 100) : 0;
45
+ const isFull = filled >= capacity;
46
+ const belowMin = minToRun !== undefined && filled < minToRun;
47
+
48
+ return (
49
+ <div className={cn("flex items-center gap-2", className)} {...props}>
50
+ <div
51
+ className={cn(
52
+ "flex-1 rounded-full bg-bg-muted overflow-hidden",
53
+ HEIGHT_MAP[size]
54
+ )}
55
+ role="progressbar"
56
+ aria-valuenow={filled}
57
+ aria-valuemin={0}
58
+ aria-valuemax={capacity}
59
+ aria-label={label ?? `${filled} of ${capacity} spots filled`}
60
+ >
61
+ <div
62
+ className={cn(
63
+ "h-full transition-all",
64
+ isFull
65
+ ? "bg-foreground"
66
+ : belowMin
67
+ ? "bg-warning"
68
+ : "bg-brand-forest"
69
+ )}
70
+ style={{ width: `${pct}%` }}
71
+ />
72
+ </div>
73
+ {showCount && (
74
+ <span className="tabular text-xs text-text-muted shrink-0">
75
+ {filled}/{capacity}
76
+ </span>
77
+ )}
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,104 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cn } from "../lib/utils";
4
+
5
+ /**
6
+ * Card primitives — per design.md §10
7
+ * Border 1px, radius 4px, padding 16–20px. NO shadow at rest.
8
+ *
9
+ * Composition:
10
+ * <Card>
11
+ * <CardHeader>
12
+ * <CardTitle>…</CardTitle>
13
+ * <CardDescription>…</CardDescription>
14
+ * </CardHeader>
15
+ * <CardContent>…</CardContent>
16
+ * <CardFooter>…</CardFooter>
17
+ * </Card>
18
+ */
19
+
20
+ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
21
+ /** Render as the child element (e.g. a Next.js Link) with card styling. */
22
+ asChild?: boolean;
23
+ }
24
+
25
+ export const Card = React.forwardRef<HTMLDivElement, CardProps>(
26
+ ({ className, asChild = false, ...props }, ref) => {
27
+ const Comp = asChild ? Slot : "div";
28
+ return (
29
+ <Comp
30
+ ref={ref}
31
+ className={cn(
32
+ "rounded border border-border bg-background text-foreground",
33
+ className
34
+ )}
35
+ {...props}
36
+ />
37
+ );
38
+ }
39
+ );
40
+ Card.displayName = "Card";
41
+
42
+ export const CardHeader = React.forwardRef<
43
+ HTMLDivElement,
44
+ React.HTMLAttributes<HTMLDivElement>
45
+ >(({ className, ...props }, ref) => (
46
+ <div
47
+ ref={ref}
48
+ className={cn(
49
+ "flex items-start justify-between gap-3 px-5 pt-4 pb-3 border-b border-border",
50
+ className
51
+ )}
52
+ {...props}
53
+ />
54
+ ));
55
+ CardHeader.displayName = "CardHeader";
56
+
57
+ export const CardTitle = React.forwardRef<
58
+ HTMLHeadingElement,
59
+ React.HTMLAttributes<HTMLHeadingElement>
60
+ >(({ className, children, ...props }, ref) => (
61
+ <h3
62
+ ref={ref}
63
+ className={cn("text-md font-semibold tracking-[-0.01em]", className)}
64
+ {...props}
65
+ >
66
+ {children}
67
+ </h3>
68
+ ));
69
+ CardTitle.displayName = "CardTitle";
70
+
71
+ export const CardDescription = React.forwardRef<
72
+ HTMLParagraphElement,
73
+ React.HTMLAttributes<HTMLParagraphElement>
74
+ >(({ className, ...props }, ref) => (
75
+ <p
76
+ ref={ref}
77
+ className={cn("text-xs text-text-muted mt-0.5", className)}
78
+ {...props}
79
+ />
80
+ ));
81
+ CardDescription.displayName = "CardDescription";
82
+
83
+ export const CardContent = React.forwardRef<
84
+ HTMLDivElement,
85
+ React.HTMLAttributes<HTMLDivElement>
86
+ >(({ className, ...props }, ref) => (
87
+ <div ref={ref} className={cn("px-5 py-4", className)} {...props} />
88
+ ));
89
+ CardContent.displayName = "CardContent";
90
+
91
+ export const CardFooter = React.forwardRef<
92
+ HTMLDivElement,
93
+ React.HTMLAttributes<HTMLDivElement>
94
+ >(({ className, ...props }, ref) => (
95
+ <div
96
+ ref={ref}
97
+ className={cn(
98
+ "flex items-center justify-end gap-2 px-5 py-3 border-t border-border bg-bg-subtle/50",
99
+ className
100
+ )}
101
+ {...props}
102
+ />
103
+ ));
104
+ CardFooter.displayName = "CardFooter";
@@ -0,0 +1,46 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
5
+ import { Check, Minus } from "lucide-react";
6
+ import { cn } from "../lib/utils";
7
+
8
+ /**
9
+ * Checkbox — square 16px control with brand-forest fill when checked.
10
+ *
11
+ * Supports `indeterminate` via Radix (pass `checked="indeterminate"`).
12
+ * Always pair with a Label (`htmlFor` matching the checkbox `id`) — the
13
+ * FormField component does this automatically.
14
+ */
15
+ export const Checkbox = React.forwardRef<
16
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
17
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
18
+ >(({ className, ...props }, ref) => (
19
+ <CheckboxPrimitive.Root
20
+ ref={ref}
21
+ className={cn(
22
+ "peer h-4 w-4 shrink-0 rounded-sm border border-border-strong bg-background transition-colors",
23
+ // Invisible hit-area extension: 24px on desktop (WCAG 2.5.8),
24
+ // 44px on touch devices — the visual stays 16px.
25
+ "relative after:absolute after:-inset-1 pointer-coarse:after:-inset-3.5",
26
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
27
+ "disabled:cursor-not-allowed disabled:opacity-50",
28
+ "data-[state=checked]:bg-brand-forest data-[state=checked]:border-brand-forest data-[state=checked]:text-brand-forest-foreground",
29
+ "data-[state=indeterminate]:bg-brand-forest data-[state=indeterminate]:border-brand-forest data-[state=indeterminate]:text-brand-forest-foreground",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ <CheckboxPrimitive.Indicator className="flex items-center justify-center text-current">
35
+ <Check
36
+ className="h-3 w-3 data-[state=indeterminate]:hidden"
37
+ strokeWidth={3}
38
+ />
39
+ <Minus
40
+ className="hidden h-3 w-3 data-[state=indeterminate]:block"
41
+ strokeWidth={3}
42
+ />
43
+ </CheckboxPrimitive.Indicator>
44
+ </CheckboxPrimitive.Root>
45
+ ));
46
+ Checkbox.displayName = "Checkbox";
@@ -0,0 +1,74 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+ import { X } from "lucide-react";
6
+ import { cn } from "../lib/utils";
7
+
8
+ /**
9
+ * Chip — small removable token. Use for active filter indicators,
10
+ * applied tags, selected options in a multi-select summary.
11
+ *
12
+ * <Chip onRemove={() => setVenue("all")}>Astoria Turf</Chip>
13
+ * <Chip variant="neutral">Soccer</Chip>
14
+ *
15
+ * Distinct from Badge: Chips are interactive (often have a close button)
16
+ * and represent transient selections, while Badges are static labels.
17
+ */
18
+ export const chipVariants = cva(
19
+ "inline-flex items-center gap-1 h-5 px-1.5 rounded-sm text-xs font-medium leading-none whitespace-nowrap",
20
+ {
21
+ variants: {
22
+ variant: {
23
+ accent: "bg-brand-forest/10 text-brand-forest",
24
+ neutral: "bg-bg-muted text-foreground",
25
+ outline: "border border-border-strong bg-transparent text-foreground",
26
+ },
27
+ },
28
+ defaultVariants: { variant: "accent" },
29
+ }
30
+ );
31
+
32
+ export interface ChipProps
33
+ extends
34
+ React.HTMLAttributes<HTMLSpanElement>,
35
+ VariantProps<typeof chipVariants> {
36
+ /** When provided, renders an × button. */
37
+ onRemove?: () => void;
38
+ /** Accessible label for the remove button (default: "Remove {children}") */
39
+ removeLabel?: string;
40
+ }
41
+
42
+ export function Chip({
43
+ className,
44
+ variant,
45
+ onRemove,
46
+ removeLabel,
47
+ children,
48
+ ...props
49
+ }: ChipProps) {
50
+ return (
51
+ <span className={cn(chipVariants({ variant }), className)} {...props}>
52
+ <span>{children}</span>
53
+ {onRemove && (
54
+ <button
55
+ type="button"
56
+ onClick={onRemove}
57
+ aria-label={
58
+ removeLabel ??
59
+ `Remove ${typeof children === "string" ? children : "filter"}`
60
+ }
61
+ className={cn(
62
+ "rounded-sm p-0.5 transition-colors",
63
+ "hover:bg-current/10",
64
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
65
+ // Invisible hit-area extension: 26px desktop / 46px touch.
66
+ "relative after:absolute after:-inset-1.5 pointer-coarse:after:-inset-4"
67
+ )}
68
+ >
69
+ <X className="h-2.5 w-2.5" strokeWidth={2} />
70
+ </button>
71
+ )}
72
+ </span>
73
+ );
74
+ }