@vrobots/storybook 0.1.73 → 0.2.1

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 (58) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +1 -0
  3. package/dist/package.json +3 -1
  4. package/dist/src/components/Breadcrumbs.js +2 -2
  5. package/dist/src/components/Display.js +1 -4
  6. package/dist/src/components/Header.js +3 -5
  7. package/dist/src/components/Sidebar.js +3 -4
  8. package/dist/src/components/VerifyAction.js +1 -1
  9. package/dist/src/components/form/FileUploader.js +1 -1
  10. package/dist/src/components/form/Login.js +1 -1
  11. package/dist/src/components/form/SecondFactorAuth.js +1 -1
  12. package/dist/src/components/index.d.ts +4 -0
  13. package/dist/src/components/index.js +4 -0
  14. package/dist/src/components/table/DataTable.d.ts +16 -0
  15. package/dist/src/components/table/DataTable.js +42 -0
  16. package/dist/src/components/table/components/ColumnText.d.ts +2 -0
  17. package/dist/src/components/table/components/ColumnText.js +5 -0
  18. package/dist/src/components/table/components/PageSizeSelector.d.ts +7 -0
  19. package/dist/src/components/table/components/PageSizeSelector.js +8 -0
  20. package/dist/src/components/table/components/Pagination.d.ts +10 -0
  21. package/dist/src/components/table/components/Pagination.js +51 -0
  22. package/dist/src/components/table/components/TableFilter.d.ts +6 -0
  23. package/dist/src/components/table/components/TableFilter.js +28 -0
  24. package/dist/src/components/table/components/TableFilterDatePicker.d.ts +5 -0
  25. package/dist/src/components/table/components/TableFilterDatePicker.js +10 -0
  26. package/dist/src/components/table/components/TableFilterInput.d.ts +2 -0
  27. package/dist/src/components/table/components/TableFilterInput.js +5 -0
  28. package/dist/src/components/table/components/TableFilterSelect.d.ts +2 -0
  29. package/dist/src/components/table/components/TableFilterSelect.js +5 -0
  30. package/dist/src/components/table/constants.d.ts +2 -0
  31. package/dist/src/components/table/constants.js +2 -0
  32. package/dist/src/components/table/hooks/useDataTable.d.ts +24 -0
  33. package/dist/src/components/table/hooks/useDataTable.js +14 -0
  34. package/dist/src/components/table/hooks/usePageSize.d.ts +4 -0
  35. package/dist/src/components/table/hooks/usePageSize.js +8 -0
  36. package/dist/src/components/table/hooks/usePagination.d.ts +5 -0
  37. package/dist/src/components/table/hooks/usePagination.js +12 -0
  38. package/dist/src/components/table/hooks/useTableFilters.d.ts +15 -0
  39. package/dist/src/components/table/hooks/useTableFilters.js +118 -0
  40. package/dist/src/components/table/types.d.ts +11 -0
  41. package/dist/src/components/table/types.js +1 -0
  42. package/dist/src/hooks/useSelectedColorSchema.d.ts +2 -1
  43. package/dist/src/hooks/useSelectedColorSchema.js +4 -3
  44. package/dist/src/stories/Breadcrumbs.stories.d.ts +1 -0
  45. package/dist/src/stories/Breadcrumbs.stories.js +15 -0
  46. package/dist/src/stories/DataTable.stories.d.ts +26 -0
  47. package/dist/src/stories/DataTable.stories.js +92 -0
  48. package/dist/src/stories/FileUploader.stories.d.ts +1 -0
  49. package/dist/src/stories/FileUploader.stories.js +19 -0
  50. package/dist/src/stories/Login.stories.d.ts +1 -0
  51. package/dist/src/stories/Login.stories.js +19 -0
  52. package/dist/src/stories/SecondFactorAuth.stories.d.ts +1 -0
  53. package/dist/src/stories/SecondFactorAuth.stories.js +23 -0
  54. package/dist/src/stories/VerifyAction.stories.d.ts +1 -0
  55. package/dist/src/stories/VerifyAction.stories.js +20 -0
  56. package/dist/src/theme/system.d.ts +2 -0
  57. package/dist/src/theme/system.js +113 -0
  58. package/package.json +3 -1
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.1",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -21,8 +21,10 @@
21
21
  "@chakra-ui/react": "^3.32.0",
22
22
  "@emotion/react": "^11.14.0",
23
23
  "@storybook/addon-themes": "^10.2.16",
24
+ "@tanstack/react-table": "^8.21.3",
24
25
  "axios": "^1.13.4",
25
26
  "motion": "^12.34.0",
27
+ "next": "^16.2.4",
26
28
  "next-themes": "^0.4.6",
