@vrobots/storybook 0.1.73 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './src/components';
2
2
  export * as utils from './src/utils';
3
3
  export * from './src/hooks';
4
+ export { default as system } from './src/theme/system';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './src/components';
2
2
  export * as utils from './src/utils';
3
3
  export * from './src/hooks';
4
+ export { default as system } from './src/theme/system';
package/dist/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vrobots/storybook",
3
3
  "private": false,
4
- "version": "0.1.73",
4
+ "version": "0.2.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -40,6 +40,7 @@
40
40
  "@storybook/addon-docs": "^10.2.16",
41
41
  "@storybook/addon-vitest": "^10.2.16",
42
42
  "@storybook/react-vite": "^10.2.16",
43
+ "@storybook/test": "^8.6.15",
43
44
  "@types/node": "^24.10.1",
44
45
  "@types/react": "^19.2.5",
45
46
  "@types/react-dom": "^19.2.3",
@@ -5,9 +5,9 @@ export const Breadcrumbs = React.forwardRef(({ breadcrumbs }, ref) => {
5
5
  if (!!breadcrumbs) {
6
6
  const crumbs = breadcrumbs.map(({ title, nav, color: itemColor, onClick }, key) => {
7
7
  if (!!nav) {
8
- return (_jsxs(HStack, { children: [_jsx(Text, { as: 'button', color: itemColor || 'blue.500', whiteSpace: 'nowrap', wordBreak: 'break-word', fontSize: 'sm', cursor: 'pointer', truncate: true, onClick: () => onClick?.(nav), children: title }), _jsx(Text, { ml: 2, mr: 2, fontSize: 'sm', children: ">" })] }, `${title}-${key}`));
8
+ return (_jsxs(HStack, { children: [_jsx(Text, { as: 'button', color: itemColor || 'neu.accent', whiteSpace: 'nowrap', wordBreak: 'break-word', fontSize: 'sm', cursor: 'pointer', p: 1, truncate: true, onClick: () => onClick?.(nav), children: title }), _jsx(Text, { ml: 2, mr: 2, fontSize: 'sm', children: ">" })] }, `${title}-${key}`));
9
9
  }
10
- return (_jsx(Text, { whiteSpace: 'nowrap', wordBreak: 'break-word', color: 'gray.500', fontSize: 'sm', truncate: true, children: title }, `${title}-${key}`));
10
+ return (_jsx(Text, { whiteSpace: 'nowrap', wordBreak: 'break-word', color: 'neu.muted', fontSize: 'sm', truncate: true, children: title }, `${title}-${key}`));
11
11
  });
12
12
  return (_jsx(HStack, { width: '100%', flex: `0 1 auto`, ref: ref, children: crumbs }));
13
13
  }
@@ -1,12 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box } from "@chakra-ui/react";
3
3
  import { forwardRef } from "react";
4
- import { useColorMode } from "./ui/color-mode";
5
4
  import { useHeader } from './Header';
6
5
  export const Display = forwardRef((props, ref) => {
7
- const { colorMode } = useColorMode();
8
- const bgColor = colorMode === 'light' ? 'gray.50' : 'gray.800';
9
6
  const { ref: headerRef } = useHeader();
10
- return (_jsx(Box, { ref: ref, maxHeight: `calc(100vh - ${headerRef.current?.offsetHeight || 0}px)`, width: '100%', overflow: 'scroll', bgColor: bgColor, children: props.children }));
7
+ return (_jsx(Box, { ref: ref, maxHeight: `calc(100vh - ${headerRef.current?.offsetHeight || 0}px)`, width: '100%', overflow: 'scroll', bgColor: 'neu.bg', children: props.children }));
11
8
  });
12
9
  Display.displayName = 'Display';
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
- import { defaultSystem, Grid, GridItem, Text } from '@chakra-ui/react';
3
+ import { Grid, GridItem, Text } from '@chakra-ui/react';
4
4
  import { forwardRef } from 'react';
5
5
  import { Logo } from './Logo';
6
6
  import { useColorMode } from './ui/color-mode';
@@ -31,10 +31,8 @@ export const Header = forwardRef(({}, ref) => {
31
31
  const { appName, logo, menu, version, defaultPath } = useHeader();
32
32
  const { colorMode } = useColorMode();
33
33
  const selectedLogo = logo[colorMode];
34
- const borderColors = { ...defaultSystem }._config.theme?.semanticTokens?.colors.border.DEFAULT.value;
35
- const borderColor = colorMode === 'light' ? borderColors._light : borderColors._dark;
36
- return (_jsxs(Grid, { id: 'header', width: '100%', position: 'relative', zIndex: 2, backgroundColor: colorMode === 'light' ? 'white' : 'black', borderBottom: `1px solid ${borderColor}`, templateColumns: {
34
+ return (_jsxs(Grid, { id: 'header', width: '100%', position: 'relative', zIndex: 2, backgroundColor: 'neu.surface', borderBottom: '1px solid', borderColor: 'neu.border', boxShadow: 'neuRaised', templateColumns: {
37
35
  base: 'repeat(12, 1fr)',
38
- }, ref: ref, children: [_jsxs(GridItem, { colSpan: 6, pt: { base: '0', lg: '8px' }, pb: { base: '0', lg: '8px' }, children: [_jsx(Logo, { defaultPath: defaultPath, alt: appName, src: selectedLogo }), _jsx(Text, { position: 'absolute', fontSize: 'xx-small', bottom: 0, left: 0, ml: 1, color: 'gray.500', children: !!version && _jsxs("em", { children: ["v. ", version] }) })] }), _jsx(GridItem, { colSpan: 6, alignItems: 'center', justifyContent: 'flex-end', pr: 2, display: 'flex', children: menu })] }));
36
+ }, ref: ref, children: [_jsxs(GridItem, { colSpan: 6, pt: { base: '0', lg: '8px' }, pb: { base: '0', lg: '8px' }, children: [_jsx(Logo, { defaultPath: defaultPath, alt: appName, src: selectedLogo }), _jsx(Text, { position: 'absolute', fontSize: 'xx-small', bottom: 0, left: 0, ml: 1, color: 'neu.muted', children: !!version && _jsxs("em", { children: ["v. ", version] }) })] }), _jsx(GridItem, { colSpan: 6, alignItems: 'center', justifyContent: 'flex-end', pr: 2, display: 'flex', children: menu })] }));
39
37
  });
40
38
  Header.displayName = 'Header';
@@ -5,7 +5,6 @@ import { useColorMode } from "./ui/color-mode";
5
5
  import { LuMenu } from "react-icons/lu";
6
6
  import { motion } from "motion/react";
7
7
  import { useBreakpoint, useIsMobile } from "../hooks";
8
- import { COLOR_GRAY_200, COLOR_GRAY_800 } from "../constants";
9
8
  export const SidebarContext = React.createContext({});
10
9
  export const SidebarProvider = (props) => {
11
10
  const [isOpen, setIsOpen] = React.useState(props?.isOpen || false);
@@ -50,7 +49,6 @@ export const Sidebar = forwardRef((props, ref) => {
50
49
  transition: { duration: 0.2 }
51
50
  };
52
51
  const action = isMobile ? sidebar.isOpen ? open : close : {};
53
- const borderColor = colorMode === 'light' ? COLOR_GRAY_200 : COLOR_GRAY_800;
54
52
  React.useEffect(() => {
55
53
  setMounted(true);
56
54
  }, []);
@@ -65,8 +63,9 @@ export const Sidebar = forwardRef((props, ref) => {
65
63
  overflow: 'hidden',
66
64
  height: 'inherit',
67
65
  width: sidebar.isOpen ? width[breakpoint || 'base'] : '0',
68
- backgroundColor: colorMode === 'light' ? 'white' : 'black',
69
- borderRight: `1px solid ${borderColor}`,
66
+ backgroundColor: 'var(--chakra-colors-neu-surface)',
67
+ borderRight: '1px solid var(--chakra-colors-neu-border)',
68
+ boxShadow: 'var(--chakra-shadows-neuRaised)',
70
69
  }, ...action, ref: ref, children: _jsx(Box, { width: width[breakpoint || 'base'], children: sidebar.menu }) }) }));
