lucent-ui 0.4.1 → 0.5.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 +158 -26
- package/dist/index.d.ts +80 -1
- package/dist/index.js +1799 -956
- package/dist-server/src/components/atoms/CodeBlock/CodeBlock.manifest.js +80 -0
- package/dist-server/src/components/atoms/NavLink/NavLink.manifest.js +96 -0
- package/dist-server/src/components/atoms/Slider/Slider.manifest.js +98 -0
- package/dist-server/src/components/atoms/Table/Table.manifest.js +98 -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/dist-server/src/manifest/examples/button.manifest.js +11 -0
- package/package.json +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'code-block',
|
|
3
|
+
name: 'CodeBlock',
|
|
4
|
+
tier: 'atom',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'A styled code display with optional tabs, a language label, copy-to-clipboard, and an AI prompt variant.',
|
|
8
|
+
designIntent: 'Use CodeBlock for static code snippets, install commands, API examples, and AI prompt sharing. ' +
|
|
9
|
+
'The tabs prop switches between related snippets (e.g. pnpm / npm / yarn). ' +
|
|
10
|
+
'The prompt variant renders a single-line truncated display suited to AI tool prompts — ' +
|
|
11
|
+
'the full text is always copied even when visually clipped. ' +
|
|
12
|
+
'Zero-dependency — no syntax highlighting library is bundled.',
|
|
13
|
+
props: [
|
|
14
|
+
{
|
|
15
|
+
name: 'code',
|
|
16
|
+
type: 'string',
|
|
17
|
+
required: false,
|
|
18
|
+
description: 'Code string — used in single (non-tabbed) mode.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'language',
|
|
22
|
+
type: 'string',
|
|
23
|
+
required: false,
|
|
24
|
+
description: 'Language label shown in the header (non-tabbed mode only). Purely cosmetic.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'tabs',
|
|
28
|
+
type: 'array',
|
|
29
|
+
required: false,
|
|
30
|
+
description: 'Tabbed mode. Each entry has { label, code, language?, icon? }. ' +
|
|
31
|
+
'Switching tabs resets the copied state.',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'variant',
|
|
35
|
+
type: 'enum',
|
|
36
|
+
required: false,
|
|
37
|
+
default: 'code',
|
|
38
|
+
enumValues: ['code', 'prompt'],
|
|
39
|
+
description: '"code" renders a <pre><code> block with horizontal scroll. ' +
|
|
40
|
+
'"prompt" renders a single-line truncated span suited to AI prompts.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'helperText',
|
|
44
|
+
type: 'string',
|
|
45
|
+
required: false,
|
|
46
|
+
description: 'Descriptive text rendered between the tab bar and the code area.',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'showCopyButton',
|
|
50
|
+
type: 'boolean',
|
|
51
|
+
required: false,
|
|
52
|
+
default: 'true',
|
|
53
|
+
description: 'Renders a copy-to-clipboard button. ' +
|
|
54
|
+
'Shows a "Copied!" confirmation for 2 s on success.',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
usageExamples: [
|
|
58
|
+
{
|
|
59
|
+
title: 'Single snippet',
|
|
60
|
+
code: `<CodeBlock language="tsx" code={\`<Button variant="primary">Save</Button>\`} />`,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
title: 'Tabbed install commands',
|
|
64
|
+
code: `<CodeBlock tabs={[\n { label: 'pnpm', code: 'pnpm add lucent-ui', language: 'bash' },\n { label: 'npm', code: 'npm install lucent-ui', language: 'bash' },\n]} />`,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
title: 'AI prompt',
|
|
68
|
+
code: `<CodeBlock\n variant="prompt"\n helperText="Paste into Claude:"\n tabs={[\n { label: 'Claude', icon: '♦', code: '"Add a Button with variant=\\"primary\\"."' },\n { label: 'Cursor', icon: '↖', code: '@lucent-ui Add a primary Button.' },\n ]}\n/>`,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
compositionGraph: [],
|
|
72
|
+
accessibility: {
|
|
73
|
+
role: 'region',
|
|
74
|
+
ariaAttributes: ['aria-label (copy button)'],
|
|
75
|
+
keyboardInteractions: [
|
|
76
|
+
'Tab — focuses the copy button',
|
|
77
|
+
'Enter / Space — copies the code',
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
};
|
|
@@ -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,98 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'slider',
|
|
3
|
+
name: 'Slider',
|
|
4
|
+
tier: 'atom',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'A range input styled with Lucent tokens for selecting a numeric value within a bounded range.',
|
|
8
|
+
designIntent: 'Use Slider for continuous or stepped numeric inputs where the relative position matters — ' +
|
|
9
|
+
'volume, brightness, border radius, spacing scale. Pair with showValue when the exact ' +
|
|
10
|
+
'number is meaningful to the user. For precise numeric entry, use Input with type="number" ' +
|
|
11
|
+
'instead. Disabled state uses muted colours without opacity hacks.',
|
|
12
|
+
props: [
|
|
13
|
+
{
|
|
14
|
+
name: 'min',
|
|
15
|
+
type: 'number',
|
|
16
|
+
required: false,
|
|
17
|
+
default: '0',
|
|
18
|
+
description: 'Minimum value.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'max',
|
|
22
|
+
type: 'number',
|
|
23
|
+
required: false,
|
|
24
|
+
default: '100',
|
|
25
|
+
description: 'Maximum value.',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'step',
|
|
29
|
+
type: 'number',
|
|
30
|
+
required: false,
|
|
31
|
+
default: '1',
|
|
32
|
+
description: 'Increment step between values.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'value',
|
|
36
|
+
type: 'number',
|
|
37
|
+
required: false,
|
|
38
|
+
description: 'Controlled current value. Pair with onChange.',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'defaultValue',
|
|
42
|
+
type: 'number',
|
|
43
|
+
required: false,
|
|
44
|
+
description: 'Initial value for uncontrolled usage. Defaults to the midpoint of min/max.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'onChange',
|
|
48
|
+
type: 'function',
|
|
49
|
+
required: false,
|
|
50
|
+
description: 'Called on every value change.',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'label',
|
|
54
|
+
type: 'string',
|
|
55
|
+
required: false,
|
|
56
|
+
description: 'Visible label rendered above the track.',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'showValue',
|
|
60
|
+
type: 'boolean',
|
|
61
|
+
required: false,
|
|
62
|
+
default: 'false',
|
|
63
|
+
description: 'Displays the current numeric value to the right of the label.',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'size',
|
|
67
|
+
type: 'enum',
|
|
68
|
+
required: false,
|
|
69
|
+
default: 'md',
|
|
70
|
+
description: 'Controls track thickness and thumb diameter.',
|
|
71
|
+
enumValues: ['sm', 'md', 'lg'],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'disabled',
|
|
75
|
+
type: 'boolean',
|
|
76
|
+
required: false,
|
|
77
|
+
default: 'false',
|
|
78
|
+
description: 'Prevents interaction and dims the control.',
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
usageExamples: [
|
|
82
|
+
{ title: 'Basic', code: `<Slider label="Volume" showValue />` },
|
|
83
|
+
{ title: 'Controlled', code: `<Slider label="Opacity" min={0} max={1} step={0.01} value={opacity} onChange={e => setOpacity(Number(e.target.value))} showValue />` },
|
|
84
|
+
{ title: 'Sizes', code: `<Slider size="sm" label="Small" />\n<Slider size="md" label="Medium" />\n<Slider size="lg" label="Large" />` },
|
|
85
|
+
{ title: 'Disabled', code: `<Slider label="Locked" disabled defaultValue={40} />` },
|
|
86
|
+
],
|
|
87
|
+
compositionGraph: [],
|
|
88
|
+
accessibility: {
|
|
89
|
+
role: 'slider',
|
|
90
|
+
ariaAttributes: ['aria-valuemin', 'aria-valuemax', 'aria-valuenow', 'aria-disabled'],
|
|
91
|
+
keyboardInteractions: [
|
|
92
|
+
'ArrowRight / ArrowUp — increase value by step',
|
|
93
|
+
'ArrowLeft / ArrowDown — decrease value by step',
|
|
94
|
+
'Home — jump to min',
|
|
95
|
+
'End — jump to max',
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'table',
|
|
3
|
+
name: 'Table',
|
|
4
|
+
tier: 'atom',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'A lightweight, token-styled HTML table primitive with compound sub-components. ' +
|
|
8
|
+
'Distinct from DataTable — no sorting, filtering, or pagination.',
|
|
9
|
+
designIntent: 'Use Table for static or lightly dynamic tabular data where full DataTable features ' +
|
|
10
|
+
'are not needed — props tables, changelog entries, comparison grids, reference docs. ' +
|
|
11
|
+
'The compound API (Table.Head, Table.Body, Table.Row, Table.Cell) maps directly to ' +
|
|
12
|
+
'semantic HTML so screen readers get the full table structure. ' +
|
|
13
|
+
'Horizontal overflow is handled automatically by a scroll wrapper.',
|
|
14
|
+
props: [
|
|
15
|
+
{
|
|
16
|
+
name: 'striped',
|
|
17
|
+
type: 'boolean',
|
|
18
|
+
required: false,
|
|
19
|
+
default: 'false',
|
|
20
|
+
description: 'Applies alternating bgMuted backgrounds to even tbody rows.',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'Table.Head',
|
|
24
|
+
type: 'component',
|
|
25
|
+
required: false,
|
|
26
|
+
description: 'Renders <thead> with bgMuted background. Accepts Table.Row children.',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'Table.Body',
|
|
30
|
+
type: 'component',
|
|
31
|
+
required: false,
|
|
32
|
+
description: 'Renders <tbody>. Accepts Table.Row children.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'Table.Foot',
|
|
36
|
+
type: 'component',
|
|
37
|
+
required: false,
|
|
38
|
+
description: 'Renders <tfoot> with bgMuted background.',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'Table.Row',
|
|
42
|
+
type: 'component',
|
|
43
|
+
required: false,
|
|
44
|
+
description: 'Renders <tr> with a hover highlight. Accepts Table.Cell children.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'Table.Cell',
|
|
48
|
+
type: 'component',
|
|
49
|
+
required: false,
|
|
50
|
+
description: 'Renders <td> by default or <th scope="col"> when as="th". ' +
|
|
51
|
+
'Header cells are semibold + secondary colour; data cells are regular + primary.',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
usageExamples: [
|
|
55
|
+
{
|
|
56
|
+
title: 'Basic',
|
|
57
|
+
code: `<Table>
|
|
58
|
+
<Table.Head>
|
|
59
|
+
<Table.Row>
|
|
60
|
+
<Table.Cell as="th">Name</Table.Cell>
|
|
61
|
+
<Table.Cell as="th">Role</Table.Cell>
|
|
62
|
+
</Table.Row>
|
|
63
|
+
</Table.Head>
|
|
64
|
+
<Table.Body>
|
|
65
|
+
<Table.Row>
|
|
66
|
+
<Table.Cell>Alice</Table.Cell>
|
|
67
|
+
<Table.Cell>Engineer</Table.Cell>
|
|
68
|
+
</Table.Row>
|
|
69
|
+
</Table.Body>
|
|
70
|
+
</Table>`,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
title: 'Striped',
|
|
74
|
+
code: `<Table striped>
|
|
75
|
+
<Table.Head>…</Table.Head>
|
|
76
|
+
<Table.Body>…</Table.Body>
|
|
77
|
+
</Table>`,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
title: 'Custom cell content',
|
|
81
|
+
code: `<Table.Cell>
|
|
82
|
+
<Badge variant="success">Active</Badge>
|
|
83
|
+
</Table.Cell>`,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
compositionGraph: [
|
|
87
|
+
{ componentId: 'table-head', componentName: 'Table.Head', role: 'head', required: false },
|
|
88
|
+
{ componentId: 'table-body', componentName: 'Table.Body', role: 'body', required: false },
|
|
89
|
+
{ componentId: 'table-foot', componentName: 'Table.Foot', role: 'foot', required: false },
|
|
90
|
+
{ componentId: 'table-row', componentName: 'Table.Row', role: 'row', required: false },
|
|
91
|
+
{ componentId: 'table-cell', componentName: 'Table.Cell', role: 'cell', required: false },
|
|
92
|
+
],
|
|
93
|
+
accessibility: {
|
|
94
|
+
role: 'table',
|
|
95
|
+
ariaAttributes: ['scope="col" on th cells'],
|
|
96
|
+
keyboardInteractions: ['Standard browser table navigation'],
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -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
|
+
};
|
|
@@ -54,6 +54,13 @@ export const ButtonManifest = {
|
|
|
54
54
|
default: 'false',
|
|
55
55
|
description: 'Stretches the button to fill its container width.',
|
|
56
56
|
},
|
|
57
|
+
{
|
|
58
|
+
name: 'bordered',
|
|
59
|
+
type: 'boolean',
|
|
60
|
+
required: false,
|
|
61
|
+
default: 'true',
|
|
62
|
+
description: 'When false removes the button border entirely, producing a flat look.',
|
|
63
|
+
},
|
|
57
64
|
{
|
|
58
65
|
name: 'leftIcon',
|
|
59
66
|
type: 'ReactNode',
|
|
@@ -106,6 +113,10 @@ export const ButtonManifest = {
|
|
|
106
113
|
title: 'Full-width submit',
|
|
107
114
|
code: `<Button variant="primary" type="submit" fullWidth>Sign in</Button>`,
|
|
108
115
|
},
|
|
116
|
+
{
|
|
117
|
+
title: 'Borderless primary',
|
|
118
|
+
code: `<Button variant="primary" bordered={false}>Flat primary</Button>`,
|
|
119
|
+
},
|
|
109
120
|
],
|
|
110
121
|
compositionGraph: [],
|
|
111
122
|
accessibility: {
|