@sovereignfs/ui 0.21.3 → 0.23.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/Checkbox.module.css +97 -0
- package/dist/DragHandleRow.module.css +51 -0
- package/dist/FormField.module.css +5 -0
- package/dist/Input.module.css +10 -0
- package/dist/Select.module.css +9 -0
- package/dist/Textarea.module.css +39 -0
- package/dist/Toast.module.css +3 -3
- package/dist/Toggle.module.css +1 -1
- package/dist/index.d.ts +58 -5
- package/dist/index.js +218 -96
- package/dist/tokens/semantic.css +6 -0
- package/package.json +1 -1
- package/src/components/Checkbox/Checkbox.module.css +97 -0
- package/src/components/Checkbox/Checkbox.tsx +68 -0
- package/src/components/DragHandleRow/DragHandleRow.module.css +51 -0
- package/src/components/DragHandleRow/DragHandleRow.tsx +61 -0
- package/src/components/FormField/FormField.module.css +5 -0
- package/src/components/FormField/FormField.tsx +36 -11
- package/src/components/FormField/__tests__/FormField.test.tsx +83 -0
- package/src/components/Input/Input.module.css +10 -0
- package/src/components/Select/Select.module.css +9 -0
- package/src/components/Select/Select.tsx +10 -2
- package/src/components/Textarea/Textarea.module.css +39 -0
- package/src/components/Textarea/Textarea.tsx +15 -0
- package/src/components/Textarea/__tests__/Textarea.test.tsx +29 -0
- package/src/components/Toast/Toast.module.css +3 -3
- package/src/components/Toggle/Toggle.module.css +1 -1
- package/src/index.ts +7 -1
- package/src/stories/Checkbox.stories.tsx +45 -0
- package/src/stories/DesignSystemOverview.stories.tsx +64 -6
- package/src/stories/DragHandleRow.stories.tsx +41 -0
- package/src/stories/FormField.stories.tsx +8 -8
- package/src/stories/MobilePatterns.stories.tsx +4 -4
- package/src/stories/Textarea.stories.tsx +51 -0
- package/src/stories/TokenGallery.stories.tsx +2 -0
- package/src/tokens/semantic.css +6 -0
|
@@ -11,6 +11,7 @@ import { EmptyState } from '../components/EmptyState/EmptyState';
|
|
|
11
11
|
import { FormField } from '../components/FormField/FormField';
|
|
12
12
|
import { Icon } from '../components/Icon/Icon';
|
|
13
13
|
import { Input } from '../components/Input/Input';
|
|
14
|
+
import { Textarea } from '../components/Textarea/Textarea';
|
|
14
15
|
import { NavTabs } from '../components/NavTabs/NavTabs';
|
|
15
16
|
import { PageHeader } from '../components/PageHeader/PageHeader';
|
|
16
17
|
import { Popover } from '../components/Popover/Popover';
|
|
@@ -22,6 +23,8 @@ import { Tabs } from '../components/Tabs/Tabs';
|
|
|
22
23
|
import { ToastProvider, useToast } from '../components/Toast/Toast';
|
|
23
24
|
import { Toggle } from '../components/Toggle/Toggle';
|
|
24
25
|
import { Tooltip } from '../components/Tooltip/Tooltip';
|
|
26
|
+
import { Checkbox } from '../components/Checkbox/Checkbox';
|
|
27
|
+
import { DragHandleRow } from '../components/DragHandleRow/DragHandleRow';
|
|
25
28
|
|
|
26
29
|
// ---------------------------------------------------------------------------
|
|
27
30
|
// Shared primitives
|
|
@@ -1181,7 +1184,7 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
|
|
|
1181
1184
|
<section style={{ marginBottom: 'var(--sv-space-12)' }}>
|
|
1182
1185
|
<SectionHeader
|
|
1183
1186
|
title="Component gallery"
|
|
1184
|
-
subtitle="All
|
|
1187
|
+
subtitle="All 15 components — click each story in the sidebar for the full API, variants, and controls."
|
|
1185
1188
|
/>
|
|
1186
1189
|
|
|
1187
1190
|
<div style={{ marginBottom: 'var(--sv-space-6)' }}>
|
|
@@ -1384,18 +1387,31 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
|
|
|
1384
1387
|
<ComponentCard
|
|
1385
1388
|
name="FormField"
|
|
1386
1389
|
importLine="import { FormField } from '@sovereignfs/ui';"
|
|
1387
|
-
usage="Accessible label + input wrapper.
|
|
1390
|
+
usage="Accessible label + input wrapper. The render-prop `children` receives field props (id, aria-describedby, aria-invalid) to spread onto the control, so hint/error text stays wired to it."
|
|
1388
1391
|
>
|
|
1389
1392
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, width: '100%' }}>
|
|
1390
|
-
<FormField label="Email" hint="We'll never share this."
|
|
1391
|
-
<Input
|
|
1393
|
+
<FormField label="Email" hint="We'll never share this." id="ov-email">
|
|
1394
|
+
{(field) => <Input {...field} type="email" placeholder="you@example.com" />}
|
|
1392
1395
|
</FormField>
|
|
1393
|
-
<FormField label="Password" error="Must be 8+ characters."
|
|
1394
|
-
<Input
|
|
1396
|
+
<FormField label="Password" error="Must be 8+ characters." id="ov-pw">
|
|
1397
|
+
{(field) => <Input {...field} type="password" />}
|
|
1395
1398
|
</FormField>
|
|
1396
1399
|
</div>
|
|
1397
1400
|
</ComponentCard>
|
|
1398
1401
|
|
|
1402
|
+
{/* Textarea */}
|
|
1403
|
+
<ComponentCard
|
|
1404
|
+
name="Textarea"
|
|
1405
|
+
importLine="import { Textarea } from '@sovereignfs/ui';"
|
|
1406
|
+
usage="The primitive multi-line text field. Forwards all native textarea props. Pair with FormField or a <label> for accessibility."
|
|
1407
|
+
>
|
|
1408
|
+
<Textarea
|
|
1409
|
+
aria-label="Description"
|
|
1410
|
+
placeholder="Add a description…"
|
|
1411
|
+
style={{ width: '100%' }}
|
|
1412
|
+
/>
|
|
1413
|
+
</ComponentCard>
|
|
1414
|
+
|
|
1399
1415
|
{/* PageHeader */}
|
|
1400
1416
|
<ComponentCard
|
|
1401
1417
|
name="PageHeader"
|
|
@@ -1493,6 +1509,48 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
|
|
|
1493
1509
|
</Tooltip>
|
|
1494
1510
|
</div>
|
|
1495
1511
|
</ComponentCard>
|
|
1512
|
+
|
|
1513
|
+
{/* Checkbox */}
|
|
1514
|
+
<ComponentCard
|
|
1515
|
+
name="Checkbox"
|
|
1516
|
+
importLine="import { Checkbox } from '@sovereignfs/ui';"
|
|
1517
|
+
usage="Accessible checkbox with optional animated strike-through on the label when checked. Used in task lists."
|
|
1518
|
+
>
|
|
1519
|
+
{(() => {
|
|
1520
|
+
const [a, setA] = useState(false);
|
|
1521
|
+
const [b, setB] = useState(true);
|
|
1522
|
+
return (
|
|
1523
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
1524
|
+
<Checkbox checked={a} onChange={setA} label="Unchecked task" strikeThrough />
|
|
1525
|
+
<Checkbox checked={b} onChange={setB} label="Completed task" strikeThrough />
|
|
1526
|
+
</div>
|
|
1527
|
+
);
|
|
1528
|
+
})()}
|
|
1529
|
+
</ComponentCard>
|
|
1530
|
+
|
|
1531
|
+
{/* DragHandleRow */}
|
|
1532
|
+
<ComponentCard
|
|
1533
|
+
name="DragHandleRow"
|
|
1534
|
+
importLine="import { DragHandleRow } from '@sovereignfs/ui';"
|
|
1535
|
+
usage="Row wrapper with a drag handle that appears on hover. Attach dnd-kit sortable props via handleProps."
|
|
1536
|
+
>
|
|
1537
|
+
<div style={{ width: '100%' }}>
|
|
1538
|
+
{['First item', 'Second item', 'Third item'].map((label) => (
|
|
1539
|
+
<DragHandleRow key={label}>
|
|
1540
|
+
<div
|
|
1541
|
+
style={{
|
|
1542
|
+
padding: 'var(--sv-space-2) var(--sv-space-1)',
|
|
1543
|
+
fontSize: 'var(--sv-font-size-sm)',
|
|
1544
|
+
color: 'var(--sv-color-text-primary)',
|
|
1545
|
+
borderBottom: '1px solid var(--sv-color-border)',
|
|
1546
|
+
}}
|
|
1547
|
+
>
|
|
1548
|
+
{label}
|
|
1549
|
+
</div>
|
|
1550
|
+
</DragHandleRow>
|
|
1551
|
+
))}
|
|
1552
|
+
</div>
|
|
1553
|
+
</ComponentCard>
|
|
1496
1554
|
</div>
|
|
1497
1555
|
</section>
|
|
1498
1556
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { DragHandleRow } from '../components/DragHandleRow/DragHandleRow';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof DragHandleRow> = {
|
|
5
|
+
title: 'Components/DragHandleRow',
|
|
6
|
+
component: DragHandleRow,
|
|
7
|
+
parameters: { layout: 'padded' },
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof DragHandleRow>;
|
|
13
|
+
|
|
14
|
+
const rowStyle = {
|
|
15
|
+
padding: 'var(--sv-space-3) var(--sv-space-2)',
|
|
16
|
+
borderBottom: '1px solid var(--sv-color-border)',
|
|
17
|
+
fontSize: 'var(--sv-font-size-sm)',
|
|
18
|
+
color: 'var(--sv-color-text-primary)',
|
|
19
|
+
width: '320px',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {
|
|
23
|
+
render: () => (
|
|
24
|
+
<div>
|
|
25
|
+
{['Buy groceries', 'Call the dentist', 'Review pull request'].map((label) => (
|
|
26
|
+
<DragHandleRow key={label}>
|
|
27
|
+
<div style={rowStyle}>{label}</div>
|
|
28
|
+
</DragHandleRow>
|
|
29
|
+
))}
|
|
30
|
+
</div>
|
|
31
|
+
),
|
|
32
|
+
name: 'List of rows (hover to reveal handle)',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const Dragging: Story = {
|
|
36
|
+
render: () => (
|
|
37
|
+
<DragHandleRow isDragging>
|
|
38
|
+
<div style={rowStyle}>This row is being dragged</div>
|
|
39
|
+
</DragHandleRow>
|
|
40
|
+
),
|
|
41
|
+
};
|
|
@@ -14,8 +14,8 @@ type Story = StoryObj<typeof meta>;
|
|
|
14
14
|
export const Default: Story = {
|
|
15
15
|
args: {
|
|
16
16
|
label: 'Email address',
|
|
17
|
-
|
|
18
|
-
children: <Input
|
|
17
|
+
id: 'email',
|
|
18
|
+
children: (field) => <Input {...field} type="email" placeholder="you@example.com" />,
|
|
19
19
|
},
|
|
20
20
|
};
|
|
21
21
|
|
|
@@ -23,8 +23,8 @@ export const WithHint: Story = {
|
|
|
23
23
|
args: {
|
|
24
24
|
label: 'Username',
|
|
25
25
|
hint: 'Letters, numbers, and underscores only.',
|
|
26
|
-
|
|
27
|
-
children: <Input
|
|
26
|
+
id: 'username',
|
|
27
|
+
children: (field) => <Input {...field} placeholder="your_username" />,
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
30
|
|
|
@@ -32,8 +32,8 @@ export const WithError: Story = {
|
|
|
32
32
|
args: {
|
|
33
33
|
label: 'Password',
|
|
34
34
|
error: 'Password must be at least 8 characters.',
|
|
35
|
-
|
|
36
|
-
children: <Input
|
|
35
|
+
id: 'password',
|
|
36
|
+
children: (field) => <Input {...field} type="password" />,
|
|
37
37
|
},
|
|
38
38
|
};
|
|
39
39
|
|
|
@@ -41,7 +41,7 @@ export const Required: Story = {
|
|
|
41
41
|
args: {
|
|
42
42
|
label: 'Full name',
|
|
43
43
|
required: true,
|
|
44
|
-
|
|
45
|
-
children: <Input
|
|
44
|
+
id: 'name',
|
|
45
|
+
children: (field) => <Input {...field} placeholder="Jane Smith" />,
|
|
46
46
|
},
|
|
47
47
|
};
|
|
@@ -405,11 +405,11 @@ function MobilePatternsDoc() {
|
|
|
405
405
|
gap: 'var(--sv-space-3)',
|
|
406
406
|
}}
|
|
407
407
|
>
|
|
408
|
-
<FormField label="Display name"
|
|
409
|
-
<Input
|
|
408
|
+
<FormField label="Display name" id="mp-name">
|
|
409
|
+
{(field) => <Input {...field} placeholder="Your name" />}
|
|
410
410
|
</FormField>
|
|
411
|
-
<FormField label="Email"
|
|
412
|
-
<Input
|
|
411
|
+
<FormField label="Email" id="mp-email">
|
|
412
|
+
{(field) => <Input {...field} type="email" placeholder="you@example.com" />}
|
|
413
413
|
</FormField>
|
|
414
414
|
<Button type="submit">Save changes</Button>
|
|
415
415
|
</div>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { Textarea } from '../components/Textarea/Textarea';
|
|
3
|
+
|
|
4
|
+
// Wrap in a label so a11y checks pass (Textarea is a bare <textarea> — no
|
|
5
|
+
// built-in label association).
|
|
6
|
+
function Labeled({ label, ...props }: React.ComponentProps<typeof Textarea> & { label: string }) {
|
|
7
|
+
return (
|
|
8
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, width: 280 }}>
|
|
9
|
+
<label
|
|
10
|
+
htmlFor="sb-textarea"
|
|
11
|
+
style={{ fontSize: 14, color: 'var(--sv-color-text-primary)', fontFamily: 'system-ui' }}
|
|
12
|
+
>
|
|
13
|
+
{label}
|
|
14
|
+
</label>
|
|
15
|
+
<Textarea id="sb-textarea" {...props} />
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const meta = {
|
|
21
|
+
title: 'Components/Textarea',
|
|
22
|
+
component: Textarea,
|
|
23
|
+
parameters: {
|
|
24
|
+
layout: 'centered',
|
|
25
|
+
docs: {
|
|
26
|
+
description: {
|
|
27
|
+
component:
|
|
28
|
+
'The primitive multi-line text field. RSC-safe; forwards all native `<textarea>` props. No label is built-in — always pair with a `<label>` (or `FormField`) for accessibility.',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
} satisfies Meta<typeof Textarea>;
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
type Story = StoryObj<typeof meta>;
|
|
36
|
+
|
|
37
|
+
export const Default: Story = {
|
|
38
|
+
render: (_args) => <Labeled label="Description" placeholder="Add a description…" />,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const WithValue: Story = {
|
|
42
|
+
render: (_args) => <Labeled label="Notes" defaultValue="Follow up next week." />,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const CustomRows: Story = {
|
|
46
|
+
render: (_args) => <Labeled label="Message" rows={8} placeholder="Write your message…" />,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const Disabled: Story = {
|
|
50
|
+
render: (_args) => <Labeled label="Read-only field" defaultValue="Cannot be changed" disabled />,
|
|
51
|
+
};
|
|
@@ -19,6 +19,7 @@ const SEMANTIC_COLORS = [
|
|
|
19
19
|
'--sv-color-border-strong',
|
|
20
20
|
'--sv-color-accent',
|
|
21
21
|
'--sv-color-accent-hover',
|
|
22
|
+
'--sv-color-accent-subtle',
|
|
22
23
|
'--sv-color-focus-ring',
|
|
23
24
|
'--sv-color-error-surface',
|
|
24
25
|
'--sv-color-error-text',
|
|
@@ -82,6 +83,7 @@ const SHADOW_TOKENS = [
|
|
|
82
83
|
'--sv-shadow-hover',
|
|
83
84
|
'--sv-shadow-popover',
|
|
84
85
|
'--sv-shadow-overlay',
|
|
86
|
+
'--sv-shadow-control',
|
|
85
87
|
];
|
|
86
88
|
|
|
87
89
|
// ---------------------------------------------------------------------------
|
package/src/tokens/semantic.css
CHANGED
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
|
|
26
26
|
--sv-color-accent: var(--sv-grey-900);
|
|
27
27
|
--sv-color-accent-hover: var(--sv-grey-700);
|
|
28
|
+
/* Derived from --sv-color-accent via color-mix, so it re-themes automatically —
|
|
29
|
+
no separate dark-mode override needed. Tinted background for badges/chips
|
|
30
|
+
paired with --sv-color-accent text. */
|
|
31
|
+
--sv-color-accent-subtle: color-mix(in srgb, var(--sv-color-accent) 12%, transparent);
|
|
28
32
|
|
|
29
33
|
--sv-color-focus-ring: var(--sv-grey-900);
|
|
30
34
|
|
|
@@ -56,6 +60,7 @@
|
|
|
56
60
|
--sv-shadow-popover:
|
|
57
61
|
0 4px 16px rgba(0, 0, 0, 0.14), 0 2px 6px rgba(0, 0, 0, 0.08); /* e2 — floating panels */
|
|
58
62
|
--sv-shadow-overlay: 0 10px 38px rgba(0, 0, 0, 0.18), 0 6px 12px rgba(0, 0, 0, 0.12);
|
|
63
|
+
--sv-shadow-control: 0 1px 3px rgba(0, 0, 0, 0.2); /* small interactive-control shadows, e.g. Toggle thumb */
|
|
59
64
|
|
|
60
65
|
/*
|
|
61
66
|
* Instance identity tokens (RFC 0027 / RFC 0032 rename). Distinct namespace from --sv-color-*:
|
|
@@ -108,4 +113,5 @@
|
|
|
108
113
|
--sv-shadow-hover: 0 2px 6px rgba(0, 0, 0, 0.3);
|
|
109
114
|
--sv-shadow-popover: 0 4px 16px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.3);
|
|
110
115
|
--sv-shadow-overlay: 0 10px 38px rgba(0, 0, 0, 0.6), 0 6px 12px rgba(0, 0, 0, 0.5);
|
|
116
|
+
--sv-shadow-control: 0 1px 3px rgba(0, 0, 0, 0.5);
|
|
111
117
|
}
|