71
70
  });
72
71
  Sidebar.displayName = 'Sidebar';
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button, Dialog, Portal, Text } from '@chakra-ui/react';
3
3
  export const VerifyAction = ({ isOpen, heading, subheading, onCancel, onProceed }) => {
4
- return (_jsx(Dialog.Root, { open: isOpen, onOpenChange: onCancel, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, {}), _jsx(Dialog.Positioner, { children: _jsxs(Dialog.Content, { children: [_jsx(Dialog.Header, { mb: 0, children: _jsx(Dialog.Title, { mb: 0, children: heading }) }), _jsx(Text, { ml: 6, children: subheading }), _jsx(Dialog.Body, {}), _jsxs(Dialog.Footer, { children: [_jsx(Dialog.ActionTrigger, { asChild: true, children: _jsx(Button, { variant: 'subtle', children: "Close" }) }), _jsx(Button, { ml: 1, onClick: onProceed, colorPalette: 'red', children: "Proceed" })] })] }) })] }) }));
4
+ return (_jsx(Dialog.Root, { open: isOpen, onOpenChange: onCancel, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, { backdropFilter: 'blur(2px)' }), _jsx(Dialog.Positioner, { children: _jsxs(Dialog.Content, { layerStyle: 'neuRaised', borderWidth: '1px', borderColor: 'neu.border', children: [_jsx(Dialog.Header, { mb: 0, children: _jsx(Dialog.Title, { mb: 0, children: heading }) }), _jsx(Text, { ml: 6, color: 'neu.muted', children: subheading }), _jsx(Dialog.Body, {}), _jsxs(Dialog.Footer, { children: [_jsx(Dialog.ActionTrigger, { asChild: true, children: _jsx(Button, { variant: 'subtle', children: "Close" }) }), _jsx(Button, { ml: 1, onClick: onProceed, colorPalette: 'red', "aria-label": 'Proceed with destructive action', children: "Proceed" })] })] }) })] }) }));
5
5
  };
