@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,189 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cva } from "class-variance-authority"
5
+ import { cn } from "../lib/cn"
6
+
7
+ // ─── Types ────────────────────────────────────────────────────────────────────
8
+
9
+ export type ProgressSize = "sm" | "md" | "lg"
10
+ export type ProgressVariant = "default" | "success" | "warning" | "error"
11
+ export type ProgressShape = "bar" | "ring"
12
+
13
+ // ─── Bar-shape variants ───────────────────────────────────────────────────────
14
+
15
+ const barTrackVariants = cva(
16
+ "ax:relative ax:w-full ax:overflow-hidden ax:rounded-full ax:bg-muted",
17
+ {
18
+ variants: {
19
+ size: {
20
+ sm: "ax:h-1",
21
+ md: "ax:h-2",
22
+ lg: "ax:h-4",
23
+ },
24
+ },
25
+ defaultVariants: { size: "md" },
26
+ }
27
+ )
28
+
29
+ const barIndicatorVariants = cva(
30
+ "ax:h-full ax:w-full ax:rounded-full ax:[transition:transform_var(--motion-slow)_var(--motion-ease-out)]",
31
+ {
32
+ variants: {
33
+ variant: {
34
+ default: "ax:bg-primary",
35
+ success: "ax:bg-success",
36
+ warning: "ax:bg-warning",
37
+ error: "ax:bg-destructive",
38
+ },
39
+ },
40
+ defaultVariants: { variant: "default" },
41
+ }
42
+ )
43
+
44
+ // ─── Ring-shape mappings ──────────────────────────────────────────────────────
45
+
46
+ // `size` for the ring shape maps to a default pixel diameter. `ringSize`
47
+ // overrides this when callers need a specific dimension.
48
+ const RING_DEFAULT_PX: Record<ProgressSize, number> = {
49
+ sm: 32,
50
+ md: 48,
51
+ lg: 64,
52
+ }
53
+
54
+ // Default stroke widths scale with size so a small ring doesn't look hollow
55
+ // and a large ring doesn't look anaemic. `strokeWidth` overrides.
56
+ const RING_DEFAULT_STROKE: Record<ProgressSize, number> = {
57
+ sm: 4,
58
+ md: 5,
59
+ lg: 6,
60
+ }
61
+
62
+ const RING_STROKE_CLASS: Record<ProgressVariant, string> = {
63
+ default: "ax:stroke-primary",
64
+ success: "ax:stroke-success",
65
+ warning: "ax:stroke-warning",
66
+ error: "ax:stroke-destructive",
67
+ }
68
+
69
+ // ─── Props ────────────────────────────────────────────────────────────────────
70
+
71
+ export interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
72
+ /** Current value, 0–max. */
73
+ value?: number
74
+ /** Maximum value. Defaults to 100. */
75
+ max?: number
76
+ /** Visual shape - horizontal `bar` (default) or circular `ring`. */
77
+ shape?: ProgressShape
78
+ /** Size - applies to both shapes:
79
+ * bar → height (h-1 / h-2 / h-4)
80
+ * ring → pixel diameter (32 / 48 / 64) */
81
+ size?: ProgressSize
82
+ /** Colour treatment. The track stays neutral; this drives the indicator. */
83
+ variant?: ProgressVariant
84
+ /** Ring-only: override the pixel diameter (takes precedence over `size`). */
85
+ ringSize?: number
86
+ /** Ring-only: override the stroke width in pixels. */
87
+ strokeWidth?: number
88
+ }
89
+
90
+ // ─── Component ────────────────────────────────────────────────────────────────
91
+
92
+ export const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
93
+ (
94
+ {
95
+ className,
96
+ value = 0,
97
+ max = 100,
98
+ shape = "bar",
99
+ size = "md",
100
+ variant = "default",
101
+ ringSize,
102
+ strokeWidth,
103
+ ...props
104
+ },
105
+ ref
106
+ ) => {
107
+ const pct = Math.min(100, Math.max(0, (value / max) * 100))
108
+
109
+ // Common ARIA + data attributes work for both shapes.
110
+ const ariaProps = {
111
+ role: "progressbar" as const,
112
+ "aria-valuemin": 0,
113
+ "aria-valuemax": max,
114
+ "aria-valuenow": value,
115
+ "data-state": pct >= 100 ? "complete" : "loading",
116
+ "data-value": value,
117
+ "data-max": max,
118
+ }
119
+
120
+ if (shape === "ring") {
121
+ const diameter = ringSize ?? RING_DEFAULT_PX[size]
122
+ const stroke = strokeWidth ?? RING_DEFAULT_STROKE[size]
123
+ const r = (diameter - stroke) / 2
124
+ const c = diameter / 2
125
+ const circ = 2 * Math.PI * r
126
+ const dashOffset = circ * (1 - pct / 100)
127
+ const strokeClass = RING_STROKE_CLASS[variant]
128
+ return (
129
+ <div
130
+ ref={ref}
131
+ className={cn("ax:inline-flex ax:shrink-0", className)}
132
+ {...ariaProps}
133
+ {...props}
134
+ >
135
+ <svg
136
+ width={diameter}
137
+ height={diameter}
138
+ viewBox={`0 0 ${diameter} ${diameter}`}
139
+ aria-hidden="true"
140
+ >
141
+ {/* Track - same hue at 15% opacity sits behind the indicator arc */}
142
+ <circle
143
+ cx={c}
144
+ cy={c}
145
+ r={r}
146
+ fill="none"
147
+ strokeWidth={stroke}
148
+ className={`${strokeClass} ax:opacity-15`}
149
+ />
150
+ {/* Indicator arc - animated stroke-dashoffset for smooth value changes */}
151
+ {pct > 0 && (
152
+ <circle
153
+ cx={c}
154
+ cy={c}
155
+ r={r}
156
+ fill="none"
157
+ strokeWidth={stroke}
158
+ strokeLinecap="round"
159
+ strokeDasharray={circ}
160
+ strokeDashoffset={dashOffset}
161
+ transform={`rotate(-90 ${c} ${c})`}
162
+ className={cn(
163
+ strokeClass,
164
+ "ax:[transition:stroke-dashoffset_var(--motion-slow)_var(--motion-ease-out)]"
165
+ )}
166
+ />
167
+ )}
168
+ </svg>
169
+ </div>
170
+ )
171
+ }
172
+
173
+ // Bar shape (default)
174
+ return (
175
+ <div
176
+ ref={ref}
177
+ className={cn(barTrackVariants({ size }), className)}
178
+ {...ariaProps}
179
+ {...props}
180
+ >
181
+ <div
182
+ className={barIndicatorVariants({ variant })}
183
+ style={{ transform: `translateX(-${100 - pct}%)` }}
184
+ />
185
+ </div>
186
+ )
187
+ }
188
+ )
189
+ Progress.displayName = "Progress"
@@ -0,0 +1,62 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Paperclip } from "lucide-react"
4
+ import { AttachmentChip, PromptInput, PromptInputAction } from "./prompt-input"
5
+ import { TooltipProvider } from "./tooltip"
6
+
7
+ const meta: Meta<typeof PromptInput> = {
8
+ title: "Components/PromptInput",
9
+ component: PromptInput,
10
+ tags: ["autodocs"],
11
+ decorators: [(Story) => <TooltipProvider><div style={{ maxWidth: 640 }}><Story /></div></TooltipProvider>],
12
+ argTypes: { onSubmit: { control: false }, onStop: { control: false }, onValueChange: { control: false } },
13
+ args: { onSubmit: () => undefined, placeholder: "Ask about properties, prices or communities" },
14
+ }
15
+
16
+ export default meta
17
+ type Story = StoryObj<typeof PromptInput>
18
+
19
+ export const Default: Story = {}
20
+
21
+ export const Busy: Story = { args: { busy: true, defaultValue: "", onStop: () => undefined } }
22
+
23
+ export const NearTheLimit: Story = { args: { maxLength: 120, defaultValue: "Three-bedroom apartments on Saadiyat Island for a family of five, near a school, with sea views please" } }
24
+
25
+ export const OverTheLimit: Story = { args: { maxLength: 60, defaultValue: "Three-bedroom apartments on Saadiyat Island for a family of five" } }
26
+
27
+ export const Disabled: Story = {
28
+ args: { disabled: true, disabledReason: "The assistant is offline for maintenance until 14:00. Your agent can still help." },
29
+ }
30
+
31
+ export const WithAttachments: Story = {
32
+ args: {
33
+ hasAttachments: true,
34
+ canSubmit: false,
35
+ hint: "Waiting for uploads to finish.",
36
+ toolbar: <PromptInputAction label="Attach files"><Paperclip /></PromptInputAction>,
37
+ attachments: (
38
+ <div style={{ display: "flex", gap: "0.5rem", overflowX: "auto" }} role="list" aria-label="Attachments">
39
+ <div role="listitem"><AttachmentChip name="floor-plan.pdf" size={1_800_000} onRemove={() => undefined} /></div>
40
+ <div role="listitem"><AttachmentChip name="living-room.jpg" size={420_000} kind="image" progress={45} onRemove={() => undefined} /></div>
41
+ <div role="listitem"><AttachmentChip name="video.mov" size={52_000_000} error="File type not supported" onRemove={() => undefined} /></div>
42
+ </div>
43
+ ),
44
+ },
45
+ }
46
+
47
+ export const Small: Story = {
48
+ decorators: [(Story) => <div style={{ maxWidth: 360 }}><Story /></div>],
49
+ args: {
50
+ size: "sm",
51
+ placeholder: "Message Adres Assistant",
52
+ toolbar: <PromptInputAction label="Attach files"><Paperclip /></PromptInputAction>,
53
+ },
54
+ }
55
+
56
+ export const FileDrop: Story = {
57
+ args: {
58
+ onFilesDrop: () => undefined,
59
+ dropHint: "PDF, PNG or JPG, up to 10 MB each",
60
+ placeholder: "Drag a file onto the composer",
61
+ },
62
+ }
@@ -0,0 +1,106 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import * as React from "react"
3
+ import { renderToStaticMarkup } from "react-dom/server"
4
+ import { AttachmentChip, PromptInput, PromptInputAction, characterCountTone, formatFileSize, isFileDrag } from "./prompt-input"
5
+ import { TooltipProvider } from "./tooltip"
6
+
7
+ const submit = () => undefined
8
+
9
+ describe("characterCountTone", () => {
10
+ it("stays hidden below 80% of the limit", () => {
11
+ expect(characterCountTone(1599, 2000)).toBeNull()
12
+ })
13
+
14
+ it("is muted from 80%, warning from 90% and destructive once over", () => {
15
+ expect(characterCountTone(1600, 2000)).toBe("muted")
16
+ expect(characterCountTone(1800, 2000)).toBe("warning")
17
+ expect(characterCountTone(2000, 2000)).toBe("warning")
18
+ expect(characterCountTone(2001, 2000)).toBe("destructive")
19
+ })
20
+ })
21
+
22
+ describe("formatFileSize", () => {
23
+ it("uses KB below a megabyte, never showing 0 KB", () => {
24
+ expect(formatFileSize(10)).toBe("1 KB")
25
+ expect(formatFileSize(420_000)).toBe("410 KB")
26
+ })
27
+
28
+ it("uses MB with one decimal from a megabyte", () => {
29
+ expect(formatFileSize(1024 * 1024 * 1.8)).toBe("1.8 MB")
30
+ })
31
+ })
32
+
33
+ describe("PromptInput", () => {
34
+ it("disables Send while the field is empty", () => {
35
+ const html = renderToStaticMarkup(<PromptInput onSubmit={submit} />)
36
+ expect(html).toMatch(/aria-label="Send message"[^>]*disabled=""|disabled=""[^>]*aria-label="Send message"/)
37
+ })
38
+
39
+ it("enables Send for attachments alone", () => {
40
+ const html = renderToStaticMarkup(<PromptInput onSubmit={submit} hasAttachments />)
41
+ expect(html).not.toMatch(/aria-label="Send message"[^>]*disabled=""|disabled=""[^>]*aria-label="Send message"/)
42
+ })
43
+
44
+ it("marks an over-limit field invalid and says how much to cut", () => {
45
+ const html = renderToStaticMarkup(<PromptInput onSubmit={submit} maxLength={10} defaultValue="Twelve chars" />)
46
+ expect(html).toContain('aria-invalid="true"')
47
+ expect(html).toContain("Shorten your message by 2 characters to send it.")
48
+ })
49
+
50
+ it("swaps Send for Stop while busy", () => {
51
+ const html = renderToStaticMarkup(<PromptInput onSubmit={submit} busy />)
52
+ expect(html).toContain('aria-label="Stop generating"')
53
+ expect(html).not.toContain('aria-label="Send message"')
54
+ })
55
+
56
+ it("explains why it is disabled", () => {
57
+ const html = renderToStaticMarkup(<PromptInput onSubmit={submit} disabled disabledReason="Offline until 14:00" />)
58
+ expect(html).toContain("Offline until 14:00")
59
+ })
60
+ })
61
+
62
+ describe("AttachmentChip", () => {
63
+ it("shows progress while uploading, then the size", () => {
64
+ expect(renderToStaticMarkup(<AttachmentChip name="a.pdf" size={2048} progress={40} onRemove={submit} />)).toContain('aria-label="Uploading a.pdf"')
65
+ expect(renderToStaticMarkup(<AttachmentChip name="a.pdf" size={2048} onRemove={submit} />)).toContain("2 KB")
66
+ })
67
+
68
+ it("shows the error in place of size", () => {
69
+ const html = renderToStaticMarkup(<AttachmentChip name="a.mov" size={2048} error="File type not supported" onRemove={submit} />)
70
+ expect(html).toContain("File type not supported")
71
+ expect(html).not.toContain("2 KB")
72
+ })
73
+ })
74
+
75
+ describe("PromptInput size", () => {
76
+ it("defaults to md with 36px buttons", () => {
77
+ const html = renderToStaticMarkup(<PromptInput onSubmit={() => undefined} />)
78
+ expect(html).toContain('data-size="md"')
79
+ expect(html).toContain("ax:size-9")
80
+ })
81
+
82
+ it("tightens to sm and sizes toolbar actions to match", () => {
83
+ const html = renderToStaticMarkup(
84
+ <TooltipProvider>
85
+ <PromptInput size="sm" onSubmit={() => undefined} toolbar={<PromptInputAction label="Attach files"><svg /></PromptInputAction>} />
86
+ </TooltipProvider>,
87
+ )
88
+ expect(html).toContain('data-size="sm"')
89
+ expect(html).toContain("ax:rounded-lg")
90
+ expect(html).not.toContain("ax:size-9")
91
+ expect(html.match(/ax:size-8/g)?.length).toBe(2)
92
+ })
93
+ })
94
+
95
+ describe("PromptInput file drop", () => {
96
+ it("only treats drags that carry files as drops", () => {
97
+ expect(isFileDrag(["Files", "application/x-moz-file"])).toBe(true)
98
+ expect(isFileDrag(["text/plain", "text/uri-list"])).toBe(false)
99
+ })
100
+
101
+ it("shows no drop overlay at rest", () => {
102
+ const html = renderToStaticMarkup(<PromptInput onSubmit={submit} onFilesDrop={submit} dropHint="PDF or images" />)
103
+ expect(html).not.toContain("Drop files to attach")
104
+ expect(html).not.toContain("PDF or images")
105
+ })
106
+ })