lucent-ui 0.1.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/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/index.cjs +112 -0
- package/dist/index.d.ts +687 -0
- package/dist/index.js +3227 -0
- package/dist-server/server/index.js +111 -0
- package/dist-server/server/registry.js +48 -0
- package/dist-server/src/components/atoms/Avatar/Avatar.manifest.js +29 -0
- package/dist-server/src/components/atoms/Badge/Badge.manifest.js +29 -0
- package/dist-server/src/components/atoms/Button/Button.manifest.js +2 -0
- package/dist-server/src/components/atoms/Checkbox/Checkbox.manifest.js +74 -0
- package/dist-server/src/components/atoms/Divider/Divider.manifest.js +25 -0
- package/dist-server/src/components/atoms/Icon/Icon.manifest.js +54 -0
- package/dist-server/src/components/atoms/Input/Input.manifest.js +36 -0
- package/dist-server/src/components/atoms/Radio/Radio.manifest.js +82 -0
- package/dist-server/src/components/atoms/Select/Select.manifest.js +103 -0
- package/dist-server/src/components/atoms/Spinner/Spinner.manifest.js +27 -0
- package/dist-server/src/components/atoms/Tag/Tag.manifest.js +62 -0
- package/dist-server/src/components/atoms/Text/Text.manifest.js +106 -0
- package/dist-server/src/components/atoms/Textarea/Textarea.manifest.js +35 -0
- package/dist-server/src/components/atoms/Toggle/Toggle.manifest.js +67 -0
- package/dist-server/src/components/atoms/Tooltip/Tooltip.manifest.js +54 -0
- package/dist-server/src/components/molecules/Alert/Alert.manifest.js +81 -0
- package/dist-server/src/components/molecules/Card/Card.manifest.js +92 -0
- package/dist-server/src/components/molecules/EmptyState/EmptyState.manifest.js +79 -0
- package/dist-server/src/components/molecules/FormField/FormField.manifest.js +93 -0
- package/dist-server/src/components/molecules/SearchInput/SearchInput.manifest.js +102 -0
- package/dist-server/src/components/molecules/Skeleton/Skeleton.manifest.js +93 -0
- package/dist-server/src/manifest/examples/button.manifest.js +116 -0
- package/dist-server/src/manifest/index.js +3 -0
- package/dist-server/src/manifest/types.js +1 -0
- package/dist-server/src/manifest/validate.js +102 -0
- package/package.json +58 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'tag',
|
|
3
|
+
name: 'Tag',
|
|
4
|
+
tier: 'atom',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'A dismissible label for applied filters, selected values, or categorisation.',
|
|
8
|
+
designIntent: 'Tags represent user-applied selections that can be removed — filters, multi-select ' +
|
|
9
|
+
'values, categories. They differ from Badge in that they are interactive: provide ' +
|
|
10
|
+
'onDismiss when the user should be able to remove the tag. Without onDismiss they render ' +
|
|
11
|
+
'as a static pill (identical to Badge in purpose). Use semantic variants to match meaning; ' +
|
|
12
|
+
'default to neutral for user-generated content where no semantic applies.',
|
|
13
|
+
props: [
|
|
14
|
+
{
|
|
15
|
+
name: 'children',
|
|
16
|
+
type: 'ReactNode',
|
|
17
|
+
required: true,
|
|
18
|
+
description: 'Tag label content.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'variant',
|
|
22
|
+
type: 'enum',
|
|
23
|
+
required: false,
|
|
24
|
+
default: 'neutral',
|
|
25
|
+
description: 'Colour scheme conveying semantic meaning.',
|
|
26
|
+
enumValues: ['neutral', 'accent', 'success', 'warning', 'danger', 'info'],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'size',
|
|
30
|
+
type: 'enum',
|
|
31
|
+
required: false,
|
|
32
|
+
default: 'md',
|
|
33
|
+
description: 'Controls height and font size.',
|
|
34
|
+
enumValues: ['sm', 'md'],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'onDismiss',
|
|
38
|
+
type: 'function',
|
|
39
|
+
required: false,
|
|
40
|
+
description: 'When provided, renders an × button that calls this handler on click.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'disabled',
|
|
44
|
+
type: 'boolean',
|
|
45
|
+
required: false,
|
|
46
|
+
default: 'false',
|
|
47
|
+
description: 'Dims the tag and prevents the dismiss action.',
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
usageExamples: [
|
|
51
|
+
{ title: 'Dismissible filter', code: `<Tag onDismiss={() => removeFilter('react')}>React</Tag>` },
|
|
52
|
+
{ title: 'Multi-select value', code: `<Tag variant="accent" onDismiss={() => deselect(id)}>Jane Doe</Tag>` },
|
|
53
|
+
{ title: 'Static category', code: `<Tag variant="info">Beta</Tag>` },
|
|
54
|
+
{ title: 'Danger (removable)', code: `<Tag variant="danger" onDismiss={handleRemove}>Blocked</Tag>` },
|
|
55
|
+
],
|
|
56
|
+
compositionGraph: [],
|
|
57
|
+
accessibility: {
|
|
58
|
+
role: 'group',
|
|
59
|
+
notes: 'The dismiss button has aria-label="Dismiss" and is keyboard-focusable.',
|
|
60
|
+
keyboardInteractions: ['Enter / Space — activates the dismiss button when focused'],
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'text',
|
|
3
|
+
name: 'Text',
|
|
4
|
+
tier: 'atom',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'Polymorphic typography primitive that maps semantic HTML elements to design-system type scales.',
|
|
8
|
+
designIntent: 'Text is the single source of truth for typography in Lucent UI. Rather than hard-coding font sizes ' +
|
|
9
|
+
'and colors directly in components, every piece of rendered text should pass through this atom. ' +
|
|
10
|
+
'The `as` prop controls the HTML element (and therefore semantic meaning), while `size`, `weight`, ' +
|
|
11
|
+
'`color`, and `lineHeight` control appearance independently — decoupling semantics from style. ' +
|
|
12
|
+
'Use `truncate` only in constrained-width containers where overflow must be prevented; it applies ' +
|
|
13
|
+
'overflow: hidden + text-overflow: ellipsis. The `family` prop enables inline code or monospaced ' +
|
|
14
|
+
'content without switching components.',
|
|
15
|
+
props: [
|
|
16
|
+
{
|
|
17
|
+
name: 'as',
|
|
18
|
+
type: 'enum',
|
|
19
|
+
required: false,
|
|
20
|
+
default: 'p',
|
|
21
|
+
description: 'HTML element to render. Controls semantic meaning independently of visual style.',
|
|
22
|
+
enumValues: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span', 'div', 'label', 'strong', 'em', 'code'],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'size',
|
|
26
|
+
type: 'enum',
|
|
27
|
+
required: false,
|
|
28
|
+
default: 'md',
|
|
29
|
+
description: 'Font size from the type scale. Maps to the corresponding --lucent-font-size-* token.',
|
|
30
|
+
enumValues: ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'weight',
|
|
34
|
+
type: 'enum',
|
|
35
|
+
required: false,
|
|
36
|
+
default: 'regular',
|
|
37
|
+
description: 'Font weight. Maps to --lucent-font-weight-* tokens.',
|
|
38
|
+
enumValues: ['regular', 'medium', 'semibold', 'bold'],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'color',
|
|
42
|
+
type: 'enum',
|
|
43
|
+
required: false,
|
|
44
|
+
default: 'primary',
|
|
45
|
+
description: 'Semantic text color. All values adapt automatically in light and dark themes.',
|
|
46
|
+
enumValues: ['primary', 'secondary', 'disabled', 'inverse', 'onAccent', 'success', 'warning', 'danger', 'info'],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'align',
|
|
50
|
+
type: 'enum',
|
|
51
|
+
required: false,
|
|
52
|
+
default: 'left',
|
|
53
|
+
description: 'Text alignment.',
|
|
54
|
+
enumValues: ['left', 'center', 'right'],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'lineHeight',
|
|
58
|
+
type: 'enum',
|
|
59
|
+
required: false,
|
|
60
|
+
default: 'base',
|
|
61
|
+
description: 'Line height. tight=1.25, base=1.5, relaxed=1.75.',
|
|
62
|
+
enumValues: ['tight', 'base', 'relaxed'],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'family',
|
|
66
|
+
type: 'enum',
|
|
67
|
+
required: false,
|
|
68
|
+
default: 'base',
|
|
69
|
+
description: 'Font family. Use mono for code or tabular data. Use display (Unbounded) for headings and marketing copy.',
|
|
70
|
+
enumValues: ['base', 'mono', 'display'],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'truncate',
|
|
74
|
+
type: 'boolean',
|
|
75
|
+
required: false,
|
|
76
|
+
default: 'false',
|
|
77
|
+
description: 'Clips overflow text with an ellipsis. Requires the container to have a constrained width.',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'children',
|
|
81
|
+
type: 'ReactNode',
|
|
82
|
+
required: true,
|
|
83
|
+
description: 'The text content to render.',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'style',
|
|
87
|
+
type: 'object',
|
|
88
|
+
required: false,
|
|
89
|
+
description: 'Inline style overrides. Applied after computed token styles.',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
usageExamples: [
|
|
93
|
+
{ title: 'Heading', code: `<Text as="h1" size="3xl" weight="bold">Page title</Text>` },
|
|
94
|
+
{ title: 'Body paragraph', code: `<Text size="md" color="secondary">Supporting copy goes here.</Text>` },
|
|
95
|
+
{ title: 'Label', code: `<Text as="label" size="sm" weight="medium" htmlFor="email">Email</Text>` },
|
|
96
|
+
{ title: 'Inline code', code: `<Text as="code" family="mono" size="sm">const x = 1;</Text>` },
|
|
97
|
+
{ title: 'Truncated', code: `<Text truncate style={{ maxWidth: 200 }}>Very long text that will be clipped</Text>` },
|
|
98
|
+
{ title: 'Status color', code: `<Text color="danger" size="xs">This field is required</Text>` },
|
|
99
|
+
],
|
|
100
|
+
compositionGraph: [],
|
|
101
|
+
accessibility: {
|
|
102
|
+
notes: 'The rendered element determines the implicit ARIA role. Use heading elements (h1–h6) for document ' +
|
|
103
|
+
'headings so screen readers can navigate the page structure. Use `as="label"` with `htmlFor` to ' +
|
|
104
|
+
'associate labels with form controls. Decorative text needs no additional ARIA.',
|
|
105
|
+
},
|
|
106
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'textarea',
|
|
3
|
+
name: 'Textarea',
|
|
4
|
+
tier: 'atom',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'A multi-line text input with optional auto-resize and character count.',
|
|
8
|
+
designIntent: 'Use autoResize for open-ended fields (bio, description) where content length is unpredictable. ' +
|
|
9
|
+
'Use maxLength + showCount for fields with hard limits (tweet-style). ' +
|
|
10
|
+
'Behaves identically to Input for label/helper/error patterns.',
|
|
11
|
+
props: [
|
|
12
|
+
{ name: 'label', type: 'string', required: false, description: 'Visible label above the textarea.' },
|
|
13
|
+
{ name: 'helperText', type: 'string', required: false, description: 'Hint text shown below.' },
|
|
14
|
+
{ name: 'errorText', type: 'string', required: false, description: 'Validation error. Triggers error styling.' },
|
|
15
|
+
{ name: 'autoResize', type: 'boolean', required: false, default: 'false', description: 'Grows with content, disables manual resize handle.' },
|
|
16
|
+
{ name: 'maxLength', type: 'number', required: false, description: 'Character limit. Displays counter when set.' },
|
|
17
|
+
{ name: 'showCount', type: 'boolean', required: false, default: 'false', description: 'Always show character counter even without maxLength.' },
|
|
18
|
+
{ name: 'value', type: 'string', required: false, description: 'Controlled value.' },
|
|
19
|
+
{ name: 'onChange', type: 'function', required: false, description: 'Change handler.' },
|
|
20
|
+
{ name: 'placeholder', type: 'string', required: false, description: 'Placeholder text.' },
|
|
21
|
+
{ name: 'disabled', type: 'boolean', required: false, default: 'false', description: 'Disables the textarea.' },
|
|
22
|
+
],
|
|
23
|
+
usageExamples: [
|
|
24
|
+
{ title: 'Basic', code: `<Textarea label="Bio" placeholder="Tell us about yourself…" />` },
|
|
25
|
+
{ title: 'Auto-resize', code: `<Textarea label="Description" autoResize value={value} onChange={e => setValue(e.target.value)} />` },
|
|
26
|
+
{ title: 'With character count', code: `<Textarea label="Tweet" maxLength={280} showCount value={value} onChange={e => setValue(e.target.value)} />` },
|
|
27
|
+
{ title: 'Error state', code: `<Textarea label="Notes" errorText="Required" value="" />` },
|
|
28
|
+
],
|
|
29
|
+
compositionGraph: [],
|
|
30
|
+
accessibility: {
|
|
31
|
+
role: 'textbox',
|
|
32
|
+
ariaAttributes: ['aria-multiline', 'aria-invalid', 'aria-describedby'],
|
|
33
|
+
keyboardInteractions: ['Tab — focuses the textarea'],
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'toggle',
|
|
3
|
+
name: 'Toggle',
|
|
4
|
+
tier: 'atom',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'A sliding switch for immediately-applied binary settings.',
|
|
8
|
+
designIntent: 'Toggles are for settings that take effect the moment they are flipped — no Save button ' +
|
|
9
|
+
'needed. If the action requires a confirmation step or only applies on form submit, use a ' +
|
|
10
|
+
'Checkbox instead. The "on" state is visually represented by the accent colour so the ' +
|
|
11
|
+
'meaning is clear without relying on text alone. Keep the label short (2–4 words) and ' +
|
|
12
|
+
'phrase it as the enabled state (e.g. "Dark mode", not "Enable dark mode").',
|
|
13
|
+
props: [
|
|
14
|
+
{
|
|
15
|
+
name: 'checked',
|
|
16
|
+
type: 'boolean',
|
|
17
|
+
required: false,
|
|
18
|
+
description: 'Controlled on/off state. Pair with onChange for controlled usage.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'defaultChecked',
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
required: false,
|
|
24
|
+
default: 'false',
|
|
25
|
+
description: 'Initial state for uncontrolled usage.',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'onChange',
|
|
29
|
+
type: 'function',
|
|
30
|
+
required: false,
|
|
31
|
+
description: 'Called when the toggle is flipped.',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'label',
|
|
35
|
+
type: 'string',
|
|
36
|
+
required: false,
|
|
37
|
+
description: 'Visible label rendered beside the toggle.',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'size',
|
|
41
|
+
type: 'enum',
|
|
42
|
+
required: false,
|
|
43
|
+
default: 'md',
|
|
44
|
+
description: 'Controls the track and thumb size.',
|
|
45
|
+
enumValues: ['sm', 'md', 'lg'],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'disabled',
|
|
49
|
+
type: 'boolean',
|
|
50
|
+
required: false,
|
|
51
|
+
default: 'false',
|
|
52
|
+
description: 'Prevents interaction and dims the control.',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
usageExamples: [
|
|
56
|
+
{ title: 'Controlled', code: `<Toggle checked={darkMode} onChange={e => setDarkMode(e.target.checked)} label="Dark mode" />` },
|
|
57
|
+
{ title: 'Uncontrolled', code: `<Toggle defaultChecked label="Email notifications" />` },
|
|
58
|
+
{ title: 'Sizes', code: `<Toggle size="sm" label="Compact" />\n<Toggle size="md" label="Default" />\n<Toggle size="lg" label="Large" />` },
|
|
59
|
+
{ title: 'Disabled', code: `<Toggle disabled label="Unavailable setting" />` },
|
|
60
|
+
],
|
|
61
|
+
compositionGraph: [],
|
|
62
|
+
accessibility: {
|
|
63
|
+
role: 'switch',
|
|
64
|
+
ariaAttributes: ['aria-checked', 'aria-disabled'],
|
|
65
|
+
keyboardInteractions: ['Space — toggles the switch state'],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'tooltip',
|
|
3
|
+
name: 'Tooltip',
|
|
4
|
+
tier: 'atom',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'A transient label that appears on hover or focus to explain an element.',
|
|
8
|
+
designIntent: 'Tooltips surface supplementary context that would clutter the UI if shown permanently — ' +
|
|
9
|
+
'keyboard shortcut hints, icon button labels, truncated text expansions. They must never ' +
|
|
10
|
+
'contain critical information (errors, required actions) because they are invisible until ' +
|
|
11
|
+
'hovered. Keep content to one short phrase; avoid wrapping. Use placement to avoid viewport ' +
|
|
12
|
+
'edges — "top" is the default and works in most cases.',
|
|
13
|
+
props: [
|
|
14
|
+
{
|
|
15
|
+
name: 'content',
|
|
16
|
+
type: 'ReactNode',
|
|
17
|
+
required: true,
|
|
18
|
+
description: 'Text or element shown inside the tooltip. Keep it short (under 80 chars).',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'children',
|
|
22
|
+
type: 'ReactNode',
|
|
23
|
+
required: true,
|
|
24
|
+
description: 'The element that triggers the tooltip on hover/focus.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'placement',
|
|
28
|
+
type: 'enum',
|
|
29
|
+
required: false,
|
|
30
|
+
default: 'top',
|
|
31
|
+
description: 'Position of the tooltip relative to the trigger.',
|
|
32
|
+
enumValues: ['top', 'bottom', 'left', 'right'],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'delay',
|
|
36
|
+
type: 'number',
|
|
37
|
+
required: false,
|
|
38
|
+
default: '300',
|
|
39
|
+
description: 'Milliseconds before the tooltip appears. Prevents flicker on fast cursor movement.',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
usageExamples: [
|
|
43
|
+
{ title: 'Icon button label', code: `<Tooltip content="Copy to clipboard"><IconButton icon={<CopyIcon />} /></Tooltip>` },
|
|
44
|
+
{ title: 'Keyboard shortcut hint', code: `<Tooltip content="⌘K"><Button variant="ghost">Search</Button></Tooltip>` },
|
|
45
|
+
{ title: 'Bottom placement', code: `<Tooltip content="Opens in a new tab" placement="bottom"><a href="…">Link</a></Tooltip>` },
|
|
46
|
+
{ title: 'No delay (instant)', code: `<Tooltip content="Delete" delay={0}><Button variant="ghost">🗑</Button></Tooltip>` },
|
|
47
|
+
],
|
|
48
|
+
compositionGraph: [],
|
|
49
|
+
accessibility: {
|
|
50
|
+
role: 'tooltip',
|
|
51
|
+
ariaAttributes: ['role="tooltip"'],
|
|
52
|
+
notes: 'The tooltip is shown on both hover and focus, making it accessible to keyboard users. Content is exposed via role="tooltip".',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'alert',
|
|
3
|
+
name: 'Alert',
|
|
4
|
+
tier: 'molecule',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'An inline feedback banner with info, success, warning, and danger variants, optional title, and dismiss button.',
|
|
8
|
+
designIntent: 'Alert uses role="alert" so screen readers announce the message immediately when it appears. ' +
|
|
9
|
+
'Each variant has a built-in icon that communicates intent visually; the icon can be overridden ' +
|
|
10
|
+
'for custom scenarios. Title and body are both optional — you can show either, both, or just an ' +
|
|
11
|
+
'icon with a body. The dismiss button is only rendered when onDismiss is provided, keeping the ' +
|
|
12
|
+
'layout clean for non-dismissible alerts. All colors use status semantic tokens so they adapt ' +
|
|
13
|
+
'correctly between light and dark themes.',
|
|
14
|
+
props: [
|
|
15
|
+
{
|
|
16
|
+
name: 'variant',
|
|
17
|
+
type: 'enum',
|
|
18
|
+
required: false,
|
|
19
|
+
default: 'info',
|
|
20
|
+
description: 'Visual and semantic variant of the alert.',
|
|
21
|
+
enumValues: ['info', 'success', 'warning', 'danger'],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'title',
|
|
25
|
+
type: 'string',
|
|
26
|
+
required: false,
|
|
27
|
+
description: 'Bold title line rendered above the body.',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'children',
|
|
31
|
+
type: 'ReactNode',
|
|
32
|
+
required: false,
|
|
33
|
+
description: 'Alert body content — typically a short sentence or ReactNode.',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'onDismiss',
|
|
37
|
+
type: 'function',
|
|
38
|
+
required: false,
|
|
39
|
+
description: 'When provided, renders a dismiss (×) button and calls this handler on click.',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'icon',
|
|
43
|
+
type: 'ReactNode',
|
|
44
|
+
required: false,
|
|
45
|
+
description: 'Custom icon to replace the built-in variant icon.',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'style',
|
|
49
|
+
type: 'object',
|
|
50
|
+
required: false,
|
|
51
|
+
description: 'Inline style overrides for the alert wrapper.',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
usageExamples: [
|
|
55
|
+
{
|
|
56
|
+
title: 'Info with body',
|
|
57
|
+
code: `<Alert variant="info">Your changes have been saved as a draft.</Alert>`,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
title: 'With title and dismiss',
|
|
61
|
+
code: `<Alert variant="danger" title="Payment failed" onDismiss={() => setVisible(false)}>
|
|
62
|
+
Check your card details and try again.
|
|
63
|
+
</Alert>`,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
title: 'Success confirmation',
|
|
67
|
+
code: `<Alert variant="success" title="Order placed!">
|
|
68
|
+
You'll receive a confirmation email shortly.
|
|
69
|
+
</Alert>`,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
compositionGraph: [
|
|
73
|
+
{ componentId: 'text', componentName: 'Text', role: 'Title and body content', required: false },
|
|
74
|
+
],
|
|
75
|
+
accessibility: {
|
|
76
|
+
role: 'alert',
|
|
77
|
+
ariaAttributes: ['aria-label'],
|
|
78
|
+
notes: 'role="alert" causes screen readers to announce the content immediately when rendered. ' +
|
|
79
|
+
'For non-urgent status messages, consider using role="status" instead by overriding via style/wrapper.',
|
|
80
|
+
},
|
|
81
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'card',
|
|
3
|
+
name: 'Card',
|
|
4
|
+
tier: 'molecule',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'A surface container with optional header, body, and footer slots, configurable padding, shadow, and radius.',
|
|
8
|
+
designIntent: 'Card provides a consistent elevated surface for grouping related content. The header and footer slots ' +
|
|
9
|
+
'are separated from the body by a border-default divider, giving visual structure without requiring ' +
|
|
10
|
+
'the consumer to manage spacing. Padding, shadow, and radius are all configurable to accommodate ' +
|
|
11
|
+
'flat/ghost cards, modal-like surfaces, and compact data-dense layouts. The overflow: hidden ensures ' +
|
|
12
|
+
'children respect the border-radius without needing additional clipping.',
|
|
13
|
+
props: [
|
|
14
|
+
{
|
|
15
|
+
name: 'children',
|
|
16
|
+
type: 'ReactNode',
|
|
17
|
+
required: true,
|
|
18
|
+
description: 'The card body content.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'header',
|
|
22
|
+
type: 'ReactNode',
|
|
23
|
+
required: false,
|
|
24
|
+
description: 'Content rendered in the header slot, separated from the body by a divider.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'footer',
|
|
28
|
+
type: 'ReactNode',
|
|
29
|
+
required: false,
|
|
30
|
+
description: 'Content rendered in the footer slot, separated from the body by a divider.',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'padding',
|
|
34
|
+
type: 'enum',
|
|
35
|
+
required: false,
|
|
36
|
+
default: 'md',
|
|
37
|
+
description: 'Inner padding applied equally to header, body, and footer.',
|
|
38
|
+
enumValues: ['none', 'sm', 'md', 'lg'],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'shadow',
|
|
42
|
+
type: 'enum',
|
|
43
|
+
required: false,
|
|
44
|
+
default: 'sm',
|
|
45
|
+
description: 'Box shadow elevation.',
|
|
46
|
+
enumValues: ['none', 'sm', 'md', 'lg'],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'radius',
|
|
50
|
+
type: 'enum',
|
|
51
|
+
required: false,
|
|
52
|
+
default: 'md',
|
|
53
|
+
description: 'Border radius of the card.',
|
|
54
|
+
enumValues: ['none', 'sm', 'md', 'lg'],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'style',
|
|
58
|
+
type: 'object',
|
|
59
|
+
required: false,
|
|
60
|
+
description: 'Inline style overrides for the card wrapper.',
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
usageExamples: [
|
|
64
|
+
{
|
|
65
|
+
title: 'Simple card',
|
|
66
|
+
code: `<Card>
|
|
67
|
+
<Text>Some content here.</Text>
|
|
68
|
+
</Card>`,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
title: 'With header and footer',
|
|
72
|
+
code: `<Card
|
|
73
|
+
header={<Text weight="semibold">Card title</Text>}
|
|
74
|
+
footer={<Button variant="primary">Save</Button>}
|
|
75
|
+
>
|
|
76
|
+
<Text color="secondary">Card body content goes here.</Text>
|
|
77
|
+
</Card>`,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
title: 'Flat variant',
|
|
81
|
+
code: `<Card shadow="none" radius="sm" padding="sm">
|
|
82
|
+
<Text size="sm">Compact flat card</Text>
|
|
83
|
+
</Card>`,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
compositionGraph: [],
|
|
87
|
+
accessibility: {
|
|
88
|
+
notes: 'Card has no implicit ARIA role. If the card represents a landmark, wrap it in a <section> or <article> ' +
|
|
89
|
+
'and provide an aria-label. For interactive cards (clickable), make the wrapper a <button> or <a> and ' +
|
|
90
|
+
'ensure focus styles are visible.',
|
|
91
|
+
},
|
|
92
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'empty-state',
|
|
3
|
+
name: 'EmptyState',
|
|
4
|
+
tier: 'molecule',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'A centered placeholder shown when a list or page has no content, with an optional illustration, title, description, and CTA.',
|
|
8
|
+
designIntent: 'EmptyState communicates the absence of data in a constructive way. The illustration slot accepts any ' +
|
|
9
|
+
'ReactNode — an Icon atom, a custom SVG, or an image — and constrains it to a 64px square to maintain ' +
|
|
10
|
+
'visual consistency. Title is required to ensure the state is always named; description is optional for ' +
|
|
11
|
+
'additional context. The action slot accepts any ReactNode (typically a Button) so the consumer controls ' +
|
|
12
|
+
'variant and label without prescribing them. The entire layout is center-aligned and padded to sit ' +
|
|
13
|
+
'naturally inside a Card or page section.',
|
|
14
|
+
props: [
|
|
15
|
+
{
|
|
16
|
+
name: 'title',
|
|
17
|
+
type: 'string',
|
|
18
|
+
required: true,
|
|
19
|
+
description: 'Short headline naming the empty state (e.g. "No results found").',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'illustration',
|
|
23
|
+
type: 'ReactNode',
|
|
24
|
+
required: false,
|
|
25
|
+
description: 'Icon, SVG, or image rendered above the title. Constrained to a 64px container.',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'description',
|
|
29
|
+
type: 'string',
|
|
30
|
+
required: false,
|
|
31
|
+
description: 'Secondary text below the title providing context or next steps.',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'action',
|
|
35
|
+
type: 'ReactNode',
|
|
36
|
+
required: false,
|
|
37
|
+
description: 'Call-to-action rendered below the description — typically a Button.',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'style',
|
|
41
|
+
type: 'object',
|
|
42
|
+
required: false,
|
|
43
|
+
description: 'Inline style overrides for the outer wrapper.',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
usageExamples: [
|
|
47
|
+
{
|
|
48
|
+
title: 'No search results',
|
|
49
|
+
code: `<EmptyState
|
|
50
|
+
illustration={<Icon size="xl"><SearchIcon /></Icon>}
|
|
51
|
+
title="No results found"
|
|
52
|
+
description="Try adjusting your search or filter to find what you're looking for."
|
|
53
|
+
action={<Button variant="secondary" onClick={clearSearch}>Clear search</Button>}
|
|
54
|
+
/>`,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
title: 'Empty list with CTA',
|
|
58
|
+
code: `<EmptyState
|
|
59
|
+
title="No projects yet"
|
|
60
|
+
description="Create your first project to get started."
|
|
61
|
+
action={<Button variant="primary">New project</Button>}
|
|
62
|
+
/>`,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
title: 'Inside a Card',
|
|
66
|
+
code: `<Card>
|
|
67
|
+
<EmptyState title="Nothing here" description="Add items to see them listed." />
|
|
68
|
+
</Card>`,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
compositionGraph: [
|
|
72
|
+
{ componentId: 'text', componentName: 'Text', role: 'Title and description', required: true },
|
|
73
|
+
],
|
|
74
|
+
accessibility: {
|
|
75
|
+
notes: 'The title renders as an h3 by default. If EmptyState appears inside a section with its own heading ' +
|
|
76
|
+
'hierarchy, override by passing a Text component as part of a custom layout. Ensure the action element ' +
|
|
77
|
+
'has a descriptive label for screen readers.',
|
|
78
|
+
},
|
|
79
|
+
};
|