@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,198 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react"
6
+ import { cn } from "../lib/cn"
7
+
8
+ export const Select = SelectPrimitive.Root
9
+ export const SelectGroup = SelectPrimitive.Group
10
+ export const SelectValue = SelectPrimitive.Value
11
+
12
+ // Sizes mirror Button so a Select sits at the same height + interior padding
13
+ // as a Button at the matching size.
14
+ export type SelectTriggerSize = "sm" | "md" | "lg"
15
+
16
+ const TRIGGER_HEIGHT: Record<SelectTriggerSize, string> = {
17
+ sm: "ax:h-2xl", // 32px
18
+ md: "ax:h-3xl", // 40px
19
+ lg: "ax:h-4xl", // 48px
20
+ }
21
+
22
+ const TRIGGER_PADDING_X: Record<SelectTriggerSize, string> = {
23
+ sm: "ax:px-sm",
24
+ md: "ax:px-md",
25
+ lg: "ax:px-xl",
26
+ }
27
+
28
+ const TRIGGER_TEXT: Record<SelectTriggerSize, string> = {
29
+ sm: "ax:t-label-sm",
30
+ md: "ax:t-label-sm",
31
+ lg: "ax:text-base",
32
+ }
33
+
34
+ export interface SelectTriggerProps
35
+ extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> {
36
+ size?: SelectTriggerSize
37
+ /** Drop the border - for inline / sidebar triggers that shouldn't read as a field. */
38
+ borderless?: boolean
39
+ /** Drop the solid background so the trigger blends into its surface. */
40
+ transparent?: boolean
41
+ }
42
+
43
+ export const SelectTrigger = React.forwardRef<
44
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
45
+ SelectTriggerProps
46
+ >(({ className, size = "md", borderless = false, transparent = false, children, ...props }, ref) => (
47
+ <SelectPrimitive.Trigger
48
+ ref={ref}
49
+ className={cn(
50
+ "ax:flex ax:w-full ax:items-center ax:justify-between ax:gap-xs ax:rounded-(--input-radius) ax:py-xs",
51
+ "ax:text-foreground ax:placeholder:text-muted-foreground",
52
+ "ax:transition-[border-color,box-shadow] ax:transition-snappy",
53
+ "ax:focus:outline-none ax:focus:ring-2 ax:focus:ring-ring ax:focus:ring-offset-1 ax:focus:ring-offset-background ax:focus:border-transparent",
54
+ "ax:cursor-pointer ax:disabled:cursor-not-allowed ax:disabled:opacity-50",
55
+ "ax:data-[placeholder]:text-muted-foreground",
56
+ // leading-snug on the value span: t-label-sm is leading-none, and line-clamp-1
57
+ // sets overflow-hidden: a 1.0 line-height would shave descenders (g, y, p).
58
+ // flex-1 + text-start keep the value filling the trigger and left-aligned so
59
+ // a truncated value (…) doesn't drift to centre against the chevron.
60
+ "ax:[&>span]:line-clamp-1 ax:[&>span]:leading-snug ax:[&>span]:min-w-0 ax:[&>span]:flex-1 ax:[&>span]:text-start",
61
+ borderless ? "ax:border-0" : "ax:border ax:border-input",
62
+ transparent ? "ax:bg-transparent" : "ax:bg-background",
63
+ TRIGGER_HEIGHT[size],
64
+ TRIGGER_PADDING_X[size],
65
+ TRIGGER_TEXT[size],
66
+ className
67
+ )}
68
+ {...props}
69
+ >
70
+ {children}
71
+ <SelectPrimitive.Icon asChild>
72
+ <ChevronDown className="ax:size-4 ax:shrink-0 ax:text-muted-foreground" />
73
+ </SelectPrimitive.Icon>
74
+ </SelectPrimitive.Trigger>
75
+ ))
76
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
77
+
78
+ export const SelectScrollUpButton = React.forwardRef<
79
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
80
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
81
+ >(({ className, ...props }, ref) => (
82
+ <SelectPrimitive.ScrollUpButton
83
+ ref={ref}
84
+ className={cn("ax:flex ax:cursor-default ax:items-center ax:justify-center ax:py-1", className)}
85
+ {...props}
86
+ >
87
+ <ChevronUp className="ax:size-4" />
88
+ </SelectPrimitive.ScrollUpButton>
89
+ ))
90
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
91
+
92
+ export const SelectScrollDownButton = React.forwardRef<
93
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
94
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
95
+ >(({ className, ...props }, ref) => (
96
+ <SelectPrimitive.ScrollDownButton
97
+ ref={ref}
98
+ className={cn("ax:flex ax:cursor-default ax:items-center ax:justify-center ax:py-1", className)}
99
+ {...props}
100
+ >
101
+ <ChevronDown className="ax:size-4" />
102
+ </SelectPrimitive.ScrollDownButton>
103
+ ))
104
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
105
+
106
+ export const SelectContent = React.forwardRef<
107
+ React.ElementRef<typeof SelectPrimitive.Content>,
108
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
109
+ >(({ className, children, position = "popper", ...props }, ref) => (
110
+ <SelectPrimitive.Portal>
111
+ <SelectPrimitive.Content
112
+ ref={ref}
113
+ className={cn(
114
+ "ax:relative ax:z-[1100] ax:max-h-96 ax:min-w-[8rem] ax:overflow-hidden",
115
+ "ax:rounded-(--input-radius) ax:border ax:border-border ax:bg-popover ax:text-popover-foreground ax:shadow-md",
116
+ "ax:data-[state=open]:animate-in ax:data-[state=closed]:animate-out",
117
+ "ax:data-[state=closed]:fade-out-0 ax:data-[state=open]:fade-in-0",
118
+ "ax:data-[state=closed]:zoom-out-[0.96] ax:data-[state=open]:zoom-in-[0.96]",
119
+ "ax:data-[side=bottom]:slide-in-from-top-1",
120
+ "ax:data-[side=left]:slide-in-from-right-1",
121
+ "ax:data-[side=right]:slide-in-from-left-1",
122
+ "ax:data-[side=top]:slide-in-from-bottom-1",
123
+ "ax:[animation-duration:var(--motion-fast)] ax:[animation-timing-function:var(--motion-ease-out)]",
124
+ "ax:motion-reduce:animate-none",
125
+ position === "popper" && [
126
+ "ax:data-[side=bottom]:translate-y-1",
127
+ "ax:data-[side=left]:-translate-x-1",
128
+ "ax:data-[side=right]:translate-x-1",
129
+ "ax:data-[side=top]:-translate-y-1",
130
+ ],
131
+ className
132
+ )}
133
+ position={position}
134
+ {...props}
135
+ >
136
+ <SelectScrollUpButton />
137
+ <SelectPrimitive.Viewport
138
+ className={cn(
139
+ "ax:p-2xs",
140
+ position === "popper" && "ax:h-[var(--radix-select-trigger-height)] ax:w-full ax:min-w-[var(--radix-select-trigger-width)]"
141
+ )}
142
+ >
143
+ {children}
144
+ </SelectPrimitive.Viewport>
145
+ <SelectScrollDownButton />
146
+ </SelectPrimitive.Content>
147
+ </SelectPrimitive.Portal>
148
+ ))
149
+ SelectContent.displayName = SelectPrimitive.Content.displayName
150
+
151
+ export const SelectLabel = React.forwardRef<
152
+ React.ElementRef<typeof SelectPrimitive.Label>,
153
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
154
+ >(({ className, ...props }, ref) => (
155
+ <SelectPrimitive.Label
156
+ ref={ref}
157
+ className={cn("ax:px-2 ax:py-1.5 ax:t-overline ax:text-muted-foreground", className)}
158
+ {...props}
159
+ />
160
+ ))
161
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
162
+
163
+ export const SelectItem = React.forwardRef<
164
+ React.ElementRef<typeof SelectPrimitive.Item>,
165
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
166
+ >(({ className, children, ...props }, ref) => (
167
+ <SelectPrimitive.Item
168
+ ref={ref}
169
+ className={cn(
170
+ "ax:relative ax:flex ax:w-full ax:cursor-pointer ax:select-none ax:items-center ax:rounded-(--input-radius) ax:py-1.5 ax:pl-2 ax:pr-8",
171
+ "ax:t-body-sm ax:text-foreground ax:outline-none",
172
+ "ax:focus:bg-muted ax:focus:text-foreground",
173
+ "ax:data-[disabled]:pointer-events-none ax:data-[disabled]:opacity-50",
174
+ className
175
+ )}
176
+ {...props}
177
+ >
178
+ <span className="ax:absolute ax:right-2 ax:flex ax:size-3.5 ax:items-center ax:justify-center">
179
+ <SelectPrimitive.ItemIndicator>
180
+ <Check className="ax:size-4" />
181
+ </SelectPrimitive.ItemIndicator>
182
+ </span>
183
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
184
+ </SelectPrimitive.Item>
185
+ ))
186
+ SelectItem.displayName = SelectPrimitive.Item.displayName
187
+
188
+ export const SelectSeparator = React.forwardRef<
189
+ React.ElementRef<typeof SelectPrimitive.Separator>,
190
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
191
+ >(({ className, ...props }, ref) => (
192
+ <SelectPrimitive.Separator
193
+ ref={ref}
194
+ className={cn("ax:-mx-1 ax:my-1 ax:h-px ax:bg-border", className)}
195
+ {...props}
196
+ />
197
+ ))
198
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
@@ -0,0 +1,22 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Separator } from "./separator"
4
+
5
+ const meta: Meta<typeof Separator> = {
6
+ title: "Components/Separator",
7
+ component: Separator,
8
+ tags: ["autodocs"],
9
+ }
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof Separator>
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <div className="ax:w-64 ax:space-y-md">
17
+ <p className="ax:t-body-sm">Section one</p>
18
+ <Separator />
19
+ <p className="ax:t-body-sm">Section two</p>
20
+ </div>
21
+ ),
22
+ }
@@ -0,0 +1,23 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator"
5
+ import { cn } from "../lib/cn"
6
+
7
+ export const Separator = React.forwardRef<
8
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
10
+ >(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
11
+ <SeparatorPrimitive.Root
12
+ ref={ref}
13
+ decorative={decorative}
14
+ orientation={orientation}
15
+ className={cn(
16
+ "ax:shrink-0 ax:bg-border",
17
+ orientation === "horizontal" ? "ax:h-px ax:w-full" : "ax:h-full ax:w-px",
18
+ className
19
+ )}
20
+ {...props}
21
+ />
22
+ ))
23
+ Separator.displayName = SeparatorPrimitive.Root.displayName
@@ -0,0 +1,23 @@
1
+ import type { Meta, StoryObj } from "@storybook/react"
2
+ import { ShimmerText } from "./shimmer-text"
3
+
4
+ const meta: Meta<typeof ShimmerText> = {
5
+ title: "Components/ShimmerText",
6
+ component: ShimmerText,
7
+ tags: ["autodocs"],
8
+ args: { children: "Searching listings on Yas Island", duration: 2.5, spread: 20 },
9
+ argTypes: {
10
+ as: { control: "select", options: ["span", "p", "div", "strong", "h1", "h2", "h3", "h4", "h5", "h6"] },
11
+ duration: { control: { type: "range", min: 0.5, max: 8, step: 0.5 } },
12
+ spread: { control: { type: "range", min: 5, max: 45, step: 1 } },
13
+ },
14
+ }
15
+
16
+ export default meta
17
+ type Story = StoryObj<typeof ShimmerText>
18
+
19
+ export const Default: Story = {}
20
+
21
+ export const Heading: Story = { args: { as: "h3", className: "ax:t-h3", children: "Preparing your report" } }
22
+
23
+ export const SlowWide: Story = { args: { duration: 5, spread: 40, children: "Comparing registered sales" } }
@@ -0,0 +1,35 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import * as React from "react"
3
+ import { renderToStaticMarkup } from "react-dom/server"
4
+ import { ShimmerText, shimmerGradient } from "./shimmer-text"
5
+
6
+ describe("shimmerGradient", () => {
7
+ it("brightens the centre and spreads the highlight either side", () => {
8
+ expect(shimmerGradient(20)).toContain("hsl(var(--muted-foreground)) 30%, hsl(var(--foreground)) 50%, hsl(var(--muted-foreground)) 70%")
9
+ })
10
+
11
+ it("clamps the spread to 5-45", () => {
12
+ expect(shimmerGradient(0)).toContain(" 45%, ")
13
+ expect(shimmerGradient(99)).toContain(" 5%, ")
14
+ })
15
+ })
16
+
17
+ describe("ShimmerText", () => {
18
+ it("renders a span with the gradient inline, so class merging cannot drop it", () => {
19
+ const html = renderToStaticMarkup(<ShimmerText className="ax:bg-primary">Thinking</ShimmerText>)
20
+ expect(html).toMatch(/^<span/)
21
+ expect(html).toContain("background-image:linear-gradient")
22
+ expect(html).toContain("background-size:200% auto")
23
+ })
24
+
25
+ it("takes the element, duration and caller styles", () => {
26
+ const html = renderToStaticMarkup(<ShimmerText as="p" duration={4} style={{ letterSpacing: "1px" }}>Searching</ShimmerText>)
27
+ expect(html).toMatch(/^<p/)
28
+ expect(html).toContain("animation-duration:4s")
29
+ expect(html).toContain("letter-spacing:1px")
30
+ })
31
+
32
+ it("stops the sweep under reduced motion", () => {
33
+ expect(renderToStaticMarkup(<ShimmerText>Thinking</ShimmerText>)).toContain("ax:motion-reduce:animate-none")
34
+ })
35
+ })
@@ -0,0 +1,43 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../lib/cn"
5
+
6
+ export type ShimmerTextAs = "span" | "p" | "div" | "strong" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6"
7
+
8
+ export interface ShimmerTextProps extends React.HTMLAttributes<HTMLElement> {
9
+ as?: ShimmerTextAs
10
+ /** Seconds for one sweep. */
11
+ duration?: number
12
+ /** Width of the highlight as a percentage either side of centre, clamped to 5-45. */
13
+ spread?: number
14
+ }
15
+
16
+ /** The gradient behind the text: muted, bright at centre, muted. */
17
+ export function shimmerGradient(spread: number) {
18
+ const s = Math.min(Math.max(spread, 5), 45)
19
+ return `linear-gradient(to right, hsl(var(--muted-foreground)) ${50 - s}%, hsl(var(--foreground)) 50%, hsl(var(--muted-foreground)) ${50 + s}%)`
20
+ }
21
+
22
+ /**
23
+ * ShimmerText - text with a highlight sweeping across it, for work in
24
+ * progress such as "Thinking" or "Searching listings". The sweep stops under
25
+ * reduced motion; the text stays readable.
26
+ *
27
+ * @example
28
+ * <ShimmerText>Searching listings</ShimmerText>
29
+ * <ShimmerText as="p" duration={4} spread={30}>Comparing registered sales</ShimmerText>
30
+ */
31
+ export const ShimmerText = React.forwardRef<HTMLElement, ShimmerTextProps>(
32
+ ({ as: Tag = "span", duration = 2.5, spread = 20, className, style, ...props }, ref) => (
33
+ <Tag
34
+ ref={ref as React.Ref<never>}
35
+ data-slot="shimmer-text"
36
+ // Gradient and size go inline: cn's tailwind-merge drops bg-linear-to-r beside other bg-* classes.
37
+ style={{ backgroundImage: shimmerGradient(spread), backgroundSize: "200% auto", animationDuration: `${duration}s`, ...style }}
38
+ className={cn("ax:bg-clip-text ax:text-transparent ax:animate-text-shimmer ax:motion-reduce:animate-none", className)}
39
+ {...props}
40
+ />
41
+ ),
42
+ )
43
+ ShimmerText.displayName = "ShimmerText"
@@ -0,0 +1,25 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Sidebar, SidebarItem, SidebarSection } from "./sidebar"
4
+
5
+ const meta: Meta<typeof Sidebar> = {
6
+ title: "Components/Sidebar",
7
+ component: Sidebar,
8
+ tags: ["autodocs"],
9
+ }
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof Sidebar>
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <div className="ax:h-[320px] ax:border ax:border-border ax:rounded-md ax:overflow-hidden">
17
+ <Sidebar>
18
+ <SidebarSection label="Main">
19
+ <SidebarItem href="#" active>Dashboard</SidebarItem>
20
+ <SidebarItem href="#">Projects</SidebarItem>
21
+ </SidebarSection>
22
+ </Sidebar>
23
+ </div>
24
+ ),
25
+ }
@@ -0,0 +1,234 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Slot } from "@radix-ui/react-slot"
5
+ import { motion, AnimatePresence } from "motion/react"
6
+ import { cn } from "../lib/cn"
7
+ import { useReducedMotion } from "../hooks/use-reduced-motion"
8
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip"
9
+
10
+ // ─── Sidebar ──────────────────────────────────────────────────────────────────
11
+
12
+ export interface SidebarProps extends React.HTMLAttributes<HTMLElement> {
13
+ /** Sidebar width in rem; default 16rem (256px). */
14
+ width?: number
15
+ /** Collapsed state - controlled. When undefined, sidebar is always open. */
16
+ collapsed?: boolean
17
+ /** Optional logo / brand mark slot at the top. */
18
+ brand?: React.ReactNode
19
+ /** Optional footer slot (typically a `SidebarUserCard`). */
20
+ footer?: React.ReactNode
21
+ }
22
+
23
+ /**
24
+ * Sidebar - primary app-shell navigation surface.
25
+ *
26
+ * Vertical column with brand, scrollable nav body, and footer. Uses logical
27
+ * properties (`border-e`) so the column sits on the inline-start edge in LTR
28
+ * and flips to inline-end in RTL automatically. Animates collapsed/expanded
29
+ * state via motion (suppressed under reduced motion).
30
+ */
31
+ export const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(
32
+ ({ className, width = 16, collapsed, brand, footer, children, ...props }, ref) => {
33
+ const reduce = useReducedMotion()
34
+ const collapsedWidth = 4
35
+
36
+ const targetWidth = collapsed ? `${collapsedWidth}rem` : `${width}rem`
37
+
38
+ const inner = (
39
+ <>
40
+ {brand && <div className="ax:h-5xl ax:px-md ax:flex ax:items-center">{brand}</div>}
41
+ <nav className="ax:flex-1 ax:overflow-y-auto ax:p-sm">{children}</nav>
42
+ {footer && <div className="ax:border-t ax:border-border ax:p-sm">{footer}</div>}
43
+ </>
44
+ )
45
+
46
+ const baseClasses = cn(
47
+ "ax:shrink-0 ax:h-full ax:overflow-hidden ax:border-e ax:border-border ax:bg-card ax:text-card-foreground",
48
+ "ax:flex ax:flex-col",
49
+ className
50
+ )
51
+
52
+ return (
53
+ // TooltipProvider here so collapsed SidebarItems can show labels-as-tooltips
54
+ // without requiring the consumer to wrap their app root themselves.
55
+ <TooltipProvider delayDuration={200}>
56
+ {reduce ? (
57
+ <aside
58
+ ref={ref}
59
+ aria-label="Primary"
60
+ style={{ width: targetWidth }}
61
+ className={baseClasses}
62
+ {...props}
63
+ >
64
+ {inner}
65
+ </aside>
66
+ ) : (
67
+ <motion.aside
68
+ ref={ref as React.Ref<HTMLElement>}
69
+ aria-label="Primary"
70
+ initial={false}
71
+ animate={{ width: targetWidth }}
72
+ transition={{ type: "spring", stiffness: 320, damping: 32 }}
73
+ className={baseClasses}
74
+ {...(props as Record<string, unknown>)}
75
+ >
76
+ {inner}
77
+ </motion.aside>
78
+ )}
79
+ </TooltipProvider>
80
+ )
81
+ }
82
+ )
83
+ Sidebar.displayName = "Sidebar"
84
+
85
+ // ─── SidebarItem ──────────────────────────────────────────────────────────────
86
+
87
+ export interface SidebarItemProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
88
+ icon?: React.ReactNode
89
+ active?: boolean
90
+ collapsed?: boolean
91
+ /**
92
+ * Render as the child element (e.g. a Next.js `<Link>`). The child receives
93
+ * the className, ref, and event props so it behaves as the sidebar row while
94
+ * keeping its own navigation behaviour.
95
+ */
96
+ asChild?: boolean
97
+ }
98
+
99
+ /**
100
+ * SidebarItem - a row inside `Sidebar`.
101
+ *
102
+ * Renders an `<a>` by default. Use `asChild` to wrap a Next.js `<Link>` (or
103
+ * any custom navigation component) without losing the visual treatment.
104
+ *
105
+ * Active state: tinted background + matching text + a 2px start-edge bar
106
+ * (`border-s-2 border-primary`). Inactive rows reserve the same 2px so
107
+ * activating doesn't shift the label horizontally.
108
+ *
109
+ * When `collapsed` is true the label is hidden and the `children` are
110
+ * surfaced as a Tooltip on the inline-end side. Keep `children` to a
111
+ * short string when collapsed mode is in play.
112
+ */
113
+ export const SidebarItem = React.forwardRef<HTMLAnchorElement, SidebarItemProps>(
114
+ ({ className, icon, active, collapsed, asChild, children, ...props }, ref) => {
115
+ const Comp = asChild ? Slot : "a"
116
+
117
+ const row = (
118
+ <Comp
119
+ ref={ref as React.Ref<HTMLAnchorElement>}
120
+ aria-current={active ? "page" : undefined}
121
+ className={cn(
122
+ "ax:group ax:flex ax:items-center ax:gap-sm ax:rounded-md ax:ps-xs ax:pe-xs ax:py-xs ax:t-label-sm ax:cursor-pointer",
123
+ // Reserve the 2px start-edge bar on every row so the active state
124
+ // doesn't nudge content horizontally.
125
+ "ax:border-s-2 ax:border-transparent",
126
+ "ax:transition-colors ax:transition-snappy",
127
+ "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-card",
128
+ active
129
+ ? "ax:bg-card ax:text-primary-foreground ax:border-primary"
130
+ : "ax:text-foreground ax:hover:bg-muted ax:hover:text-foreground-strong",
131
+ collapsed && "ax:justify-center ax:ps-2xs ax:pe-2xs",
132
+ className
133
+ )}
134
+ {...props}
135
+ >
136
+ {icon && <span className="ax:size-4 ax:shrink-0 ax:flex ax:items-center ax:justify-center ax:[&>svg]:size-4 ax:[&>svg]:shrink-0">{icon}</span>}
137
+ <AnimatePresence initial={false}>
138
+ {!collapsed && (
139
+ <motion.span
140
+ initial={{ opacity: 0, width: 0 }}
141
+ animate={{ opacity: 1, width: "auto" }}
142
+ exit={{ opacity: 0, width: 0 }}
143
+ transition={{ duration: 0.15 }}
144
+ className="ax:overflow-hidden ax:whitespace-nowrap"
145
+ >
146
+ {children}
147
+ </motion.span>
148
+ )}
149
+ </AnimatePresence>
150
+ </Comp>
151
+ )
152
+
153
+ if (collapsed) {
154
+ return (
155
+ <Tooltip>
156
+ <TooltipTrigger asChild>{row}</TooltipTrigger>
157
+ {/* `side="right"` flips to the inline-end in RTL because Radix uses
158
+ physical sides; we mirror with a logical `rtl:` rule on the parent. */}
159
+ <TooltipContent side="right">{children}</TooltipContent>
160
+ </Tooltip>
161
+ )
162
+ }
163
+
164
+ return row
165
+ }
166
+ )
167
+ SidebarItem.displayName = "SidebarItem"
168
+
169
+ // ─── SidebarSection ───────────────────────────────────────────────────────────
170
+
171
+ export const SidebarSection = React.forwardRef<
172
+ HTMLDivElement,
173
+ React.HTMLAttributes<HTMLDivElement> & { label?: React.ReactNode; collapsed?: boolean }
174
+ >(
175
+ ({ className, label, collapsed, children, ...props }, ref) => (
176
+ <div ref={ref} className={cn("ax:flex ax:flex-col ax:gap-0.5 ax:py-xs", className)} {...props}>
177
+ {label && !collapsed && (
178
+ <div className="ax:px-xs ax:pb-xs ax:t-caption ax:text-foreground/50 ax:font-medium">
179
+ {label}
180
+ </div>
181
+ )}
182
+ {children}
183
+ </div>
184
+ )
185
+ )
186
+ SidebarSection.displayName = "SidebarSection"
187
+
188
+ // ─── SidebarUserCard ──────────────────────────────────────────────────────────
189
+ //
190
+ // Conventional footer block: avatar + name + role + optional action slot.
191
+ // Matches the pattern called out in the TPB spec ("User avatar + name +
192
+ // 'Company Admin' role label, settings, logout"). Collapses to just the
193
+ // avatar when the sidebar is collapsed.
194
+
195
+ // `role` is our domain prop (the sub-label under the name); the HTML
196
+ // attribute of the same name is omitted to keep the type clean.
197
+ export interface SidebarUserCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "role"> {
198
+ /** Avatar element (typically `<Avatar>` from the DS). */
199
+ avatar: React.ReactNode
200
+ /** Display name. */
201
+ name: React.ReactNode
202
+ /** Role / sub-label rendered under the name (e.g. "Company Admin"). */
203
+ role?: React.ReactNode
204
+ /** Trailing slot - typically a DropdownMenu trigger for settings/logout. */
205
+ action?: React.ReactNode
206
+ /** Hides the name/role/action when the parent Sidebar is collapsed. */
207
+ collapsed?: boolean
208
+ }
209
+
210
+ export const SidebarUserCard = React.forwardRef<HTMLDivElement, SidebarUserCardProps>(
211
+ ({ className, avatar, name, role, action, collapsed, ...props }, ref) => (
212
+ <div
213
+ ref={ref}
214
+ className={cn(
215
+ "ax:flex ax:items-center ax:gap-sm",
216
+ collapsed && "ax:justify-center",
217
+ className
218
+ )}
219
+ {...props}
220
+ >
221
+ <div className="ax:shrink-0">{avatar}</div>
222
+ {!collapsed && (
223
+ <>
224
+ <div className="ax:min-w-0 ax:flex-1">
225
+ <div className="ax:t-label-sm ax:text-foreground ax:truncate">{name}</div>
226
+ {role && <div className="ax:t-caption ax:text-muted-foreground ax:truncate">{role}</div>}
227
+ </div>
228
+ {action && <div className="ax:shrink-0">{action}</div>}
229
+ </>
230
+ )}
231
+ </div>
232
+ )
233
+ )
234
+ SidebarUserCard.displayName = "SidebarUserCard"
@@ -0,0 +1,22 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Skeleton } from "./skeleton"
4
+
5
+ const meta: Meta<typeof Skeleton> = {
6
+ title: "Components/Skeleton",
7
+ component: Skeleton,
8
+ tags: ["autodocs"],
9
+ }
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof Skeleton>
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <div className="ax:space-y-sm ax:w-64">
17
+ <Skeleton className="ax:h-4 ax:w-full" />
18
+ <Skeleton className="ax:h-4 ax:w-3/4" />
19
+ <Skeleton className="ax:h-20 ax:w-full ax:rounded-md" />
20
+ </div>
21
+ ),
22
+ }