lucent-ui 0.4.0 → 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.
@@ -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,160 @@
1
+ export const COMPONENT_MANIFEST = {
2
+ id: 'page-layout',
3
+ name: 'PageLayout',
4
+ tier: 'molecule',
5
+ domain: 'neutral',
6
+ specVersion: '0.1',
7
+ description: 'Full-viewport shell layout with optional header, left sidebar, right panel, and footer slots arranged in a flex column/row structure.',
8
+ designIntent: 'PageLayout owns the outermost chrome of an application page. The body row is a flex row containing ' +
9
+ 'an optional left sidebar, a bordered main content card, and an optional right panel — all as structural ' +
10
+ 'siblings so they share the same vertical space. The header and footer sit outside the body row as ' +
11
+ 'flex children of the outer column, ensuring they span the full width. Sidebars collapse to zero width ' +
12
+ 'with a smooth width transition, avoiding layout jumps. The main card automatically drops its right ' +
13
+ 'margin when a right panel is present so no manual mainStyle override is needed. ' +
14
+ 'The footer is intentionally narrow (default 28px) and should be used sparingly — suited to ' +
15
+ 'status bars, connection indicators, keyboard shortcut hints, or contextual action strips, ' +
16
+ 'in the style of an editor status bar. It is not meant as a general-purpose page footer.',
17
+ props: [
18
+ {
19
+ name: 'children',
20
+ type: 'ReactNode',
21
+ required: true,
22
+ description: 'Content rendered inside the main content card.',
23
+ },
24
+ {
25
+ name: 'header',
26
+ type: 'ReactNode',
27
+ required: false,
28
+ description: 'Content rendered in the full-width header bar above the body row.',
29
+ },
30
+ {
31
+ name: 'headerHeight',
32
+ type: 'string',
33
+ required: false,
34
+ default: '48',
35
+ description: 'Header height in px (number) or any CSS value (string). Default: 48.',
36
+ },
37
+ {
38
+ name: 'sidebar',
39
+ type: 'ReactNode',
40
+ required: false,
41
+ description: 'Content rendered in the left sidebar, a flex sibling of <main>.',
42
+ },
43
+ {
44
+ name: 'sidebarWidth',
45
+ type: 'string',
46
+ required: false,
47
+ default: '240',
48
+ description: 'Left sidebar width in px (number) or any CSS value (string). Default: 240.',
49
+ },
50
+ {
51
+ name: 'sidebarCollapsed',
52
+ type: 'boolean',
53
+ required: false,
54
+ default: 'false',
55
+ description: 'When true, collapses the left sidebar to zero width with a transition.',
56
+ },
57
+ {
58
+ name: 'rightSidebar',
59
+ type: 'ReactNode',
60
+ required: false,
61
+ description: 'Content rendered in the right panel, a flex sibling of <main> after it.',
62
+ },
63
+ {
64
+ name: 'rightSidebarWidth',
65
+ type: 'string',
66
+ required: false,
67
+ default: '240',
68
+ description: 'Right panel width in px (number) or any CSS value (string). Default: 240.',
69
+ },
70
+ {
71
+ name: 'rightSidebarCollapsed',
72
+ type: 'boolean',
73
+ required: false,
74
+ default: 'false',
75
+ description: 'When true, collapses the right panel to zero width with a transition.',
76
+ },
77
+ {
78
+ name: 'footer',
79
+ type: 'ReactNode',
80
+ required: false,
81
+ description: 'Content rendered in the full-width footer bar below the body row.',
82
+ },
83
+ {
84
+ name: 'footerHeight',
85
+ type: 'string',
86
+ required: false,
87
+ default: '28',
88
+ description: 'Footer height in px (number) or any CSS value (string). Default: 28 — sized for a compact status bar.',
89
+ },
90
+ {
91
+ name: 'mainStyle',
92
+ type: 'object',
93
+ required: false,
94
+ description: 'Inline style overrides for the main content card (border, borderRadius, boxShadow, etc.).',
95
+ },
96
+ {
97
+ name: 'style',
98
+ type: 'object',
99
+ required: false,
100
+ description: 'Inline style overrides for the outermost layout wrapper.',
101
+ },
102
+ ],
103
+ usageExamples: [
104
+ {
105
+ title: 'Header and left sidebar',
106
+ code: `<PageLayout
107
+ header={<div>My App</div>}
108
+ sidebar={<nav>...</nav>}
109
+ sidebarWidth={200}
110
+ >
111
+ <div>Page content</div>
112
+ </PageLayout>`,
113
+ },
114
+ {
115
+ title: 'With right panel',
116
+ code: `<PageLayout
117
+ header={<div>My App</div>}
118
+ sidebar={<nav>...</nav>}
119
+ rightSidebar={<aside>Details panel</aside>}
120
+ rightSidebarWidth={280}
121
+ >
122
+ <div>Page content</div>
123
+ </PageLayout>`,
124
+ },
125
+ {
126
+ title: 'Collapsible sidebar',
127
+ code: `<PageLayout
128
+ sidebar={<nav>...</nav>}
129
+ sidebarCollapsed={isCollapsed}
130
+ >
131
+ <div>Page content</div>
132
+ </PageLayout>`,
133
+ },
134
+ {
135
+ title: 'With status bar footer',
136
+ code: `<PageLayout
137
+ header={<div>My App</div>}
138
+ sidebar={<nav>...</nav>}
139
+ footer={
140
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', height: '100%', padding: '0 8px' }}>
141
+ <Text size="xs" color="secondary">main</Text>
142
+ <Text size="xs" color="info">Ready</Text>
143
+ </div>
144
+ }
145
+ >
146
+ <div>Page content</div>
147
+ </PageLayout>`,
148
+ },
149
+ ],
150
+ compositionGraph: [
151
+ { componentId: 'text', componentName: 'Text', role: 'Header/footer/sidebar labels', required: false },
152
+ { componentId: 'nav-link', componentName: 'NavLink', role: 'Sidebar navigation items', required: false },
153
+ ],
154
+ accessibility: {
155
+ notes: 'The left sidebar renders as a <div>; wrap its contents in a <nav> with aria-label for landmark semantics. ' +
156
+ 'The right panel renders as <aside> which is a complementary landmark — provide an aria-label if multiple ' +
157
+ 'aside elements are present on the page. The main content renders as <main>, which is a main landmark ' +
158
+ 'and should appear only once per page.',
159
+ },
160
+ };
@@ -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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lucent-ui",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "An AI-first React component library with machine-readable manifests.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",