@fragments-sdk/ui 0.11.1 → 0.13.0

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 (303) hide show
  1. package/README.md +15 -0
  2. package/dist/assets/ui.css +25 -18
  3. package/dist/blocks/AccountSettings.block.d.ts +1 -1
  4. package/dist/blocks/ActivityFeed.block.d.ts +1 -1
  5. package/dist/blocks/ActivityFeedSkeleton.block.d.ts +1 -1
  6. package/dist/blocks/BlogEditor.block.d.ts +1 -1
  7. package/dist/blocks/ChatInterface.block.d.ts +1 -1
  8. package/dist/blocks/ChatMessages.block.d.ts +1 -1
  9. package/dist/blocks/CheckoutForm.block.d.ts +1 -1
  10. package/dist/blocks/CommandPalette.block.d.ts +1 -1
  11. package/dist/blocks/ContactForm.block.d.ts +1 -1
  12. package/dist/blocks/DashboardLayout.block.d.ts +1 -1
  13. package/dist/blocks/DashboardPage.block.d.ts +1 -1
  14. package/dist/blocks/DashboardSkeleton.block.d.ts +1 -1
  15. package/dist/blocks/DataTable.block.d.ts +1 -1
  16. package/dist/blocks/EmptyState.block.d.ts +1 -1
  17. package/dist/blocks/FAQSection.block.d.ts +1 -1
  18. package/dist/blocks/FeatureGrid.block.d.ts +1 -1
  19. package/dist/blocks/HeroSection.block.d.ts +1 -1
  20. package/dist/blocks/LoginForm.block.d.ts +1 -1
  21. package/dist/blocks/NavigationHeader.block.d.ts +1 -1
  22. package/dist/blocks/PaginatedTable.block.d.ts +1 -1
  23. package/dist/blocks/PricingComparison.block.d.ts +1 -1
  24. package/dist/blocks/ProductCard.block.d.ts +1 -1
  25. package/dist/blocks/RegistrationForm.block.d.ts +1 -1
  26. package/dist/blocks/SettingsDrawer.block.d.ts +1 -1
  27. package/dist/blocks/SettingsPanel.block.d.ts +1 -1
  28. package/dist/blocks/ShoppingCart.block.d.ts +1 -1
  29. package/dist/blocks/StatsCard.block.d.ts +1 -1
  30. package/dist/blocks/StatsCardSkeleton.block.d.ts +1 -1
  31. package/dist/blocks/TableSkeleton.block.d.ts +1 -1
  32. package/dist/blocks/ThinkingStates.block.d.ts +1 -1
  33. package/dist/codeblock.cjs +7 -1
  34. package/dist/codeblock.cjs.map +1 -1
  35. package/dist/codeblock.js +7 -1
  36. package/dist/codeblock.js.map +1 -1
  37. package/dist/components/Accordion/index.cjs +11 -4
  38. package/dist/components/Accordion/index.cjs.map +1 -1
  39. package/dist/components/Accordion/index.d.ts +3 -3
  40. package/dist/components/Accordion/index.d.ts.map +1 -1
  41. package/dist/components/Accordion/index.js +11 -4
  42. package/dist/components/Accordion/index.js.map +1 -1
  43. package/dist/components/Alert/index.cjs.map +1 -1
  44. package/dist/components/Alert/index.d.ts +7 -0
  45. package/dist/components/Alert/index.d.ts.map +1 -1
  46. package/dist/components/Alert/index.js.map +1 -1
  47. package/dist/components/Avatar/index.cjs.map +1 -1
  48. package/dist/components/Avatar/index.d.ts +4 -0
  49. package/dist/components/Avatar/index.d.ts.map +1 -1
  50. package/dist/components/Avatar/index.js.map +1 -1
  51. package/dist/components/Badge/index.cjs.map +1 -1
  52. package/dist/components/Badge/index.d.ts +12 -0
  53. package/dist/components/Badge/index.d.ts.map +1 -1
  54. package/dist/components/Badge/index.js.map +1 -1
  55. package/dist/components/Button/index.cjs +9 -1
  56. package/dist/components/Button/index.cjs.map +1 -1
  57. package/dist/components/Button/index.d.ts +14 -1
  58. package/dist/components/Button/index.d.ts.map +1 -1
  59. package/dist/components/Button/index.js +9 -1
  60. package/dist/components/Button/index.js.map +1 -1
  61. package/dist/components/Card/index.cjs +2 -1
  62. package/dist/components/Card/index.cjs.map +1 -1
  63. package/dist/components/Card/index.d.ts +12 -2
  64. package/dist/components/Card/index.d.ts.map +1 -1
  65. package/dist/components/Card/index.js +2 -1
  66. package/dist/components/Card/index.js.map +1 -1
  67. package/dist/components/Checkbox/index.cjs.map +1 -1
  68. package/dist/components/Checkbox/index.d.ts +6 -1
  69. package/dist/components/Checkbox/index.d.ts.map +1 -1
  70. package/dist/components/Checkbox/index.js.map +1 -1
  71. package/dist/components/Chip/index.cjs +2 -1
  72. package/dist/components/Chip/index.cjs.map +1 -1
  73. package/dist/components/Chip/index.d.ts +10 -3
  74. package/dist/components/Chip/index.d.ts.map +1 -1
  75. package/dist/components/Chip/index.js +2 -1
  76. package/dist/components/Chip/index.js.map +1 -1
  77. package/dist/components/CodeBlock/index.d.ts +1 -1
  78. package/dist/components/CodeBlock/index.d.ts.map +1 -1
  79. package/dist/components/Collapsible/index.cjs +45 -10
  80. package/dist/components/Collapsible/index.cjs.map +1 -1
  81. package/dist/components/Collapsible/index.d.ts +6 -12
  82. package/dist/components/Collapsible/index.d.ts.map +1 -1
  83. package/dist/components/Collapsible/index.js +45 -10
  84. package/dist/components/Collapsible/index.js.map +1 -1
  85. package/dist/components/Combobox/index.cjs +18 -9
  86. package/dist/components/Combobox/index.cjs.map +1 -1
  87. package/dist/components/Combobox/index.d.ts +8 -12
  88. package/dist/components/Combobox/index.d.ts.map +1 -1
  89. package/dist/components/Combobox/index.js +18 -9
  90. package/dist/components/Combobox/index.js.map +1 -1
  91. package/dist/components/Command/index.cjs +54 -21
  92. package/dist/components/Command/index.cjs.map +1 -1
  93. package/dist/components/Command/index.d.ts +2 -2
  94. package/dist/components/Command/index.d.ts.map +1 -1
  95. package/dist/components/Command/index.js +54 -21
  96. package/dist/components/Command/index.js.map +1 -1
  97. package/dist/components/DataTable/index.cjs +13 -1
  98. package/dist/components/DataTable/index.cjs.map +1 -1
  99. package/dist/components/DataTable/index.d.ts.map +1 -1
  100. package/dist/components/DataTable/index.js +13 -1
  101. package/dist/components/DataTable/index.js.map +1 -1
  102. package/dist/components/DatePicker/index.d.ts +2 -3
  103. package/dist/components/DatePicker/index.d.ts.map +1 -1
  104. package/dist/components/Dialog/index.cjs +12 -9
  105. package/dist/components/Dialog/index.cjs.map +1 -1
  106. package/dist/components/Dialog/index.d.ts +20 -12
  107. package/dist/components/Dialog/index.d.ts.map +1 -1
  108. package/dist/components/Dialog/index.js +12 -9
  109. package/dist/components/Dialog/index.js.map +1 -1
  110. package/dist/components/Drawer/index.cjs +12 -9
  111. package/dist/components/Drawer/index.cjs.map +1 -1
  112. package/dist/components/Drawer/index.d.ts +22 -12
  113. package/dist/components/Drawer/index.d.ts.map +1 -1
  114. package/dist/components/Drawer/index.js +12 -9
  115. package/dist/components/Drawer/index.js.map +1 -1
  116. package/dist/components/Grid/index.cjs +4 -1
  117. package/dist/components/Grid/index.cjs.map +1 -1
  118. package/dist/components/Grid/index.d.ts +6 -2
  119. package/dist/components/Grid/index.d.ts.map +1 -1
  120. package/dist/components/Grid/index.js +4 -1
  121. package/dist/components/Grid/index.js.map +1 -1
  122. package/dist/components/Input/index.cjs.map +1 -1
  123. package/dist/components/Input/index.d.ts +15 -1
  124. package/dist/components/Input/index.d.ts.map +1 -1
  125. package/dist/components/Input/index.js.map +1 -1
  126. package/dist/components/Menu/index.cjs +30 -16
  127. package/dist/components/Menu/index.cjs.map +1 -1
  128. package/dist/components/Menu/index.d.ts +17 -25
  129. package/dist/components/Menu/index.d.ts.map +1 -1
  130. package/dist/components/Menu/index.js +30 -16
  131. package/dist/components/Menu/index.js.map +1 -1
  132. package/dist/components/NavigationMenu/NavigationMenuContext.cjs.map +1 -1
  133. package/dist/components/NavigationMenu/NavigationMenuContext.d.ts +1 -0
  134. package/dist/components/NavigationMenu/NavigationMenuContext.d.ts.map +1 -1
  135. package/dist/components/NavigationMenu/NavigationMenuContext.js.map +1 -1
  136. package/dist/components/NavigationMenu/index.cjs +43 -11
  137. package/dist/components/NavigationMenu/index.cjs.map +1 -1
  138. package/dist/components/NavigationMenu/index.d.ts.map +1 -1
  139. package/dist/components/NavigationMenu/index.js +43 -11
  140. package/dist/components/NavigationMenu/index.js.map +1 -1
  141. package/dist/components/NavigationMenu/useNavigationMenu.cjs +2 -0
  142. package/dist/components/NavigationMenu/useNavigationMenu.cjs.map +1 -1
  143. package/dist/components/NavigationMenu/useNavigationMenu.d.ts +1 -0
  144. package/dist/components/NavigationMenu/useNavigationMenu.d.ts.map +1 -1
  145. package/dist/components/NavigationMenu/useNavigationMenu.js +2 -0
  146. package/dist/components/NavigationMenu/useNavigationMenu.js.map +1 -1
  147. package/dist/components/Popover/index.cjs +11 -10
  148. package/dist/components/Popover/index.cjs.map +1 -1
  149. package/dist/components/Popover/index.d.ts +17 -12
  150. package/dist/components/Popover/index.d.ts.map +1 -1
  151. package/dist/components/Popover/index.js +11 -10
  152. package/dist/components/Popover/index.js.map +1 -1
  153. package/dist/components/RadioGroup/index.cjs.map +1 -1
  154. package/dist/components/RadioGroup/index.d.ts +4 -0
  155. package/dist/components/RadioGroup/index.d.ts.map +1 -1
  156. package/dist/components/RadioGroup/index.js.map +1 -1
  157. package/dist/components/Select/index.cjs +7 -6
  158. package/dist/components/Select/index.cjs.map +1 -1
  159. package/dist/components/Select/index.d.ts +20 -9
  160. package/dist/components/Select/index.d.ts.map +1 -1
  161. package/dist/components/Select/index.js +7 -6
  162. package/dist/components/Select/index.js.map +1 -1
  163. package/dist/components/Sidebar/index.cjs +71 -24
  164. package/dist/components/Sidebar/index.cjs.map +1 -1
  165. package/dist/components/Sidebar/index.d.ts +21 -33
  166. package/dist/components/Sidebar/index.d.ts.map +1 -1
  167. package/dist/components/Sidebar/index.js +71 -24
  168. package/dist/components/Sidebar/index.js.map +1 -1
  169. package/dist/components/Slider/index.cjs +3 -1
  170. package/dist/components/Slider/index.cjs.map +1 -1
  171. package/dist/components/Slider/index.d.ts +10 -0
  172. package/dist/components/Slider/index.d.ts.map +1 -1
  173. package/dist/components/Slider/index.js +3 -1
  174. package/dist/components/Slider/index.js.map +1 -1
  175. package/dist/components/Stack/index.cjs +6 -0
  176. package/dist/components/Stack/index.cjs.map +1 -1
  177. package/dist/components/Stack/index.d.ts +12 -6
  178. package/dist/components/Stack/index.d.ts.map +1 -1
  179. package/dist/components/Stack/index.js +6 -0
  180. package/dist/components/Stack/index.js.map +1 -1
  181. package/dist/components/Tabs/index.cjs.map +1 -1
  182. package/dist/components/Tabs/index.d.ts +13 -1
  183. package/dist/components/Tabs/index.d.ts.map +1 -1
  184. package/dist/components/Tabs/index.js.map +1 -1
  185. package/dist/components/Text/Text.module.scss.cjs +44 -32
  186. package/dist/components/Text/Text.module.scss.cjs.map +1 -1
  187. package/dist/components/Text/Text.module.scss.js +44 -32
  188. package/dist/components/Text/Text.module.scss.js.map +1 -1
  189. package/dist/components/Text/index.cjs.map +1 -1
  190. package/dist/components/Text/index.d.ts +18 -3
  191. package/dist/components/Text/index.d.ts.map +1 -1
  192. package/dist/components/Text/index.js.map +1 -1
  193. package/dist/components/Theme/index.cjs.map +1 -1
  194. package/dist/components/Theme/index.d.ts +12 -0
  195. package/dist/components/Theme/index.d.ts.map +1 -1
  196. package/dist/components/Theme/index.js.map +1 -1
  197. package/dist/components/Toggle/index.cjs +2 -1
  198. package/dist/components/Toggle/index.cjs.map +1 -1
  199. package/dist/components/Toggle/index.d.ts +9 -0
  200. package/dist/components/Toggle/index.d.ts.map +1 -1
  201. package/dist/components/Toggle/index.js +2 -1
  202. package/dist/components/Toggle/index.js.map +1 -1
  203. package/dist/components/ToggleGroup/index.cjs +4 -1
  204. package/dist/components/ToggleGroup/index.cjs.map +1 -1
  205. package/dist/components/ToggleGroup/index.d.ts +13 -4
  206. package/dist/components/ToggleGroup/index.d.ts.map +1 -1
  207. package/dist/components/ToggleGroup/index.js +4 -1
  208. package/dist/components/ToggleGroup/index.js.map +1 -1
  209. package/dist/components/Tooltip/index.cjs +20 -10
  210. package/dist/components/Tooltip/index.cjs.map +1 -1
  211. package/dist/components/Tooltip/index.d.ts +5 -1
  212. package/dist/components/Tooltip/index.d.ts.map +1 -1
  213. package/dist/components/Tooltip/index.js +20 -10
  214. package/dist/components/Tooltip/index.js.map +1 -1
  215. package/dist/datepicker.cjs +24 -10
  216. package/dist/datepicker.cjs.map +1 -1
  217. package/dist/datepicker.js +24 -10
  218. package/dist/datepicker.js.map +1 -1
  219. package/dist/index.cjs +4 -0
  220. package/dist/index.cjs.map +1 -1
  221. package/dist/index.d.ts.map +1 -1
  222. package/dist/index.js +4 -0
  223. package/dist/index.js.map +1 -1
  224. package/dist/utils/css-warning.cjs +18 -0
  225. package/dist/utils/css-warning.cjs.map +1 -0
  226. package/dist/utils/css-warning.d.ts +2 -0
  227. package/dist/utils/css-warning.d.ts.map +1 -0
  228. package/dist/utils/css-warning.js +18 -0
  229. package/dist/utils/css-warning.js.map +1 -0
  230. package/fragments.json +1 -1
  231. package/package.json +2 -2
  232. package/src/components/Accordion/Accordion.test.tsx +33 -0
  233. package/src/components/Accordion/index.tsx +10 -3
  234. package/src/components/Alert/index.tsx +7 -0
  235. package/src/components/Avatar/index.tsx +4 -0
  236. package/src/components/Badge/Badge.fragment.tsx +10 -2
  237. package/src/components/Badge/index.tsx +12 -0
  238. package/src/components/Button/Button.fragment.tsx +12 -2
  239. package/src/components/Button/Button.test.tsx +16 -0
  240. package/src/components/Button/index.tsx +27 -2
  241. package/src/components/Card/Card.fragment.tsx +14 -2
  242. package/src/components/Card/Card.test.tsx +5 -0
  243. package/src/components/Card/index.tsx +15 -2
  244. package/src/components/Checkbox/index.tsx +6 -1
  245. package/src/components/Chip/Chip.fragment.tsx +12 -2
  246. package/src/components/Chip/Chip.test.tsx +5 -0
  247. package/src/components/Chip/index.tsx +14 -4
  248. package/src/components/CodeBlock/index.tsx +13 -2
  249. package/src/components/Collapsible/Collapsible.test.tsx +41 -0
  250. package/src/components/Collapsible/index.tsx +53 -16
  251. package/src/components/Combobox/Combobox.test.tsx +55 -0
  252. package/src/components/Combobox/index.tsx +23 -17
  253. package/src/components/Command/Command.test.tsx +93 -0
  254. package/src/components/Command/index.tsx +61 -18
  255. package/src/components/DataTable/DataTable.test.tsx +11 -2
  256. package/src/components/DataTable/index.tsx +22 -2
  257. package/src/components/DatePicker/DatePicker.test.tsx +79 -0
  258. package/src/components/DatePicker/index.tsx +29 -14
  259. package/src/components/Dialog/Dialog.test.tsx +23 -0
  260. package/src/components/Dialog/index.tsx +27 -16
  261. package/src/components/Drawer/Drawer.test.tsx +27 -0
  262. package/src/components/Drawer/index.tsx +29 -16
  263. package/src/components/Grid/Grid.fragment.tsx +14 -2
  264. package/src/components/Grid/Grid.test.tsx +6 -0
  265. package/src/components/Grid/index.tsx +12 -3
  266. package/src/components/Input/index.tsx +15 -1
  267. package/src/components/Menu/index.tsx +35 -30
  268. package/src/components/NavigationMenu/NavigationMenu.fragment.tsx +1 -1
  269. package/src/components/NavigationMenu/NavigationMenu.test.tsx +40 -4
  270. package/src/components/NavigationMenu/NavigationMenuContext.ts +3 -0
  271. package/src/components/NavigationMenu/index.tsx +49 -13
  272. package/src/components/NavigationMenu/useNavigationMenu.ts +4 -0
  273. package/src/components/Popover/Popover.test.tsx +23 -0
  274. package/src/components/Popover/index.tsx +24 -18
  275. package/src/components/RadioGroup/index.tsx +4 -0
  276. package/src/components/Select/Select.test.tsx +41 -0
  277. package/src/components/Select/index.tsx +24 -12
  278. package/src/components/Sidebar/Sidebar.test.tsx +83 -4
  279. package/src/components/Sidebar/index.tsx +87 -45
  280. package/src/components/Slider/Slider.fragment.tsx +5 -1
  281. package/src/components/Slider/Slider.test.tsx +6 -0
  282. package/src/components/Slider/index.tsx +13 -1
  283. package/src/components/Stack/Stack.fragment.tsx +22 -2
  284. package/src/components/Stack/Stack.test.tsx +6 -0
  285. package/src/components/Stack/index.tsx +20 -6
  286. package/src/components/Tabs/index.tsx +13 -1
  287. package/src/components/Text/Text.fragment.tsx +10 -8
  288. package/src/components/Text/Text.module.scss +8 -2
  289. package/src/components/Text/Text.test.tsx +15 -0
  290. package/src/components/Text/index.tsx +18 -3
  291. package/src/components/Theme/index.tsx +12 -0
  292. package/src/components/Toggle/Toggle.fragment.tsx +5 -1
  293. package/src/components/Toggle/Toggle.test.tsx +19 -0
  294. package/src/components/Toggle/index.tsx +11 -1
  295. package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +5 -2
  296. package/src/components/ToggleGroup/ToggleGroup.test.tsx +20 -0
  297. package/src/components/ToggleGroup/index.tsx +15 -4
  298. package/src/components/Tooltip/Tooltip.test.tsx +17 -0
  299. package/src/components/Tooltip/index.tsx +58 -34
  300. package/src/index.ts +6 -0
  301. package/src/tokens/_seeds.scss +5 -3
  302. package/src/tokens/_variables.scss +2 -0
  303. package/src/utils/css-warning.ts +29 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragments-sdk/ui",
