@adres/mgda-ui 0.0.0-stage → 0.0.1

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,191 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Progress } from "./progress"
5
+ import { cn } from "../lib/cn"
6
+
7
+ export type QuotaBarShape = "bar" | "ring"
8
+
9
+ export interface QuotaBarProps extends React.HTMLAttributes<HTMLDivElement> {
10
+ /** Current usage. */
11
+ used: number
12
+ /** Maximum allowed. When `used >= total`, the bar renders in the error
13
+ * variant and the "full" state is shown. */
14
+ total: number
15
+ /** Compact mode - uses a smaller bar / ring. */
16
+ compact?: boolean
17
+ /** Visual shape. `bar` (default) is a horizontal Progress with the caption
18
+ * underneath; `ring` puts a circular Progress on the inline-start side
19
+ * with the caption stacked beside it and the pct% inside the ring. */
20
+ shape?: QuotaBarShape
21
+ /** Ring-only: pixel diameter override (passes through to Progress). */
22
+ ringSize?: number
23
+ /** Ring-only: stroke width in pixels. */
24
+ strokeWidth?: number
25
+ /** Unit label appended after the count - e.g. "seats", "licences", "GB".
26
+ * Used in the "X / Y {unit} used" sentence. */
27
+ unit?: string
28
+ /** Label shown next to the count when the quota is *not* full.
29
+ * Defaults to `"{remaining} remaining"`. */
30
+ remainingLabel?: (remaining: number) => string
31
+ /** Replaces the default "No {unit} remaining" string in the full state. */
32
+ fullLabel?: string
33
+ /** When the quota is full, the full-state CTA renders as a link to this
34
+ * href. Use this OR `ctaOnClick`, not both. */
35
+ ctaHref?: string
36
+ /** When the quota is full, the full-state CTA renders as a button that
37
+ * fires this callback (use for non-routing actions like opening a Drawer). */
38
+ ctaOnClick?: () => void
39
+ /** Visible text for the full-state CTA. Defaults to "Request More". */
40
+ ctaLabel?: string
41
+ }
42
+
43
+ /** Visual quota indicator - a Progress bar (or ring) plus a small
44
+ * "{used} / {total}" caption and an optional CTA when full.
45
+ * Domain-agnostic: pass `unit` to control the wording for your use case
46
+ * (seats, licences, storage GB, emails sent this month, …).
47
+ *
48
+ * Threshold ramp:
49
+ * • < 90 % - `default` variant
50
+ * • 90–99 % - `warning` variant, "remaining" text warning-tinted
51
+ * • ≥ 100 % - `error` variant, "No {unit} remaining" + optional CTA */
52
+ export const QuotaBar = React.forwardRef<HTMLDivElement, QuotaBarProps>(
53
+ (
54
+ {
55
+ used,
56
+ total,
57
+ compact,
58
+ shape = "bar",
59
+ ringSize,
60
+ strokeWidth,
61
+ unit = "items",
62
+ remainingLabel,
63
+ fullLabel,
64
+ ctaHref,
65
+ ctaOnClick,
66
+ ctaLabel = "Request More",
67
+ className,
68
+ ...props
69
+ },
70
+ ref,
71
+ ) => {
72
+ const isFull = used >= total
73
+ const remaining = Math.max(0, total - used)
74
+ const pct = total > 0 ? (used / total) * 100 : 0
75
+ const isWarn = !isFull && pct >= 90
76
+ const remainingText = remainingLabel
77
+ ? remainingLabel(remaining)
78
+ : `${remaining} remaining`
79
+
80
+ const variant = isFull ? "error" : isWarn ? "warning" : "default"
81
+ const size: "sm" | "md" = compact ? "sm" : "md"
82
+
83
+ const ctaNode = !isFull ? null : ctaOnClick ? (
84
+ <button
85
+ type="button"
86
+ onClick={ctaOnClick}
87
+ className="ax:text-primary ax:hover:underline ax:focus-visible:outline-none ax:focus-visible:underline ax:cursor-pointer"
88
+ >
89
+ {ctaLabel}
90
+ </button>
91
+ ) : ctaHref ? (
92
+ <a
93
+ href={ctaHref}
94
+ className="ax:text-primary ax:hover:underline ax:focus-visible:outline-none ax:focus-visible:underline"
95
+ >
96
+ {ctaLabel}
97
+ </a>
98
+ ) : null
99
+
100
+ // ── Ring shape ────────────────────────────────────────────────────────────
101
+ if (shape === "ring") {
102
+ return (
103
+ <div
104
+ ref={ref}
105
+ className={cn("ax:flex ax:items-center ax:gap-md", className)}
106
+ {...props}
107
+ >
108
+ <div className="ax:relative ax:inline-flex ax:shrink-0">
109
+ <Progress
110
+ shape="ring"
111
+ value={used}
112
+ max={total}
113
+ size={size}
114
+ variant={variant}
115
+ ringSize={ringSize}
116
+ strokeWidth={strokeWidth}
117
+ />
118
+ {/* Center pct% - the ring conveys the proportion visually, this
119
+ gives a precise reading at a glance. Hidden when the ring is
120
+ tiny (compact, no override) since the text wouldn't fit. */}
121
+ {!(compact && !ringSize) && (
122
+ <span
123
+ aria-hidden="true"
124
+ className={cn(
125
+ "ax:absolute ax:inset-0 ax:flex ax:items-center ax:justify-center",
126
+ "ax:t-caption ax:font-medium",
127
+ isFull ? "ax:text-destructive-strong" : isWarn ? "ax:text-warning-strong" : "ax:text-foreground",
128
+ )}
129
+ >
130
+ {Math.round(pct)}%
131
+ </span>
132
+ )}
133
+ </div>
134
+
135
+ <div className="ax:flex-1 ax:min-w-0 ax:flex ax:flex-col ax:gap-2xs ax:t-caption ax:text-muted-foreground">
136
+ {isFull ? (
137
+ <>
138
+ <span className="ax:text-destructive-strong ax:font-medium">
139
+ {fullLabel ?? `No ${unit} remaining`}
140
+ </span>
141
+ {ctaNode}
142
+ </>
143
+ ) : (
144
+ <>
145
+ <span>
146
+ {used} / {total} {unit} used
147
+ </span>
148
+ <span className={cn(isWarn && "ax:text-warning-strong ax:font-medium")}>
149
+ {remainingText}
150
+ </span>
151
+ </>
152
+ )}
153
+ </div>
154
+ </div>
155
+ )
156
+ }
157
+
158
+ // ── Bar shape (default) ───────────────────────────────────────────────────
159
+ return (
160
+ <div ref={ref} className={cn("ax:flex ax:flex-col ax:gap-xs", className)} {...props}>
161
+ <Progress
162
+ value={used}
163
+ max={total}
164
+ size={size}
165
+ variant={variant}
166
+ />
167
+
168
+ <div className="ax:flex ax:items-center ax:justify-between ax:gap-xs ax:t-caption ax:text-muted-foreground">
169
+ {isFull ? (
170
+ <>
171
+ <span className="ax:text-destructive-strong ax:font-medium">
172
+ {fullLabel ?? `No ${unit} remaining`}
173
+ </span>
174
+ {ctaNode}
175
+ </>
176
+ ) : (
177
+ <>
178
+ <span>
179
+ {used} / {total} {unit} used
180
+ </span>
181
+ <span className={cn(isWarn && "ax:text-warning-strong ax:font-medium")}>
182
+ {remainingText}
183
+ </span>
184
+ </>
185
+ )}
186
+ </div>
187
+ </div>
188
+ )
189
+ },
190
+ )
191
+ QuotaBar.displayName = "QuotaBar"
@@ -0,0 +1,18 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Quote } from "./quote"
4
+
5
+ const meta: Meta<typeof Quote> = {
6
+ title: "Components/Quote",
7
+ component: Quote,
8
+ tags: ["autodocs"],
9
+ args: {
10
+ children: "Great products are built on great design systems.",
11
+ size: "md",
12
+ },
13
+ }
14
+
15
+ export default meta
16
+ type Story = StoryObj<typeof Quote>
17
+
18
+ export const Default: Story = {}
@@ -0,0 +1,43 @@
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
+ export const quoteVariants = cva(
8
+ "ax:italic",
9
+ {
10
+ variants: {
11
+ size: {
12
+ sm: "ax:t-body-sm",
13
+ md: "ax:t-body-md",
14
+ lg: "ax:t-body-lg",
15
+ xl: "ax:text-xl",
16
+ },
17
+ color: {
18
+ default: "ax:text-foreground",
19
+ muted: "ax:text-muted-foreground",
20
+ accent: "ax:text-accent-foreground",
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ size: "md",
25
+ color: "default",
26
+ },
27
+ }
28
+ )
29
+
30
+ export interface QuoteProps
31
+ extends Omit<React.HTMLAttributes<HTMLQuoteElement>, "color">,
32
+ VariantProps<typeof quoteVariants> {}
33
+
34
+ export const Quote = React.forwardRef<HTMLQuoteElement, QuoteProps>(
35
+ ({ className, size, color, ...props }, ref) => (
36
+ <q
37
+ ref={ref}
38
+ className={cn(quoteVariants({ size, color }), className)}
39
+ {...props}
40
+ />
41
+ )
42
+ )
43
+ Quote.displayName = "Quote"
@@ -0,0 +1,27 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { RadioCard, RadioCardDescription, RadioCardGroup, RadioCardTitle } from "./radio-card"
4
+
5
+ const meta: Meta<typeof RadioCardGroup> = {
6
+ title: "Components/RadioCard",
7
+ component: RadioCardGroup,
8
+ tags: ["autodocs"],
9
+ }
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof RadioCardGroup>
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <RadioCardGroup defaultValue="monthly" className="ax:max-w-md">
17
+ <RadioCard value="monthly">
18
+ <RadioCardTitle>Monthly</RadioCardTitle>
19
+ <RadioCardDescription>Pay as you go each month.</RadioCardDescription>
20
+ </RadioCard>
21
+ <RadioCard value="yearly">
22
+ <RadioCardTitle>Yearly</RadioCardTitle>
23
+ <RadioCardDescription>Save 20% with annual billing.</RadioCardDescription>
24
+ </RadioCard>
25
+ </RadioCardGroup>
26
+ ),
27
+ }
@@ -0,0 +1,225 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
5
+ import { Circle, CircleCheck } from "lucide-react"
6
+ import { cva } from "class-variance-authority"
7
+ import { cn } from "../lib/cn"
8
+
9
+ export type RadioCardSize = "sm" | "base" | "lg" | "xl"
10
+ export type RadioCardOrientation = "horizontal" | "vertical"
11
+
12
+ // ─── Context ──────────────────────────────────────────────────────────────────
13
+
14
+ interface RadioCardGroupContext {
15
+ size: RadioCardSize
16
+ compact: boolean
17
+ orientation: RadioCardOrientation
18
+ }
19
+
20
+ const RadioCardGroupContext = React.createContext<RadioCardGroupContext>({
21
+ size: "base",
22
+ compact: false,
23
+ orientation: "vertical",
24
+ })
25
+
26
+ // ─── Group ────────────────────────────────────────────────────────────────────
27
+
28
+ export interface RadioCardGroupProps
29
+ extends React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root> {
30
+ size?: RadioCardSize
31
+ compact?: boolean
32
+ orientation?: RadioCardOrientation
33
+ }
34
+
35
+ export const RadioCardGroup = React.forwardRef<
36
+ React.ElementRef<typeof RadioGroupPrimitive.Root>,
37
+ RadioCardGroupProps
38
+ >(({ className, size = "base", compact = false, orientation = "vertical", ...props }, ref) => (
39
+ <RadioCardGroupContext.Provider value={{ size, compact, orientation }}>
40
+ <RadioGroupPrimitive.Root
41
+ ref={ref}
42
+ orientation={orientation}
43
+ className={cn(
44
+ compact
45
+ ? orientation === "horizontal" ? "ax:flex ax:flex-row" : "ax:flex ax:flex-col"
46
+ : "ax:grid ax:gap-3",
47
+ className
48
+ )}
49
+ {...props}
50
+ />
51
+ </RadioCardGroupContext.Provider>
52
+ ))
53
+ RadioCardGroup.displayName = "RadioCardGroup"
54
+
55
+ // ─── Card ─────────────────────────────────────────────────────────────────────
56
+
57
+ // Checked background is intentionally omitted here: it's applied conditionally
58
+ // below so compact and default modes can use different tint strengths.
59
+ const radioCardVariants = cva(
60
+ [
61
+ "ax:relative ax:flex ax:items-start ax:cursor-pointer ax:border ax:border-border ax:bg-card",
62
+ "ax:transition-colors ax:transition-snappy",
63
+ // Selected border matches the selected title/icon colour (primary-strong /
64
+ // dark:primary) rather than the base --primary.
65
+ "ax:has-[[data-state=checked]]:border-primary",
66
+ "ax:dark:has-[[data-state=checked]]:border-primary",
67
+ // Hover only on unchecked cards - darker brand-aware border (≈30% foreground)
68
+ // for a clearly visible interactivity cue. `hover:z-10` lifts the hovered
69
+ // card above its compact-mode neighbours so the new border isn't clipped
70
+ // by the adjacent card's idle border.
71
+ "ax:[&:not(:has([data-state=checked]))]:hover:border-foreground/50",
72
+ "ax:[&:not(:has([data-state=checked]))]:hover:z-10",
73
+ "ax:has-[[data-disabled]]:cursor-not-allowed ax:has-[[data-disabled]]:opacity-50",
74
+ // Trailing radio indicator (top-right): a hollow ring shown BY DEFAULT that
75
+ // swaps to a filled check when the card is selected. Both glyphs share one
76
+ // stacked slot so the swap is layout-shift-free. State is driven from the
77
+ // card (ancestor) selectors (mirroring CheckboxCard) so source order
78
+ // resolves the overrides.
79
+ // off ring: --input at rest, --foreground/40 on hover, hidden when checked
80
+ // on check: hidden by default; primary-strong / dark:primary when checked,
81
+ // mirrors the selected title colour
82
+ "ax:[&_[data-slot=radio-card-off]]:text-input",
83
+ "ax:[&:not(:has([data-state=checked])):hover_[data-slot=radio-card-off]]:text-foreground/40",
84
+ "ax:[&:has([data-state=checked])_[data-slot=radio-card-off]]:opacity-0",
85
+ "ax:[&_[data-slot=radio-card-on]]:text-primary-foreground ax:[&_[data-slot=radio-card-on]]:opacity-0",
86
+ "ax:dark:[&_[data-slot=radio-card-on]]:text-primary-foreground",
87
+ "ax:[&:has([data-state=checked])_[data-slot=radio-card-on]]:opacity-100",
88
+ ],
89
+ {
90
+ variants: {
91
+ size: {
92
+ sm: "ax:p-3 ax:gap-3 ax:text-xs",
93
+ base: "ax:p-4 ax:gap-3 ax:text-sm",
94
+ lg: "ax:p-5 ax:gap-4 ax:text-base",
95
+ xl: "ax:px-7 ax:py-5 ax:gap-4 ax:text-base",
96
+ },
97
+ },
98
+ defaultVariants: { size: "base" },
99
+ }
100
+ )
101
+
102
+ // Default mode: subtle background tint on selection PLUS primary-coloured
103
+ // title and icon (mirrors compact mode so both variants read the same way
104
+ // at-a-glance: selected card → brand-tinted title + icon).
105
+ const DEFAULT_CHECKED =
106
+ "ax:has-[[data-state=checked]]:bg-primary-muted " +
107
+ "ax:[&:has([data-state=checked])_[data-slot=radio-card-title]]:text-primary-foreground " +
108
+ "ax:[&:has([data-state=checked])_[data-slot=radio-card-icon]]:text-primary-foreground " +
109
+ // Dark mode: primary-strong is a mid sage that reads muddy on a dark card, so
110
+ // brighten the selected title + icon (matches the bright checked indicator).
111
+ "ax:dark:[&:has([data-state=checked])_[data-slot=radio-card-title]]:text-primary-foreground " +
112
+ "ax:dark:[&:has([data-state=checked])_[data-slot=radio-card-icon]]:text-primary-foreground"
113
+
114
+ // Compact mode: primary-weakest background tint plus primary-coloured title and
115
+ // icon on selection. The [&:has(...)_[data-slot]] form styles a DESCENDANT
116
+ // matching data-slot whenever the label itself contains a checked radio.
117
+ const COMPACT_CHECKED =
118
+ "ax:has-[[data-state=checked]]:bg-primary-muted " +
119
+ "ax:[&:has([data-state=checked])_[data-slot=radio-card-title]]:text-primary-foreground " +
120
+ "ax:[&:has([data-state=checked])_[data-slot=radio-card-icon]]:text-primary-foreground " +
121
+ // Dark mode: brighten the selected title + icon (see DEFAULT_CHECKED).
122
+ "ax:dark:[&:has([data-state=checked])_[data-slot=radio-card-title]]:text-primary-foreground " +
123
+ "ax:dark:[&:has([data-state=checked])_[data-slot=radio-card-icon]]:text-primary-foreground"
124
+
125
+ const COMPACT_LAYOUT: Record<RadioCardOrientation, string> = {
126
+ horizontal: [
127
+ "ax:rounded-none",
128
+ "ax:first:rounded-l-(--input-radius) ax:last:rounded-r-(--input-radius)",
129
+ "ax:[&:not(:first-child)]:-ml-px",
130
+ "ax:has-[[data-state=checked]]:z-10",
131
+ ].join(" "),
132
+ vertical: [
133
+ "ax:rounded-none",
134
+ "ax:first:rounded-t-(--input-radius) ax:last:rounded-b-(--input-radius)",
135
+ "ax:[&:not(:first-child)]:-mt-px",
136
+ "ax:has-[[data-state=checked]]:z-10",
137
+ ].join(" "),
138
+ }
139
+
140
+ const INDICATOR_SIZE: Record<RadioCardSize, string> = {
141
+ sm: "ax:size-3.5",
142
+ base: "ax:size-4",
143
+ lg: "ax:size-4",
144
+ xl: "ax:size-5",
145
+ }
146
+
147
+ export interface RadioCardProps
148
+ extends React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> {
149
+ size?: RadioCardSize
150
+ }
151
+
152
+ export const RadioCard = React.forwardRef<
153
+ React.ElementRef<typeof RadioGroupPrimitive.Item>,
154
+ RadioCardProps
155
+ >(({ className, size: sizeProp, children, ...props }, ref) => {
156
+ const { size: ctxSize, compact, orientation } = React.useContext(RadioCardGroupContext)
157
+ const size = sizeProp ?? ctxSize
158
+
159
+ return (
160
+ // No htmlFor needed: label activates its first labelable descendant (the button) on click.
161
+ <label
162
+ className={cn(
163
+ radioCardVariants({ size }),
164
+ compact ? [COMPACT_LAYOUT[orientation], COMPACT_CHECKED] : ["ax:rounded-(--input-radius)", DEFAULT_CHECKED],
165
+ className
166
+ )}
167
+ >
168
+ <div className="ax:flex-1 ax:min-w-0">{children}</div>
169
+ <span
170
+ aria-hidden="true"
171
+ className={cn("ax:relative ax:shrink-0 ax:mt-0", INDICATOR_SIZE[size])}
172
+ >
173
+ {/* Unchecked - hollow ring, shown by default. */}
174
+ <Circle
175
+ data-slot="radio-card-off"
176
+ className="ax:absolute ax:inset-0 ax:size-full ax:transition ax:transition-snappy"
177
+ />
178
+ {/* Checked - filled check, revealed on selection. */}
179
+ <CircleCheck
180
+ data-slot="radio-card-on"
181
+ className="ax:absolute ax:inset-0 ax:size-full ax:transition ax:transition-snappy"
182
+ />
183
+ </span>
184
+ <RadioGroupPrimitive.Item ref={ref} className="ax:sr-only" {...props} />
185
+ </label>
186
+ )
187
+ })
188
+ RadioCard.displayName = "RadioCard"
189
+
190
+ // ─── Sub-components ───────────────────────────────────────────────────────────
191
+
192
+ // data-slot is used by the compact checked selectors to colour title and icon primary.
193
+ export const RadioCardIcon = React.forwardRef<
194
+ HTMLDivElement,
195
+ React.HTMLAttributes<HTMLDivElement>
196
+ >(({ className, ...props }, ref) => (
197
+ <div
198
+ ref={ref}
199
+ data-slot="radio-card-icon"
200
+ className={cn("ax:shrink-0 ax:text-muted-foreground", className)}
201
+ {...props}
202
+ />
203
+ ))
204
+ RadioCardIcon.displayName = "RadioCardIcon"
205
+
206
+ export const RadioCardTitle = React.forwardRef<
207
+ HTMLParagraphElement,
208
+ React.HTMLAttributes<HTMLParagraphElement>
209
+ >(({ className, ...props }, ref) => (
210
+ <p
211
+ ref={ref}
212
+ data-slot="radio-card-title"
213
+ className={cn("ax:t-label-sm ax:text-foreground", className)}
214
+ {...props}
215
+ />
216
+ ))
217
+ RadioCardTitle.displayName = "RadioCardTitle"
218
+
219
+ export const RadioCardDescription = React.forwardRef<
220
+ HTMLParagraphElement,
221
+ React.HTMLAttributes<HTMLParagraphElement>
222
+ >(({ className, ...props }, ref) => (
223
+ <p ref={ref} className={cn("ax:text-[0.875em] ax:text-muted-foreground ax:mt-1", className)} {...props} />
224
+ ))
225
+ RadioCardDescription.displayName = "RadioCardDescription"
@@ -0,0 +1,32 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Label } from "./input"
4
+ import { RadioGroup, RadioGroupItem } from "./radio-group"
5
+
6
+ const meta: Meta<typeof RadioGroup> = {
7
+ title: "Components/RadioGroup",
8
+ component: RadioGroup,
9
+ tags: ["autodocs"],
10
+ }
11
+
12
+ export default meta
13
+ type Story = StoryObj<typeof RadioGroup>
14
+
15
+ export const Default: Story = {
16
+ render: () => (
17
+ <RadioGroup defaultValue="comfortable">
18
+ <div className="ax:flex ax:items-center ax:gap-sm">
19
+ <RadioGroupItem value="default" id="r1" />
20
+ <Label htmlFor="r1">Default</Label>
21
+ </div>
22
+ <div className="ax:flex ax:items-center ax:gap-sm">
23
+ <RadioGroupItem value="comfortable" id="r2" />
24
+ <Label htmlFor="r2">Comfortable</Label>
25
+ </div>
26
+ <div className="ax:flex ax:items-center ax:gap-sm">
27
+ <RadioGroupItem value="compact" id="r3" />
28
+ <Label htmlFor="r3">Compact</Label>
29
+ </div>
30
+ </RadioGroup>
31
+ ),
32
+ }
@@ -0,0 +1,68 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
5
+ import { Circle } from "lucide-react"
6
+ import { cva, type VariantProps } from "class-variance-authority"
7
+ import { cn } from "../lib/cn"
8
+
9
+ export const RadioGroup = React.forwardRef<
10
+ React.ElementRef<typeof RadioGroupPrimitive.Root>,
11
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
12
+ >(({ className, ...props }, ref) => (
13
+ <RadioGroupPrimitive.Root
14
+ ref={ref}
15
+ className={cn("ax:grid ax:gap-2", className)}
16
+ {...props}
17
+ />
18
+ ))
19
+ RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
20
+
21
+ const radioItemVariants = cva(
22
+ [
23
+ "ax:aspect-square ax:rounded-full ax:border ax:border-input",
24
+ "ax:transition-colors ax:transition-snappy",
25
+ "ax:focus:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring ax:focus-visible:ring-offset-2 ax:focus-visible:ring-offset-background",
26
+ "ax:cursor-pointer ax:disabled:cursor-not-allowed ax:disabled:opacity-50",
27
+ // primary-weak fill + primary-strong dot. The fill auto-inverts to a light-ish
28
+ // tint in dark, so the dot flips to a dark `primary-muted` there (dark: override)
29
+ // to keep contrast on that lighter fill. Mirrors Checkbox.
30
+ "ax:data-[state=checked]:bg-primary-weak ax:data-[state=checked]:border-primary-weak ax:data-[state=checked]:text-primary-strong ax:dark:data-[state=checked]:text-primary-muted",
31
+ ],
32
+ {
33
+ variants: {
34
+ size: {
35
+ sm: "ax:size-3",
36
+ base: "ax:size-4",
37
+ lg: "ax:size-5",
38
+ },
39
+ },
40
+ defaultVariants: { size: "base" },
41
+ }
42
+ )
43
+
44
+ const INDICATOR_SIZE: Record<NonNullable<VariantProps<typeof radioItemVariants>["size"]>, string> = {
45
+ sm: "ax:size-1.5",
46
+ base: "ax:size-2",
47
+ lg: "ax:size-2.5",
48
+ }
49
+
50
+ export interface RadioGroupItemProps
51
+ extends React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>,
52
+ VariantProps<typeof radioItemVariants> {}
53
+
54
+ export const RadioGroupItem = React.forwardRef<
55
+ React.ElementRef<typeof RadioGroupPrimitive.Item>,
56
+ RadioGroupItemProps
57
+ >(({ className, size, ...props }, ref) => (
58
+ <RadioGroupPrimitive.Item
59
+ ref={ref}
60
+ className={cn(radioItemVariants({ size }), className)}
61
+ {...props}
62
+ >
63
+ <RadioGroupPrimitive.Indicator className="ax:flex ax:items-center ax:justify-center">
64
+ <Circle className={cn(INDICATOR_SIZE[size ?? "base"], "ax:fill-current ax:text-current")} />
65
+ </RadioGroupPrimitive.Indicator>
66
+ </RadioGroupPrimitive.Item>
67
+ ))
68
+ RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
@@ -0,0 +1,50 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Reasoning, ReasoningContent, ReasoningTrigger } from "./reasoning"
4
+ import { Button } from "./button"
5
+ import { Text } from "./text"
6
+
7
+ const meta: Meta<typeof Reasoning> = {
8
+ title: "Components/Reasoning",
9
+ component: Reasoning,
10
+ tags: ["autodocs"],
11
+ decorators: [(Story) => <div style={{ maxWidth: 560 }}><Story /></div>],
12
+ }
13
+
14
+ export default meta
15
+ type Story = StoryObj<typeof Reasoning>
16
+
17
+ const THOUGHT =
18
+ "The buyer wants a Yas Island townhouse under AED 5M with a garden. 11 listings match. The cheapest asks AED 4.2M, " +
19
+ "and the median registered sale in Yas Acres is AED 4.6M, so it is priced below recent sales."
20
+
21
+ export const Default: Story = {
22
+ render: () => (
23
+ <Reasoning>
24
+ <ReasoningTrigger>Show reasoning</ReasoningTrigger>
25
+ <ReasoningContent>{THOUGHT}</ReasoningContent>
26
+ </Reasoning>
27
+ ),
28
+ }
29
+
30
+ function StreamingDemo() {
31
+ const [text, setText] = React.useState("")
32
+ const streaming = text.length < THOUGHT.length && text.length > 0
33
+ React.useEffect(() => {
34
+ if (!streaming) return
35
+ const timer = window.setTimeout(() => setText(THOUGHT.slice(0, text.length + 4)), 40)
36
+ return () => window.clearTimeout(timer)
37
+ }, [text, streaming])
38
+ return (
39
+ <div style={{ display: "flex", flexDirection: "column", gap: 12, alignItems: "flex-start" }}>
40
+ <Reasoning streaming={streaming}>
41
+ <ReasoningTrigger>{streaming ? "Thinking" : "Thought for 4 seconds"}</ReasoningTrigger>
42
+ <ReasoningContent>{text}</ReasoningContent>
43
+ </Reasoning>
44
+ {!streaming && text && <Text as="p" size="2">The AED 4.2M Yas Acres townhouse is the best value.</Text>}
45
+ <Button variant="outline" size="sm" disabled={streaming} onClick={() => setText(THOUGHT.slice(0, 4))}>Stream</Button>
46
+ </div>
47
+ )
48
+ }
49
+
50
+ export const Streaming: Story = { render: () => <StreamingDemo /> }