@adres/mgda-ui 0.0.0-stage → 0.0.2

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 (325) hide show
  1. package/brand-knowledge.json +451 -0
  2. package/knowledge/accordion.md +135 -0
  3. package/knowledge/activity-rings.md +123 -0
  4. package/knowledge/alert.md +184 -0
  5. package/knowledge/answer-actions.md +217 -0
  6. package/knowledge/aspect-ratio.md +82 -0
  7. package/knowledge/avatar.md +158 -0
  8. package/knowledge/badge.md +172 -0
  9. package/knowledge/bar-graph.md +167 -0
  10. package/knowledge/bento-grid.md +115 -0
  11. package/knowledge/blockquote.md +153 -0
  12. package/knowledge/box-list.md +177 -0
  13. package/knowledge/bullet-list.md +128 -0
  14. package/knowledge/button-group.md +179 -0
  15. package/knowledge/button.md +231 -0
  16. package/knowledge/calendar.md +168 -0
  17. package/knowledge/card.md +321 -0
  18. package/knowledge/cards-nav.md +170 -0
  19. package/knowledge/chain-of-thought.md +164 -0
  20. package/knowledge/chat-container.md +135 -0
  21. package/knowledge/chat-message.md +224 -0
  22. package/knowledge/checkbox-card.md +172 -0
  23. package/knowledge/checkbox.md +111 -0
  24. package/knowledge/citations.md +241 -0
  25. package/knowledge/combobox.md +265 -0
  26. package/knowledge/countdown-pill.md +153 -0
  27. package/knowledge/cta.md +118 -0
  28. package/knowledge/data-list.md +173 -0
  29. package/knowledge/data-table.md +220 -0
  30. package/knowledge/dialog.md +249 -0
  31. package/knowledge/drawer.md +236 -0
  32. package/knowledge/dropdown-menu.md +238 -0
  33. package/knowledge/dropzone.md +153 -0
  34. package/knowledge/email-chips-input.md +161 -0
  35. package/knowledge/empty-state.md +106 -0
  36. package/knowledge/entity-header.md +166 -0
  37. package/knowledge/feature-grid.md +120 -0
  38. package/knowledge/feedback-bar.md +127 -0
  39. package/knowledge/footer.md +116 -0
  40. package/knowledge/heading.md +189 -0
  41. package/knowledge/hero.md +361 -0
  42. package/knowledge/input-button.md +187 -0
  43. package/knowledge/input-group.md +185 -0
  44. package/knowledge/input.md +256 -0
  45. package/knowledge/inset.md +177 -0
  46. package/knowledge/kpi-card.md +206 -0
  47. package/knowledge/list-nav.md +181 -0
  48. package/knowledge/logo-cloud.md +102 -0
  49. package/knowledge/media-gallery.md +158 -0
  50. package/knowledge/message-actions.md +175 -0
  51. package/knowledge/nav-sidebar.md +225 -0
  52. package/knowledge/number-stepper.md +159 -0
  53. package/knowledge/page-header.md +109 -0
  54. package/knowledge/pagination.md +225 -0
  55. package/knowledge/popover.md +159 -0
  56. package/knowledge/pricing.md +132 -0
  57. package/knowledge/progress.md +156 -0
  58. package/knowledge/prompt-input.md +337 -0
  59. package/knowledge/prompt-suggestions.md +168 -0
  60. package/knowledge/quota-bar.md +160 -0
  61. package/knowledge/quote.md +116 -0
  62. package/knowledge/radio-card.md +172 -0
  63. package/knowledge/radio-group.md +135 -0
  64. package/knowledge/reasoning.md +142 -0
  65. package/knowledge/search.md +131 -0
  66. package/knowledge/section-nav.md +162 -0
  67. package/knowledge/segmented-control.md +181 -0
  68. package/knowledge/select-button.md +205 -0
  69. package/knowledge/select.md +210 -0
  70. package/knowledge/separator.md +75 -0
  71. package/knowledge/shimmer-text.md +140 -0
  72. package/knowledge/sidebar.md +227 -0
  73. package/knowledge/skeleton.md +142 -0
  74. package/knowledge/slider.md +133 -0
  75. package/knowledge/slideshow.md +159 -0
  76. package/knowledge/source.md +156 -0
  77. package/knowledge/spinner.md +120 -0
  78. package/knowledge/stat-strip.md +128 -0
  79. package/knowledge/stats.md +117 -0
  80. package/knowledge/step-indicator.md +157 -0
  81. package/knowledge/switch.md +88 -0
  82. package/knowledge/table.md +190 -0
  83. package/knowledge/tabs.md +169 -0
  84. package/knowledge/testimonials.md +115 -0
  85. package/knowledge/text-link.md +192 -0
  86. package/knowledge/text.md +194 -0
  87. package/knowledge/thinking-bar.md +115 -0
  88. package/knowledge/thinking-steps.md +140 -0
  89. package/knowledge/toaster.md +127 -0
  90. package/knowledge/toggle.md +113 -0
  91. package/knowledge/tool-call-card.md +241 -0
  92. package/knowledge/tooltip.md +120 -0
  93. package/knowledge/top-nav.md +138 -0
  94. package/knowledge.json +19854 -0
  95. package/package.json +94 -4
  96. package/src/components/accordion.stories.tsx +46 -0
  97. package/src/components/accordion.tsx +74 -0
  98. package/src/components/activity-rings.stories.tsx +73 -0
  99. package/src/components/activity-rings.tsx +121 -0
  100. package/src/components/alert.stories.tsx +43 -0
  101. package/src/components/alert.tsx +132 -0
  102. package/src/components/answer-actions.stories.tsx +54 -0
  103. package/src/components/answer-actions.test.tsx +37 -0
  104. package/src/components/answer-actions.tsx +313 -0
  105. package/src/components/aspect-ratio.stories.tsx +32 -0
  106. package/src/components/aspect-ratio.tsx +5 -0
  107. package/src/components/avatar.stories.tsx +40 -0
  108. package/src/components/avatar.tsx +154 -0
  109. package/src/components/badge.stories.tsx +27 -0
  110. package/src/components/badge.tsx +63 -0
  111. package/src/components/bar-graph.stories.tsx +28 -0
  112. package/src/components/bar-graph.tsx +204 -0
  113. package/src/components/bento-grid.stories.tsx +23 -0
  114. package/src/components/bento-grid.tsx +67 -0
  115. package/src/components/blockquote.stories.tsx +28 -0
  116. package/src/components/blockquote.tsx +88 -0
  117. package/src/components/box-list.stories.tsx +25 -0
  118. package/src/components/box-list.tsx +108 -0
  119. package/src/components/bullet-list.stories.tsx +22 -0
  120. package/src/components/bullet-list.tsx +101 -0
  121. package/src/components/button-group.stories.tsx +31 -0
  122. package/src/components/button-group.tsx +156 -0
  123. package/src/components/button.stories.tsx +66 -0
  124. package/src/components/button.test.ts +17 -0
  125. package/src/components/button.tsx +139 -0
  126. package/src/components/calendar.stories.tsx +25 -0
  127. package/src/components/calendar.tsx +367 -0
  128. package/src/components/card.stories.tsx +44 -0
  129. package/src/components/card.tsx +392 -0
  130. package/src/components/cards-nav.stories.tsx +35 -0
  131. package/src/components/cards-nav.tsx +115 -0
  132. package/src/components/chain-of-thought.stories.tsx +67 -0
  133. package/src/components/chain-of-thought.test.tsx +50 -0
  134. package/src/components/chain-of-thought.tsx +170 -0
  135. package/src/components/chat-container.stories.tsx +47 -0
  136. package/src/components/chat-container.test.tsx +49 -0
  137. package/src/components/chat-container.tsx +222 -0
  138. package/src/components/chat-message.stories.tsx +52 -0
  139. package/src/components/chat-message.test.tsx +36 -0
  140. package/src/components/chat-message.tsx +163 -0
  141. package/src/components/checkbox-card.stories.tsx +27 -0
  142. package/src/components/checkbox-card.tsx +200 -0
  143. package/src/components/checkbox.stories.tsx +26 -0
  144. package/src/components/checkbox.tsx +61 -0
  145. package/src/components/citations.stories.tsx +64 -0
  146. package/src/components/citations.test.tsx +61 -0
  147. package/src/components/citations.tsx +220 -0
  148. package/src/components/combobox.stories.tsx +184 -0
  149. package/src/components/combobox.test.tsx +82 -0
  150. package/src/components/combobox.tsx +407 -0
  151. package/src/components/countdown-pill.stories.tsx +24 -0
  152. package/src/components/countdown-pill.tsx +129 -0
  153. package/src/components/cta.stories.tsx +29 -0
  154. package/src/components/cta.tsx +52 -0
  155. package/src/components/data-list.stories.tsx +50 -0
  156. package/src/components/data-list.test.tsx +35 -0
  157. package/src/components/data-list.tsx +181 -0
  158. package/src/components/data-table.stories.tsx +29 -0
  159. package/src/components/data-table.tsx +200 -0
  160. package/src/components/dialog.stories.tsx +42 -0
  161. package/src/components/dialog.tsx +200 -0
  162. package/src/components/drawer.stories.tsx +48 -0
  163. package/src/components/drawer.tsx +385 -0
  164. package/src/components/dropdown-menu.stories.tsx +41 -0
  165. package/src/components/dropdown-menu.tsx +252 -0
  166. package/src/components/dropzone.stories.tsx +19 -0
  167. package/src/components/dropzone.tsx +250 -0
  168. package/src/components/email-chips-input.stories.tsx +135 -0
  169. package/src/components/email-chips-input.tsx +210 -0
  170. package/src/components/empty-state.stories.tsx +22 -0
  171. package/src/components/empty-state.tsx +35 -0
  172. package/src/components/entity-header.stories.tsx +43 -0
  173. package/src/components/entity-header.test.tsx +35 -0
  174. package/src/components/entity-header.tsx +92 -0
  175. package/src/components/feature-grid.stories.tsx +25 -0
  176. package/src/components/feature-grid.tsx +87 -0
  177. package/src/components/feedback-bar.stories.tsx +25 -0
  178. package/src/components/feedback-bar.test.tsx +24 -0
  179. package/src/components/feedback-bar.tsx +62 -0
  180. package/src/components/footer.stories.tsx +23 -0
  181. package/src/components/footer.tsx +78 -0
  182. package/src/components/heading.stories.tsx +45 -0
  183. package/src/components/heading.test.tsx +22 -0
  184. package/src/components/heading.tsx +77 -0
  185. package/src/components/hero.stories.tsx +38 -0
  186. package/src/components/hero.tsx +498 -0
  187. package/src/components/input-button.stories.tsx +19 -0
  188. package/src/components/input-button.tsx +112 -0
  189. package/src/components/input-group.stories.tsx +38 -0
  190. package/src/components/input-group.tsx +157 -0
  191. package/src/components/input.stories.tsx +31 -0
  192. package/src/components/input.tsx +284 -0
  193. package/src/components/inset.stories.tsx +29 -0
  194. package/src/components/inset.tsx +70 -0
  195. package/src/components/kpi-card.stories.tsx +29 -0
  196. package/src/components/kpi-card.tsx +284 -0
  197. package/src/components/list-nav.stories.tsx +22 -0
  198. package/src/components/list-nav.tsx +145 -0
  199. package/src/components/logo-cloud.stories.tsx +23 -0
  200. package/src/components/logo-cloud.tsx +42 -0
  201. package/src/components/media-gallery.stories.tsx +25 -0
  202. package/src/components/media-gallery.tsx +347 -0
  203. package/src/components/message-actions.stories.tsx +45 -0
  204. package/src/components/message-actions.test.tsx +32 -0
  205. package/src/components/message-actions.tsx +119 -0
  206. package/src/components/nav-sidebar.stories.tsx +58 -0
  207. package/src/components/nav-sidebar.test.tsx +54 -0
  208. package/src/components/nav-sidebar.tsx +462 -0
  209. package/src/components/number-stepper.stories.tsx +19 -0
  210. package/src/components/number-stepper.tsx +158 -0
  211. package/src/components/page-header.stories.tsx +25 -0
  212. package/src/components/page-header.tsx +64 -0
  213. package/src/components/pagination.stories.tsx +110 -0
  214. package/src/components/pagination.test.tsx +46 -0
  215. package/src/components/pagination.tsx +144 -0
  216. package/src/components/popover.stories.tsx +26 -0
  217. package/src/components/popover.tsx +62 -0
  218. package/src/components/pricing.stories.tsx +42 -0
  219. package/src/components/pricing.tsx +130 -0
  220. package/src/components/progress.stories.tsx +22 -0
  221. package/src/components/progress.tsx +189 -0
  222. package/src/components/prompt-input.stories.tsx +62 -0
  223. package/src/components/prompt-input.test.tsx +106 -0
  224. package/src/components/prompt-input.tsx +343 -0
  225. package/src/components/prompt-suggestions.stories.tsx +53 -0
  226. package/src/components/prompt-suggestions.test.tsx +37 -0
  227. package/src/components/prompt-suggestions.tsx +122 -0
  228. package/src/components/quota-bar.stories.tsx +19 -0
  229. package/src/components/quota-bar.tsx +191 -0
  230. package/src/components/quote.stories.tsx +18 -0
  231. package/src/components/quote.tsx +43 -0
  232. package/src/components/radio-card.stories.tsx +27 -0
  233. package/src/components/radio-card.tsx +225 -0
  234. package/src/components/radio-group.stories.tsx +32 -0
  235. package/src/components/radio-group.tsx +68 -0
  236. package/src/components/reasoning.stories.tsx +50 -0
  237. package/src/components/reasoning.test.tsx +48 -0
  238. package/src/components/reasoning.tsx +143 -0
  239. package/src/components/search.stories.tsx +18 -0
  240. package/src/components/search.tsx +133 -0
  241. package/src/components/section-nav.stories.tsx +59 -0
  242. package/src/components/section-nav.test.tsx +36 -0
  243. package/src/components/section-nav.tsx +146 -0
  244. package/src/components/segmented-control.stories.tsx +22 -0
  245. package/src/components/segmented-control.tsx +301 -0
  246. package/src/components/select-button.stories.tsx +27 -0
  247. package/src/components/select-button.tsx +174 -0
  248. package/src/components/select.stories.tsx +27 -0
  249. package/src/components/select.tsx +198 -0
  250. package/src/components/separator.stories.tsx +22 -0
  251. package/src/components/separator.tsx +23 -0
  252. package/src/components/shimmer-text.stories.tsx +23 -0
  253. package/src/components/shimmer-text.test.tsx +35 -0
  254. package/src/components/shimmer-text.tsx +43 -0
  255. package/src/components/sidebar.stories.tsx +25 -0
  256. package/src/components/sidebar.tsx +234 -0
  257. package/src/components/skeleton.stories.tsx +22 -0
  258. package/src/components/skeleton.tsx +96 -0
  259. package/src/components/slider.stories.tsx +20 -0
  260. package/src/components/slider.tsx +176 -0
  261. package/src/components/slideshow.stories.tsx +30 -0
  262. package/src/components/slideshow.tsx +291 -0
  263. package/src/components/source.stories.tsx +32 -0
  264. package/src/components/source.test.tsx +40 -0
  265. package/src/components/source.tsx +161 -0
  266. package/src/components/spinner.stories.tsx +19 -0
  267. package/src/components/spinner.tsx +76 -0
  268. package/src/components/stat-strip.stories.tsx +36 -0
  269. package/src/components/stat-strip.test.tsx +25 -0
  270. package/src/components/stat-strip.tsx +85 -0
  271. package/src/components/stats.stories.tsx +24 -0
  272. package/src/components/stats.tsx +83 -0
  273. package/src/components/step-indicator.stories.tsx +18 -0
  274. package/src/components/step-indicator.test.tsx +18 -0
  275. package/src/components/step-indicator.tsx +286 -0
  276. package/src/components/switch.stories.tsx +26 -0
  277. package/src/components/switch.tsx +32 -0
  278. package/src/components/table.stories.tsx +38 -0
  279. package/src/components/table.tsx +220 -0
  280. package/src/components/tabs.stories.tsx +25 -0
  281. package/src/components/tabs.tsx +276 -0
  282. package/src/components/testimonials.stories.tsx +22 -0
  283. package/src/components/testimonials.tsx +109 -0
  284. package/src/components/text-link.stories.tsx +47 -0
  285. package/src/components/text-link.test.tsx +18 -0
  286. package/src/components/text-link.tsx +88 -0
  287. package/src/components/text.stories.tsx +49 -0
  288. package/src/components/text.tsx +105 -0
  289. package/src/components/thinking-bar.stories.tsx +19 -0
  290. package/src/components/thinking-bar.test.tsx +27 -0
  291. package/src/components/thinking-bar.tsx +64 -0
  292. package/src/components/thinking-steps.stories.tsx +44 -0
  293. package/src/components/thinking-steps.test.tsx +42 -0
  294. package/src/components/thinking-steps.tsx +92 -0
  295. package/src/components/toaster.stories.tsx +80 -0
  296. package/src/components/toaster.tsx +36 -0
  297. package/src/components/toggle.stories.tsx +21 -0
  298. package/src/components/toggle.tsx +103 -0
  299. package/src/components/tool-call-card.stories.tsx +97 -0
  300. package/src/components/tool-call-card.test.tsx +88 -0
  301. package/src/components/tool-call-card.tsx +231 -0
  302. package/src/components/tooltip.stories.tsx +25 -0
  303. package/src/components/tooltip.tsx +43 -0
  304. package/src/components/top-nav.stories.tsx +20 -0
  305. package/src/components/top-nav.tsx +76 -0
  306. package/src/docs/Colors.stories.tsx +192 -0
  307. package/src/docs/GettingStarted.mdx +72 -0
  308. package/src/docs/Mcp.mdx +52 -0
  309. package/src/docs/Typography.stories.tsx +114 -0
  310. package/src/hooks/use-in-view.ts +32 -0
  311. package/src/hooks/use-reduced-motion.ts +26 -0
  312. package/src/index.ts +343 -0
  313. package/src/lib/cn.test.ts +67 -0
  314. package/src/lib/cn.ts +81 -0
  315. package/src/lib/motion.ts +109 -0
  316. package/src/lib/prefix-utilities.test.ts +56 -0
  317. package/src/lib/prefix-utilities.ts +50 -0
  318. package/src/styles/base.css +489 -0
  319. package/src/styles/motion.css +69 -0
  320. package/src/styles/overrides.css +44 -0
  321. package/src/styles/typography.css +173 -0
  322. package/src/theme/theme.css +391 -0
  323. package/src/theme/tokens.ts +29 -0
  324. package/src/theme/types.ts +26 -0
  325. package/README.md +0 -3
