@sovereignfs/ui 0.21.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 +661 -0
- package/NOTICE +24 -0
- package/dist/Avatar.module.css +38 -0
- package/dist/Badge.module.css +88 -0
- package/dist/Button.module.css +77 -0
- package/dist/Card.module.css +34 -0
- package/dist/Dialog.module.css +188 -0
- package/dist/Drawer.module.css +30 -0
- package/dist/EmptyState.module.css +27 -0
- package/dist/FormField.module.css +28 -0
- package/dist/Icon.module.css +22 -0
- package/dist/Input.module.css +37 -0
- package/dist/NavTabs.module.css +48 -0
- package/dist/PageHeader.module.css +32 -0
- package/dist/Popover.module.css +44 -0
- package/dist/SegmentedControl.module.css +53 -0
- package/dist/Select.module.css +69 -0
- package/dist/Spinner.module.css +39 -0
- package/dist/SystemBanner.module.css +67 -0
- package/dist/Tabs.module.css +49 -0
- package/dist/Toast.module.css +130 -0
- package/dist/Toggle.module.css +52 -0
- package/dist/Tooltip.module.css +51 -0
- package/dist/index.d.ts +327 -0
- package/dist/index.js +1370 -0
- package/dist/tokens/primitives.css +99 -0
- package/dist/tokens/semantic.css +111 -0
- package/dist/tokens.css +5 -0
- package/package.json +50 -0
- package/src/components/Avatar/Avatar.module.css +38 -0
- package/src/components/Avatar/Avatar.tsx +42 -0
- package/src/components/Badge/Badge.module.css +88 -0
- package/src/components/Badge/Badge.stories.tsx +118 -0
- package/src/components/Badge/Badge.tsx +59 -0
- package/src/components/Badge/__tests__/Badge.test.tsx +64 -0
- package/src/components/Button/Button.module.css +77 -0
- package/src/components/Button/Button.stories.tsx +76 -0
- package/src/components/Button/Button.tsx +36 -0
- package/src/components/Button/__tests__/Button.test.tsx +37 -0
- package/src/components/Card/Card.module.css +34 -0
- package/src/components/Card/Card.tsx +34 -0
- package/src/components/Dialog/Dialog.module.css +188 -0
- package/src/components/Dialog/Dialog.stories.tsx +97 -0
- package/src/components/Dialog/Dialog.tsx +137 -0
- package/src/components/Dialog/__tests__/Dialog.test.tsx +73 -0
- package/src/components/Drawer/Drawer.module.css +30 -0
- package/src/components/Drawer/Drawer.stories.tsx +88 -0
- package/src/components/Drawer/Drawer.tsx +103 -0
- package/src/components/Drawer/__tests__/Drawer.test.tsx +56 -0
- package/src/components/EmptyState/EmptyState.module.css +27 -0
- package/src/components/EmptyState/EmptyState.tsx +27 -0
- package/src/components/FormField/FormField.module.css +28 -0
- package/src/components/FormField/FormField.tsx +53 -0
- package/src/components/Icon/Icon.module.css +22 -0
- package/src/components/Icon/Icon.stories.tsx +94 -0
- package/src/components/Icon/Icon.tsx +43 -0
- package/src/components/Icon/__tests__/Icon.test.tsx +40 -0
- package/src/components/Icon/icons/activity.tsx +20 -0
- package/src/components/Icon/icons/alert-triangle.tsx +22 -0
- package/src/components/Icon/icons/bell.tsx +21 -0
- package/src/components/Icon/icons/check.tsx +20 -0
- package/src/components/Icon/icons/chevron-down.tsx +20 -0
- package/src/components/Icon/icons/chevron-left.tsx +20 -0
- package/src/components/Icon/icons/chevron-right.tsx +20 -0
- package/src/components/Icon/icons/chevron-up.tsx +20 -0
- package/src/components/Icon/icons/eye-off.tsx +23 -0
- package/src/components/Icon/icons/eye.tsx +21 -0
- package/src/components/Icon/icons/grid-2x2.tsx +22 -0
- package/src/components/Icon/icons/house.tsx +21 -0
- package/src/components/Icon/icons/index.ts +122 -0
- package/src/components/Icon/icons/info.tsx +22 -0
- package/src/components/Icon/icons/lock.tsx +21 -0
- package/src/components/Icon/icons/log-out.tsx +22 -0
- package/src/components/Icon/icons/mail.tsx +21 -0
- package/src/components/Icon/icons/package.tsx +23 -0
- package/src/components/Icon/icons/pencil.tsx +21 -0
- package/src/components/Icon/icons/plus.tsx +21 -0
- package/src/components/Icon/icons/rotate-ccw.tsx +21 -0
- package/src/components/Icon/icons/search.tsx +21 -0
- package/src/components/Icon/icons/settings.tsx +21 -0
- package/src/components/Icon/icons/shield.tsx +20 -0
- package/src/components/Icon/icons/sliders-horizontal.tsx +28 -0
- package/src/components/Icon/icons/terminal.tsx +19 -0
- package/src/components/Icon/icons/trash-2.tsx +24 -0
- package/src/components/Icon/icons/user.tsx +21 -0
- package/src/components/Icon/icons/x.tsx +21 -0
- package/src/components/Input/Input.module.css +37 -0
- package/src/components/Input/Input.stories.tsx +86 -0
- package/src/components/Input/Input.tsx +14 -0
- package/src/components/Input/__tests__/Input.test.tsx +22 -0
- package/src/components/NavTabs/NavTabs.module.css +48 -0
- package/src/components/NavTabs/NavTabs.tsx +33 -0
- package/src/components/PageHeader/PageHeader.module.css +32 -0
- package/src/components/PageHeader/PageHeader.tsx +21 -0
- package/src/components/Popover/Popover.module.css +44 -0
- package/src/components/Popover/Popover.stories.tsx +232 -0
- package/src/components/Popover/Popover.tsx +75 -0
- package/src/components/Popover/__tests__/Popover.test.tsx +98 -0
- package/src/components/SegmentedControl/SegmentedControl.module.css +53 -0
- package/src/components/SegmentedControl/SegmentedControl.stories.tsx +118 -0
- package/src/components/SegmentedControl/SegmentedControl.tsx +56 -0
- package/src/components/SegmentedControl/__tests__/SegmentedControl.test.tsx +63 -0
- package/src/components/Select/Select.module.css +69 -0
- package/src/components/Select/Select.stories.tsx +115 -0
- package/src/components/Select/Select.tsx +40 -0
- package/src/components/Select/__tests__/Select.test.tsx +59 -0
- package/src/components/Spinner/Spinner.module.css +39 -0
- package/src/components/Spinner/Spinner.tsx +23 -0
- package/src/components/SystemBanner/SystemBanner.module.css +67 -0
- package/src/components/SystemBanner/SystemBanner.stories.tsx +86 -0
- package/src/components/SystemBanner/SystemBanner.tsx +50 -0
- package/src/components/SystemBanner/__tests__/SystemBanner.test.tsx +50 -0
- package/src/components/Tabs/Tabs.module.css +49 -0
- package/src/components/Tabs/Tabs.stories.tsx +100 -0
- package/src/components/Tabs/Tabs.tsx +46 -0
- package/src/components/Tabs/__tests__/Tabs.test.tsx +45 -0
- package/src/components/Toast/Toast.module.css +130 -0
- package/src/components/Toast/Toast.stories.tsx +191 -0
- package/src/components/Toast/Toast.tsx +139 -0
- package/src/components/Toggle/Toggle.module.css +52 -0
- package/src/components/Toggle/Toggle.stories.tsx +95 -0
- package/src/components/Toggle/Toggle.tsx +39 -0
- package/src/components/Toggle/__tests__/Toggle.test.tsx +47 -0
- package/src/components/Tooltip/Tooltip.module.css +51 -0
- package/src/components/Tooltip/Tooltip.tsx +30 -0
- package/src/css-modules.d.ts +4 -0
- package/src/index.ts +45 -0
- package/src/stories/Avatar.stories.tsx +44 -0
- package/src/stories/Card.stories.tsx +35 -0
- package/src/stories/DesignSystemOverview.stories.tsx +1683 -0
- package/src/stories/EmptyState.stories.tsx +33 -0
- package/src/stories/FormField.stories.tsx +47 -0
- package/src/stories/MobilePatterns.stories.tsx +1285 -0
- package/src/stories/NavTabs.stories.tsx +35 -0
- package/src/stories/PageHeader.stories.tsx +31 -0
- package/src/stories/Spinner.stories.tsx +25 -0
- package/src/stories/TokenGallery.stories.tsx +355 -0
- package/src/stories/Tooltip.stories.tsx +59 -0
- package/src/tokens/primitives.css +99 -0
- package/src/tokens/semantic.css +111 -0
- package/src/tokens.css +5 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function PackageIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z" />
|
|
18
|
+
<path d="M12 22V12" />
|
|
19
|
+
<polyline points="3.29 7 12 12 20.71 7" />
|
|
20
|
+
<path d="m7.5 4.27 9 5.15" />
|
|
21
|
+
</svg>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function PencilIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
|
|
18
|
+
<path d="m15 5 4 4" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function PlusIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="M5 12h14" />
|
|
18
|
+
<path d="M12 5v14" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function RotateCcwIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
|
|
18
|
+
<path d="M3 3v5h5" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function SearchIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="m21 21-4.34-4.34" />
|
|
18
|
+
<circle cx="11" cy="11" r="8" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function SettingsIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" />
|
|
18
|
+
<circle cx="12" cy="12" r="3" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function ShieldIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" />
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function SlidersHorizontalIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<line x1="21" x2="14" y1="4" y2="4" />
|
|
18
|
+
<line x1="10" x2="3" y1="4" y2="4" />
|
|
19
|
+
<line x1="21" x2="12" y1="12" y2="12" />
|
|
20
|
+
<line x1="8" x2="3" y1="12" y2="12" />
|
|
21
|
+
<line x1="21" x2="16" y1="20" y2="20" />
|
|
22
|
+
<line x1="12" x2="3" y1="20" y2="20" />
|
|
23
|
+
<line x1="14" x2="14" y1="2" y2="6" />
|
|
24
|
+
<line x1="8" x2="8" y1="10" y2="14" />
|
|
25
|
+
<line x1="16" x2="16" y1="18" y2="22" />
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SVGProps } from 'react';
|
|
2
|
+
|
|
3
|
+
export function TerminalIcon(props: SVGProps<SVGSVGElement>) {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
strokeWidth="2"
|
|
11
|
+
strokeLinecap="round"
|
|
12
|
+
strokeLinejoin="round"
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<polyline points="4 17 10 11 4 5" />
|
|
16
|
+
<line x1="12" x2="20" y1="19" y2="19" />
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function Trash2Icon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="M10 11v6" />
|
|
18
|
+
<path d="M14 11v6" />
|
|
19
|
+
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" />
|
|
20
|
+
<path d="M3 6h18" />
|
|
21
|
+
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
22
|
+
</svg>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function UserIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
|
|
18
|
+
<circle cx="12" cy="7" r="4" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function XIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="M18 6 6 18" />
|
|
18
|
+
<path d="m6 6 12 12" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
.input {
|
|
2
|
+
display: block;
|
|
3
|
+
width: 100%;
|
|
4
|
+
padding: var(--sv-space-2) var(--sv-space-3);
|
|
5
|
+
font-family: var(--sv-font-family);
|
|
6
|
+
font-size: var(--sv-font-size-sm);
|
|
7
|
+
line-height: 1.5;
|
|
8
|
+
color: var(--sv-color-text-primary);
|
|
9
|
+
background-color: var(--sv-color-surface);
|
|
10
|
+
border: 1px solid var(--sv-color-border-strong);
|
|
11
|
+
border-radius: var(--sv-radius-md);
|
|
12
|
+
transition:
|
|
13
|
+
border-color 0.15s ease,
|
|
14
|
+
box-shadow 0.15s ease;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.input::placeholder {
|
|
18
|
+
color: var(--sv-color-text-muted);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.input:focus-visible {
|
|
22
|
+
outline: none;
|
|
23
|
+
border-color: var(--sv-color-accent);
|
|
24
|
+
box-shadow: 0 0 0 2px var(--sv-color-focus-ring);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.input:read-only {
|
|
28
|
+
background-color: var(--sv-color-surface-sunken);
|
|
29
|
+
color: var(--sv-color-text-muted);
|
|
30
|
+
border-color: var(--sv-color-border);
|
|
31
|
+
cursor: default;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.input:disabled {
|
|
35
|
+
opacity: 0.5;
|
|
36
|
+
cursor: not-allowed;
|
|
37
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { Input } from './Input';
|
|
3
|
+
|
|
4
|
+
// Wrap in a label so a11y checks pass (Input is a bare <input> — no built-in
|
|
5
|
+
// label association).
|
|
6
|
+
function Labeled({ label, ...props }: React.ComponentProps<typeof Input> & { label: string }) {
|
|
7
|
+
return (
|
|
8
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
9
|
+
<label
|
|
10
|
+
htmlFor="sb-input"
|
|
11
|
+
style={{ fontSize: 14, color: 'var(--sv-color-text-primary)', fontFamily: 'system-ui' }}
|
|
12
|
+
>
|
|
13
|
+
{label}
|
|
14
|
+
</label>
|
|
15
|
+
<Input id="sb-input" {...props} />
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const meta = {
|
|
21
|
+
title: 'Components/Input',
|
|
22
|
+
component: Input,
|
|
23
|
+
parameters: {
|
|
24
|
+
layout: 'centered',
|
|
25
|
+
docs: {
|
|
26
|
+
description: {
|
|
27
|
+
component:
|
|
28
|
+
'The primitive text field. RSC-safe; forwards all native `<input>` props. No label is built-in — always pair with a `<label>` for accessibility.',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
} satisfies Meta<typeof Input>;
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
type Story = StoryObj<typeof meta>;
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
export const Text: Story = {
|
|
40
|
+
render: (_args) => <Labeled label="Display name" type="text" placeholder="e.g. Jane Smith" />,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const Email: Story = {
|
|
44
|
+
render: (_args) => <Labeled label="Email address" type="email" placeholder="you@example.com" />,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const Password: Story = {
|
|
48
|
+
render: (_args) => <Labeled label="Password" type="password" placeholder="••••••••" />,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Disabled: Story = {
|
|
52
|
+
render: (_args) => (
|
|
53
|
+
<Labeled label="Read-only field" type="text" defaultValue="Cannot be changed" disabled />
|
|
54
|
+
),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Mimics an error state — the container adds red border via CSS token. */
|
|
58
|
+
export const ErrorState: Story = {
|
|
59
|
+
render: (_args) => (
|
|
60
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
61
|
+
<label
|
|
62
|
+
htmlFor="sb-input-err"
|
|
63
|
+
style={{ fontSize: 14, color: 'var(--sv-color-text-primary)', fontFamily: 'system-ui' }}
|
|
64
|
+
>
|
|
65
|
+
Email address
|
|
66
|
+
</label>
|
|
67
|
+
<Input
|
|
68
|
+
id="sb-input-err"
|
|
69
|
+
type="email"
|
|
70
|
+
defaultValue="not-an-email"
|
|
71
|
+
aria-invalid="true"
|
|
72
|
+
aria-describedby="sb-input-err-msg"
|
|
73
|
+
style={{
|
|
74
|
+
borderColor: 'var(--sv-color-error-border)',
|
|
75
|
+
outline: 'none',
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
<span
|
|
79
|
+
id="sb-input-err-msg"
|
|
80
|
+
style={{ fontSize: 12, color: 'var(--sv-color-error-text)', fontFamily: 'system-ui' }}
|
|
81
|
+
>
|
|
82
|
+
Enter a valid email address.
|
|
83
|
+
</span>
|
|
84
|
+
</div>
|
|
85
|
+
),
|
|
86
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { InputHTMLAttributes } from 'react';
|
|
2
|
+
import styles from './Input.module.css';
|
|
3
|
+
|
|
4
|
+
export type InputProps = InputHTMLAttributes<HTMLInputElement>;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Input — the primitive text field. Presentational and RSC-safe: it forwards
|
|
8
|
+
* all native input props to the underlying `<input>`. Styling references
|
|
9
|
+
* `--sv-*` tokens via CSS Modules; there are no hardcoded values.
|
|
10
|
+
*/
|
|
11
|
+
export function Input({ type = 'text', className, ...rest }: InputProps) {
|
|
12
|
+
const classes = [styles.input, className].filter(Boolean).join(' ');
|
|
13
|
+
return <input type={type} className={classes} {...rest} />;
|
|
14
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { Input } from '../Input';
|
|
5
|
+
|
|
6
|
+
describe('Input', () => {
|
|
7
|
+
it('renders and forwards native props', () => {
|
|
8
|
+
render(<Input placeholder="Email" defaultValue="a@b.c" />);
|
|
9
|
+
const input = screen.getByPlaceholderText('Email') as HTMLInputElement;
|
|
10
|
+
expect(input.value).toBe('a@b.c');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('defaults to type="text"', () => {
|
|
14
|
+
render(<Input aria-label="field" />);
|
|
15
|
+
expect((screen.getByLabelText('field') as HTMLInputElement).type).toBe('text');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('honours an explicit type', () => {
|
|
19
|
+
render(<Input aria-label="pw" type="password" />);
|
|
20
|
+
expect((screen.getByLabelText('pw') as HTMLInputElement).type).toBe('password');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
.nav {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-wrap: wrap;
|
|
4
|
+
gap: 0;
|
|
5
|
+
border-bottom: 1px solid var(--sv-color-border);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.link {
|
|
9
|
+
padding: var(--sv-space-2) var(--sv-space-3);
|
|
10
|
+
padding-bottom: calc(var(--sv-space-2) + 2px);
|
|
11
|
+
border-bottom: 2px solid transparent;
|
|
12
|
+
font-size: var(--sv-font-size-sm);
|
|
13
|
+
font-weight: var(--sv-font-weight-medium);
|
|
14
|
+
color: var(--sv-color-text-muted);
|
|
15
|
+
text-decoration: none;
|
|
16
|
+
white-space: nowrap;
|
|
17
|
+
transition: color 0.1s ease;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.link:hover {
|
|
21
|
+
color: var(--sv-color-text-primary);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.link:focus-visible {
|
|
25
|
+
outline: 2px solid var(--sv-color-focus-ring);
|
|
26
|
+
outline-offset: -2px;
|
|
27
|
+
border-radius: var(--sv-radius-sm);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.active {
|
|
31
|
+
color: var(--sv-color-text-primary);
|
|
32
|
+
font-weight: var(--sv-font-weight-semibold);
|
|
33
|
+
border-bottom-color: var(--sv-color-text-primary);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@media (max-width: 640px) {
|
|
37
|
+
.nav {
|
|
38
|
+
flex-wrap: nowrap;
|
|
39
|
+
overflow-x: auto;
|
|
40
|
+
scrollbar-width: none;
|
|
41
|
+
-webkit-overflow-scrolling: touch;
|
|
42
|
+
mask-image: linear-gradient(to right, black calc(100% - 32px), transparent 100%);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.nav::-webkit-scrollbar {
|
|
46
|
+
display: none;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import styles from './NavTabs.module.css';
|
|
2
|
+
|
|
3
|
+
export interface NavTabItem {
|
|
4
|
+
label: string;
|
|
5
|
+
href: string;
|
|
6
|
+
active?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface NavTabsProps {
|
|
10
|
+
items: NavTabItem[];
|
|
11
|
+
className?: string;
|
|
12
|
+
'aria-label'?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function NavTabs({ items, className, 'aria-label': ariaLabel }: NavTabsProps) {
|
|
16
|
+
return (
|
|
17
|
+
<nav
|
|
18
|
+
className={[styles.nav, className].filter(Boolean).join(' ')}
|
|
19
|
+
aria-label={ariaLabel ?? 'Page navigation'}
|
|
20
|
+
>
|
|
21
|
+
{items.map((item) => (
|
|
22
|
+
<a
|
|
23
|
+
key={item.href}
|
|
24
|
+
href={item.href}
|
|
25
|
+
className={[styles.link, item.active && styles.active].filter(Boolean).join(' ')}
|
|
26
|
+
aria-current={item.active ? 'page' : undefined}
|
|
27
|
+
>
|
|
28
|
+
{item.label}
|
|
29
|
+
</a>
|
|
30
|
+
))}
|
|
31
|
+
</nav>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
.header {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: flex-start;
|
|
4
|
+
justify-content: space-between;
|
|
5
|
+
gap: var(--sv-space-4);
|
|
6
|
+
margin-bottom: var(--sv-space-6);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.text {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: var(--sv-space-1);
|
|
13
|
+
min-width: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.title {
|
|
17
|
+
margin: 0;
|
|
18
|
+
font-size: var(--sv-font-size-2xl);
|
|
19
|
+
font-weight: var(--sv-font-weight-semibold);
|
|
20
|
+
color: var(--sv-color-text-primary);
|
|
21
|
+
line-height: 1.2;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.description {
|
|
25
|
+
margin: 0;
|
|
26
|
+
font-size: var(--sv-font-size-sm);
|
|
27
|
+
color: var(--sv-color-text-muted);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.action {
|
|
31
|
+
flex-shrink: 0;
|
|
32
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import styles from './PageHeader.module.css';
|
|
3
|
+
|
|
4
|
+
export interface PageHeaderProps {
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
action?: ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function PageHeader({ title, description, action, className }: PageHeaderProps) {
|
|
12
|
+
return (
|
|
13
|
+
<header className={[styles.header, className].filter(Boolean).join(' ')}>
|
|
14
|
+
<div className={styles.text}>
|
|
15
|
+
<h1 className={styles.title}>{title}</h1>
|
|
16
|
+
{description && <p className={styles.description}>{description}</p>}
|
|
17
|
+
</div>
|
|
18
|
+
{action && <div className={styles.action}>{action}</div>}
|
|
19
|
+
</header>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.panel {
|
|
7
|
+
position: absolute;
|
|
8
|
+
top: calc(100% + var(--sv-space-2));
|
|
9
|
+
z-index: 60;
|
|
10
|
+
background: var(--sv-color-surface);
|
|
11
|
+
border: 1px solid var(--sv-color-border);
|
|
12
|
+
border-radius: var(--sv-radius-2xl);
|
|
13
|
+
box-shadow: var(--sv-shadow-popover);
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
/* Slide in from slightly above */
|
|
16
|
+
animation: sv-popover-in 0.12s ease;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@keyframes sv-popover-in {
|
|
20
|
+
from {
|
|
21
|
+
opacity: 0;
|
|
22
|
+
transform: translateY(-4px);
|
|
23
|
+
}
|
|
24
|
+
to {
|
|
25
|
+
opacity: 1;
|
|
26
|
+
transform: translateY(0);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@media (prefers-reduced-motion: reduce) {
|
|
31
|
+
.panel {
|
|
32
|
+
animation: none;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Alignment: right edge of panel aligns to right edge of trigger */
|
|
37
|
+
.right {
|
|
38
|
+
right: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Alignment: left edge of panel aligns to left edge of trigger */
|
|
42
|
+
.left {
|
|
43
|
+
left: 0;
|
|
44
|
+
}
|