@douglasneuroinformatics/libui 2.5.2 → 2.6.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 (104) hide show
  1. package/README.md +7 -1
  2. package/dist/components/ClientTable/ClientTable.d.ts.map +1 -1
  3. package/dist/components/ClientTable/ClientTable.js +4 -4
  4. package/dist/components/Drawer/Drawer.d.ts +5 -5
  5. package/dist/components/Drawer/DrawerContent.d.ts +1 -1
  6. package/dist/components/Drawer/DrawerDescription.d.ts +1 -1
  7. package/dist/components/Drawer/DrawerDescription.d.ts.map +1 -1
  8. package/dist/components/Drawer/DrawerTitle.d.ts +1 -1
  9. package/dist/components/Drawer/DrawerTitle.d.ts.map +1 -1
  10. package/dist/components/Form/NumberField/NumberFieldRadio.d.ts +1 -1
  11. package/dist/components/Form/NumberField/NumberFieldRadio.d.ts.map +1 -1
  12. package/dist/components/Form/NumberField/NumberFieldRadio.js +7 -4
  13. package/dist/components/Form/NumberField/NumberFieldSelect.d.ts +1 -1
  14. package/dist/components/Form/NumberField/NumberFieldSelect.d.ts.map +1 -1
  15. package/dist/components/Form/NumberField/NumberFieldSelect.js +6 -2
  16. package/dist/douglasneuroinformatics-libui-2.6.0.tgz +0 -0
  17. package/dist/i18n.js +1 -1
  18. package/package.json +60 -59
  19. package/src/components/Accordion/Accordion.spec.tsx +37 -0
  20. package/src/components/Accordion/Accordion.stories.tsx +35 -0
  21. package/src/components/ActionDropdown/ActionDropdown.stories.tsx +17 -0
  22. package/src/components/AlertDialog/AlertDialog.stories.tsx +35 -0
  23. package/src/components/ArrowToggle/ArrowToggle.spec.tsx +49 -0
  24. package/src/components/ArrowToggle/ArrowToggle.stories.tsx +27 -0
  25. package/src/components/Avatar/Avatar.spec.tsx +26 -0
  26. package/src/components/Avatar/Avatar.stories.tsx +20 -0
  27. package/src/components/Badge/Badge.spec.tsx +19 -0
  28. package/src/components/Badge/Badge.stories.tsx +13 -0
  29. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +44 -0
  30. package/src/components/Button/Button.spec.tsx +27 -0
  31. package/src/components/Button/Button.stories.tsx +63 -0
  32. package/src/components/Card/Card.spec.tsx +19 -0
  33. package/src/components/Card/Card.stories.tsx +56 -0
  34. package/src/components/Checkbox/Checkbox.spec.tsx +28 -0
  35. package/src/components/Checkbox/Checkbox.stories.tsx +34 -0
  36. package/src/components/ClientTable/ClientTable.stories.tsx +126 -0
  37. package/src/components/ClientTable/ClientTable.tsx +5 -7
  38. package/src/components/Collapsible/Collapsible.stories.tsx +45 -0
  39. package/src/components/Command/Command.stories.tsx +55 -0
  40. package/src/components/ContextMenu/ContextMenu.stories.tsx +61 -0
  41. package/src/components/DatePicker/DatePicker.stories.tsx +15 -0
  42. package/src/components/Dialog/Dialog.stories.tsx +44 -0
  43. package/src/components/Drawer/Drawer.stories.tsx +37 -0
  44. package/src/components/DropdownButton/DropdownButton.stories.tsx +13 -0
  45. package/src/components/DropdownMenu/DropdownMenu.stories.tsx +78 -0
  46. package/src/components/ErrorFallback/ErrorFallback.stories.tsx +9 -0
  47. package/src/components/Form/BooleanField/BooleanField.spec.tsx +35 -0
  48. package/src/components/Form/BooleanField/BooleanField.stories.tsx +49 -0
  49. package/src/components/Form/DateField/DateField.spec.tsx +99 -0
  50. package/src/components/Form/DateField/DateField.stories.tsx +28 -0
  51. package/src/components/Form/Form.stories.tsx +360 -0
  52. package/src/components/Form/Form.test.tsx +119 -0
  53. package/src/components/Form/NumberField/NumberField.stories.tsx +103 -0
  54. package/src/components/Form/NumberField/NumberFieldRadio.tsx +16 -8
  55. package/src/components/Form/NumberField/NumberFieldSelect.tsx +10 -6
  56. package/src/components/Form/SetField/SetField.stories.tsx +63 -0
  57. package/src/components/Form/StringField/StringField.stories.tsx +94 -0
  58. package/src/components/Heading/Heading.stories.tsx +37 -0
  59. package/src/components/HoverCard/HoverCard.stories.tsx +40 -0
  60. package/src/components/Input/Input.spec.tsx +23 -0
  61. package/src/components/Input/Input.stories.tsx +9 -0
  62. package/src/components/Label/Label.spec.tsx +17 -0
  63. package/src/components/Label/Label.stories.tsx +13 -0
  64. package/src/components/LanguageToggle/LanguageToggle.stories.tsx +17 -0
  65. package/src/components/LineGraph/LineGraph.stories.tsx +81 -0
  66. package/src/components/ListboxDropdown/ListboxDropdown.stories.tsx +44 -0
  67. package/src/components/MenuBar/MenuBar.stories.tsx +101 -0
  68. package/src/components/NotificationHub/NotificationHub.stories.tsx +41 -0
  69. package/src/components/Pagination/Pagination.stories.tsx +41 -0
  70. package/src/components/Popover/Popover.spec.tsx +24 -0
  71. package/src/components/Popover/Popover.stories.tsx +72 -0
  72. package/src/components/Progress/Progress.stories.tsx +24 -0
  73. package/src/components/RadioGroup/RadioGroup.spec.tsx +42 -0
  74. package/src/components/RadioGroup/RadioGroup.stories.tsx +35 -0
  75. package/src/components/Resizable/Resizable.stories.tsx +39 -0
  76. package/src/components/ScrollArea/ScrollArea.spec.tsx +19 -0
  77. package/src/components/ScrollArea/ScrollArea.stories.tsx +23 -0
  78. package/src/components/SearchBar/SearchBar.stories.tsx +11 -0
  79. package/src/components/Select/Select.stories.tsx +31 -0
  80. package/src/components/Separator/Separator.spec.tsx +19 -0
  81. package/src/components/Separator/Separator.stories.tsx +30 -0
  82. package/src/components/Sheet/Sheet.stories.tsx +49 -0
  83. package/src/components/Slider/Slider.stories.tsx +9 -0
  84. package/src/components/Spinner/Spinner.stories.tsx +14 -0
  85. package/src/components/SpinnerIcon/SpinnerIcon.stories.tsx +9 -0
  86. package/src/components/Switch/Switch.stories.tsx +21 -0
  87. package/src/components/Table/Table.stories.tsx +88 -0
  88. package/src/components/Tabs/Tabs.stories.tsx +70 -0
  89. package/src/components/TextArea/TextArea.spec.tsx +23 -0
  90. package/src/components/TextArea/TextArea.stories.tsx +13 -0
  91. package/src/components/ThemeToggle/ThemeToggle.stories.tsx +9 -0
  92. package/src/components/Tooltip/Tooltip.stories.tsx +44 -0
  93. package/src/hooks/useDownload.test.ts +66 -0
  94. package/src/hooks/useEventCallback.test.tsx +22 -0
  95. package/src/hooks/useEventListener.test.tsx +120 -0
  96. package/src/hooks/useInterval.test.ts +58 -0
  97. package/src/hooks/useIsomorphicLayoutEffect.test.ts +27 -0
  98. package/src/hooks/useMediaQuery.test.ts +33 -0
  99. package/src/hooks/useNotificationsStore.test.ts +30 -0
  100. package/src/hooks/useOnClickOutside.test.ts +59 -0
  101. package/src/hooks/useSessionStorage.test.ts +186 -0
  102. package/src/hooks/useTheme.test.ts +74 -0
  103. package/src/hooks/useWindowSize.test.ts +57 -0
  104. package/src/i18n.ts +1 -1
