@shipfox/react-ui 0.14.0 → 0.15.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/.storybook/preview.tsx +7 -0
- package/.turbo/turbo-build.log +7 -7
- package/.turbo/turbo-check.log +2 -2
- package/.turbo/turbo-type.log +1 -1
- package/CHANGELOG.md +10 -0
- package/dist/components/avatar/avatar.js +1 -1
- package/dist/components/avatar/avatar.js.map +1 -1
- package/dist/components/button-group/button-group.d.ts +17 -0
- package/dist/components/button-group/button-group.d.ts.map +1 -0
- package/dist/components/button-group/button-group.js +74 -0
- package/dist/components/button-group/button-group.js.map +1 -0
- package/dist/components/button-group/button-group.stories.js +644 -0
- package/dist/components/button-group/button-group.stories.js.map +1 -0
- package/dist/components/button-group/index.d.ts +2 -0
- package/dist/components/button-group/index.d.ts.map +1 -0
- package/dist/components/button-group/index.js +3 -0
- package/dist/components/button-group/index.js.map +1 -0
- package/dist/components/code-block/code-block-footer.d.ts.map +1 -1
- package/dist/components/code-block/code-block-footer.js +4 -2
- package/dist/components/code-block/code-block-footer.js.map +1 -1
- package/dist/components/command/command.d.ts +28 -0
- package/dist/components/command/command.d.ts.map +1 -0
- package/dist/components/command/command.js +190 -0
- package/dist/components/command/command.js.map +1 -0
- package/dist/components/command/command.stories.js +228 -0
- package/dist/components/command/command.stories.js.map +1 -0
- package/dist/components/command/index.d.ts +2 -0
- package/dist/components/command/index.d.ts.map +1 -0
- package/dist/components/command/index.js +3 -0
- package/dist/components/command/index.js.map +1 -0
- package/dist/components/dashboard/components/analytics-content.d.ts +2 -0
- package/dist/components/dashboard/components/analytics-content.d.ts.map +1 -0
- package/dist/components/dashboard/components/analytics-content.js +180 -0
- package/dist/components/dashboard/components/analytics-content.js.map +1 -0
- package/dist/components/dashboard/components/animated-logo.d.ts +4 -0
- package/dist/components/dashboard/components/animated-logo.d.ts.map +1 -0
- package/dist/components/dashboard/components/animated-logo.js +23 -0
- package/dist/components/dashboard/components/animated-logo.js.map +1 -0
- package/dist/components/dashboard/components/complete-setup-button.d.ts +4 -0
- package/dist/components/dashboard/components/complete-setup-button.d.ts.map +1 -0
- package/dist/components/dashboard/components/complete-setup-button.js +28 -0
- package/dist/components/dashboard/components/complete-setup-button.js.map +1 -0
- package/dist/components/dashboard/components/jobs-content.d.ts +2 -0
- package/dist/components/dashboard/components/jobs-content.d.ts.map +1 -0
- package/dist/components/dashboard/components/jobs-content.js +69 -0
- package/dist/components/dashboard/components/jobs-content.js.map +1 -0
- package/dist/components/dashboard/components/mobile-menu.d.ts +2 -0
- package/dist/components/dashboard/components/mobile-menu.d.ts.map +1 -0
- package/dist/components/dashboard/components/mobile-menu.js +65 -0
- package/dist/components/dashboard/components/mobile-menu.js.map +1 -0
- package/dist/components/dashboard/components/organization-selector.d.ts +2 -0
- package/dist/components/dashboard/components/organization-selector.d.ts.map +1 -0
- package/dist/components/dashboard/components/organization-selector.js +92 -0
- package/dist/components/dashboard/components/organization-selector.js.map +1 -0
- package/dist/components/dashboard/components/top-menu.d.ts +5 -0
- package/dist/components/dashboard/components/top-menu.d.ts.map +1 -0
- package/dist/components/dashboard/components/top-menu.js +31 -0
- package/dist/components/dashboard/components/top-menu.js.map +1 -0
- package/dist/components/dashboard/components/topbar-button.d.ts +7 -0
- package/dist/components/dashboard/components/topbar-button.d.ts.map +1 -0
- package/dist/components/dashboard/components/topbar-button.js +18 -0
- package/dist/components/dashboard/components/topbar-button.js.map +1 -0
- package/dist/components/dashboard/components/topbar.d.ts +4 -0
- package/dist/components/dashboard/components/topbar.d.ts.map +1 -0
- package/dist/components/dashboard/components/topbar.js +62 -0
- package/dist/components/dashboard/components/topbar.js.map +1 -0
- package/dist/components/dashboard/components/user-profile.d.ts +2 -0
- package/dist/components/dashboard/components/user-profile.d.ts.map +1 -0
- package/dist/components/dashboard/components/user-profile.js +146 -0
- package/dist/components/dashboard/components/user-profile.js.map +1 -0
- package/dist/components/dashboard/dashboard.d.ts +2 -0
- package/dist/components/dashboard/dashboard.d.ts.map +1 -0
- package/dist/components/dashboard/dashboard.js +70 -0
- package/dist/components/dashboard/dashboard.js.map +1 -0
- package/dist/components/dashboard/dashboard.stories.js +23 -0
- package/dist/components/dashboard/dashboard.stories.js.map +1 -0
- package/dist/components/dashboard/index.d.ts +2 -0
- package/dist/components/dashboard/index.d.ts.map +1 -0
- package/dist/components/dashboard/index.js +3 -0
- package/dist/components/dashboard/index.js.map +1 -0
- package/dist/components/form/form.stories.js +6 -1
- package/dist/components/form/form.stories.js.map +1 -1
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +7 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/kbd/index.d.ts +2 -0
- package/dist/components/kbd/index.d.ts.map +1 -0
- package/dist/components/kbd/index.js +3 -0
- package/dist/components/kbd/index.js.map +1 -0
- package/dist/components/kbd/kbd.d.ts +7 -0
- package/dist/components/kbd/kbd.d.ts.map +1 -0
- package/dist/components/kbd/kbd.js +18 -0
- package/dist/components/kbd/kbd.js.map +1 -0
- package/dist/components/kbd/kbd.stories.js +119 -0
- package/dist/components/kbd/kbd.stories.js.map +1 -0
- package/dist/components/search/index.d.ts +7 -0
- package/dist/components/search/index.d.ts.map +1 -0
- package/dist/components/search/index.js +8 -0
- package/dist/components/search/index.js.map +1 -0
- package/dist/components/search/search-context.d.ts +11 -0
- package/dist/components/search/search-context.d.ts.map +1 -0
- package/dist/components/search/search-context.js +56 -0
- package/dist/components/search/search-context.js.map +1 -0
- package/dist/components/search/search-inline.d.ts +9 -0
- package/dist/components/search/search-inline.d.ts.map +1 -0
- package/dist/components/search/search-inline.js +85 -0
- package/dist/components/search/search-inline.js.map +1 -0
- package/dist/components/search/search-modal.d.ts +25 -0
- package/dist/components/search/search-modal.d.ts.map +1 -0
- package/dist/components/search/search-modal.js +162 -0
- package/dist/components/search/search-modal.js.map +1 -0
- package/dist/components/search/search-trigger.d.ts +9 -0
- package/dist/components/search/search-trigger.d.ts.map +1 -0
- package/dist/components/search/search-trigger.js +37 -0
- package/dist/components/search/search-trigger.js.map +1 -0
- package/dist/components/search/search-variants.d.ts +14 -0
- package/dist/components/search/search-variants.d.ts.map +1 -0
- package/dist/components/search/search-variants.js +90 -0
- package/dist/components/search/search-variants.js.map +1 -0
- package/dist/components/search/search.d.ts +11 -0
- package/dist/components/search/search.d.ts.map +1 -0
- package/dist/components/search/search.js +35 -0
- package/dist/components/search/search.js.map +1 -0
- package/dist/components/search/search.stories.js +630 -0
- package/dist/components/search/search.stories.js.map +1 -0
- package/dist/components/select/index.d.ts +2 -0
- package/dist/components/select/index.d.ts.map +1 -0
- package/dist/components/select/index.js +3 -0
- package/dist/components/select/index.js.map +1 -0
- package/dist/components/select/select.d.ts +25 -0
- package/dist/components/select/select.d.ts.map +1 -0
- package/dist/components/select/select.js +153 -0
- package/dist/components/select/select.js.map +1 -0
- package/dist/components/select/select.stories.js +393 -0
- package/dist/components/select/select.stories.js.map +1 -0
- package/dist/components/skeleton/index.d.ts +2 -0
- package/dist/components/skeleton/index.d.ts.map +1 -0
- package/dist/components/skeleton/index.js +3 -0
- package/dist/components/skeleton/index.js.map +1 -0
- package/dist/components/skeleton/skeleton.d.ts +5 -0
- package/dist/components/skeleton/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton/skeleton.js +11 -0
- package/dist/components/skeleton/skeleton.js.map +1 -0
- package/dist/components/skeleton/skeleton.stories.js +345 -0
- package/dist/components/skeleton/skeleton.stories.js.map +1 -0
- package/dist/components/table/data-table.d.ts +70 -0
- package/dist/components/table/data-table.d.ts.map +1 -0
- package/dist/components/table/data-table.js +159 -0
- package/dist/components/table/data-table.js.map +1 -0
- package/dist/components/table/index.d.ts +6 -0
- package/dist/components/table/index.d.ts.map +1 -0
- package/dist/components/table/index.js +6 -0
- package/dist/components/table/index.js.map +1 -0
- package/dist/components/table/table-column-header.d.ts +79 -0
- package/dist/components/table/table-column-header.d.ts.map +1 -0
- package/dist/components/table/table-column-header.js +99 -0
- package/dist/components/table/table-column-header.js.map +1 -0
- package/dist/components/table/table-pagination.d.ts +53 -0
- package/dist/components/table/table-pagination.d.ts.map +1 -0
- package/dist/components/table/table-pagination.js +139 -0
- package/dist/components/table/table-pagination.js.map +1 -0
- package/dist/components/table/table.d.ts +11 -0
- package/dist/components/table/table.d.ts.map +1 -0
- package/dist/components/table/table.js +64 -0
- package/dist/components/table/table.js.map +1 -0
- package/dist/components/table/table.stories.columns.d.ts +24 -0
- package/dist/components/table/table.stories.columns.d.ts.map +1 -0
- package/dist/components/table/table.stories.columns.js +310 -0
- package/dist/components/table/table.stories.columns.js.map +1 -0
- package/dist/components/table/table.stories.components.d.ts +14 -0
- package/dist/components/table/table.stories.components.d.ts.map +1 -0
- package/dist/components/table/table.stories.components.js +107 -0
- package/dist/components/table/table.stories.components.js.map +1 -0
- package/dist/components/table/table.stories.data.d.ts +54 -0
- package/dist/components/table/table.stories.data.d.ts.map +1 -0
- package/dist/components/table/table.stories.data.js +122 -0
- package/dist/components/table/table.stories.data.js.map +1 -0
- package/dist/components/table/table.stories.js +302 -0
- package/dist/components/table/table.stories.js.map +1 -0
- package/dist/styles.css +1 -1
- package/index.css +48 -0
- package/package.json +3 -2
- package/src/components/avatar/avatar.tsx +1 -1
- package/src/components/button-group/button-group.stories.tsx +361 -0
- package/src/components/button-group/button-group.tsx +111 -0
- package/src/components/button-group/index.ts +1 -0
- package/src/components/code-block/code-block-footer.tsx +8 -1
- package/src/components/command/command.stories.tsx +133 -0
- package/src/components/command/command.tsx +265 -0
- package/src/components/command/index.ts +1 -0
- package/src/components/dashboard/components/analytics-content.tsx +102 -0
- package/src/components/dashboard/components/animated-logo.tsx +25 -0
- package/src/components/dashboard/components/complete-setup-button.tsx +30 -0
- package/src/components/dashboard/components/jobs-content.tsx +51 -0
- package/src/components/dashboard/components/mobile-menu.tsx +50 -0
- package/src/components/dashboard/components/organization-selector.tsx +51 -0
- package/src/components/dashboard/components/top-menu.tsx +26 -0
- package/src/components/dashboard/components/topbar-button.tsx +27 -0
- package/src/components/dashboard/components/topbar.tsx +40 -0
- package/src/components/dashboard/components/user-profile.tsx +90 -0
- package/src/components/dashboard/dashboard.stories.tsx +25 -0
- package/src/components/dashboard/dashboard.tsx +61 -0
- package/src/components/dashboard/index.ts +1 -0
- package/src/components/form/form.stories.tsx +5 -0
- package/src/components/index.ts +7 -0
- package/src/components/kbd/index.ts +1 -0
- package/src/components/kbd/kbd.stories.tsx +64 -0
- package/src/components/kbd/kbd.tsx +32 -0
- package/src/components/search/index.ts +28 -0
- package/src/components/search/search-context.tsx +78 -0
- package/src/components/search/search-inline.tsx +107 -0
- package/src/components/search/search-modal.tsx +198 -0
- package/src/components/search/search-trigger.tsx +47 -0
- package/src/components/search/search-variants.ts +88 -0
- package/src/components/search/search.stories.tsx +392 -0
- package/src/components/search/search.tsx +47 -0
- package/src/components/select/index.ts +1 -0
- package/src/components/select/select.stories.tsx +207 -0
- package/src/components/select/select.tsx +220 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/skeleton.stories.tsx +178 -0
- package/src/components/skeleton/skeleton.tsx +14 -0
- package/src/components/table/data-table.tsx +254 -0
- package/src/components/table/index.ts +5 -0
- package/src/components/table/table-column-header.tsx +141 -0
- package/src/components/table/table-pagination.tsx +161 -0
- package/src/components/table/table.stories.columns.tsx +198 -0
- package/src/components/table/table.stories.components.tsx +104 -0
- package/src/components/table/table.stories.data.ts +117 -0
- package/src/components/table/table.stories.tsx +256 -0
- package/src/components/table/table.tsx +95 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {Avatar} from 'components/avatar';
|
|
2
|
+
import {UserBadge} from 'components/badge';
|
|
3
|
+
import {Button} from 'components/button';
|
|
4
|
+
import {
|
|
5
|
+
DropdownMenu,
|
|
6
|
+
DropdownMenuContent,
|
|
7
|
+
DropdownMenuItem,
|
|
8
|
+
DropdownMenuSeparator,
|
|
9
|
+
DropdownMenuTrigger,
|
|
10
|
+
} from 'components/dropdown-menu';
|
|
11
|
+
import {ShinyText} from 'components/shiny-text';
|
|
12
|
+
|
|
13
|
+
function UsageGauge({used, total}: {used: number; total: number}) {
|
|
14
|
+
const percentage = total <= 0 ? 0 : Math.min((used / total) * 100, 100);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="flex h-8 w-full rounded-full bg-tag-neutral-bg overflow-hidden">
|
|
18
|
+
<div className="h-full bg-tag-success-icon rounded-l-2" style={{width: `${percentage}%`}} />
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function UserProfile() {
|
|
24
|
+
const userName = 'Thierry Abalea';
|
|
25
|
+
const userEmail = 'thierryabalea@acme.com';
|
|
26
|
+
const creditsUsed = 3213;
|
|
27
|
+
const creditsTotal = 6000;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<DropdownMenu>
|
|
31
|
+
<DropdownMenuTrigger asChild>
|
|
32
|
+
<div className="flex items-center justify-center h-40 px-8 cursor-pointer border-l border-border-neutral-strong">
|
|
33
|
+
<Avatar className="size-24 md:hidden" content="image" fallback="TA" />
|
|
34
|
+
<UserBadge name={userName} className="hidden md:inline-flex" />
|
|
35
|
+
</div>
|
|
36
|
+
</DropdownMenuTrigger>
|
|
37
|
+
<DropdownMenuContent align="end" className="w-220">
|
|
38
|
+
<div className="flex items-center gap-12 px-8 py-4">
|
|
39
|
+
<Avatar className="size-28 shrink-0" content="image" fallback="TA" />
|
|
40
|
+
<div className="flex flex-col min-w-0">
|
|
41
|
+
<span className="text-sm font-medium leading-20 text-foreground-neutral-base truncate">
|
|
42
|
+
{userName}
|
|
43
|
+
</span>
|
|
44
|
+
<span className="text-xs leading-20 text-foreground-neutral-subtle truncate">
|
|
45
|
+
{userEmail}
|
|
46
|
+
</span>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<DropdownMenuSeparator />
|
|
51
|
+
|
|
52
|
+
<DropdownMenuItem icon="sparkling2Fill">Getting started</DropdownMenuItem>
|
|
53
|
+
<DropdownMenuItem icon="userLine">Profile settings</DropdownMenuItem>
|
|
54
|
+
|
|
55
|
+
<DropdownMenuSeparator />
|
|
56
|
+
|
|
57
|
+
<div className="flex flex-col gap-8 px-8 py-4">
|
|
58
|
+
<div className="flex items-center justify-between gap-8">
|
|
59
|
+
<div className="flex items-center gap-8">
|
|
60
|
+
<Avatar className="size-12" content="logo" logoName="stripe" radius="rounded" />
|
|
61
|
+
<span className="text-sm font-medium leading-20 text-foreground-neutral-subtle">
|
|
62
|
+
Usage
|
|
63
|
+
</span>
|
|
64
|
+
</div>
|
|
65
|
+
<span className="text-sm font-medium leading-20 text-foreground-neutral-subtle">
|
|
66
|
+
{creditsUsed} / {creditsTotal}
|
|
67
|
+
</span>
|
|
68
|
+
</div>
|
|
69
|
+
<UsageGauge used={creditsUsed} total={creditsTotal} />
|
|
70
|
+
<span className="text-xs leading-20 text-foreground-neutral-subtle">
|
|
71
|
+
{creditsTotal} free credits every month.
|
|
72
|
+
</span>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div className="px-8 pb-12">
|
|
76
|
+
<Button type="button" className="w-full">
|
|
77
|
+
<ShinyText
|
|
78
|
+
text="Upgrade Plan"
|
|
79
|
+
className="flex-1 text-sm font-medium leading-20 text-foreground-neutral-base truncate text-center"
|
|
80
|
+
/>
|
|
81
|
+
</Button>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<DropdownMenuSeparator />
|
|
85
|
+
|
|
86
|
+
<DropdownMenuItem icon="logoutCircleLine">Log out</DropdownMenuItem>
|
|
87
|
+
</DropdownMenuContent>
|
|
88
|
+
</DropdownMenu>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
2
|
+
import {Dashboard} from './dashboard';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Dashboard/Example',
|
|
6
|
+
tags: ['autodocs'],
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: 'fullscreen',
|
|
9
|
+
viewport: {
|
|
10
|
+
defaultViewport: 'extraLarge',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
} satisfies Meta;
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
|
|
17
|
+
type Story = StoryObj<typeof meta>;
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {
|
|
20
|
+
render: () => (
|
|
21
|
+
<div className="h-screen w-full">
|
|
22
|
+
<Dashboard />
|
|
23
|
+
</div>
|
|
24
|
+
),
|
|
25
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {useMotionValueEvent, useScroll} from 'framer-motion';
|
|
2
|
+
import {useCallback, useRef, useState} from 'react';
|
|
3
|
+
import {AnalyticsContent} from './components/analytics-content';
|
|
4
|
+
import {AnimatedLogo} from './components/animated-logo';
|
|
5
|
+
import {JobsContent} from './components/jobs-content';
|
|
6
|
+
import {TopMenu} from './components/top-menu';
|
|
7
|
+
import {Topbar} from './components/topbar';
|
|
8
|
+
|
|
9
|
+
const LOGO_HEIGHT = 48;
|
|
10
|
+
const TOPBAR_HEIGHT = 41;
|
|
11
|
+
|
|
12
|
+
export function Dashboard() {
|
|
13
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
14
|
+
const topbarRef = useRef<HTMLDivElement>(null);
|
|
15
|
+
const [scrollProgress, setScrollProgress] = useState(0);
|
|
16
|
+
const [activeTab, setActiveTab] = useState('analytics');
|
|
17
|
+
|
|
18
|
+
const {scrollY} = useScroll({
|
|
19
|
+
container: containerRef,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
useMotionValueEvent(scrollY, 'change', (latest) => {
|
|
23
|
+
const progress = Math.min(latest / TOPBAR_HEIGHT, 1);
|
|
24
|
+
setScrollProgress(progress);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const handleTabChange = useCallback((tab: string) => {
|
|
28
|
+
setActiveTab(tab);
|
|
29
|
+
if (containerRef.current) {
|
|
30
|
+
containerRef.current.scrollTo({top: 0, behavior: 'instant'});
|
|
31
|
+
}
|
|
32
|
+
setScrollProgress(0);
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
const isTopbarHidden = scrollProgress >= 1;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="flex flex-col w-full h-full">
|
|
39
|
+
<AnimatedLogo scrollProgress={scrollProgress} />
|
|
40
|
+
|
|
41
|
+
<div ref={containerRef} className="flex flex-col w-full h-full overflow-auto">
|
|
42
|
+
<div ref={topbarRef}>
|
|
43
|
+
<Topbar hideLogo={isTopbarHidden} />
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div className="sticky top-0 z-40 border-b border-border-neutral-strong bg-background-neutral-base">
|
|
47
|
+
<div
|
|
48
|
+
style={{
|
|
49
|
+
paddingLeft: `${(1 - (1 - scrollProgress) ** 3) * (LOGO_HEIGHT - 8)}px`,
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<TopMenu activeTab={activeTab} onTabChange={handleTabChange} />
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{activeTab === 'analytics' && <AnalyticsContent />}
|
|
57
|
+
{activeTab === 'jobs' && <JobsContent />}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './dashboard';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {argosScreenshot} from '@argos-ci/storybook/vitest';
|
|
1
2
|
import {zodResolver} from '@hookform/resolvers/zod';
|
|
2
3
|
import type {Meta, StoryObj} from '@storybook/react';
|
|
3
4
|
import {Button} from 'components/button';
|
|
@@ -86,6 +87,10 @@ function BasicFormExample() {
|
|
|
86
87
|
|
|
87
88
|
export const Basic: Story = {
|
|
88
89
|
render: () => <BasicFormExample />,
|
|
90
|
+
play: async (context) => {
|
|
91
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
92
|
+
await argosScreenshot(context, 'Form Basic');
|
|
93
|
+
},
|
|
89
94
|
};
|
|
90
95
|
|
|
91
96
|
const componentsFormSchema = z.object({
|
package/src/components/index.ts
CHANGED
|
@@ -2,9 +2,11 @@ export * from './alert';
|
|
|
2
2
|
export * from './avatar';
|
|
3
3
|
export * from './badge';
|
|
4
4
|
export * from './button';
|
|
5
|
+
export * from './button-group';
|
|
5
6
|
export * from './calendar';
|
|
6
7
|
export * from './checkbox';
|
|
7
8
|
export * from './code-block';
|
|
9
|
+
export * from './command';
|
|
8
10
|
export * from './confetti';
|
|
9
11
|
export * from './date-picker';
|
|
10
12
|
export * from './date-time-range-picker';
|
|
@@ -16,11 +18,16 @@ export * from './icon';
|
|
|
16
18
|
export * from './inline-tips';
|
|
17
19
|
export * from './input';
|
|
18
20
|
export * from './item';
|
|
21
|
+
export * from './kbd';
|
|
19
22
|
export * from './label';
|
|
20
23
|
export * from './modal';
|
|
21
24
|
export * from './moving-border';
|
|
22
25
|
export * from './popover';
|
|
26
|
+
export * from './search';
|
|
27
|
+
export * from './select';
|
|
23
28
|
export * from './shiny-text';
|
|
29
|
+
export * from './skeleton';
|
|
30
|
+
export * from './table';
|
|
24
31
|
export * from './tabs';
|
|
25
32
|
export * from './textarea';
|
|
26
33
|
export * from './theme';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './kbd';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
2
|
+
import {Kbd, KbdGroup} from './kbd';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Components/Kbd',
|
|
6
|
+
component: Kbd,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
} satisfies Meta<typeof Kbd>;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<div className="flex flex-wrap gap-8">
|
|
17
|
+
<Kbd>Ctrl</Kbd>
|
|
18
|
+
<Kbd>Alt</Kbd>
|
|
19
|
+
<Kbd>Shift</Kbd>
|
|
20
|
+
<Kbd>⌘</Kbd>
|
|
21
|
+
<Kbd>⌥</Kbd>
|
|
22
|
+
<Kbd>⇧</Kbd>
|
|
23
|
+
</div>
|
|
24
|
+
),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const KeyCombination: Story = {
|
|
28
|
+
render: () => (
|
|
29
|
+
<div className="flex flex-wrap items-center gap-12">
|
|
30
|
+
<Kbd>⌘K</Kbd>
|
|
31
|
+
<KbdGroup>
|
|
32
|
+
<Kbd>Ctrl</Kbd>
|
|
33
|
+
<Kbd>Shift</Kbd>
|
|
34
|
+
<Kbd>P</Kbd>
|
|
35
|
+
</KbdGroup>
|
|
36
|
+
<KbdGroup>
|
|
37
|
+
<Kbd>Alt</Kbd>
|
|
38
|
+
<Kbd>Enter</Kbd>
|
|
39
|
+
</KbdGroup>
|
|
40
|
+
</div>
|
|
41
|
+
),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const InMenu: Story = {
|
|
45
|
+
render: () => (
|
|
46
|
+
<div className="max-w-400 rounded-10 border border-border-neutral-base p-8">
|
|
47
|
+
<div className="flex flex-col gap-4">
|
|
48
|
+
<div className="flex items-center justify-between p-8 rounded-6 hover:bg-background-components-hover transition-colors">
|
|
49
|
+
<span className="text-sm text-foreground-neutral-subtle">Copy</span>
|
|
50
|
+
<Kbd>⌘C</Kbd>
|
|
51
|
+
</div>
|
|
52
|
+
<div className="flex items-center justify-between p-8 rounded-6 hover:bg-background-components-hover transition-colors">
|
|
53
|
+
<span className="text-sm text-foreground-neutral-subtle">Paste</span>
|
|
54
|
+
<Kbd>⌘V</Kbd>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="h-px bg-border-neutral-base my-4" />
|
|
57
|
+
<div className="flex items-center justify-between p-8 rounded-6 hover:bg-background-components-hover transition-colors">
|
|
58
|
+
<span className="text-sm text-foreground-neutral-subtle">Command Palette</span>
|
|
59
|
+
<Kbd>⌘K</Kbd>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
),
|
|
64
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type {ComponentProps} from 'react';
|
|
2
|
+
import {cn} from 'utils/cn';
|
|
3
|
+
|
|
4
|
+
type KbdProps = ComponentProps<'kbd'>;
|
|
5
|
+
|
|
6
|
+
export function Kbd({className, ...props}: KbdProps) {
|
|
7
|
+
return (
|
|
8
|
+
<kbd
|
|
9
|
+
data-slot="kbd"
|
|
10
|
+
className={cn(
|
|
11
|
+
'pointer-events-none inline-flex h-20 w-fit min-w-20 items-center justify-center gap-1 rounded-4 px-4 font-display text-xs font-medium select-none',
|
|
12
|
+
'bg-background-components-base text-foreground-neutral-subtle border border-border-neutral-base shadow-button-neutral',
|
|
13
|
+
'[&_svg:not([class*="size-"])]:size-12',
|
|
14
|
+
'in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10',
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type KbdGroupProps = ComponentProps<'div'>;
|
|
23
|
+
|
|
24
|
+
export function KbdGroup({className, ...props}: KbdGroupProps) {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
data-slot="kbd-group"
|
|
28
|
+
className={cn('inline-flex items-center gap-4', className)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export {Search, type SearchProps} from './search';
|
|
2
|
+
export {useSearchContext} from './search-context';
|
|
3
|
+
export {SearchInline, type SearchInlineProps} from './search-inline';
|
|
4
|
+
export {
|
|
5
|
+
SearchContent,
|
|
6
|
+
type SearchContentProps,
|
|
7
|
+
SearchEmpty,
|
|
8
|
+
type SearchEmptyProps,
|
|
9
|
+
SearchFooter,
|
|
10
|
+
type SearchFooterProps,
|
|
11
|
+
SearchGroup,
|
|
12
|
+
type SearchGroupProps,
|
|
13
|
+
SearchInput,
|
|
14
|
+
type SearchInputProps,
|
|
15
|
+
SearchItem,
|
|
16
|
+
type SearchItemProps,
|
|
17
|
+
SearchList,
|
|
18
|
+
type SearchListProps,
|
|
19
|
+
SearchSeparator,
|
|
20
|
+
type SearchSeparatorProps,
|
|
21
|
+
} from './search-modal';
|
|
22
|
+
export {SearchTrigger, type SearchTriggerProps} from './search-trigger';
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
searchDefaultTransition,
|
|
26
|
+
searchInputVariants,
|
|
27
|
+
searchTriggerVariants,
|
|
28
|
+
} from './search-variants';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {createContext, useCallback, useContext, useEffect, useState} from 'react';
|
|
2
|
+
import {SHORTCUT_KEY_REGEX} from './search-variants';
|
|
3
|
+
|
|
4
|
+
export type SearchContextValue = {
|
|
5
|
+
open: boolean;
|
|
6
|
+
setOpen: (open: boolean) => void;
|
|
7
|
+
searchValue: string;
|
|
8
|
+
setSearchValue: (value: string) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const SearchContext = createContext<SearchContextValue | null>(null);
|
|
12
|
+
|
|
13
|
+
export function useSearchContext() {
|
|
14
|
+
const context = useContext(SearchContext);
|
|
15
|
+
if (!context) {
|
|
16
|
+
throw new Error('Search components must be used within a Search component');
|
|
17
|
+
}
|
|
18
|
+
return context;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useControllableState<T>(
|
|
22
|
+
controlledValue: T | undefined,
|
|
23
|
+
defaultValue: T,
|
|
24
|
+
onChange?: (value: T) => void,
|
|
25
|
+
): [T, (value: T) => void] {
|
|
26
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
27
|
+
const isControlled = controlledValue !== undefined;
|
|
28
|
+
const value = isControlled ? controlledValue : internalValue;
|
|
29
|
+
|
|
30
|
+
const setValue = useCallback(
|
|
31
|
+
(newValue: T) => {
|
|
32
|
+
if (!isControlled) {
|
|
33
|
+
setInternalValue(newValue);
|
|
34
|
+
}
|
|
35
|
+
onChange?.(newValue);
|
|
36
|
+
},
|
|
37
|
+
[isControlled, onChange],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return [value, setValue];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useKeyboardShortcut(shortcutKey: string | undefined, onTrigger: () => void) {
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!shortcutKey) return;
|
|
46
|
+
|
|
47
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
48
|
+
const key = shortcutKey.toLowerCase();
|
|
49
|
+
const isMetaKey = key.startsWith('meta+') || key.startsWith('cmd+') || key.startsWith('⌘');
|
|
50
|
+
const isCtrlKey = key.startsWith('ctrl+');
|
|
51
|
+
const targetKey = key.replace(SHORTCUT_KEY_REGEX, '');
|
|
52
|
+
|
|
53
|
+
const shouldTrigger =
|
|
54
|
+
(isMetaKey && e.metaKey && e.key.toLowerCase() === targetKey) ||
|
|
55
|
+
(isCtrlKey && e.ctrlKey && e.key.toLowerCase() === targetKey) ||
|
|
56
|
+
(!isMetaKey && !isCtrlKey && e.key.toLowerCase() === targetKey && !e.metaKey && !e.ctrlKey);
|
|
57
|
+
|
|
58
|
+
if (!shouldTrigger) return;
|
|
59
|
+
|
|
60
|
+
if (!isMetaKey && !isCtrlKey) {
|
|
61
|
+
const target = e.target as HTMLElement;
|
|
62
|
+
if (
|
|
63
|
+
target.tagName === 'INPUT' ||
|
|
64
|
+
target.tagName === 'TEXTAREA' ||
|
|
65
|
+
target.isContentEditable
|
|
66
|
+
) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
onTrigger();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
76
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
77
|
+
}, [shortcutKey, onTrigger]);
|
|
78
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type {VariantProps} from 'class-variance-authority';
|
|
2
|
+
import type {ComponentProps} from 'react';
|
|
3
|
+
import {useCallback, useRef, useState} from 'react';
|
|
4
|
+
import {cn} from 'utils/cn';
|
|
5
|
+
import {Icon} from '../icon';
|
|
6
|
+
import {searchInputVariants} from './search-variants';
|
|
7
|
+
|
|
8
|
+
export type SearchInlineProps = Omit<ComponentProps<'input'>, 'size'> &
|
|
9
|
+
VariantProps<typeof searchInputVariants> & {
|
|
10
|
+
showClearButton?: boolean;
|
|
11
|
+
onClear?: () => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function SearchInline({
|
|
15
|
+
className,
|
|
16
|
+
variant,
|
|
17
|
+
size,
|
|
18
|
+
radius,
|
|
19
|
+
value,
|
|
20
|
+
onChange,
|
|
21
|
+
onClear,
|
|
22
|
+
showClearButton = true,
|
|
23
|
+
...props
|
|
24
|
+
}: SearchInlineProps) {
|
|
25
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
26
|
+
const [internalValue, setInternalValue] = useState('');
|
|
27
|
+
const isControlled = value !== undefined;
|
|
28
|
+
const inputValue = isControlled ? value : internalValue;
|
|
29
|
+
const hasValue = Boolean(inputValue);
|
|
30
|
+
const isSmall = size === 'small';
|
|
31
|
+
|
|
32
|
+
const handleChange = useCallback(
|
|
33
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
34
|
+
if (!isControlled) {
|
|
35
|
+
setInternalValue(e.target.value);
|
|
36
|
+
}
|
|
37
|
+
onChange?.(e);
|
|
38
|
+
},
|
|
39
|
+
[isControlled, onChange],
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const handleClear = useCallback(() => {
|
|
43
|
+
if (!isControlled) {
|
|
44
|
+
setInternalValue('');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (onChange && inputRef.current) {
|
|
48
|
+
inputRef.current.value = '';
|
|
49
|
+
const syntheticEvent = {
|
|
50
|
+
target: inputRef.current,
|
|
51
|
+
currentTarget: inputRef.current,
|
|
52
|
+
} as React.ChangeEvent<HTMLInputElement>;
|
|
53
|
+
onChange(syntheticEvent);
|
|
54
|
+
}
|
|
55
|
+
onClear?.();
|
|
56
|
+
inputRef.current?.focus();
|
|
57
|
+
}, [isControlled, onChange, onClear]);
|
|
58
|
+
|
|
59
|
+
const handleKeyDown = useCallback(
|
|
60
|
+
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
61
|
+
if (e.key === 'Escape' && hasValue) {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
handleClear();
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
[hasValue, handleClear],
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
data-slot="search-inline"
|
|
72
|
+
className={cn(searchInputVariants({variant, size, radius}), className)}
|
|
73
|
+
>
|
|
74
|
+
<Icon
|
|
75
|
+
name="searchLine"
|
|
76
|
+
className={cn('shrink-0 text-foreground-neutral-muted', isSmall ? 'size-14' : 'size-16')}
|
|
77
|
+
/>
|
|
78
|
+
<input
|
|
79
|
+
ref={inputRef}
|
|
80
|
+
type="text"
|
|
81
|
+
value={inputValue}
|
|
82
|
+
onChange={handleChange}
|
|
83
|
+
onKeyDown={handleKeyDown}
|
|
84
|
+
className={cn(
|
|
85
|
+
'flex-1 bg-transparent outline-none min-w-0',
|
|
86
|
+
'text-foreground-neutral-base',
|
|
87
|
+
'placeholder:text-foreground-neutral-muted',
|
|
88
|
+
'disabled:cursor-not-allowed disabled:text-foreground-neutral-disabled',
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
{showClearButton && hasValue && (
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
onClick={handleClear}
|
|
96
|
+
className={cn(
|
|
97
|
+
'shrink-0 cursor-pointer rounded-4 p-2 -mx-2',
|
|
98
|
+
'text-foreground-neutral-muted hover:text-foreground-neutral-subtle transition-colors',
|
|
99
|
+
)}
|
|
100
|
+
aria-label="Clear search"
|
|
101
|
+
>
|
|
102
|
+
<Icon name="closeLine" className="size-16" />
|
|
103
|
+
</button>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|