@@ -0,0 +1,204 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../lib/cn"
5
+
6
+ // ─── Types ────────────────────────────────────────────────────────────────────
7
+
8
+ export type BarGraphSize = "sm" | "md" | "lg"
9
+ export type BarGraphDirection = "horizontal" | "vertical"
10
+
11
+ export interface BarGraphItem {
12
+ /** Display label, e.g. `"Compliant"`, `"Q3"`. */
13
+ label: string
14
+ /** Absolute count shown beside the bar. The bar fill is `value / max`. */
15
+ value: number
16
+ /** Tailwind background utility, e.g. `"ax:bg-chart-1"`, `"ax:bg-success"`.
17
+ * The track renders the same colour at 15 % opacity, mirroring the
18
+ * Activity Rings idiom so bar charts and rings read as one system. */
19
+ color: string
20
+ }
21
+
22
+ export interface BarGraphProps extends React.HTMLAttributes<HTMLDivElement> {
23
+ /** One item per bar, rendered in order. */
24
+ bars: BarGraphItem[]
25
+ /** Ceiling value - each bar fills to `value / max`.
26
+ * Omit to let the tallest bar reach 100 % (relative comparison).
27
+ * Pass explicitly when bars are fractions of a known whole. */
28
+ max?: number
29
+ /** Growth direction. `horizontal` grows left → right (default);
30
+ * `vertical` grows bottom → top. */
31
+ direction?: BarGraphDirection
32
+ /** Visual density: drives bar thickness, chart height, and type scale. */
33
+ size?: BarGraphSize
34
+ /** Render the computed percentage beside / above each bar. Defaults to `true`. */
35
+ showPct?: boolean
36
+ /** Render the raw value count beside / above each bar. Defaults to `true`. */
37
+ showCount?: boolean
38
+ /** Horizontal-mode label alignment within its fixed-width column. `right`
39
+ * (default) sets a shared axis flush against the bars; `left` aligns with
40
+ * a left-aligned heading above the chart instead. No effect in `vertical`
41
+ * mode (labels there are centred under each bar). */
42
+ labelAlign?: "left" | "right"
43
+ }
44
+
45
+ // ─── Size tokens ─────────────────────────────────────────────────────────────
46
+
47
+ // Horizontal bar track height - mirrors the Progress bar height scale
48
+ const H_TRACK_H: Record<BarGraphSize, string> = {
49
+ sm: "ax:h-1.5",
50
+ md: "ax:h-2.5",
51
+ lg: "ax:h-4",
52
+ }
53
+
54
+ // Vertical chart area height in px (bar drawing area, excluding label/stat space)
55
+ const V_CHART_H: Record<BarGraphSize, number> = {
56
+ sm: 80,
57
+ md: 120,
58
+ lg: 160,
59
+ }
60
+
61
+ // Label column width (horizontal) - right-aligned so all bars share one axis
62
+ const LABEL_W: Record<BarGraphSize, string> = {
63
+ sm: "ax:w-20",
64
+ md: "ax:w-28",
65
+ lg: "ax:w-32",
66
+ }
67
+
68
+ const LABEL_CLS: Record<BarGraphSize, string> = {
69
+ sm: "ax:t-label-2xs",
70
+ md: "ax:t-label-xs",
71
+ lg: "ax:t-label-xs",
72
+ }
73
+
74
+ const STAT_CLS: Record<BarGraphSize, string> = {
75
+ sm: "ax:t-micro",
76
+ md: "ax:t-caption",
77
+ lg: "ax:t-label-sm",
78
+ }
79
+
80
+ // ─── Component ────────────────────────────────────────────────────────────────
81
+
82
+ export const BarGraph = React.forwardRef<HTMLDivElement, BarGraphProps>(
83
+ (
84
+ {
85
+ bars,
86
+ max,
87
+ direction = "horizontal",
88
+ size = "md",
89
+ showPct = true,
90
+ showCount = true,
91
+ labelAlign = "right",
92
+ className,
93
+ ...props
94
+ },
95
+ ref,
96
+ ) => {
97
+ const resolvedMax = max ?? Math.max(...bars.map((b) => b.value), 1)
98
+
99
+ // ── Vertical ─────────────────────────────────────────────────────────────
100
+ if (direction === "vertical") {
101
+ return (
102
+ <div
103
+ ref={ref}
104
+ className={cn("ax:flex ax:items-end ax:gap-sm", className)}
105
+ {...props}
106
+ >
107
+ {bars.map((bar, i) => {
108
+ const pct = Math.min(100, Math.max(0, (bar.value / resolvedMax) * 100))
109
+ return (
110
+ <div key={i} className="ax:flex ax:flex-col ax:items-center ax:gap-xs ax:flex-1 ax:min-w-0">
111
+ {/* Stats above bar - percentage + count */}
112
+ {(showPct || showCount) && (
113
+ <div className={cn(STAT_CLS[size], "ax:flex ax:flex-col ax:items-center ax:gap-2xs ax:tabular-nums")}>
114
+ {showPct && <span className="ax:text-foreground ax:font-medium">{Math.round(pct)}%</span>}
115
+ {showCount && <span className="ax:text-muted-foreground">{bar.value}</span>}
116
+ </div>
117
+ )}
118
+
119
+ {/* Bar column: track behind fill, fill grows from bottom */}
120
+ <div
121
+ className="ax:relative ax:w-full ax:overflow-hidden ax:rounded-md"
122
+ style={{ height: V_CHART_H[size] }}
123
+ >
124
+ {/* Track - same hue at 15 % opacity, matches the ring idiom */}
125
+ <div className={cn("ax:absolute ax:inset-0", bar.color, "ax:opacity-[0.15]")} />
126
+ {/* Fill - animated height from bottom */}
127
+ <div
128
+ className={cn(
129
+ "ax:absolute ax:bottom-0 ax:left-0 ax:right-0 ax:rounded-md",
130
+ bar.color,
131
+ "ax:[transition:height_var(--motion-slow)_var(--motion-ease-out)]",
132
+ )}
133
+ style={{ height: `${pct}%` }}
134
+ />
135
+ </div>
136
+
137
+ {/* Label below bar */}
138
+ <span
139
+ className={cn(LABEL_CLS[size], "ax:text-muted-foreground ax:text-center ax:w-full ax:truncate")}
140
+ >
141
+ {bar.label}
142
+ </span>
143
+ </div>
144
+ )
145
+ })}
146
+ </div>
147
+ )
148
+ }
149
+
150
+ // ── Horizontal (default) ──────────────────────────────────────────────────
151
+ return (
152
+ <div
153
+ ref={ref}
154
+ className={cn("ax:flex ax:flex-col ax:gap-sm", className)}
155
+ {...props}
156
+ >
157
+ {bars.map((bar, i) => {
158
+ const pct = Math.min(100, Math.max(0, (bar.value / resolvedMax) * 100))
159
+ return (
160
+ <div key={i} className="ax:flex ax:items-center ax:gap-sm">
161
+ {/* Row label - fixed-width column, right-aligned for clean axis */}
162
+ <span
163
+ className={cn(
164
+ LABEL_CLS[size],
165
+ LABEL_W[size],
166
+ "ax:shrink-0 ax:text-muted-foreground",
167
+ labelAlign === "left" ? "ax:text-left" : "ax:text-right",
168
+ )}
169
+ >
170
+ {bar.label}
171
+ </span>
172
+
173
+ {/* Track + fill */}
174
+ <div className={cn("ax:relative ax:flex-1 ax:overflow-hidden ax:rounded-full", H_TRACK_H[size])}>
175
+ {/* Track - same hue at 15 % opacity */}
176
+ <div className={cn("ax:absolute ax:inset-0", bar.color, "ax:opacity-[0.15]")} />
177
+ {/* Fill - animated width from left */}
178
+ <div
179
+ className={cn(
180
+ "ax:absolute ax:inset-y-0 ax:left-0 ax:rounded-full",
181
+ bar.color,
182
+ "ax:[transition:width_var(--motion-slow)_var(--motion-ease-out)]",
183
+ )}
184
+ style={{ width: `${pct}%` }}
185
+ />
186
+ </div>
187
+
188
+ {/* Stats - percentage + count, tabular so digits don't dance */}
189
+ <div className={cn(STAT_CLS[size], "ax:flex ax:items-baseline ax:gap-xs ax:shrink-0 ax:tabular-nums")}>
190
+ {showPct && (
191
+ <span className="ax:text-muted-foreground ax:w-9 ax:text-right">{Math.round(pct)}%</span>
192
+ )}
193
+ {showCount && (
194
+ <span className="ax:text-foreground ax:font-medium">{bar.value}</span>
195
+ )}
196
+ </div>
197
+ </div>
198
+ )
199
+ })}
200
+ </div>
201
+ )
202
+ },
203
+ )
204
+ BarGraph.displayName = "BarGraph"
@@ -0,0 +1,23 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { BentoGrid, BentoItem } from "./bento-grid"
4
+
5
+ const meta: Meta<typeof BentoGrid> = {
6
+ title: "Components/BentoGrid",
7
+ component: BentoGrid,
8
+ tags: ["autodocs"],
9
+ }
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof BentoGrid>
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <BentoGrid cols={3} className="ax:max-w-3xl">
17
+ <BentoItem colSpan={2} className="ax:p-lg">Wide feature</BentoItem>
18
+ <BentoItem className="ax:p-lg">Side card</BentoItem>
19
+ <BentoItem className="ax:p-lg">Analytics</BentoItem>
20
+ <BentoItem colSpan={2} className="ax:p-lg">Reports</BentoItem>
21
+ </BentoGrid>
22
+ ),
23
+ }
@@ -0,0 +1,67 @@
1
+ import * as React from "react"
2
+ import { cn } from "../lib/cn"
3
+
4
+ // ─── Column / row span lookup tables ─────────────────────────────────────────
5
+ // Full class strings required so Tailwind's JIT includes them in the bundle.
6
+
7
+ const GRID_COLS = {
8
+ 2: "ax:grid-cols-2",
9
+ 3: "ax:grid-cols-3",
10
+ 4: "ax:grid-cols-4",
11
+ } as const
12
+
13
+ const COL_SPAN = {
14
+ 1: "ax:col-span-1",
15
+ 2: "ax:col-span-2",
16
+ 3: "ax:col-span-3",
17
+ 4: "ax:col-span-4",
18
+ } as const
19
+
20
+ const ROW_SPAN = {
21
+ 1: "ax:row-span-1",
22
+ 2: "ax:row-span-2",
23
+ 3: "ax:row-span-3",
24
+ } as const
25
+
26
+ // ─── BentoGrid ────────────────────────────────────────────────────────────────
27
+
28
+ export interface BentoGridProps extends React.HTMLAttributes<HTMLDivElement> {
29
+ /** Number of columns at all breakpoints. Default: 3. */
30
+ cols?: 2 | 3 | 4
31
+ }
32
+
33
+ export const BentoGrid = React.forwardRef<HTMLDivElement, BentoGridProps>(
34
+ ({ className, cols = 3, ...props }, ref) => (
35
+ <div
36
+ ref={ref}
37
+ className={cn("ax:grid ax:gap-3", GRID_COLS[cols], className)}
38
+ {...props}
39
+ />
40
+ )
41
+ )
42
+ BentoGrid.displayName = "BentoGrid"
43
+
44
+ // ─── BentoItem ────────────────────────────────────────────────────────────────
45
+
46
+ export interface BentoItemProps extends React.HTMLAttributes<HTMLDivElement> {
47
+ /** How many grid columns this cell spans. Default: 1. */
48
+ colSpan?: 1 | 2 | 3 | 4
49
+ /** How many grid rows this cell spans. Default: 1. */
50
+ rowSpan?: 1 | 2 | 3
51
+ }
52
+
53
+ export const BentoItem = React.forwardRef<HTMLDivElement, BentoItemProps>(
54
+ ({ className, colSpan = 1, rowSpan = 1, ...props }, ref) => (
55
+ <div
56
+ ref={ref}
57
+ className={cn(
58
+ "ax:rounded-2xl ax:bg-card ax:border ax:border-border ax:overflow-hidden",
59
+ COL_SPAN[colSpan],
60
+ ROW_SPAN[rowSpan],
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+ )
66
+ )
67
+ BentoItem.displayName = "BentoItem"
@@ -0,0 +1,28 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Blockquote, BlockquoteCaption, BlockquoteFigure } from "./blockquote"
4
+
5
+ const meta: Meta<typeof Blockquote> = {
6
+ title: "Components/Blockquote",
7
+ component: Blockquote,
8
+ tags: ["autodocs"],
9
+ args: {
10
+ children: "Design is not just what it looks like. Design is how it works.",
11
+ size: "md",
12
+ color: "primary",
13
+ },
14
+ }
15
+
16
+ export default meta
17
+ type Story = StoryObj<typeof Blockquote>
18
+
19
+ export const Default: Story = {}
20
+
21
+ export const WithCaption: Story = {
22
+ render: (args) => (
23
+ <BlockquoteFigure>
24
+ <Blockquote {...args} />
25
+ <BlockquoteCaption>Steve Jobs</BlockquoteCaption>
26
+ </BlockquoteFigure>
27
+ ),
28
+ }
@@ -0,0 +1,88 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+ import { cn } from "../lib/cn"
6
+
7
+ // ─── Types ────────────────────────────────────────────────────────────────────
8
+
9
+ export type BlockquoteSize = "sm" | "md" | "lg" | "xl"
10
+ export type BlockquoteColor = "primary" | "neutral"
11
+
12
+ // ─── Variants ─────────────────────────────────────────────────────────────────
13
+
14
+ export const blockquoteVariants = cva(
15
+ "ax:border-l-2 ax:pl-4 ax:text-foreground",
16
+ {
17
+ variants: {
18
+ size: {
19
+ sm: "ax:t-body-sm",
20
+ md: "ax:t-body-md",
21
+ lg: "ax:t-body-lg",
22
+ // xl is one step bigger than t-body-lg for editorial / hero quotes.
23
+ // No t-body-xl exists, but font-normal makes the body intent explicit.
24
+ xl: "ax:text-xl ax:font-normal ax:leading-normal",
25
+ },
26
+ color: {
27
+ primary: "ax:border-primary",
28
+ neutral: "ax:border-border-strong",
29
+ },
30
+ },
31
+ defaultVariants: {
32
+ size: "md",
33
+ color: "primary",
34
+ },
35
+ }
36
+ )
37
+
38
+ // ─── Figure wrapper ───────────────────────────────────────────────────────────
39
+ //
40
+ // Renders `<figure>` so a contained <figcaption> (via BlockquoteCaption) is
41
+ // valid HTML. Use whenever Blockquote is paired with a caption.
42
+
43
+ export interface BlockquoteFigureProps extends React.HTMLAttributes<HTMLElement> {}
44
+
45
+ export const BlockquoteFigure = React.forwardRef<HTMLElement, BlockquoteFigureProps>(
46
+ ({ className, ...props }, ref) => (
47
+ // Pure structural wrapper: the caption manages its own top margin so the
48
+ // figure has no styling opinions of its own.
49
+ <figure ref={ref} className={className} {...props} />
50
+ )
51
+ )
52
+ BlockquoteFigure.displayName = "BlockquoteFigure"
53
+
54
+ // ─── Blockquote ───────────────────────────────────────────────────────────────
55
+
56
+ export interface BlockquoteProps
57
+ extends Omit<React.BlockquoteHTMLAttributes<HTMLQuoteElement>, "color">,
58
+ VariantProps<typeof blockquoteVariants> {}
59
+
60
+ export const Blockquote = React.forwardRef<HTMLQuoteElement, BlockquoteProps>(
61
+ ({ className, size, color, ...props }, ref) => (
62
+ <blockquote
63
+ ref={ref}
64
+ className={cn(blockquoteVariants({ size, color }), className)}
65
+ {...props}
66
+ />
67
+ )
68
+ )
69
+ Blockquote.displayName = "Blockquote"
70
+
71
+ // ─── Caption ──────────────────────────────────────────────────────────────────
72
+ //
73
+ // Renders `<figcaption>` for attribution. Must be a child of `<BlockquoteFigure>`
74
+ // (or any `<figure>`) to be valid HTML: `<figcaption>` outside `<figure>` is
75
+ // invalid markup that screen readers handle inconsistently.
76
+
77
+ export interface BlockquoteCaptionProps extends React.HTMLAttributes<HTMLElement> {}
78
+
79
+ export const BlockquoteCaption = React.forwardRef<HTMLElement, BlockquoteCaptionProps>(
80
+ ({ className, ...props }, ref) => (
81
+ <figcaption
82
+ ref={ref}
83
+ className={cn("ax:mt-2 ax:t-body-sm ax:text-muted-foreground", className)}
84
+ {...props}
85
+ />
86
+ )
87
+ )
88
+ BlockquoteCaption.displayName = "BlockquoteCaption"
@@ -0,0 +1,25 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { BoxList } from "./box-list"
4
+
5
+ const items = [
6
+ { title: "Unlimited projects", description: "Create as many workspaces as you need." },
7
+ { title: "Team collaboration", description: "Invite teammates and assign roles." },
8
+ { title: "Priority support", description: "Get help within one business day." },
9
+ ]
10
+
11
+ const meta: Meta<typeof BoxList> = {
12
+ title: "Components/BoxList",
13
+ component: BoxList,
14
+ tags: ["autodocs"],
15
+ args: { items, columns: 3 },
16
+ }
17
+
18
+ export default meta
19
+ type Story = StoryObj<typeof BoxList>
20
+
21
+ export const Default: Story = {}
22
+
23
+ export const WithChecks: Story = {
24
+ args: { check: true },
25
+ }
@@ -0,0 +1,108 @@
1
+ import * as React from "react"
2
+ import { Check } from "lucide-react"
3
+ import { cn } from "../lib/cn"
4
+
5
+ export interface BoxListItem {
6
+ /** Heading line, rendered with the title type token. Omit for a plain
7
+ * body-text item (the `description` reads as the box's only line). */
8
+ title?: React.ReactNode
9
+ /** Supporting line. Sits under a `title`, or stands alone as body text. */
10
+ description?: React.ReactNode
11
+ }
12
+
13
+ /** Responsive column count at desktop. */
14
+ const COLUMNS: Record<1 | 2 | 3 | 4, string> = {
15
+ 1: "",
16
+ 2: "ax:sm:grid-cols-2",
17
+ 3: "ax:sm:grid-cols-2 ax:lg:grid-cols-3",
18
+ 4: "ax:sm:grid-cols-2 ax:lg:grid-cols-4",
19
+ }
20
+
21
+ export type BoxListSize = "sm" | "md" | "lg"
22
+
23
+ /** Box padding per size. */
24
+ const SIZE: Record<BoxListSize, string> = {
25
+ sm: "ax:p-md",
26
+ md: "ax:p-lg",
27
+ lg: "ax:p-xl",
28
+ }
29
+
30
+ /** Type scale per text size - steps the title and body line up together. */
31
+ const TEXT: Record<BoxListSize, { title: string; desc: string }> = {
32
+ sm: { title: "ax:t-h6", desc: "ax:t-caption" },
33
+ md: { title: "ax:t-h5", desc: "ax:t-body-sm" },
34
+ lg: { title: "ax:t-h4", desc: "ax:t-body-md" },
35
+ }
36
+
37
+ export interface BoxListProps extends React.HTMLAttributes<HTMLDivElement> {
38
+ items: BoxListItem[]
39
+ /** Columns at desktop (1–4). Default 3. Ignored when `vertical`. */
40
+ columns?: 1 | 2 | 3 | 4
41
+ /** Box padding. Default "lg". */
42
+ size?: BoxListSize
43
+ /** Type scale for the title + description. Default "md". */
44
+ textSize?: BoxListSize
45
+ /** Stack items in a single vertical column instead of a grid. Overrides
46
+ * `columns`. */
47
+ vertical?: boolean
48
+ /** Show a leading bullet dot before each item's title. */
49
+ bullet?: boolean
50
+ /** Show a leading Check icon before each item's title - for "included" /
51
+ * affirmative sets. Takes precedence over `bullet` when both are set. */
52
+ check?: boolean
53
+ /** Show a two-digit ordinal (01, 02, …) as a t-overline eyebrow above each
54
+ * item. Takes precedence over `check` and `bullet` when several are set. */
55
+ numerical?: boolean
56
+ /** Sit each item on the muted surface (`bg-muted`) instead of the default
57
+ * card surface - a softer, lower-contrast fill for secondary lists. */
58
+ muted?: boolean
59
+ }
60
+
61
+ /**
62
+ * BoxList - a responsive grid of bordered "box" cards, each a title with an
63
+ * optional supporting line. The tidy way to present a set of features,
64
+ * services, terms or options without hand-rolling the card + grid each time.
65
+ *
66
+ * Optionally mark each item (precedence: numerical → check → bullet):
67
+ * `numerical` prefixes a 01, 02, … eyebrow (ordered sequences), `check` a Check
68
+ * icon (affirmative sets), or `bullet` a leading dot (an unordered set).
69
+ */
70
+ export function BoxList({ items, columns = 3, size = "lg", textSize = "md", vertical = false, bullet = false, check = false, numerical = false, muted = false, className, ...props }: BoxListProps) {
71
+ return (
72
+ <div
73
+ className={cn(
74
+ "ax:gap-2xs",
75
+ vertical ? "ax:flex ax:flex-col" : cn("ax:grid", COLUMNS[columns]),
76
+ className,
77
+ )}
78
+ {...props}
79
+ >
80
+ {items.map((item, i) => (
81
+ <div key={i} className={cn("ax:rounded-xs", muted ? "ax:bg-muted" : "ax:bg-card", SIZE[size])}>
82
+ {numerical && (
83
+ <div className="ax:mb-sm ax:t-overline ax:text-primary">{String(i + 1).padStart(2, "0")}</div>
84
+ )}
85
+ <div className="ax:flex ax:items-center ax:gap-sm">
86
+ {!numerical && check && (
87
+ <Check aria-hidden="true" className="ax:size-4 ax:shrink-0 ax:text-primary" />
88
+ )}
89
+ {!numerical && !check && bullet && (
90
+ <span aria-hidden="true" className="ax:size-1.5 ax:shrink-0 ax:rounded-full ax:bg-foreground-strong" />
91
+ )}
92
+ <div
93
+ className={cn(
94
+ item.title != null ? TEXT[textSize].title : TEXT[textSize].desc,
95
+ "ax:text-foreground",
96
+ )}
97
+ >
98
+ {item.title ?? item.description}
99
+ </div>
100
+ </div>
101
+ {item.title != null && item.description != null && (
102
+ <div className={cn("ax:mt-2xs ax:text-muted-foreground", TEXT[textSize].desc)}>{item.description}</div>
103
+ )}
104
+ </div>
105
+ ))}
106
+ </div>
107
+ )
108
+ }
@@ -0,0 +1,22 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { BulletList, BulletListItem } from "./bullet-list"
4
+
5
+ const meta: Meta<typeof BulletList> = {
6
+ title: "Components/BulletList",
7
+ component: BulletList,
8
+ tags: ["autodocs"],
9
+ }
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof BulletList>
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <BulletList>
17
+ <BulletListItem>Responsive by default</BulletListItem>
18
+ <BulletListItem>Accessible components</BulletListItem>
19
+ <BulletListItem>Brand-aware tokens</BulletListItem>
20
+ </BulletList>
21
+ ),
22
+ }
@@ -0,0 +1,101 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../lib/cn"
5
+
6
+ export type BulletListSize = "sm" | "md" | "lg"
7
+
8
+ // ─── Size tokens ──────────────────────────────────────────────────────────────
9
+ //
10
+ // Text uses the matching `t-body-*` token so the bullet rhythm tracks the
11
+ // surrounding prose. Bullet diameter scales with text size; row gap stays at
12
+ // `gap-sm` (12px), comfortable across all three sizes without feeling loose.
13
+
14
+ const TEXT_CLASS: Record<BulletListSize, string> = {
15
+ sm: "ax:t-body-sm",
16
+ md: "ax:t-body-md",
17
+ lg: "ax:t-body-lg",
18
+ }
19
+
20
+ const BULLET_CLASS: Record<BulletListSize, string> = {
21
+ sm: "ax:size-1", // 4px
22
+ md: "ax:size-1.5", // 6px
23
+ lg: "ax:size-2", // 8px
24
+ }
25
+
26
+ const ROW_GAP_CLASS: Record<BulletListSize, string> = {
27
+ sm: "ax:gap-xs",
28
+ md: "ax:gap-sm",
29
+ lg: "ax:gap-sm",
30
+ }
31
+
32
+ const LIST_GAP_CLASS: Record<BulletListSize, string> = {
33
+ sm: "ax:space-y-2xs",
34
+ md: "ax:space-y-xs",
35
+ lg: "ax:space-y-sm",
36
+ }
37
+
38
+ // ─── Context ──────────────────────────────────────────────────────────────────
39
+ //
40
+ // Size is propagated via context so an item can pick up the right text/bullet
41
+ // classes without the consumer threading it through every <BulletListItem>.
42
+
43
+ interface BulletListContextValue {
44
+ size: BulletListSize
45
+ }
46
+
47
+ const BulletListContext = React.createContext<BulletListContextValue>({ size: "md" })
48
+
49
+ // ─── BulletList ───────────────────────────────────────────────────────────────
50
+
51
+ export interface BulletListProps extends React.HTMLAttributes<HTMLUListElement> {
52
+ size?: BulletListSize
53
+ }
54
+
55
+ /** Bulleted list with a small foreground-strong dot, body-text rhythm, and
56
+ * consistent spacing. Compose with `BulletListItem`. Use for short lists
57
+ * of related items (goals, features, qualities) where the surrounding
58
+ * context already provides the heading. */
59
+ export const BulletList = React.forwardRef<HTMLUListElement, BulletListProps>(
60
+ ({ size = "md", className, children, ...props }, ref) => (
61
+ <BulletListContext.Provider value={{ size }}>
62
+ <ul
63
+ ref={ref}
64
+ className={cn("ax:list-none", LIST_GAP_CLASS[size], className)}
65
+ {...props}
66
+ >
67
+ {children}
68
+ </ul>
69
+ </BulletListContext.Provider>
70
+ ),
71
+ )
72
+ BulletList.displayName = "BulletList"
73
+
74
+ // ─── BulletListItem ───────────────────────────────────────────────────────────
75
+
76
+ export interface BulletListItemProps extends React.LiHTMLAttributes<HTMLLIElement> {}
77
+
78
+ export const BulletListItem = React.forwardRef<HTMLLIElement, BulletListItemProps>(
79
+ ({ className, children, ...props }, ref) => {
80
+ const { size } = React.useContext(BulletListContext)
81
+ return (
82
+ <li
83
+ ref={ref}
84
+ className={cn(
85
+ "ax:flex ax:items-center ax:text-foreground",
86
+ ROW_GAP_CLASS[size],
87
+ TEXT_CLASS[size],
88
+ className,
89
+ )}
90
+ {...props}
91
+ >
92
+ <span
93
+ aria-hidden="true"
94
+ className={cn("ax:rounded-full ax:shrink-0 ax:bg-foreground-strong", BULLET_CLASS[size])}
95
+ />
96
+ <span>{children}</span>
97
+ </li>
98
+ )
99
+ },
100
+ )
101
+ BulletListItem.displayName = "BulletListItem"