package/README.md CHANGED
@@ -48,12 +48,18 @@ pnpm install @douglasneuroinformatics/libui
48
48
  const baseConfig = require('@douglasneuroinformatics/libui/tailwind.config.cjs');
49
49
 
50
50
  /** @type {import('tailwindcss').Config} */
51
- module.exports {
51
+ module.exports = {
52
52
  content: [...baseConfig.content, './src/**/*.{js,ts,jsx,tsx}'],
53
53
  presets: [baseConfig]
54
54
  };
55
55
  ```
56
56
 
57
+ **main.tsx**
58
+
59
+ ```js
60
+ import '@douglasneuroinformatics/libui/styles/globals.css';
61
+ ```
62
+
57
63
  ## Contributing
58
64
 
59
65
  We welcome contributions! If you're interested in improving the library or adding new features, please refer to our contribution guide.
@@ -1 +1 @@
1
- {"version":3,"file":"ClientTable.d.ts","sourceRoot":"","sources":["../../../src/components/ClientTable/ClientTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AA0BxC,MAAM,MAAM,gBAAgB,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE1D,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,gBAAgB,GAAG,gBAAgB,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;AAErG,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,gBAAgB,IAAI;IAC1D,6CAA6C;IAC7C,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;IAEvC,oDAAoD;IAEpD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAC;IAEnC,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,SAAS,gBAAgB,IAAI;IACnE,IAAI,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;CACrD,EAAE,CAAC;AAEJ,MAAM,MAAM,sBAAsB,CAAC,CAAC,SAAS,gBAAgB,IAAI;IAC/D,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC7B,eAAe,CAAC,EAAE,0BAA0B,CAAC,CAAC,CAAC,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,IAAI;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qBAAqB,CAAC,EAAE,0BAA0B,CAAC,CAAC,CAAC,CAAC;IACtD,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;IAChC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;CACnC,CAAC;AAEF,eAAO,MAAM,WAAW,mIASrB,iBAAiB,CAAC,CAAC,sBAoGrB,CAAC"}
1
+ {"version":3,"file":"ClientTable.d.ts","sourceRoot":"","sources":["../../../src/components/ClientTable/ClientTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AA0BxC,MAAM,MAAM,gBAAgB,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE1D,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,gBAAgB,GAAG,gBAAgB,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;AAErG,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,gBAAgB,IAAI;IAC1D,6CAA6C;IAC7C,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;IAEvC,oDAAoD;IACpD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAC;IAEnC,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,SAAS,gBAAgB,IAAI;IACnE,IAAI,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;CACrD,EAAE,CAAC;AAEJ,MAAM,MAAM,sBAAsB,CAAC,CAAC,SAAS,gBAAgB,IAAI;IAC/D,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC7B,eAAe,CAAC,EAAE,0BAA0B,CAAC,CAAC,CAAC,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,IAAI;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qBAAqB,CAAC,EAAE,0BAA0B,CAAC,CAAC,CAAC,CAAC;IACtD,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;IAChC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;CACnC,CAAC;AAEF,eAAO,MAAM,WAAW,mIASrB,iBAAiB,CAAC,CAAC,sBAmGrB,CAAC"}
@@ -48,9 +48,8 @@ export const ClientTable = ({ className, columnDropdownOptions, columns, data, e
48
48
  }))))) : (column.label)))))),
49
49
  React.createElement(Table.Body, null, range(nRows).map((i) => {
50
50
  const entry = currentEntries[i];
51
- return (React.createElement(Table.Row, { className: cn(typeof onEntryClick === 'function' && 'cursor-pointer hover:backdrop-brightness-95'), key: i, onClick: () => {
52
- onEntryClick?.(entry);
53
- } }, columns.map(({ field, formatter }, j) => {
51
+ const onClick = onEntryClick && entry ? () => onEntryClick(entry) : undefined;
52
+ return (React.createElement(Table.Row, { className: cn(onClick && 'cursor-pointer hover:backdrop-brightness-95'), key: i, onClick: onClick }, columns.map(({ field, formatter }, j) => {
54
53
  let value;
55
54
  if (!entry) {
56
55
  value = 'NA';
@@ -62,7 +61,8 @@ export const ClientTable = ({ className, columnDropdownOptions, columns, data, e
62
61
  value = entry[field];
63
62
  }
64
63
  const formattedValue = entry && formatter ? formatter(value) : defaultFormatter(value);
65
- return (React.createElement(Table.Cell, { className: cn('text-ellipsis leading-none', !entry && 'invisible', noWrap && 'max-w-3xl overflow-hidden text-ellipsis whitespace-nowrap'), key: j }, formattedValue));
64
+ return (React.createElement(Table.Cell, { className: cn('text-ellipsis leading-none', !entry && 'opacity-0', // safari does not include borders if invisible
65
+ noWrap && 'max-w-3xl overflow-hidden text-ellipsis whitespace-nowrap'), key: j }, formattedValue));
66
66
  })));
67
67
  })))),
68
68
  React.createElement(ClientTablePagination, { currentPage: currentPage, firstEntry: firstEntry, lastEntry: lastEntry, pageCount: pageCount, setCurrentPage: setCurrentPage, totalEntries: data.length })));
@@ -1,14 +1,14 @@
1
1
  import React from 'react';
2
2
  import { Drawer as DrawerPrimitive } from 'vaul';
3
3
  export declare const Drawer: (({ shouldScaleBackground, ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) => React.JSX.Element) & {
4
- Close: React.ForwardRefExoticComponent<import("@radix-ui/react-dialog", { with: { "resolution-mode": "require" } }).DialogCloseProps & React.RefAttributes<HTMLButtonElement>>;
5
- Content: React.ForwardRefExoticComponent<Omit<Omit<import("@radix-ui/react-dialog", { with: { "resolution-mode": "require" } }).DialogContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
4
+ Close: React.ForwardRefExoticComponent<import("@radix-ui/react-dialog").DialogCloseProps & React.RefAttributes<HTMLButtonElement>>;
5
+ Content: React.ForwardRefExoticComponent<Omit<Omit<import("@radix-ui/react-dialog").DialogContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
6
6
  onAnimationEnd?: ((open: boolean) => void) | undefined;
7
7
  } & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
8
- Description: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-dialog", { with: { "resolution-mode": "require" } }).DialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
8
+ Description: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-dialog").DialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
9
9
  Footer: ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => React.JSX.Element;
10
10
  Header: ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => React.JSX.Element;
11
- Title: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-dialog", { with: { "resolution-mode": "require" } }).DialogTitleProps & React.RefAttributes<HTMLHeadingElement>, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
12
- Trigger: React.ForwardRefExoticComponent<import("@radix-ui/react-dialog", { with: { "resolution-mode": "require" } }).DialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
11
+ Title: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-dialog").DialogTitleProps & React.RefAttributes<HTMLHeadingElement>, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
12
+ Trigger: React.ForwardRefExoticComponent<import("@radix-ui/react-dialog").DialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
13
13
  };
14
14
  //# sourceMappingURL=Drawer.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- export declare const DrawerContent: React.ForwardRefExoticComponent<Omit<Omit<import("@radix-ui/react-dialog", { with: { "resolution-mode": "require" } }).DialogContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
2
+ export declare const DrawerContent: React.ForwardRefExoticComponent<Omit<Omit<import("@radix-ui/react-dialog").DialogContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
3
3
  onAnimationEnd?: ((open: boolean) => void) | undefined;
4
4
  } & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
5
5
  //# sourceMappingURL=DrawerContent.d.ts.map
@@ -1,3 +1,3 @@
1
1
  import React from 'react';
2
- export declare const DrawerDescription: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-dialog", { with: { "resolution-mode": "require" } }).DialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
2
+ export declare const DrawerDescription: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-dialog").DialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
3
3
  //# sourceMappingURL=DrawerDescription.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DrawerDescription.d.ts","sourceRoot":"","sources":["../../../src/components/Drawer/DrawerDescription.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,eAAO,MAAM,iBAAiB,2OAO5B,CAAC"}
1
+ {"version":3,"file":"DrawerDescription.d.ts","sourceRoot":"","sources":["../../../src/components/Drawer/DrawerDescription.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,eAAO,MAAM,iBAAiB,+LAO5B,CAAC"}
@@ -1,3 +1,3 @@
1
1
  import React from 'react';
2
- export declare const DrawerTitle: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-dialog", { with: { "resolution-mode": "require" } }).DialogTitleProps & React.RefAttributes<HTMLHeadingElement>, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
2
+ export declare const DrawerTitle: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-dialog").DialogTitleProps & React.RefAttributes<HTMLHeadingElement>, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
3
3
  //# sourceMappingURL=DrawerTitle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DrawerTitle.d.ts","sourceRoot":"","sources":["../../../src/components/Drawer/DrawerTitle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,eAAO,MAAM,WAAW,iOAWtB,CAAC"}
1
+ {"version":3,"file":"DrawerTitle.d.ts","sourceRoot":"","sources":["../../../src/components/Drawer/DrawerTitle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,eAAO,MAAM,WAAW,qLAWtB,CAAC"}
@@ -5,5 +5,5 @@ import type { BaseFieldComponentProps } from '../types.js';
5
5
  export type NumberFieldRadioProps = Simplify<BaseFieldComponentProps<number> & Extract<NumberFormField, {
6
6
  options: object;
7
7
  }>>;
8
- export declare const NumberFieldRadio: ({ description, error, label, name, options, readOnly, setValue, value }: NumberFieldRadioProps) => React.JSX.Element;
8
+ export declare const NumberFieldRadio: ({ description, disableAutoPrefix, error, label, name, options, readOnly, setValue, value }: NumberFieldRadioProps) => React.JSX.Element;
9
9
  //# sourceMappingURL=NumberFieldRadio.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NumberFieldRadio.d.ts","sourceRoot":"","sources":["../../../../src/components/Form/NumberField/NumberFieldRadio.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAO1C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAC1C,uBAAuB,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,eAAe,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAChF,CAAC;AAEF,eAAO,MAAM,gBAAgB,4EAS1B,qBAAqB,sBAiCvB,CAAC"}
1
+ {"version":3,"file":"NumberFieldRadio.d.ts","sourceRoot":"","sources":["../../../../src/components/Form/NumberField/NumberFieldRadio.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAO1C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAC1C,uBAAuB,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,eAAe,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAChF,CAAC;AAEF,eAAO,MAAM,gBAAgB,+FAU1B,qBAAqB,sBAwCvB,CAAC"}
@@ -3,7 +3,7 @@ import { cn } from '../../../utils.js';
3
3
  import { Label } from '../../Label/Label.js';
4
4
  import { RadioGroup } from '../../RadioGroup/RadioGroup.js';
5
5
  import { FieldGroup } from '../FieldGroup/FieldGroup.js';
6
- export const NumberFieldRadio = ({ description, error, label, name, options, readOnly, setValue, value }) => {
6
+ export const NumberFieldRadio = ({ description, disableAutoPrefix, error, label, name, options, readOnly, setValue, value }) => {
7
7
  const optionsCount = Object.keys(options).length;
8
8
  return (React.createElement(FieldGroup, null,
9
9
  React.createElement(FieldGroup.Row, null,
@@ -12,8 +12,11 @@ export const NumberFieldRadio = ({ description, error, label, name, options, rea
12
12
  React.createElement(RadioGroup, { className: cn('flex', optionsCount > 5 ? 'flex-col' : 'flex-col @3xl:flex-row @3xl:items-center @3xl:justify-between'), name: name, value: value?.toString() ?? '', onValueChange: (value) => setValue(parseInt(value)) }, Object.keys(options)
13
13
  .map((val) => parseInt(val))
14
14
  .toSorted((a, b) => a - b)
15
- .map((val) => (React.createElement("div", { className: "flex items-center gap-2", key: val },
16
- React.createElement(RadioGroup.Item, { disabled: readOnly, id: `${name}-${val}`, value: val.toString() }),
17
- React.createElement(Label, { "aria-disabled": readOnly, className: "font-normal text-muted-foreground", htmlFor: `${name}-${val}` }, `${val} - ${options[val]}`))))),
15
+ .map((val) => {
16
+ const text = (disableAutoPrefix ? '' : `${val} - `) + options[val];
17
+ return (React.createElement("div", { className: "flex items-center gap-2", key: val },
18
+ React.createElement(RadioGroup.Item, { disabled: readOnly, id: `${name}-${val}`, value: val.toString() }),
19
+ React.createElement(Label, { "aria-disabled": readOnly, className: "font-normal text-muted-foreground", htmlFor: `${name}-${val}` }, text)));
20
+ })),
18
21
  React.createElement(FieldGroup.Error, { error: error })));
19
22
  };
@@ -5,5 +5,5 @@ import { type BaseFieldComponentProps } from '../types.js';
5
5
  export type NumberFieldSelectProps<T extends number = number> = Simplify<BaseFieldComponentProps<T> & Extract<NumberFormField<T>, {
6
6
  options: object;
7
7
  }>>;
8
- export declare const NumberFieldSelect: <T extends number = number>({ description, error, label, name, options, readOnly, setValue, value }: NumberFieldSelectProps<T>) => React.JSX.Element;
8
+ export declare const NumberFieldSelect: <T extends number = number>({ description, disableAutoPrefix, error, label, name, options, readOnly, setValue, value }: NumberFieldSelectProps<T>) => React.JSX.Element;
9
9
  //# sourceMappingURL=NumberFieldSelect.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NumberFieldSelect.d.ts","sourceRoot":"","sources":["../../../../src/components/Form/NumberField/NumberFieldSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAK1C,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,MAAM,sBAAsB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CACtE,uBAAuB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAC9E,CAAC;AAEF,eAAO,MAAM,iBAAiB,uGAS3B,uBAAuB,CAAC,CAAC,sBAuB3B,CAAC"}
1
+ {"version":3,"file":"NumberFieldSelect.d.ts","sourceRoot":"","sources":["../../../../src/components/Form/NumberField/NumberFieldSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAK1C,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,MAAM,sBAAsB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CACtE,uBAAuB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAC9E,CAAC;AAEF,eAAO,MAAM,iBAAiB,0HAU3B,uBAAuB,CAAC,CAAC,sBA0B3B,CAAC"}
@@ -3,7 +3,7 @@ import { Label } from '../../Label/Label.js';
3
3
  import { Select } from '../../Select/Select.js';
4
4
  import { FieldGroup } from '../FieldGroup/FieldGroup.js';
5
5
  import {} from '../types.js';
6
- export const NumberFieldSelect = ({ description, error, label, name, options, readOnly, setValue, value }) => {
6
+ export const NumberFieldSelect = ({ description, disableAutoPrefix, error, label, name, options, readOnly, setValue, value }) => {
7
7
  return (React.createElement(FieldGroup, null,
8
8
  React.createElement(FieldGroup.Row, null,
9
9
  React.createElement(Label, null, label),
@@ -11,6 +11,10 @@ export const NumberFieldSelect = ({ description, error, label, name, options, re
11
11
  React.createElement(Select, { name: name, value: value?.toString() ?? '', onValueChange: (value) => setValue(parseFloat(value)) },
12
12
  React.createElement(Select.Trigger, { "data-cy": `${name}-select-trigger`, "data-testid": `${name}-select-trigger`, disabled: readOnly },
13
13
  React.createElement(Select.Value, null)),
14
- React.createElement(Select.Content, { "data-cy": `${name}-select-content`, "data-testid": `${name}-select-content` }, Object.keys(options).map((option) => (React.createElement(Select.Item, { key: option, value: option }, `${option} - ${options[option]}`))))),
14
+ React.createElement(Select.Content, { "data-cy": `${name}-select-content`, "data-testid": `${name}-select-content` }, Object.keys(options).map((option) => {
15
+ // Option needs to be type number (this was a design flaw), but is actually always coerced to string anyways
16
+ const text = (disableAutoPrefix ? '' : `${option} - `) + options[option];
17
+ return (React.createElement(Select.Item, { key: option, value: option }, text));
18
+ }))),
15
19
  React.createElement(FieldGroup.Error, { error: error })));
16
20
  };
package/dist/i18n.js CHANGED
@@ -6,7 +6,7 @@ import { initReactI18next } from 'react-i18next';
6
6
  import libui from './translations/libui.json';
7
7
  export const supportedLngs = ['en', 'fr'];
8
8
  export function transformTranslations(translations, locale) {
9
- if (!isPlainObject) {
9
+ if (!isPlainObject(translations)) {
10
10
  throw new Error('Invalid format of translations: must be plain object');
11
11
  }
12
12
  const result = {};
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@douglasneuroinformatics/libui",
3
3
  "type": "module",
4
- "version": "2.5.2",
5
- "packageManager": "pnpm@8.15.3",
4
+ "version": "2.6.0",
5
+ "packageManager": "pnpm@9.3.0",
6
6
  "description": "Generic UI components for DNP projects, built using React and TailwindCSS",
7
7
  "author": {
8
8
  "name": "Douglas Neuroinformatics",
@@ -50,15 +50,25 @@
50
50
  "/tailwind.config.cjs",
51
51
  "/tailwind.config.d.cts"
52
52
  ],
53
+ "scripts": {
54
+ "build": "rm -rf dist && tsc -b tsconfig.build.json && cp -r src/styles dist",
55
+ "format": "prettier --write src",
56
+ "format:translations": "find src/translations -name '*.json' -exec pnpm exec sort-json {} \\;",
57
+ "lint": "tsc && eslint --fix src",
58
+ "prepare": "husky",
59
+ "storybook": "storybook dev --no-open -p 6006",
60
+ "storybook:build": "storybook build",
61
+ "test": "vitest",
62
+ "test:coverage": "vitest --coverage"
63
+ },
53
64
  "peerDependencies": {
54
65
  "react": "^18.2.0",
55
66
  "react-dom": "^18.2.0"
56
67
  },
57
68
  "dependencies": {
58
69
  "@douglasneuroinformatics/libjs": "^0.3.1",
59
- "@douglasneuroinformatics/libui-form-types": "^0.7.0",
60
- "@headlessui/react": "^1.7.18",
61
- "@headlessui/tailwindcss": "^0.2.0",
70
+ "@douglasneuroinformatics/libui-form-types": "^0.8.0",
71
+ "@headlessui/tailwindcss": "^0.2.1",
62
72
  "@heroicons/react": "^2.1.3",
63
73
  "@radix-ui/react-accordion": "^1.1.2",
64
74
  "@radix-ui/react-alert-dialog": "^1.0.5",
@@ -84,77 +94,68 @@
84
94
  "@radix-ui/react-tooltip": "^1.0.7",
85
95
  "@tailwindcss/container-queries": "^0.1.1",
86
96
  "class-variance-authority": "^0.7.0",
87
- "clsx": "^2.1.0",
97
+ "clsx": "^2.1.1",
88
98
  "cmdk": "^1.0.0",
89
- "framer-motion": "^11.0.24",
99
+ "framer-motion": "^11.2.10",
90
100
  "i18next": "23.x",
91
- "i18next-browser-languagedetector": "^7.2.1",
101
+ "i18next-browser-languagedetector": "^8.0.0",
92
102
  "lodash-es": "^4.17.21",
93
- "lucide-react": "^0.364.0",
103
+ "lucide-react": "^0.390.0",
94
104
  "react-error-boundary": "^4.0.13",
95
- "react-i18next": "^14.1.0",
96
- "react-resizable-panels": "^2.0.16",
97
- "recharts": "^2.12.3",
98
- "tailwind-merge": "^2.2.2",
105
+ "react-i18next": "^14.1.2",
106
+ "react-resizable-panels": "^2.0.19",
107
+ "recharts": "^2.12.7",
108
+ "tailwind-merge": "^2.3.0",
99
109
  "tailwindcss-animate": "^1.0.7",
100
- "ts-pattern": "^5.1.0",
101
- "type-fest": "^4.15.0",
102
- "vaul": "^0.9.0",
103
- "zod": "^3.22.4",
110
+ "ts-pattern": "^5.1.2",
111
+ "type-fest": "^4.20.0",
112
+ "vaul": "^0.9.1",
113
+ "zod": "^3.23.8",
104
114
  "zustand": "^4.5.2"
105
115
  },
106
116
  "devDependencies": {
107
- "@commitlint/cli": "^19.2.1",
108
- "@commitlint/config-conventional": "^19.1.0",
117
+ "@commitlint/cli": "^19.3.0",
118
+ "@commitlint/config-conventional": "^19.2.2",
109
119
  "@commitlint/types": "^19.0.3",
110
- "@douglasneuroinformatics/eslint-config": "^4.2.1",
120
+ "@douglasneuroinformatics/eslint-config": "^4.2.2",
111
121
  "@semantic-release/changelog": "^6.0.3",
112
122
  "@semantic-release/git": "^10.0.1",
113
- "@semantic-release/npm": "^12.0.0",
114
- "@storybook/addon-essentials": "^8.0.5",
115
- "@storybook/addon-interactions": "^8.0.5",
116
- "@storybook/addon-links": "^8.0.5",
117
- "@storybook/blocks": "^8.0.5",
118
- "@storybook/components": "^8.0.5",
123
+ "@semantic-release/npm": "^12.0.1",
124
+ "@storybook/addon-essentials": "^8.1.6",
125
+ "@storybook/addon-interactions": "^8.1.6",
126
+ "@storybook/addon-links": "^8.1.6",
127
+ "@storybook/blocks": "^8.1.6",
128
+ "@storybook/components": "^8.1.6",
119
129
  "@storybook/icons": "^1.2.9",
120
- "@storybook/manager-api": "^8.0.5",
121
- "@storybook/react": "^8.0.5",
122
- "@storybook/react-vite": "^8.0.5",
123
- "@storybook/theming": "^8.0.5",
124
- "@testing-library/jest-dom": "^6.4.2",
125
- "@testing-library/react": "14.2.2",
130
+ "@storybook/manager-api": "^8.1.6",
131
+ "@storybook/react": "^8.1.6",
132
+ "@storybook/react-vite": "^8.1.6",
133
+ "@storybook/theming": "^8.1.6",
134
+ "@testing-library/dom": "^10.1.0",
135
+ "@testing-library/jest-dom": "^6.4.5",
136
+ "@testing-library/react": "16.0.0",
126
137
  "@testing-library/user-event": "^14.5.2",
127
138
  "@types/lodash-es": "^4.17.12",
128
- "@types/node": "^20.12.3",
129
- "@types/react": "^18.2.74",
130
- "@types/react-dom": "^18.2.23",
131
- "@vitejs/plugin-react-swc": "^3.6.0",
132
- "@vitest/coverage-v8": "^1.4.0",
139
+ "@types/node": "^20.14.2",
140
+ "@types/react": "^18.3.3",
141
+ "@types/react-dom": "^18.3.0",
142
+ "@vitejs/plugin-react-swc": "^3.7.0",
143
+ "@vitest/coverage-v8": "^1.6.0",
133
144
  "autoprefixer": "^10.4.19",
134
145
  "eslint": "^8.57.0",
135
- "happy-dom": "^14.3.10",
146
+ "happy-dom": "^14.12.0",
136
147
  "husky": "^9.0.11",
137
- "jsdom": "24.0.0",
148
+ "jsdom": "24.1.0",
138
149
  "postcss": "^8.4.38",
139
- "prettier": "^3.2.5",
140
- "prettier-plugin-tailwindcss": "^0.5.13",
141
- "semantic-release": "^23.0.7",
150
+ "prettier": "^3.3.1",
151
+ "prettier-plugin-tailwindcss": "^0.6.2",
152
+ "semantic-release": "^23.0.8",
142
153
  "sort-json": "^2.0.1",
143
- "storybook": "^8.0.5",
144
- "storybook-react-i18next": "^3.0.1",
145
- "tailwindcss": "^3.4.3",
146
- "typescript": "~5.4.3",
147
- "vite": "5.2.8",
148
- "vitest": "^1.4.0"
149
- },
150
- "scripts": {
151
- "build": "rm -rf dist && tsc -b tsconfig.build.json && cp -r src/styles dist",
152
- "format": "prettier --write src",
153
- "format:translations": "find src/translations -name '*.json' -exec pnpm exec sort-json {} \\;",
154
- "lint": "tsc && eslint --fix src",
155
- "storybook": "storybook dev --no-open -p 6006",
156
- "storybook:build": "storybook build",
157
- "test": "vitest",
158
- "test:coverage": "vitest --coverage"
154
+ "storybook": "^8.1.6",
155
+ "storybook-react-i18next": "^3.1.1",
156
+ "tailwindcss": "^3.4.4",
157
+ "typescript": "~5.4.5",
158
+ "vite": "5.2.13",
159
+ "vitest": "^1.6.0"
159
160
  }
160
- }
161
+ }
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+
3
+ import { fireEvent, render, screen } from '@testing-library/react';
4
+ import { describe, expect, it } from 'vitest';
5
+
6
+ import { Accordion } from './Accordion.js';
7
+
8
+ const TEST_ID = 'accordion';
9
+
10
+ const TestAccordion = () => (
11
+ <Accordion collapsible type="single">
12
+ <Accordion.Item value="item-1">
13
+ <Accordion.Trigger>T1</Accordion.Trigger>
14
+ <Accordion.Content>C1</Accordion.Content>
15
+ </Accordion.Item>
16
+ </Accordion>
17
+ );
18
+
19
+ describe('Accordion', () => {
20
+ it('should render', () => {
21
+ render(<TestAccordion />);
22
+ expect(screen.getByTestId(TEST_ID)).toBeDefined();
23
+ });
24
+ it('should open and close an item', () => {
25
+ render(<TestAccordion />);
26
+
27
+ const toggle = screen.getByText('T1');
28
+ expect(toggle).toBeInTheDocument();
29
+ expect(() => screen.getByText('C1')).toThrow();
30
+
31
+ fireEvent.click(toggle);
32
+ expect(screen.getByText('C1')).toBeInTheDocument();
33
+
34
+ fireEvent.click(toggle);
35
+ expect(() => screen.getByText('C1')).toThrow();
36
+ });
37
+ });
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+
3
+ import type { Meta, StoryObj } from '@storybook/react';
4
+
5
+ import { Accordion } from './Accordion.js';
6
+
7
+ type Story = StoryObj<typeof Accordion>;
8
+
9
+ export default { component: Accordion, tags: ['autodocs'] } as Meta<typeof Accordion>;
10
+
11
+ export const Default: Story = {
12
+ args: {
13
+ children: (
14
+ <>
15
+ <Accordion.Item value="item-1">
16
+ <Accordion.Trigger>Is it accessible?</Accordion.Trigger>
17
+ <Accordion.Content>Yes. It adheres to the WAI-ARIA design pattern.</Accordion.Content>
18
+ </Accordion.Item>
19
+ <Accordion.Item value="item-2">
20
+ <Accordion.Trigger>Is it styled?</Accordion.Trigger>
21
+ <Accordion.Content>
22
+ Yes. It comes with default styles that matches the other components&apos; aesthetic.
23
+ </Accordion.Content>
24
+ </Accordion.Item>
25
+ <Accordion.Item value="item-3">
26
+ <Accordion.Trigger>Is it animated?</Accordion.Trigger>
27
+ <Accordion.Content>Yes. It is animated by default, but you can disable it if you prefer.</Accordion.Content>
28
+ </Accordion.Item>
29
+ </>
30
+ ),
31
+ collapsible: true,
32
+ id: 'accordion',
33
+ type: 'single'
34
+ }
35
+ };
@@ -0,0 +1,17 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import { ActionDropdown } from './ActionDropdown.js';
4
+
5
+ type Story = StoryObj<typeof ActionDropdown>;
6
+
7
+ export default { component: ActionDropdown } satisfies Meta<typeof ActionDropdown>;
8
+
9
+ export const Default: Story = {
10
+ args: {
11
+ onSelection(option) {
12
+ alert(option);
13
+ },
14
+ options: ['Option 1', 'Option 2'],
15
+ title: 'Action Dropdown'
16
+ }
17
+ };
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+
3
+ import type { Meta, StoryObj } from '@storybook/react';
4
+
5
+ import { Button } from '../Button/Button.js';
6
+ import { AlertDialog } from './AlertDialog.js';
7
+
8
+ type Story = StoryObj<typeof AlertDialog>;
9
+
10
+ export default { component: AlertDialog } as Meta<typeof AlertDialog>;
11
+
12
+ export const Default: Story = {
13
+ args: {
14
+ children: (
15
+ <React.Fragment>
16
+ <AlertDialog.Trigger asChild>
17
+ <Button variant="outline">Show Dialog</Button>
18
+ </AlertDialog.Trigger>
19
+ <AlertDialog.Content>
20
+ <AlertDialog.Header>
21
+ <AlertDialog.Title>Are you absolutely sure?</AlertDialog.Title>
22
+ <AlertDialog.Description>
23
+ This action cannot be undone. This will permanently delete your account and remove your data from our
24
+ servers.
25
+ </AlertDialog.Description>
26
+ </AlertDialog.Header>
27
+ <AlertDialog.Footer>
28
+ <AlertDialog.Action>Continue</AlertDialog.Action>
29
+ <AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
30
+ </AlertDialog.Footer>
31
+ </AlertDialog.Content>
32
+ </React.Fragment>
33
+ )
34
+ }
35
+ };
@@ -0,0 +1,49 @@
1
+ import React, { useState } from 'react';
2
+
3
+ import { fireEvent, render, screen } from '@testing-library/react';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+
6
+ import { ArrowToggle, type ArrowToggleProps } from './ArrowToggle.js';
7
+
8
+ const TestArrowToggle = ({ onClick, ...props }: Omit<ArrowToggleProps, 'isToggled'>) => {
9
+ const [isToggled, setIsToggled] = useState(false);
10
+ return (
11
+ <ArrowToggle
12
+ isToggled={isToggled}
13
+ onClick={(event) => {
14
+ setIsToggled(!isToggled);
15
+ onClick?.(event);
16
+ }}
17
+ {...props}
18
+ />
19
+ );
20
+ };
21
+
22
+ describe('ArrowToggle', () => {
23
+ it('renders with default props', () => {
24
+ render(<TestArrowToggle position="up" rotation={90} />);
25
+ const button = screen.getByRole('button');
26
+ expect(button).toBeInTheDocument();
27
+ });
28
+
29
+ it('renders with a custom class', () => {
30
+ render(<TestArrowToggle className="custom-class" position="down" rotation={90} />);
31
+ expect(screen.getByRole('button')).toHaveClass('custom-class');
32
+ });
33
+
34
+ it('toggles rotation on click', () => {
35
+ render(<TestArrowToggle position="right" rotation={90} />);
36
+ const button = screen.getByRole('button');
37
+ fireEvent.click(button);
38
+ expect(screen.getByTestId('arrow-up-icon')).toHaveStyle('transform: rotate(180deg)');
39
+ fireEvent.click(button);
40
+ expect(screen.getByTestId('arrow-up-icon')).toHaveStyle('transform: rotate(90deg)');
41
+ });
42
+
43
+ it('handles custom onClick', () => {
44
+ const handleClick = vi.fn();
45
+ render(<TestArrowToggle position="up" rotation={90} onClick={handleClick} />);
46
+ fireEvent.click(screen.getByRole('button'));
47
+ expect(handleClick).toHaveBeenCalledTimes(1);
48
+ });
49
+ });
@@ -0,0 +1,27 @@
1
+ import React, { useState } from 'react';
2
+
3
+ import type { Meta, StoryObj } from '@storybook/react';
4
+
5
+ import { ArrowToggle } from './ArrowToggle.js';
6
+
7
+ type Story = StoryObj<typeof ArrowToggle>;
8
+
9
+ export default { component: ArrowToggle, tags: ['autodocs'] } as Meta<typeof ArrowToggle>;
10
+
11
+ export const UpToDown: Story = {
12
+ decorators: [
13
+ (Story) => {
14
+ const [isToggled, setIsToggled] = useState(false);
15
+ return <Story args={{ isToggled, onClick: () => setIsToggled(!isToggled), position: 'up', rotation: 180 }} />;
16
+ }
17
+ ]
18
+ };
19
+
20
+ export const LeftToDown: Story = {
21
+ decorators: [
22
+ (Story) => {
23
+ const [isToggled, setIsToggled] = useState(false);
24
+ return <Story args={{ isToggled, onClick: () => setIsToggled(!isToggled), position: 'left', rotation: -90 }} />;
25
+ }
26
+ ]
27
+ };
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+
3
+ import { render, screen } from '@testing-library/react';
4
+ import { describe, expect, it } from 'vitest';
5
+
6
+ import { Avatar } from './Avatar.js';
7
+
8
+ const TEST_ID = 'avatar';
9
+
10
+ const TestAvatar = (props: { [key: string]: any }) => (
11
+ <Avatar {...props}>
12
+ <Avatar.Image alt="@shadcn" src="https://github.com/shadcn.png" />
13
+ <Avatar.Fallback>CN</Avatar.Fallback>
14
+ </Avatar>
15
+ );
16
+
17
+ describe('Avatar', () => {
18
+ it('should render', () => {
19
+ render(<TestAvatar />);
20
+ expect(screen.getByTestId(TEST_ID)).toBeInTheDocument();
21
+ });
22
+ it('should contain a custom class name', () => {
23
+ render(<Avatar className="foo" />);
24
+ expect(screen.getByTestId(TEST_ID)).toHaveClass('foo');
25
+ });
26
+ });
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+
3
+ import type { Meta, StoryObj } from '@storybook/react';
4
+
5
+ import { Avatar } from './Avatar.js';
6
+
7
+ type Story = StoryObj<typeof Avatar>;
8
+
9
+ export default { component: Avatar, tags: ['autodocs'] } as Meta<typeof Avatar>;
10
+
11
+ export const Default: Story = {
12
+ args: {
13
+ children: (
14
+ <>
15
+ <Avatar.Image alt="@shadcn" src="https://github.com/shadcn.png" />
16
+ <Avatar.Fallback>CN</Avatar.Fallback>
17
+ </>
18
+ )
19
+ }
20
+ };