@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,313 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Check, ChevronLeft, ChevronRight, Flag, Link2, MoreHorizontal } from "lucide-react"
5
+ import { cn } from "../lib/cn"
6
+ import { Button } from "./button"
7
+ import {
8
+ Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle,
9
+ } from "./dialog"
10
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./dropdown-menu"
11
+ import { Label, Textarea } from "./input"
12
+ import { MessageActions, type MessageRating } from "./message-actions"
13
+ import { RadioGroup, RadioGroupItem } from "./radio-group"
14
+ import { Text } from "./text"
15
+ import { TextLink } from "./text-link"
16
+
17
+ export interface AnswerFeedback {
18
+ rating: MessageRating
19
+ reasons: string[]
20
+ comment: string
21
+ }
22
+
23
+ export interface AnswerReport {
24
+ reason: string
25
+ details: string
26
+ }
27
+
28
+ export interface FeedbackReasonChipsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onToggle"> {
29
+ reasons: string[]
30
+ selected: string[]
31
+ onToggle: (reason: string) => void
32
+ /** Accessible name of the group. */
33
+ label: string
34
+ }
35
+
36
+ /** Multi-select reason chips: toggle buttons with aria-pressed. */
37
+ export function FeedbackReasonChips({ reasons, selected, onToggle, label, className, ...props }: FeedbackReasonChipsProps) {
38
+ return (
39
+ <div role="group" aria-label={label} className={cn("ax:flex ax:flex-wrap ax:gap-xs", className)} {...props}>
40
+ {reasons.map((reason) => {
41
+ const on = selected.includes(reason)
42
+ return (
43
+ <Button
44
+ key={reason}
45
+ type="button"
46
+ variant={on ? "secondary" : "outline"}
47
+ size="sm"
48
+ aria-pressed={on}
49
+ onClick={() => onToggle(reason)}
50
+ className="ax:rounded-full"
51
+ >
52
+ {on && <Check />}
53
+ {reason}
54
+ </Button>
55
+ )
56
+ })}
57
+ </div>
58
+ )
59
+ }
60
+
61
+ /** Opens after a rating: why, in a tap, plus an optional comment. Down asks; up only offers. */
62
+ function FeedbackForm({ rating, reasons, onSubmit, onCancel }: {
63
+ rating: MessageRating
64
+ reasons: string[]
65
+ onSubmit: (feedback: AnswerFeedback) => void
66
+ onCancel: () => void
67
+ }) {
68
+ const id = React.useId()
69
+ const [selected, setSelected] = React.useState<string[]>([])
70
+ const [comment, setComment] = React.useState("")
71
+ const firstRef = React.useRef<HTMLDivElement>(null)
72
+
73
+ // Move focus into the form so keyboard users land on the reasons.
74
+ React.useEffect(() => {
75
+ firstRef.current?.querySelector("button")?.focus()
76
+ }, [])
77
+
78
+ const title = rating === "down" ? "What went wrong?" : "What was good?"
79
+ return (
80
+ <div className="ax:flex ax:flex-col ax:gap-sm ax:rounded-md ax:border ax:border-border ax:bg-card ax:p-md">
81
+ <form
82
+ aria-labelledby={`${id}-title`}
83
+ className="ax:flex ax:flex-col ax:gap-sm"
84
+ onSubmit={(event) => {
85
+ event.preventDefault()
86
+ onSubmit({ rating, reasons: selected, comment: comment.trim() })
87
+ }}
88
+ >
89
+ <Text as="p" id={`${id}-title`} size="2" weight="medium">{title}</Text>
90
+ <div ref={firstRef}>
91
+ <FeedbackReasonChips
92
+ reasons={reasons}
93
+ selected={selected}
94
+ label={title}
95
+ onToggle={(reason) => setSelected((prev) => prev.includes(reason) ? prev.filter((r) => r !== reason) : [...prev, reason])}
96
+ />
97
+ </div>
98
+ <div className="ax:flex ax:flex-col ax:gap-1.5">
99
+ <Label htmlFor={`${id}-comment`} className="ax:text-muted-foreground">Anything else? (optional)</Label>
100
+ <Textarea id={`${id}-comment`} value={comment} onChange={(event) => setComment(event.target.value)} rows={2} resize="none" size="sm" />
101
+ </div>
102
+ <div className="ax:flex ax:gap-xs">
103
+ <Button type="submit" size="sm" className="ax:rounded-full" disabled={rating === "down" && selected.length === 0 && !comment.trim()}>
104
+ Send feedback
105
+ </Button>
106
+ <Button type="button" variant="ghost" size="sm" className="ax:rounded-full" onClick={onCancel}>{rating === "down" ? "Cancel" : "Skip"}</Button>
107
+ </div>
108
+ </form>
109
+ </div>
110
+ )
111
+ }
112
+
113
+ function ReportDialog({ open, onOpenChange, reasons, description, onReport }: {
114
+ open: boolean
115
+ onOpenChange: (open: boolean) => void
116
+ reasons: { value: string; label: string }[]
117
+ description: string
118
+ onReport?: (report: AnswerReport) => void
119
+ }) {
120
+ const id = React.useId()
121
+ const [reason, setReason] = React.useState("")
122
+ const [details, setDetails] = React.useState("")
123
+ return (
124
+ <Dialog open={open} onOpenChange={(next) => {
125
+ onOpenChange(next)
126
+ if (!next) { setReason(""); setDetails("") }
127
+ }}>
128
+ <DialogContent size="sm">
129
+ <form onSubmit={(event) => {
130
+ event.preventDefault()
131
+ onReport?.({ reason, details: details.trim() })
132
+ onOpenChange(false)
133
+ setReason("")
134
+ setDetails("")
135
+ }}>
136
+ <DialogHeader>
137
+ <DialogTitle>Report a problem</DialogTitle>
138
+ <DialogDescription>{description}</DialogDescription>
139
+ </DialogHeader>
140
+ <div className="ax:flex ax:flex-col ax:gap-md ax:py-md">
141
+ <RadioGroup value={reason} onValueChange={setReason} aria-label="Reason">
142
+ {reasons.map((option) => (
143
+ <div key={option.value} className="ax:flex ax:items-center ax:gap-sm">
144
+ <RadioGroupItem value={option.value} id={`${id}-${option.value}`} />
145
+ <Label htmlFor={`${id}-${option.value}`}>{option.label}</Label>
146
+ </div>
147
+ ))}
148
+ </RadioGroup>
149
+ <div className="ax:flex ax:flex-col ax:gap-1.5">
150
+ <Label htmlFor={`${id}-details`}>Details (optional)</Label>
151
+ <Textarea id={`${id}-details`} value={details} onChange={(event) => setDetails(event.target.value)} rows={3} resize="none" />
152
+ </div>
153
+ </div>
154
+ <DialogFooter>
155
+ <DialogClose asChild><Button type="button" variant="outline">Cancel</Button></DialogClose>
156
+ <Button type="submit" disabled={!reason}>Send report</Button>
157
+ </DialogFooter>
158
+ </form>
159
+ </DialogContent>
160
+ </Dialog>
161
+ )
162
+ }
163
+
164
+ export interface AnswerActionsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onCopy"> {
165
+ /** Text the Copy action copies (the visible version). */
166
+ text: string
167
+ /** Reasons offered after thumbs down; one or a comment is required to send. */
168
+ negativeReasons: string[]
169
+ /** Reasons offered after thumbs up; optional to send. */
170
+ positiveReasons: string[]
171
+ /** Sent feedback, for logging. */
172
+ onFeedback?: (feedback: AnswerFeedback) => void
173
+ /** The "Undo" link after sending was used. */
174
+ onUndo?: () => void
175
+ /** Zero-based index of the visible version. */
176
+ version?: number
177
+ versionCount?: number
178
+ onVersionChange?: (version: number) => void
179
+ onRegenerate?: () => void
180
+ regenerating?: boolean
181
+ /** Keeps Regenerate in place but disabled, e.g. when no more versions can be made. */
182
+ regenerateDisabled?: boolean
183
+ /** Adds "Copy link" to the More menu. */
184
+ onCopyLink?: () => void
185
+ /** Adds "Report a problem" to the More menu, with these reasons. */
186
+ reportReasons?: { value: string; label: string }[]
187
+ /** Who reads reports and what happens next. */
188
+ reportDescription?: string
189
+ onReport?: (report: AnswerReport) => void
190
+ }
191
+
192
+ /**
193
+ * AnswerActions - the action row under an AI answer: copy, rate, regenerate
194
+ * with version history, and report. Rating opens a short reasons form;
195
+ * sent feedback can be undone.
196
+ *
197
+ * @example
198
+ * <AnswerActions text={answer} negativeReasons={["Wrong price"]} positiveReasons={["Clear"]} onFeedback={log} />
199
+ */
200
+ export const AnswerActions = React.forwardRef<HTMLDivElement, AnswerActionsProps>(
201
+ (
202
+ {
203
+ text, negativeReasons, positiveReasons, onFeedback, onUndo, version = 0, versionCount = 1, onVersionChange,
204
+ onRegenerate, regenerating, regenerateDisabled, onCopyLink, reportReasons = [],
205
+ reportDescription = "Tell us what's wrong with this answer.", onReport, className, ...props
206
+ },
207
+ ref,
208
+ ) => {
209
+ const [rating, setRating] = React.useState<MessageRating | null>(null)
210
+ const [formOpen, setFormOpen] = React.useState(false)
211
+ const [sent, setSent] = React.useState<AnswerFeedback | null>(null)
212
+ const [reportOpen, setReportOpen] = React.useState(false)
213
+ const canReport = reportReasons.length > 0
214
+
215
+ function rate(next: MessageRating) {
216
+ if (rating === next) {
217
+ setRating(null)
218
+ setFormOpen(false)
219
+ setSent(null)
220
+ return
221
+ }
222
+ setRating(next)
223
+ setSent(null)
224
+ setFormOpen(true)
225
+ }
226
+
227
+ function undo() {
228
+ setRating(null)
229
+ setSent(null)
230
+ setFormOpen(false)
231
+ onUndo?.()
232
+ }
233
+
234
+ return (
235
+ <div ref={ref} data-slot="answer-actions" className={cn("ax:flex ax:flex-col ax:gap-sm", className)} {...props}>
236
+ <MessageActions
237
+ role="toolbar"
238
+ aria-label="Answer actions"
239
+ className="ax:gap-2xs"
240
+ copyText={text}
241
+ rating={rating}
242
+ onRate={rate}
243
+ onRegenerate={onRegenerate}
244
+ regenerating={regenerating}
245
+ regenerateDisabled={regenerateDisabled}
246
+ >
247
+ {versionCount > 1 && onVersionChange && (
248
+ <div className="ax:flex ax:items-center" role="group" aria-label="Response versions">
249
+ <Button variant="ghost" size="icon-sm" className="ax:rounded-full" aria-label="Previous version" disabled={version === 0} onClick={() => onVersionChange(version - 1)}>
250
+ <ChevronLeft />
251
+ </Button>
252
+ <Text size="1" color="muted" className="ax:tabular-nums" aria-live="polite">{version + 1} of {versionCount}</Text>
253
+ <Button variant="ghost" size="icon-sm" className="ax:rounded-full" aria-label="Next version" disabled={version === versionCount - 1} onClick={() => onVersionChange(version + 1)}>
254
+ <ChevronRight />
255
+ </Button>
256
+ </div>
257
+ )}
258
+ {(onCopyLink || canReport) && (
259
+ <DropdownMenu>
260
+ <DropdownMenuTrigger asChild>
261
+ <Button variant="ghost" size="icon-sm" className="ax:rounded-full ax:text-muted-foreground ax:hover:text-foreground" aria-label="More actions">
262
+ <MoreHorizontal />
263
+ </Button>
264
+ </DropdownMenuTrigger>
265
+ <DropdownMenuContent align="start">
266
+ {onCopyLink && <DropdownMenuItem onSelect={onCopyLink}><Link2 />Copy link</DropdownMenuItem>}
267
+ {canReport && <DropdownMenuItem variant="destructive" onSelect={() => setReportOpen(true)}><Flag />Report a problem</DropdownMenuItem>}
268
+ </DropdownMenuContent>
269
+ </DropdownMenu>
270
+ )}
271
+ </MessageActions>
272
+
273
+ {formOpen && rating && !sent && (
274
+ <FeedbackForm
275
+ key={rating}
276
+ rating={rating}
277
+ reasons={rating === "down" ? negativeReasons : positiveReasons}
278
+ onSubmit={(feedback) => {
279
+ setSent(feedback)
280
+ setFormOpen(false)
281
+ onFeedback?.(feedback)
282
+ }}
283
+ onCancel={() => {
284
+ setFormOpen(false)
285
+ if (rating === "down") setRating(null)
286
+ }}
287
+ />
288
+ )}
289
+
290
+ <Text as="p" size="1" color="muted" aria-live="polite" className="ax:flex ax:flex-wrap ax:items-center ax:gap-x-xs">
291
+ {sent && (
292
+ <>
293
+ Thanks, this helps improve answers.
294
+ <TextLink asChild size="1"><button type="button" onClick={undo}>Undo</button></TextLink>
295
+ </>
296
+ )}
297
+ {!sent && rating === "up" && !formOpen && "Thanks for the feedback."}
298
+ </Text>
299
+
300
+ {canReport && (
301
+ <ReportDialog
302
+ open={reportOpen}
303
+ onOpenChange={setReportOpen}
304
+ reasons={reportReasons}
305
+ description={reportDescription}
306
+ onReport={onReport}
307
+ />
308
+ )}
309
+ </div>
310
+ )
311
+ },
312
+ )
313
+ AnswerActions.displayName = "AnswerActions"
@@ -0,0 +1,32 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { AspectRatio } from "./aspect-ratio"
4
+
5
+ const meta: Meta<typeof AspectRatio> = {
6
+ title: "Components/AspectRatio",
7
+ component: AspectRatio,
8
+ tags: ["autodocs"],
9
+ }
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof AspectRatio>
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <div className="ax:w-[320px]">
17
+ <AspectRatio ratio={16 / 9} className="ax:bg-muted ax:rounded-md ax:overflow-hidden">
18
+ <div className="ax:flex ax:h-full ax:w-full ax:items-center ax:justify-center ax:text-muted-foreground">16:9</div>
19
+ </AspectRatio>
20
+ </div>
21
+ ),
22
+ }
23
+
24
+ export const Square: Story = {
25
+ render: () => (
26
+ <div className="ax:w-[200px]">
27
+ <AspectRatio ratio={1} className="ax:bg-muted ax:rounded-md ax:overflow-hidden">
28
+ <div className="ax:flex ax:h-full ax:w-full ax:items-center ax:justify-center ax:text-muted-foreground">1:1</div>
29
+ </AspectRatio>
30
+ </div>
31
+ ),
32
+ }
@@ -0,0 +1,5 @@
1
+ "use client"
2
+
3
+ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
4
+
5
+ export const AspectRatio = AspectRatioPrimitive.Root
@@ -0,0 +1,40 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Avatar, AvatarFallback, AvatarImage, AvatarStack } from "./avatar"
4
+
5
+ const meta: Meta<typeof Avatar> = {
6
+ title: "Components/Avatar",
7
+ component: Avatar,
8
+ tags: ["autodocs"],
9
+ }
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof Avatar>
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <Avatar>
17
+ <AvatarImage src="https://api.dicebear.com/7.x/avataaars/svg?seed=Adres" alt="User" />
18
+ <AvatarFallback>JD</AvatarFallback>
19
+ </Avatar>
20
+ ),
21
+ }
22
+
23
+ export const Fallback: Story = {
24
+ render: () => (
25
+ <Avatar>
26
+ <AvatarFallback>AB</AvatarFallback>
27
+ </Avatar>
28
+ ),
29
+ }
30
+
31
+ export const Stack: Story = {
32
+ render: () => (
33
+ <AvatarStack max={3}>
34
+ <Avatar><AvatarFallback>A</AvatarFallback></Avatar>
35
+ <Avatar><AvatarFallback>B</AvatarFallback></Avatar>
36
+ <Avatar><AvatarFallback>C</AvatarFallback></Avatar>
37
+ <Avatar><AvatarFallback>D</AvatarFallback></Avatar>
38
+ </AvatarStack>
39
+ ),
40
+ }
@@ -0,0 +1,154 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+ import { cn } from "../lib/cn"
7
+
8
+ // ─── Types ────────────────────────────────────────────────────────────────────
9
+
10
+ export type AvatarSize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl"
11
+
12
+ const avatarVariants = cva(
13
+ "ax:relative ax:flex ax:shrink-0 ax:overflow-hidden ax:rounded-full",
14
+ {
15
+ variants: {
16
+ size: {
17
+ xs: "ax:size-6 ax:text-[10px]",
18
+ sm: "ax:size-8 ax:text-xs",
19
+ md: "ax:size-10 ax:text-sm",
20
+ lg: "ax:size-12 ax:text-base",
21
+ xl: "ax:size-16 ax:text-lg",
22
+ "2xl": "ax:size-20 ax:text-xl",
23
+ },
24
+ },
25
+ defaultVariants: { size: "md" },
26
+ }
27
+ )
28
+
29
+ // ─── Context (used by AvatarStack to propagate size to children) ──────────────
30
+
31
+ const AvatarStackContext = React.createContext<{ size?: AvatarSize }>({ size: undefined })
32
+
33
+ // ─── Avatar ──────────────────────────────────────────────────────────────────
34
+
35
+ export interface AvatarProps
36
+ extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>,
37
+ VariantProps<typeof avatarVariants> {}
38
+
39
+ export const Avatar = React.forwardRef<
40
+ React.ElementRef<typeof AvatarPrimitive.Root>,
41
+ AvatarProps
42
+ >(({ className, size: sizeProp, ...props }, ref) => {
43
+ const { size: ctxSize } = React.useContext(AvatarStackContext)
44
+ const size = sizeProp ?? ctxSize
45
+ return (
46
+ <AvatarPrimitive.Root
47
+ ref={ref}
48
+ data-slot="avatar"
49
+ className={cn(avatarVariants({ size }), className)}
50
+ {...props}
51
+ />
52
+ )
53
+ })
54
+ Avatar.displayName = AvatarPrimitive.Root.displayName
55
+
56
+ // ─── AvatarImage ─────────────────────────────────────────────────────────────
57
+
58
+ export type AvatarImageProps = React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
59
+
60
+ export const AvatarImage = React.forwardRef<
61
+ React.ElementRef<typeof AvatarPrimitive.Image>,
62
+ AvatarImageProps
63
+ >(({ className, ...props }, ref) => (
64
+ <AvatarPrimitive.Image
65
+ ref={ref}
66
+ className={cn("ax:aspect-square ax:h-full ax:w-full ax:object-cover", className)}
67
+ {...props}
68
+ />
69
+ ))
70
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
71
+
72
+ // ─── AvatarFallback ──────────────────────────────────────────────────────────
73
+
74
+ export type AvatarFallbackProps = React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
75
+
76
+ export const AvatarFallback = React.forwardRef<
77
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
78
+ AvatarFallbackProps
79
+ >(({ className, ...props }, ref) => (
80
+ <AvatarPrimitive.Fallback
81
+ ref={ref}
82
+ className={cn(
83
+ "ax:flex ax:h-full ax:w-full ax:items-center ax:justify-center ax:rounded-full",
84
+ "ax:bg-muted ax:text-muted-foreground ax:font-medium",
85
+ className
86
+ )}
87
+ {...props}
88
+ />
89
+ ))
90
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
91
+
92
+ // ─── AvatarStack ─────────────────────────────────────────────────────────────
93
+ //
94
+ // Overlapping horizontal group of avatars (collaborators, attendees, contacts).
95
+ // Children must be <Avatar> elements; each one picks up:
96
+ // • the stack's `size` via context (so consumers don't have to set it
97
+ // individually on every child),
98
+ // • a background-coloured ring via the `*:` direct-children selector, gives
99
+ // the classic "white-ring between overlapping circles" effect on any
100
+ // surface, since the ring uses `--background`.
101
+ //
102
+ // When `max` is provided and the visible count exceeds it, the last position
103
+ // is replaced by a "+N" fallback chip. Without `max`, every child renders.
104
+
105
+ const STACK_OVERLAP: Record<AvatarSize, string> = {
106
+ xs: "ax:-space-x-1",
107
+ sm: "ax:-space-x-2",
108
+ md: "ax:-space-x-2",
109
+ lg: "ax:-space-x-3",
110
+ xl: "ax:-space-x-4",
111
+ "2xl": "ax:-space-x-5",
112
+ }
113
+
114
+ export interface AvatarStackProps extends React.HTMLAttributes<HTMLDivElement> {
115
+ /** Size applied to every Avatar child via context. Individual children can
116
+ * still override with their own `size` prop. */
117
+ size?: AvatarSize
118
+ /** Maximum number of children to render before collapsing the remainder into
119
+ * a `+N` fallback chip. When omitted, every child is shown. */
120
+ max?: number
121
+ }
122
+
123
+ export const AvatarStack = React.forwardRef<HTMLDivElement, AvatarStackProps>(
124
+ ({ className, size = "md", max, children, ...props }, ref) => {
125
+ const childArray = React.Children.toArray(children)
126
+ const total = childArray.length
127
+ const shouldOverflow = max != null && total > max
128
+ const visibleCount = shouldOverflow ? max - 1 : total
129
+ const overflowCount = total - visibleCount
130
+
131
+ return (
132
+ <AvatarStackContext.Provider value={{ size }}>
133
+ <div
134
+ ref={ref}
135
+ role="group"
136
+ className={cn(
137
+ "ax:flex *:ring-2 *:ring-background *:rounded-full",
138
+ STACK_OVERLAP[size],
139
+ className
140
+ )}
141
+ {...props}
142
+ >
143
+ {childArray.slice(0, visibleCount)}
144
+ {shouldOverflow && (
145
+ <Avatar aria-label={`${overflowCount} more`}>
146
+ <AvatarFallback>+{overflowCount}</AvatarFallback>
147
+ </Avatar>
148
+ )}
149
+ </div>
150
+ </AvatarStackContext.Provider>
151
+ )
152
+ }
153
+ )
154
+ AvatarStack.displayName = "AvatarStack"
@@ -0,0 +1,27 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Badge } from "./badge"
4
+
5
+ const meta: Meta<typeof Badge> = {
6
+ title: "Components/Badge",
7
+ component: Badge,
8
+ tags: ["autodocs"],
9
+ args: {
10
+ children: "Badge",
11
+ variant: "default",
12
+ size: "base",
13
+ },
14
+ }
15
+
16
+ export default meta
17
+ type Story = StoryObj<typeof Badge>
18
+
19
+ export const Default: Story = {}
20
+
21
+ export const Secondary: Story = {
22
+ args: { variant: "secondary", children: "Secondary" },
23
+ }
24
+
25
+ export const Success: Story = {
26
+ args: { variant: "success", children: "Active" },
27
+ }
@@ -0,0 +1,63 @@
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 badgeVariants = cva(
8
+ [
9
+ "ax:inline-flex ax:items-center ax:justify-center ax:gap-1 ax:rounded-[var(--radius-badge)] ax:whitespace-nowrap",
10
+ // Reserved 1px transparent border on every variant so the `outline`
11
+ // variant's visible border doesn't make it 2px taller than the others.
12
+ "ax:border ax:border-transparent",
13
+ "ax:transition-colors",
14
+ "ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring ax:focus-visible:ring-offset-2",
15
+ ],
16
+ {
17
+ variants: {
18
+ variant: {
19
+ // Soft filled chips using the `weakest` emphasis alias: a pale tint in light
20
+ // mode that auto-inverts to a deep tint in dark, so `-foreground` reads
21
+ // correctly on it in both modes, no dark: override. `muted`/`outline`
22
+ // already use mode-aware semantic tokens.
23
+ default: "ax:bg-primary-weakest ax:text-primary-foreground ax:dark:bg-primary-strong ax:dark:text-primary-weaker",
24
+ secondary: "ax:bg-secondary-weaker ax:text-secondary-foreground ax:dark:bg-secondary-strong ax:dark:text-secondary-muted",
25
+ outline: "ax:border-border ax:text-stronger",
26
+ muted: "ax:bg-neutral-weakest ax:text-neutral-foreground",
27
+ success: "ax:bg-success-weakest ax:text-success-foreground ax:dark:bg-success-weak ax:dark:text-success-muted",
28
+ warning: "ax:bg-warning-weakest ax:text-warning-foreground ax:dark:bg-warning-weak ax:dark:text-warning-muted",
29
+ destructive: "ax:bg-destructive-weakest ax:text-destructive-foreground ax:dark:bg-destructive ax:dark:text-destructive-muted",
30
+ accent: "ax:bg-accent-weakest ax:text-accent-foreground ax:dark:bg-accent ax:dark:text-accent-muted",
31
+ },
32
+ // `min-w-*` equals each size's box height (line-height + padding + border)
33
+ // so single-character content renders as a full circle under the pill
34
+ // radius; multi-character content expands past it into a stadium.
35
+ size: {
36
+ sm: "ax:px-xs ax:py-[3px] ax:t-label-xs ax:min-w-5", /* h 20px */
37
+ base: "ax:px-xs ax:py-[4px] ax:t-label-xs ax:min-w-6", /* h 24px */
38
+ lg: "ax:px-sm ax:py-[5px] ax:t-label-sm ax:min-w-6.5", /* h 26px */
39
+ },
40
+ },
41
+ defaultVariants: {
42
+ variant: "default",
43
+ size: "base",
44
+ },
45
+ }
46
+ )
47
+
48
+ export interface BadgeProps
49
+ extends React.HTMLAttributes<HTMLSpanElement>,
50
+ VariantProps<typeof badgeVariants> {}
51
+
52
+ export const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
53
+ ({ className, variant, size, ...props }, ref) => (
54
+ <span
55
+ ref={ref}
56
+ data-slot="badge"
57
+ data-variant={variant ?? "default"}
58
+ className={cn(badgeVariants({ variant, size }), className)}
59
+ {...props}
60
+ />
61
+ )
62
+ )
63
+ Badge.displayName = "Badge"
@@ -0,0 +1,28 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { BarGraph } from "./bar-graph"
4
+
5
+ const sampleBars = [
6
+ { label: "Compliant", value: 72, color: "ax:bg-success" },
7
+ { label: "Pending", value: 18, color: "ax:bg-warning" },
8
+ { label: "Overdue", value: 10, color: "ax:bg-destructive" },
9
+ ]
10
+
11
+ const meta: Meta<typeof BarGraph> = {
12
+ title: "Components/BarGraph",
13
+ component: BarGraph,
14
+ tags: ["autodocs"],
15
+ args: {
16
+ bars: sampleBars,
17
+ size: "md",
18
+ },
19
+ }
20
+
21
+ export default meta
22
+ type Story = StoryObj<typeof BarGraph>
23
+
24
+ export const Default: Story = {}
25
+
26
+ export const Vertical: Story = {
27
+ args: { direction: "vertical" },
28
+ }