@fragments-sdk/ui 0.8.6 → 0.8.7

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 (98) hide show
  1. package/fragments.json +1 -1
  2. package/package.json +2 -2
  3. package/src/blocks/AccountSettings.block.ts +1 -1
  4. package/src/blocks/ActivityFeed.block.ts +1 -1
  5. package/src/blocks/ChatInterface.block.ts +1 -1
  6. package/src/blocks/ChatMessages.block.ts +1 -1
  7. package/src/blocks/CheckoutForm.block.ts +1 -1
  8. package/src/blocks/ContactForm.block.ts +1 -1
  9. package/src/blocks/DashboardLayout.block.ts +1 -1
  10. package/src/blocks/DashboardPage.block.ts +1 -1
  11. package/src/blocks/DataTable.block.ts +1 -1
  12. package/src/blocks/EmptyState.block.ts +1 -1
  13. package/src/blocks/FAQSection.block.ts +1 -1
  14. package/src/blocks/FeatureGrid.block.ts +1 -1
  15. package/src/blocks/HeroSection.block.ts +1 -1
  16. package/src/blocks/LoginForm.block.ts +1 -1
  17. package/src/blocks/NavigationHeader.block.ts +1 -1
  18. package/src/blocks/PricingComparison.block.ts +1 -1
  19. package/src/blocks/ProductCard.block.ts +1 -1
  20. package/src/blocks/RegistrationForm.block.ts +1 -1
  21. package/src/blocks/SettingsPanel.block.ts +1 -1
  22. package/src/blocks/ShoppingCart.block.ts +1 -1
  23. package/src/blocks/StatsCard.block.ts +1 -1
  24. package/src/blocks/ThinkingStates.block.ts +1 -1
  25. package/src/components/Accordion/Accordion.fragment.tsx +1 -1
  26. package/src/components/Alert/Alert.fragment.tsx +1 -1
  27. package/src/components/AppShell/AppShell.fragment.tsx +11 -11
  28. package/src/components/Avatar/Avatar.fragment.tsx +1 -1
  29. package/src/components/Badge/Badge.fragment.tsx +1 -1
  30. package/src/components/Box/Box.fragment.tsx +1 -1
  31. package/src/components/Breadcrumbs/Breadcrumbs.fragment.tsx +1 -1
  32. package/src/components/Button/Button.fragment.tsx +1 -1
  33. package/src/components/ButtonGroup/ButtonGroup.fragment.tsx +1 -1
  34. package/src/components/Card/Card.fragment.tsx +1 -1
  35. package/src/components/Chart/Chart.fragment.tsx +1 -1
  36. package/src/components/Checkbox/Checkbox.fragment.tsx +1 -1
  37. package/src/components/Chip/Chip.fragment.tsx +1 -1
  38. package/src/components/CodeBlock/CodeBlock.fragment.tsx +1 -1
  39. package/src/components/Collapsible/Collapsible.fragment.tsx +1 -1
  40. package/src/components/ColorPicker/ColorPicker.fragment.tsx +1 -1
  41. package/src/components/Combobox/Combobox.fragment.tsx +1 -1
  42. package/src/components/ConversationList/ConversationList.fragment.tsx +1 -1
  43. package/src/components/DatePicker/DatePicker.fragment.tsx +10 -9
  44. package/src/components/Dialog/Dialog.fragment.tsx +1 -1
  45. package/src/components/EmptyState/EmptyState.fragment.tsx +1 -1
  46. package/src/components/Field/Field.fragment.tsx +1 -1
  47. package/src/components/Fieldset/Fieldset.fragment.tsx +1 -1
  48. package/src/components/Form/Form.fragment.tsx +1 -1
  49. package/src/components/Grid/Grid.fragment.tsx +1 -1
  50. package/src/components/Header/Header.fragment.tsx +1 -1
  51. package/src/components/Icon/Icon.fragment.tsx +1 -1
  52. package/src/components/Image/Image.fragment.tsx +1 -1
  53. package/src/components/Input/Input.fragment.tsx +1 -1
  54. package/src/components/Link/Link.fragment.tsx +1 -1
  55. package/src/components/List/List.fragment.tsx +1 -1
  56. package/src/components/Listbox/Listbox.fragment.tsx +1 -1
  57. package/src/components/Loading/Loading.fragment.tsx +1 -1
  58. package/src/components/Markdown/Markdown.fragment.tsx +1 -1
  59. package/src/components/Menu/Menu.fragment.tsx +55 -5
  60. package/src/components/Menu/Menu.module.scss +21 -10
  61. package/src/components/Menu/Menu.test.tsx +126 -3
  62. package/src/components/Menu/index.tsx +85 -11
  63. package/src/components/Message/Message.fragment.tsx +1 -1
  64. package/src/components/Message/Message.module.scss +2 -1
  65. package/src/components/NavigationMenu/NavigationMenu.fragment.tsx +1 -1
  66. package/src/components/Popover/Popover.fragment.tsx +1 -1
  67. package/src/components/Progress/Progress.fragment.tsx +1 -1
  68. package/src/components/Prompt/Prompt.fragment.tsx +1 -1
  69. package/src/components/RadioGroup/RadioGroup.fragment.tsx +1 -1
  70. package/src/components/ScrollArea/ScrollArea.fragment.tsx +1 -1
  71. package/src/components/Select/Select.fragment.tsx +1 -1
  72. package/src/components/Separator/Separator.fragment.tsx +1 -1
  73. package/src/components/Sidebar/Sidebar.fragment.tsx +2 -2
  74. package/src/components/Skeleton/Skeleton.fragment.tsx +1 -1
  75. package/src/components/Slider/Slider.fragment.tsx +1 -1
  76. package/src/components/Stack/Stack.fragment.tsx +1 -1
  77. package/src/components/Table/Table.fragment.tsx +1 -1
  78. package/src/components/TableOfContents/TableOfContents.fragment.tsx +1 -1
  79. package/src/components/Tabs/Tabs.fragment.tsx +1 -1
  80. package/src/components/Text/Text.fragment.tsx +1 -1
  81. package/src/components/Textarea/Textarea.fragment.tsx +1 -1
  82. package/src/components/Theme/Theme.fragment.tsx +1 -1
  83. package/src/components/ThinkingIndicator/ThinkingIndicator.fragment.tsx +1 -1
  84. package/src/components/Toast/Toast.fragment.tsx +1 -1
  85. package/src/components/Toggle/Toggle.fragment.tsx +1 -1
  86. package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +1 -1
  87. package/src/components/Tooltip/Tooltip.fragment.tsx +1 -1
  88. package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +1 -1
  89. package/src/styles/globals.scss +65 -7
  90. package/src/tokens/_computed.scss +1 -1
  91. package/src/tokens/_density.scss +1 -1
  92. package/src/tokens/_derive.scss +1 -1
  93. package/src/tokens/_index.scss +1 -1
  94. package/src/tokens/_mixins.scss +1 -1
  95. package/src/tokens/_palettes.scss +1 -1
  96. package/src/tokens/_radius.scss +1 -1
  97. package/src/tokens/_seeds.scss +1 -1
  98. package/src/tokens/_variables.scss +2 -2
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Menu } from '.';
4
4
  import { Button } from '../Button';
