@fragments-sdk/ui 0.11.0 → 0.12.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 (220) hide show
  1. package/README.md +15 -0
  2. package/dist/assets/ui.css +50 -44
  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 +44 -11
  34. package/dist/codeblock.cjs.map +1 -1
  35. package/dist/codeblock.js +22 -11
  36. package/dist/codeblock.js.map +1 -1
  37. package/dist/components/Alert/index.cjs.map +1 -1
  38. package/dist/components/Alert/index.d.ts +7 -0
  39. package/dist/components/Alert/index.d.ts.map +1 -1
  40. package/dist/components/Alert/index.js.map +1 -1
  41. package/dist/components/AppShell/AppShell.module.scss.cjs +14 -14
  42. package/dist/components/AppShell/AppShell.module.scss.js +14 -14
  43. package/dist/components/Avatar/index.cjs.map +1 -1
  44. package/dist/components/Avatar/index.d.ts +4 -0
  45. package/dist/components/Avatar/index.d.ts.map +1 -1
  46. package/dist/components/Avatar/index.js.map +1 -1
  47. package/dist/components/Badge/index.cjs.map +1 -1
  48. package/dist/components/Badge/index.d.ts +12 -0
  49. package/dist/components/Badge/index.d.ts.map +1 -1
  50. package/dist/components/Badge/index.js.map +1 -1
  51. package/dist/components/Button/index.cjs +9 -1
  52. package/dist/components/Button/index.cjs.map +1 -1
  53. package/dist/components/Button/index.d.ts +14 -1
  54. package/dist/components/Button/index.d.ts.map +1 -1
  55. package/dist/components/Button/index.js +9 -1
  56. package/dist/components/Button/index.js.map +1 -1
  57. package/dist/components/Card/index.cjs +2 -1
  58. package/dist/components/Card/index.cjs.map +1 -1
  59. package/dist/components/Card/index.d.ts +12 -2
  60. package/dist/components/Card/index.d.ts.map +1 -1
  61. package/dist/components/Card/index.js +2 -1
  62. package/dist/components/Card/index.js.map +1 -1
  63. package/dist/components/Checkbox/index.cjs.map +1 -1
  64. package/dist/components/Checkbox/index.d.ts +6 -1
  65. package/dist/components/Checkbox/index.d.ts.map +1 -1
  66. package/dist/components/Checkbox/index.js.map +1 -1
  67. package/dist/components/Chip/index.cjs +2 -1
  68. package/dist/components/Chip/index.cjs.map +1 -1
  69. package/dist/components/Chip/index.d.ts +10 -3
  70. package/dist/components/Chip/index.d.ts.map +1 -1
  71. package/dist/components/Chip/index.js +2 -1
  72. package/dist/components/Chip/index.js.map +1 -1
  73. package/dist/components/CodeBlock/index.d.ts +1 -1
  74. package/dist/components/CodeBlock/index.d.ts.map +1 -1
  75. package/dist/components/Dialog/index.cjs.map +1 -1
  76. package/dist/components/Dialog/index.d.ts +12 -0
  77. package/dist/components/Dialog/index.d.ts.map +1 -1
  78. package/dist/components/Dialog/index.js.map +1 -1
  79. package/dist/components/Drawer/index.cjs +2 -1
  80. package/dist/components/Drawer/index.cjs.map +1 -1
  81. package/dist/components/Drawer/index.d.ts +17 -1
  82. package/dist/components/Drawer/index.d.ts.map +1 -1
  83. package/dist/components/Drawer/index.js +2 -1
  84. package/dist/components/Drawer/index.js.map +1 -1
  85. package/dist/components/Grid/index.cjs +4 -1
  86. package/dist/components/Grid/index.cjs.map +1 -1
  87. package/dist/components/Grid/index.d.ts +6 -2
  88. package/dist/components/Grid/index.d.ts.map +1 -1
  89. package/dist/components/Grid/index.js +4 -1
  90. package/dist/components/Grid/index.js.map +1 -1
  91. package/dist/components/Input/index.cjs.map +1 -1
  92. package/dist/components/Input/index.d.ts +15 -1
  93. package/dist/components/Input/index.d.ts.map +1 -1
  94. package/dist/components/Input/index.js.map +1 -1
  95. package/dist/components/Popover/index.cjs.map +1 -1
  96. package/dist/components/Popover/index.d.ts +9 -0
  97. package/dist/components/Popover/index.d.ts.map +1 -1
  98. package/dist/components/Popover/index.js.map +1 -1
  99. package/dist/components/RadioGroup/index.cjs.map +1 -1
  100. package/dist/components/RadioGroup/index.d.ts +4 -0
  101. package/dist/components/RadioGroup/index.d.ts.map +1 -1
  102. package/dist/components/RadioGroup/index.js.map +1 -1
  103. package/dist/components/Select/index.cjs.map +1 -1
  104. package/dist/components/Select/index.d.ts +14 -0
  105. package/dist/components/Select/index.d.ts.map +1 -1
  106. package/dist/components/Select/index.js.map +1 -1
  107. package/dist/components/Sidebar/index.cjs +8 -5
  108. package/dist/components/Sidebar/index.cjs.map +1 -1
  109. package/dist/components/Sidebar/index.d.ts +4 -6
  110. package/dist/components/Sidebar/index.d.ts.map +1 -1
  111. package/dist/components/Sidebar/index.js +8 -5
  112. package/dist/components/Sidebar/index.js.map +1 -1
  113. package/dist/components/Slider/index.cjs +3 -1
  114. package/dist/components/Slider/index.cjs.map +1 -1
  115. package/dist/components/Slider/index.d.ts +10 -0
  116. package/dist/components/Slider/index.d.ts.map +1 -1
  117. package/dist/components/Slider/index.js +3 -1
  118. package/dist/components/Slider/index.js.map +1 -1
  119. package/dist/components/Stack/index.cjs +6 -0
  120. package/dist/components/Stack/index.cjs.map +1 -1
  121. package/dist/components/Stack/index.d.ts +12 -6
  122. package/dist/components/Stack/index.d.ts.map +1 -1
  123. package/dist/components/Stack/index.js +6 -0
  124. package/dist/components/Stack/index.js.map +1 -1
  125. package/dist/components/Tabs/index.cjs.map +1 -1
  126. package/dist/components/Tabs/index.d.ts +13 -1
  127. package/dist/components/Tabs/index.d.ts.map +1 -1
  128. package/dist/components/Tabs/index.js.map +1 -1
  129. package/dist/components/Text/Text.module.scss.cjs +44 -32
  130. package/dist/components/Text/Text.module.scss.cjs.map +1 -1
  131. package/dist/components/Text/Text.module.scss.js +44 -32
  132. package/dist/components/Text/Text.module.scss.js.map +1 -1
  133. package/dist/components/Text/index.cjs.map +1 -1
  134. package/dist/components/Text/index.d.ts +18 -3
  135. package/dist/components/Text/index.d.ts.map +1 -1
  136. package/dist/components/Text/index.js.map +1 -1
  137. package/dist/components/Theme/index.cjs.map +1 -1
  138. package/dist/components/Theme/index.d.ts +12 -0
  139. package/dist/components/Theme/index.d.ts.map +1 -1
  140. package/dist/components/Theme/index.js.map +1 -1
  141. package/dist/components/Toggle/index.cjs +2 -1
  142. package/dist/components/Toggle/index.cjs.map +1 -1
  143. package/dist/components/Toggle/index.d.ts +9 -0
  144. package/dist/components/Toggle/index.d.ts.map +1 -1
  145. package/dist/components/Toggle/index.js +2 -1
  146. package/dist/components/Toggle/index.js.map +1 -1
  147. package/dist/components/ToggleGroup/index.cjs +4 -1
  148. package/dist/components/ToggleGroup/index.cjs.map +1 -1
  149. package/dist/components/ToggleGroup/index.d.ts +13 -4
  150. package/dist/components/ToggleGroup/index.d.ts.map +1 -1
  151. package/dist/components/ToggleGroup/index.js +4 -1
  152. package/dist/components/ToggleGroup/index.js.map +1 -1
  153. package/dist/components/Tooltip/index.cjs +8 -4
  154. package/dist/components/Tooltip/index.cjs.map +1 -1
  155. package/dist/components/Tooltip/index.d.ts +5 -1
  156. package/dist/components/Tooltip/index.d.ts.map +1 -1
  157. package/dist/components/Tooltip/index.js +8 -4
  158. package/dist/components/Tooltip/index.js.map +1 -1
  159. package/dist/index.cjs +4 -0
  160. package/dist/index.cjs.map +1 -1
  161. package/dist/index.d.ts.map +1 -1
  162. package/dist/index.js +4 -0
  163. package/dist/index.js.map +1 -1
  164. package/dist/utils/css-warning.cjs +18 -0
  165. package/dist/utils/css-warning.cjs.map +1 -0
  166. package/dist/utils/css-warning.d.ts +2 -0
  167. package/dist/utils/css-warning.d.ts.map +1 -0
  168. package/dist/utils/css-warning.js +18 -0
  169. package/dist/utils/css-warning.js.map +1 -0
  170. package/fragments.json +1 -1
  171. package/package.json +2 -2
  172. package/src/components/Alert/index.tsx +7 -0
  173. package/src/components/AppShell/AppShell.module.scss +12 -13
  174. package/src/components/Avatar/index.tsx +4 -0
  175. package/src/components/Badge/Badge.fragment.tsx +10 -2
  176. package/src/components/Badge/index.tsx +12 -0
  177. package/src/components/Button/Button.fragment.tsx +12 -2
  178. package/src/components/Button/Button.test.tsx +16 -0
  179. package/src/components/Button/index.tsx +27 -2
  180. package/src/components/Card/Card.fragment.tsx +14 -2
  181. package/src/components/Card/Card.test.tsx +5 -0
  182. package/src/components/Card/index.tsx +15 -2
  183. package/src/components/Checkbox/index.tsx +6 -1
  184. package/src/components/Chip/Chip.fragment.tsx +12 -2
  185. package/src/components/Chip/Chip.test.tsx +5 -0
  186. package/src/components/Chip/index.tsx +14 -4
  187. package/src/components/CodeBlock/index.tsx +28 -13
  188. package/src/components/Dialog/index.tsx +12 -0
  189. package/src/components/Drawer/index.tsx +18 -1
  190. package/src/components/Grid/Grid.fragment.tsx +14 -2
  191. package/src/components/Grid/Grid.test.tsx +6 -0
  192. package/src/components/Grid/index.tsx +12 -3
  193. package/src/components/Input/index.tsx +15 -1
  194. package/src/components/Popover/index.tsx +9 -0
  195. package/src/components/RadioGroup/index.tsx +4 -0
  196. package/src/components/Select/index.tsx +14 -0
  197. package/src/components/Sidebar/index.tsx +9 -8
  198. package/src/components/Slider/Slider.fragment.tsx +5 -1
  199. package/src/components/Slider/Slider.test.tsx +6 -0
  200. package/src/components/Slider/index.tsx +13 -1
  201. package/src/components/Stack/Stack.fragment.tsx +22 -2
  202. package/src/components/Stack/Stack.test.tsx +6 -0
  203. package/src/components/Stack/index.tsx +20 -6
  204. package/src/components/Tabs/index.tsx +13 -1
  205. package/src/components/Text/Text.fragment.tsx +10 -8
  206. package/src/components/Text/Text.module.scss +8 -2
  207. package/src/components/Text/Text.test.tsx +15 -0
  208. package/src/components/Text/index.tsx +18 -3
  209. package/src/components/Theme/index.tsx +12 -0
  210. package/src/components/Toggle/Toggle.fragment.tsx +5 -1
  211. package/src/components/Toggle/Toggle.test.tsx +19 -0
  212. package/src/components/Toggle/index.tsx +11 -1
  213. package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +5 -2
  214. package/src/components/ToggleGroup/ToggleGroup.test.tsx +20 -0
  215. package/src/components/ToggleGroup/index.tsx +15 -4
  216. package/src/components/Tooltip/index.tsx +14 -4
  217. package/src/index.ts +6 -0
  218. package/src/tokens/_seeds.scss +5 -3
  219. package/src/tokens/_variables.scss +2 -0
  220. 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.0",