27
29
  "owasp-password-strength-test": "^1.3.0",
28
30
  "react": "^19.2.0",
@@ -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;
@@ -19,3 +19,7 @@ export * from './form/Login';
19
19
  export * from './form/SecondFactorAuth';
20
20
  export * from './form/FileUploader';
21
21
  export { default as Form } from './form';
22
+ export { default as DataTable } from './table/DataTable';
23
+ export * from './table/types';
24
+ export * from './table/components/ColumnText';
25
+ export { default as useDataTable } from './table/hooks/useDataTable';
@@ -19,3 +19,7 @@ export * from './form/Login';
19
19
  export * from './form/SecondFactorAuth';
20
20
  export * from './form/FileUploader';
21
21
  export { default as Form } from './form';
22
+ export { default as DataTable } from './table/DataTable';
23
+ export * from './table/types';
24
+ export * from './table/components/ColumnText';
25
+ export { default as useDataTable } from './table/hooks/useDataTable';
@@ -0,0 +1,16 @@
1
+ import { IColumnFilter, TFilterChange } from "./types";
2
+ export interface IDataTableProps {
3
+ data: any[];
4
+ total: number;
5
+ pageNumber: number;
6
+ pagesTotal: number;
7
+ pageSize: number;
8
+ setPageNumber: React.Dispatch<React.SetStateAction<number>>;
9
+ setPageSize: React.Dispatch<React.SetStateAction<number>>;
10
+ columns: any[];
11
+ filters?: IColumnFilter[];
12
+ onRowClick?: (row: any) => void;
13
+ onFilterChange?: TFilterChange;
14
+ }
15
+ declare const DataTable: React.FC<IDataTableProps>;
16
+ export default DataTable;
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Card, Table } from "@chakra-ui/react";
3
+ import { useColorMode } from "../ui/color-mode";
4
+ import { flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
5
+ import { useEffect, useState } from "react";
6
+ import TableFilter from "./components/TableFilter";
7
+ import { Pagination } from "./components/Pagination";
8
+ const Pager = ({ border, pagesTotal, pageNumber, setPageNumber, pageSize, setPageSize }) => (_jsx(Box, { p: 2, pt: 6, border: border, borderBottom: 'none', children: _jsx(Pagination, { pagesTotal: pagesTotal, pageNumber: pageNumber, setPageNumber: setPageNumber, pageSize: pageSize, setPageSize: setPageSize, showPageSelector: true }) }));
9
+ const DataTable = ({ data: defaultData, total, pageNumber, pagesTotal, setPageNumber, setPageSize, pageSize, columns, filters = [], onRowClick, onFilterChange, }) => {
10
+ const [data, setData] = useState(() => [...defaultData]);
11
+ const { colorMode } = useColorMode();
12
+ const columnResizeMode = 'onChange';
13
+ const columnResizeDirection = 'ltr';
14
+ const isDarkMode = colorMode === 'dark';
15
+ const border = `1px solid ${isDarkMode ? '#333333' : '#E2E8F0'} !important`;
16
+ const table = useReactTable({
17
+ data,
18
+ columns,
19
+ columnResizeMode,
20
+ columnResizeDirection,
21
+ getCoreRowModel: getCoreRowModel(),
22
+ });
23
+ const pagerProps = { border, pagesTotal, pageNumber, setPageNumber, pageSize, setPageSize };
24
+ useEffect(() => {
25
+ setData([...defaultData]);
26
+ }, [defaultData, setData]);
27
+ return (_jsx(Card.Root, { children: _jsxs(Card.Body, { p: 0, children: [_jsx(Pager, { ...pagerProps }), _jsx(Box, { overflow: 'auto', children: _jsxs(Table.Root, { width: '100%', children: [_jsx(Table.Header, { children: table.getHeaderGroups().map(headerGroup => (_jsx(Table.Row, { children: headerGroup.headers.map(header => (_jsxs(Table.ColumnHeader, { borderLeft: border, borderRight: border, children: [header.isPlaceholder
28
+ ? null
29
+ : flexRender(header.column.columnDef.header, header.getContext()), filters.find((filter) => filter.id === header.id) && !!onFilterChange ? (_jsx(TableFilter, { filter: filters.find(filter => filter.id === header.id), onFilterChange: onFilterChange })) : null] }, header.id))) }, headerGroup.id))) }), _jsxs(Table.Body, { children: [total === 0 ? (_jsx(Table.Row, { children: _jsx(Table.Cell, { colSpan: columns.length, textAlign: 'center', children: "No records found" }) })) : null, table.getRowModel().rows.map((row, i) => {
30
+ const isEven = i % 2 === 0;
31
+ const colorEven = isDarkMode ? 'gray.700' : 'gray.50';
32
+ const colorOdd = isDarkMode ? 'gray.600' : 'white';
33
+ const backgroundColor = isEven ? colorEven : colorOdd;
34
+ return (_jsx(Table.Row, { background: backgroundColor, _hover: !!onRowClick ? {
35
+ cursor: 'pointer',
36
+ backgroundColor: 'rgba(107, 160, 252, 0.3) !important',
37
+ } : void 0, onClick: () => onRowClick && onRowClick(row.original), children: row.getVisibleCells().map(cell => (_jsx(Table.Cell, { border: border, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }, row.id));
38
+ })] }), _jsx(Table.Footer, { children: table.getFooterGroups().map(footerGroup => (_jsx(Table.Row, { children: footerGroup.headers.map(header => (_jsx(Table.ColumnHeader, { borderLeft: border, borderRight: border, children: header.isPlaceholder
39
+ ? null
40
+ : flexRender(header.column.columnDef.header, header.getContext()) }, header.id))) }, footerGroup.id))) })] }) }), _jsx(Pager, { ...pagerProps })] }) }));
41
+ };
42
+ export default DataTable;
@@ -0,0 +1,2 @@
1
+ import { TextProps } from "@chakra-ui/react";
2
+ export default function ColumnText(props: TextProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Text } from "@chakra-ui/react";
3
+ export default function ColumnText(props) {
4
+ return (_jsx(Text, { wordBreak: 'break-word', whiteSpace: 'nowrap', truncate: true, ...props }));
5
+ }
@@ -0,0 +1,7 @@
1
+ import * as React from 'react';
2
+ export interface PageSizeSelectorProps {
3
+ sizes: number[];
4
+ selectedPageSize: number;
5
+ onSelect: (pageSize: number) => void;
6
+ }
7
+ export declare const PageSizeSelector: React.FC<PageSizeSelectorProps>;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { NativeSelect, HStack, Text } from '@chakra-ui/react';
3
+ import * as React from 'react';
4
+ export const PageSizeSelector = ({ sizes, selectedPageSize, onSelect, }) => {
5
+ const idKey = React.useId();
6
+ const handleSetPageSize = (e) => onSelect(Number(e.target.value));
7
+ return (_jsxs(HStack, { gap: "3", align: "center", children: [_jsx(Text, { fontSize: "sm", fontWeight: "normal", children: "Showing" }), _jsxs(NativeSelect.Root, { size: 'sm', maxW: '75px', children: [_jsx(NativeSelect.Field, { id: `page-size-selector-${idKey}`, value: selectedPageSize, onChange: handleSetPageSize, cursor: 'pointer', children: sizes.map((size, key) => (_jsx("option", { value: size, children: size }, `options-${size}-${key}`))) }), _jsx(NativeSelect.Indicator, {})] })] }));
8
+ };
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+ export interface IPaginationProps {
3
+ pagesTotal: number;
4
+ pageNumber: number;
5
+ pageSize: number;
6
+ showPageSelector?: boolean;
7
+ setPageSize: React.Dispatch<React.SetStateAction<number>>;
8
+ setPageNumber: React.Dispatch<React.SetStateAction<number>>;
9
+ }
10
+ export declare const Pagination: React.FC<IPaginationProps>;
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { Flex, Button, Field, HStack, Text, Grid, GridItem, NumberInput } from '@chakra-ui/react';
4
+ import { PAGE_SIZES } from '../constants';
5
+ import { PageSizeSelector } from './PageSizeSelector';
6
+ import { useBreakpoint } from '../../../hooks';
7
+ const PAGE_LIMIT_MOBILE = 2;
8
+ const PAGE_LIMIT = 4;
9
+ const makeNavButton = (onChange, pageNumber) => function PageButton(page) {
10
+ return (_jsx(Button, { "aria-label": `page-${page}`, onClick: () => onChange(page.toString()), variant: page === pageNumber ? 'solid' : 'outline', size: 'sm', paddingLeft: '1px !important', paddingRight: '1px !important', marginRight: 1, children: page }, `nav-button-${page}`));
11
+ };
12
+ export const Pagination = ({ pagesTotal, pageNumber, pageSize, showPageSelector, setPageNumber, setPageSize }) => {
13
+ const [pageInput, setPageInput] = React.useState(pageNumber);
14
+ const [shouldDebounce, setShouldDebounce] = React.useState(false);
15
+ const pageInputRef = React.useRef(pageNumber);
16
+ const breakpoint = useBreakpoint() ?? 'base';
17
+ const isMobile = ['base', 'sm'].includes(breakpoint);
18
+ const limit = isMobile ? PAGE_LIMIT_MOBILE : PAGE_LIMIT;
19
+ const pages = [];
20
+ for (let i = 0; pagesTotal > i; i++) {
21
+ pages.push(i + 1);
22
+ }
23
+ const handlePages = (pages, limit, pageNumber) => pages.filter(page => {
24
+ const cp = pageNumber < 4 ? 3 : pageNumber;
25
+ const rangeA = cp - (limit / 2);
26
+ const rangeB = cp + (limit / 2);
27
+ return page >= rangeA && page <= rangeB;
28
+ });
29
+ const handleChangePageInput = (shouldDebounce) => (ivalue) => {
30
+ const value = Number(ivalue) > pagesTotal ? pagesTotal : Number(ivalue);
31
+ setShouldDebounce(shouldDebounce);
32
+ setPageInput(value);
33
+ };
34
+ React.useEffect(() => {
35
+ let timeout;
36
+ if ((pageInputRef.current !== pageInput) && !!pageInput) {
37
+ pageInputRef.current = pageInput;
38
+ setShouldDebounce(previousShouldDebounce => {
39
+ timeout = setTimeout(() => setPageNumber(pageInput), previousShouldDebounce ? 1000 : 0);
40
+ return false;
41
+ });
42
+ }
43
+ return () => clearTimeout(timeout);
44
+ }, [pageInput, setShouldDebounce, setPageNumber]);
45
+ return (_jsxs(Grid, { templateRows: {
46
+ sm: 'repeat(1, 1fr)',
47
+ }, templateColumns: {
48
+ sm: `repeat(1, 1fr)`,
49
+ md: `repeat(2, 1fr)`,
50
+ }, children: [showPageSelector ? (_jsx(_Fragment, { children: _jsx(GridItem, { mb: 4, children: _jsxs(Flex, { children: [_jsx(PageSizeSelector, { sizes: PAGE_SIZES, selectedPageSize: pageSize, onSelect: setPageSize }), _jsxs(Text, { fontSize: 'sm', fontWeight: 'normal', ml: 4, mt: 1, children: ["page ", _jsx("strong", { children: pageNumber.toString() }), " of ", _jsx("strong", { children: pagesTotal.toString() })] })] }) }) })) : _jsx(GridItem, { colSpan: 1 }), _jsx(GridItem, { children: _jsx(Field.Root, { children: _jsxs(HStack, { justifyContent: 'right', children: [handlePages(pages, limit, pageNumber).map(makeNavButton(handleChangePageInput(false), pageNumber)), _jsx(Field.Label, { htmlFor: 'pagination', style: { fontSize: 10 }, mt: 2, ml: 4, children: "goto page:" }), _jsxs(NumberInput.Root, { value: String(pageInput), size: 'sm', max: pagesTotal, min: 1, onValueChange: (details) => handleChangePageInput(true)(details.value), children: [_jsx(NumberInput.Input, { id: 'pagination', style: { width: 90 } }), _jsxs(NumberInput.Control, { children: [_jsx(NumberInput.IncrementTrigger, {}), _jsx(NumberInput.DecrementTrigger, {})] })] })] }) }) })] }));
51
+ };
@@ -0,0 +1,6 @@
1
+ import { IColumnFilter, TFilterChange } from "../types";
2
+ export interface ITableFilterProps {
3
+ filter: IColumnFilter;
4
+ onFilterChange: TFilterChange;
5
+ }
6
+ export default function TableFilter({ filter, onFilterChange, }: ITableFilterProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import TableFilterInput from "./TableFilterInput";
3
+ import { Field, IconButton, InputGroup } from "@chakra-ui/react";
4
+ import TableFilterDatePicker from "./TableFilterDatePicker";
5
+ import { FaX } from "react-icons/fa6";
6
+ import { Tooltip } from "../../ui/tooltip";
7
+ export default function TableFilter({ filter, onFilterChange, }) {
8
+ const handleChangeInput = (event) => {
9
+ const { value } = event.target;
10
+ onFilterChange({ ...filter, value });
11
+ };
12
+ const handleChangeDate = (date) => {
13
+ if (date) {
14
+ onFilterChange({ ...filter, value: new Date(date) });
15
+ }
16
+ };
17
+ const handleClearFilter = () => {
18
+ onFilterChange({ ...filter, value: undefined });
19
+ };
20
+ const ClearFilterButton = () => !!filter.value ? (_jsx(Tooltip, { content: `Clear filter`, positioning: { placement: 'bottom-end' }, showArrow: true, children: _jsx(IconButton, { "aria-label": `Clear filter ${filter.type}`, onClick: handleClearFilter, size: 'sm', variant: 'ghost', children: _jsx(FaX, {}) }) })) : null;
21
+ if (filter.type === 'string' || filter.type === 'number') {
22
+ return (_jsxs(Field.Root, { children: [_jsx(InputGroup, { endElement: _jsx(ClearFilterButton, {}), children: _jsx(TableFilterInput, { value: filter.value?.toString() || '', onChange: handleChangeInput }) }), _jsx(Field.HelperText, { color: 'gray.300', whiteSpace: 'nowrap', wordBreak: 'break-word', children: filter.helperText })] }));
23
+ }
24
+ if (filter.type === 'date') {
25
+ return (_jsxs(Field.Root, { children: [_jsx(InputGroup, { endElement: _jsx(ClearFilterButton, {}), children: _jsx(TableFilterDatePicker, { selected: filter.value, onChange: (date) => handleChangeDate(date) }) }), _jsx(Field.HelperText, { color: 'gray.300', whiteSpace: 'nowrap', wordBreak: 'break-word', children: filter.helperText })] }));
26
+ }
27
+ return null;
28
+ }
@@ -0,0 +1,5 @@
1
+ export interface ITableFilterDatePickerProps {
2
+ selected?: Date | null;
3
+ onChange: (date: Date | null) => void;
4
+ }
5
+ export default function TableFilterDatePicker({ selected, onChange }: ITableFilterDatePickerProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Input } from "@chakra-ui/react";
3
+ export default function TableFilterDatePicker({ selected, onChange }) {
4
+ const value = selected ? selected.toISOString().split('T')[0] : '';
5
+ const handleChange = (e) => {
6
+ const val = e.target.value;
7
+ onChange(val ? new Date(val) : null);
8
+ };
9
+ return (_jsx(Input, { type: "date", value: value, onChange: handleChange, width: '100%', mt: 2, size: 'md' }));
10
+ }
@@ -0,0 +1,2 @@
1
+ import { InputProps } from "@chakra-ui/react";
2
+ export default function TableFilterInput(props: InputProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Input } from "@chakra-ui/react";
3
+ export default function TableFilterInput(props) {
4
+ return (_jsx(Input, { width: '100%', mt: 2, ...props }));
5
+ }
@@ -0,0 +1,2 @@
1
+ import { type NativeSelectRootProps } from "@chakra-ui/react";
2
+ export default function TableFilterSelect(props: NativeSelectRootProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { NativeSelect } from "@chakra-ui/react";
3
+ export default function TableFilterSelect(props) {
4
+ return (_jsxs(NativeSelect.Root, { width: '100%', ...props, children: [_jsx(NativeSelect.Field, {}), _jsx(NativeSelect.Indicator, {})] }));
5
+ }
@@ -0,0 +1,2 @@
1
+ export declare const PAGE_FIRST_PAGE = 1;
2
+ export declare const PAGE_SIZES: number[];
@@ -0,0 +1,2 @@
1
+ export const PAGE_FIRST_PAGE = 1;
2
+ export const PAGE_SIZES = [25, 50, 100, 150, 200, 250];
@@ -0,0 +1,24 @@
1
+ import { IFilter } from "@vrobots/types";
2
+ import { IColumnFilter } from "../types";
3
+ export interface IUseDataTableArgs {
4
+ columnFilters: IColumnFilter[];
5
+ skip: number;
6
+ limit: number;
7
+ totalRecords: number;
8
+ sort?: {
9
+ [key: string]: 1 | -1;
10
+ };
11
+ baseFilter?: IFilter;
12
+ additionalFilters?: IFilter[];
13
+ shouldUseQuerystring?: boolean;
14
+ }
15
+ export default function useDataTable(options: IUseDataTableArgs): {
16
+ pageSize: number;
17
+ setPageSize: import("react").Dispatch<import("react").SetStateAction<number>>;
18
+ pageNumber: number;
19
+ pagesTotal: number;
20
+ setPageNumber: import("react").Dispatch<import("react").SetStateAction<number>>;
21
+ query: import("@vrobots/types").IQuery;
22
+ filters: IColumnFilter[];
23
+ onFilterChange: (filter: IColumnFilter) => void;
24
+ };
@@ -0,0 +1,14 @@
1
+ import useTableFilters from "./useTableFilters";
2
+ import usePagination from "./usePagination";
3
+ import usePageSize from "./usePageSize";
4
+ export default function useDataTable(options) {
5
+ const { columnFilters, skip, limit, totalRecords, sort, baseFilter, additionalFilters, shouldUseQuerystring, } = options;
6
+ const pageSize = usePageSize(limit);
7
+ const pagination = usePagination(skip, limit, totalRecords, shouldUseQuerystring);
8
+ const filters = useTableFilters(columnFilters, skip, pageSize.pageSize, pagination.pageNumber, sort, baseFilter, additionalFilters, shouldUseQuerystring);
9
+ return {
10
+ ...filters,
11
+ ...pagination,
12
+ ...pageSize,
13
+ };
14
+ }
@@ -0,0 +1,4 @@
1
+ export default function usePageSize(defaultLimit: number): {
2
+ pageSize: number;
3
+ setPageSize: import("react").Dispatch<import("react").SetStateAction<number>>;
4
+ };
@@ -0,0 +1,8 @@
1
+ import { useState } from 'react';
2
+ export default function usePageSize(defaultLimit) {
3
+ const [pageSize, setPageSize] = useState(defaultLimit);
4
+ return {
5
+ pageSize,
6
+ setPageSize,
7
+ };
8
+ }
@@ -0,0 +1,5 @@
1
+ export default function usePagination(skip: number, limit: number, totalRecords: number, shouldUseQuerystring?: boolean): {
2
+ pageNumber: number;
3
+ pagesTotal: number;
4
+ setPageNumber: import("react").Dispatch<import("react").SetStateAction<number>>;
5
+ };
@@ -0,0 +1,12 @@
1
+ import { useState } from "react";
2
+ import { PAGE_FIRST_PAGE } from "../constants";
3
+ export default function usePagination(skip, limit, totalRecords, shouldUseQuerystring) {
4
+ const defaultPageNumber = !!limit ? (skip / limit) + 1 : PAGE_FIRST_PAGE;
5
+ const [pageNumber, setPageNumber] = useState(defaultPageNumber);
6
+ const pagesTotal = Math.ceil(totalRecords / limit);
7
+ return {
8
+ pageNumber,
9
+ pagesTotal,
10
+ setPageNumber,
11
+ };
12
+ }
@@ -0,0 +1,15 @@
1
+ import { IColumnFilter, TColumnFilterType } from "../types";
2
+ import { IFilter, IQuery } from "@vrobots/types";
3
+ export interface ITableFilter {
4
+ [key: string]: {
5
+ type: TColumnFilterType;
6
+ value: string | number | Date;
7
+ };
8
+ }
9
+ export default function useTableFilters(columnFilters: IColumnFilter[], skip: number, limit: number, pageNumber: number, sort?: {
10
+ [key: string]: 1 | -1;
11
+ }, baseFilter?: IFilter, additionalFilters?: IFilter[], shouldUseQuerystring?: boolean): {
12
+ query: IQuery;
13
+ filters: IColumnFilter[];
14
+ onFilterChange: (filter: IColumnFilter) => void;
15
+ };
@@ -0,0 +1,118 @@
1
+ import { usePathname, useRouter, useSearchParams } from "next/navigation";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ export default function useTableFilters(columnFilters, skip, limit, pageNumber, sort, baseFilter, additionalFilters, shouldUseQuerystring) {
4
+ const [filters, setFilters] = useState(columnFilters);
5
+ const pathname = usePathname();
6
+ const router = useRouter();
7
+ const searchParams = useSearchParams();
8
+ const refPath = useRef('');
9
+ const query = useMemo(() => {
10
+ const coreFilters = filters.filter(filter => typeof filter.value !== 'undefined' && filter.value !== null && filter.value !== '');
11
+ const dateFilters = coreFilters
12
+ .filter(filter => filter.type === 'date')
13
+ .map((filter) => ({
14
+ logic: 'and',
15
+ column: filter.accessor,
16
+ operator: filter.operator,
17
+ dataType: filter.type,
18
+ value: new Date(filter?.value).getTime(),
19
+ }));
20
+ const nonDateFilters = coreFilters
21
+ .filter(filter => filter.type !== 'date')
22
+ .map((filter) => {
23
+ const filterParts = filter?.value?.toString().split(' ') || [];
24
+ if (filterParts.length === 1) {
25
+ return {
26
+ logic: 'and',
27
+ column: filter.accessor,
28
+ operator: filter.operator,
29
+ dataType: filter.type,
30
+ value: filter?.value,
31
+ };
32
+ }
33
+ else {
34
+ return {
35
+ logic: 'and',
36
+ filters: filterParts.map((part) => ({
37
+ logic: 'or',
38
+ column: filter.accessor,
39
+ operator: filter.operator,
40
+ dataType: filter.type,
41
+ value: part,
42
+ })),
43
+ };
44
+ }
45
+ });
46
+ return {
47
+ limit,
48
+ skip: (pageNumber - 1) * limit,
49
+ filter: {
50
+ logic: 'and',
51
+ ...baseFilter,
52
+ filters: [
53
+ ...dateFilters,
54
+ ...nonDateFilters,
55
+ ...additionalFilters || []
56
+ ],
57
+ },
58
+ sort,
59
+ };
60
+ }, [limit, pageNumber, filters, baseFilter, additionalFilters, sort]);
61
+ const onFilterChange = (filter) => {
62
+ setFilters((previousFilters) => ([
63
+ ...previousFilters.filter(f => f.accessor !== filter.accessor),
64
+ filter,
65
+ ]));
66
+ };
67
+ useEffect(() => {
68
+ const params = new URLSearchParams(searchParams.toString());
69
+ const incomingQuery = searchParams.get('query');
70
+ if (refPath.current === '' && !!incomingQuery) {
71
+ const query = JSON.parse(incomingQuery);
72
+ const filters = query?.filter?.filters || [];
73
+ const updatedFiltersPopulated = filters.map(filter => {
74
+ const isNestedFilter = Array.isArray(filter.filters);
75
+ const columnFilter = columnFilters.find(f => f.accessor === filter.column);
76
+ if (columnFilter && !isNestedFilter) {
77
+ return {
78
+ ...columnFilter,
79
+ value: filter.value,
80
+ };
81
+ }
82
+ if (isNestedFilter) {
83
+ const nestedColumn = filter.filters?.[0]?.column;
84
+ const nestedColumnFilter = columnFilters.find(f => f.accessor === nestedColumn);
85
+ return {
86
+ ...nestedColumnFilter,
87
+ value: filter.filters?.map(f => f.value).join(' '),
88
+ };
89
+ }
90
+ return;
91
+ })
92
+ .filter(filter => filter !== null);
93
+ const update = columnFilters.map(filter => {
94
+ const updatedFilter = updatedFiltersPopulated.find(f => f.accessor === filter.accessor);
95
+ if (updatedFilter) {
96
+ return updatedFilter;
97
+ }
98
+ return filter;
99
+ });
100
+ if (update?.length) {
101
+ setFilters(update);
102
+ }
103
+ }
104
+ else {
105
+ params.set('query', JSON.stringify(query));
106
+ }
107
+ const path = `${pathname}?${params.toString()}`;
108
+ if (shouldUseQuerystring && refPath.current !== path) {
109
+ refPath.current = path;
110
+ router.push(path);
111
+ }
112
+ }, [filters, pathname, router, shouldUseQuerystring, searchParams, skip, limit, baseFilter, additionalFilters, sort, columnFilters, query]);
113
+ return {
114
+ query,
115
+ filters,
116
+ onFilterChange,
117
+ };
118
+ }
@@ -0,0 +1,11 @@
1
+ export type TFilterChange = (filter: IColumnFilter) => void;
2
+ export type TColumnFilterType = 'date' | 'string' | 'number' | 'boolean';
3
+ export type TColumnFilterOperation = '%' | '!%' | '^%' | '%^' | '=' | '!=' | '>' | '>=' | '<' | '<=' | 'null';
4
+ export interface IColumnFilter {
5
+ id: string;
6
+ accessor: string;
7
+ type: TColumnFilterType;
8
+ operator: TColumnFilterOperation;
9
+ helperText?: string;
10
+ value?: string | number | Date;
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -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
+ };
@@ -0,0 +1,26 @@
1
+ import type { StoryObj } from '@storybook/react-vite';
2
+ import { IDataTableProps } from '../components/table/DataTable';
3
+ declare const meta: {
4
+ title: string;
5
+ component: import("react").FC<IDataTableProps>;
6
+ tags: string[];
7
+ parameters: {
8
+ layout: string;
9
+ };
10
+ args: {
11
+ data: never[];
12
+ total: number;
13
+ pageNumber: number;
14
+ pagesTotal: number;
15
+ pageSize: number;
16
+ setPageNumber: import("react").Dispatch<import("react").SetStateAction<number>>;
17
+ setPageSize: import("react").Dispatch<import("react").SetStateAction<number>>;
18
+ columns: never[];
19
+ };
20
+ };
21
+ export default meta;
22
+ type Story = StoryObj<typeof meta>;
23
+ export declare const Default: Story;
24
+ export declare const WithRowClick: Story;
25
+ export declare const EmptyState: Story;
26
+ export declare const WithFilters: Story;
@@ -0,0 +1,92 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import DataTable from '../components/table/DataTable';
4
+ const noopSetPageNumber = () => undefined;
5
+ const noopSetPageSize = () => undefined;
6
+ const SAMPLE_DATA = [
7
+ { id: 'P-001', name: 'Alice Johnson', dob: '1985-03-12', provider: 'Dr. Smith', status: 'Active', balance: 125.0 },
8
+ { id: 'P-002', name: 'Bob Martinez', dob: '1990-07-24', provider: 'Dr. Patel', status: 'Inactive', balance: 0.0 },
9
+ { id: 'P-003', name: 'Carol White', dob: '1978-11-05', provider: 'Dr. Smith', status: 'Active', balance: 340.5 },
10
+ { id: 'P-004', name: 'David Brown', dob: '2000-01-30', provider: 'Dr. Lee', status: 'Pending', balance: 75.25 },
11
+ { id: 'P-005', name: 'Eva Green', dob: '1995-09-18', provider: 'Dr. Patel', status: 'Active', balance: 210.0 },
12
+ ];
13
+ const COLUMNS = [
14
+ {
15
+ accessorKey: 'id',
16
+ header: 'ID',
17
+ cell: (info) => info.getValue(),
18
+ },
19
+ {
20
+ accessorKey: 'name',
21
+ header: 'Patient Name',
22
+ cell: (info) => info.getValue(),
23
+ },
24
+ {
25
+ accessorKey: 'dob',
26
+ header: 'Date of Birth',
27
+ cell: (info) => info.getValue(),
28
+ },
29
+ {
30
+ accessorKey: 'provider',
31
+ header: 'Provider',
32
+ cell: (info) => info.getValue(),
33
+ },
34
+ {
35
+ accessorKey: 'status',
36
+ header: 'Status',
37
+ cell: (info) => info.getValue(),
38
+ },
39
+ {
40
+ accessorKey: 'balance',
41
+ header: 'Balance',
42
+ cell: (info) => `$${info.getValue().toFixed(2)}`,
43
+ },
44
+ ];
45
+ function DataTableDemo(props) {
46
+ const [pageNumber, setPageNumber] = useState(1);
47
+ const [pageSize, setPageSize] = useState(25);
48
+ return (_jsx(DataTable, { data: SAMPLE_DATA, total: SAMPLE_DATA.length, columns: COLUMNS, pageNumber: pageNumber, pagesTotal: Math.ceil(SAMPLE_DATA.length / pageSize), pageSize: pageSize, setPageNumber: setPageNumber, setPageSize: setPageSize, ...props }));
49
+ }
50
+ const meta = {
51
+ title: 'Data/DataTable',
52
+ component: DataTable,
53
+ tags: ['autodocs'],
54
+ parameters: {
55
+ layout: 'padded',
56
+ },
57
+ args: {
58
+ data: [],
59
+ total: 0,
60
+ pageNumber: 1,
61
+ pagesTotal: 1,
62
+ pageSize: 25,
63
+ setPageNumber: noopSetPageNumber,
64
+ setPageSize: noopSetPageSize,
65
+ columns: [],
66
+ },
67
+ };
68
+ export default meta;
69
+ export const Default = {
70
+ args: {},
71
+ render: () => _jsx(DataTableDemo, {}),
72
+ };
73
+ export const WithRowClick = {
74
+ args: {},
75
+ render: () => (_jsx(DataTableDemo, { onRowClick: (row) => alert(`Clicked: ${row.name}`) })),
76
+ };
77
+ export const EmptyState = {
78
+ args: {},
79
+ render: () => _jsx(EmptyStateDemo, {}),
80
+ };
81
+ function EmptyStateDemo() {
82
+ const [pageNumber, setPageNumber] = useState(1);
83
+ const [pageSize, setPageSize] = useState(25);
84
+ return (_jsx(DataTable, { data: [], total: 0, columns: COLUMNS, pageNumber: pageNumber, pagesTotal: 0, pageSize: pageSize, setPageNumber: setPageNumber, setPageSize: setPageSize }));
85
+ }
86
+ export const WithFilters = {
87
+ args: {},
88
+ render: () => (_jsx(DataTableDemo, { filters: [
89
+ { id: 'name', accessor: 'name', type: 'string', operator: '%', helperText: 'Search by name' },
90
+ { id: 'status', accessor: 'status', type: 'string', operator: '=', helperText: 'Filter by status' },
91
+ ], onFilterChange: (filter) => console.log('Filter changed:', filter) })),
92
+ };
@@ -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.1",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -21,8 +21,10 @@
21
21
  "@chakra-ui/react": "^3.32.0",
22
22
  "@emotion/react": "^11.14.0",
23
23
  "@storybook/addon-themes": "^10.2.16",
24
+ "@tanstack/react-table": "^8.21.3",
24
25
  "axios": "^1.13.4",
25
26
  "motion": "^12.34.0",
27
+ "next": "^16.2.4",
26
28
  "next-themes": "^0.4.6",
27
29
  "owasp-password-strength-test": "^1.3.0",
28
30
  "react": "^19.2.0",