@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,154 @@
1
+ {
2
+ "$description": "DT Sports design tokens — single source of truth. Edit this file, then run `npm run build:tokens`. Never edit the generated blocks in globals.css, src/styles/generated/, or src/tokens/index.ts by hand.",
3
+ "color": {
4
+ "$format": "HSL channel triplets (\"H S% L%\") so CSS can compose hsl(var(--x) / alpha).",
5
+ "light": {
6
+ "brand-lime": "72 65% 52%",
7
+ "brand-lime-foreground": "0 0% 4%",
8
+ "brand-forest": "147 66% 26%",
9
+ "brand-forest-foreground": "0 0% 100%",
10
+ "background": "0 0% 100%",
11
+ "bg-subtle": "0 0% 98%",
12
+ "bg-muted": "240 5% 96%",
13
+ "border": "0 0% 90%",
14
+ "border-strong": "0 0% 83%",
15
+ "foreground": "0 0% 4%",
16
+ "text-muted": "0 0% 32%",
17
+ "text-subtle": "0 0% 45%",
18
+ "success": "142 72% 29%",
19
+ "success-foreground": "0 0% 100%",
20
+ "warning": "35 90% 33%",
21
+ "warning-foreground": "0 0% 100%",
22
+ "danger": "0 72% 49%",
23
+ "danger-foreground": "0 0% 100%",
24
+ "info": "217 91% 50%",
25
+ "info-foreground": "0 0% 100%",
26
+ "ring": "147 66% 26%"
27
+ },
28
+ "dark": {
29
+ "$comment": "Overrides only — anything not listed inherits the light value.",
30
+ "background": "0 0% 4%",
31
+ "bg-subtle": "0 0% 8%",
32
+ "bg-muted": "0 0% 12%",
33
+ "border": "0 0% 15%",
34
+ "border-strong": "240 5% 26%",
35
+ "foreground": "0 0% 98%",
36
+ "text-muted": "240 5% 65%",
37
+ "text-subtle": "240 4% 52%",
38
+ "success": "142 60% 45%",
39
+ "success-foreground": "0 0% 4%",
40
+ "warning": "38 92% 50%",
41
+ "warning-foreground": "0 0% 4%",
42
+ "danger": "0 84% 62%",
43
+ "danger-foreground": "0 0% 4%",
44
+ "info": "217 91% 62%",
45
+ "info-foreground": "0 0% 4%",
46
+ "ring": "72 65% 52%"
47
+ }
48
+ },
49
+ "fontFamily": {
50
+ "sans": ["\"Exo 2\"", "system-ui", "sans-serif"],
51
+ "mono": [
52
+ "ui-monospace",
53
+ "SFMono-Regular",
54
+ "\"JetBrains Mono\"",
55
+ "Menlo",
56
+ "monospace"
57
+ ]
58
+ },
59
+ "fontSize": {
60
+ "xs": { "size": 11, "lineHeight": 16 },
61
+ "sm": { "size": 13, "lineHeight": 18 },
62
+ "base": { "size": 14, "lineHeight": 20 },
63
+ "md": { "size": 15, "lineHeight": 22 },
64
+ "lg": { "size": 17, "lineHeight": 24 },
65
+ "xl": { "size": 20, "lineHeight": 28 },
66
+ "2xl": { "size": 24, "lineHeight": 32 },
67
+ "3xl": { "size": 30, "lineHeight": 36 }
68
+ },
69
+ "fontSizeFluid": {
70
+ "$comment": "Marketing display scale — clamp(minPx, basePx + vw, maxPx). Web gets fluid clamp(); React Native gets {min,max}.",
71
+ "display-sm": {
72
+ "min": 32,
73
+ "max": 44,
74
+ "base": 24,
75
+ "vw": 2.5,
76
+ "lineHeight": 1.1,
77
+ "letterSpacing": "-0.02em"
78
+ },
79
+ "display": {
80
+ "min": 40,
81
+ "max": 64,
82
+ "base": 26,
83
+ "vw": 4,
84
+ "lineHeight": 1.05,
85
+ "letterSpacing": "-0.02em"
86
+ },
87
+ "display-lg": {
88
+ "min": 48,
89
+ "max": 96,
90
+ "base": 28,
91
+ "vw": 6,
92
+ "lineHeight": 1,
93
+ "letterSpacing": "-0.025em"
94
+ }
95
+ },
96
+ "fontWeight": {
97
+ "normal": 400,
98
+ "medium": 500,
99
+ "semibold": 600,
100
+ "bold": 700
101
+ },
102
+ "spacing": {
103
+ "0.5": 2,
104
+ "1": 4,
105
+ "1.5": 6,
106
+ "2": 8,
107
+ "2.5": 10,
108
+ "3": 12,
109
+ "4": 16,
110
+ "5": 20,
111
+ "6": 24,
112
+ "8": 32,
113
+ "10": 40,
114
+ "12": 48,
115
+ "16": 64,
116
+ "20": 80,
117
+ "24": 96,
118
+ "32": 128,
119
+ "40": 160
120
+ },
121
+ "radius": {
122
+ "sm": 2,
123
+ "DEFAULT": 4,
124
+ "md": 6
125
+ },
126
+ "shadow": {
127
+ "sm": "0 1px 2px rgba(0,0,0,0.04), 0 1px 1px rgba(0,0,0,0.04)",
128
+ "md": "0 4px 12px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.06)",
129
+ "lg": "0 12px 32px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.08)"
130
+ },
131
+ "gradient": {
132
+ "brand": "linear-gradient(135deg, hsl(var(--brand-forest)) 0%, hsl(100 55% 38%) 55%, hsl(var(--brand-lime)) 100%)",
133
+ "forest-fade": "linear-gradient(180deg, hsl(var(--brand-forest) / 0.08) 0%, transparent 100%)"
134
+ },
135
+ "motion": {
136
+ "duration": { "DEFAULT": 150, "slow": 200, "pulse": 1500 },
137
+ "easing": { "out": "cubic-bezier(0.2, 0, 0, 1)" }
138
+ },
139
+ "zIndex": {
140
+ "sticky": 20,
141
+ "overlay": 40,
142
+ "modal": 50,
143
+ "popover": 50,
144
+ "toast": 60
145
+ },
146
+ "breakpoint": {
147
+ "$comment": "Matches Tailwind defaults; declared so RN and marketing share them.",
148
+ "sm": 640,
149
+ "md": 768,
150
+ "lg": 1024,
151
+ "xl": 1280,
152
+ "2xl": 1536
153
+ }
154
+ }
package/package.json ADDED
@@ -0,0 +1,124 @@
1
+ {
2
+ "name": "@dtsports/design-system",
3
+ "version": "0.2.0",
4
+ "description": "DT Sports — shared design system. Tokens, components, and UX patterns for hosts and players.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": [
8
+ "**/*.css"
9
+ ],
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./showcase": {
18
+ "types": "./dist/showcase/index.d.ts",
19
+ "default": "./dist/showcase/index.js"
20
+ },
21
+ "./marketing": {
22
+ "types": "./dist/marketing/index.d.ts",
23
+ "default": "./dist/marketing/index.js"
24
+ },
25
+ "./styles/globals.css": "./dist/styles/globals.css",
26
+ "./tailwind-preset": {
27
+ "types": "./dist/styles/tailwind.preset.d.ts",
28
+ "default": "./dist/styles/tailwind.preset.js"
29
+ },
30
+ "./tokens": {
31
+ "types": "./dist/tokens/index.d.ts",
32
+ "default": "./dist/tokens/index.js"
33
+ },
34
+ "./tokens.json": "./dist/tokens/tokens.json"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "src"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "scripts": {
44
+ "build": "npm run build:tokens && rm -rf dist && tsc -p tsconfig.build.json && node scripts/fix-extensions.mjs && cp src/styles/globals.css dist/styles/globals.css && cp src/tokens/tokens.json dist/tokens/tokens.json",
45
+ "dev": "vite --config dev/vite.config.ts",
46
+ "test:visual": "playwright test",
47
+ "test:visual:update": "playwright test --update-snapshots",
48
+ "typecheck": "tsc --noEmit",
49
+ "test": "vitest run",
50
+ "test:watch": "vitest",
51
+ "check:contrast": "node scripts/check-contrast.mjs",
52
+ "build:tokens": "node scripts/build-tokens.mjs",
53
+ "check:tokens": "node scripts/build-tokens.mjs --check",
54
+ "lint": "eslint src",
55
+ "format": "prettier --write \"src/**/*.{ts,tsx,css}\" \"*.{js,json,md}\"",
56
+ "format:check": "prettier --check \"src/**/*.{ts,tsx,css}\" \"*.{js,json,md}\""
57
+ },
58
+ "dependencies": {
59
+ "@radix-ui/react-accordion": "^1.2.1",
60
+ "@radix-ui/react-aspect-ratio": "^1.1.0",
61
+ "@radix-ui/react-checkbox": "^1.1.2",
62
+ "@radix-ui/react-collapsible": "^1.1.15",
63
+ "@radix-ui/react-dialog": "^1.1.2",
64
+ "@radix-ui/react-dropdown-menu": "^2.1.2",
65
+ "@radix-ui/react-label": "^2.1.0",
66
+ "@radix-ui/react-popover": "^1.1.2",
67
+ "@radix-ui/react-progress": "^1.1.0",
68
+ "@radix-ui/react-radio-group": "^1.2.1",
69
+ "@radix-ui/react-scroll-area": "^1.2.1",
70
+ "@radix-ui/react-select": "^2.1.2",
71
+ "@radix-ui/react-separator": "^1.1.0",
72
+ "@radix-ui/react-slider": "^1.4.2",
73
+ "@radix-ui/react-slot": "^1.1.0",
74
+ "@radix-ui/react-switch": "^1.1.1",
75
+ "@radix-ui/react-tabs": "^1.1.1",
76
+ "@radix-ui/react-tooltip": "^1.1.3",
77
+ "class-variance-authority": "^0.7.1",
78
+ "clsx": "^2.1.1",
79
+ "cmdk": "^1.1.1",
80
+ "lucide-react": "^0.460.0",
81
+ "react-day-picker": "^10.0.1",
82
+ "sonner": "^1.7.0",
83
+ "tailwind-merge": "^2.5.4"
84
+ },
85
+ "peerDependencies": {
86
+ "react": ">=19",
87
+ "react-dom": ">=19",
88
+ "tailwindcss": ">=3.4"
89
+ },
90
+ "peerDependenciesMeta": {
91
+ "react-dom": {
92
+ "optional": true
93
+ },
94
+ "tailwindcss": {
95
+ "optional": true
96
+ }
97
+ },
98
+ "devDependencies": {
99
+ "@changesets/cli": "^2.31.0",
100
+ "@eslint/js": "^9.39.4",
101
+ "@playwright/test": "^1.61.1",
102
+ "@testing-library/jest-dom": "^6.9.1",
103
+ "@testing-library/react": "^16.3.2",
104
+ "@testing-library/user-event": "^14.6.1",
105
+ "@types/jest-axe": "^3.5.9",
106
+ "@types/react": "^19.2.15",
107
+ "@types/react-dom": "^19.2.3",
108
+ "@vitejs/plugin-react": "^5.2.0",
109
+ "autoprefixer": "^10.5.2",
110
+ "eslint": "^9.39.4",
111
+ "eslint-config-prettier": "^10.1.8",
112
+ "eslint-plugin-jsx-a11y": "^6.10.2",
113
+ "eslint-plugin-react": "^7.37.5",
114
+ "eslint-plugin-react-hooks": "^7.1.1",
115
+ "jest-axe": "^10.0.0",
116
+ "jsdom": "^27.0.1",
117
+ "prettier": "^3.9.4",
118
+ "tailwindcss": "^3.4.15",
119
+ "typescript": "^5.7.2",
120
+ "typescript-eslint": "^8.62.1",
121
+ "vite": "^7.3.6",
122
+ "vitest": "^3.2.6"
123
+ }
124
+ }
@@ -0,0 +1,76 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as AccordionPrimitive from "@radix-ui/react-accordion";
5
+ import { ChevronDown } from "lucide-react";
6
+ import { cn } from "../lib/utils";
7
+
8
+ /**
9
+ * Accordion — for grouped expandable sections (House rules · Policy ·
10
+ * About). Use `type="single"` for FAQ-style (one-open-at-a-time) and
11
+ * `type="multiple"` for settings panels.
12
+ *
13
+ * <Accordion type="single" collapsible>
14
+ * <AccordionItem value="a">
15
+ * <AccordionTrigger>House rules</AccordionTrigger>
16
+ * <AccordionContent>…</AccordionContent>
17
+ * </AccordionItem>
18
+ * </Accordion>
19
+ */
20
+ export const Accordion = AccordionPrimitive.Root;
21
+
22
+ export const AccordionItem = React.forwardRef<
23
+ React.ElementRef<typeof AccordionPrimitive.Item>,
24
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
25
+ >(({ className, ...props }, ref) => (
26
+ <AccordionPrimitive.Item
27
+ ref={ref}
28
+ className={cn("border-b border-border last:border-b-0", className)}
29
+ {...props}
30
+ />
31
+ ));
32
+ AccordionItem.displayName = "AccordionItem";
33
+
34
+ export const AccordionTrigger = React.forwardRef<
35
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
36
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
37
+ >(({ className, children, ...props }, ref) => (
38
+ <AccordionPrimitive.Header className="flex">
39
+ <AccordionPrimitive.Trigger
40
+ ref={ref}
41
+ className={cn(
42
+ "flex flex-1 items-center justify-between py-3 text-sm font-medium text-foreground transition-all duration-150",
43
+ "hover:text-brand-forest",
44
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:rounded-sm",
45
+ "[&[data-state=open]>svg]:rotate-180",
46
+ className
47
+ )}
48
+ {...props}
49
+ >
50
+ {children}
51
+ <ChevronDown
52
+ className="h-3.5 w-3.5 shrink-0 text-text-muted transition-transform duration-150"
53
+ strokeWidth={1.75}
54
+ />
55
+ </AccordionPrimitive.Trigger>
56
+ </AccordionPrimitive.Header>
57
+ ));
58
+ AccordionTrigger.displayName = "AccordionTrigger";
59
+
60
+ export const AccordionContent = React.forwardRef<
61
+ React.ElementRef<typeof AccordionPrimitive.Content>,
62
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
63
+ >(({ className, children, ...props }, ref) => (
64
+ <AccordionPrimitive.Content
65
+ ref={ref}
66
+ className={cn(
67
+ "overflow-hidden text-sm text-foreground",
68
+ "data-[state=closed]:animate-fade-in data-[state=open]:animate-fade-in",
69
+ className
70
+ )}
71
+ {...props}
72
+ >
73
+ <div className="pb-3 pt-0">{children}</div>
74
+ </AccordionPrimitive.Content>
75
+ ));
76
+ AccordionContent.displayName = "AccordionContent";
@@ -0,0 +1,113 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { AlertTriangle, CheckCircle2, Info, X, XCircle } from "lucide-react";
4
+ import { cn } from "../lib/utils";
5
+
6
+ /**
7
+ * Alert / inline notice — for status messages that aren't transient.
8
+ * Pair with the right variant:
9
+ * - info neutral notices
10
+ * - success confirmations that stick (booking saved, etc.)
11
+ * - warning needs attention (below min to run, low credit)
12
+ * - danger error / failure
13
+ *
14
+ * Use Toast for transient feedback; use Alert when the message belongs
15
+ * inline with the content.
16
+ */
17
+ export const alertVariants = cva(
18
+ "relative w-full rounded border px-3 py-2.5 text-sm",
19
+ {
20
+ variants: {
21
+ variant: {
22
+ info: "border-info/30 bg-info/5 text-foreground",
23
+ success: "border-success/30 bg-success/5 text-foreground",
24
+ warning: "border-warning/40 bg-warning/5 text-foreground",
25
+ danger: "border-danger/30 bg-danger/5 text-foreground",
26
+ },
27
+ },
28
+ defaultVariants: { variant: "info" },
29
+ }
30
+ );
31
+
32
+ const ICON_MAP = {
33
+ info: Info,
34
+ success: CheckCircle2,
35
+ warning: AlertTriangle,
36
+ danger: XCircle,
37
+ } as const;
38
+
39
+ const ICON_COLOR_MAP = {
40
+ info: "text-info",
41
+ success: "text-success",
42
+ warning: "text-warning",
43
+ danger: "text-danger",
44
+ } as const;
45
+
46
+ export interface AlertProps
47
+ extends
48
+ Omit<React.HTMLAttributes<HTMLDivElement>, "title">,
49
+ VariantProps<typeof alertVariants> {
50
+ title?: React.ReactNode;
51
+ /** Set false to hide the leading status icon */
52
+ showIcon?: boolean;
53
+ /** Show a close (×) button. Provide onDismiss to handle the click. */
54
+ dismissible?: boolean;
55
+ onDismiss?: () => void;
56
+ }
57
+
58
+ export function Alert({
59
+ className,
60
+ variant,
61
+ title,
62
+ showIcon = true,
63
+ dismissible,
64
+ onDismiss,
65
+ children,
66
+ ...props
67
+ }: AlertProps) {
68
+ const v = variant ?? "info";
69
+ const Icon = ICON_MAP[v];
70
+ const iconColor = ICON_COLOR_MAP[v];
71
+ return (
72
+ <div
73
+ role={v === "danger" ? "alert" : "status"}
74
+ className={cn(alertVariants({ variant: v }), className)}
75
+ {...props}
76
+ >
77
+ <div className="flex items-start gap-2.5">
78
+ {showIcon && (
79
+ <Icon
80
+ className={cn("h-4 w-4 shrink-0 mt-0.5", iconColor)}
81
+ strokeWidth={2}
82
+ aria-hidden="true"
83
+ />
84
+ )}
85
+ <div className="flex-1 min-w-0">
86
+ {title && (
87
+ <div className="font-medium leading-tight text-sm">{title}</div>
88
+ )}
89
+ {children && (
90
+ <div
91
+ className={cn(
92
+ title && "mt-1",
93
+ "text-xs text-text-muted [&_a]:text-brand-forest [&_a]:underline-offset-2 [&_a]:hover:underline"
94
+ )}
95
+ >
96
+ {children}
97
+ </div>
98
+ )}
99
+ </div>
100
+ {dismissible && (
101
+ <button
102
+ type="button"
103
+ onClick={onDismiss}
104
+ aria-label="Dismiss"
105
+ className="shrink-0 -mr-1 -mt-0.5 h-5 w-5 grid place-items-center rounded-sm text-text-muted hover:bg-bg-subtle hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring relative after:absolute after:-inset-0.5 pointer-coarse:after:-inset-3"
106
+ >
107
+ <X className="h-3 w-3" strokeWidth={1.75} />
108
+ </button>
109
+ )}
110
+ </div>
111
+ </div>
112
+ );
113
+ }
@@ -0,0 +1,43 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ /**
5
+ * AppShell — flex layout for sidebar + main content.
6
+ * The actual sidebar and content are children; AppShell just provides
7
+ * the full-height flex frame.
8
+ *
9
+ * <AppShell>
10
+ * <Sidebar>…</Sidebar>
11
+ * <AppMain>
12
+ * <TopBar>…</TopBar>
13
+ * <PageContent>…</PageContent>
14
+ * </AppMain>
15
+ * </AppShell>
16
+ */
17
+ export const AppShell = React.forwardRef<
18
+ HTMLDivElement,
19
+ React.HTMLAttributes<HTMLDivElement>
20
+ >(({ className, ...props }, ref) => (
21
+ <div
22
+ ref={ref}
23
+ className={cn("flex min-h-screen w-full", className)}
24
+ {...props}
25
+ />
26
+ ));
27
+ AppShell.displayName = "AppShell";
28
+
29
+ /**
30
+ * AppMain — main content area, scrollable, takes remaining width.
31
+ * Pair with TopBar (sticky) and your page content inside.
32
+ */
33
+ export const AppMain = React.forwardRef<
34
+ HTMLDivElement,
35
+ React.HTMLAttributes<HTMLDivElement>
36
+ >(({ className, ...props }, ref) => (
37
+ <div
38
+ ref={ref}
39
+ className={cn("flex-1 min-w-0 flex flex-col", className)}
40
+ {...props}
41
+ />
42
+ ));
43
+ AppMain.displayName = "AppMain";
@@ -0,0 +1,18 @@
1
+ "use client";
2
+
3
+ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
4
+
5
+ /**
6
+ * AspectRatio — locks a region to a given ratio without layout shift.
7
+ *
8
+ * <AspectRatio ratio={16 / 9}>
9
+ * <iframe src={…} className="h-full w-full" />
10
+ * </AspectRatio>
11
+ *
12
+ * Common ratios:
13
+ * - 16/9 video
14
+ * - 4/3 older video / photos
15
+ * - 1/1 square
16
+ * - 21/9 ultrawide / banner
17
+ */
18
+ export const AspectRatio = AspectRatioPrimitive.Root;
@@ -0,0 +1,73 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ /**
5
+ * AvatarGroup — stacked overlapping avatars. Shows up to `max` avatars
6
+ * followed by a "+N" pill for the overflow.
7
+ *
8
+ * <AvatarGroup max={4}>
9
+ * <Avatar name="Alex Chen" />
10
+ * <Avatar name="Jordan Lee" />
11
+ * <Avatar name="Sam Patel" />
12
+ * <Avatar name="Maria Garcia" />
13
+ * <Avatar name="Danny Tran" />
14
+ * </AvatarGroup>
15
+ *
16
+ * Use for: roster previews, "8 people booked" inline indicators.
17
+ */
18
+ const SIZE_CLASSES = {
19
+ sm: "h-6 w-6 text-[10px]",
20
+ default: "h-8 w-8 text-xs",
21
+ lg: "h-10 w-10 text-sm",
22
+ } as const;
23
+
24
+ export interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
25
+ /** Max avatars shown before collapsing to "+N". */
26
+ max?: number;
27
+ /** Size of the overflow pill — should match the avatars inside. */
28
+ size?: keyof typeof SIZE_CLASSES;
29
+ /** Accessible label for screen readers (e.g. "8 players booked"). */
30
+ label?: string;
31
+ }
32
+
33
+ export const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
34
+ (
35
+ { className, max = 4, size = "default", label, children, ...props },
36
+ ref
37
+ ) => {
38
+ const all = React.Children.toArray(children).filter(Boolean);
39
+ const visible = all.slice(0, max);
40
+ const overflow = all.length - visible.length;
41
+
42
+ return (
43
+ <div
44
+ ref={ref}
45
+ role={label ? "img" : undefined}
46
+ aria-label={label}
47
+ className={cn("inline-flex -space-x-1.5", className)}
48
+ {...props}
49
+ >
50
+ {visible.map((child, i) => (
51
+ <span
52
+ key={i}
53
+ className="ring-2 ring-background rounded-full inline-flex"
54
+ >
55
+ {child}
56
+ </span>
57
+ ))}
58
+ {overflow > 0 && (
59
+ <span
60
+ aria-hidden={label ? "true" : undefined}
61
+ className={cn(
62
+ "ring-2 ring-background rounded-full bg-bg-muted text-text-muted font-medium tabular grid place-items-center",
63
+ SIZE_CLASSES[size]
64
+ )}
65
+ >
66
+ +{overflow}
67
+ </span>
68
+ )}
69
+ </div>
70
+ );
71
+ }
72
+ );
73
+ AvatarGroup.displayName = "AvatarGroup";
@@ -0,0 +1,81 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ /**
5
+ * Avatar — per design.md §10
6
+ * Sizes: sm 24 / default 32 / lg 64
7
+ * Fallback: brand-forest bg, white initials (first letter of first + last)
8
+ */
9
+ export interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {
10
+ name: string;
11
+ src?: string;
12
+ size?: "sm" | "default" | "lg";
13
+ /** Optional status dot: lime = checked-in, gray = booked */
14
+ status?: "checked-in" | "booked";
15
+ }
16
+
17
+ function initials(name: string) {
18
+ const parts = name.trim().split(/\s+/);
19
+ const first = parts[0]?.[0] ?? "";
20
+ const last = parts.length > 1 ? parts[parts.length - 1][0] : "";
21
+ return (first + last).toUpperCase() || "?";
22
+ }
23
+
24
+ const sizeMap = {
25
+ sm: { box: "h-6 w-6", text: "text-[10px]", dot: "h-1.5 w-1.5" },
26
+ default: { box: "h-8 w-8", text: "text-xs", dot: "h-2 w-2" },
27
+ lg: { box: "h-16 w-16", text: "text-lg", dot: "h-3 w-3" },
28
+ };
29
+
30
+ export function Avatar({
31
+ name,
32
+ src,
33
+ size = "default",
34
+ status,
35
+ className,
36
+ ...props
37
+ }: AvatarProps) {
38
+ const s = sizeMap[size];
39
+ return (
40
+ <div
41
+ role="img"
42
+ // Descendants of role="img" are presentational, so the status must
43
+ // live in the container's label — a labelled dot would be ignored.
44
+ aria-label={
45
+ status
46
+ ? `${name} (${status === "checked-in" ? "checked in" : "booked"})`
47
+ : name
48
+ }
49
+ className={cn("relative inline-flex shrink-0", className)}
50
+ {...props}
51
+ >
52
+ {src ? (
53
+ <img
54
+ src={src}
55
+ alt={name}
56
+ className={cn("rounded-full object-cover", s.box)}
57
+ />
58
+ ) : (
59
+ <div
60
+ className={cn(
61
+ "flex items-center justify-center rounded-full bg-brand-forest text-brand-forest-foreground font-semibold tabular",
62
+ s.box,
63
+ s.text
64
+ )}
65
+ >
66
+ {initials(name)}
67
+ </div>
68
+ )}
69
+ {status && (
70
+ <span
71
+ aria-hidden="true"
72
+ className={cn(
73
+ "absolute -bottom-0.5 -right-0.5 rounded-full ring-2 ring-background",
74
+ s.dot,
75
+ status === "checked-in" ? "bg-brand-lime" : "bg-text-subtle"
76
+ )}
77
+ />
78
+ )}
79
+ </div>
80
+ );
81
+ }