@sovereignfs/ui 0.21.3 → 0.33.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/Button.module.css +75 -8
- package/dist/Calendar.module.css +126 -0
- package/dist/Card.module.css +4 -2
- package/dist/Checkbox.module.css +109 -0
- package/dist/CodeTextarea.module.css +53 -0
- package/dist/ConfirmDialog.module.css +109 -0
- package/dist/DatePicker.module.css +57 -0
- package/dist/Dialog.module.css +73 -46
- package/dist/DragHandleRow.module.css +57 -0
- package/dist/Drawer.module.css +61 -0
- package/dist/FormField.module.css +5 -0
- package/dist/Icon.module.css +5 -0
- package/dist/Input.module.css +10 -0
- package/dist/Menu.module.css +82 -0
- package/dist/NavTabs.module.css +4 -2
- package/dist/OverlayHeader.module.css +98 -0
- package/dist/Popover.module.css +31 -1
- package/dist/SegmentedControl.module.css +4 -2
- package/dist/Select.module.css +9 -0
- package/dist/Sheet.module.css +55 -0
- package/dist/SplitPane.module.css +85 -0
- package/dist/StatusBadge.module.css +59 -0
- package/dist/SystemBanner.module.css +4 -2
- package/dist/Tabs.module.css +4 -2
- package/dist/TagInput.module.css +141 -0
- package/dist/Textarea.module.css +39 -0
- package/dist/Toast.module.css +7 -5
- package/dist/Toggle.module.css +1 -1
- package/dist/Tooltip.module.css +6 -1
- package/dist/index.d.ts +549 -20
- package/dist/index.js +1496 -193
- package/dist/tokens/primitives.css +19 -0
- package/dist/tokens/semantic.css +19 -0
- package/package.json +1 -1
- package/src/__tests__/motion.test.tsx +105 -0
- package/src/components/Button/Button.module.css +75 -8
- package/src/components/Button/Button.stories.tsx +6 -0
- package/src/components/Button/Button.tsx +17 -1
- package/src/components/Button/__tests__/Button.test.tsx +19 -0
- package/src/components/Calendar/Calendar.module.css +126 -0
- package/src/components/Calendar/Calendar.tsx +213 -0
- package/src/components/Calendar/__tests__/Calendar.test.tsx +98 -0
- package/src/components/Calendar/dateUtils.ts +97 -0
- package/src/components/Card/Card.module.css +4 -2
- package/src/components/Checkbox/Checkbox.module.css +109 -0
- package/src/components/Checkbox/Checkbox.tsx +78 -0
- package/src/components/CodeTextarea/CodeTextarea.module.css +53 -0
- package/src/components/CodeTextarea/CodeTextarea.stories.tsx +77 -0
- package/src/components/CodeTextarea/CodeTextarea.tsx +36 -0
- package/src/components/ConfirmDialog/ConfirmDialog.module.css +109 -0
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +124 -0
- package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +146 -0
- package/src/components/DatePicker/DatePicker.module.css +57 -0
- package/src/components/DatePicker/DatePicker.tsx +103 -0
- package/src/components/DatePicker/__tests__/DatePicker.test.tsx +89 -0
- package/src/components/Dialog/Dialog.module.css +73 -46
- package/src/components/Dialog/Dialog.stories.tsx +51 -1
- package/src/components/Dialog/Dialog.tsx +86 -22
- package/src/components/Dialog/__tests__/Dialog.test.tsx +67 -3
- package/src/components/DragHandleRow/DragHandleRow.module.css +57 -0
- package/src/components/DragHandleRow/DragHandleRow.tsx +61 -0
- package/src/components/Drawer/Drawer.module.css +61 -0
- package/src/components/Drawer/Drawer.tsx +117 -13
- package/src/components/Drawer/__tests__/Drawer.test.tsx +123 -2
- 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/Icon/Icon.module.css +5 -0
- package/src/components/Icon/Icon.stories.tsx +4 -3
- package/src/components/Icon/Icon.tsx +1 -1
- package/src/components/Icon/icons/calendar.tsx +23 -0
- package/src/components/Icon/icons/ellipsis-vertical.tsx +22 -0
- package/src/components/Icon/icons/index.ts +8 -4
- package/src/components/Icon/icons/sliders-horizontal.tsx +9 -9
- package/src/components/Input/Input.module.css +10 -0
- package/src/components/Menu/Menu.module.css +82 -0
- package/src/components/Menu/Menu.tsx +137 -0
- package/src/components/Menu/__tests__/Menu.test.tsx +197 -0
- package/src/components/NavTabs/NavTabs.module.css +4 -2
- package/src/components/OverlayHeader/OverlayHeader.module.css +98 -0
- package/src/components/OverlayHeader/OverlayHeader.tsx +62 -0
- package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +51 -0
- package/src/components/Popover/Popover.module.css +31 -1
- package/src/components/Popover/Popover.stories.tsx +126 -1
- package/src/components/Popover/Popover.tsx +140 -7
- package/src/components/SegmentedControl/SegmentedControl.module.css +4 -2
- package/src/components/Select/Select.module.css +9 -0
- package/src/components/Select/Select.tsx +10 -2
- package/src/components/Sheet/Sheet.module.css +55 -0
- package/src/components/Sheet/Sheet.tsx +151 -0
- package/src/components/Sheet/__tests__/Sheet.test.tsx +99 -0
- package/src/components/SplitPane/SplitPane.module.css +85 -0
- package/src/components/SplitPane/SplitPane.stories.tsx +160 -0
- package/src/components/SplitPane/SplitPane.tsx +149 -0
- package/src/components/SplitPane/__tests__/SplitPane.test.tsx +51 -0
- package/src/components/StatusBadge/StatusBadge.module.css +59 -0
- package/src/components/StatusBadge/StatusBadge.stories.tsx +74 -0
- package/src/components/StatusBadge/StatusBadge.tsx +65 -0
- package/src/components/SystemBanner/SystemBanner.module.css +4 -2
- package/src/components/Tabs/Tabs.module.css +4 -2
- package/src/components/TagInput/TagInput.module.css +141 -0
- package/src/components/TagInput/TagInput.stories.tsx +109 -0
- package/src/components/TagInput/TagInput.tsx +174 -0
- package/src/components/TagInput/__tests__/TagInput.test.tsx +69 -0
- 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 +7 -5
- package/src/components/Toggle/Toggle.module.css +1 -1
- package/src/components/Tooltip/Tooltip.module.css +6 -1
- package/src/hooks/__tests__/useDoubleTap.test.tsx +117 -0
- package/src/hooks/__tests__/useIsMobile.test.tsx +80 -0
- package/src/hooks/__tests__/useLongPress.test.tsx +158 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useDoubleTap.ts +91 -0
- package/src/hooks/useIsMobile.ts +33 -0
- package/src/hooks/useLongPress.ts +185 -0
- package/src/index.ts +37 -2
- package/src/motion.ts +78 -0
- package/src/stories/Checkbox.stories.tsx +45 -0
- package/src/stories/DesignSystemOverview.stories.tsx +354 -8
- package/src/stories/DragHandleRow.stories.tsx +41 -0
- package/src/stories/FormField.stories.tsx +8 -8
- package/src/stories/InteractionHooks.stories.tsx +388 -0
- package/src/stories/MobilePatterns.stories.tsx +4 -4
- package/src/stories/Textarea.stories.tsx +51 -0
- package/src/stories/TokenGallery.stories.tsx +87 -1
- package/src/tokens/primitives.css +19 -0
- package/src/tokens/semantic.css +19 -0
- package/src/components/Icon/icons/terminal.tsx +0 -19
|
@@ -11,6 +11,11 @@ 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 { CodeTextarea } from '../components/CodeTextarea/CodeTextarea';
|
|
15
|
+
import { Textarea } from '../components/Textarea/Textarea';
|
|
16
|
+
import { StatusBadge } from '../components/StatusBadge/StatusBadge';
|
|
17
|
+
import { SplitPane } from '../components/SplitPane/SplitPane';
|
|
18
|
+
import { TagInput } from '../components/TagInput/TagInput';
|
|
14
19
|
import { NavTabs } from '../components/NavTabs/NavTabs';
|
|
15
20
|
import { PageHeader } from '../components/PageHeader/PageHeader';
|
|
16
21
|
import { Popover } from '../components/Popover/Popover';
|
|
@@ -22,6 +27,14 @@ import { Tabs } from '../components/Tabs/Tabs';
|
|
|
22
27
|
import { ToastProvider, useToast } from '../components/Toast/Toast';
|
|
23
28
|
import { Toggle } from '../components/Toggle/Toggle';
|
|
24
29
|
import { Tooltip } from '../components/Tooltip/Tooltip';
|
|
30
|
+
import { Checkbox } from '../components/Checkbox/Checkbox';
|
|
31
|
+
import { DragHandleRow } from '../components/DragHandleRow/DragHandleRow';
|
|
32
|
+
import { OverlayHeader } from '../components/OverlayHeader/OverlayHeader';
|
|
33
|
+
import { Sheet } from '../components/Sheet/Sheet';
|
|
34
|
+
import { ConfirmDialog } from '../components/ConfirmDialog/ConfirmDialog';
|
|
35
|
+
import { Menu } from '../components/Menu/Menu';
|
|
36
|
+
import { Calendar } from '../components/Calendar/Calendar';
|
|
37
|
+
import { DatePicker } from '../components/DatePicker/DatePicker';
|
|
25
38
|
|
|
26
39
|
// ---------------------------------------------------------------------------
|
|
27
40
|
// Shared primitives
|
|
@@ -167,6 +180,8 @@ const COLOR_GROUPS: Array<{
|
|
|
167
180
|
{ token: '--sv-color-text-muted', name: 'text-muted' },
|
|
168
181
|
{ token: '--sv-color-text-subtle', name: 'text-subtle' },
|
|
169
182
|
{ token: '--sv-color-text-on-accent', name: 'text-on-accent' },
|
|
183
|
+
{ token: '--sv-color-text-on-error', name: 'text-on-error' },
|
|
184
|
+
{ token: '--sv-color-text-on-success', name: 'text-on-success' },
|
|
170
185
|
],
|
|
171
186
|
},
|
|
172
187
|
{
|
|
@@ -194,6 +209,7 @@ const COLOR_GROUPS: Array<{
|
|
|
194
209
|
{ token: '--sv-color-error-surface', name: 'error-surface' },
|
|
195
210
|
{ token: '--sv-color-error-text', name: 'error-text' },
|
|
196
211
|
{ token: '--sv-color-error-border', name: 'error-border' },
|
|
212
|
+
{ token: '--sv-color-error-solid', name: 'error-solid' },
|
|
197
213
|
],
|
|
198
214
|
},
|
|
199
215
|
{
|
|
@@ -212,6 +228,7 @@ const COLOR_GROUPS: Array<{
|
|
|
212
228
|
{ token: '--sv-color-success-surface', name: 'success-surface' },
|
|
213
229
|
{ token: '--sv-color-success-text', name: 'success-text' },
|
|
214
230
|
{ token: '--sv-color-success-border', name: 'success-border' },
|
|
231
|
+
{ token: '--sv-color-success-solid', name: 'success-solid' },
|
|
215
232
|
],
|
|
216
233
|
},
|
|
217
234
|
{
|
|
@@ -545,6 +562,150 @@ function PopoverDemo() {
|
|
|
545
562
|
);
|
|
546
563
|
}
|
|
547
564
|
|
|
565
|
+
function OverlayHeaderDemo() {
|
|
566
|
+
return (
|
|
567
|
+
<div
|
|
568
|
+
style={{
|
|
569
|
+
width: '100%',
|
|
570
|
+
maxWidth: 360,
|
|
571
|
+
border: '1px solid var(--sv-color-border)',
|
|
572
|
+
borderRadius: 'var(--sv-radius-lg)',
|
|
573
|
+
overflow: 'hidden',
|
|
574
|
+
}}
|
|
575
|
+
>
|
|
576
|
+
<OverlayHeader
|
|
577
|
+
title="Edit list"
|
|
578
|
+
onClose={() => {}}
|
|
579
|
+
onBack={() => {}}
|
|
580
|
+
action={
|
|
581
|
+
<Button size="sm" variant="ghost" onClick={() => {}}>
|
|
582
|
+
Save
|
|
583
|
+
</Button>
|
|
584
|
+
}
|
|
585
|
+
/>
|
|
586
|
+
</div>
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function SheetDemo() {
|
|
591
|
+
const [open, setOpen] = useState(false);
|
|
592
|
+
return (
|
|
593
|
+
<>
|
|
594
|
+
<Button size="sm" onClick={() => setOpen(true)}>
|
|
595
|
+
Open sheet
|
|
596
|
+
</Button>
|
|
597
|
+
{/* Storybook has no --sv-shell-header-height/--sv-shell-footer-height, so
|
|
598
|
+
the panel fills this fixed-height preview box instead of the real
|
|
599
|
+
app's header-to-footer region — same component, just no shell
|
|
600
|
+
around it here. */}
|
|
601
|
+
<div
|
|
602
|
+
style={{
|
|
603
|
+
position: 'relative',
|
|
604
|
+
width: '100%',
|
|
605
|
+
maxWidth: 360,
|
|
606
|
+
height: 280,
|
|
607
|
+
marginTop: 12,
|
|
608
|
+
border: '1px solid var(--sv-color-border)',
|
|
609
|
+
borderRadius: 'var(--sv-radius-lg)',
|
|
610
|
+
overflow: 'hidden',
|
|
611
|
+
}}
|
|
612
|
+
>
|
|
613
|
+
<div style={{ position: 'absolute', inset: 0 }}>
|
|
614
|
+
<Sheet
|
|
615
|
+
open={open}
|
|
616
|
+
onClose={() => setOpen(false)}
|
|
617
|
+
title="Task detail"
|
|
618
|
+
aria-label="Task detail"
|
|
619
|
+
>
|
|
620
|
+
<div
|
|
621
|
+
style={{
|
|
622
|
+
padding: 16,
|
|
623
|
+
fontFamily: ff,
|
|
624
|
+
fontSize: 14,
|
|
625
|
+
color: 'var(--sv-color-text-muted)',
|
|
626
|
+
}}
|
|
627
|
+
>
|
|
628
|
+
Full-page overlay content — same pattern the tasks plugin used for its task-detail and
|
|
629
|
+
list-edit panels.
|
|
630
|
+
</div>
|
|
631
|
+
</Sheet>
|
|
632
|
+
</div>
|
|
633
|
+
</div>
|
|
634
|
+
</>
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function ConfirmDialogDemo() {
|
|
639
|
+
const [open, setOpen] = useState(false);
|
|
640
|
+
return (
|
|
641
|
+
<>
|
|
642
|
+
<Button size="sm" variant="destructive" onClick={() => setOpen(true)}>
|
|
643
|
+
Remove passkey
|
|
644
|
+
</Button>
|
|
645
|
+
<ConfirmDialog
|
|
646
|
+
open={open}
|
|
647
|
+
onClose={() => setOpen(false)}
|
|
648
|
+
onConfirm={() => setOpen(false)}
|
|
649
|
+
title="Remove passkey"
|
|
650
|
+
message="You will no longer be able to sign in with this passkey."
|
|
651
|
+
confirmLabel="Remove"
|
|
652
|
+
destructive
|
|
653
|
+
/>
|
|
654
|
+
</>
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function MenuDemo() {
|
|
659
|
+
const [open, setOpen] = useState(false);
|
|
660
|
+
const [sortBy, setSortBy] = useState<'manual' | 'title' | 'due'>('manual');
|
|
661
|
+
return (
|
|
662
|
+
<Menu
|
|
663
|
+
open={open}
|
|
664
|
+
onClose={() => setOpen(false)}
|
|
665
|
+
aria-label="List actions"
|
|
666
|
+
align="left"
|
|
667
|
+
trigger={
|
|
668
|
+
<Button size="sm" variant="ghost" onClick={() => setOpen((o) => !o)}>
|
|
669
|
+
<Icon name="settings" size="sm" aria-hidden /> Actions
|
|
670
|
+
</Button>
|
|
671
|
+
}
|
|
672
|
+
items={[
|
|
673
|
+
{ type: 'label', label: 'Sort by' },
|
|
674
|
+
{ label: 'Manual', checked: sortBy === 'manual', onSelect: () => setSortBy('manual') },
|
|
675
|
+
{ label: 'Title', checked: sortBy === 'title', onSelect: () => setSortBy('title') },
|
|
676
|
+
{ label: 'Due date', checked: sortBy === 'due', onSelect: () => setSortBy('due') },
|
|
677
|
+
{ type: 'separator' },
|
|
678
|
+
{ label: 'Rename', icon: 'pencil', onSelect: () => {} },
|
|
679
|
+
{ label: 'Duplicate', icon: 'plus', onSelect: () => {} },
|
|
680
|
+
{ label: 'Delete', icon: 'trash-2', destructive: true, onSelect: () => {} },
|
|
681
|
+
]}
|
|
682
|
+
/>
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function CalendarDemo() {
|
|
687
|
+
const [value, setValue] = useState<Date | null>(new Date());
|
|
688
|
+
return (
|
|
689
|
+
<div style={{ width: '100%', maxWidth: 320 }}>
|
|
690
|
+
<Calendar value={value} onChange={setValue} aria-label="Example calendar" />
|
|
691
|
+
</div>
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function DatePickerDemo() {
|
|
696
|
+
const [value, setValue] = useState<Date | null>(null);
|
|
697
|
+
return (
|
|
698
|
+
<div style={{ width: '100%', maxWidth: 240 }}>
|
|
699
|
+
<DatePicker
|
|
700
|
+
value={value}
|
|
701
|
+
onChange={setValue}
|
|
702
|
+
aria-label="Due date"
|
|
703
|
+
placeholder="Select date"
|
|
704
|
+
/>
|
|
705
|
+
</div>
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
|
|
548
709
|
function ToastDemo() {
|
|
549
710
|
const { show } = useToast();
|
|
550
711
|
return (
|
|
@@ -564,6 +725,11 @@ function ToastDemo() {
|
|
|
564
725
|
);
|
|
565
726
|
}
|
|
566
727
|
|
|
728
|
+
function TagInputDemo() {
|
|
729
|
+
const [tags, setTags] = useState(['draft', 'frontmatter']);
|
|
730
|
+
return <TagInput value={tags} onChange={setTags} aria-label="Tags" />;
|
|
731
|
+
}
|
|
732
|
+
|
|
567
733
|
// ---------------------------------------------------------------------------
|
|
568
734
|
// Main component
|
|
569
735
|
// ---------------------------------------------------------------------------
|
|
@@ -1181,7 +1347,7 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
|
|
|
1181
1347
|
<section style={{ marginBottom: 'var(--sv-space-12)' }}>
|
|
1182
1348
|
<SectionHeader
|
|
1183
1349
|
title="Component gallery"
|
|
1184
|
-
subtitle="All
|
|
1350
|
+
subtitle="All 22 components — click each story in the sidebar for the full API, variants, and controls."
|
|
1185
1351
|
/>
|
|
1186
1352
|
|
|
1187
1353
|
<div style={{ marginBottom: 'var(--sv-space-6)' }}>
|
|
@@ -1203,7 +1369,7 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
|
|
|
1203
1369
|
<ComponentCard
|
|
1204
1370
|
name="Button"
|
|
1205
1371
|
importLine="import { Button } from '@sovereignfs/ui';"
|
|
1206
|
-
usage="Primary interactive control. Four variants: primary, secondary, ghost, destructive. Two sizes: md (default), sm."
|
|
1372
|
+
usage="Primary interactive control. Four variants: primary, secondary, ghost, destructive. Two sizes: md (default), sm. loading disables the button, sets aria-busy, and shows a spinner. 44px min-height under (pointer: coarse); hover behind (hover: hover)."
|
|
1207
1373
|
>
|
|
1208
1374
|
<Button>Primary</Button>
|
|
1209
1375
|
<Button variant="secondary">Secondary</Button>
|
|
@@ -1211,6 +1377,7 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
|
|
|
1211
1377
|
<Button variant="destructive" size="sm">
|
|
1212
1378
|
Delete
|
|
1213
1379
|
</Button>
|
|
1380
|
+
<Button loading>Saving…</Button>
|
|
1214
1381
|
</ComponentCard>
|
|
1215
1382
|
|
|
1216
1383
|
{/* Badge */}
|
|
@@ -1229,6 +1396,18 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
|
|
|
1229
1396
|
<Badge variant="mono">v1.2.0</Badge>
|
|
1230
1397
|
</ComponentCard>
|
|
1231
1398
|
|
|
1399
|
+
{/* StatusBadge */}
|
|
1400
|
+
<ComponentCard
|
|
1401
|
+
name="StatusBadge"
|
|
1402
|
+
importLine="import { StatusBadge } from '@sovereignfs/ui';"
|
|
1403
|
+
usage="Inline status indicator for editor sync, draft, conflict, and delete-pending states. RSC-safe."
|
|
1404
|
+
>
|
|
1405
|
+
<StatusBadge status="draft" />
|
|
1406
|
+
<StatusBadge status="synced" />
|
|
1407
|
+
<StatusBadge status="conflict" />
|
|
1408
|
+
<StatusBadge status="pending-delete" />
|
|
1409
|
+
</ComponentCard>
|
|
1410
|
+
|
|
1232
1411
|
{/* Input */}
|
|
1233
1412
|
<ComponentCard
|
|
1234
1413
|
name="Input"
|
|
@@ -1241,6 +1420,15 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
|
|
|
1241
1420
|
</div>
|
|
1242
1421
|
</ComponentCard>
|
|
1243
1422
|
|
|
1423
|
+
{/* TagInput */}
|
|
1424
|
+
<ComponentCard
|
|
1425
|
+
name="TagInput"
|
|
1426
|
+
importLine="import { TagInput } from '@sovereignfs/ui';"
|
|
1427
|
+
usage="Controlled multi-value input for tags and frontmatter arrays. Enter/comma adds, Backspace removes, paste can split multiple tags."
|
|
1428
|
+
>
|
|
1429
|
+
<TagInputDemo />
|
|
1430
|
+
</ComponentCard>
|
|
1431
|
+
|
|
1244
1432
|
{/* Select */}
|
|
1245
1433
|
<ComponentCard
|
|
1246
1434
|
name="Select"
|
|
@@ -1328,11 +1516,38 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
|
|
|
1328
1516
|
<ComponentCard
|
|
1329
1517
|
name="Drawer"
|
|
1330
1518
|
importLine="import { Drawer } from '@sovereignfs/ui';"
|
|
1331
|
-
usage="Bottom-sheet panel
|
|
1519
|
+
usage="Bottom-sheet panel. Esc, scrim-click, or swipe-down on the built-in grab handle to dismiss. snapHeight: 'content' (default, capped 80dvh) or 'half' (fixed 50dvh). Respects safe-area-inset-bottom."
|
|
1332
1520
|
>
|
|
1333
1521
|
<DrawerDemo />
|
|
1334
1522
|
</ComponentCard>
|
|
1335
1523
|
|
|
1524
|
+
{/* OverlayHeader */}
|
|
1525
|
+
<ComponentCard
|
|
1526
|
+
name="OverlayHeader"
|
|
1527
|
+
importLine="import { OverlayHeader } from '@sovereignfs/ui';"
|
|
1528
|
+
usage="Shared fixed secondary header for Dialog's mobile mode, Sheet, and Drawer: title + close, optional back button, trailing action, and a second row for tab strips."
|
|
1529
|
+
>
|
|
1530
|
+
<OverlayHeaderDemo />
|
|
1531
|
+
</ComponentCard>
|
|
1532
|
+
|
|
1533
|
+
{/* Sheet */}
|
|
1534
|
+
<ComponentCard
|
|
1535
|
+
name="Sheet"
|
|
1536
|
+
importLine="import { Sheet } from '@sovereignfs/ui';"
|
|
1537
|
+
usage="Full-page overlay filling a plugin's content area between the shell header and footer — for detail views (task detail, list edit). No desktop equivalent; a desktop layout shows the same content inline instead."
|
|
1538
|
+
>
|
|
1539
|
+
<SheetDemo />
|
|
1540
|
+
</ComponentCard>
|
|
1541
|
+
|
|
1542
|
+
{/* ConfirmDialog */}
|
|
1543
|
+
<ComponentCard
|
|
1544
|
+
name="ConfirmDialog"
|
|
1545
|
+
importLine="import { ConfirmDialog } from '@sovereignfs/ui';"
|
|
1546
|
+
usage="Small, content-sized confirm/cancel prompt. Same presentation on desktop and mobile — not a full-screen sheet. destructive for a solid red confirm action; pending + error for an async onConfirm."
|
|
1547
|
+
>
|
|
1548
|
+
<ConfirmDialogDemo />
|
|
1549
|
+
</ComponentCard>
|
|
1550
|
+
|
|
1336
1551
|
{/* Popover */}
|
|
1337
1552
|
<ComponentCard
|
|
1338
1553
|
name="Popover"
|
|
@@ -1342,6 +1557,33 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
|
|
|
1342
1557
|
<PopoverDemo />
|
|
1343
1558
|
</ComponentCard>
|
|
1344
1559
|
|
|
1560
|
+
{/* Menu */}
|
|
1561
|
+
<ComponentCard
|
|
1562
|
+
name="Menu"
|
|
1563
|
+
importLine="import { Menu } from '@sovereignfs/ui';"
|
|
1564
|
+
usage="Adaptive action menu: Popover on desktop, Drawer on mobile. Same items list renders in both — for '⋯' row/list actions. Entries can be a plain item, a { type: 'label' } section heading, a { type: 'separator' } divider, or a checkable item (pass checked on every item in the group) for a mutually-exclusive set like sort order."
|
|
1565
|
+
>
|
|
1566
|
+
<MenuDemo />
|
|
1567
|
+
</ComponentCard>
|
|
1568
|
+
|
|
1569
|
+
{/* Calendar */}
|
|
1570
|
+
<ComponentCard
|
|
1571
|
+
name="Calendar"
|
|
1572
|
+
importLine="import { Calendar } from '@sovereignfs/ui';"
|
|
1573
|
+
usage="Keyboard-navigable month grid. Date-only (no time/range yet). Arrow keys, Home/End, PageUp/PageDown, Enter/Space to select."
|
|
1574
|
+
>
|
|
1575
|
+
<CalendarDemo />
|
|
1576
|
+
</ComponentCard>
|
|
1577
|
+
|
|
1578
|
+
{/* DatePicker */}
|
|
1579
|
+
<ComponentCard
|
|
1580
|
+
name="DatePicker"
|
|
1581
|
+
importLine="import { DatePicker } from '@sovereignfs/ui';"
|
|
1582
|
+
usage="Form field pairing a trigger with Calendar: Popover on desktop, Drawer on mobile. Built-in trigger, unlike Menu's caller-supplied one."
|
|
1583
|
+
>
|
|
1584
|
+
<DatePickerDemo />
|
|
1585
|
+
</ComponentCard>
|
|
1586
|
+
|
|
1345
1587
|
{/* SystemBanner */}
|
|
1346
1588
|
<ComponentCard
|
|
1347
1589
|
name="SystemBanner"
|
|
@@ -1384,18 +1626,80 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
|
|
|
1384
1626
|
<ComponentCard
|
|
1385
1627
|
name="FormField"
|
|
1386
1628
|
importLine="import { FormField } from '@sovereignfs/ui';"
|
|
1387
|
-
usage="Accessible label + input wrapper.
|
|
1629
|
+
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
1630
|
>
|
|
1389
1631
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, width: '100%' }}>
|
|
1390
|
-
<FormField label="Email" hint="We'll never share this."
|
|
1391
|
-
<Input
|
|
1632
|
+
<FormField label="Email" hint="We'll never share this." id="ov-email">
|
|
1633
|
+
{(field) => <Input {...field} type="email" placeholder="you@example.com" />}
|
|
1392
1634
|
</FormField>
|
|
1393
|
-
<FormField label="Password" error="Must be 8+ characters."
|
|
1394
|
-
<Input
|
|
1635
|
+
<FormField label="Password" error="Must be 8+ characters." id="ov-pw">
|
|
1636
|
+
{(field) => <Input {...field} type="password" />}
|
|
1395
1637
|
</FormField>
|
|
1396
1638
|
</div>
|
|
1397
1639
|
</ComponentCard>
|
|
1398
1640
|
|
|
1641
|
+
{/* Textarea */}
|
|
1642
|
+
<ComponentCard
|
|
1643
|
+
name="Textarea"
|
|
1644
|
+
importLine="import { Textarea } from '@sovereignfs/ui';"
|
|
1645
|
+
usage="The primitive multi-line text field. Forwards all native textarea props. Pair with FormField or a <label> for accessibility."
|
|
1646
|
+
>
|
|
1647
|
+
<Textarea
|
|
1648
|
+
aria-label="Description"
|
|
1649
|
+
placeholder="Add a description…"
|
|
1650
|
+
style={{ width: '100%' }}
|
|
1651
|
+
/>
|
|
1652
|
+
</ComponentCard>
|
|
1653
|
+
|
|
1654
|
+
{/* CodeTextarea */}
|
|
1655
|
+
<ComponentCard
|
|
1656
|
+
name="CodeTextarea"
|
|
1657
|
+
importLine="import { CodeTextarea } from '@sovereignfs/ui';"
|
|
1658
|
+
usage="Monospace textarea for Markdown, YAML, JSON, and raw frontmatter editing. Pair with FormField for labels and validation."
|
|
1659
|
+
>
|
|
1660
|
+
<CodeTextarea
|
|
1661
|
+
aria-label="YAML source"
|
|
1662
|
+
defaultValue={'title: Release notes\ntags:\n - launch'}
|
|
1663
|
+
rows={5}
|
|
1664
|
+
style={{ width: '100%' }}
|
|
1665
|
+
/>
|
|
1666
|
+
</ComponentCard>
|
|
1667
|
+
|
|
1668
|
+
{/* SplitPane */}
|
|
1669
|
+
<ComponentCard
|
|
1670
|
+
name="SplitPane"
|
|
1671
|
+
importLine="import { SplitPane } from '@sovereignfs/ui';"
|
|
1672
|
+
usage="Responsive editor/preview or list/detail layout. Desktop panes are resizable with pointer and keyboard controls; mobile stacks to one column."
|
|
1673
|
+
>
|
|
1674
|
+
<SplitPane
|
|
1675
|
+
defaultPrimarySize={44}
|
|
1676
|
+
primary={
|
|
1677
|
+
<div
|
|
1678
|
+
style={{
|
|
1679
|
+
padding: 'var(--sv-space-3)',
|
|
1680
|
+
fontFamily: ffm,
|
|
1681
|
+
fontSize: 'var(--sv-font-size-xs)',
|
|
1682
|
+
color: 'var(--sv-color-text-primary)',
|
|
1683
|
+
}}
|
|
1684
|
+
>
|
|
1685
|
+
# Draft
|
|
1686
|
+
</div>
|
|
1687
|
+
}
|
|
1688
|
+
secondary={
|
|
1689
|
+
<div
|
|
1690
|
+
style={{
|
|
1691
|
+
padding: 'var(--sv-space-3)',
|
|
1692
|
+
fontFamily: ff,
|
|
1693
|
+
fontSize: 'var(--sv-font-size-sm)',
|
|
1694
|
+
color: 'var(--sv-color-text-primary)',
|
|
1695
|
+
}}
|
|
1696
|
+
>
|
|
1697
|
+
Draft preview
|
|
1698
|
+
</div>
|
|
1699
|
+
}
|
|
1700
|
+
/>
|
|
1701
|
+
</ComponentCard>
|
|
1702
|
+
|
|
1399
1703
|
{/* PageHeader */}
|
|
1400
1704
|
<ComponentCard
|
|
1401
1705
|
name="PageHeader"
|
|
@@ -1493,6 +1797,48 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
|
|
|
1493
1797
|
</Tooltip>
|
|
1494
1798
|
</div>
|
|
1495
1799
|
</ComponentCard>
|
|
1800
|
+
|
|
1801
|
+
{/* Checkbox */}
|
|
1802
|
+
<ComponentCard
|
|
1803
|
+
name="Checkbox"
|
|
1804
|
+
importLine="import { Checkbox } from '@sovereignfs/ui';"
|
|
1805
|
+
usage="Accessible checkbox with optional animated strike-through on the label when checked. Used in task lists. Tap target expands to 44px under (pointer: coarse) without growing the visible 18px box."
|
|
1806
|
+
>
|
|
1807
|
+
{(() => {
|
|
1808
|
+
const [a, setA] = useState(false);
|
|
1809
|
+
const [b, setB] = useState(true);
|
|
1810
|
+
return (
|
|
1811
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
1812
|
+
<Checkbox checked={a} onChange={setA} label="Unchecked task" strikeThrough />
|
|
1813
|
+
<Checkbox checked={b} onChange={setB} label="Completed task" strikeThrough />
|
|
1814
|
+
</div>
|
|
1815
|
+
);
|
|
1816
|
+
})()}
|
|
1817
|
+
</ComponentCard>
|
|
1818
|
+
|
|
1819
|
+
{/* DragHandleRow */}
|
|
1820
|
+
<ComponentCard
|
|
1821
|
+
name="DragHandleRow"
|
|
1822
|
+
importLine="import { DragHandleRow } from '@sovereignfs/ui';"
|
|
1823
|
+
usage="Row wrapper with a drag handle that appears on hover. Attach dnd-kit sortable props via handleProps."
|
|
1824
|
+
>
|
|
1825
|
+
<div style={{ width: '100%' }}>
|
|
1826
|
+
{['First item', 'Second item', 'Third item'].map((label) => (
|
|
1827
|
+
<DragHandleRow key={label}>
|
|
1828
|
+
<div
|
|
1829
|
+
style={{
|
|
1830
|
+
padding: 'var(--sv-space-2) var(--sv-space-1)',
|
|
1831
|
+
fontSize: 'var(--sv-font-size-sm)',
|
|
1832
|
+
color: 'var(--sv-color-text-primary)',
|
|
1833
|
+
borderBottom: '1px solid var(--sv-color-border)',
|
|
1834
|
+
}}
|
|
1835
|
+
>
|
|
1836
|
+
{label}
|
|
1837
|
+
</div>
|
|
1838
|
+
</DragHandleRow>
|
|
1839
|
+
))}
|
|
1840
|
+
</div>
|
|
1841
|
+
</ComponentCard>
|
|
1496
1842
|
</div>
|
|
1497
1843
|
</section>
|
|
1498
1844
|
|
|
@@ -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
|
};
|