@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,157 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { ChevronDown } from "lucide-react"
6
+ import { tv } from "tailwind-variants"
7
+ import { SelectContent } from "./select"
8
+ import { Input, type InputProps } from "./input"
9
+ import { cn } from "../lib/cn"
10
+
11
+ /**
12
+ * InputGroup - a Select + Input compactly joined into a single inline group.
13
+ *
14
+ * The third member of the joined-pill composite family alongside InputButton
15
+ * (Input+Button) and SelectButton (Select+Button): the Select trigger occupies
16
+ * the left half, the Input occupies the right half, both at the same height
17
+ * ladder so they line up with the other composites at a matching size.
18
+ *
19
+ * <InputGroup
20
+ * size="md"
21
+ * selectPlaceholder="Title"
22
+ * selectValue={title}
23
+ * onSelectValueChange={setTitle}
24
+ * placeholder="Full name"
25
+ * value={name}
26
+ * onChange={(e) => setName(e.target.value)}
27
+ * >
28
+ * <SelectItem value="mr">Mr.</SelectItem>
29
+ * <SelectItem value="ms">Ms.</SelectItem>
30
+ * </InputGroup>
31
+ *
32
+ * Select-half props are prefixed (selectValue, selectPlaceholder, ...) so they
33
+ * don't collide with the native input attributes (value, placeholder, ...)
34
+ * inherited from InputProps. `disabled` disables BOTH halves; use
35
+ * `selectDisabled` to disable only the select.
36
+ */
37
+
38
+ export type InputGroupSize = "sm" | "md" | "lg"
39
+
40
+ // Two-slot composite: the select trigger's height/padding/type ramp scales
41
+ // with `size` (mirrors the standalone Select / SelectButton trigger); the
42
+ // input slot only carries the static join classes since Input derives its
43
+ // own size-driven height/padding/type from its own `size` prop.
44
+ const inputGroupVariants = tv(
45
+ {
46
+ slots: {
47
+ trigger: [
48
+ "ax:inline-flex ax:shrink-0 ax:items-center ax:justify-between ax:gap-xs ax:whitespace-nowrap ax:cursor-pointer ax:select-none",
49
+ "ax:bg-transparent ax:text-foreground",
50
+ "ax:border ax:border-input ax:rounded-l-(--input-radius) ax:rounded-r-none ax:-mr-px",
51
+ "ax:transition-[border-color,box-shadow] ax:transition-snappy",
52
+ // ring-inset (not ring-offset) keeps the focus ring inside the trigger's
53
+ // own box: an outward halo would bleed across the seam onto the Input.
54
+ "ax:focus:outline-none ax:focus:z-10 ax:focus:ring-2 ax:focus:ring-inset ax:focus:ring-ring ax:focus:border-transparent",
55
+ "ax:disabled:cursor-not-allowed ax:disabled:opacity-50",
56
+ "ax:data-placeholder:text-muted-foreground",
57
+ // leading-snug on the value span: t-label-sm is leading-none, and line-clamp-1
58
+ // sets overflow-hidden: a 1.0 line-height would shave descenders (g, y, p).
59
+ "ax:[&>span]:line-clamp-1 ax:[&>span]:leading-snug",
60
+ ],
61
+ // Join: square the left corner, overlap the trigger by 1px, and lift on
62
+ // focus so the focus ring sits above the adjoining trigger. ring-inset +
63
+ // ring-offset-0 override Input's own standalone ring-offset styling so
64
+ // its focus ring doesn't bleed left onto the trigger either.
65
+ input: "ax:relative ax:min-w-0 ax:flex-1 ax:-ml-px ax:rounded-l-none ax:focus-visible:z-10 ax:focus-visible:ring-inset ax:focus-visible:ring-offset-0",
66
+ },
67
+ variants: {
68
+ size: {
69
+ sm: { trigger: "ax:h-2xl ax:px-sm ax:t-label-sm" },
70
+ md: { trigger: "ax:h-3xl ax:px-md ax:t-label-sm" },
71
+ lg: { trigger: "ax:h-4xl ax:px-xl ax:text-base" },
72
+ },
73
+ },
74
+ defaultVariants: {
75
+ size: "md",
76
+ },
77
+ },
78
+ { twMerge: false }
79
+ )
80
+
81
+ export interface InputGroupProps extends Omit<InputProps, "size"> {
82
+ /** Shared height/typography ladder - matches Button, Input, Select, InputButton, SelectButton. */
83
+ size?: InputGroupSize
84
+
85
+ // ─── Select half ──────────────────────────────────────────────────────────────
86
+ selectValue?: string
87
+ selectDefaultValue?: string
88
+ onSelectValueChange?: (value: string) => void
89
+ selectPlaceholder?: string
90
+ selectName?: string
91
+ /** Disables only the select half. `disabled` (inherited from InputProps) disables both. */
92
+ selectDisabled?: boolean
93
+ /** Extra className appended to the select trigger. */
94
+ selectClassName?: string
95
+ /** SelectItem (and SelectLabel/SelectSeparator) children for the popover. */
96
+ children: React.ReactNode
97
+
98
+ // ─── Input half ───────────────────────────────────────────────────────────────
99
+ /** Extra className appended to the inner Input. */
100
+ inputClassName?: string
101
+
102
+ /** className for the wrapping group (the input fills the remaining width). */
103
+ className?: string
104
+ }
105
+
106
+ export const InputGroup = React.forwardRef<HTMLInputElement, InputGroupProps>(
107
+ (
108
+ {
109
+ size = "md",
110
+ selectValue,
111
+ selectDefaultValue,
112
+ onSelectValueChange,
113
+ selectPlaceholder,
114
+ selectName,
115
+ selectDisabled,
116
+ selectClassName,
117
+ children,
118
+ inputClassName,
119
+ className,
120
+ disabled,
121
+ ...inputProps
122
+ },
123
+ ref
124
+ ) => {
125
+ const { trigger, input } = inputGroupVariants({ size })
126
+ return (
127
+ <div
128
+ data-slot="input-group"
129
+ className={cn("ax:isolate ax:flex ax:w-full ax:items-stretch", disabled && "ax:opacity-50 ax:pointer-events-none", className)}
130
+ >
131
+ <SelectPrimitive.Root
132
+ value={selectValue}
133
+ defaultValue={selectDefaultValue}
134
+ onValueChange={onSelectValueChange}
135
+ disabled={disabled || selectDisabled}
136
+ name={selectName}
137
+ >
138
+ <SelectPrimitive.Trigger className={cn(trigger(), selectClassName)}>
139
+ <SelectPrimitive.Value placeholder={selectPlaceholder} />
140
+ <SelectPrimitive.Icon asChild>
141
+ <ChevronDown className="ax:size-4 ax:shrink-0 ax:text-muted-foreground" />
142
+ </SelectPrimitive.Icon>
143
+ </SelectPrimitive.Trigger>
144
+ <SelectContent>{children}</SelectContent>
145
+ </SelectPrimitive.Root>
146
+ <Input
147
+ ref={ref}
148
+ size={size}
149
+ disabled={disabled}
150
+ className={cn(input(), inputClassName)}
151
+ {...inputProps}
152
+ />
153
+ </div>
154
+ )
155
+ }
156
+ )
157
+ InputGroup.displayName = "InputGroup"
@@ -0,0 +1,31 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Field, Input, Label, Textarea } from "./input"
4
+
5
+ const meta: Meta<typeof Input> = {
6
+ title: "Components/Input",
7
+ component: Input,
8
+ tags: ["autodocs"],
9
+ }
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof Input>
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <Field className="ax:max-w-sm">
17
+ <Label htmlFor="email">Email</Label>
18
+ <Input id="email" type="email" placeholder="you@example.com" />
19
+ </Field>
20
+ ),
21
+ }
22
+
23
+ export const TextareaStory: Story = {
24
+ name: "Textarea",
25
+ render: () => (
26
+ <Field className="ax:max-w-sm">
27
+ <Label htmlFor="bio">Bio</Label>
28
+ <Textarea id="bio" placeholder="Tell us about yourself" />
29
+ </Field>
30
+ ),
31
+ }
@@ -0,0 +1,284 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../lib/cn"
5
+
6
+ // ─── Types ────────────────────────────────────────────────────────────────────
7
+
8
+ export type InputSize = "sm" | "md" | "lg"
9
+
10
+ // ─── TextField context ────────────────────────────────────────────────────────
11
+
12
+ interface TextFieldContextValue {
13
+ inRoot: boolean
14
+ size: InputSize
15
+ }
16
+
17
+ const TextFieldContext = React.createContext<TextFieldContextValue>({
18
+ inRoot: false,
19
+ size: "md",
20
+ })
21
+
22
+ // ─── Size maps ────────────────────────────────────────────────────────────────
23
+
24
+ const HEIGHT: Record<InputSize, string> = {
25
+ sm: "ax:h-2xl", // 32px
26
+ md: "ax:h-3xl", // 40px
27
+ lg: "ax:h-4xl", // 48px
28
+ }
29
+
30
+ // Mirrors Button's px-scale so an Input and a Button at the same size share
31
+ // identical horizontal interior padding.
32
+ const PADDING_X: Record<InputSize, string> = {
33
+ sm: "ax:px-sm",
34
+ md: "ax:px-sm",
35
+ lg: "ax:px-md",
36
+ }
37
+
38
+ // Mirrors Button's type ramp (label-md for sm/md, text-base for lg).
39
+ const TEXT_SIZE: Record<InputSize, string> = {
40
+ sm: "ax:t-label-sm",
41
+ md: "ax:t-label-sm",
42
+ lg: "ax:text-base",
43
+ }
44
+
45
+ // ─── Input ────────────────────────────────────────────────────────────────────
46
+
47
+ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
48
+ invalid?: boolean
49
+ size?: InputSize
50
+ }
51
+
52
+ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
53
+ ({ className, type = "text", invalid, size: sizeProp, ...props }, ref) => {
54
+ const { inRoot, size: ctxSize } = React.useContext(TextFieldContext)
55
+ const size = sizeProp ?? ctxSize
56
+
57
+ return (
58
+ <input
59
+ ref={ref}
60
+ type={type}
61
+ aria-invalid={invalid || props["aria-invalid"]}
62
+ data-slot="input"
63
+ className={cn(
64
+ "ax:bg-card ax:text-foreground ax:placeholder:text-muted-foreground/50",
65
+ "ax:transition-[border-color,box-shadow] ax:transition-snappy",
66
+ "ax:file:border-0 ax:file:bg-transparent ax:file:text-sm ax:file:font-medium",
67
+ TEXT_SIZE[size],
68
+ inRoot
69
+ ? [
70
+ // Inside TextFieldRoot: root owns the border, ring, height, and px
71
+ "ax:flex-1 ax:min-w-0 ax:h-full ax:rounded-none ax:border-0 ax:px-0 ax:py-0",
72
+ "ax:focus-visible:outline-none ax:focus-visible:ring-0 ax:focus-visible:ring-offset-0",
73
+ "ax:disabled:cursor-not-allowed ax:disabled:opacity-50 ax:disabled:bg-transparent",
74
+ ]
75
+ : [
76
+ // Standalone
77
+ "ax:w-full ax:rounded-(--input-radius) ax:border ax:border-input",
78
+ HEIGHT[size],
79
+ PADDING_X[size],
80
+ "ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring ax:focus-visible:ring-offset-1 ax:focus-visible:ring-offset-background ax:focus-visible:border-transparent",
81
+ "ax:disabled:cursor-not-allowed ax:disabled:opacity-50 ax:disabled:bg-muted",
82
+ "ax:aria-[invalid=true]:border-destructive ax:aria-[invalid=true]:ring-2 ax:aria-[invalid=true]:ring-destructive/30",
83
+ ],
84
+ className
85
+ )}
86
+ {...props}
87
+ />
88
+ )
89
+ }
90
+ )
91
+ Input.displayName = "Input"
92
+
93
+ // ─── TextFieldRoot ─────────────────────────────────────────────────────────────
94
+
95
+ export interface TextFieldRootProps extends React.HTMLAttributes<HTMLDivElement> {
96
+ size?: InputSize
97
+ invalid?: boolean
98
+ }
99
+
100
+ export const TextFieldRoot = React.forwardRef<HTMLDivElement, TextFieldRootProps>(
101
+ ({ className, size: sizeProp, invalid, ...props }, ref) => {
102
+ const { size: ctxSize } = React.useContext(TextFieldContext)
103
+ const size = sizeProp ?? ctxSize
104
+
105
+ return (
106
+ <TextFieldContext.Provider value={{ inRoot: true, size }}>
107
+ <div
108
+ ref={ref}
109
+ className={cn(
110
+ "ax:flex ax:items-center ax:gap-xs ax:w-full ax:rounded-(--input-radius) ax:border ax:border-input ax:bg-card ax:overflow-hidden",
111
+ "ax:transition-[border-color,box-shadow] ax:transition-snappy",
112
+ "ax:focus-within:ring-2 ax:focus-within:ring-ring ax:focus-within:ring-offset-1 ax:focus-within:ring-offset-background ax:focus-within:border-transparent",
113
+ // CSS detects disabled/invalid state on nested input
114
+ "ax:has-[input:disabled]:opacity-50 ax:has-[input:disabled]:cursor-not-allowed ax:has-[input:disabled]:bg-muted",
115
+ "ax:has-[input[aria-invalid=true]]:border-destructive ax:has-[input[aria-invalid=true]]:focus-within:ring-destructive/30",
116
+ HEIGHT[size],
117
+ PADDING_X[size],
118
+ invalid && "ax:border-destructive ax:ring-2 ax:ring-destructive/30",
119
+ className
120
+ )}
121
+ {...props}
122
+ />
123
+ </TextFieldContext.Provider>
124
+ )
125
+ }
126
+ )
127
+ TextFieldRoot.displayName = "TextFieldRoot"
128
+
129
+ // ─── TextFieldSlot ─────────────────────────────────────────────────────────────
130
+
131
+ export interface TextFieldSlotProps extends React.HTMLAttributes<HTMLDivElement> {}
132
+
133
+ export const TextFieldSlot = React.forwardRef<HTMLDivElement, TextFieldSlotProps>(
134
+ ({ className, ...props }, ref) => (
135
+ <div
136
+ ref={ref}
137
+ className={cn(
138
+ "ax:flex ax:items-center ax:shrink-0 ax:text-muted-foreground",
139
+ // Slots with interactive children (buttons, links) need pointer events
140
+ "ax:pointer-events-none ax:[&:has(button)]:pointer-events-auto ax:[&:has(a)]:pointer-events-auto",
141
+ className
142
+ )}
143
+ {...props}
144
+ />
145
+ )
146
+ )
147
+ TextFieldSlot.displayName = "TextFieldSlot"
148
+
149
+ // ─── Textarea ─────────────────────────────────────────────────────────────────
150
+
151
+ export type TextareaResize = "none" | "vertical" | "horizontal" | "both"
152
+ export type TextareaSize = "sm" | "md" | "lg"
153
+
154
+ // Mirrors Button's px-scale + type ramp. py-* tuned so the single-line
155
+ // height of a Textarea matches the equivalent-size Button:
156
+ // sm 32px (py-1.5 = 6px × 2 + 20px line), md 40px, lg 48px.
157
+ const TEXTAREA_SIZE: Record<TextareaSize, string> = {
158
+ sm: "ax:p-sm ax:t-label-sm",
159
+ md: "ax:p-md ax:t-label-sm",
160
+ lg: "ax:p-md ax:text-base",
161
+ }
162
+
163
+ const RESIZE_CLASS: Record<TextareaResize, string> = {
164
+ none: "ax:resize-none",
165
+ vertical: "ax:resize-y",
166
+ horizontal: "ax:resize-x",
167
+ both: "resize",
168
+ }
169
+
170
+ export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
171
+ invalid?: boolean
172
+ resize?: TextareaResize
173
+ size?: TextareaSize
174
+ }
175
+
176
+ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
177
+ ({ className, invalid, resize = "vertical", size: sizeProp, ...props }, ref) => {
178
+ const { size: ctxSize } = React.useContext(TextFieldContext)
179
+ const size = sizeProp ?? ctxSize
180
+
181
+ return (
182
+ <textarea
183
+ ref={ref}
184
+ aria-invalid={invalid || props["aria-invalid"]}
185
+ data-slot="textarea"
186
+ className={cn(
187
+ "ax:flex ax:min-h-20 ax:w-full ax:rounded-(--input-radius) ax:bg-card",
188
+ "ax:border ax:border-input",
189
+ "ax:text-foreground ax:placeholder:text-muted-foreground/50",
190
+ "ax:transition-[border-color,box-shadow] ax:transition-snappy",
191
+ "ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring ax:focus-visible:ring-offset-1 ax:focus-visible:ring-offset-background ax:focus-visible:border-transparent",
192
+ "ax:disabled:cursor-not-allowed ax:disabled:opacity-50 ax:disabled:bg-muted",
193
+ "ax:aria-[invalid=true]:border-destructive ax:aria-[invalid=true]:ring-2 ax:aria-[invalid=true]:ring-destructive/30",
194
+ TEXTAREA_SIZE[size],
195
+ RESIZE_CLASS[resize],
196
+ className
197
+ )}
198
+ {...props}
199
+ />
200
+ )
201
+ }
202
+ )
203
+ Textarea.displayName = "Textarea"
204
+
205
+ // ─── Label ────────────────────────────────────────────────────────────────────
206
+
207
+ export type LabelSize = "xs" | "sm" | "md" | "lg"
208
+
209
+ // xs/sm/md share the compact label scale; only lg steps up, matching the
210
+ // point where Input's own type ramp moves off t-label-sm (see TEXT_SIZE above).
211
+ const LABEL_TEXT_SIZE: Record<LabelSize, string> = {
212
+ xs: "ax:t-label-xs",
213
+ sm: "ax:t-label-xs",
214
+ md: "ax:t-label-xs",
215
+ lg: "ax:t-label-sm",
216
+ }
217
+
218
+ export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
219
+ size?: LabelSize
220
+ }
221
+
222
+ export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
223
+ ({ className, size: sizeProp, ...props }, ref) => {
224
+ const { size: ctxSize } = React.useContext(TextFieldContext)
225
+ const size = sizeProp ?? ctxSize
226
+
227
+ return (
228
+ <label
229
+ ref={ref}
230
+ className={cn(
231
+ LABEL_TEXT_SIZE[size],
232
+ "ax:text-foreground",
233
+ "ax:peer-disabled:cursor-not-allowed ax:peer-disabled:opacity-70",
234
+ className
235
+ )}
236
+ {...props}
237
+ />
238
+ )
239
+ }
240
+ )
241
+ Label.displayName = "Label"
242
+
243
+ // ─── Field ────────────────────────────────────────────────────────────────────
244
+
245
+ export interface FieldProps extends React.HTMLAttributes<HTMLDivElement> {
246
+ help?: React.ReactNode
247
+ error?: React.ReactNode
248
+ /** Sizes the Label and any Input/Textarea/TextFieldRoot inside, unless one overrides it directly. */
249
+ size?: InputSize
250
+ }
251
+
252
+ export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
253
+ ({ className, help, error, size = "md", children, ...props }, ref) => {
254
+ const id = React.useId()
255
+ const helpId = `${id}-help`
256
+ return (
257
+ <TextFieldContext.Provider value={{ inRoot: false, size }}>
258
+ <div ref={ref} className={cn("ax:flex ax:flex-col ax:gap-1.5", className)} {...props}>
259
+ {React.Children.map(children, (child) =>
260
+ React.isValidElement<{ id?: string; "aria-describedby"?: string; "aria-invalid"?: boolean }>(child)
261
+ ? React.cloneElement(child, {
262
+ id: child.props.id ?? id,
263
+ "aria-describedby":
264
+ help || error
265
+ ? `${helpId} ${child.props["aria-describedby"] ?? ""}`.trim()
266
+ : child.props["aria-describedby"],
267
+ "aria-invalid": error ? true : child.props["aria-invalid"],
268
+ })
269
+ : child
270
+ )}
271
+ {(error || help) && (
272
+ <p
273
+ id={helpId}
274
+ className={cn("ax:text-xs", error ? "ax:text-destructive-foreground" : "ax:text-muted-foreground")}
275
+ >
276
+ {error ?? help}
277
+ </p>
278
+ )}
279
+ </div>
280
+ </TextFieldContext.Provider>
281
+ )
282
+ }
283
+ )
284
+ Field.displayName = "Field"
@@ -0,0 +1,29 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Card, CardContent, CardHeader, CardTitle } from "./card"
4
+ import { Inset } from "./inset"
5
+
6
+ const meta: Meta<typeof Inset> = {
7
+ title: "Components/Inset",
8
+ component: Inset,
9
+ tags: ["autodocs"],
10
+ }
11
+
12
+ export default meta
13
+ type Story = StoryObj<typeof Inset>
14
+
15
+ export const Default: Story = {
16
+ render: () => (
17
+ <Card className="ax:max-w-sm">
18
+ <CardHeader>
19
+ <CardTitle>Media card</CardTitle>
20
+ </CardHeader>
21
+ <Inset side="x" clip="padding-box">
22
+ <div className="ax:h-32 ax:bg-muted ax:flex ax:items-center ax:justify-center ax:text-muted-foreground">Inset content</div>
23
+ </Inset>
24
+ <CardContent>
25
+ <p>Content below the inset.</p>
26
+ </CardContent>
27
+ </Card>
28
+ ),
29
+ }
@@ -0,0 +1,70 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../lib/cn"
5
+
6
+ export type InsetSide = "all" | "x" | "y" | "top" | "bottom" | "left" | "right"
7
+ export type InsetClip = "border-box" | "padding-box"
8
+
9
+ export interface InsetProps extends React.HTMLAttributes<HTMLDivElement> {
10
+ /**
11
+ * Which edges the content bleeds to. Named by the face of the container:
12
+ * "top" → bleeds to top, left, and right edges.
13
+ * "left" → bleeds to left, top, and bottom edges.
14
+ */
15
+ side?: InsetSide
16
+ /** Whether the content clips at the border or allows overflow. Default "border-box". */
17
+ clip?: InsetClip
18
+ /** Add padding equal to the container's inset padding ABOVE the element.
19
+ * Use with side="bottom" to gap the inset away from siblings above it. */
20
+ pt?: "none" | "current"
21
+ /** Add padding equal to the container's inset padding BELOW the element.
22
+ * Use with side="top" to gap the inset away from siblings below it. */
23
+ pb?: "none" | "current"
24
+ /** Add padding equal to the container's inset padding LEFT of the element.
25
+ * Use with side="right" to gap the inset away from siblings to its left. */
26
+ pl?: "none" | "current"
27
+ /** Add padding equal to the container's inset padding RIGHT of the element.
28
+ * Use with side="left" to gap the inset away from siblings to its right. */
29
+ pr?: "none" | "current"
30
+ }
31
+
32
+ const P = "var(--inset-padding, 0px)"
33
+ const NEG = `calc(-1 * ${P})`
34
+
35
+ function sideStyle(side: InsetSide): React.CSSProperties {
36
+ switch (side) {
37
+ case "all": return { margin: NEG }
38
+ case "x": return { marginLeft: NEG, marginRight: NEG }
39
+ case "y": return { marginTop: NEG, marginBottom: NEG }
40
+ case "top": return { marginTop: NEG, marginLeft: NEG, marginRight: NEG }
41
+ case "bottom": return { marginBottom: NEG, marginLeft: NEG, marginRight: NEG }
42
+ case "left": return { marginLeft: NEG, marginTop: NEG, marginBottom: NEG }
43
+ case "right": return { marginRight: NEG, marginTop: NEG, marginBottom: NEG }
44
+ }
45
+ }
46
+
47
+ export const Inset = React.forwardRef<HTMLDivElement, InsetProps>(
48
+ ({ className, side = "all", clip = "border-box", pt = "none", pb = "none", pl = "none", pr = "none", style, children, ...props }, ref) => {
49
+ const inlineStyle: React.CSSProperties = {
50
+ ...sideStyle(side),
51
+ ...(pt === "current" ? { paddingTop: P } : {}),
52
+ ...(pb === "current" ? { paddingBottom: P } : {}),
53
+ ...(pl === "current" ? { paddingLeft: P } : {}),
54
+ ...(pr === "current" ? { paddingRight: P } : {}),
55
+ ...style,
56
+ }
57
+
58
+ return (
59
+ <div
60
+ ref={ref}
61
+ style={inlineStyle}
62
+ className={cn(clip === "border-box" && "ax:overflow-hidden", className)}
63
+ {...props}
64
+ >
65
+ {children}
66
+ </div>
67
+ )
68
+ }
69
+ )
70
+ Inset.displayName = "Inset"
@@ -0,0 +1,29 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+ import { Users } from "lucide-react"
4
+ import { KpiCard, KpiCardGroup } from "./kpi-card"
5
+
6
+ const meta: Meta<typeof KpiCard> = {
7
+ title: "Components/KpiCard",
8
+ component: KpiCard,
9
+ tags: ["autodocs"],
10
+ }
11
+
12
+ export default meta
13
+ type Story = StoryObj<typeof KpiCard>
14
+
15
+ export const Default: Story = {
16
+ render: () => (
17
+ <KpiCardGroup className="ax:max-w-xs">
18
+ <KpiCard title="Active users" value={1284} change={12} icon={<Users className="ax:size-4" />} description="vs last 30 days" />
19
+ </KpiCardGroup>
20
+ ),
21
+ }
22
+
23
+ export const Loading: Story = {
24
+ render: () => (
25
+ <KpiCardGroup className="ax:max-w-xs">
26
+ <KpiCard title="Active users" value={0} loading />
27
+ </KpiCardGroup>
28
+ ),
29
+ }