@shipfox/react-ui 0.4.0 → 0.5.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/main.ts +20 -10
- package/.storybook/vitest.setup.ts +4 -0
- package/.turbo/turbo-build.log +2 -2
- package/.turbo/turbo-check.log +2 -2
- package/.turbo/turbo-type.log +1 -1
- package/CHANGELOG.md +7 -0
- package/README.md +40 -1
- package/argos.config.ts +33 -0
- package/dist/components/button/button-link.d.ts +14 -0
- package/dist/components/button/button-link.d.ts.map +1 -0
- package/dist/components/button/button-link.js +63 -0
- package/dist/components/button/button-link.js.map +1 -0
- package/dist/components/button/button-link.stories.js +127 -0
- package/dist/components/button/button-link.stories.js.map +1 -0
- package/dist/components/button/button.d.ts +1 -1
- package/dist/components/button/button.d.ts.map +1 -1
- package/dist/components/button/button.js +7 -6
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/button/button.stories.js +1 -13
- package/dist/components/button/button.stories.js.map +1 -1
- package/dist/components/button/icon-button.d.ts +14 -0
- package/dist/components/button/icon-button.d.ts.map +1 -0
- package/dist/components/button/icon-button.js +53 -0
- package/dist/components/button/icon-button.js.map +1 -0
- package/dist/components/button/icon-button.stories.js +254 -0
- package/dist/components/button/icon-button.stories.js.map +1 -0
- package/dist/components/button/index.d.ts +2 -0
- package/dist/components/button/index.d.ts.map +1 -1
- package/dist/components/button/index.js +2 -0
- package/dist/components/button/index.js.map +1 -1
- package/dist/components/code-block/code-content.d.ts.map +1 -1
- package/dist/components/code-block/code-content.js +2 -2
- package/dist/components/code-block/code-content.js.map +1 -1
- package/dist/components/icon/icon.d.ts +2 -0
- package/dist/components/icon/icon.d.ts.map +1 -1
- package/dist/components/icon/icon.js +4 -2
- package/dist/components/icon/icon.js.map +1 -1
- package/dist/onboarding/sign-in.stories.js +93 -0
- package/dist/onboarding/sign-in.stories.js.map +1 -0
- package/index.css +29 -3
- package/package.json +9 -1
- package/src/components/button/button-link.stories.tsx +86 -0
- package/src/components/button/button-link.tsx +76 -0
- package/src/components/button/button.stories.tsx +1 -7
- package/src/components/button/button.tsx +8 -6
- package/src/components/button/icon-button.stories.tsx +182 -0
- package/src/components/button/icon-button.tsx +69 -0
- package/src/components/button/index.ts +2 -0
- package/src/components/code-block/code-content.tsx +5 -2
- package/src/components/icon/icon.tsx +4 -0
- package/src/onboarding/sign-in.stories.tsx +73 -0
- package/vitest.config.ts +30 -3
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/react-ui",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -43,6 +43,9 @@
|
|
|
43
43
|
"react-hook-form": "^7.52.1"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
+
"@argos-ci/cli": "^3.2.1",
|
|
47
|
+
"@argos-ci/storybook": "^5.2.1",
|
|
48
|
+
"@storybook/addon-vitest": "^9.1.16",
|
|
46
49
|
"@storybook/builder-vite": "^9.1.8",
|
|
47
50
|
"@storybook/react": "^9.1.8",
|
|
48
51
|
"@storybook/react-vite": "^9.1.8",
|
|
@@ -59,6 +62,10 @@
|
|
|
59
62
|
"storybook-addon-pseudo-states": "^9.1.8",
|
|
60
63
|
"tailwindcss": "^4.1.13",
|
|
61
64
|
"tw-animate-css": "^1.4.0",
|
|
65
|
+
"vitest": "^4.0.8",
|
|
66
|
+
"playwright": "^1.56.1",
|
|
67
|
+
"@vitest/browser-playwright": "^4.0.8",
|
|
68
|
+
"@vitest/coverage-v8": "^4.0.8",
|
|
62
69
|
"@shipfox/biome": "1.3.1",
|
|
63
70
|
"@shipfox/swc": "1.2.1",
|
|
64
71
|
"@shipfox/ts-config": "1.3.5",
|
|
@@ -71,6 +78,7 @@
|
|
|
71
78
|
"check": "biome-check",
|
|
72
79
|
"storybook": "storybook dev",
|
|
73
80
|
"storybook:build": "storybook build -o storybook-static",
|
|
81
|
+
"argos": "argos upload --build-name react-ui storybook-static",
|
|
74
82
|
"test": "vitest-run",
|
|
75
83
|
"type": "tsc-emit"
|
|
76
84
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
2
|
+
import {ButtonLink} from './button-link';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Components/Button/ButtonLink',
|
|
6
|
+
component: ButtonLink,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
argTypes: {
|
|
9
|
+
variant: {
|
|
10
|
+
control: 'select',
|
|
11
|
+
options: ['base', 'interactive', 'muted', 'subtle'],
|
|
12
|
+
},
|
|
13
|
+
size: {
|
|
14
|
+
control: 'select',
|
|
15
|
+
options: ['xs', 'sm', 'md', 'xl'],
|
|
16
|
+
},
|
|
17
|
+
underline: {control: 'boolean'},
|
|
18
|
+
asChild: {control: 'boolean'},
|
|
19
|
+
},
|
|
20
|
+
args: {
|
|
21
|
+
children: 'Label',
|
|
22
|
+
variant: 'base',
|
|
23
|
+
size: 'sm',
|
|
24
|
+
underline: false,
|
|
25
|
+
href: '#',
|
|
26
|
+
},
|
|
27
|
+
} satisfies Meta<typeof ButtonLink>;
|
|
28
|
+
|
|
29
|
+
export default meta;
|
|
30
|
+
type Story = StoryObj<typeof meta>;
|
|
31
|
+
|
|
32
|
+
export const Default: Story = {};
|
|
33
|
+
|
|
34
|
+
export const Variants: Story = {
|
|
35
|
+
render: (args) => (
|
|
36
|
+
<div className="flex gap-16 items-center">
|
|
37
|
+
<ButtonLink {...args} variant="base">
|
|
38
|
+
Base
|
|
39
|
+
</ButtonLink>
|
|
40
|
+
<ButtonLink {...args} variant="interactive">
|
|
41
|
+
Interactive
|
|
42
|
+
</ButtonLink>
|
|
43
|
+
<ButtonLink {...args} variant="muted">
|
|
44
|
+
Muted
|
|
45
|
+
</ButtonLink>
|
|
46
|
+
<ButtonLink {...args} variant="subtle">
|
|
47
|
+
Subtle
|
|
48
|
+
</ButtonLink>
|
|
49
|
+
</div>
|
|
50
|
+
),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const WithUnderline: Story = {
|
|
54
|
+
render: (args) => (
|
|
55
|
+
<div className="flex gap-16 items-center">
|
|
56
|
+
<ButtonLink {...args} variant="base" underline>
|
|
57
|
+
Base
|
|
58
|
+
</ButtonLink>
|
|
59
|
+
<ButtonLink {...args} variant="interactive" underline>
|
|
60
|
+
Interactive
|
|
61
|
+
</ButtonLink>
|
|
62
|
+
<ButtonLink {...args} variant="muted" underline>
|
|
63
|
+
Muted
|
|
64
|
+
</ButtonLink>
|
|
65
|
+
<ButtonLink {...args} variant="subtle" underline>
|
|
66
|
+
Subtle
|
|
67
|
+
</ButtonLink>
|
|
68
|
+
</div>
|
|
69
|
+
),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const WithIcons: Story = {
|
|
73
|
+
render: (args) => (
|
|
74
|
+
<div className="flex gap-16 items-center">
|
|
75
|
+
<ButtonLink {...args} iconLeft="addLine">
|
|
76
|
+
Icon Left
|
|
77
|
+
</ButtonLink>
|
|
78
|
+
<ButtonLink {...args} iconRight="chevronRight">
|
|
79
|
+
Icon Right
|
|
80
|
+
</ButtonLink>
|
|
81
|
+
<ButtonLink {...args} iconLeft="addLine" iconRight="chevronRight">
|
|
82
|
+
Both Icons
|
|
83
|
+
</ButtonLink>
|
|
84
|
+
</div>
|
|
85
|
+
),
|
|
86
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {Slot} from '@radix-ui/react-slot';
|
|
2
|
+
import {cva, type VariantProps} from 'class-variance-authority';
|
|
3
|
+
import {Icon, type IconName} from 'components/icon';
|
|
4
|
+
import type {ComponentProps} from 'react';
|
|
5
|
+
import {cn} from 'utils/cn';
|
|
6
|
+
|
|
7
|
+
export const buttonLinkVariants = cva(
|
|
8
|
+
'inline-flex items-center justify-center gap-4 whitespace-nowrap transition-colors disabled:pointer-events-none outline-none font-medium',
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
base: 'text-foreground-neutral-base hover:text-foreground-neutral-base focus-visible:text-foreground-neutral-base disabled:text-foreground-neutral-disabled',
|
|
13
|
+
interactive:
|
|
14
|
+
'text-foreground-highlight-interactive hover:text-foreground-highlight-interactive-hover focus-visible:text-foreground-highlight-interactive disabled:text-foreground-neutral-disabled',
|
|
15
|
+
muted:
|
|
16
|
+
'text-foreground-neutral-muted hover:text-foreground-neutral-base focus-visible:text-foreground-neutral-base disabled:text-foreground-neutral-disabled',
|
|
17
|
+
subtle:
|
|
18
|
+
'text-foreground-neutral-subtle hover:text-foreground-neutral-base focus-visible:text-foreground-neutral-base disabled:text-foreground-neutral-disabled',
|
|
19
|
+
},
|
|
20
|
+
size: {
|
|
21
|
+
xs: 'text-xs',
|
|
22
|
+
sm: 'text-sm',
|
|
23
|
+
md: 'text-md',
|
|
24
|
+
xl: 'text-xl',
|
|
25
|
+
},
|
|
26
|
+
underline: {
|
|
27
|
+
true: 'underline decoration-solid [text-underline-position:from-font]',
|
|
28
|
+
false: '',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
defaultVariants: {
|
|
32
|
+
variant: 'base',
|
|
33
|
+
size: 'sm',
|
|
34
|
+
underline: false,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const iconSizeMap = {
|
|
40
|
+
xs: 14,
|
|
41
|
+
sm: 14,
|
|
42
|
+
md: 16,
|
|
43
|
+
xl: 20,
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
export function ButtonLink({
|
|
47
|
+
className,
|
|
48
|
+
variant,
|
|
49
|
+
size = 'sm',
|
|
50
|
+
underline,
|
|
51
|
+
asChild = false,
|
|
52
|
+
children,
|
|
53
|
+
iconLeft,
|
|
54
|
+
iconRight,
|
|
55
|
+
...props
|
|
56
|
+
}: ComponentProps<'a'> &
|
|
57
|
+
VariantProps<typeof buttonLinkVariants> & {
|
|
58
|
+
asChild?: boolean;
|
|
59
|
+
iconLeft?: IconName;
|
|
60
|
+
iconRight?: IconName;
|
|
61
|
+
}) {
|
|
62
|
+
const Comp = asChild ? Slot : 'a';
|
|
63
|
+
const iconSize = iconSizeMap[size as keyof typeof iconSizeMap];
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Comp
|
|
67
|
+
data-slot="button-link"
|
|
68
|
+
className={cn(buttonLinkVariants({variant, size, underline, className}))}
|
|
69
|
+
{...props}
|
|
70
|
+
>
|
|
71
|
+
{iconLeft && <Icon name={iconLeft} size={iconSize} />}
|
|
72
|
+
{children}
|
|
73
|
+
{iconRight && <Icon name={iconRight} size={iconSize} />}
|
|
74
|
+
</Comp>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -6,6 +6,7 @@ const variantOptions = [
|
|
|
6
6
|
'primary',
|
|
7
7
|
'secondary',
|
|
8
8
|
'danger',
|
|
9
|
+
'success',
|
|
9
10
|
'transparent',
|
|
10
11
|
'transparentMuted',
|
|
11
12
|
] as const;
|
|
@@ -48,7 +49,6 @@ export const Variants: Story = {
|
|
|
48
49
|
<th>{size}</th>
|
|
49
50
|
<th>Default</th>
|
|
50
51
|
<th>Hover</th>
|
|
51
|
-
<th>Active</th>
|
|
52
52
|
<th>Focus</th>
|
|
53
53
|
<th>Disabled</th>
|
|
54
54
|
</tr>
|
|
@@ -71,11 +71,6 @@ export const Variants: Story = {
|
|
|
71
71
|
Click me
|
|
72
72
|
</Button>
|
|
73
73
|
</td>
|
|
74
|
-
<td>
|
|
75
|
-
<Button {...args} variant={variant} className="active" size={size}>
|
|
76
|
-
Click me
|
|
77
|
-
</Button>
|
|
78
|
-
</td>
|
|
79
74
|
<td>
|
|
80
75
|
<Button {...args} variant={variant} className="focus" size={size}>
|
|
81
76
|
Click me
|
|
@@ -98,7 +93,6 @@ export const Variants: Story = {
|
|
|
98
93
|
Variants.parameters = {
|
|
99
94
|
pseudo: {
|
|
100
95
|
hover: '.hover',
|
|
101
|
-
active: '.active',
|
|
102
96
|
focusVisible: '.focus',
|
|
103
97
|
},
|
|
104
98
|
};
|
|
@@ -15,18 +15,20 @@ export const buttonVariants = cva(
|
|
|
15
15
|
'bg-background-button-neutral-default text-foreground-neutral-base shadow-button-neutral hover:bg-background-button-neutral-hover active:bg-background-button-neutral-pressed disabled:bg-background-neutral-disabled focus-visible:shadow-button-neutral-focus disabled:text-foreground-neutral-disabled disabled:shadow-none',
|
|
16
16
|
danger:
|
|
17
17
|
'bg-background-button-danger-default text-foreground-neutral-on-color shadow-button-danger hover:bg-background-button-danger-hover active:bg-background-button-danger-pressed focus-visible:shadow-button-danger-focus disabled:bg-background-neutral-disabled disabled:text-foreground-neutral-disabled disabled:shadow-none',
|
|
18
|
+
success:
|
|
19
|
+
'bg-background-button-success-default text-foreground-neutral-on-color shadow-button-success hover:bg-background-button-success-hover active:bg-background-button-success-pressed focus-visible:shadow-button-success-focus disabled:bg-background-neutral-disabled disabled:text-foreground-neutral-disabled disabled:shadow-none',
|
|
18
20
|
transparent:
|
|
19
21
|
'bg-background-button-transparent-default text-foreground-neutral-base hover:bg-background-button-transparent-hover active:bg-background-button-transparent-pressed focus-visible:shadow-button-neutral-focus disabled:text-foreground-neutral-disabled',
|
|
20
22
|
transparentMuted:
|
|
21
23
|
'bg-background-button-transparent-default text-foreground-neutral-muted hover:bg-background-button-transparent-hover active:bg-background-button-transparent-pressed focus-visible:shadow-button-neutral-focus disabled:text-foreground-neutral-disabled',
|
|
22
24
|
},
|
|
23
25
|
size: {
|
|
24
|
-
'2xs': 'px-6 text-xs gap-4',
|
|
25
|
-
xs: 'px-6
|
|
26
|
-
sm: 'px-8
|
|
27
|
-
md: 'px-10
|
|
28
|
-
lg: 'px-12
|
|
29
|
-
xl: 'px-12
|
|
26
|
+
'2xs': 'h-20 px-6 text-xs gap-4',
|
|
27
|
+
xs: 'h-24 px-6 text-xs gap-4',
|
|
28
|
+
sm: 'h-28 px-8 text-sm gap-6',
|
|
29
|
+
md: 'h-32 px-10 text-md gap-8',
|
|
30
|
+
lg: 'h-36 px-12 text-lg gap-8',
|
|
31
|
+
xl: 'h-40 px-12 text-xl gap-10',
|
|
30
32
|
},
|
|
31
33
|
},
|
|
32
34
|
defaultVariants: {
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
2
|
+
import {Code} from 'components/typography';
|
|
3
|
+
import {IconButton} from './icon-button';
|
|
4
|
+
|
|
5
|
+
const variantOptions = ['primary', 'transparent'] as const;
|
|
6
|
+
const sizeOptions = ['2xs', 'xs', 'sm', 'md', 'lg', 'xl'] as const;
|
|
7
|
+
const radiusOptions = ['rounded', 'full'] as const;
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: 'Components/Button/IconButton',
|
|
11
|
+
component: IconButton,
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
argTypes: {
|
|
14
|
+
variant: {
|
|
15
|
+
control: 'select',
|
|
16
|
+
options: variantOptions,
|
|
17
|
+
},
|
|
18
|
+
size: {
|
|
19
|
+
control: 'select',
|
|
20
|
+
options: sizeOptions,
|
|
21
|
+
},
|
|
22
|
+
radius: {
|
|
23
|
+
control: 'select',
|
|
24
|
+
options: radiusOptions,
|
|
25
|
+
},
|
|
26
|
+
muted: {control: 'boolean'},
|
|
27
|
+
asChild: {control: 'boolean'},
|
|
28
|
+
},
|
|
29
|
+
args: {
|
|
30
|
+
icon: 'addLine',
|
|
31
|
+
variant: 'primary',
|
|
32
|
+
size: 'md',
|
|
33
|
+
radius: 'rounded',
|
|
34
|
+
muted: false,
|
|
35
|
+
},
|
|
36
|
+
} satisfies Meta<typeof IconButton>;
|
|
37
|
+
|
|
38
|
+
export default meta;
|
|
39
|
+
type Story = StoryObj<typeof meta>;
|
|
40
|
+
|
|
41
|
+
export const Default: Story = {};
|
|
42
|
+
|
|
43
|
+
export const Variants: Story = {
|
|
44
|
+
render: (args) => (
|
|
45
|
+
<div className="flex flex-col gap-32">
|
|
46
|
+
{sizeOptions.map((size) => (
|
|
47
|
+
<div key={size} className="flex flex-col gap-16">
|
|
48
|
+
<Code variant="label" className="text-foreground-neutral-subtle">
|
|
49
|
+
Size: {size}
|
|
50
|
+
</Code>
|
|
51
|
+
{radiusOptions.map((radius) => (
|
|
52
|
+
<table
|
|
53
|
+
key={radius}
|
|
54
|
+
className="w-fit border-separate border-spacing-x-32 border-spacing-y-16"
|
|
55
|
+
>
|
|
56
|
+
<thead>
|
|
57
|
+
<tr>
|
|
58
|
+
<th>{radius}</th>
|
|
59
|
+
<th>Default</th>
|
|
60
|
+
<th>Hover</th>
|
|
61
|
+
<th>Focus</th>
|
|
62
|
+
<th>Disabled</th>
|
|
63
|
+
</tr>
|
|
64
|
+
</thead>
|
|
65
|
+
<tbody>
|
|
66
|
+
{variantOptions.map((variant) => (
|
|
67
|
+
<tr key={variant}>
|
|
68
|
+
<td>
|
|
69
|
+
<Code variant="label" className="text-foreground-neutral-subtle">
|
|
70
|
+
{variant}
|
|
71
|
+
</Code>
|
|
72
|
+
</td>
|
|
73
|
+
<td>
|
|
74
|
+
<IconButton
|
|
75
|
+
{...args}
|
|
76
|
+
icon="addLine"
|
|
77
|
+
aria-label="Add"
|
|
78
|
+
variant={variant}
|
|
79
|
+
size={size}
|
|
80
|
+
radius={radius}
|
|
81
|
+
/>
|
|
82
|
+
</td>
|
|
83
|
+
<td>
|
|
84
|
+
<IconButton
|
|
85
|
+
{...args}
|
|
86
|
+
icon="addLine"
|
|
87
|
+
aria-label="Add"
|
|
88
|
+
variant={variant}
|
|
89
|
+
className="hover"
|
|
90
|
+
size={size}
|
|
91
|
+
radius={radius}
|
|
92
|
+
/>
|
|
93
|
+
</td>
|
|
94
|
+
<td>
|
|
95
|
+
<IconButton
|
|
96
|
+
{...args}
|
|
97
|
+
icon="addLine"
|
|
98
|
+
aria-label="Add"
|
|
99
|
+
variant={variant}
|
|
100
|
+
className="focus"
|
|
101
|
+
size={size}
|
|
102
|
+
radius={radius}
|
|
103
|
+
/>
|
|
104
|
+
</td>
|
|
105
|
+
<td>
|
|
106
|
+
<IconButton
|
|
107
|
+
{...args}
|
|
108
|
+
icon="addLine"
|
|
109
|
+
aria-label="Add"
|
|
110
|
+
variant={variant}
|
|
111
|
+
disabled
|
|
112
|
+
size={size}
|
|
113
|
+
radius={radius}
|
|
114
|
+
/>
|
|
115
|
+
</td>
|
|
116
|
+
</tr>
|
|
117
|
+
))}
|
|
118
|
+
</tbody>
|
|
119
|
+
</table>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
),
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
Variants.parameters = {
|
|
128
|
+
pseudo: {
|
|
129
|
+
hover: '.hover',
|
|
130
|
+
focusVisible: '.focus',
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const Muted: Story = {
|
|
135
|
+
render: (args) => (
|
|
136
|
+
<div className="flex flex-col gap-16">
|
|
137
|
+
<div className="flex gap-16 items-center">
|
|
138
|
+
<Code variant="label">Normal:</Code>
|
|
139
|
+
<IconButton {...args} icon="addLine" aria-label="Add" />
|
|
140
|
+
<IconButton {...args} icon="addLine" aria-label="Add" variant="transparent" />
|
|
141
|
+
</div>
|
|
142
|
+
<div className="flex gap-16 items-center">
|
|
143
|
+
<Code variant="label">Muted:</Code>
|
|
144
|
+
<IconButton {...args} icon="addLine" aria-label="Add" muted />
|
|
145
|
+
<IconButton {...args} icon="addLine" aria-label="Add" variant="transparent" muted />
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
),
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const Sizes: Story = {
|
|
152
|
+
render: ({children: _children, ...args}) => (
|
|
153
|
+
<div className="flex flex-col gap-16">
|
|
154
|
+
<div className="flex gap-16 items-center">
|
|
155
|
+
<Code variant="label">Rounded:</Code>
|
|
156
|
+
{sizeOptions.map((size) => (
|
|
157
|
+
<IconButton
|
|
158
|
+
{...args}
|
|
159
|
+
key={size}
|
|
160
|
+
icon="addLine"
|
|
161
|
+
aria-label="Add"
|
|
162
|
+
size={size}
|
|
163
|
+
radius="rounded"
|
|
164
|
+
/>
|
|
165
|
+
))}
|
|
166
|
+
</div>
|
|
167
|
+
<div className="flex gap-16 items-center">
|
|
168
|
+
<Code variant="label">Full:</Code>
|
|
169
|
+
{sizeOptions.map((size) => (
|
|
170
|
+
<IconButton
|
|
171
|
+
{...args}
|
|
172
|
+
key={size}
|
|
173
|
+
icon="addLine"
|
|
174
|
+
aria-label="Add"
|
|
175
|
+
size={size}
|
|
176
|
+
radius="full"
|
|
177
|
+
/>
|
|
178
|
+
))}
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
),
|
|
182
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {Slot} from '@radix-ui/react-slot';
|
|
2
|
+
import {cva, type VariantProps} from 'class-variance-authority';
|
|
3
|
+
import {Icon, type IconName} from 'components/icon';
|
|
4
|
+
import type {ComponentProps} from 'react';
|
|
5
|
+
import {cn} from 'utils/cn';
|
|
6
|
+
|
|
7
|
+
export const iconButtonVariants = cva(
|
|
8
|
+
'inline-flex items-center justify-center whitespace-nowrap transition-colors disabled:pointer-events-none shrink-0 outline-none',
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
primary:
|
|
13
|
+
'bg-background-button-inverted-default text-tag-neutral-icon shadow-button-inverted hover:bg-background-button-inverted-hover active:bg-background-button-inverted-pressed focus-visible:shadow-button-inverted-focus disabled:bg-background-neutral-disabled disabled:text-foreground-neutral-disabled disabled:shadow-none',
|
|
14
|
+
transparent:
|
|
15
|
+
'bg-background-button-transparent-default text-tag-neutral-icon hover:bg-background-button-transparent-hover active:bg-background-button-transparent-pressed focus-visible:shadow-button-neutral-focus disabled:text-foreground-neutral-disabled',
|
|
16
|
+
},
|
|
17
|
+
size: {
|
|
18
|
+
'2xs': 'w-20 h-20 text-xs',
|
|
19
|
+
xs: 'w-24 h-24 text-xs',
|
|
20
|
+
sm: 'w-28 h-28 text-sm',
|
|
21
|
+
md: 'w-32 h-32 text-md',
|
|
22
|
+
lg: 'w-36 h-36 text-lg',
|
|
23
|
+
xl: 'w-40 h-40 text-xl',
|
|
24
|
+
},
|
|
25
|
+
radius: {
|
|
26
|
+
rounded: 'rounded-6',
|
|
27
|
+
full: 'rounded-full',
|
|
28
|
+
},
|
|
29
|
+
muted: {
|
|
30
|
+
true: 'opacity-60',
|
|
31
|
+
false: '',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
defaultVariants: {
|
|
35
|
+
variant: 'primary',
|
|
36
|
+
size: 'md',
|
|
37
|
+
radius: 'rounded',
|
|
38
|
+
muted: false,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
export function IconButton({
|
|
44
|
+
className,
|
|
45
|
+
variant,
|
|
46
|
+
size,
|
|
47
|
+
radius,
|
|
48
|
+
muted,
|
|
49
|
+
asChild = false,
|
|
50
|
+
children,
|
|
51
|
+
icon,
|
|
52
|
+
...props
|
|
53
|
+
}: ComponentProps<'button'> &
|
|
54
|
+
VariantProps<typeof iconButtonVariants> & {
|
|
55
|
+
asChild?: boolean;
|
|
56
|
+
icon?: IconName;
|
|
57
|
+
}) {
|
|
58
|
+
const Comp = asChild ? Slot : 'button';
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Comp
|
|
62
|
+
data-slot="icon-button"
|
|
63
|
+
className={cn(iconButtonVariants({variant, size, radius, muted}), className)}
|
|
64
|
+
{...props}
|
|
65
|
+
>
|
|
66
|
+
{icon ? <Icon name={icon} /> : children}
|
|
67
|
+
</Comp>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -49,8 +49,11 @@ export function CodeContent({
|
|
|
49
49
|
'[counter-reset:line] [counter-increment:line_0] [&_.line]:before:content-[counter(line)] [&_.line]:before:inline-block [&_.line]:before:[counter-increment:line] [&_.line]:before:w-16 [&_.line]:before:mr-16 [&_.line]:before:text-xs [&_.line]:before:text-right [&_.line]:before:text-foreground-neutral-subtle [&_.line]:before:font-code [&_.line]:before:select-none',
|
|
50
50
|
)}
|
|
51
51
|
>
|
|
52
|
-
{lines.map((line) => (
|
|
53
|
-
<span
|
|
52
|
+
{lines.map((line, index) => (
|
|
53
|
+
<span
|
|
54
|
+
className="line px-12 w-full relative font-code text-xs leading-20"
|
|
55
|
+
key={`${index}-${line}`}
|
|
56
|
+
>
|
|
54
57
|
{line}
|
|
55
58
|
</span>
|
|
56
59
|
))}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type RemixiconComponentType,
|
|
3
|
+
RiAddLine,
|
|
4
|
+
RiArrowRightSLine,
|
|
3
5
|
RiCheckLine,
|
|
4
6
|
RiCloseLine,
|
|
5
7
|
RiFileCopyLine,
|
|
@@ -56,6 +58,8 @@ const iconsMap = {
|
|
|
56
58
|
money: RiMoneyDollarCircleLine,
|
|
57
59
|
homeSmile: RiHomeSmileFill,
|
|
58
60
|
copy: RiFileCopyLine,
|
|
61
|
+
addLine: RiAddLine,
|
|
62
|
+
chevronRight: RiArrowRightSLine,
|
|
59
63
|
} as const satisfies Record<string, RemixiconComponentType>;
|
|
60
64
|
|
|
61
65
|
export type IconName = keyof typeof iconsMap;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {argosScreenshot} from '@argos-ci/storybook/vitest';
|
|
2
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
3
|
+
import {Avatar} from 'components/avatar';
|
|
4
|
+
import {Button} from 'components/button';
|
|
5
|
+
import {Header, Text} from 'components/typography';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: 'Onboarding/Signin',
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: 'fullscreen',
|
|
11
|
+
},
|
|
12
|
+
} satisfies Meta;
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof meta>;
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
play: async (ctx) => {
|
|
19
|
+
await argosScreenshot(ctx, 'example-screenshot');
|
|
20
|
+
},
|
|
21
|
+
render: () => {
|
|
22
|
+
return (
|
|
23
|
+
<div className="flex min-h-screen items-center justify-center bg-background-subtle-base">
|
|
24
|
+
{/* Background illustration - simplified decorative element */}
|
|
25
|
+
<div className="pointer-events-none absolute left-1/2 top-0 -translate-x-1/2 -translate-y-[120px]">
|
|
26
|
+
<div
|
|
27
|
+
className="h-[332px] w-[800px] opacity-20"
|
|
28
|
+
style={{
|
|
29
|
+
backgroundImage: `radial-gradient(circle, rgba(255, 75, 0, 0.3) 1px, transparent 1px)`,
|
|
30
|
+
backgroundSize: '24px 24px',
|
|
31
|
+
backgroundPosition: '-80px 31px',
|
|
32
|
+
}}
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
{/* Main content */}
|
|
37
|
+
<div className="relative flex w-full max-w-[384px] flex-col items-center gap-32 px-24 pb-80 pt-24">
|
|
38
|
+
{/* Logo and title section */}
|
|
39
|
+
<div className="flex flex-col items-center gap-16">
|
|
40
|
+
<Avatar content="logo" size="xl" radius="rounded" logoName="shipfox" />
|
|
41
|
+
<div className="flex min-w-[128px] flex-col items-center gap-4 text-center">
|
|
42
|
+
<Header
|
|
43
|
+
variant="h1"
|
|
44
|
+
className="text-[28px] font-medium leading-[44px] text-foreground-neutral-base"
|
|
45
|
+
>
|
|
46
|
+
Connect to Shipfox
|
|
47
|
+
</Header>
|
|
48
|
+
<Text
|
|
49
|
+
size="sm"
|
|
50
|
+
className="text-sm font-normal leading-[24px] text-foreground-neutral-subtle"
|
|
51
|
+
>
|
|
52
|
+
Log in to access Shipfox.
|
|
53
|
+
</Text>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
{/* Action buttons */}
|
|
58
|
+
<div className="flex w-full flex-col gap-20">
|
|
59
|
+
<Button variant="primary" size="md" iconLeft="google" className="w-full">
|
|
60
|
+
Continue with Google
|
|
61
|
+
</Button>
|
|
62
|
+
<Button variant="primary" size="md" iconLeft="microsoft" className="w-full">
|
|
63
|
+
Continue with Microsoft
|
|
64
|
+
</Button>
|
|
65
|
+
<Button variant="transparent" size="md" className="w-full">
|
|
66
|
+
Connect with Enterprise SSO
|
|
67
|
+
</Button>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
},
|
|
73
|
+
};
|
package/vitest.config.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import {fileURLToPath} from 'node:url';
|
|
3
|
+
import {argosVitestPlugin} from '@argos-ci/storybook/vitest-plugin';
|
|
1
4
|
import {defineConfig} from '@shipfox/vitest';
|
|
5
|
+
import {storybookTest} from '@storybook/addon-vitest/vitest-plugin';
|
|
2
6
|
import tailwindcss from '@tailwindcss/vite';
|
|
3
7
|
import react from '@vitejs/plugin-react';
|
|
8
|
+
import {playwright} from '@vitest/browser-playwright';
|
|
9
|
+
|
|
10
|
+
const dirname =
|
|
11
|
+
typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
|
|
4
12
|
|
|
5
13
|
// https://vitejs.dev/config/
|
|
6
14
|
export default defineConfig(
|
|
@@ -8,9 +16,28 @@ export default defineConfig(
|
|
|
8
16
|
plugins: [react(), tailwindcss()],
|
|
9
17
|
css: {},
|
|
10
18
|
test: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
projects: [
|
|
20
|
+
{
|
|
21
|
+
extends: true,
|
|
22
|
+
plugins: [
|
|
23
|
+
storybookTest({configDir: path.join(dirname, '.storybook')}),
|
|
24
|
+
argosVitestPlugin({
|
|
25
|
+
uploadToArgos: !!process.env.CI,
|
|
26
|
+
token: process.env.ARGOS_TOKEN,
|
|
27
|
+
}),
|
|
28
|
+
],
|
|
29
|
+
test: {
|
|
30
|
+
name: 'storybook',
|
|
31
|
+
browser: {
|
|
32
|
+
enabled: true,
|
|
33
|
+
headless: true,
|
|
34
|
+
provider: playwright(),
|
|
35
|
+
instances: [{browser: 'chromium'}],
|
|
36
|
+
},
|
|
37
|
+
setupFiles: ['.storybook/vitest.setup.ts'],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
],
|
|
14
41
|
},
|
|
15
42
|
},
|
|
16
43
|
import.meta.url,
|