@@ -54,6 +54,6 @@ const FileUploader = ({ title, description, buttonLabel, helperText, accept, mul
54
54
  const droppedFiles = Array.from(event.dataTransfer.files ?? []);
55
55
  handleFiles(droppedFiles);
56
56
  };
57
- return (_jsx(Card.Root, { ...props, children: _jsxs(Card.Body, { children: [_jsx(Card.Title, { children: title || `Upload ${multiple ? 'File/s' : 'a File'}` }), !!description && _jsx(Card.Description, { children: description }), _jsxs(Box, { mt: 4, p: 6, borderWidth: "2px", borderStyle: "dashed", borderColor: isDragging ? "teal.500" : "border.emphasized", borderRadius: "md", bg: isDragging ? "teal.subtle" : "bg.muted", textAlign: "center", transition: "background 0.2s ease, border-color 0.2s ease", onDragEnter: handleDrag(true), onDragOver: handleDrag(true), onDragLeave: handleDrag(false), onDrop: handleDrop, children: [_jsxs(Text, { fontWeight: "medium", children: ["Drag and drop ", multiple ? 'file/s' : 'a file', " here"] }), _jsx(Text, { fontSize: "sm", color: "fg.muted", mt: 1, children: "or click the button below to browse from your device" }), _jsx("input", { ref: fileInputRef, type: "file", hidden: true, accept: accept, multiple: multiple, onChange: handleFileInputChange }), _jsx(Button, { mt: 4, onClick: () => fileInputRef.current?.click(), children: buttonLabel || `Choose ${multiple ? 'File/s' : 'a File'}` }), !!helperText && (_jsx(Text, { fontSize: "xs", color: "fg.muted", mt: 3, children: helperText }))] }), files.length > 0 && (_jsxs(Box, { mt: 4, mb: 4, children: [_jsxs(Text, { fontSize: "sm", fontWeight: "medium", mb: 2, children: ["Selected ", multiple ? 'File/s' : 'a File'] }), _jsx(List.Root, { gap: "1", children: files.map((file, index) => (_jsx(List.Item, { children: `${file.name} ${!!uploadPercentages[index] ? '-' + uploadPercentages[index] + '%' : ''}` }, `${file.name}-${file.size}`))) })] })), children] }) }));
57
+ return (_jsx(Card.Root, { ...props, children: _jsxs(Card.Body, { children: [_jsx(Card.Title, { children: title || `Upload ${multiple ? 'File/s' : 'a File'}` }), !!description && _jsx(Card.Description, { children: description }), _jsxs(Box, { mt: 4, p: 6, borderWidth: "2px", borderStyle: "dashed", borderColor: isDragging ? "neu.accent" : "neu.border", borderRadius: "md", bg: isDragging ? "neu.surface" : "neu.bg", textAlign: "center", transition: "background 0.2s ease, border-color 0.2s ease", onDragEnter: handleDrag(true), onDragOver: handleDrag(true), onDragLeave: handleDrag(false), onDrop: handleDrop, children: [_jsxs(Text, { fontWeight: "medium", children: ["Drag and drop ", multiple ? 'file/s' : 'a file', " here"] }), _jsx(Text, { fontSize: "sm", color: "neu.muted", mt: 1, children: "or click the button below to browse from your device" }), _jsx("input", { ref: fileInputRef, type: "file", hidden: true, accept: accept, multiple: multiple, onChange: handleFileInputChange }), _jsx(Button, { mt: 4, onClick: () => fileInputRef.current?.click(), children: buttonLabel || `Choose ${multiple ? 'File/s' : 'a File'}` }), !!helperText && (_jsx(Text, { fontSize: "xs", color: "neu.muted", mt: 3, children: helperText }))] }), files.length > 0 && (_jsxs(Box, { mt: 4, mb: 4, children: [_jsxs(Text, { fontSize: "sm", fontWeight: "medium", mb: 2, children: ["Selected ", multiple ? 'File/s' : 'a File'] }), _jsx(List.Root, { gap: "1", children: files.map((file, index) => (_jsx(List.Item, { children: `${file.name} ${!!uploadPercentages[index] ? '-' + uploadPercentages[index] + '%' : ''}` }, `${file.name}-${file.size}`))) })] })), children] }) }));
58
58
  };
