@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,105 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "../lib/cn"
4
+
5
+ // Numeric `size` is a convenience API over the `t-*` scale defined in typography.css.
6
+
7
+ // ─── Types ────────────────────────────────────────────────────────────────────
8
+
9
+ export type TextAs = "span" | "p" | "div" | "label" | "strong" | "em"
10
+ export type TextSize = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
11
+ export type TextWeight = "light" | "regular" | "medium" | "bold"
12
+ export type TextAlign = "left" | "center" | "right"
13
+ export type TextColor = "default" | "muted" | "strong" | "primary" | "accent" | "success" | "warning" | "destructive"
14
+ export type TextWrap = "wrap" | "nowrap" | "balance" | "pretty"
15
+
16
+ // ─── Variants ─────────────────────────────────────────────────────────────────
17
+
18
+ const textVariants = cva("", {
19
+ variants: {
20
+ size: {
21
+ "1": "ax:t-body-xs",
22
+ "2": "ax:t-body-sm",
23
+ "3": "ax:t-body-md",
24
+ "4": "ax:t-body-lg",
25
+ "5": "ax:t-body-xl",
26
+ "6": "ax:t-h1",
27
+ "7": "ax:t-display-sm",
28
+ "8": "ax:t-display-md",
29
+ "9": "ax:t-display-xl",
30
+ },
31
+ weight: {
32
+ light: "ax:font-light",
33
+ regular: "ax:font-normal",
34
+ medium: "ax:font-medium",
35
+ bold: "ax:font-bold",
36
+ },
37
+ align: {
38
+ left: "ax:text-left",
39
+ center: "ax:text-center",
40
+ right: "ax:text-right",
41
+ },
42
+ color: {
43
+ default: "ax:text-foreground",
44
+ muted: "ax:text-muted-foreground",
45
+ strong: "ax:text-foreground-strong",
46
+ primary: "ax:text-primary-foreground",
47
+ accent: "ax:text-accent-foreground",
48
+ success: "ax:text-success-foreground",
49
+ warning: "ax:text-warning-foreground",
50
+ destructive: "ax:text-destructive-foreground",
51
+ },
52
+ },
53
+ defaultVariants: {
54
+ size: "3",
55
+ weight: "regular",
56
+ color: "default",
57
+ },
58
+ })
59
+
60
+ const WRAP_CLASS: Record<TextWrap, string> = {
61
+ wrap: "ax:text-wrap",
62
+ nowrap: "ax:text-nowrap",
63
+ balance: "ax:text-balance",
64
+ pretty: "ax:text-pretty",
65
+ }
66
+
67
+ // ─── Component ────────────────────────────────────────────────────────────────
68
+
69
+ export interface TextProps
70
+ extends Omit<React.HTMLAttributes<HTMLElement>, "color">,
71
+ VariantProps<typeof textVariants> {
72
+ as?: TextAs
73
+ truncate?: boolean
74
+ wrap?: TextWrap
75
+ }
76
+
77
+ export const Text = React.forwardRef<HTMLElement, TextProps>(
78
+ (
79
+ {
80
+ as: Tag = "span",
81
+ className,
82
+ size,
83
+ weight,
84
+ align,
85
+ color,
86
+ truncate,
87
+ wrap,
88
+ ...props
89
+ },
90
+ ref
91
+ ) => (
92
+ <Tag
93
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
+ ref={ref as any}
95
+ className={cn(
96
+ textVariants({ size, weight, align, color }),
97
+ truncate && "ax:truncate",
98
+ wrap && WRAP_CLASS[wrap],
99
+ className
100
+ )}
101
+ {...props}
102
+ />
103
+ )
104
+ )
105
+ Text.displayName = "Text"
@@ -0,0 +1,19 @@
1
+ import type { Meta, StoryObj } from "@storybook/react"
2
+ import { ThinkingBar } from "./thinking-bar"
3
+
4
+ const meta: Meta<typeof ThinkingBar> = {
5
+ title: "Components/ThinkingBar",
6
+ component: ThinkingBar,
7
+ tags: ["autodocs"],
8
+ decorators: [(Story) => <div style={{ maxWidth: 560 }}><Story /></div>],
9
+ args: { text: "Thinking" },
10
+ }
11
+
12
+ export default meta
13
+ type Story = StoryObj<typeof ThinkingBar>
14
+
15
+ export const Default: Story = {}
16
+
17
+ export const WithActions: Story = {
18
+ args: { text: "Comparing registered sales", onClick: () => undefined, onStop: () => undefined },
19
+ }
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import * as React from "react"
3
+ import { renderToStaticMarkup } from "react-dom/server"
4
+ import { ThinkingBar } from "./thinking-bar"
5
+
6
+ const buttons = (html: string) => html.match(/<button/g)?.length ?? 0
7
+
8
+ describe("ThinkingBar", () => {
9
+ it("shows a shimmering Thinking status with no buttons by default", () => {
10
+ const html = renderToStaticMarkup(<ThinkingBar />)
11
+ expect(html).toContain('data-slot="shimmer-text"')
12
+ expect(html).toContain(">Thinking<")
13
+ expect(buttons(html)).toBe(0)
14
+ })
15
+
16
+ it("turns the label into a button with a chevron when onClick is passed", () => {
17
+ const html = renderToStaticMarkup(<ThinkingBar text="Searching listings" onClick={() => undefined} />)
18
+ expect(buttons(html)).toBe(1)
19
+ expect(html).toContain("lucide-chevron-right")
20
+ expect(html).toContain(">Searching listings<")
21
+ })
22
+
23
+ it("adds the stop action with its label when onStop is passed", () => {
24
+ expect(renderToStaticMarkup(<ThinkingBar onStop={() => undefined} />)).toContain(">Answer now</button>")
25
+ expect(renderToStaticMarkup(<ThinkingBar onStop={() => undefined} stopLabel="Skip" />)).toContain(">Skip</button>")
26
+ })
27
+ })
@@ -0,0 +1,64 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ChevronRight } from "lucide-react"
5
+ import { cn } from "../lib/cn"
6
+ import { ShimmerText } from "./shimmer-text"
7
+
8
+ export interface ThinkingBarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onClick"> {
9
+ /** The status, present tense. */
10
+ text?: string
11
+ /** Makes the label a button with a chevron, e.g. to open the reasoning. */
12
+ onClick?: () => void
13
+ /** Adds a stop action on the right, e.g. to answer without more thinking. */
14
+ onStop?: () => void
15
+ stopLabel?: string
16
+ }
17
+
18
+ /**
19
+ * ThinkingBar - one line that says the assistant is thinking: a shimmering
20
+ * status, optionally a button to see more, and an optional action to stop
21
+ * and answer now.
22
+ *
23
+ * @example
24
+ * <ThinkingBar text="Thinking" onClick={openReasoning} onStop={answerNow} />
25
+ */
26
+ export const ThinkingBar = React.forwardRef<HTMLDivElement, ThinkingBarProps>(
27
+ ({ text = "Thinking", onClick, onStop, stopLabel = "Answer now", className, ...props }, ref) => (
28
+ <div
29
+ ref={ref}
30
+ data-slot="thinking-bar"
31
+ className={cn("ax:flex ax:w-full ax:items-center ax:justify-between ax:gap-md ax:t-body-sm", className)}
32
+ {...props}
33
+ >
34
+ {onClick ? (
35
+ <button
36
+ type="button"
37
+ onClick={onClick}
38
+ className="ax:group/thinking ax:flex ax:items-center ax:gap-1 ax:rounded-xs ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring"
39
+ >
40
+ <ShimmerText className="ax:font-medium">{text}</ShimmerText>
41
+ <ChevronRight
42
+ className="ax:size-4 ax:text-muted-foreground ax:transition-colors ax:group-hover/thinking:text-foreground"
43
+ aria-hidden
44
+ />
45
+ </button>
46
+ ) : (
47
+ <ShimmerText className="ax:font-medium">{text}</ShimmerText>
48
+ )}
49
+ {onStop && (
50
+ <button
51
+ type="button"
52
+ onClick={onStop}
53
+ className={cn(
54
+ "ax:shrink-0 ax:rounded-xs ax:text-muted-foreground ax:underline ax:decoration-dotted ax:underline-offset-4 ax:transition-colors",
55
+ "ax:hover:text-foreground ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring",
56
+ )}
57
+ >
58
+ {stopLabel}
59
+ </button>
60
+ )}
61
+ </div>
62
+ ),
63
+ )
64
+ ThinkingBar.displayName = "ThinkingBar"
@@ -0,0 +1,44 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { StreamingCursor, ThinkingSteps } from "./thinking-steps"
4
+ import { Text } from "./text"
5
+
6
+ const meta: Meta<typeof ThinkingSteps> = {
7
+ title: "Components/ThinkingSteps",
8
+ component: ThinkingSteps,
9
+ tags: ["autodocs"],
10
+ decorators: [(Story) => <div style={{ maxWidth: 560 }}><Story /></div>],
11
+ args: {
12
+ running: true,
13
+ total: 3,
14
+ steps: [
15
+ { label: "Searched 128 listings on Yas Island", status: "done" },
16
+ { label: "Comparing registered sales", status: "running" },
17
+ ],
18
+ },
19
+ }
20
+
21
+ export default meta
22
+ type Story = StoryObj<typeof ThinkingSteps>
23
+
24
+ export const Running: Story = {}
25
+
26
+ export const Done: Story = {
27
+ args: {
28
+ running: false,
29
+ steps: [
30
+ { label: "Searched 128 listings on Yas Island", status: "done" },
31
+ { label: "Compared 42 registered sales", status: "done" },
32
+ { label: "Checked developer handover dates", status: "done" },
33
+ ],
34
+ },
35
+ }
36
+
37
+ export const Cursor: Story = {
38
+ render: () => (
39
+ <Text as="p" size="2">
40
+ Yas Acres townhouses with a private garden start at
41
+ <StreamingCursor />
42
+ </Text>
43
+ ),
44
+ }
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import * as React from "react"
3
+ import { renderToStaticMarkup } from "react-dom/server"
4
+ import { StreamingCursor, ThinkingSteps, thinkingStepsSummary } from "./thinking-steps"
5
+
6
+ describe("thinkingStepsSummary", () => {
7
+ it("names the step in progress out of the planned total while running", () => {
8
+ expect(thinkingStepsSummary({ running: true, done: 1, total: 3 })).toBe("Working, step 2 of 3")
9
+ })
10
+
11
+ it("never counts past the total", () => {
12
+ expect(thinkingStepsSummary({ running: true, done: 3, total: 3 })).toBe("Working, step 3 of 3")
13
+ })
14
+
15
+ it("counts steps used once finished, singular for one", () => {
16
+ expect(thinkingStepsSummary({ running: false, done: 3, total: 3 })).toBe("Used 3 steps")
17
+ expect(thinkingStepsSummary({ running: false, done: 1, total: 1 })).toBe("Used 1 step")
18
+ })
19
+ })
20
+
21
+ describe("ThinkingSteps", () => {
22
+ it("opens the step list while running", () => {
23
+ const html = renderToStaticMarkup(
24
+ <ThinkingSteps running total={2} steps={[{ label: "Searched listings", status: "done" }, { label: "Comparing sales", status: "running" }]} />,
25
+ )
26
+ expect(html).toContain("Working, step 2 of 2")
27
+ expect(html).toContain('aria-expanded="true"')
28
+ expect(html).toContain("Comparing sales")
29
+ })
30
+
31
+ it("folds the list away once done", () => {
32
+ const html = renderToStaticMarkup(<ThinkingSteps running={false} steps={[{ label: "Searched listings", status: "done" }]} />)
33
+ expect(html).toContain("Used 1 step")
34
+ expect(html).toContain('aria-expanded="false"')
35
+ })
36
+ })
37
+
38
+ describe("StreamingCursor", () => {
39
+ it("is hidden from assistive tech", () => {
40
+ expect(renderToStaticMarkup(<StreamingCursor />)).toContain('aria-hidden="true"')
41
+ })
42
+ })
@@ -0,0 +1,92 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ChevronDown } from "lucide-react"
5
+ import { cn } from "../lib/cn"
6
+ import { ShimmerText } from "./shimmer-text"
7
+
8
+ export type ThinkingStepStatus = "done" | "running"
9
+
10
+ export interface ThinkingStep {
11
+ /** Past tense once done ("Searched 12 listings"), present while running ("Searching listings"). */
12
+ label: string
13
+ status: ThinkingStepStatus
14
+ }
15
+
16
+ /** "Working, step 2 of 4" while running; "Used 4 steps" once finished. */
17
+ export function thinkingStepsSummary({ running, done, total }: { running: boolean; done: number; total: number }) {
18
+ if (running) return `Working, step ${Math.min(done + 1, total)} of ${total}`
19
+ return `Used ${done} ${done === 1 ? "step" : "steps"}`
20
+ }
21
+
22
+ export interface ThinkingStepsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "dir"> {
23
+ /** Steps to show so far, in order. Leave out steps that have not started. */
24
+ steps: ThinkingStep[]
25
+ /** True while the assistant is still working; the list opens and the summary shimmers. */
26
+ running: boolean
27
+ /** Steps planned in all, for "step 2 of 4". Defaults to `steps.length`. */
28
+ total?: number
29
+ }
30
+
31
+ /**
32
+ * ThinkingSteps - what the assistant is doing before and while it answers: a
33
+ * muted one-line summary that toggles the step list, which hangs off a thin
34
+ * rule. Open while running, folded away once done.
35
+ *
36
+ * @example
37
+ * <ThinkingSteps running={working} total={3} steps={[{ label: "Searched listings", status: "done" }]} />
38
+ */
39
+ export const ThinkingSteps = React.forwardRef<HTMLDivElement, ThinkingStepsProps>(
40
+ ({ steps, running, total = steps.length, className, ...props }, ref) => {
41
+ const id = React.useId()
42
+ const [open, setOpen] = React.useState(running)
43
+ React.useEffect(() => setOpen(running), [running])
44
+ const done = steps.filter((step) => step.status === "done").length
45
+ const summary = thinkingStepsSummary({ running, done, total })
46
+
47
+ return (
48
+ <div ref={ref} data-slot="thinking-steps" className={cn("ax:text-sm", className)} {...props}>
49
+ <button
50
+ type="button"
51
+ aria-expanded={open}
52
+ aria-controls={`${id}-steps`}
53
+ onClick={() => setOpen((value) => !value)}
54
+ className="ax:flex ax:items-center ax:gap-1 ax:rounded-xs ax:text-muted-foreground ax:transition-colors ax:hover:text-foreground ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring"
55
+ >
56
+ {running ? <ShimmerText className="ax:font-medium">{summary}</ShimmerText> : <span>{summary}</span>}
57
+ <ChevronDown className={cn("ax:size-4 ax:transition-transform ax:motion-reduce:transition-none", open && "ax:rotate-180")} aria-hidden />
58
+ </button>
59
+ {open && (
60
+ <div id={`${id}-steps`} className="ax:mt-sm ax:grid ax:grid-cols-[min-content_minmax(0,1fr)] ax:gap-x-sm">
61
+ <div className="ax:w-0.5 ax:self-stretch ax:rounded-full ax:bg-muted" aria-hidden />
62
+ <ol className="ax:flex ax:min-w-0 ax:flex-col ax:gap-xs">
63
+ {steps.map((step, index) => (
64
+ <li key={index} className={step.status === "done" ? "ax:text-muted-foreground" : undefined}>
65
+ {step.status === "running" ? <ShimmerText>{step.label}</ShimmerText> : step.label}
66
+ </li>
67
+ ))}
68
+ </ol>
69
+ </div>
70
+ )}
71
+ </div>
72
+ )
73
+ },
74
+ )
75
+ ThinkingSteps.displayName = "ThinkingSteps"
76
+
77
+ /** The blinking block caret at the end of text that is still being written. Decorative. */
78
+ export const StreamingCursor = React.forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(
79
+ ({ className, ...props }, ref) => (
80
+ <span
81
+ ref={ref}
82
+ data-slot="streaming-cursor"
83
+ aria-hidden
84
+ className={cn(
85
+ "ax:ms-0.5 ax:inline-block ax:h-4 ax:w-1.5 ax:translate-y-0.5 ax:animate-pulse ax:rounded-xs ax:bg-foreground ax:motion-reduce:animate-none",
86
+ className,
87
+ )}
88
+ {...props}
89
+ />
90
+ ),
91
+ )
92
+ StreamingCursor.displayName = "StreamingCursor"
@@ -0,0 +1,80 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Button } from "./button"
4
+ import { toast } from "./toaster"
5
+
6
+ const meta = {
7
+ title: "Components/Toaster",
8
+ tags: ["autodocs"],
9
+ parameters: {
10
+ layout: "centered",
11
+ docs: {
12
+ description: {
13
+ component:
14
+ "Application toast host (Sonner). A single Toaster is mounted in Storybook preview, same as placing it once near your app root. Call `toast()` from click handlers or other events; never call it during render.",
15
+ },
16
+ },
17
+ },
18
+ } satisfies Meta
19
+
20
+ export default meta
21
+ type Story = StoryObj
22
+
23
+ export const Default: Story = {
24
+ render: () => (
25
+ <div className="ax:space-y-sm">
26
+ <p className="ax:t-body-sm ax:text-muted-foreground ax:max-w-sm">
27
+ Click the button to fire a toast. The host is mounted globally in Storybook preview.
28
+ </p>
29
+ <Button onClick={() => toast("Changes saved")}>Show toast</Button>
30
+ </div>
31
+ ),
32
+ }
33
+
34
+ export const WithDescription: Story = {
35
+ render: () => (
36
+ <Button
37
+ onClick={() =>
38
+ toast("Invite sent", {
39
+ description: "We emailed a sign-in link to your teammate.",
40
+ })
41
+ }
42
+ >
43
+ Show toast with description
44
+ </Button>
45
+ ),
46
+ }
47
+
48
+ export const Success: Story = {
49
+ render: () => (
50
+ <Button variant="secondary" onClick={() => toast.success("Payment received")}>
51
+ Success toast
52
+ </Button>
53
+ ),
54
+ }
55
+
56
+ export const Error: Story = {
57
+ render: () => (
58
+ <Button variant="destructive" onClick={() => toast.error("Could not save changes")}>
59
+ Error toast
60
+ </Button>
61
+ ),
62
+ }
63
+
64
+ export const Action: Story = {
65
+ render: () => (
66
+ <Button
67
+ variant="outline"
68
+ onClick={() =>
69
+ toast("Draft deleted", {
70
+ action: {
71
+ label: "Undo",
72
+ onClick: () => toast("Draft restored"),
73
+ },
74
+ })
75
+ }
76
+ >
77
+ Toast with undo action
78
+ </Button>
79
+ ),
80
+ }
@@ -0,0 +1,36 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Toaster as SonnerToaster, toast } from "sonner"
5
+
6
+ /**
7
+ * Toaster - application-level toast host.
8
+ *
9
+ * Uses sonner under the hood. Mount once near the root of every app.
10
+ * Re-export `toast()` so consumers can call it without a second dependency.
11
+ */
12
+ export function Toaster() {
13
+ return (
14
+ <SonnerToaster
15
+ position="bottom-right"
16
+ richColors={false}
17
+ closeButton
18
+ toastOptions={{
19
+ unstyled: false,
20
+ classNames: {
21
+ toast:
22
+ "ax:group ax:bg-card ax:text-card-foreground ax:border ax:border-border ax:rounded-md ax:shadow-md",
23
+ title: "ax:t-label-sm ax:text-foreground",
24
+ description: "ax:t-caption ax:text-muted-foreground",
25
+ actionButton:
26
+ "ax:bg-primary ax:text-primary-foreground ax:rounded-md ax:px-2.5 ax:py-1 ax:t-label-sm",
27
+ cancelButton:
28
+ "ax:bg-muted ax:text-muted-foreground ax:rounded-md ax:px-2.5 ax:py-1 ax:t-label-sm",
29
+ closeButton: "ax:bg-muted ax:text-muted-foreground ax:border ax:border-border",
30
+ },
31
+ }}
32
+ />
33
+ )
34
+ }
35
+
36
+ export { toast }
@@ -0,0 +1,21 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Bold } from "lucide-react"
4
+ import { Toggle } from "./toggle"
5
+
6
+ const meta: Meta<typeof Toggle> = {
7
+ title: "Components/Toggle",
8
+ component: Toggle,
9
+ tags: ["autodocs"],
10
+ }
11
+
12
+ export default meta
13
+ type Story = StoryObj<typeof Toggle>
14
+
15
+ export const Default: Story = {
16
+ render: () => (
17
+ <Toggle aria-label="Toggle bold">
18
+ <Bold className="ax:size-4" />
19
+ </Toggle>
20
+ ),
21
+ }
@@ -0,0 +1,103 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as TogglePrimitive from "@radix-ui/react-switch"
5
+ import { cn } from "../lib/cn"
6
+
7
+ export type ToggleSize = "sm" | "md" | "lg"
8
+
9
+ // Track / thumb / travel scale together. Travel = track inner width − thumb:
10
+ // sm 28−4(borders)−12 = 12 (translate-x-3)
11
+ // md 36−4−16 = 16 (translate-x-4)
12
+ // lg 44−4−20 = 20 (translate-x-5)
13
+ const TRACK: Record<ToggleSize, string> = {
14
+ sm: "ax:h-4 ax:w-7", // 16 × 28
15
+ md: "ax:h-5 ax:w-9", // 20 × 36
16
+ lg: "ax:h-6 ax:w-11", // 24 × 44
17
+ }
18
+ const THUMB: Record<ToggleSize, string> = {
19
+ sm: "ax:size-3", // 12
20
+ md: "ax:size-4", // 16
21
+ lg: "ax:size-5", // 20
22
+ }
23
+ const THUMB_TRANSLATE: Record<ToggleSize, string> = {
24
+ sm: "ax:data-[state=checked]:translate-x-3",
25
+ md: "ax:data-[state=checked]:translate-x-4",
26
+ lg: "ax:data-[state=checked]:translate-x-5",
27
+ }
28
+
29
+ export interface ToggleProps
30
+ extends React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> {
31
+ size?: ToggleSize
32
+ /**
33
+ * Optional caption rendered to the inline-start (left) of the switch. When set,
34
+ * the switch and text are wrapped in a <label> so the text is clickable and
35
+ * names the control - no separate `aria-label` needed.
36
+ */
37
+ label?: React.ReactNode
38
+ /**
39
+ * Classes for the wrapping <label> (only used when `label` is set). `className`
40
+ * still targets the switch track itself.
41
+ */
42
+ labelClassName?: string
43
+ }
44
+
45
+ /**
46
+ * A binary on/off toggle (built on Radix Switch).
47
+ *
48
+ * Pass `label` for a captioned control: the text renders to the LEFT of the
49
+ * switch and toggles it on click. Omit `label` to render the bare switch and
50
+ * compose your own layout (e.g. a spaced settings row with `justify-between`).
51
+ */
52
+ export const Toggle = React.forwardRef<
53
+ React.ElementRef<typeof TogglePrimitive.Root>,
54
+ ToggleProps
55
+ >(({ className, size = "md", label, labelClassName, ...props }, ref) => {
56
+ const control = (
57
+ <TogglePrimitive.Root
58
+ ref={ref}
59
+ className={cn(
60
+ "ax:peer ax:inline-flex ax:shrink-0 ax:cursor-pointer ax:items-center ax:rounded-full ax:border-2 ax:border-transparent",
61
+ "ax:transition-colors ax:transition-snappy",
62
+ "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",
63
+ "ax:disabled:cursor-not-allowed ax:disabled:opacity-50",
64
+ "ax:data-[state=checked]:bg-primary ax:data-[state=unchecked]:bg-input",
65
+ TRACK[size],
66
+ className
67
+ )}
68
+ {...props}
69
+ >
70
+ <TogglePrimitive.Thumb
71
+ className={cn(
72
+ "ax:pointer-events-none ax:block ax:rounded-full ax:bg-card ax:shadow-sm",
73
+ "ax:transition-transform ax:transition-snappy",
74
+ "ax:data-[state=unchecked]:translate-x-0",
75
+ THUMB[size],
76
+ THUMB_TRANSLATE[size]
77
+ )}
78
+ />
79
+ </TogglePrimitive.Root>
80
+ )
81
+
82
+ if (label == null) return control
83
+
84
+ // Label on the inline-start (left). The switch dims itself when disabled
85
+ // (`disabled:opacity-50`); the text follows via `has-[button:disabled]`, so
86
+ // the two never double up on opacity.
87
+ return (
88
+ <label
89
+ className={cn(
90
+ "ax:inline-flex ax:items-center ax:gap-sm ax:cursor-pointer",
91
+ "ax:has-[button:disabled]:cursor-not-allowed",
92
+ "ax:[&:has(button:disabled)_[data-slot=toggle-label]]:opacity-50",
93
+ labelClassName
94
+ )}
95
+ >
96
+ <span data-slot="toggle-label" className="ax:t-label-sm ax:text-foreground ax:select-none">
97
+ {label}
98
+ </span>
99
+ {control}
100
+ </label>
101
+ )
102
+ })
103
+ Toggle.displayName = "Toggle"