lucent-ui 0.22.0 → 0.24.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.
- package/dist/index.cjs +113 -86
- package/dist/index.d.ts +130 -0
- package/dist/index.js +4557 -3563
- package/dist-server/src/components/atoms/ButtonGroup/ButtonGroup.manifest.js +43 -0
- package/dist-server/src/components/atoms/SplitButton/SplitButton.manifest.js +75 -0
- package/dist-server/src/components/molecules/NavMenu/NavMenu.manifest.js +247 -0
- package/dist-server/src/manifest/examples/button.manifest.js +11 -5
- package/package.json +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'button-group',
|
|
3
|
+
name: 'ButtonGroup',
|
|
4
|
+
tier: 'atom',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'A layout wrapper that visually groups multiple Button or SplitButton children into a related strip.',
|
|
8
|
+
designIntent: 'Use ButtonGroup for peer actions that belong together visually — toolbars, action bars, ' +
|
|
9
|
+
'toggle groups. Unlike SegmentedControl (single-select with indicator), ButtonGroup has no ' +
|
|
10
|
+
'built-in selection state; each child handles its own behaviour. ' +
|
|
11
|
+
'Items are separated by a small token-based gap with subtle inner corner radius ' +
|
|
12
|
+
'so the group reads as a unit while each button retains its own border and hover states.',
|
|
13
|
+
props: [
|
|
14
|
+
{ name: 'children', type: 'ReactNode', required: true, description: 'Button or SplitButton elements to group.' },
|
|
15
|
+
{ name: 'style', type: 'object', required: false, description: 'Style overrides for the wrapper div.' },
|
|
16
|
+
],
|
|
17
|
+
usageExamples: [
|
|
18
|
+
{
|
|
19
|
+
title: 'Toolbar',
|
|
20
|
+
code: `<ButtonGroup>\n <Button variant="outline" leftIcon={<Icon name="bold" />} />\n <Button variant="outline" leftIcon={<Icon name="italic" />} />\n <Button variant="outline" leftIcon={<Icon name="underline" />} />\n</ButtonGroup>`,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
title: 'Action pair',
|
|
24
|
+
code: `<ButtonGroup>\n <Button variant="primary">Save</Button>\n <Button variant="outline">Cancel</Button>\n</ButtonGroup>`,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
title: 'With SplitButton',
|
|
28
|
+
code: `<ButtonGroup>\n <SplitButton onClick={deploy} menuItems={[{ label: 'Staging', onSelect: staging }]}>Deploy</SplitButton>\n <Button variant="outline">Logs</Button>\n</ButtonGroup>`,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
compositionGraph: [
|
|
32
|
+
{ componentId: 'button', componentName: 'Button', role: 'Grouped action item', required: false },
|
|
33
|
+
{ componentId: 'split-button', componentName: 'SplitButton', role: 'Grouped split action item', required: false },
|
|
34
|
+
],
|
|
35
|
+
accessibility: {
|
|
36
|
+
role: 'group',
|
|
37
|
+
ariaAttributes: [],
|
|
38
|
+
keyboardInteractions: [
|
|
39
|
+
'Tab moves focus between buttons in the group',
|
|
40
|
+
],
|
|
41
|
+
notes: 'Uses role="group" on the wrapper. Individual button accessibility is inherited from the children.',
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'split-button',
|
|
3
|
+
name: 'SplitButton',
|
|
4
|
+
tier: 'atom',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'A compound button pairing a primary action with a chevron dropdown for secondary actions.',
|
|
8
|
+
designIntent: 'Use SplitButton when there is one dominant action alongside a small set of related alternatives ' +
|
|
9
|
+
'(e.g. "Save" + "Save as draft", "Deploy" + "Deploy to staging"). ' +
|
|
10
|
+
'Each half is a fully independent button separated by a small token-based gap, with a subtle ' +
|
|
11
|
+
'inner corner radius (radius-sm) so the pair reads as a unit without sharing a border. ' +
|
|
12
|
+
'Hover lift and press ring apply independently per half. ' +
|
|
13
|
+
'Ghost variants use tighter inner padding to keep the halves visually close. ' +
|
|
14
|
+
'The chevron half opens a Menu molecule; all dropdown keyboard navigation is inherited.',
|
|
15
|
+
props: [
|
|
16
|
+
{ name: 'children', type: 'ReactNode', required: true, description: 'Label content for the primary action button.' },
|
|
17
|
+
{ name: 'onClick', type: 'function', required: true, description: 'Handler fired when the primary (left) half is clicked.' },
|
|
18
|
+
{ name: 'menuItems', type: 'array', required: true, description: 'Array of { label, onSelect, disabled?, danger?, icon? } for the dropdown.' },
|
|
19
|
+
{ name: 'variant', type: 'enum', required: false, default: 'primary', description: 'Visual variant applied to both halves.', enumValues: ['primary', 'secondary', 'outline', 'ghost', 'danger', 'danger-outline', 'danger-ghost'] },
|
|
20
|
+
{ name: 'size', type: 'enum', required: false, default: 'md', description: 'Size applied to both halves.', enumValues: ['2xs', 'xs', 'sm', 'md', 'lg'] },
|
|
21
|
+
{ name: 'bordered', type: 'boolean', required: false, default: 'true', description: 'If false, removes the border on both halves.' },
|
|
22
|
+
{ name: 'menuPlacement', type: 'enum', required: false, default: 'bottom-end', description: 'Dropdown placement relative to the chevron trigger.', enumValues: ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'right'] },
|
|
23
|
+
{ name: 'disabled', type: 'boolean', required: false, description: 'Disables both halves.' },
|
|
24
|
+
{ name: 'loading', type: 'boolean', required: false, default: 'false', description: 'Shows a spinner in the primary half and disables both.' },
|
|
25
|
+
{ name: 'leftIcon', type: 'ReactNode', required: false, description: 'Icon rendered before the label in the primary half.' },
|
|
26
|
+
{ name: 'style', type: 'object', required: false, description: 'Style overrides for the wrapper div.' },
|
|
27
|
+
],
|
|
28
|
+
usageExamples: [
|
|
29
|
+
{
|
|
30
|
+
title: 'Basic',
|
|
31
|
+
code: `<SplitButton onClick={handleSave} menuItems={[{ label: 'Save as draft', onSelect: handleDraft }]}>Save</SplitButton>`,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
title: 'Outline with leftIcon',
|
|
35
|
+
code: `<SplitButton variant="outline" leftIcon={<Icon name="deploy" />} onClick={deploy} menuItems={[{ label: 'Deploy to staging', onSelect: deployStaging }, { label: 'Rollback', onSelect: rollback, danger: true }]}>Deploy</SplitButton>`,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
title: 'Menu items with icons',
|
|
39
|
+
code: `<SplitButton onClick={handleSave} menuItems={[{ label: 'Save as draft', onSelect: handleDraft, icon: <Icon name="file" /> }, { label: 'Export', onSelect: handleExport, icon: <Icon name="download" /> }]}>Save</SplitButton>`,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
title: 'Small ghost bordered (issue #90 use case)',
|
|
43
|
+
code: `<SplitButton variant="ghost" size="2xs" bordered onClick={toggle} menuItems={[{ label: 'Reset layout', onSelect: reset }]}>Collapse all</SplitButton>`,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
title: 'Danger outline with disabled item',
|
|
47
|
+
code: `<SplitButton variant="danger-outline" onClick={handleDelete} menuItems={[{ label: 'Move to trash', onSelect: trash }, { label: 'Delete forever', onSelect: nuke, danger: true }, { label: 'Archive (unavailable)', onSelect: archive, disabled: true }]}>Delete</SplitButton>`,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
title: 'Disabled',
|
|
51
|
+
code: `<SplitButton disabled onClick={handleSave} menuItems={[{ label: 'Option', onSelect: fn }]}>Save</SplitButton>`,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
title: 'Loading',
|
|
55
|
+
code: `<SplitButton loading onClick={handleSave} menuItems={[{ label: 'Option', onSelect: fn }]}>Saving...</SplitButton>`,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
compositionGraph: [
|
|
59
|
+
{ componentId: 'menu', componentName: 'Menu', role: 'Dropdown container for secondary actions', required: true },
|
|
60
|
+
{ componentId: 'menu-item', componentName: 'MenuItem', role: 'Individual dropdown action', required: true },
|
|
61
|
+
],
|
|
62
|
+
accessibility: {
|
|
63
|
+
role: 'group',
|
|
64
|
+
ariaAttributes: ['aria-label', 'aria-haspopup', 'aria-expanded', 'aria-busy'],
|
|
65
|
+
keyboardInteractions: [
|
|
66
|
+
'Enter/Space on primary button fires onClick',
|
|
67
|
+
'Enter/Space/ArrowDown on chevron opens dropdown',
|
|
68
|
+
'ArrowDown/ArrowUp cycles dropdown items',
|
|
69
|
+
'Home/End jumps to first/last item',
|
|
70
|
+
'Escape closes dropdown and returns focus to chevron',
|
|
71
|
+
'Tab closes dropdown',
|
|
72
|
+
],
|
|
73
|
+
notes: 'The wrapper uses role="group" with aria-label derived from children. The chevron button carries aria-haspopup="menu" and aria-expanded via Menu. Primary button has aria-busy when loading.',
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'nav-menu',
|
|
3
|
+
name: 'NavMenu',
|
|
4
|
+
tier: 'molecule',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'Multi-level navigation menu supporting vertical (sidebar) and horizontal (top bar) orientations with a sliding highlight pill, CSS-driven hover states, collapsible groups, and nested sub-menus.',
|
|
8
|
+
designIntent: 'NavMenu provides hierarchical navigation for sidebar and top-bar layouts. ' +
|
|
9
|
+
'A single sliding highlight pill follows the active item, driven entirely from the root via DOM measurement — ' +
|
|
10
|
+
'the root queries for data-active / data-active-parent attributes and positions an absolutely-placed pill ' +
|
|
11
|
+
'using requestAnimationFrame. MutationObserver and ResizeObserver auto-trigger re-measurement; ' +
|
|
12
|
+
'aria-hidden ancestry is used to detect collapsed items (not offsetHeight), eliminating all timeout-based coordination. ' +
|
|
13
|
+
'Hover states use a CSS rule on [data-lucent-navitem] with :not() exclusions for active, parent-active, hint, and disabled states, ' +
|
|
14
|
+
'rendering a 5% translucent text-primary tint that never conflicts with the accent pill. ' +
|
|
15
|
+
'In vertical orientation, parent items expand inline with smooth height animation (same pattern as Collapsible) ' +
|
|
16
|
+
'and children are indented by depth level. When a parent with an active child is collapsed, the pill slides ' +
|
|
17
|
+
'to the parent button with a lighter visual style (12% accent tint or surface-secondary in inverse mode) ' +
|
|
18
|
+
'so text remains readable without on-accent color. When re-expanded, the pill slides back to the child. ' +
|
|
19
|
+
'Parent items support "self-active" mode: setting isActive on a parent with no active children ' +
|
|
20
|
+
'highlights the parent with the full accent pill, useful for section-level pages that represent all children. ' +
|
|
21
|
+
'Inverse mode uses surface background with accent right-border (inset -3px) and elevation shadow. ' +
|
|
22
|
+
'The hasIcons prop controls left-padding alignment globally: when true, items use tighter padding (space-2) ' +
|
|
23
|
+
'and group headers align with icon start; sub-menu children inherit parentHasIcon via context so their ' +
|
|
24
|
+
'text aligns with the parent label text regardless of whether they have icons themselves. ' +
|
|
25
|
+
'In horizontal orientation, parent items show dropdown sub-menus on hover/click with enter/exit animation ' +
|
|
26
|
+
'and viewport collision detection. Groups are flattened in horizontal mode. ' +
|
|
27
|
+
'Three sizes (sm/md/lg) scale font, padding, gap, and icon width via a token map. ' +
|
|
28
|
+
'The compound API (NavMenu.Item, NavMenu.Group, NavMenu.Sub, NavMenu.Separator) keeps the tree declarative.',
|
|
29
|
+
props: [
|
|
30
|
+
{
|
|
31
|
+
name: 'orientation',
|
|
32
|
+
type: 'enum',
|
|
33
|
+
required: false,
|
|
34
|
+
default: '"vertical"',
|
|
35
|
+
description: 'Layout direction. Vertical for sidebars, horizontal for top navigation bars.',
|
|
36
|
+
enumValues: ['vertical', 'horizontal'],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'inverse',
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
required: false,
|
|
42
|
+
default: 'false',
|
|
43
|
+
description: 'Uses surface background with accent right-border and elevation shadow instead of accent fill for the active highlight pill.',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'size',
|
|
47
|
+
type: 'enum',
|
|
48
|
+
required: false,
|
|
49
|
+
default: '"md"',
|
|
50
|
+
description: 'Size variant controlling font size, padding, gap, and icon width.',
|
|
51
|
+
enumValues: ['sm', 'md', 'lg'],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'hasIcons',
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
required: false,
|
|
57
|
+
default: 'false',
|
|
58
|
+
description: 'Whether items use icons. Tightens left padding on items and group headers so text aligns with icon positions. ' +
|
|
59
|
+
'Sub-menu children automatically inherit parent icon awareness via context for consistent text alignment.',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'aria-label',
|
|
63
|
+
type: 'string',
|
|
64
|
+
required: false,
|
|
65
|
+
default: '"Navigation"',
|
|
66
|
+
description: 'Accessible label for the root <nav> element.',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'style',
|
|
70
|
+
type: 'object',
|
|
71
|
+
required: false,
|
|
72
|
+
description: 'Inline style overrides for the root <nav> element.',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
usageExamples: [
|
|
76
|
+
{
|
|
77
|
+
title: 'Vertical sidebar with icons',
|
|
78
|
+
code: `<NavMenu orientation="vertical" hasIcons>
|
|
79
|
+
<NavMenu.Item icon={<DashIcon />} href="/dashboard" isActive>Dashboard</NavMenu.Item>
|
|
80
|
+
<NavMenu.Group label="Workspace">
|
|
81
|
+
<NavMenu.Item icon={<FolderIcon />} href="/projects">Projects</NavMenu.Item>
|
|
82
|
+
<NavMenu.Item icon={<GearIcon />}>
|
|
83
|
+
Settings
|
|
84
|
+
<NavMenu.Sub>
|
|
85
|
+
<NavMenu.Item href="/settings/general" isActive>General</NavMenu.Item>
|
|
86
|
+
<NavMenu.Item href="/settings/team">Team</NavMenu.Item>
|
|
87
|
+
</NavMenu.Sub>
|
|
88
|
+
</NavMenu.Item>
|
|
89
|
+
</NavMenu.Group>
|
|
90
|
+
<NavMenu.Separator />
|
|
91
|
+
<NavMenu.Item icon={<HelpIcon />} href="/help">Help</NavMenu.Item>
|
|
92
|
+
</NavMenu>`,
|
|
93
|
+
description: 'Sidebar with icons, groups, and nested sub-menu. hasIcons tightens padding so group headers align with icons ' +
|
|
94
|
+
'and sub-menu children align with parent text.',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
title: 'Vertical sidebar without icons',
|
|
98
|
+
code: `<NavMenu orientation="vertical">
|
|
99
|
+
<NavMenu.Item href="/dashboard" isActive>Dashboard</NavMenu.Item>
|
|
100
|
+
<NavMenu.Group label="Workspace">
|
|
101
|
+
<NavMenu.Item href="/projects">Projects</NavMenu.Item>
|
|
102
|
+
<NavMenu.Item>
|
|
103
|
+
Settings
|
|
104
|
+
<NavMenu.Sub>
|
|
105
|
+
<NavMenu.Item href="/settings/general">General</NavMenu.Item>
|
|
106
|
+
<NavMenu.Item href="/settings/team">Team</NavMenu.Item>
|
|
107
|
+
</NavMenu.Sub>
|
|
108
|
+
</NavMenu.Item>
|
|
109
|
+
</NavMenu.Group>
|
|
110
|
+
</NavMenu>`,
|
|
111
|
+
description: 'Without hasIcons, items and group headers use standard padding (space-4) for comfortable text-only layout.',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
title: 'Horizontal top navigation',
|
|
115
|
+
code: `<NavMenu orientation="horizontal">
|
|
116
|
+
<NavMenu.Item href="/dashboard" isActive>Dashboard</NavMenu.Item>
|
|
117
|
+
<NavMenu.Item href="/projects">Projects</NavMenu.Item>
|
|
118
|
+
<NavMenu.Item>
|
|
119
|
+
Settings
|
|
120
|
+
<NavMenu.Sub>
|
|
121
|
+
<NavMenu.Item href="/settings/general">General</NavMenu.Item>
|
|
122
|
+
<NavMenu.Item href="/settings/team">Team</NavMenu.Item>
|
|
123
|
+
</NavMenu.Sub>
|
|
124
|
+
</NavMenu.Item>
|
|
125
|
+
</NavMenu>`,
|
|
126
|
+
description: 'Top bar layout. Parent items show dropdown sub-menus on hover/click with viewport collision detection.',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
title: 'Inverse mode with section groups',
|
|
130
|
+
code: `<NavMenu orientation="vertical" inverse>
|
|
131
|
+
<NavMenu.Group label="Main">
|
|
132
|
+
<NavMenu.Item href="/dashboard" isActive>Dashboard</NavMenu.Item>
|
|
133
|
+
<NavMenu.Item href="/analytics">Analytics</NavMenu.Item>
|
|
134
|
+
</NavMenu.Group>
|
|
135
|
+
<NavMenu.Separator />
|
|
136
|
+
<NavMenu.Group label="Admin" defaultOpen={false}>
|
|
137
|
+
<NavMenu.Item href="/users">Users</NavMenu.Item>
|
|
138
|
+
<NavMenu.Item href="/roles">Roles</NavMenu.Item>
|
|
139
|
+
</NavMenu.Group>
|
|
140
|
+
</NavMenu>`,
|
|
141
|
+
description: 'Inverse highlight: surface background with accent right-border and elevation shadow. Text stays text-primary.',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
title: 'Self-active parent (section-level page)',
|
|
145
|
+
code: `<NavMenu orientation="vertical" hasIcons>
|
|
146
|
+
<NavMenu.Item isActive icon={<TextIcon />}>
|
|
147
|
+
Text & Labels
|
|
148
|
+
<NavMenu.Sub>
|
|
149
|
+
<NavMenu.Item href="/text">Text</NavMenu.Item>
|
|
150
|
+
<NavMenu.Item href="/badge">Badge</NavMenu.Item>
|
|
151
|
+
<NavMenu.Item href="/chip">Chip</NavMenu.Item>
|
|
152
|
+
</NavMenu.Sub>
|
|
153
|
+
</NavMenu.Item>
|
|
154
|
+
<NavMenu.Item icon={<InputIcon />}>
|
|
155
|
+
Input Fields
|
|
156
|
+
<NavMenu.Sub>
|
|
157
|
+
<NavMenu.Item href="/input" isActive>Input</NavMenu.Item>
|
|
158
|
+
<NavMenu.Item href="/select">Select</NavMenu.Item>
|
|
159
|
+
</NavMenu.Sub>
|
|
160
|
+
</NavMenu.Item>
|
|
161
|
+
</NavMenu>`,
|
|
162
|
+
description: 'Setting isActive on a parent with no active children gives it the full accent highlight. ' +
|
|
163
|
+
'The sub-nav expands but no child is individually selected — the parent represents all items. ' +
|
|
164
|
+
'Clicking a specific child moves the highlight to that child.',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
title: 'Compact sidebar (sm size)',
|
|
168
|
+
code: `<NavMenu orientation="vertical" size="sm" hasIcons>
|
|
169
|
+
<NavMenu.Item href="/dash" icon={<DashIcon />} isActive>Dashboard</NavMenu.Item>
|
|
170
|
+
<NavMenu.Item href="/projects" icon={<FolderIcon />}>Projects</NavMenu.Item>
|
|
171
|
+
<NavMenu.Item icon={<GearIcon />}>
|
|
172
|
+
Settings
|
|
173
|
+
<NavMenu.Sub>
|
|
174
|
+
<NavMenu.Item href="/settings/general">General</NavMenu.Item>
|
|
175
|
+
<NavMenu.Item href="/settings/team">Team</NavMenu.Item>
|
|
176
|
+
</NavMenu.Sub>
|
|
177
|
+
</NavMenu.Item>
|
|
178
|
+
</NavMenu>`,
|
|
179
|
+
description: 'Small size variant for compact sidebar layouts with tighter padding and smaller font.',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
title: 'Badges and disabled items',
|
|
183
|
+
code: `<NavMenu orientation="vertical" hasIcons>
|
|
184
|
+
<NavMenu.Item href="/inbox" icon={<InboxIcon />} badge={<Chip size="sm" variant="accent">3</Chip>}>Inbox</NavMenu.Item>
|
|
185
|
+
<NavMenu.Item href="/settings" icon={<GearIcon />} isActive>Settings</NavMenu.Item>
|
|
186
|
+
<NavMenu.Item href="/archive" icon={<ArchiveIcon />} disabled>Archived</NavMenu.Item>
|
|
187
|
+
</NavMenu>`,
|
|
188
|
+
description: 'Items with badge counts and disabled state. Disabled items use not-allowed cursor and muted text.',
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
title: 'Polymorphic items with React Router',
|
|
192
|
+
code: `<NavMenu orientation="vertical">
|
|
193
|
+
<NavMenu.Item as={Link} href="/dashboard" isActive>Dashboard</NavMenu.Item>
|
|
194
|
+
<NavMenu.Item as={Link} href="/projects">Projects</NavMenu.Item>
|
|
195
|
+
</NavMenu>`,
|
|
196
|
+
description: 'Using the "as" prop to render items as React Router Link components instead of anchor tags.',
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
compositionGraph: [
|
|
200
|
+
{
|
|
201
|
+
componentId: 'nav-menu-item',
|
|
202
|
+
componentName: 'NavMenu.Item',
|
|
203
|
+
role: 'Individual navigation link or parent toggle. Sets data-active when self-active, data-active-parent when collapsed with an active child. Uses data-lucent-navitem for CSS hover targeting.',
|
|
204
|
+
required: true,
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
componentId: 'nav-menu-sub',
|
|
208
|
+
componentName: 'NavMenu.Sub',
|
|
209
|
+
role: 'Marker wrapper for nested sub-menu children inside a parent NavMenu.Item.',
|
|
210
|
+
required: false,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
componentId: 'nav-menu-group',
|
|
214
|
+
componentName: 'NavMenu.Group',
|
|
215
|
+
role: 'Section grouping with optional uppercase label header and independent collapse. Header left padding responds to hasIcons context.',
|
|
216
|
+
required: false,
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
componentId: 'nav-menu-separator',
|
|
220
|
+
componentName: 'NavMenu.Separator',
|
|
221
|
+
role: 'Visual divider between sections. Horizontal line in vertical mode, vertical line in horizontal mode.',
|
|
222
|
+
required: false,
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
accessibility: {
|
|
226
|
+
role: 'navigation',
|
|
227
|
+
ariaAttributes: [
|
|
228
|
+
'aria-label on root <nav>',
|
|
229
|
+
'aria-expanded on parent items with sub-menus',
|
|
230
|
+
'aria-current="page" on active leaf items',
|
|
231
|
+
'aria-disabled on disabled items',
|
|
232
|
+
'aria-hidden on collapsed sub-menu content and the highlight pill',
|
|
233
|
+
'role="separator" on dividers',
|
|
234
|
+
],
|
|
235
|
+
keyboardInteractions: [
|
|
236
|
+
'Enter / Space — toggle parent item expand/collapse',
|
|
237
|
+
'ArrowRight (vertical) / ArrowDown (horizontal) — expand parent item',
|
|
238
|
+
'ArrowLeft (vertical) / ArrowUp (horizontal) — collapse parent item',
|
|
239
|
+
'Escape — close open sub-menu',
|
|
240
|
+
],
|
|
241
|
+
notes: 'Parent items use aria-expanded to communicate open/closed state. ' +
|
|
242
|
+
'Active leaf items use aria-current="page". Disabled items use aria-disabled with no click handler. ' +
|
|
243
|
+
'Collapsed sections are marked aria-hidden="true", which the sliding highlight uses to detect ' +
|
|
244
|
+
'visibility — items inside aria-hidden containers are skipped in favor of the parent fallback. ' +
|
|
245
|
+
'Separators use role="separator". The highlight pill is aria-hidden.',
|
|
246
|
+
},
|
|
247
|
+
};
|
|
@@ -7,11 +7,12 @@ export const ButtonManifest = {
|
|
|
7
7
|
description: 'A clickable control that triggers an action. The primary interactive primitive in Lucent UI.',
|
|
8
8
|
designIntent: 'Buttons communicate available actions. Variant conveys hierarchy: use "primary" for the ' +
|
|
9
9
|
'single most important action in a view, "secondary" for supporting actions, "ghost" for ' +
|
|
10
|
-
'low-emphasis actions in dense UIs, "outline" for bordered buttons with
|
|
10
|
+
'low-emphasis actions in dense UIs, "outline" for bordered buttons with transparent background, and "danger" exclusively for destructive or irreversible ' +
|
|
11
11
|
'operations. Use "danger-ghost" for low-emphasis destructive actions (red text, no fill) and ' +
|
|
12
|
-
'"danger-outline" for bordered destructive buttons. Size should match surrounding content density — prefer "md" as the default, ' +
|
|
12
|
+
'"danger-outline" for bordered destructive buttons (also transparent background). Size should match surrounding content density — prefer "md" as the default, ' +
|
|
13
13
|
'"sm" for toolbars or tables, "xs" for compact UIs like customizer panels, and "2xs" for ' +
|
|
14
|
-
'ultra-dense inline controls (~22px height) such as table-inline actions or toolbar icon triggers.'
|
|
14
|
+
'ultra-dense inline controls (~22px height) such as table-inline actions or toolbar icon triggers. ' +
|
|
15
|
+
'Icon-only buttons (leftIcon/rightIcon without children) automatically render as square with aspect-ratio: 1.',
|
|
15
16
|
props: [
|
|
16
17
|
{
|
|
17
18
|
name: 'variant',
|
|
@@ -44,8 +45,8 @@ export const ButtonManifest = {
|
|
|
44
45
|
{
|
|
45
46
|
name: 'children',
|
|
46
47
|
type: 'ReactNode',
|
|
47
|
-
required:
|
|
48
|
-
description: 'Button label or content.',
|
|
48
|
+
required: false,
|
|
49
|
+
description: 'Button label or content. Omit for icon-only buttons (provide leftIcon or rightIcon instead).',
|
|
49
50
|
},
|
|
50
51
|
{
|
|
51
52
|
name: 'disabled',
|
|
@@ -161,6 +162,11 @@ export const ButtonManifest = {
|
|
|
161
162
|
title: 'Dense inline action',
|
|
162
163
|
code: `<Button variant="ghost" size="2xs" leftIcon={<RefreshIcon />}>Retry</Button>`,
|
|
163
164
|
},
|
|
165
|
+
{
|
|
166
|
+
title: 'Icon-only (square)',
|
|
167
|
+
code: `<Button variant="outline" size="2xs" leftIcon={<CloseIcon />} aria-label="Close" />`,
|
|
168
|
+
description: 'Omitting children auto-sizes the button as a square via aspect-ratio: 1.',
|
|
169
|
+
},
|
|
164
170
|
],
|
|
165
171
|
compositionGraph: [],
|
|
166
172
|
accessibility: {
|