59
59
  export default FileUploader;
@@ -12,6 +12,6 @@ const Login = ({ title, description, onLogin, ...props }) => {
12
12
  }
13
13
  }), autoFocus: true }), _jsx(Field.ErrorText, { children: errors.emailAddress?.message })] }), _jsxs(Field.Root, { invalid: !!errors.password, children: [_jsx(Field.Label, { children: "Password" }), _jsx(PasswordInput, { ...register("password", {
14
14
  required: "Please enter your password",
15
- }) }), _jsx(Field.ErrorText, { children: errors.password?.message })] })] }), _jsx(Button, { type: "submit", colorScheme: "teal", width: "full", mt: 6, children: "Login" })] })] }) }));
15
+ }) }), _jsx(Field.ErrorText, { children: errors.password?.message })] })] }), _jsx(Button, { type: "submit", width: "full", mt: 6, children: "Login" })] })] }) }));
16
16
  };
17
17
  export default Login;
@@ -5,6 +5,6 @@ const SecondFactorAuth = ({ title, description, onSecondFactorAuth, onResendAuth
5
5
  const { register, handleSubmit, formState: { errors }, } = useForm();
6
6
  return (_jsx(Card.Root, { ...props, children: _jsxs(Card.Body, { children: [_jsx(Card.Title, { children: title || "Second Factor Auth" }), !!description && _jsx(Card.Description, { children: description }), _jsxs(Box, { as: "form", onSubmit: handleSubmit((data) => onSecondFactorAuth(data)), mt: 4, children: [_jsxs(Stack, { gap: "4", children: [_jsxs(Field.Root, { invalid: !!errors.authCode, required: true, children: [_jsx(Field.Label, { children: "Authorization Code" }), _jsx(InputGroup, { endAddon: _jsx(Button, { h: '1.75rem', size: 'sm', variant: 'ghost', onClick: onResendAuthCode, children: "Resend" }), children: _jsx(Input, { type: 'text', placeholder: 'Authorization code', ...register("authCode", {
7
7
  required: "Please enter your authorization code",
8
- }), autoFocus: true }) }), _jsx(Field.ErrorText, { children: errors.authCode?.message })] }), _jsxs(Checkbox.Root, { ...register("rememberDevice"), size: 'sm', children: [_jsx(Checkbox.HiddenInput, {}), _jsx(Checkbox.Control, {}), _jsx(Checkbox.Label, { children: "Remember Device" })] })] }), _jsx(Button, { type: "submit", colorScheme: "teal", width: "full", mt: 6, children: "Submit" })] })] }) }));
8
+ }), autoFocus: true }) }), _jsx(Field.ErrorText, { children: errors.authCode?.message })] }), _jsxs(Checkbox.Root, { ...register("rememberDevice"), size: 'sm', children: [_jsx(Checkbox.HiddenInput, {}), _jsx(Checkbox.Control, {}), _jsx(Checkbox.Label, { children: "Remember Device" })] })] }), _jsx(Button, { type: "submit", width: "full", mt: 6, children: "Submit" })] })] }) }));
9
9
  };
10
10
  export default SecondFactorAuth;