3
- "version": "0.11.1",
3
+ "version": "0.13.0",
4
4
  "license": "MIT",
5
5
  "description": "Customizable UI components built on Base UI headless primitives",
6
6
  "author": "Conan McNicholl",
@@ -230,7 +230,7 @@
230
230
  "vite": "^6.0.0",
231
231
  "vitest": "^2.1.8",
232
232
  "vitest-axe": "^0.1.0",
233
- "@fragments-sdk/cli": "0.9.1"
233
+ "@fragments-sdk/cli": "0.10.1"
234
234
  },
235
235
  "files": [
236
236
  "src",
@@ -148,6 +148,22 @@ describe('Accordion', () => {
148
148
  expect(trigger).toHaveAttribute('aria-expanded', 'false');
149
149
  });
150
150
 
151
+ it('emits undefined when a single collapsible accordion fully closes', async () => {
152
+ const user = userEvent.setup();
153
+ const onValueChange = vi.fn();
154
+
155
+ renderAccordion({
156
+ type: 'single',
157
+ collapsible: true,
158
+ defaultValue: 'one',
159
+ onValueChange,
160
+ });
161
+
162
+ await user.click(screen.getByRole('button', { name: /item one/i }));
163
+
164
+ expect(onValueChange).toHaveBeenCalledWith(undefined);
165
+ });
166
+
151
167
  it('non-collapsible single type prevents full collapse', async () => {
152
168
  const user = userEvent.setup();
153
169
  renderAccordion({ type: 'single', collapsible: false, defaultValue: 'one' });
@@ -160,6 +176,23 @@ describe('Accordion', () => {
160
176
  expect(trigger).toHaveAttribute('aria-expanded', 'true');
161
177
  });
162
178
 
179
+ it('forwards html props to trigger and content', async () => {
180
+ const user = userEvent.setup();
181
+ render(
182
+ <Accordion>
183
+ <Accordion.Item value="one">
184
+ <Accordion.Trigger data-testid="trigger" data-track="accordion-trigger">Item One</Accordion.Trigger>
185
+ <Accordion.Content data-testid="content" aria-label="Accordion panel">Content One</Accordion.Content>
186
+ </Accordion.Item>
187
+ </Accordion>
188
+ );
189
+
190
+ await user.click(screen.getByTestId('trigger'));
191
+
192
+ expect(screen.getByTestId('trigger')).toHaveAttribute('data-track', 'accordion-trigger');
193
+ expect(screen.getByTestId('content')).toHaveAttribute('aria-label', 'Accordion panel');
194
+ });
195
+
163
196
  it('has no accessibility violations', async () => {
164
197
  const { container } = renderAccordion({ defaultValue: 'one' });
165
198
  await expectNoA11yViolations(container, {
@@ -19,7 +19,7 @@ export interface AccordionProps extends Omit<React.HTMLAttributes<HTMLDivElement
19
19
  /** Default value for uncontrolled usage */
20
20
  defaultValue?: AccordionValue;
21
21
  /** Callback when value changes */
22
- onValueChange?: (value: AccordionValue) => void;
22
+ onValueChange?: (value: AccordionValue | undefined) => void;
23
23
  /** Whether items can be fully collapsed (only for type="single") */
24
24
  collapsible?: boolean;
25
25
  /**
@@ -137,7 +137,7 @@ function AccordionRoot({
137
137
  }
138
138
 
139
139
  if (onValueChange) {
140
- onValueChange(type === 'single' ? (newItems[0] ?? '') : newItems);
140
+ onValueChange(type === 'single' ? newItems[0] : newItems);
141
141
  }
142
142
  }, [type, currentOpenItems, collapsible, controlledOpenItems, onValueChange]);
143
143
 
@@ -186,11 +186,15 @@ function AccordionItem({
186
186
  function AccordionTrigger({
187
187
  children,
188
188
  className,
189
+ onClick,
190
+ ...htmlProps
189
191
  }: AccordionTriggerProps) {
190
192
  const { toggle, headingLevel } = useAccordionContext();
191
193
  const { value, isOpen, disabled, triggerId, contentId } = useAccordionItemContext();
192
194
 
193
- const handleClick = () => {
195
+ const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
196
+ onClick?.(event);
197
+ if (event.defaultPrevented) return;
194
198
  if (!disabled) {
195
199
  toggle(value);
196
200
  }
@@ -204,6 +208,7 @@ function AccordionTrigger({
204
208
  return (
205
209
  <HeadingTag className={styles.heading}>
206
210
  <BaseCollapsible.Trigger
211
+ {...htmlProps}
207
212
  id={triggerId}
208
213
  className={classes}
209
214
  onClick={handleClick}
@@ -237,6 +242,7 @@ function AccordionTrigger({
237
242
  function AccordionContent({
238
243
  children,
239
244
  className,
245
+ ...htmlProps
240
246
  }: AccordionContentProps) {
241
247
  const { isOpen, triggerId, contentId } = useAccordionItemContext();
242
248
 
@@ -244,6 +250,7 @@ function AccordionContent({
244
250
 
245
251
  return (
246
252
  <BaseCollapsible.Panel
253
+ {...htmlProps}
247
254
  id={contentId}
248
255
  className={classes}
249
256
  data-state={isOpen ? 'open' : 'closed'}
@@ -10,8 +10,15 @@ import styles from './Alert.module.scss';
10
10
 
11
11
  export type AlertSeverity = 'info' | 'success' | 'warning' | 'error';
12
12
 
13
+ /**
14
+ * Alert for contextual feedback messages (info, success, warning, error).
15
+ * @see https://usefragments.com/components/alert
16
+ */
13
17
  export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
14
18
  children: React.ReactNode;
19
+ /** Alert severity level. Controls color and default icon.
20
+ * @default "info"
21
+ * @see https://usefragments.com/components/alert#variants */
15
22
  severity?: AlertSeverity;
16
23
  }
17
24
 
@@ -9,6 +9,10 @@ import styles from './Avatar.module.scss';
9
9
 
10
10
  export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
11
11
 
12
+ /**
13
+ * Avatar for user photos, initials, or placeholder icons.
14
+ * @see https://usefragments.com/components/avatar
15
+ */
12
16
  export interface AvatarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color'> {
13
17
  /** Image source URL */
14
18
  src?: string;
@@ -50,7 +50,7 @@ export default defineFragment({
50
50
  variant: {
51
51
  type: 'enum',
52
52
  description: 'Visual style indicating severity or category',
53
- values: ['default', 'success', 'warning', 'error', 'info'],
53
+ values: ['default', 'success', 'warning', 'error', 'info', 'outline'],
54
54
  default: 'default',
55
55
  },
56
56
  size: {
@@ -82,7 +82,7 @@ export default defineFragment({
82
82
  contract: {
83
83
  propsSummary: [
84
84
  'children: ReactNode - badge label (required)',
85
- 'variant: default|success|warning|error|info - visual style',
85
+ 'variant: default|success|warning|error|info|outline - visual style',
86
86
  'size: sm|md - badge size',
87
87
  'dot: boolean - show status dot indicator',
88
88
  'onRemove: () => void - makes badge removable',
@@ -147,6 +147,14 @@ import { Stack } from '@/components/Stack';
147
147
  </Stack>
148
148
  ),
149
149
  },
150
+ {
151
+ name: 'Outline',
152
+ description: 'Minimal bordered badge for neutral emphasis',
153
+ code: `import { Badge } from '@/components/Badge';
154
+
155
+ <Badge variant="outline">Outline</Badge>`,
156
+ render: () => <Badge variant="outline">Outline</Badge>,
157
+ },
150
158
  {
151
159
  name: 'Small Size',
152
160
  description: 'Compact badges for dense UIs',
@@ -4,12 +4,24 @@ import * as React from 'react';
4
4
  import { Button as BaseButton } from '@base-ui/react/button';
5
5
  import styles from './Badge.module.scss';
6
6
 
7
+ /**
8
+ * Badge for status indicators, labels, and counts.
9
+ * @see https://usefragments.com/components/badge
10
+ */
7
11
  export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
8
12
  children: React.ReactNode;
13
+ /** Visual style variant.
14
+ * @default "default"
15
+ * @see https://usefragments.com/components/badge#variants */
9
16
  variant?: 'default' | 'success' | 'warning' | 'error' | 'info' | 'outline';
17
+ /** Badge size.
18
+ * @default "md" */
10
19
  size?: 'sm' | 'md' | 'lg';
20
+ /** Show a status dot before the label */
11
21
  dot?: boolean;
22
+ /** Icon element rendered before the label */
12
23
  icon?: React.ReactNode;
24
+ /** Makes the badge removable. Called when dismiss button is clicked. */
13
25
  onRemove?: () => void;
14
26
  }
15
27
 
@@ -46,7 +46,7 @@ export default defineFragment({
46
46
  },
47
47
  variant: {
48
48
  type: 'enum',
49
- values: ['primary', 'secondary', 'ghost', 'danger'],
49
+ values: ['primary', 'secondary', 'ghost', 'danger', 'outlined', 'outline'],
50
50
  default: 'primary',
51
51
  description: 'Visual style variant',
52
52
  constraints: ['Only one primary button per context'],
@@ -63,6 +63,11 @@ export default defineFragment({
63
63
  default: 'button',
64
64
  description: 'Render as a native button or anchor element',
65
65
  },
66
+ asChild: {
67
+ type: 'boolean',
68
+ default: 'false',
69
+ description: 'Merge button styling onto child element (e.g. Next.js Link)',
70
+ },
66
71
  icon: {
67
72
  type: 'boolean',
68
73
  default: 'false',
@@ -95,7 +100,7 @@ export default defineFragment({
95
100
 
96
101
  contract: {
97
102
  propsSummary: [
98
- 'variant: primary|secondary|ghost|danger (default: primary)',
103
+ 'variant: primary|secondary|ghost|danger|outlined|outline (default: primary)',
99
104
  'size: sm|md|lg (default: md)',
100
105
  'disabled: boolean - disables interaction',
101
106
  'type: button|submit|reset (default: button)',
@@ -140,6 +145,11 @@ export default defineFragment({
140
145
  description: 'Destructive action requiring attention',
141
146
  render: () => <Button variant="danger">Delete Item</Button>,
142
147
  },
148
+ {
149
+ name: 'Outline',
150
+ description: 'Bordered button with transparent background',
151
+ render: () => <Button variant="outline">View Details</Button>,
152
+ },
143
153
  {
144
154
  name: 'Sizes',
145
155
  description: 'Available size options',
@@ -46,6 +46,22 @@ describe('Button', () => {
46
46
  expect(ref).toHaveBeenCalled();
47
47
  });
48
48
 
49
+ it('resolves variant="outline" to "outlined"', () => {
50
+ render(<Button variant="outline">Outline</Button>);
51
+ expect(screen.getByRole('button')).toHaveClass('outlined');
52
+ });
53
+
54
+ it('renders as child element when asChild is true', () => {
55
+ render(
56
+ <Button asChild>
57
+ <a href="/test">Link Button</a>
58
+ </Button>
59
+ );
60
+ const link = screen.getByRole('link', { name: 'Link Button' });
61
+ expect(link).toHaveAttribute('href', '/test');
62
+ expect(link).toHaveClass('button');
63
+ });
64
+
49
65
  it('has no accessibility violations', async () => {
50
66
  const { container } = render(<Button>Accessible</Button>);
51
67
  await expectNoA11yViolations(container);
@@ -4,14 +4,27 @@ import * as React from 'react';
4
4
  import { Button as BaseButton } from '@base-ui/react/button';
5
5
  import styles from './Button.module.scss';
6
6
 
7
+ /**
8
+ * Button props.
9
+ * @see https://usefragments.com/components/button
10
+ */
7
11
  type ButtonBaseProps = {
8
12
  children: React.ReactNode;
9
- variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'outlined';
13
+ /** Visual style variant. `"outline"` is an alias for `"outlined"`.
14
+ * @default "primary"
15
+ * @see https://usefragments.com/components/button#variants */
16
+ variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'outlined' | 'outline';
17
+ /** Button size.
18
+ * @default "md"
19
+ * @see https://usefragments.com/components/button#sizes */
10
20
  size?: 'sm' | 'md' | 'lg';
11
21
  /** Render as icon-only button (square aspect ratio) */
12
22
  icon?: boolean;
13
23
  /** Make button full width of container */
14
24
  fullWidth?: boolean;
25
+ /** Merge props onto child element instead of rendering a button. Useful for composition with Link components.
26
+ * @see https://usefragments.com/components/button#aschild */
27
+ asChild?: boolean;
15
28
  };
16
29
 
17
30
  // Button as native button element
@@ -36,14 +49,18 @@ const ButtonRoot = React.forwardRef<
36
49
  >(function Button(props, ref) {
37
50
  const {
38
51
  children,
39
- variant = 'primary',
52
+ variant: variantProp = 'primary',
40
53
  size = 'md',
41
54
  icon = false,
42
55
  fullWidth = false,
56
+ asChild = false,
43
57
  className,
44
58
  ...rest
45
59
  } = props;
46
60
 
61
+ // Resolve alias: "outline" → "outlined"
62
+ const variant = variantProp === 'outline' ? 'outlined' : variantProp;
63
+
47
64
  const classNames = [
48
65
  styles.button,
49
66
  styles[size],
@@ -55,6 +72,14 @@ const ButtonRoot = React.forwardRef<
55
72
  .filter(Boolean)
56
73
  .join(' ');
57
74
 
75
+ // asChild: merge button styling onto child element (e.g. Next.js Link)
76
+ if (asChild && React.isValidElement(children)) {
77
+ return React.cloneElement(children as React.ReactElement<Record<string, unknown>>, {
78
+ className: [classNames, (children.props as Record<string, unknown>).className].filter(Boolean).join(' '),
79
+ ref,
80
+ });
81
+ }
82
+
58
83
  // Render as anchor
59
84
  if (props.as === 'a') {
60
85
  const { as: _as, ...anchorProps } = rest as ButtonAsAnchorProps & { as?: 'a' };
@@ -44,7 +44,7 @@ export default defineFragment({
44
44
  },
45
45
  variant: {
46
46
  type: 'enum',
47
- values: ['default', 'outlined', 'elevated'],
47
+ values: ['default', 'outlined', 'outline', 'elevated'],
48
48
  default: 'default',
49
49
  description: 'Visual style of the card surface',
50
50
  constraints: ['Use "elevated" sparingly to maintain visual hierarchy'],
@@ -76,7 +76,7 @@ export default defineFragment({
76
76
 
77
77
  contract: {
78
78
  propsSummary: [
79
- 'variant: default|outlined|elevated (default: default)',
79
+ 'variant: default|outlined|outline|elevated (default: default)',
80
80
  'padding: none|sm|md|lg (default: md)',
81
81
  'onClick: () => void - makes card interactive',
82
82
  'Sub-components: Card.Header, Card.Title, Card.Description, Card.Body, Card.Footer',
@@ -130,6 +130,18 @@ export default defineFragment({
130
130
  </Card>
131
131
  ),
132
132
  },
133
+ {
134
+ name: 'Outline',
135
+ description: 'Card with border, using the "outline" alias for "outlined"',
136
+ render: () => (
137
+ <Card variant="outline">
138
+ <Card.Header>
139
+ <Card.Title>Outline Card</Card.Title>
140
+ </Card.Header>
141
+ <Card.Body>Uses the Radix/Shadcn-style alias.</Card.Body>
142
+ </Card>
143
+ ),
144
+ },
133
145
  {
134
146
  name: 'Elevated',
135
147
  description: 'Card with prominent shadow for emphasis',
@@ -57,6 +57,11 @@ describe('Card', () => {
57
57
  expect(screen.getByRole('button')).toHaveClass('interactive');
58
58
  });
59
59
 
60
+ it('resolves variant="outline" to "outlined"', () => {
61
+ render(<Card variant="outline">Content</Card>);
62
+ expect(screen.getByRole('article')).toHaveClass('outlined');
63
+ });
64
+
60
65
  it('has no accessibility violations', async () => {
61
66
  const { container } = render(
62
67
  <Card>
@@ -7,10 +7,20 @@ import styles from './Card.module.scss';
7
7
  // Types
8
8
  // ============================================
9
9
 
10
+ /**
11
+ * Card container for grouping related content.
12
+ * @see https://usefragments.com/components/card
13
+ */
10
14
  export interface CardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onClick'> {
11
15
  children: React.ReactNode;
12
- variant?: 'default' | 'outlined' | 'elevated';
16
+ /** Visual style variant. `"outline"` is an alias for `"outlined"`.
17
+ * @default "default"
18
+ * @see https://usefragments.com/components/card#variants */
19
+ variant?: 'default' | 'outlined' | 'outline' | 'elevated';
20
+ /** Inner padding.
21
+ * @default "md" */
13
22
  padding?: 'none' | 'sm' | 'md' | 'lg';
23
+ /** Makes the card interactive (clickable) */
14
24
  onClick?: () => void;
15
25
  }
16
26
 
@@ -71,7 +81,7 @@ const paddingMap = {
71
81
 
72
82
  function CardRoot({
73
83
  children,
74
- variant = 'default',
84
+ variant: variantProp = 'default',
75
85
  padding = 'md',
76
86
  onClick,
77
87
  className,
@@ -80,6 +90,9 @@ function CardRoot({
80
90
  'aria-describedby': ariaDescribedBy,
81
91
  ...htmlProps
82
92
  }: CardProps) {
93
+ // Resolve alias: "outline" → "outlined"
94
+ const variant = variantProp === 'outline' ? 'outlined' : variantProp;
95
+
83
96
  const isInteractive = !!onClick;
84
97
 
85
98
  const classes = [
@@ -8,6 +8,10 @@ import styles from './Checkbox.module.scss';
8
8
  // Types
9
9
  // ============================================
10
10
 
11
+ /**
12
+ * Checkbox for boolean or indeterminate selections in forms.
13
+ * @see https://usefragments.com/components/checkbox
14
+ */
11
15
  export interface CheckboxProps extends Omit<React.HTMLAttributes<HTMLLabelElement>, 'onChange' | 'defaultChecked'> {
12
16
  /** Whether the checkbox is checked */
13
17
  checked?: boolean;
@@ -21,7 +25,8 @@ export interface CheckboxProps extends Omit<React.HTMLAttributes<HTMLLabelElemen
21
25
  disabled?: boolean;
22
26
  /** Whether the checkbox is required */
23
27
  required?: boolean;
24
- /** Size variant */
28
+ /** Size variant.
29
+ * @default "md" */
25
30
  size?: 'sm' | 'md' | 'lg';
26
31
  /** Label text */
27
32
  label?: string;
@@ -50,7 +50,7 @@ export default defineFragment({
50
50
  variant: {
51
51
  type: 'enum',
52
52
  description: 'Visual style variant',
53
- values: ['filled', 'outlined', 'soft'],
53
+ values: ['filled', 'outlined', 'outline', 'soft'],
54
54
  default: 'filled',
55
55
  },
56
56
  size: {
@@ -91,7 +91,7 @@ export default defineFragment({
91
91
  contract: {
92
92
  propsSummary: [
93
93
  'children: ReactNode - chip label (required)',
94
- 'variant: filled|outlined|soft - visual style',
94
+ 'variant: filled|outlined|outline|soft - visual style',
95
95
  'size: sm|md - chip size',
96
96
  'selected: boolean - selection state',
97
97
  'icon/avatar: ReactNode - leading visual',
@@ -113,6 +113,16 @@ export default defineFragment({
113
113
  description: 'Basic filled chip',
114
114
  render: () => <Chip>Default</Chip>,
115
115
  },
116
+ {
117
+ name: 'Outline',
118
+ description: 'Chip with border using the "outline" alias for "outlined"',
119
+ render: () => (
120
+ <div style={{ display: 'flex', gap: '8px' }}>
121
+ <Chip variant="outline">Outline</Chip>
122
+ <Chip variant="outline" selected>Outline Selected</Chip>
123
+ </div>
124
+ ),
125
+ },
116
126
  {
117
127
  name: 'Selected',
118
128
  description: 'Chip in selected state across variants',
@@ -43,6 +43,11 @@ describe('Chip', () => {
43
43
  expect(handleClick).toHaveBeenCalledTimes(1);
44
44
  });
45
45
 
46
+ it('resolves variant="outline" to "outlined"', () => {
47
+ render(<Chip variant="outline">Outline</Chip>);
48
+ expect(screen.getByRole('button', { name: 'Outline' })).toHaveClass('outlined');
49
+ });
50
+
46
51
  it('has no accessibility violations', async () => {
47
52
  const { container } = render(<Chip>Accessible chip</Chip>);
48
53
  await expectNoA11yViolations(container);
@@ -3,11 +3,18 @@
3
3
  import * as React from 'react';
4
4
  import styles from './Chip.module.scss';
5
5
 
6
+ /**
7
+ * Chip for selections, filters, and tags. Use with Chip.Group for multi-select.
8
+ * @see https://usefragments.com/components/chip
9
+ */
6
10
  export interface ChipProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
7
11
  children: React.ReactNode;
8
- /** Visual style variant */
9
- variant?: 'filled' | 'outlined' | 'soft';
10
- /** Size of the chip */
12
+ /** Visual style variant. `"outline"` is an alias for `"outlined"`.
13
+ * @default "filled"
14
+ * @see https://usefragments.com/components/chip#variants */
15
+ variant?: 'filled' | 'outlined' | 'outline' | 'soft';
16
+ /** Chip size.
17
+ * @default "md" */
11
18
  size?: 'sm' | 'md' | 'lg';
12
19
  /** Whether the chip is selected */
13
20
  selected?: boolean;
@@ -36,7 +43,7 @@ const ChipBase = React.forwardRef<HTMLButtonElement, ChipProps>(
36
43
  function Chip(
37
44
  {
38
45
  children,
39
- variant = 'filled',
46
+ variant: variantProp = 'filled',
40
47
  size = 'md',
41
48
  selected = false,
42
49
  disabled = false,
@@ -50,6 +57,9 @@ const ChipBase = React.forwardRef<HTMLButtonElement, ChipProps>(
50
57
  },
51
58
  ref
52
59
  ) {
60
+ // Resolve alias: "outline" → "outlined"
61
+ const variant = variantProp === 'outline' ? 'outlined' : variantProp;
62
+
53
63
  const classes = [
54
64
  styles.chip,
55
65
  styles[size],
@@ -35,7 +35,9 @@ import "../../styles/globals.scss";
35
35
  export type CodeBlockLanguage =
36
36
  | "tsx"
37
37
  | "typescript"
38
+ | "ts"
38
39
  | "javascript"
40
+ | "js"
39
41
  | "jsx"
40
42
  | "bash"
41
43
  | "shell"
@@ -64,7 +66,15 @@ export type CodeBlockLanguage =
64
66
  | "sql"
65
67
  | "graphql"
66
68
  | "diff"
67
- | "plaintext";
69
+ | "plaintext"
70
+ | "text";
71
+
72
+ /** Resolves language aliases to their canonical Shiki names */
73
+ const LANGUAGE_ALIASES: Partial<Record<CodeBlockLanguage, string>> = {
74
+ ts: "typescript",
75
+ js: "javascript",
76
+ text: "plaintext",
77
+ };
68
78
 
69
79
  /** Available syntax highlighting themes */
70
80
  export type CodeBlockTheme =
@@ -591,7 +601,8 @@ const CodeBlockBase = React.forwardRef<HTMLDivElement, CodeBlockProps>(function
591
601
  }
592
602
 
593
603
  try {
594
- const html = await _codeToHtml(visibleCode, { lang: language, theme });
604
+ const resolvedLang = LANGUAGE_ALIASES[language] || language;
605
+ const html = await _codeToHtml(visibleCode, { lang: resolvedLang, theme });
595
606
  return processShikiHtml(html, {
596
607
  showLineNumbers,
597
608
  startLineNumber,
@@ -96,6 +96,47 @@ describe('Collapsible', () => {
96
96
  expect(screen.queryByText('Collapsible content here')).not.toBeInTheDocument();
97
97
  });
98
98
 
99
+ it('composes child handlers when trigger uses asChild', async () => {
100
+ const user = userEvent.setup();
101
+ const childClick = vi.fn();
102
+ const childKeyDown = vi.fn();
103
+
104
+ render(
105
+ <Collapsible>
106
+ <Collapsible.Trigger asChild>
107
+ <button onClick={childClick} onKeyDown={childKeyDown}>Toggle</button>
108
+ </Collapsible.Trigger>
109
+ <Collapsible.Content>Collapsible content here</Collapsible.Content>
110
+ </Collapsible>
111
+ );
112
+
113
+ const trigger = screen.getByRole('button', { name: /toggle/i });
114
+ await user.click(trigger);
115
+ expect(childClick).toHaveBeenCalled();
116
+ expect(screen.getByText('Collapsible content here')).toBeInTheDocument();
117
+
118
+ await user.keyboard('{Enter}');
119
+ expect(childKeyDown).toHaveBeenCalled();
120
+ });
121
+
122
+ it('forwards html props to root, trigger, and content', async () => {
123
+ const user = userEvent.setup();
124
+ render(
125
+ <Collapsible data-testid="root" data-track="collapsible-root">
126
+ <Collapsible.Trigger data-testid="trigger" aria-label="Toggle section">Toggle</Collapsible.Trigger>
127
+ <Collapsible.Content data-testid="content" data-panel="details">
128
+ Collapsible content here
129
+ </Collapsible.Content>
130
+ </Collapsible>
131
+ );
132
+
133
+ await user.click(screen.getByTestId('trigger'));
134
+
135
+ expect(screen.getByTestId('root')).toHaveAttribute('data-track', 'collapsible-root');
136
+ expect(screen.getByTestId('trigger')).toHaveAttribute('aria-label', 'Toggle section');
137
+ expect(screen.getByTestId('content')).toHaveAttribute('data-panel', 'details');
138
+ });
139
+
99
140
  it('has no accessibility violations', async () => {
100
141
  const { container } = renderCollapsible({ defaultOpen: true });
101
142
  await expectNoA11yViolations(container);