@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.
Files changed (52) hide show
  1. package/.storybook/main.ts +20 -10
  2. package/.storybook/vitest.setup.ts +4 -0
  3. package/.turbo/turbo-build.log +2 -2
  4. package/.turbo/turbo-check.log +2 -2
  5. package/.turbo/turbo-type.log +1 -1
  6. package/CHANGELOG.md +7 -0
  7. package/README.md +40 -1
  8. package/argos.config.ts +33 -0
  9. package/dist/components/button/button-link.d.ts +14 -0
  10. package/dist/components/button/button-link.d.ts.map +1 -0
  11. package/dist/components/button/button-link.js +63 -0
  12. package/dist/components/button/button-link.js.map +1 -0
  13. package/dist/components/button/button-link.stories.js +127 -0
  14. package/dist/components/button/button-link.stories.js.map +1 -0
  15. package/dist/components/button/button.d.ts +1 -1
  16. package/dist/components/button/button.d.ts.map +1 -1
  17. package/dist/components/button/button.js +7 -6
  18. package/dist/components/button/button.js.map +1 -1
  19. package/dist/components/button/button.stories.js +1 -13
  20. package/dist/components/button/button.stories.js.map +1 -1
  21. package/dist/components/button/icon-button.d.ts +14 -0
  22. package/dist/components/button/icon-button.d.ts.map +1 -0
  23. package/dist/components/button/icon-button.js +53 -0
  24. package/dist/components/button/icon-button.js.map +1 -0
  25. package/dist/components/button/icon-button.stories.js +254 -0
  26. package/dist/components/button/icon-button.stories.js.map +1 -0
  27. package/dist/components/button/index.d.ts +2 -0
  28. package/dist/components/button/index.d.ts.map +1 -1
  29. package/dist/components/button/index.js +2 -0
  30. package/dist/components/button/index.js.map +1 -1
  31. package/dist/components/code-block/code-content.d.ts.map +1 -1
  32. package/dist/components/code-block/code-content.js +2 -2
  33. package/dist/components/code-block/code-content.js.map +1 -1
  34. package/dist/components/icon/icon.d.ts +2 -0
  35. package/dist/components/icon/icon.d.ts.map +1 -1
  36. package/dist/components/icon/icon.js +4 -2
  37. package/dist/components/icon/icon.js.map +1 -1
  38. package/dist/onboarding/sign-in.stories.js +93 -0
  39. package/dist/onboarding/sign-in.stories.js.map +1 -0
  40. package/index.css +29 -3
  41. package/package.json +9 -1
  42. package/src/components/button/button-link.stories.tsx +86 -0
  43. package/src/components/button/button-link.tsx +76 -0
  44. package/src/components/button/button.stories.tsx +1 -7
  45. package/src/components/button/button.tsx +8 -6
  46. package/src/components/button/icon-button.stories.tsx +182 -0
  47. package/src/components/button/icon-button.tsx +69 -0
  48. package/src/components/button/index.ts +2 -0
  49. package/src/components/code-block/code-content.tsx +5 -2
  50. package/src/components/icon/icon.tsx +4 -0
  51. package/src/onboarding/sign-in.stories.tsx +73 -0
  52. 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.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 py-2 text-xs gap-4',
26
- sm: 'px-8 py-4 text-sm gap-6',
27
- md: 'px-10 py-6 text-md gap-8',
28
- lg: 'px-12 py-8 text-lg gap-8',
29
- xl: 'px-12 py-10 text-xl gap-10',
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
+ }
@@ -1 +1,3 @@
1
1
  export * from './button';
2
+ export * from './button-link';
3
+ export * from './icon-button';
@@ -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 className="line px-12 w-full relative font-code text-xs leading-20" key={line}>
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
- environment: 'jsdom',
12
- setupFiles: ['./test/setup.ts'],
13
- globalSetup: './test/global.ts',
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,