@@ -1,5 +1,6 @@
1
1
  import { TColor } from "../types";
2
- export declare const useSelectedColorSchema: (color: TColor) => {
2
+ export declare const useSelectedColorSchema: (_color: TColor) => {
3
3
  color: string;
4
4
  bgColor: string;
5
+ boxShadow: string;
5
6
  };
@@ -1,8 +1,9 @@
1
1
  import { useColorMode } from "../components";
2
- export const useSelectedColorSchema = (color) => {
2
+ export const useSelectedColorSchema = (_color) => {
3
3
  const { colorMode } = useColorMode();
4
4
  return {
5
- color: colorMode === 'light' ? `${color}.800` : `${color}.400`,
6
- bgColor: colorMode === 'light' ? `${color}.200` : `${color}.800`,
5
+ color: 'neu.text',
6
+ bgColor: colorMode === 'light' ? 'neu.bg' : 'neu.surface',
7
+ boxShadow: 'neuInset',
7
8
  };
8
9
  };
@@ -12,3 +12,4 @@ declare const meta: {
12
12
  export default meta;
13
13
  type Story = StoryObj<typeof meta>;
14
14
  export declare const Component: Story;
15
+ export declare const KeyboardFocus: Story;
@@ -1,3 +1,4 @@
1
+ import { expect, userEvent, within } from '@storybook/test';
1
2
  import { Breadcrumbs } from '../components/Breadcrumbs';
2
3
  const onClick = (nav) => {
3
4
  console.log('Breadcrumb clicked:', nav);
@@ -23,3 +24,17 @@ export const Component = {
23
24
  breadcrumbs: EXAMPLE_BREADCRUMBS,
24
25
  }
25
26
  };
27
+ export const KeyboardFocus = {
28
+ name: 'Keyboard Focus',
29
+ args: {
30
+ breadcrumbs: EXAMPLE_BREADCRUMBS,
31
+ },
32
+ play: async ({ canvasElement }) => {
33
+ const canvas = within(canvasElement);
34
+ const links = canvas.getAllByRole('button');
35
+ for (const link of links) {
36
+ await userEvent.tab();
37
+ await expect(link).toHaveFocus();
38
+ }
39
+ }
40
+ };
@@ -13,3 +13,4 @@ declare const meta: {
13
13
  export default meta;
14
14
  type Story = StoryObj<typeof meta>;
15
15
  export declare const Component: Story;
16
+ export declare const KeyboardFocus: Story;
@@ -1,3 +1,4 @@
1
+ import { expect, userEvent, within } from '@storybook/test';
1
2
  import { Form } from '../components';
2
3
  const meta = {
3
4
  title: 'Forms/FileUploader',
@@ -22,3 +23,21 @@ export const Component = {
22
23
  onFilesSelected: (files) => console.log(files),
23
24
  }
24
25
  };
26
+ export const KeyboardFocus = {
27
+ name: 'Keyboard Focus',
28
+ args: {
29
+ title: 'Upload Files',
30
+ description: 'Drag and drop files in the area below, or choose files from your device.',
31
+ buttonLabel: 'Browse Files',
32
+ helperText: 'Accepted: image/*, .pdf',
33
+ accept: 'image/*',
34
+ multiple: true,
35
+ onFilesSelected: (files) => console.log(files),
36
+ },
37
+ play: async ({ canvasElement }) => {
38
+ const canvas = within(canvasElement);
39
+ await userEvent.tab();
40
+ const chooseButton = canvas.getByRole('button', { name: /browse files/i });
41
+ await expect(chooseButton).toHaveFocus();
42
+ }
43
+ };
@@ -13,3 +13,4 @@ declare const meta: {
13
13
  export default meta;
14
14
  type Story = StoryObj<typeof meta>;
15
15
  export declare const Component: Story;
16
+ export declare const KeyboardFocus: Story;
@@ -1,3 +1,4 @@
1
+ import { expect, userEvent, within } from '@storybook/test';
1
2
  import { Form } from '../components';
2
3
  const meta = {
3
4
  title: 'Forms/Login',
@@ -18,3 +19,21 @@ export const Component = {
18
19
  onLogin: (data) => console.log(data)
19
20
  }
20
21
  };
22
+ export const KeyboardFocus = {
23
+ name: 'Keyboard Focus',
24
+ args: {
25
+ title: "Login Form",
26
+ description: "Please enter your credentials to continue",
27
+ onLogin: (data) => console.log(data)
28
+ },
29
+ play: async ({ canvasElement }) => {
30
+ const canvas = within(canvasElement);
31
+ await userEvent.tab();
32
+ const emailInput = canvas.getByRole('textbox', { name: /email address/i });
33
+ await expect(emailInput).toHaveFocus();
34
+ await userEvent.tab();
35
+ await userEvent.tab();
36
+ const submitButton = canvas.getByRole('button', { name: /login/i });
37
+ await expect(submitButton).toHaveFocus();
38
+ }
39
+ };
@@ -13,3 +13,4 @@ declare const meta: {
13
13
  export default meta;
14
14
  type Story = StoryObj<typeof meta>;
15
15
  export declare const Component: Story;
16
+ export declare const KeyboardFocus: Story;
@@ -1,3 +1,4 @@
1
+ import { expect, userEvent, within } from '@storybook/test';
1
2
  import { Form } from '../components';
2
3
  const meta = {
3
4
  title: 'Forms/SecondFactorAuth',
@@ -19,3 +20,25 @@ export const Component = {
19
20
  onResendAuthCode: () => console.log("Resend auth code")
20
21
  }
21
22
  };
23
+ export const KeyboardFocus = {
24
+ name: 'Keyboard Focus',
25
+ args: {
26
+ title: "Second FactorAuth Form",
27
+ description: "Please enter your auth code to continue",
28
+ onSecondFactorAuth: (data) => console.log(data),
29
+ onResendAuthCode: () => console.log("Resend auth code")
30
+ },
31
+ play: async ({ canvasElement }) => {
32
+ const canvas = within(canvasElement);
33
+ await userEvent.tab();
34
+ const codeInput = canvas.getByPlaceholderText(/authorization code/i);
35
+ await expect(codeInput).toHaveFocus();
36
+ await userEvent.tab();
37
+ const resendButton = canvas.getByRole('button', { name: /resend/i });
38
+ await expect(resendButton).toHaveFocus();
39
+ await userEvent.tab();
40
+ await userEvent.tab();
41
+ const submitButton = canvas.getByRole('button', { name: /submit/i });
42
+ await expect(submitButton).toHaveFocus();
43
+ }
44
+ };
@@ -10,3 +10,4 @@ declare const meta: {
10
10
  export default meta;
11
11
  type Story = StoryObj<typeof meta>;
12
12
  export declare const Component: Story;
13
+ export declare const KeyboardFocus: Story;
@@ -1,3 +1,4 @@
1
+ import { expect, userEvent, within } from '@storybook/test';
1
2
  import { VerifyAction } from '../components';
2
3
  const meta = {
3
4
  title: 'Feedback/VerifyAction',
@@ -17,3 +18,22 @@ export const Component = {
17
18
  onProceed: () => console.log('proceed')
18
19
  }
19
20
  };
21
+ export const KeyboardFocus = {
22
+ name: 'Keyboard Focus — Destructive Action',
23
+ args: {
24
+ isOpen: true,
25
+ heading: 'Are you sure?',
26
+ subheading: `This action can't be undone!`,
27
+ onCancel: () => console.log('close dialog'),
28
+ onProceed: () => console.log('proceed')
29
+ },
30
+ play: async ({ canvasElement }) => {
31
+ const canvas = within(canvasElement);
32
+ await userEvent.tab();
33
+ const closeButton = canvas.getByRole('button', { name: /close/i });
34
+ await expect(closeButton).toHaveFocus();
35
+ await userEvent.tab();
36
+ const proceedButton = canvas.getByRole('button', { name: /proceed/i });
37
+ await expect(proceedButton).toHaveFocus();
38
+ }
39
+ };
@@ -0,0 +1,2 @@
1
+ declare const system: import("@chakra-ui/react").SystemContext;
2
+ export default system;
@@ -0,0 +1,113 @@
1
+ import { createSystem, defaultConfig, defineConfig } from "@chakra-ui/react";
2
+ const config = defineConfig({
3
+ globalCss: {
4
+ "html, body": {
5
+ bg: "neu.bg",
6
+ color: "neu.text",
7
+ minHeight: "100%",
8
+ },
9
+ ".sb-show-main": {
10
+ bg: "neu.bg",
11
+ minHeight: "100vh",
12
+ },
13
+ button: {
14
+ bg: "neu.surface",
15
+ borderColor: "neu.border",
16
+ borderWidth: "1px",
17
+ borderRadius: "14px",
18
+ boxShadow: "neuRaised",
19
+ transition: "transform 0.12s ease, box-shadow 0.16s ease, background-color 0.16s ease",
20
+ _hover: {
21
+ transform: "translateY(-1px)",
22
+ },
23
+ _active: {
24
+ transform: "translateY(0)",
25
+ boxShadow: "neuInset",
26
+ },
27
+ },
28
+ "input, textarea, select": {
29
+ bg: "neu.surface",
30
+ borderColor: "neu.border",
31
+ borderWidth: "1px",
32
+ borderRadius: "12px",
33
+ boxShadow: "neuInset",
34
+ },
35
+ "[data-scope='card'][data-part='root']": {
36
+ bg: "neu.surface",
37
+ borderColor: "neu.border",
38
+ borderWidth: "1px",
39
+ borderRadius: "20px",
40
+ boxShadow: "neuRaised",
41
+ },
42
+ "button:focus-visible, [role='button']:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible": {
43
+ outline: "2px solid",
44
+ outlineColor: "neu.accent",
45
+ outlineOffset: "2px",
46
+ },
47
+ "[data-scope='button'][data-color-palette='red']": {
48
+ bg: "red.600",
49
+ color: "white",
50
+ borderColor: "red.700",
51
+ boxShadow: "none",
52
+ _hover: {
53
+ bg: "red.700",
54
+ },
55
+ _active: {
56
+ bg: "red.800",
57
+ },
58
+ _focusVisible: {
59
+ outlineColor: "red.300",
60
+ },
61
+ },
62
+ },
63
+ theme: {
64
+ semanticTokens: {
65
+ colors: {
66
+ neu: {
67
+ bg: { value: { base: "#e6ebf2", _dark: "#161c24" } },
68
+ surface: { value: { base: "#edf2f8", _dark: "#1f2732" } },
69
+ text: { value: { base: "#1f2937", _dark: "#e5e7eb" } },
70
+ border: { value: { base: "#d7dee8", _dark: "#2a3442" } },
71
+ muted: { value: { base: "#64748b", _dark: "#94a3b8" } },
72
+ accent: { value: { base: "#2b6cb0", _dark: "#63b3ed" } },
73
+ },
74
+ },
75
+ shadows: {
76
+ neuRaised: {
77
+ value: {
78
+ base: "10px 10px 24px rgba(163, 177, 198, 0.58), -10px -10px 24px rgba(255, 255, 255, 0.9)",
79
+ _dark: "10px 10px 24px rgba(0, 0, 0, 0.62), -10px -10px 24px rgba(255, 255, 255, 0.04)",
80
+ },
81
+ },
82
+ neuInset: {
83
+ value: {
84
+ base: "inset 8px 8px 18px rgba(163, 177, 198, 0.46), inset -8px -8px 18px rgba(255, 255, 255, 0.8)",
85
+ _dark: "inset 8px 8px 18px rgba(0, 0, 0, 0.56), inset -8px -8px 18px rgba(255, 255, 255, 0.04)",
86
+ },
87
+ },
88
+ },
89
+ },
90
+ layerStyles: {
91
+ neuRaised: {
92
+ value: {
93
+ bg: "neu.surface",
94
+ borderRadius: "24px",
95
+ borderWidth: "1px",
96
+ borderColor: "neu.border",
97
+ boxShadow: "neuRaised",
98
+ },
99
+ },
100
+ neuInset: {
101
+ value: {
102
+ bg: "neu.surface",
103
+ borderRadius: "20px",
104
+ borderWidth: "1px",
105
+ borderColor: "neu.border",
106
+ boxShadow: "neuInset",
107
+ },
108
+ },
109
+ },
110
+ },
111
+ });
112
+ const system = createSystem(defaultConfig, config);
113
+ export default system;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vrobots/storybook",
3
3
  "private": false,
4
- "version": "0.1.73",
4
+ "version": "0.2.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -40,6 +40,7 @@
40
40
  "@storybook/addon-docs": "^10.2.16",
41
41
  "@storybook/addon-vitest": "^10.2.16",
42
42
  "@storybook/react-vite": "^10.2.16",
43
+ "@storybook/test": "^8.6.15",
43
44
  "@types/node": "^24.10.1",
44
45
  "@types/react": "^19.2.5",
45
46
  "@types/react-dom": "^19.2.3",