3
+ "version": "0.12.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.0"
233
+ "@fragments-sdk/cli": "0.10.0"
234
234
  },
235
235
  "files": [
236
236
  "src",
@@ -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
 
@@ -1,5 +1,5 @@
1
- @use '../../tokens/variables' as *;
2
- @use '../../tokens/mixins' as *;
1
+ @use "../../tokens/variables" as *;
2
+ @use "../../tokens/mixins" as *;
3
3
 
4
4
  // ============================================
5
5
  // AppShell Root - CSS Grid Layout
@@ -20,8 +20,8 @@
20
20
  // header header header
21
21
  // sidebar main aside
22
22
  grid-template-areas:
23
- 'header header header'
24
- 'sidebar main aside';
23
+ "header header header"
24
+ "sidebar main aside";
25
25
  grid-template-columns:
26
26
  var(--appshell-sidebar-width, 240px)
27
27
  1fr
@@ -36,9 +36,9 @@
36
36
  // Mobile: single column layout
37
37
  @include below-md {
38
38
  grid-template-areas:
39
- 'header'
40
- 'main'
41
- 'aside';
39
+ "header"
40
+ "main"
41
+ "aside";
42
42
  grid-template-columns: 1fr;
43
43
  grid-template-rows:
44
44
  var(--appshell-header-height, 56px)
@@ -53,14 +53,14 @@
53
53
  // sidebar main aside
54
54
  .sidebarLayout {
55
55
  grid-template-areas:
56
- 'sidebar header header'
57
- 'sidebar main aside';
56
+ "sidebar header header"
57
+ "sidebar main aside";
58
58
 
59
59
  @include below-md {
60
60
  grid-template-areas:
61
- 'header'
62
- 'main'
63
- 'aside';
61
+ "header"
62
+ "main"
63
+ "aside";
64
64
  grid-template-columns: 1fr;
65
65
  }
66
66
  }
@@ -157,7 +157,6 @@
157
157
  min-height: 0; // Allow shrinking in grid
158
158
  min-width: 0; // Prevent overflow
159
159
  background-color: var(--fui-bg-primary, $fui-bg-primary);
160
- overflow-x: hidden;
161
160
  }
162
161
 
163
162
  // Main content in sidebar-floating layout — elevated card effect
@@ -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],
@@ -9,19 +9,23 @@ import { useState, useCallback, useEffect, useMemo } from "react";
9
9
  let _codeToHtml:
10
10
  | ((code: string, options: { lang: string; theme: string }) => Promise<string>)
11
11
  | null = null;
12
- let _shikiLoaded = false;
12
+ let _shikiLoadPromise: Promise<void> | null = null;
13
13
  let _shikiFailed = false;
14
14
 
15
- function loadShikiDeps() {
16
- if (_shikiLoaded) return;
17
- _shikiLoaded = true;
18
- try {
19
- // eslint-disable-next-line @typescript-eslint/no-require-imports
20
- const shiki = require("shiki");
21
- _codeToHtml = shiki.codeToHtml;
22
- } catch {
23
- _shikiFailed = true;
15
+ async function loadShikiDeps() {
16
+ if (_codeToHtml) return;
17
+ if (_shikiFailed) return;
18
+ if (!_shikiLoadPromise) {
19
+ _shikiLoadPromise = (async () => {
20
+ try {
21
+ const shiki = await import("shiki");
22
+ _codeToHtml = shiki.codeToHtml;
23
+ } catch {
24
+ _shikiFailed = true;
25
+ }
26
+ })();
24
27
  }
28
+ await _shikiLoadPromise;
25
29
  }
26
30
  import { TabsRoot, TabsList, Tab, TabsPanel } from "../Tabs";
27
31
  import { Button } from "../Button";
@@ -31,7 +35,9 @@ import "../../styles/globals.scss";
31
35
  export type CodeBlockLanguage =
32
36
  | "tsx"
33
37
  | "typescript"
38
+ | "ts"
34
39
  | "javascript"
40
+ | "js"
35
41
  | "jsx"
36
42
  | "bash"
37
43
  | "shell"
@@ -60,7 +66,15 @@ export type CodeBlockLanguage =
60
66
  | "sql"
61
67
  | "graphql"
62
68
  | "diff"
63
- | "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
+ };
64
78
 
65
79
  /** Available syntax highlighting themes */
66
80
  export type CodeBlockTheme =
@@ -572,7 +586,7 @@ const CodeBlockBase = React.forwardRef<HTMLDivElement, CodeBlockProps>(function
572
586
  setHighlight((prev) => ({ ...prev, loading: true }));
573
587
 
574
588
  const run = async () => {
575
- loadShikiDeps();
589
+ await loadShikiDeps();
576
590
 
577
591
  const fallbackHtml = `<pre class="shiki"><code>${escapeHtml(visibleCode)}</code></pre>`;
578
592
 
@@ -587,7 +601,8 @@ const CodeBlockBase = React.forwardRef<HTMLDivElement, CodeBlockProps>(function
587
601
  }
588
602
 
589
603
  try {
590
- const html = await _codeToHtml(visibleCode, { lang: language, theme });
604
+ const resolvedLang = LANGUAGE_ALIASES[language] || language;
605
+ const html = await _codeToHtml(visibleCode, { lang: resolvedLang, theme });
591
606
  return processShikiHtml(html, {
592
607
  showLineNumbers,
593
608
  startLineNumber,
@@ -8,16 +8,28 @@ import styles from './Dialog.module.scss';
8
8
  // Types
9
9
  // ============================================
10
10
 
11
+ /**
12
+ * Modal dialog overlay for confirmations, forms, and focused tasks.
13
+ * @see https://usefragments.com/components/dialog
14
+ */
11
15
  export interface DialogProps {
12
16
  children: React.ReactNode;
17
+ /** Controlled open state */
13
18
  open?: boolean;
19
+ /** Default open state */
14
20
  defaultOpen?: boolean;
21
+ /** Called when open state changes */
15
22
  onOpenChange?: (open: boolean) => void;
23
+ /** Whether the dialog blocks interaction with the rest of the page.
24
+ * @default true */
16
25
  modal?: boolean;
17
26
  }
18
27
 
19
28
  export interface DialogContentProps extends React.HTMLAttributes<HTMLDivElement> {
20
29
  children: React.ReactNode;
30
+ /** Dialog width.
31
+ * @default "md"
32
+ * @see https://usefragments.com/components/dialog#sizes */
21
33
  size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
22
34
  }
23
35
 
@@ -8,18 +8,34 @@ import styles from './Drawer.module.scss';
8
8
  // Types
9
9
  // ============================================
10
10
 
11
+ /**
12
+ * Slide-in panel for navigation, forms, or supplementary content.
13
+ * @see https://usefragments.com/components/drawer
14
+ */
11
15
  export interface DrawerProps {
12
16
  children: React.ReactNode;
17
+ /** Controlled open state */
13
18
  open?: boolean;
19
+ /** Default open state */
14
20
  defaultOpen?: boolean;
21
+ /** Called when open state changes */
15
22
  onOpenChange?: (open: boolean) => void;
23
+ /** Whether the drawer blocks interaction with the rest of the page.
24
+ * @default true */
16
25
  modal?: boolean;
17
26
  }
18
27
 
19
28
  export interface DrawerContentProps extends React.HTMLAttributes<HTMLDivElement> {
20
29
  children: React.ReactNode;
30
+ /** Which edge the drawer slides from.
31
+ * @default "right" */
21
32
  side?: 'left' | 'right' | 'top' | 'bottom';
33
+ /** Drawer width (for left/right) or height (for top/bottom).
34
+ * @default "md"
35
+ * @see https://usefragments.com/components/drawer#sizes */
22
36
  size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
37
+ /** Whether to show the backdrop overlay (default: true). Set to false for non-modal bottom panels. */
38
+ backdrop?: boolean;
23
39
  }
24
40
 
25
41
  export interface DrawerTriggerProps {
@@ -127,6 +143,7 @@ function DrawerContent({
127
143
  children,
128
144
  side = 'right',
129
145
  size = 'md',
146
+ backdrop = true,
130
147
  className,
131
148
  ...htmlProps
132
149
  }: DrawerContentProps) {
@@ -141,7 +158,7 @@ function DrawerContent({
141
158
 
142
159
  return (
143
160
  <BaseDialog.Portal>
144
- <BaseDialog.Backdrop className={styles.backdrop} />
161
+ {backdrop && <BaseDialog.Backdrop className={styles.backdrop} />}
145
162
  <BaseDialog.Popup initialFocus {...htmlProps} data-side={side} className={popupClasses}>
146
163
  {children}
147
164
  </BaseDialog.Popup>