@douglasneuroinformatics/libui 2.5.3 → 2.6.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.
- package/README.md +1 -1
- package/dist/components/Form/NumberField/NumberFieldRadio.d.ts +1 -1
- package/dist/components/Form/NumberField/NumberFieldRadio.d.ts.map +1 -1
- package/dist/components/Form/NumberField/NumberFieldRadio.js +7 -4
- package/dist/components/Form/NumberField/NumberFieldSelect.d.ts +1 -1
- package/dist/components/Form/NumberField/NumberFieldSelect.d.ts.map +1 -1
- package/dist/components/Form/NumberField/NumberFieldSelect.js +6 -2
- package/dist/douglasneuroinformatics-libui-2.6.1.tgz +0 -0
- package/dist/i18n.js +1 -1
- package/package.json +16 -14
- package/src/components/Accordion/Accordion.spec.tsx +37 -0
- package/src/components/Accordion/Accordion.stories.tsx +35 -0
- package/src/components/ActionDropdown/ActionDropdown.stories.tsx +17 -0
- package/src/components/AlertDialog/AlertDialog.stories.tsx +35 -0
- package/src/components/ArrowToggle/ArrowToggle.spec.tsx +49 -0
- package/src/components/ArrowToggle/ArrowToggle.stories.tsx +27 -0
- package/src/components/Avatar/Avatar.spec.tsx +26 -0
- package/src/components/Avatar/Avatar.stories.tsx +20 -0
- package/src/components/Badge/Badge.spec.tsx +19 -0
- package/src/components/Badge/Badge.stories.tsx +13 -0
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +44 -0
- package/src/components/Button/Button.spec.tsx +27 -0
- package/src/components/Button/Button.stories.tsx +63 -0
- package/src/components/Card/Card.spec.tsx +19 -0
- package/src/components/Card/Card.stories.tsx +56 -0
- package/src/components/Checkbox/Checkbox.spec.tsx +28 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +34 -0
- package/src/components/ClientTable/ClientTable.stories.tsx +126 -0
- package/src/components/Collapsible/Collapsible.stories.tsx +45 -0
- package/src/components/Command/Command.stories.tsx +55 -0
- package/src/components/ContextMenu/ContextMenu.stories.tsx +61 -0
- package/src/components/DatePicker/DatePicker.stories.tsx +15 -0
- package/src/components/Dialog/Dialog.stories.tsx +44 -0
- package/src/components/Drawer/Drawer.stories.tsx +37 -0
- package/src/components/DropdownButton/DropdownButton.stories.tsx +13 -0
- package/src/components/DropdownMenu/DropdownMenu.stories.tsx +78 -0
- package/src/components/ErrorFallback/ErrorFallback.stories.tsx +9 -0
- package/src/components/Form/BooleanField/BooleanField.spec.tsx +35 -0
- package/src/components/Form/BooleanField/BooleanField.stories.tsx +49 -0
- package/src/components/Form/DateField/DateField.spec.tsx +99 -0
- package/src/components/Form/DateField/DateField.stories.tsx +28 -0
- package/src/components/Form/Form.stories.tsx +360 -0
- package/src/components/Form/Form.test.tsx +119 -0
- package/src/components/Form/NumberField/NumberField.stories.tsx +103 -0
- package/src/components/Form/NumberField/NumberFieldRadio.tsx +16 -8
- package/src/components/Form/NumberField/NumberFieldSelect.tsx +10 -6
- package/src/components/Form/SetField/SetField.stories.tsx +63 -0
- package/src/components/Form/StringField/StringField.stories.tsx +94 -0
- package/src/components/Heading/Heading.stories.tsx +37 -0
- package/src/components/HoverCard/HoverCard.stories.tsx +40 -0
- package/src/components/Input/Input.spec.tsx +23 -0
- package/src/components/Input/Input.stories.tsx +9 -0
- package/src/components/Label/Label.spec.tsx +17 -0
- package/src/components/Label/Label.stories.tsx +13 -0
- package/src/components/LanguageToggle/LanguageToggle.stories.tsx +17 -0
- package/src/components/LineGraph/LineGraph.stories.tsx +81 -0
- package/src/components/ListboxDropdown/ListboxDropdown.stories.tsx +44 -0
- package/src/components/MenuBar/MenuBar.stories.tsx +101 -0
- package/src/components/NotificationHub/NotificationHub.stories.tsx +41 -0
- package/src/components/Pagination/Pagination.stories.tsx +41 -0
- package/src/components/Popover/Popover.spec.tsx +24 -0
- package/src/components/Popover/Popover.stories.tsx +72 -0
- package/src/components/Progress/Progress.stories.tsx +24 -0
- package/src/components/RadioGroup/RadioGroup.spec.tsx +42 -0
- package/src/components/RadioGroup/RadioGroup.stories.tsx +35 -0
- package/src/components/Resizable/Resizable.stories.tsx +39 -0
- package/src/components/ScrollArea/ScrollArea.spec.tsx +19 -0
- package/src/components/ScrollArea/ScrollArea.stories.tsx +23 -0
- package/src/components/SearchBar/SearchBar.stories.tsx +11 -0
- package/src/components/Select/Select.stories.tsx +31 -0
- package/src/components/Separator/Separator.spec.tsx +19 -0
- package/src/components/Separator/Separator.stories.tsx +30 -0
- package/src/components/Sheet/Sheet.stories.tsx +49 -0
- package/src/components/Slider/Slider.stories.tsx +9 -0
- package/src/components/Spinner/Spinner.stories.tsx +14 -0
- package/src/components/SpinnerIcon/SpinnerIcon.stories.tsx +9 -0
- package/src/components/Switch/Switch.stories.tsx +21 -0
- package/src/components/Table/Table.stories.tsx +88 -0
- package/src/components/Tabs/Tabs.stories.tsx +70 -0
- package/src/components/TextArea/TextArea.spec.tsx +23 -0
- package/src/components/TextArea/TextArea.stories.tsx +13 -0
- package/src/components/ThemeToggle/ThemeToggle.stories.tsx +9 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +44 -0
- package/src/hooks/useDownload.test.ts +66 -0
- package/src/hooks/useEventCallback.test.tsx +22 -0
- package/src/hooks/useEventListener.test.tsx +120 -0
- package/src/hooks/useInterval.test.ts +58 -0
- package/src/hooks/useIsomorphicLayoutEffect.test.ts +27 -0
- package/src/hooks/useMediaQuery.test.ts +33 -0
- package/src/hooks/useNotificationsStore.test.ts +30 -0
- package/src/hooks/useOnClickOutside.test.ts +59 -0
- package/src/hooks/useSessionStorage.test.ts +186 -0
- package/src/hooks/useTheme.test.ts +74 -0
- package/src/hooks/useWindowSize.test.ts +57 -0
- package/src/i18n.ts +1 -1
- package/tailwind.config.cjs +4 -1
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ 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
|
};
|
|
@@ -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,
|
|
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) =>
|
|
16
|
-
|
|
17
|
-
React.createElement(
|
|
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,
|
|
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) =>
|
|
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
|
};
|
|
Binary file
|
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,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@douglasneuroinformatics/libui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.6.1",
|
|
5
|
+
"packageManager": "pnpm@9.3.0",
|
|
5
6
|
"description": "Generic UI components for DNP projects, built using React and TailwindCSS",
|
|
6
7
|
"author": {
|
|
7
8
|
"name": "Douglas Neuroinformatics",
|
|
@@ -49,13 +50,24 @@
|
|
|
49
50
|
"/tailwind.config.cjs",
|
|
50
51
|
"/tailwind.config.d.cts"
|
|
51
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
|
+
},
|
|
52
64
|
"peerDependencies": {
|
|
53
65
|
"react": "^18.2.0",
|
|
54
66
|
"react-dom": "^18.2.0"
|
|
55
67
|
},
|
|
56
68
|
"dependencies": {
|
|
57
69
|
"@douglasneuroinformatics/libjs": "^0.3.1",
|
|
58
|
-
"@douglasneuroinformatics/libui-form-types": "^0.
|
|
70
|
+
"@douglasneuroinformatics/libui-form-types": "^0.8.0",
|
|
59
71
|
"@headlessui/tailwindcss": "^0.2.1",
|
|
60
72
|
"@heroicons/react": "^2.1.3",
|
|
61
73
|
"@radix-ui/react-accordion": "^1.1.2",
|
|
@@ -137,7 +149,7 @@
|
|
|
137
149
|
"postcss": "^8.4.38",
|
|
138
150
|
"prettier": "^3.3.1",
|
|
139
151
|
"prettier-plugin-tailwindcss": "^0.6.2",
|
|
140
|
-
"semantic-release": "^
|
|
152
|
+
"semantic-release": "^23.0.8",
|
|
141
153
|
"sort-json": "^2.0.1",
|
|
142
154
|
"storybook": "^8.1.6",
|
|
143
155
|
"storybook-react-i18next": "^3.1.1",
|
|
@@ -145,15 +157,5 @@
|
|
|
145
157
|
"typescript": "~5.4.5",
|
|
146
158
|
"vite": "5.2.13",
|
|
147
159
|
"vitest": "^1.6.0"
|
|
148
|
-
},
|
|
149
|
-
"scripts": {
|
|
150
|
-
"build": "rm -rf dist && tsc -b tsconfig.build.json && cp -r src/styles dist",
|
|
151
|
-
"format": "prettier --write src",
|
|
152
|
-
"format:translations": "find src/translations -name '*.json' -exec pnpm exec sort-json {} \\;",
|
|
153
|
-
"lint": "tsc && eslint --fix src",
|
|
154
|
-
"storybook": "storybook dev --no-open -p 6006",
|
|
155
|
-
"storybook:build": "storybook build",
|
|
156
|
-
"test": "vitest",
|
|
157
|
-
"test:coverage": "vitest --coverage"
|
|
158
160
|
}
|
|
159
|
-
}
|
|
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' 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
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { Badge } from './Badge.js';
|
|
7
|
+
|
|
8
|
+
const TEST_ID = 'badge';
|
|
9
|
+
|
|
10
|
+
describe('Badge', () => {
|
|
11
|
+
it('should render', () => {
|
|
12
|
+
render(<Badge />);
|
|
13
|
+
expect(screen.getByTestId(TEST_ID)).toBeInTheDocument();
|
|
14
|
+
});
|
|
15
|
+
it('should contain a custom class name', () => {
|
|
16
|
+
render(<Badge className="foo" />);
|
|
17
|
+
expect(screen.getByTestId(TEST_ID)).toHaveClass('foo');
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { Badge } from './Badge.js';
|
|
4
|
+
|
|
5
|
+
type Story = StoryObj<typeof Badge>;
|
|
6
|
+
|
|
7
|
+
export default { component: Badge, tags: ['autodocs'] } as Meta<typeof Badge>;
|
|
8
|
+
|
|
9
|
+
export const Default: Story = {
|
|
10
|
+
args: {
|
|
11
|
+
children: 'Badge'
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
|
|
5
|
+
import { DropdownMenu } from '../DropdownMenu/DropdownMenu.js';
|
|
6
|
+
import { Breadcrumb } from './Breadcrumb.js';
|
|
7
|
+
|
|
8
|
+
type Story = StoryObj<typeof Breadcrumb>;
|
|
9
|
+
|
|
10
|
+
export default { component: Breadcrumb } as Meta<typeof Breadcrumb>;
|
|
11
|
+
|
|
12
|
+
export const Default: Story = {
|
|
13
|
+
args: {
|
|
14
|
+
children: (
|
|
15
|
+
<Breadcrumb.List>
|
|
16
|
+
<Breadcrumb.Item>
|
|
17
|
+
<Breadcrumb.Link href="/">Home</Breadcrumb.Link>
|
|
18
|
+
</Breadcrumb.Item>
|
|
19
|
+
<Breadcrumb.Separator />
|
|
20
|
+
<Breadcrumb.Item>
|
|
21
|
+
<DropdownMenu>
|
|
22
|
+
<DropdownMenu.Trigger className="flex items-center gap-1">
|
|
23
|
+
<Breadcrumb.Ellipsis className="h-4 w-4" />
|
|
24
|
+
<span className="sr-only">Toggle menu</span>
|
|
25
|
+
</DropdownMenu.Trigger>
|
|
26
|
+
<DropdownMenu.Content align="start">
|
|
27
|
+
<DropdownMenu.Item>Documentation</DropdownMenu.Item>
|
|
28
|
+
<DropdownMenu.Item>Themes</DropdownMenu.Item>
|
|
29
|
+
<DropdownMenu.Item>GitHub</DropdownMenu.Item>
|
|
30
|
+
</DropdownMenu.Content>
|
|
31
|
+
</DropdownMenu>
|
|
32
|
+
</Breadcrumb.Item>
|
|
33
|
+
<Breadcrumb.Separator />
|
|
34
|
+
<Breadcrumb.Item>
|
|
35
|
+
<Breadcrumb.Link href="/docs/components">Components</Breadcrumb.Link>
|
|
36
|
+
</Breadcrumb.Item>
|
|
37
|
+
<Breadcrumb.Separator />
|
|
38
|
+
<Breadcrumb.Item>
|
|
39
|
+
<Breadcrumb.Page>Breadcrumb.</Breadcrumb.Page>
|
|
40
|
+
</Breadcrumb.Item>
|
|
41
|
+
</Breadcrumb.List>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { Button } from './Button.js';
|
|
7
|
+
|
|
8
|
+
describe('Button', () => {
|
|
9
|
+
it('should render', () => {
|
|
10
|
+
render(<Button>My Button</Button>);
|
|
11
|
+
expect(screen.getByText('My Button')).toBeInTheDocument();
|
|
12
|
+
});
|
|
13
|
+
it('should contain a custom class name', () => {
|
|
14
|
+
render(<Button className="foo">My Button</Button>);
|
|
15
|
+
expect(screen.getByText('My Button')).toHaveClass('foo');
|
|
16
|
+
});
|
|
17
|
+
it('should render a custom class name within a child element, if asChild is set to true', () => {
|
|
18
|
+
render(
|
|
19
|
+
<Button asChild className="foo">
|
|
20
|
+
<a data-testid="link" href="https://google.com">
|
|
21
|
+
My Link
|
|
22
|
+
</a>
|
|
23
|
+
</Button>
|
|
24
|
+
);
|
|
25
|
+
expect(screen.getByTestId('link')).toHaveClass('foo');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
import { DownloadIcon } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
import { Button } from './Button.js';
|
|
7
|
+
|
|
8
|
+
type Story = StoryObj<typeof Button>;
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
args: {
|
|
12
|
+
asChild: false,
|
|
13
|
+
size: 'md'
|
|
14
|
+
},
|
|
15
|
+
component: Button,
|
|
16
|
+
parameters: {
|
|
17
|
+
layout: 'centered'
|
|
18
|
+
},
|
|
19
|
+
tags: ['autodocs']
|
|
20
|
+
} satisfies Meta<typeof Button>;
|
|
21
|
+
|
|
22
|
+
export const Primary: Story = {
|
|
23
|
+
args: {
|
|
24
|
+
children: 'Primary Button',
|
|
25
|
+
variant: 'primary'
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const Secondary: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
children: 'Secondary Button',
|
|
32
|
+
variant: 'secondary'
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const Danger: Story = {
|
|
37
|
+
args: {
|
|
38
|
+
children: 'Danger Button',
|
|
39
|
+
variant: 'danger'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const Outline: Story = {
|
|
44
|
+
args: {
|
|
45
|
+
children: 'Outline Button',
|
|
46
|
+
variant: 'outline'
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const Icon: Story = {
|
|
51
|
+
args: {
|
|
52
|
+
children: <DownloadIcon />,
|
|
53
|
+
size: 'icon',
|
|
54
|
+
variant: 'outline'
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const AsChild: Story = {
|
|
59
|
+
args: {
|
|
60
|
+
asChild: true,
|
|
61
|
+
children: <a href="https://google.com">My Link</a>
|
|
62
|
+
}
|
|
63
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { Card } from './Card.js';
|
|
7
|
+
|
|
8
|
+
const TEST_ID = 'card';
|
|
9
|
+
|
|
10
|
+
describe('Card', () => {
|
|
11
|
+
it('should render', () => {
|
|
12
|
+
render(<Card />);
|
|
13
|
+
expect(screen.getByTestId(TEST_ID)).toBeInTheDocument();
|
|
14
|
+
});
|
|
15
|
+
it('should contain a custom class name', () => {
|
|
16
|
+
render(<Card className="foo" />);
|
|
17
|
+
expect(screen.getByTestId(TEST_ID)).toHaveClass('foo');
|
|
18
|
+
});
|
|
19
|
+
});
|