5
5
 
@@ -8,10 +8,10 @@ export default defineFragment({
8
8
 
9
9
  meta: {
10
10
  name: 'Menu',
11
- description: 'Dropdown menu for actions and commands. Use for contextual actions, overflow menus, or grouped commands.',
11
+ description: 'Dropdown menu for actions and commands. Supports submenus, check items, radio groups, and keyboard shortcuts.',
12
12
  category: 'feedback',
13
13
  status: 'stable',
14
- tags: ['menu', 'dropdown', 'actions', 'context-menu', 'commands'],
14
+ tags: ['menu', 'dropdown', 'actions', 'context-menu', 'commands', 'submenu'],
15
15
  since: '0.1.0',
16
16
  },
17
17
 
@@ -21,6 +21,7 @@ export default defineFragment({
21
21
  'Context menus (right-click)',
22
22
  'User account menus',
23
23
  'Grouped actions with separators',
24
+ 'Nested menus with submenus',
24
25
  ],
25
26
  whenNot: [
26
27
  'Selecting from options (use Select)',
@@ -33,12 +34,15 @@ export default defineFragment({
33
34
  'Include keyboard shortcuts where applicable',
34
35
  'Use danger variant for destructive actions',
35
36
  'Keep menu items under 10-12 for usability',
37
+ 'Use checked prop on Menu.Item for simple selection state',
38
+ 'Use Menu.Submenu for nested secondary options',
36
39
  ],
37
40
  accessibility: [
38
41
  'Full keyboard navigation with arrow keys',
39
42
  'Type-ahead search for items',
40
43
  'Focus returns to trigger on close',
41
44
  'Proper ARIA menu roles',
45
+ 'ArrowRight opens submenus, ArrowLeft closes them',
42
46
  ],
43
47
  },
44
48
 
@@ -79,6 +83,7 @@ export default defineFragment({
79
83
  'onOpenChange: (open) => void - state handler',
80
84
  'Menu.Item danger: boolean - destructive styling',
81
85
  'Menu.Item shortcut: string - keyboard shortcut text',
86
+ 'Menu.Item checked: boolean - check indicator for selection state',
82
87
  ],
83
88
  scenarioTags: [
84
89
  'action.menu',
@@ -90,7 +95,7 @@ export default defineFragment({
90
95
 
91
96
  ai: {
92
97
  compositionPattern: 'compound',
93
- subComponents: ['Trigger', 'Content', 'Item', 'CheckboxItem', 'RadioGroup', 'RadioItem', 'Group', 'GroupLabel', 'Separator'],
98
+ subComponents: ['Trigger', 'Content', 'Item', 'CheckboxItem', 'RadioGroup', 'RadioItem', 'Group', 'GroupLabel', 'Separator', 'Submenu', 'SubmenuTrigger'],
94
99
  requiredChildren: ['Trigger', 'Content'],
95
100
  commonPatterns: [
96
101
  '<Menu><Menu.Trigger asChild><Button>Actions</Button></Menu.Trigger><Menu.Content><Menu.Item>{action1}</Menu.Item><Menu.Separator /><Menu.Item danger>{delete}</Menu.Item></Menu.Content></Menu>',
@@ -159,7 +164,7 @@ export default defineFragment({
159
164
  },
160
165
  {
161
166
  name: 'With Checkboxes',
162
- description: 'Menu with toggleable options',
167
+ description: 'Menu with toggleable checkbox options',
163
168
  render: () => (
164
169
  <Menu>
165
170
  <Menu.Trigger asChild>
@@ -173,5 +178,50 @@ export default defineFragment({
173
178
  </Menu>
174
179
  ),
175
180
  },
181
+ {
182
+ name: 'With Checked Items',
183
+ description: 'Filter menu with check marks indicating active selections',
184
+ render: () => {
185
+ const [view, setView] = React.useState('grid');
186
+ return (
187
+ <Menu>
188
+ <Menu.Trigger asChild>
189
+ <Button variant="secondary">View</Button>
190
+ </Menu.Trigger>
191
+ <Menu.Content>
192
+ <Menu.Item checked={view === 'grid'} onSelect={() => setView('grid')}>Grid</Menu.Item>
193
+ <Menu.Item checked={view === 'list'} onSelect={() => setView('list')}>List</Menu.Item>
194
+ <Menu.Item checked={view === 'board'} onSelect={() => setView('board')}>Board</Menu.Item>
195
+ </Menu.Content>
196
+ </Menu>
197
+ );
198
+ },
199
+ },
200
+ {
201
+ name: 'With Submenu',
202
+ description: 'Menu with nested submenu for grouped secondary options',
203
+ render: () => (
204
+ <Menu>
205
+ <Menu.Trigger asChild>
206
+ <Button variant="secondary">File</Button>
207
+ </Menu.Trigger>
208
+ <Menu.Content>
209
+ <Menu.Item onSelect={() => {}}>New File</Menu.Item>
210
+ <Menu.Item onSelect={() => {}}>Open</Menu.Item>
211
+ <Menu.Separator />
212
+ <Menu.Submenu>
213
+ <Menu.SubmenuTrigger>Export As</Menu.SubmenuTrigger>
214
+ <Menu.Content side="right" align="start">
215
+ <Menu.Item onSelect={() => {}}>PNG</Menu.Item>
216
+ <Menu.Item onSelect={() => {}}>SVG</Menu.Item>
217
+ <Menu.Item onSelect={() => {}}>PDF</Menu.Item>
218
+ </Menu.Content>
219
+ </Menu.Submenu>
220
+ <Menu.Separator />
221
+ <Menu.Item danger onSelect={() => {}}>Delete</Menu.Item>
222
+ </Menu.Content>
223
+ </Menu>
224
+ ),
225
+ },
176
226
  ],
177
227
  });
@@ -101,13 +101,30 @@
101
101
  margin-left: auto;
102
102
  }
103
103
 
104
- // Checkbox/Radio indicator
105
- .itemIndicator {
104
+ // Check indicator (checkmark icon for checked items)
105
+ .checkIndicator {
106
106
  display: flex;
107
107
  align-items: center;
108
108
  justify-content: center;
109
109
  width: 1rem;
110
110
  height: 1rem;
111
+ flex-shrink: 0;
112
+ color: var(--fui-color-accent, $fui-color-accent);
113
+ }
114
+
115
+ // Checkbox item
116
+ .checkboxItem {
117
+ // Uses .checkIndicator for checked state visual
118
+ }
119
+
120
+ // Radio indicator (still uses icon)
121
+ .radioIndicator {
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ width: 1rem;
126
+ height: 1rem;
127
+ visibility: hidden;
111
128
 
112
129
  svg {
113
130
  width: 0.75rem;
@@ -115,16 +132,10 @@
115
132
  }
116
133
  }
117
134
 
118
- // Checkbox item states
119
- .checkboxItem {
120
- &[data-checked] .itemIndicator {
121
- color: var(--fui-color-accent, $fui-color-accent);
122
- }
123
- }
124
-
125
135
  // Radio item states
126
136
  .radioItem {
127
- &[data-checked] .itemIndicator {
137
+ &[data-checked] .radioIndicator {
138
+ visibility: visible;
128
139
  color: var(--fui-color-accent, $fui-color-accent);
129
140
  }
130
141
  }
@@ -73,23 +73,30 @@ describe('Menu', () => {
73
73
  });
74
74
  });
75
75
 
76
- it('renders checkbox items', async () => {
76
+ it('renders checkbox items with check icon', async () => {
77
77
  const onCheckedChange = vi.fn();
78
- const user = userEvent.setup();
79
78
  render(
80
79
  <Menu defaultOpen>
81
80
  <Menu.Trigger>Open</Menu.Trigger>
82
81
  <Menu.Content>
83
- <Menu.CheckboxItem checked={false} onCheckedChange={onCheckedChange}>
82
+ <Menu.CheckboxItem checked={true} onCheckedChange={onCheckedChange}>
84
83
  Show toolbar
85
84
  </Menu.CheckboxItem>
85
+ <Menu.CheckboxItem checked={false}>
86
+ Show sidebar
87
+ </Menu.CheckboxItem>
86
88
  </Menu.Content>
87
89
  </Menu>
88
90
  );
89
91
 
90
92
  await waitFor(() => {
91
93
  expect(screen.getByText('Show toolbar')).toBeInTheDocument();
94
+ expect(screen.getByText('Show sidebar')).toBeInTheDocument();
92
95
  });
96
+
97
+ // Checked item should have a checkmark SVG
98
+ const checkedItem = screen.getByText('Show toolbar').closest('[role="menuitemcheckbox"]');
99
+ expect(checkedItem?.querySelector('svg')).toBeInTheDocument();
93
100
  });
94
101
 
95
102
  it('renders radio group items', async () => {
@@ -142,6 +149,122 @@ describe('Menu', () => {
142
149
  });
143
150
  });
144
151
 
152
+ describe('checked items', () => {
153
+ it('renders check indicator when checked={true}', async () => {
154
+ render(
155
+ <Menu defaultOpen>
156
+ <Menu.Trigger>Open</Menu.Trigger>
157
+ <Menu.Content>
158
+ <Menu.Item checked={true} onSelect={() => {}}>Grid</Menu.Item>
159
+ </Menu.Content>
160
+ </Menu>
161
+ );
162
+
163
+ await waitFor(() => {
164
+ expect(screen.getByText('Grid')).toBeInTheDocument();
165
+ });
166
+
167
+ const item = screen.getByText('Grid').closest('[role="menuitem"]');
168
+ expect(item?.querySelector('svg')).toBeInTheDocument();
169
+ });
170
+
171
+ it('reserves space but shows no icon when checked={false}', async () => {
172
+ render(
173
+ <Menu defaultOpen>
174
+ <Menu.Trigger>Open</Menu.Trigger>
175
+ <Menu.Content>
176
+ <Menu.Item checked={false} onSelect={() => {}}>List</Menu.Item>
177
+ </Menu.Content>
178
+ </Menu>
179
+ );
180
+
181
+ await waitFor(() => {
182
+ expect(screen.getByText('List')).toBeInTheDocument();
183
+ });
184
+
185
+ const item = screen.getByText('List').closest('[role="menuitem"]');
186
+ // Should not have a checkmark SVG
187
+ expect(item?.querySelector('svg')).not.toBeInTheDocument();
188
+ });
189
+
190
+ it('does not render check indicator when checked is omitted', async () => {
191
+ const { container } = render(
192
+ <Menu defaultOpen>
193
+ <Menu.Trigger>Open</Menu.Trigger>
194
+ <Menu.Content>
195
+ <Menu.Item onSelect={() => {}}>Normal Item</Menu.Item>
196
+ </Menu.Content>
197
+ </Menu>
198
+ );
199
+
200
+ await waitFor(() => {
201
+ expect(screen.getByText('Normal Item')).toBeInTheDocument();
202
+ });
203
+
204
+ const item = screen.getByText('Normal Item').closest('[role="menuitem"]');
205
+ // Should not have any SVG or check indicator
206
+ expect(item?.querySelector('svg')).not.toBeInTheDocument();
207
+ });
208
+ });
209
+
210
+ describe('submenu', () => {
211
+ function renderSubmenu(props: Partial<React.ComponentProps<typeof Menu>> = {}) {
212
+ return render(
213
+ <Menu {...props}>
214
+ <Menu.Trigger>Open Menu</Menu.Trigger>
215
+ <Menu.Content>
216
+ <Menu.Item onSelect={() => {}}>New File</Menu.Item>
217
+ <Menu.Submenu>
218
+ <Menu.SubmenuTrigger>Export As</Menu.SubmenuTrigger>
219
+ <Menu.Content side="right" align="start">
220
+ <Menu.Item onSelect={() => {}}>PNG</Menu.Item>
221
+ <Menu.Item onSelect={() => {}}>SVG</Menu.Item>
222
+ </Menu.Content>
223
+ </Menu.Submenu>
224
+ </Menu.Content>
225
+ </Menu>
226
+ );
227
+ }
228
+
229
+ it('renders submenu trigger', async () => {
230
+ const user = userEvent.setup();
231
+ renderSubmenu();
232
+
233
+ await user.click(screen.getByRole('button', { name: /open menu/i }));
234
+ await waitFor(() => {
235
+ expect(screen.getByText('Export As')).toBeInTheDocument();
236
+ });
237
+ });
238
+
239
+ it('opens submenu on click', async () => {
240
+ const user = userEvent.setup();
241
+ renderSubmenu();
242
+
243
+ await user.click(screen.getByRole('button', { name: /open menu/i }));
244
+ await waitFor(() => {
245
+ expect(screen.getByText('Export As')).toBeInTheDocument();
246
+ });
247
+
248
+ await user.click(screen.getByText('Export As'));
249
+ await waitFor(() => {
250
+ expect(screen.getByText('PNG')).toBeInTheDocument();
251
+ expect(screen.getByText('SVG')).toBeInTheDocument();
252
+ });
253
+ });
254
+
255
+ it('has no accessibility violations', async () => {
256
+ const { container } = renderSubmenu({ defaultOpen: true });
257
+
258
+ await waitFor(() => {
259
+ expect(screen.getByText('Export As')).toBeInTheDocument();
260
+ });
261
+
262
+ await expectNoA11yViolations(container, {
263
+ disabledRules: ['aria-command-name'],
264
+ });
265
+ });
266
+ });
267
+
145
268
  describe('keyboard & focus', () => {
146
269
  /**
147
270
  * Opens the menu by clicking the trigger and waits for it to be present.
@@ -37,6 +37,8 @@ export interface MenuItemProps {
37
37
  className?: string;
38
38
  icon?: React.ReactNode;
39
39
  shortcut?: string;
40
+ /** When passed, renders a check indicator. `true` shows a checkmark, `false` reserves space. */
41
+ checked?: boolean;
40
42
  }
41
43
 
42
44
  export interface MenuCheckboxItemProps {
@@ -76,25 +78,39 @@ export interface MenuSeparatorProps {
76
78
  className?: string;
77
79
  }
78
80
 
81
+ export interface MenuSubmenuProps {
82
+ children: React.ReactNode;
83
+ open?: boolean;
84
+ defaultOpen?: boolean;
85
+ onOpenChange?: (open: boolean) => void;
86
+ }
87
+
88
+ export interface MenuSubmenuTriggerProps {
89
+ children: React.ReactNode;
90
+ disabled?: boolean;
91
+ className?: string;
92
+ icon?: React.ReactNode;
93
+ }
94
+
79
95
  // ============================================
80
96
  // Icons
81
97
  // ============================================
82
98
 
83
- function CheckIcon() {
99
+ function CheckmarkIcon() {
84
100
  return (
85
101
  <svg
86
102
  xmlns="http://www.w3.org/2000/svg"
87
- width="12"
88
- height="12"
103
+ width="14"
104
+ height="14"
89
105
  viewBox="0 0 24 24"
90
106
  fill="none"
91
107
  stroke="currentColor"
92
- strokeWidth="3"
108
+ strokeWidth={3}
93
109
  strokeLinecap="round"
94
110
  strokeLinejoin="round"
95
111
  aria-hidden="true"
96
112
  >
97
- <polyline points="20 6 9 17 4 12" />
113
+ <path d="M5 13l4 4L19 7" />
98
114
  </svg>
99
115
  );
100
116
  }
@@ -187,7 +203,9 @@ function MenuItem({
187
203
  className,
188
204
  icon,
189
205
  shortcut,
206
+ checked,
190
207
  }: MenuItemProps) {
208
+ const hasChecked = checked !== undefined;
191
209
  const classes = [
192
210
  styles.item,
193
211
  danger && styles.itemDanger,
@@ -200,6 +218,11 @@ function MenuItem({
200
218
  onClick={onSelect}
201
219
  className={classes}
202
220
  >
221
+ {hasChecked && (
222
+ <span className={styles.checkIndicator}>
223
+ {checked ? <CheckmarkIcon /> : null}
224
+ </span>
225
+ )}
203
226
  {icon && <span className={styles.itemIcon}>{icon}</span>}
204
227
  <span className={styles.itemLabel}>{children}</span>
205
228
  {shortcut && <span className={styles.itemShortcut}>{shortcut}</span>}
@@ -209,26 +232,38 @@ function MenuItem({
209
232
 
210
233
  function MenuCheckboxItem({
211
234
  children,
212
- checked,
235
+ checked: checkedProp,
213
236
  defaultChecked,
214
237
  onCheckedChange,
215
238
  disabled,
216
239
  className,
217
240
  }: MenuCheckboxItemProps) {
241
+ const isControlled = checkedProp !== undefined;
242
+ const [internalChecked, setInternalChecked] = React.useState(defaultChecked ?? false);
243
+ const visualChecked = isControlled ? checkedProp : internalChecked;
244
+
245
+ const handleCheckedChange = React.useCallback(
246
+ (value: boolean) => {
247
+ if (!isControlled) setInternalChecked(value);
248
+ onCheckedChange?.(value);
249
+ },
250
+ [isControlled, onCheckedChange],
251
+ );
252
+
218
253
  const classes = [styles.item, styles.checkboxItem, className]
219
254
  .filter(Boolean)
220
255
  .join(' ');
221
256
 
222
257
  return (
223
258
  <BaseMenu.CheckboxItem
224
- checked={checked}
259
+ checked={checkedProp}
225
260
  defaultChecked={defaultChecked}
226
- onCheckedChange={onCheckedChange}
261
+ onCheckedChange={handleCheckedChange}
227
262
  disabled={disabled}
228
263
  className={classes}
229
264
  >
230
- <span className={styles.itemIndicator}>
231
- <CheckIcon />
265
+ <span className={styles.checkIndicator}>
266
+ {visualChecked ? <CheckmarkIcon /> : null}
232
267
  </span>
233
268
  <span className={styles.itemLabel}>{children}</span>
234
269
  </BaseMenu.CheckboxItem>
@@ -264,7 +299,7 @@ function MenuRadioItem({
264
299
 
265
300
  return (
266
301
  <BaseMenu.RadioItem value={value} disabled={disabled} className={classes}>
267
- <span className={styles.itemIndicator}>
302
+ <span className={styles.radioIndicator}>
268
303
  <DotIcon />
269
304
  </span>
270
305
  <span className={styles.itemLabel}>{children}</span>
@@ -287,6 +322,41 @@ function MenuSeparator({ className }: MenuSeparatorProps) {
287
322
  return <BaseMenu.Separator className={classes} />;
288
323
  }
289
324
 
325
+ function MenuSubmenu({
326
+ children,
327
+ open,
328
+ defaultOpen,
329
+ onOpenChange,
330
+ }: MenuSubmenuProps) {
331
+ return (
332
+ <BaseMenu.SubmenuRoot
333
+ open={open}
334
+ defaultOpen={defaultOpen}
335
+ onOpenChange={onOpenChange as any}
336
+ >
337
+ {children}
338
+ </BaseMenu.SubmenuRoot>
339
+ );
340
+ }
341
+
342
+ function MenuSubmenuTrigger({
343
+ children,
344
+ disabled,
345
+ className,
346
+ icon,
347
+ }: MenuSubmenuTriggerProps) {
348
+ const classes = [styles.item, styles.submenuTrigger, className]
349
+ .filter(Boolean)
350
+ .join(' ');
351
+
352
+ return (
353
+ <BaseMenu.SubmenuTrigger disabled={disabled} className={classes}>
354
+ {icon && <span className={styles.itemIcon}>{icon}</span>}
355
+ <span className={styles.itemLabel}>{children}</span>
356
+ </BaseMenu.SubmenuTrigger>
357
+ );
358
+ }
359
+
290
360
  // ============================================
291
361
  // Export compound component
292
362
  // ============================================
@@ -301,6 +371,8 @@ export const Menu = Object.assign(MenuRoot, {
301
371
  Group: MenuGroup,
302
372
  GroupLabel: MenuGroupLabel,
303
373
  Separator: MenuSeparator,
374
+ Submenu: MenuSubmenu,
375
+ SubmenuTrigger: MenuSubmenuTrigger,
304
376
  });
305
377
 
306
378
  // Re-export individual components
@@ -315,4 +387,6 @@ export {
315
387
  MenuGroup,
316
388
  MenuGroupLabel,
317
389
  MenuSeparator,
390
+ MenuSubmenu,
391
+ MenuSubmenuTrigger,
318
392
  };
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Message } from '.';
4
4
 
5
5
  export default defineFragment({
@@ -36,7 +36,7 @@
36
36
 
37
37
  .content {
38
38
  background-color: var(--fui-color-accent, $fui-color-accent);
39
- color: var(--fui-color-danger-text, $fui-color-danger-text);
39
+ color: var(--fui-text-inverse, $fui-text-inverse);
40
40
  }
41
41
  }
42
42
 
@@ -68,6 +68,7 @@
68
68
  .content {
69
69
  background-color: var(--fui-color-danger-bg, $fui-color-danger-bg);
70
70
  border: 1px solid var(--fui-color-danger, $fui-color-danger);
71
+ color: var(--fui-color-danger-text, $fui-color-danger-text);
71
72
  }
72
73
  }
73
74
 
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { NavigationMenu } from '.';
4
4
 
5
5
  export default defineFragment({
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Popover } from '.';
4
4
  import { Button } from '../Button';
5
5
  import { Input } from '../Input';
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Progress, CircularProgress } from '.';
4
4
 
5
5
  export default defineFragment({
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Prompt } from '.';
4
4
 
5
5
  export default defineFragment({
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { RadioGroup } from '.';
4
4
 
5
5
  export default defineFragment({
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { ScrollArea } from '.';
4
4
 
5
5
  const tags = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4', 'Tag 5', 'Tag 6', 'Tag 7', 'Tag 8', 'Tag 9', 'Tag 10', 'Tag 11', 'Tag 12', 'Tag 13', 'Tag 14', 'Tag 15'];
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Select } from '.';
4
4
 
5
5
  // Stateful wrapper for interactive demos
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Separator } from '.';
4
4
 
5
5
  export default defineFragment({
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Sidebar, SidebarProvider, useSidebar } from '.';
4
4
  import { Button } from '../Button';
5
5
 
@@ -81,7 +81,7 @@ const mainContentStyle: React.CSSProperties = {
81
81
  };
82
82
 
83
83
  const demoContainerStyle: React.CSSProperties = {
84
- height: '400px',
84
+ minHeight: '100vh',
85
85
  display: 'flex',
86
86
  width: '100%',
87
87
  };
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Skeleton } from '.';
4
4
 
5
5
  export default defineFragment({
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Slider } from '.';
4
4
 
5
5
  export default defineFragment({
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Stack } from '.';
4
4
  import { Button } from '../Button';
5
5
  import { Badge } from '../Badge';
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Table, createColumns } from '.';
4
4
  import { Badge } from '../Badge';
5
5
 
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { TableOfContents } from '.';
4
4
 
5
5
  export default defineFragment({
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Tabs } from '.';
4
4
 
5
5
  export default defineFragment({
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineFragment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments-sdk/cli/core';
3
3
  import { Text } from '.';
4
4
 
5
5
  export default defineFragment({