lucent-ui 0.4.1 → 0.4.2
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-server/src/components/atoms/NavLink/NavLink.manifest.js +96 -0
- package/dist-server/src/components/molecules/Breadcrumb/Breadcrumb.manifest.js +76 -0
- package/dist-server/src/components/molecules/Collapsible/Collapsible.manifest.js +83 -0
- package/dist-server/src/components/molecules/Tabs/Tabs.manifest.js +105 -0
- package/package.json +1 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'nav-link',
|
|
3
|
+
name: 'NavLink',
|
|
4
|
+
tier: 'atom',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'Polymorphic navigation link atom that renders an active, hover, and disabled state using accent background and token colours.',
|
|
8
|
+
designIntent: 'NavLink is the single primitive for all sidebar and top-nav items. ' +
|
|
9
|
+
'The active state uses the accent background with on-accent text so it stands out clearly ' +
|
|
10
|
+
'regardless of theme. Hover applies a subtle muted background only on inactive items. ' +
|
|
11
|
+
'The disabled state drains colour without changing layout. ' +
|
|
12
|
+
'Polymorphism via the `as` prop lets consumers pass a router Link (react-router, Next.js, etc.) ' +
|
|
13
|
+
'without losing the visual contract — the component owns style, the consumer owns routing.',
|
|
14
|
+
props: [
|
|
15
|
+
{
|
|
16
|
+
name: 'children',
|
|
17
|
+
type: 'ReactNode',
|
|
18
|
+
required: true,
|
|
19
|
+
description: 'Label content of the nav item.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'href',
|
|
23
|
+
type: 'string',
|
|
24
|
+
required: false,
|
|
25
|
+
description: 'Destination URL passed to the underlying element. Omitted when disabled.',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'isActive',
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
required: false,
|
|
31
|
+
default: 'false',
|
|
32
|
+
description: 'When true, renders the accent background and sets aria-current="page".',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'icon',
|
|
36
|
+
type: 'ReactNode',
|
|
37
|
+
required: false,
|
|
38
|
+
description: 'Optional leading icon rendered before the label.',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'disabled',
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
required: false,
|
|
44
|
+
default: 'false',
|
|
45
|
+
description: 'Disables interaction and renders muted text. Removes href and onClick.',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'onClick',
|
|
49
|
+
type: 'function',
|
|
50
|
+
required: false,
|
|
51
|
+
description: 'Click handler. Omitted when disabled.',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'as',
|
|
55
|
+
type: 'React.ElementType',
|
|
56
|
+
required: false,
|
|
57
|
+
default: '"a"',
|
|
58
|
+
description: 'Root element type. Pass a router Link component for SPA navigation.',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'style',
|
|
62
|
+
type: 'object',
|
|
63
|
+
required: false,
|
|
64
|
+
description: 'Inline style overrides merged onto the root element.',
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
usageExamples: [
|
|
68
|
+
{
|
|
69
|
+
title: 'Basic link',
|
|
70
|
+
code: `<NavLink href="/dashboard">Dashboard</NavLink>`,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
title: 'Active state with icon',
|
|
74
|
+
code: `<NavLink href="/settings" isActive icon={<Icon name="settings" size={16} />}>
|
|
75
|
+
Settings
|
|
76
|
+
</NavLink>`,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
title: 'With react-router Link',
|
|
80
|
+
code: `import { Link } from 'react-router-dom';
|
|
81
|
+
|
|
82
|
+
<NavLink as={Link} to="/profile">Profile</NavLink>`,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
title: 'Disabled',
|
|
86
|
+
code: `<NavLink href="/admin" disabled>Admin</NavLink>`,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
compositionGraph: [],
|
|
90
|
+
accessibility: {
|
|
91
|
+
ariaAttributes: ['aria-current="page"', 'aria-disabled'],
|
|
92
|
+
notes: 'Sets aria-current="page" on the active item. When disabled, aria-disabled is set and ' +
|
|
93
|
+
'href/onClick are removed so the element is not interactive. Wrap a list of NavLinks in ' +
|
|
94
|
+
'a <nav> element with an aria-label for landmark semantics.',
|
|
95
|
+
},
|
|
96
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'breadcrumb',
|
|
3
|
+
name: 'Breadcrumb',
|
|
4
|
+
tier: 'molecule',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'Horizontal breadcrumb trail that renders an ordered list of navigable items with a configurable separator.',
|
|
8
|
+
designIntent: 'Breadcrumb communicates the user\'s location within a hierarchy. ' +
|
|
9
|
+
'All intermediate items are secondary-coloured and interactive (link or button), brightening to primary on hover. ' +
|
|
10
|
+
'The last item is always the current page: rendered as static primary text with aria-current="page", never a link. ' +
|
|
11
|
+
'The separator is aria-hidden so screen readers announce only the labels. ' +
|
|
12
|
+
'Items accept any ReactNode label so icons or badges can be embedded when needed.',
|
|
13
|
+
props: [
|
|
14
|
+
{
|
|
15
|
+
name: 'items',
|
|
16
|
+
type: 'array',
|
|
17
|
+
required: true,
|
|
18
|
+
description: 'Ordered array of BreadcrumbItem objects: { label: ReactNode; href?: string; onClick?: () => void }. ' +
|
|
19
|
+
'The last item is always the current page and is rendered as static text.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'separator',
|
|
23
|
+
type: 'ReactNode',
|
|
24
|
+
required: false,
|
|
25
|
+
default: '"/"',
|
|
26
|
+
description: 'Visual separator rendered between items. Defaults to "/".',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'style',
|
|
30
|
+
type: 'object',
|
|
31
|
+
required: false,
|
|
32
|
+
description: 'Inline style overrides for the root <nav> element.',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
usageExamples: [
|
|
36
|
+
{
|
|
37
|
+
title: 'Basic breadcrumb',
|
|
38
|
+
code: `<Breadcrumb
|
|
39
|
+
items={[
|
|
40
|
+
{ label: 'Home', href: '/' },
|
|
41
|
+
{ label: 'Projects', href: '/projects' },
|
|
42
|
+
{ label: 'My Project' },
|
|
43
|
+
]}
|
|
44
|
+
/>`,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
title: 'Custom separator',
|
|
48
|
+
code: `<Breadcrumb
|
|
49
|
+
items={[
|
|
50
|
+
{ label: 'Home', href: '/' },
|
|
51
|
+
{ label: 'Settings' },
|
|
52
|
+
]}
|
|
53
|
+
separator="›"
|
|
54
|
+
/>`,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
title: 'Button-based items (SPA navigation)',
|
|
58
|
+
code: `<Breadcrumb
|
|
59
|
+
items={[
|
|
60
|
+
{ label: 'Home', onClick: () => navigate('/') },
|
|
61
|
+
{ label: 'Reports', onClick: () => navigate('/reports') },
|
|
62
|
+
{ label: 'Q1 Summary' },
|
|
63
|
+
]}
|
|
64
|
+
/>`,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
compositionGraph: [
|
|
68
|
+
{ componentId: 'text', componentName: 'Text', role: 'Current page (last) item label', required: true },
|
|
69
|
+
],
|
|
70
|
+
accessibility: {
|
|
71
|
+
role: 'navigation',
|
|
72
|
+
ariaAttributes: ['aria-label="Breadcrumb"', 'aria-current="page"'],
|
|
73
|
+
notes: 'Rendered as a <nav aria-label="Breadcrumb"> containing an <ol>. ' +
|
|
74
|
+
'The last item receives aria-current="page". Separators are aria-hidden.',
|
|
75
|
+
},
|
|
76
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'collapsible',
|
|
3
|
+
name: 'Collapsible',
|
|
4
|
+
tier: 'molecule',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'Animated expand/collapse container with a built-in chevron trigger and smooth height transition.',
|
|
8
|
+
designIntent: 'Collapsible hides secondary content behind a trigger button to reduce visual noise. ' +
|
|
9
|
+
'Height is animated by snapshotting scrollHeight before closing and transitioning to 0, ' +
|
|
10
|
+
'then removing the fixed height after the open transition completes so content can reflow freely. ' +
|
|
11
|
+
'A built-in chevron rotates 180° on open, giving clear directional affordance. ' +
|
|
12
|
+
'The `trigger` prop accepts only label content — the chevron and button chrome are owned by the component ' +
|
|
13
|
+
'so callers cannot accidentally break the expand/collapse contract. ' +
|
|
14
|
+
'The component supports controlled (open + onOpenChange) and uncontrolled (defaultOpen) modes.',
|
|
15
|
+
props: [
|
|
16
|
+
{
|
|
17
|
+
name: 'trigger',
|
|
18
|
+
type: 'ReactNode',
|
|
19
|
+
required: true,
|
|
20
|
+
description: 'Label content displayed inside the trigger button alongside the built-in chevron.',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'children',
|
|
24
|
+
type: 'ReactNode',
|
|
25
|
+
required: true,
|
|
26
|
+
description: 'Content revealed when the collapsible is open.',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'defaultOpen',
|
|
30
|
+
type: 'boolean',
|
|
31
|
+
required: false,
|
|
32
|
+
default: 'false',
|
|
33
|
+
description: 'Initial open state in uncontrolled mode.',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'open',
|
|
37
|
+
type: 'boolean',
|
|
38
|
+
required: false,
|
|
39
|
+
description: 'Controlled open state. When provided the component is fully controlled.',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'onOpenChange',
|
|
43
|
+
type: 'function',
|
|
44
|
+
required: false,
|
|
45
|
+
description: 'Callback fired with the new open boolean when the trigger is clicked.',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'style',
|
|
49
|
+
type: 'object',
|
|
50
|
+
required: false,
|
|
51
|
+
description: 'Inline style overrides for the root container.',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
usageExamples: [
|
|
55
|
+
{
|
|
56
|
+
title: 'Uncontrolled',
|
|
57
|
+
code: `<Collapsible trigger="Advanced options">
|
|
58
|
+
<Text size="sm" color="secondary">Hidden settings go here.</Text>
|
|
59
|
+
</Collapsible>`,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
title: 'Controlled',
|
|
63
|
+
code: `const [open, setOpen] = useState(false);
|
|
64
|
+
|
|
65
|
+
<Collapsible open={open} onOpenChange={setOpen} trigger="Details">
|
|
66
|
+
<Text size="sm">Detailed content</Text>
|
|
67
|
+
</Collapsible>`,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
title: 'Default open',
|
|
71
|
+
code: `<Collapsible trigger="Description" defaultOpen>
|
|
72
|
+
<Text size="sm" color="secondary">This section starts expanded.</Text>
|
|
73
|
+
</Collapsible>`,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
compositionGraph: [],
|
|
77
|
+
accessibility: {
|
|
78
|
+
ariaAttributes: ['aria-expanded', 'aria-hidden'],
|
|
79
|
+
notes: 'The trigger is a <button> with aria-expanded reflecting the open state. ' +
|
|
80
|
+
'The content wrapper has aria-hidden="true" when collapsed so screen readers skip it. ' +
|
|
81
|
+
'The built-in chevron SVG is aria-hidden.',
|
|
82
|
+
},
|
|
83
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'tabs',
|
|
3
|
+
name: 'Tabs',
|
|
4
|
+
tier: 'molecule',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'Controlled or uncontrolled tab strip with a sliding accent indicator and full ARIA tablist keyboard navigation.',
|
|
8
|
+
designIntent: 'Tabs organises sibling content panels behind a horizontal strip of tab buttons. ' +
|
|
9
|
+
'The active tab is marked by a sliding 2px accent-coloured underline that animates between tabs ' +
|
|
10
|
+
'using left/width transitions (no animation on the initial render to avoid a flash). ' +
|
|
11
|
+
'Hover shows a subtle muted pill on the inner label span — intentionally scoped to the label area ' +
|
|
12
|
+
'so it does not bleed into the bottom border zone where the indicator lives. ' +
|
|
13
|
+
'Active text is medium-weight; inactive tabs are secondary-coloured to reduce visual noise. ' +
|
|
14
|
+
'Disabled tabs are rendered with not-allowed cursor and disabled colour, and are skipped by ' +
|
|
15
|
+
'keyboard navigation. The component supports controlled (value + onChange) and uncontrolled ' +
|
|
16
|
+
'(defaultValue) modes.',
|
|
17
|
+
props: [
|
|
18
|
+
{
|
|
19
|
+
name: 'tabs',
|
|
20
|
+
type: 'array',
|
|
21
|
+
required: true,
|
|
22
|
+
description: 'Array of TabItem objects: { value: string; label: ReactNode; content: ReactNode; disabled?: boolean }.',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'defaultValue',
|
|
26
|
+
type: 'string',
|
|
27
|
+
required: false,
|
|
28
|
+
description: 'Initially selected tab value in uncontrolled mode. Defaults to the first tab.',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'value',
|
|
32
|
+
type: 'string',
|
|
33
|
+
required: false,
|
|
34
|
+
description: 'Controlled active tab value. When provided the component is fully controlled.',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'onChange',
|
|
38
|
+
type: 'function',
|
|
39
|
+
required: false,
|
|
40
|
+
description: 'Callback fired with the new tab value when the user selects a tab.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'style',
|
|
44
|
+
type: 'object',
|
|
45
|
+
required: false,
|
|
46
|
+
description: 'Inline style overrides for the root container.',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
usageExamples: [
|
|
50
|
+
{
|
|
51
|
+
title: 'Uncontrolled tabs',
|
|
52
|
+
code: `<Tabs
|
|
53
|
+
tabs={[
|
|
54
|
+
{ value: 'overview', label: 'Overview', content: <p>Overview content</p> },
|
|
55
|
+
{ value: 'details', label: 'Details', content: <p>Details content</p> },
|
|
56
|
+
{ value: 'history', label: 'History', content: <p>History content</p> },
|
|
57
|
+
]}
|
|
58
|
+
/>`,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
title: 'Controlled tabs',
|
|
62
|
+
code: `const [tab, setTab] = useState('overview');
|
|
63
|
+
|
|
64
|
+
<Tabs
|
|
65
|
+
value={tab}
|
|
66
|
+
onChange={setTab}
|
|
67
|
+
tabs={[
|
|
68
|
+
{ value: 'overview', label: 'Overview', content: <p>Overview</p> },
|
|
69
|
+
{ value: 'details', label: 'Details', content: <p>Details</p> },
|
|
70
|
+
]}
|
|
71
|
+
/>`,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
title: 'With a disabled tab',
|
|
75
|
+
code: `<Tabs
|
|
76
|
+
tabs={[
|
|
77
|
+
{ value: 'active', label: 'Active', content: <p>Active</p> },
|
|
78
|
+
{ value: 'disabled', label: 'Disabled', content: <p>Hidden</p>, disabled: true },
|
|
79
|
+
]}
|
|
80
|
+
/>`,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
compositionGraph: [],
|
|
84
|
+
accessibility: {
|
|
85
|
+
role: 'tablist',
|
|
86
|
+
ariaAttributes: [
|
|
87
|
+
'role="tablist"',
|
|
88
|
+
'role="tab"',
|
|
89
|
+
'role="tabpanel"',
|
|
90
|
+
'aria-selected',
|
|
91
|
+
'aria-controls',
|
|
92
|
+
'aria-labelledby',
|
|
93
|
+
],
|
|
94
|
+
keyboardInteractions: [
|
|
95
|
+
'ArrowRight — move focus to the next enabled tab and activate it',
|
|
96
|
+
'ArrowLeft — move focus to the previous enabled tab and activate it',
|
|
97
|
+
'Home — move focus and activate the first enabled tab',
|
|
98
|
+
'End — move focus and activate the last enabled tab',
|
|
99
|
+
],
|
|
100
|
+
notes: 'Each tab button has role="tab", aria-selected, and aria-controls pointing to its panel. ' +
|
|
101
|
+
'Each panel has role="tabpanel" and aria-labelledby pointing to its tab. ' +
|
|
102
|
+
'Inactive tabs have tabIndex=-1; only the active tab is in the natural tab order. ' +
|
|
103
|
+
'Disabled tabs are excluded from keyboard cycling.',
|
|
104
|
+
},
|
|
105
|
+
};
|