@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,163 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { AlertCircle, Bot } from "lucide-react"
5
+ import { cn } from "../lib/cn"
6
+ import { Avatar, AvatarFallback, type AvatarProps } from "./avatar"
7
+ import { Separator } from "./separator"
8
+ import { Text } from "./text"
9
+ import { TextLink } from "./text-link"
10
+
11
+ export interface ChatAvatarProps extends Omit<AvatarProps, "children"> {
12
+ /** Replaces the default bot icon, e.g. with an `AvatarImage`. */
13
+ children?: React.ReactNode
14
+ }
15
+
16
+ /** The assistant's identity mark: a small tinted avatar with a bot icon. */
17
+ export const ChatAvatar = React.forwardRef<React.ElementRef<typeof Avatar>, ChatAvatarProps>(
18
+ ({ className, size = "sm", children, ...props }, ref) => (
19
+ <Avatar ref={ref} size={size} className={cn("ax:shrink-0", className)} {...props}>
20
+ {children ?? (
21
+ <AvatarFallback className="ax:bg-primary-weaker ax:text-primary-foreground">
22
+ <Bot className="ax:size-4" aria-hidden />
23
+ </AvatarFallback>
24
+ )}
25
+ </Avatar>
26
+ ),
27
+ )
28
+ ChatAvatar.displayName = "ChatAvatar"
29
+
30
+ export interface ChatDividerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
31
+ /** Short system note, e.g. "Today" or "Conversation restarted". */
32
+ label: string
33
+ }
34
+
35
+ /** A labelled rule between turns for system notes and date breaks. */
36
+ export const ChatDivider = React.forwardRef<HTMLDivElement, ChatDividerProps>(
37
+ ({ label, className, ...props }, ref) => (
38
+ <div
39
+ ref={ref}
40
+ data-slot="chat-divider"
41
+ role="separator"
42
+ aria-label={label}
43
+ className={cn("ax:flex ax:items-center ax:gap-sm", className)}
44
+ {...props}
45
+ >
46
+ <Separator className="ax:flex-1" />
47
+ <Text size="1" color="muted">{label}</Text>
48
+ <Separator className="ax:flex-1" />
49
+ </div>
50
+ ),
51
+ )
52
+ ChatDivider.displayName = "ChatDivider"
53
+
54
+ export type ChatMessageFrom = "assistant" | "user"
55
+ export type ChatMessageStatus = "sent" | "failed"
56
+
57
+ export interface ChatMessageProps extends React.HTMLAttributes<HTMLElement> {
58
+ /** Who wrote it. Named `from` so it never clashes with the ARIA `role` attribute. */
59
+ from: ChatMessageFrom
60
+ /** Assistant only. None by default; pass `<ChatAvatar />` to show one beside the turn. */
61
+ avatar?: React.ReactNode
62
+ /** Drops the bubble so `children` lay out freely, e.g. steps, tool runs or a cited answer. */
63
+ bare?: boolean
64
+ /** User only. `failed` tints the bubble and swaps `meta` for a retry line. */
65
+ status?: ChatMessageStatus
66
+ onRetry?: () => void
67
+ /** Content under the bubble, such as listing cards or a sources line. */
68
+ attachments?: React.ReactNode
69
+ /** Assistant only. Usually `MessageActions`; sits after `meta` in the footer row. */
70
+ actions?: React.ReactNode
71
+ /** Small muted footer text, e.g. the time or "Delivered · 10:42". */
72
+ meta?: React.ReactNode
73
+ /** Extra classes for the column that holds the bubble and footer. */
74
+ bodyClassName?: string
75
+ }
76
+
77
+ function Body({ children }: { children: React.ReactNode }) {
78
+ return typeof children === "string" ? <Text as="p" size="2" wrap="pretty">{children}</Text> : <>{children}</>
79
+ }
80
+
81
+ function FailedLine({ onRetry }: { onRetry?: () => void }) {
82
+ return (
83
+ <Text as="p" size="1" color="destructive" className="ax:flex ax:items-center ax:gap-1">
84
+ <AlertCircle className="ax:size-3.5" aria-hidden />
85
+ Not delivered.
86
+ {onRetry && (
87
+ <TextLink asChild size="1" underline="always" className="ax:text-destructive-foreground">
88
+ <button type="button" onClick={onRetry}>Retry</button>
89
+ </TextLink>
90
+ )}
91
+ </Text>
92
+ )
93
+ }
94
+
95
+ /**
96
+ * ChatMessage - one turn in a conversation. Assistant turns sit in a
97
+ * primary-tinted bubble at the start with an actions and meta footer; user
98
+ * turns align to the end in a muted rounded bubble with a delivery line that
99
+ * offers retry when sending failed.
100
+ *
101
+ * @example
102
+ * <ChatMessage from="assistant" actions={<MessageActions copyText={text} />} meta="10:42">{text}</ChatMessage>
103
+ * <ChatMessage from="user" status="failed" onRetry={retry}>{text}</ChatMessage>
104
+ */
105
+ export const ChatMessage = React.forwardRef<HTMLElement, ChatMessageProps>(
106
+ ({ from, avatar, bare = false, status = "sent", onRetry, attachments, actions, meta, bodyClassName, className, children, ...props }, ref) => {
107
+ if (from === "user") {
108
+ const failed = status === "failed"
109
+ return (
110
+ <article
111
+ ref={ref}
112
+ data-slot="chat-message"
113
+ data-from="user"
114
+ aria-label="Your message"
115
+ className={cn("ax:group/message ax:flex ax:flex-col ax:items-end ax:gap-1", className)}
116
+ {...props}
117
+ >
118
+ {bare ? children : (
119
+ <div
120
+ className={cn(
121
+ "ax:max-w-[90%] ax:rounded-xl ax:px-md ax:py-xs",
122
+ failed ? "ax:bg-destructive-weakest" : "ax:bg-muted",
123
+ )}
124
+ >
125
+ <Body>{children}</Body>
126
+ </div>
127
+ )}
128
+ {attachments}
129
+ {failed ? <FailedLine onRetry={onRetry} /> : meta && <Text size="1" color="muted">{meta}</Text>}
130
+ </article>
131
+ )
132
+ }
133
+
134
+ return (
135
+ <article
136
+ ref={ref}
137
+ data-slot="chat-message"
138
+ data-from="assistant"
139
+ aria-label="Assistant message"
140
+ className={cn("ax:group/message ax:flex ax:gap-sm", className)}
141
+ {...props}
142
+ >
143
+ {avatar}
144
+ <div className={cn("ax:flex ax:min-w-0 ax:flex-1 ax:flex-col ax:gap-xs", bodyClassName)}>
145
+ {bare ? children : (
146
+ <div className="ax:self-start ax:rounded-xl ax:bg-primary-weakest ax:px-md ax:py-xs ax:max-w-[90%]">
147
+ <Body>{children}</Body>
148
+ </div>
149
+ )}
150
+ {attachments}
151
+ {(actions || meta) && (
152
+ // With the time first, it marks the bubble's edge; MessageActions drops the inset that otherwise aligns its first icon there.
153
+ <div className={cn("ax:flex ax:items-center ax:gap-sm", meta && "ax:*:data-[slot=message-actions]:ms-0")}>
154
+ {meta && <Text size="1" color="muted">{meta}</Text>}
155
+ {actions}
156
+ </div>
157
+ )}
158
+ </div>
159
+ </article>
160
+ )
161
+ },
162
+ )
163
+ ChatMessage.displayName = "ChatMessage"
@@ -0,0 +1,27 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { CheckboxCard, CheckboxCardDescription, CheckboxCardGroup, CheckboxCardTitle } from "./checkbox-card"
4
+
5
+ const meta: Meta<typeof CheckboxCardGroup> = {
6
+ title: "Components/CheckboxCard",
7
+ component: CheckboxCardGroup,
8
+ tags: ["autodocs"],
9
+ }
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof CheckboxCardGroup>
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <CheckboxCardGroup className="ax:max-w-md">
17
+ <CheckboxCard defaultChecked>
18
+ <CheckboxCardTitle>Email alerts</CheckboxCardTitle>
19
+ <CheckboxCardDescription>Receive updates about your account.</CheckboxCardDescription>
20
+ </CheckboxCard>
21
+ <CheckboxCard>
22
+ <CheckboxCardTitle>SMS alerts</CheckboxCardTitle>
23
+ <CheckboxCardDescription>Get text messages for urgent events.</CheckboxCardDescription>
24
+ </CheckboxCard>
25
+ </CheckboxCardGroup>
26
+ ),
27
+ }
@@ -0,0 +1,200 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5
+ import { Check } from "lucide-react"
6
+ import { cva, type VariantProps } from "class-variance-authority"
7
+ import { cn } from "../lib/cn"
8
+
9
+ export type CheckboxCardSize = "sm" | "base" | "lg"
10
+ export type CheckboxCardOrientation = "horizontal" | "vertical"
11
+
12
+ // ─── Context ──────────────────────────────────────────────────────────────────
13
+
14
+ interface CheckboxCardGroupContextValue {
15
+ size: CheckboxCardSize
16
+ compact: boolean
17
+ orientation: CheckboxCardOrientation
18
+ }
19
+
20
+ const CheckboxCardGroupContext = React.createContext<CheckboxCardGroupContextValue>({
21
+ size: "base",
22
+ compact: false,
23
+ orientation: "vertical",
24
+ })
25
+
26
+ // ─── Group ────────────────────────────────────────────────────────────────────
27
+
28
+ // Multi-select layout wrapper. Each contained CheckboxCard manages its own
29
+ // checked state: the group only provides shared size + layout context.
30
+ export interface CheckboxCardGroupProps extends React.HTMLAttributes<HTMLDivElement> {
31
+ size?: CheckboxCardSize
32
+ compact?: boolean
33
+ orientation?: CheckboxCardOrientation
34
+ }
35
+
36
+ export const CheckboxCardGroup = React.forwardRef<HTMLDivElement, CheckboxCardGroupProps>(
37
+ ({ className, size = "base", compact = false, orientation = "vertical", ...props }, ref) => (
38
+ <CheckboxCardGroupContext.Provider value={{ size, compact, orientation }}>
39
+ <div
40
+ ref={ref}
41
+ role="group"
42
+ className={cn(
43
+ compact
44
+ ? orientation === "horizontal" ? "ax:flex ax:flex-row" : "ax:flex ax:flex-col"
45
+ : "ax:grid ax:gap-3",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ </CheckboxCardGroupContext.Provider>
51
+ )
52
+ )
53
+ CheckboxCardGroup.displayName = "CheckboxCardGroup"
54
+
55
+ // ─── Card ─────────────────────────────────────────────────────────────────────
56
+
57
+ const checkboxCardVariants = cva(
58
+ [
59
+ "ax:relative ax:flex ax:items-start ax:cursor-pointer ax:border ax:border-border ax:bg-card",
60
+ "ax:transition-colors ax:transition-snappy",
61
+ // Selected border matches the selected title/icon colour (primary-strong /
62
+ // dark:primary) rather than the base --primary.
63
+ "ax:has-[[data-state=checked]]:border-primary",
64
+ "ax:dark:has-[[data-state=checked]]:border-primary",
65
+ // Selected background - a weak brand tint (mode-aware, auto-inverts in dark).
66
+ "ax:has-[[data-state=checked]]:bg-primary-muted",
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-neutral-weak",
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
+ // Checked-state title tint - mirrors RadioCard so both cards read the same way.
75
+ "ax:[&:has([data-state=checked])_[data-slot=checkbox-card-title]]:text-primary-foreground",
76
+ // Dark mode: primary-strong reads muddy on a dark card, so brighten the selected
77
+ // title (matches RadioCard and the bright check indicator).
78
+ "ax:dark:[&:has([data-state=checked])_[data-slot=checkbox-card-title]]:text-primary-foreground",
79
+ // Trailing check icon - three states:
80
+ // idle (unchecked, no hover) → transparent (slot still reserves space, no layout shift)
81
+ // hover on unchecked → --input (tinted to match the border at rest)
82
+ // checked → primary-strong / dark:primary, mirrors the selected title colour
83
+ "ax:[&_[data-slot=checkbox-card-check]]:text-transparent",
84
+ "ax:[&:not(:has([data-state=checked])):hover_[data-slot=checkbox-card-check]]:text-input",
85
+ "ax:[&:has([data-state=checked])_[data-slot=checkbox-card-check]]:text-primary",
86
+ "ax:dark:[&:has([data-state=checked])_[data-slot=checkbox-card-check]]:text-primary",
87
+ ],
88
+ {
89
+ variants: {
90
+ size: {
91
+ sm: "ax:p-3 ax:gap-3 ax:text-xs",
92
+ base: "ax:p-4 ax:gap-3 ax:text-sm",
93
+ lg: "ax:p-5 ax:gap-4 ax:text-base",
94
+ },
95
+ },
96
+ defaultVariants: { size: "base" },
97
+ }
98
+ )
99
+
100
+ const COMPACT_LAYOUT: Record<CheckboxCardOrientation, string> = {
101
+ horizontal: [
102
+ "ax:rounded-none",
103
+ "ax:first:rounded-l-(--input-radius) ax:last:rounded-r-(--input-radius)",
104
+ "ax:[&:not(:first-child)]:-ml-px",
105
+ "ax:has-[[data-state=checked]]:z-10",
106
+ ].join(" "),
107
+ vertical: [
108
+ "ax:rounded-none",
109
+ "ax:first:rounded-t-(--input-radius) ax:last:rounded-b-(--input-radius)",
110
+ "ax:[&:not(:first-child)]:-mt-px",
111
+ "ax:has-[[data-state=checked]]:z-10",
112
+ ].join(" "),
113
+ }
114
+
115
+ const CHECK_SIZE: Record<CheckboxCardSize, string> = {
116
+ sm: "ax:size-3.5",
117
+ base: "ax:size-4",
118
+ lg: "ax:size-4",
119
+ }
120
+
121
+ export interface CheckboxCardProps
122
+ extends VariantProps<typeof checkboxCardVariants>,
123
+ Pick<
124
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>,
125
+ "checked" | "defaultChecked" | "onCheckedChange" | "disabled" | "name" | "value" | "required"
126
+ > {
127
+ className?: string
128
+ children?: React.ReactNode
129
+ }
130
+
131
+ export const CheckboxCard = React.forwardRef<
132
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
133
+ CheckboxCardProps
134
+ >(
135
+ (
136
+ { className, size: sizeProp, children, checked, defaultChecked, onCheckedChange, disabled, name, value, required },
137
+ ref
138
+ ) => {
139
+ const { size: ctxSize, compact, orientation } = React.useContext(CheckboxCardGroupContext)
140
+ const size = sizeProp ?? ctxSize
141
+ const id = React.useId()
142
+
143
+ return (
144
+ <label
145
+ htmlFor={id}
146
+ className={cn(
147
+ checkboxCardVariants({ size }),
148
+ compact ? COMPACT_LAYOUT[orientation] : "ax:rounded-(--input-radius)",
149
+ className
150
+ )}
151
+ >
152
+ <div className="ax:flex-1 ax:min-w-0">{children}</div>
153
+ <Check
154
+ data-slot="checkbox-card-check"
155
+ aria-hidden="true"
156
+ className={cn(
157
+ "ax:shrink-0 ax:mt-0.5 ax:transition-colors ax:transition-snappy",
158
+ CHECK_SIZE[size]
159
+ )}
160
+ />
161
+ <CheckboxPrimitive.Root
162
+ ref={ref}
163
+ id={id}
164
+ checked={checked}
165
+ defaultChecked={defaultChecked}
166
+ onCheckedChange={onCheckedChange}
167
+ disabled={disabled}
168
+ name={name}
169
+ value={value}
170
+ required={required}
171
+ className="ax:sr-only"
172
+ />
173
+ </label>
174
+ )
175
+ }
176
+ )
177
+ CheckboxCard.displayName = "CheckboxCard"
178
+
179
+ // ─── Sub-components ───────────────────────────────────────────────────────────
180
+
181
+ export const CheckboxCardTitle = React.forwardRef<
182
+ HTMLParagraphElement,
183
+ React.HTMLAttributes<HTMLParagraphElement>
184
+ >(({ className, ...props }, ref) => (
185
+ <p
186
+ ref={ref}
187
+ data-slot="checkbox-card-title"
188
+ className={cn("ax:t-label-sm ax:text-foreground", className)}
189
+ {...props}
190
+ />
191
+ ))
192
+ CheckboxCardTitle.displayName = "CheckboxCardTitle"
193
+
194
+ export const CheckboxCardDescription = React.forwardRef<
195
+ HTMLParagraphElement,
196
+ React.HTMLAttributes<HTMLParagraphElement>
197
+ >(({ className, ...props }, ref) => (
198
+ <p ref={ref} className={cn("ax:text-[0.875em] ax:text-muted-foreground ax:mt-1", className)} {...props} />
199
+ ))
200
+ CheckboxCardDescription.displayName = "CheckboxCardDescription"
@@ -0,0 +1,26 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Checkbox } from "./checkbox"
4
+ import { Label } from "./input"
5
+
6
+ const meta: Meta<typeof Checkbox> = {
7
+ title: "Components/Checkbox",
8
+ component: Checkbox,
9
+ tags: ["autodocs"],
10
+ }
11
+
12
+ export default meta
13
+ type Story = StoryObj<typeof Checkbox>
14
+
15
+ export const Default: Story = {
16
+ render: () => (
17
+ <div className="ax:flex ax:items-center ax:gap-sm">
18
+ <Checkbox id="terms" />
19
+ <Label htmlFor="terms">Accept terms and conditions</Label>
20
+ </div>
21
+ ),
22
+ }
23
+
24
+ export const Checked: Story = {
25
+ render: () => <Checkbox defaultChecked />,
26
+ }
@@ -0,0 +1,61 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5
+ import { Check, Minus } from "lucide-react"
6
+ import { cva, type VariantProps } from "class-variance-authority"
7
+ import { cn } from "../lib/cn"
8
+
9
+ const checkboxVariants = cva(
10
+ [
11
+ "ax:peer ax:shrink-0 ax:rounded-xs ax:border ax:border-input",
12
+ "ax:transition-colors ax:transition-snappy",
13
+ "ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring ax:focus-visible:ring-offset-2 ax:focus-visible:ring-offset-background",
14
+ "ax:cursor-pointer ax:disabled:cursor-not-allowed ax:disabled:opacity-50",
15
+ // Selected: primary-weak fill + primary-strong glyph, matches RadioGroup. The
16
+ // fill auto-inverts to a light-ish tint in dark, so the glyph flips to a dark
17
+ // `primary-muted` there (dark: override) to keep contrast on that lighter fill.
18
+ "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",
19
+ "ax:data-[state=indeterminate]:bg-primary-weak ax:data-[state=indeterminate]:border-primary-weak ax:data-[state=indeterminate]:text-primary-strong ax:dark:data-[state=indeterminate]:text-primary-muted",
20
+ ],
21
+ {
22
+ variants: {
23
+ size: {
24
+ sm: "ax:size-3",
25
+ base: "ax:size-4",
26
+ lg: "ax:size-5",
27
+ },
28
+ },
29
+ defaultVariants: { size: "base" },
30
+ }
31
+ )
32
+
33
+ const ICON_SIZE: Record<NonNullable<VariantProps<typeof checkboxVariants>["size"]>, string> = {
34
+ sm: "ax:size-2",
35
+ base: "ax:size-3",
36
+ lg: "ax:size-4",
37
+ }
38
+
39
+ export interface CheckboxProps
40
+ extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>,
41
+ VariantProps<typeof checkboxVariants> {}
42
+
43
+ export const Checkbox = React.forwardRef<
44
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
45
+ CheckboxProps
46
+ >(({ className, size, ...props }, ref) => (
47
+ <CheckboxPrimitive.Root
48
+ ref={ref}
49
+ className={cn(checkboxVariants({ size }), className)}
50
+ {...props}
51
+ >
52
+ <CheckboxPrimitive.Indicator className="ax:flex ax:items-center ax:justify-center ax:text-current">
53
+ {props.checked === "indeterminate" ? (
54
+ <Minus className={ICON_SIZE[size ?? "base"]} strokeWidth={2} />
55
+ ) : (
56
+ <Check className={ICON_SIZE[size ?? "base"]} strokeWidth={4} />
57
+ )}
58
+ </CheckboxPrimitive.Indicator>
59
+ </CheckboxPrimitive.Root>
60
+ ))
61
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
@@ -0,0 +1,64 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Home, LineChart, Newspaper } from "lucide-react"
4
+ import { CitationMarker, SourceList, UnsupportedClaim, type CitationSource } from "./citations"
5
+ import { Text } from "./text"
6
+ import { TooltipProvider } from "./tooltip"
7
+
8
+ const SOURCES: CitationSource[] = [
9
+ {
10
+ id: 1,
11
+ title: "Saadiyat Lagoons, Apt 1316",
12
+ publisher: "Adres listings",
13
+ date: "2026-09-28",
14
+ url: "#",
15
+ excerpt: "Asking AED 4,162,000 for 1,734 sqft, three bedrooms with a lagoon view.",
16
+ kind: { label: "Listing", icon: Home },
17
+ },
18
+ {
19
+ id: 2,
20
+ title: "Saadiyat Lagoons registered sales, Q3 2026",
21
+ publisher: "DLD",
22
+ date: "2026-09-30",
23
+ url: "#",
24
+ excerpt: "Median AED 2,550 per sqft across 42 registered sales.",
25
+ kind: { label: "Registered sales", icon: LineChart },
26
+ },
27
+ {
28
+ id: 3,
29
+ title: "Saadiyat schools guide",
30
+ publisher: "The National",
31
+ date: "2025-11-02",
32
+ url: "#",
33
+ excerpt: "Two international schools sit within a ten-minute drive.",
34
+ kind: { label: "Article", icon: Newspaper },
35
+ },
36
+ ]
37
+
38
+ const meta: Meta<typeof SourceList> = {
39
+ title: "Components/Citations",
40
+ component: SourceList,
41
+ tags: ["autodocs"],
42
+ decorators: [(Story) => <TooltipProvider><div style={{ maxWidth: 640 }}><Story /></div></TooltipProvider>],
43
+ args: { sources: SOURCES, asOf: "2026-10-06" },
44
+ }
45
+
46
+ export default meta
47
+ type Story = StoryObj<typeof SourceList>
48
+
49
+ export const Sources: Story = {}
50
+
51
+ export const Collapsed: Story = { args: { initial: 2, onExpand: () => undefined } }
52
+
53
+ export const Highlighted: Story = { args: { highlighted: 2 } }
54
+
55
+ export const CitedAnswer: Story = {
56
+ render: () => (
57
+ <Text as="p" size="2" wrap="pretty">
58
+ Apt 1316 is listed at about AED 2,400 per sqft
59
+ <CitationMarker source={SOURCES[0]!} />, below the area median of AED 2,550
60
+ <CitationMarker source={SOURCES[1]!} />.{" "}
61
+ <UnsupportedClaim>Prices here are likely to rise next year.</UnsupportedClaim>
62
+ </Text>
63
+ ),
64
+ }
@@ -0,0 +1,61 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import * as React from "react"
3
+ import { renderToStaticMarkup } from "react-dom/server"
4
+ import { CitationMarker, SourceCard, SourceList, UnsupportedClaim, formatSourceDate, isStale, type CitationSource } from "./citations"
5
+ import { TooltipProvider } from "./tooltip"
6
+
7
+ const source = (id: number, date = "2026-09-28"): CitationSource => ({
8
+ id,
9
+ title: `Source ${id} title`,
10
+ publisher: "Adres listings",
11
+ date,
12
+ url: "https://example.com",
13
+ excerpt: "The passage used.",
14
+ })
15
+
16
+ describe("isStale", () => {
17
+ it("is false up to the limit and true after it", () => {
18
+ expect(isStale("2026-04-06", "2026-10-06")).toBe(false)
19
+ expect(isStale("2026-03-01", "2026-10-06")).toBe(true)
20
+ })
21
+
22
+ it("takes a custom limit in days", () => {
23
+ expect(isStale("2026-09-01", "2026-10-06", 30)).toBe(true)
24
+ })
25
+ })
26
+
27
+ describe("formatSourceDate", () => {
28
+ it("formats in UTC as day, short month, year", () => {
29
+ expect(formatSourceDate("2026-10-06")).toBe("6 Oct 2026")
30
+ })
31
+ })
32
+
33
+ describe("SourceCard", () => {
34
+ it("marks old sources only when asOf is given", () => {
35
+ expect(renderToStaticMarkup(<SourceCard source={source(1, "2025-01-01")} asOf="2026-10-06" />)).toContain("Older than 6 months")
36
+ expect(renderToStaticMarkup(<SourceCard source={source(1, "2025-01-01")} />)).not.toContain("Older than 6 months")
37
+ })
38
+ })
39
+
40
+ describe("SourceList", () => {
41
+ it("shows the first sources and offers the rest", () => {
42
+ const html = renderToStaticMarkup(
43
+ <SourceList sources={[source(1), source(2), source(3)]} initial={2} onExpand={() => undefined} getSourceId={(id) => `s-${id}`} />,
44
+ )
45
+ expect(html).toContain('id="s-1"')
46
+ expect(html).not.toContain('id="s-3"')
47
+ expect(html).toContain("Show 1 more source")
48
+ })
49
+ })
50
+
51
+ describe("CitationMarker and UnsupportedClaim", () => {
52
+ it("names the marker by number and title", () => {
53
+ expect(renderToStaticMarkup(<CitationMarker source={source(2)} />)).toContain('aria-label="Source 2: Source 2 title"')
54
+ })
55
+
56
+ it("gives the unsupported mark a reachable button", () => {
57
+ const html = renderToStaticMarkup(<TooltipProvider><UnsupportedClaim>Prices will rise.</UnsupportedClaim></TooltipProvider>)
58
+ expect(html).toContain('aria-label="Not backed by a source"')
59
+ expect(html).toContain("Prices will rise.")
60
+ })
61
+ })