@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.
- package/README.md +7 -1
- package/dist/components/ClientTable/ClientTable.d.ts.map +1 -1
- package/dist/components/ClientTable/ClientTable.js +4 -4
- package/dist/components/Drawer/Drawer.d.ts +5 -5
- package/dist/components/Drawer/DrawerContent.d.ts +1 -1
- package/dist/components/Drawer/DrawerDescription.d.ts +1 -1
- package/dist/components/Drawer/DrawerDescription.d.ts.map +1 -1
- package/dist/components/Drawer/DrawerTitle.d.ts +1 -1
- package/dist/components/Drawer/DrawerTitle.d.ts.map +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.0.tgz +0 -0
- package/dist/i18n.js +1 -1
- package/package.json +60 -59
- 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/ClientTable/ClientTable.tsx +5 -7
- 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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
|
|
5
|
+
import { Button } from '../Button/Button.js';
|
|
6
|
+
import { Input } from '../Input/Input.js';
|
|
7
|
+
import { Label } from '../Label/Label.js';
|
|
8
|
+
import { Dialog } from './Dialog.js';
|
|
9
|
+
|
|
10
|
+
type Story = StoryObj<typeof Dialog>;
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
args: {
|
|
14
|
+
children: (
|
|
15
|
+
<>
|
|
16
|
+
<Dialog.Trigger asChild>
|
|
17
|
+
<Button variant="outline">Edit Profile</Button>
|
|
18
|
+
</Dialog.Trigger>
|
|
19
|
+
<Dialog.Content className="sm:max-w-[425px]">
|
|
20
|
+
<Dialog.Header>
|
|
21
|
+
<Dialog.Title>Edit profile</Dialog.Title>
|
|
22
|
+
<Dialog.Description>Make changes to your profile here. Click save when you are done.</Dialog.Description>
|
|
23
|
+
</Dialog.Header>
|
|
24
|
+
<Dialog.Body className="grid gap-4">
|
|
25
|
+
<div className="grid gap-2">
|
|
26
|
+
<Label htmlFor="name">Name</Label>
|
|
27
|
+
<Input id="name" />
|
|
28
|
+
</div>
|
|
29
|
+
<div className="grid gap-2">
|
|
30
|
+
<Label htmlFor="description">Description</Label>
|
|
31
|
+
<Input id="description" />
|
|
32
|
+
</div>
|
|
33
|
+
</Dialog.Body>
|
|
34
|
+
<Dialog.Footer>
|
|
35
|
+
<Button type="submit">Save changes</Button>
|
|
36
|
+
</Dialog.Footer>
|
|
37
|
+
</Dialog.Content>
|
|
38
|
+
</>
|
|
39
|
+
)
|
|
40
|
+
},
|
|
41
|
+
component: Dialog
|
|
42
|
+
} as Meta<typeof Dialog>;
|
|
43
|
+
|
|
44
|
+
export const Default: Story = {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
|
|
5
|
+
import { Button } from '../Button/Button.js';
|
|
6
|
+
import { Drawer } from './Drawer.js';
|
|
7
|
+
|
|
8
|
+
type Story = StoryObj<typeof Drawer>;
|
|
9
|
+
|
|
10
|
+
export default { component: Drawer } as Meta<typeof Drawer>;
|
|
11
|
+
|
|
12
|
+
export const Default: Story = {
|
|
13
|
+
args: {
|
|
14
|
+
children: (
|
|
15
|
+
<>
|
|
16
|
+
<Drawer.Trigger asChild>
|
|
17
|
+
<Button variant="outline">Open Drawer</Button>
|
|
18
|
+
</Drawer.Trigger>
|
|
19
|
+
<Drawer.Content>
|
|
20
|
+
<div className="mx-auto w-full max-w-sm">
|
|
21
|
+
<Drawer.Header>
|
|
22
|
+
<Drawer.Title>Title</Drawer.Title>
|
|
23
|
+
<Drawer.Description>
|
|
24
|
+
Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...
|
|
25
|
+
</Drawer.Description>
|
|
26
|
+
</Drawer.Header>
|
|
27
|
+
<Drawer.Footer>
|
|
28
|
+
<Drawer.Close asChild>
|
|
29
|
+
<Button variant="outline">Close</Button>
|
|
30
|
+
</Drawer.Close>
|
|
31
|
+
</Drawer.Footer>
|
|
32
|
+
</div>
|
|
33
|
+
</Drawer.Content>
|
|
34
|
+
</>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { DropdownButton } from './DropdownButton.js';
|
|
4
|
+
|
|
5
|
+
type Story = StoryObj<typeof DropdownButton>;
|
|
6
|
+
|
|
7
|
+
export default { component: DropdownButton } as Meta<typeof DropdownButton>;
|
|
8
|
+
|
|
9
|
+
export const Default: Story = {
|
|
10
|
+
args: {
|
|
11
|
+
children: 'My Dropdown'
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
|
|
5
|
+
import { Button } from '../Button/Button.js';
|
|
6
|
+
import { DropdownMenu } from './DropdownMenu.js';
|
|
7
|
+
|
|
8
|
+
type Story = StoryObj<typeof DropdownMenu>;
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
component: DropdownMenu,
|
|
12
|
+
parameters: {
|
|
13
|
+
layout: 'centered'
|
|
14
|
+
},
|
|
15
|
+
tags: ['autodocs']
|
|
16
|
+
} as Meta<typeof DropdownMenu>;
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {
|
|
19
|
+
args: {
|
|
20
|
+
children: (
|
|
21
|
+
<div className="mx-24">
|
|
22
|
+
<DropdownMenu.Trigger asChild>
|
|
23
|
+
<Button variant="outline">Open</Button>
|
|
24
|
+
</DropdownMenu.Trigger>
|
|
25
|
+
<DropdownMenu.Content className="w-56">
|
|
26
|
+
<DropdownMenu.Label>My Account</DropdownMenu.Label>
|
|
27
|
+
<DropdownMenu.Separator />
|
|
28
|
+
<DropdownMenu.Group>
|
|
29
|
+
<DropdownMenu.Item>
|
|
30
|
+
Profile
|
|
31
|
+
<DropdownMenu.Shortcut>⇧⌘P</DropdownMenu.Shortcut>
|
|
32
|
+
</DropdownMenu.Item>
|
|
33
|
+
<DropdownMenu.Item>
|
|
34
|
+
Billing
|
|
35
|
+
<DropdownMenu.Shortcut>⌘B</DropdownMenu.Shortcut>
|
|
36
|
+
</DropdownMenu.Item>
|
|
37
|
+
<DropdownMenu.Item>
|
|
38
|
+
Settings
|
|
39
|
+
<DropdownMenu.Shortcut>⌘S</DropdownMenu.Shortcut>
|
|
40
|
+
</DropdownMenu.Item>
|
|
41
|
+
<DropdownMenu.Item>
|
|
42
|
+
Keyboard shortcuts
|
|
43
|
+
<DropdownMenu.Shortcut>⌘K</DropdownMenu.Shortcut>
|
|
44
|
+
</DropdownMenu.Item>
|
|
45
|
+
</DropdownMenu.Group>
|
|
46
|
+
<DropdownMenu.Separator />
|
|
47
|
+
<DropdownMenu.Group>
|
|
48
|
+
<DropdownMenu.Item>Team</DropdownMenu.Item>
|
|
49
|
+
<DropdownMenu.Sub>
|
|
50
|
+
<DropdownMenu.SubTrigger>Invite users</DropdownMenu.SubTrigger>
|
|
51
|
+
<DropdownMenu.Portal>
|
|
52
|
+
<DropdownMenu.SubContent>
|
|
53
|
+
<DropdownMenu.Item>Email</DropdownMenu.Item>
|
|
54
|
+
<DropdownMenu.Item>Message</DropdownMenu.Item>
|
|
55
|
+
<DropdownMenu.Separator />
|
|
56
|
+
<DropdownMenu.Item>More...</DropdownMenu.Item>
|
|
57
|
+
</DropdownMenu.SubContent>
|
|
58
|
+
</DropdownMenu.Portal>
|
|
59
|
+
</DropdownMenu.Sub>
|
|
60
|
+
<DropdownMenu.Item>
|
|
61
|
+
New Team
|
|
62
|
+
<DropdownMenu.Shortcut>⌘+T</DropdownMenu.Shortcut>
|
|
63
|
+
</DropdownMenu.Item>
|
|
64
|
+
</DropdownMenu.Group>
|
|
65
|
+
<DropdownMenu.Separator />
|
|
66
|
+
<DropdownMenu.Item>GitHub</DropdownMenu.Item>
|
|
67
|
+
<DropdownMenu.Item>Support</DropdownMenu.Item>
|
|
68
|
+
<DropdownMenu.Item disabled>API</DropdownMenu.Item>
|
|
69
|
+
<DropdownMenu.Separator />
|
|
70
|
+
<DropdownMenu.Item>
|
|
71
|
+
Log out
|
|
72
|
+
<DropdownMenu.Shortcut>⇧⌘Q</DropdownMenu.Shortcut>
|
|
73
|
+
</DropdownMenu.Item>
|
|
74
|
+
</DropdownMenu.Content>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { ErrorFallback } from './ErrorFallback.js';
|
|
4
|
+
|
|
5
|
+
type Story = StoryObj<typeof ErrorFallback>;
|
|
6
|
+
|
|
7
|
+
export default { component: ErrorFallback } satisfies Meta<typeof ErrorFallback>;
|
|
8
|
+
|
|
9
|
+
export const Default: Story = {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { BooleanField, type BooleanFieldProps } from './BooleanField.js';
|
|
7
|
+
|
|
8
|
+
const TestBooleanField = ({ variant }: Pick<BooleanFieldProps, 'variant'>) => {
|
|
9
|
+
const [error, setError] = useState<string | undefined>();
|
|
10
|
+
const [value, setValue] = useState<boolean | undefined>();
|
|
11
|
+
return (
|
|
12
|
+
<BooleanField
|
|
13
|
+
error={error}
|
|
14
|
+
label="boolean-field"
|
|
15
|
+
name="boolean-field"
|
|
16
|
+
setError={setError}
|
|
17
|
+
setValue={setValue}
|
|
18
|
+
value={value}
|
|
19
|
+
variant={variant}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('BooleanField', () => {
|
|
25
|
+
it('should render a checkbox field', () => {
|
|
26
|
+
render(<TestBooleanField variant="checkbox" />);
|
|
27
|
+
expect(screen.getByRole('checkbox')).toBeInTheDocument();
|
|
28
|
+
expect(() => screen.getByRole('radiogroup')).toThrow();
|
|
29
|
+
});
|
|
30
|
+
it('should render a radio field', () => {
|
|
31
|
+
render(<TestBooleanField variant="radio" />);
|
|
32
|
+
expect(screen.getByRole('radiogroup')).toBeInTheDocument();
|
|
33
|
+
expect(() => screen.getByRole('checkbox')).toThrow();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
|
|
5
|
+
import { BooleanField } from './BooleanField.js';
|
|
6
|
+
|
|
7
|
+
type Story = StoryObj<typeof BooleanField>;
|
|
8
|
+
|
|
9
|
+
export default { component: BooleanField } as Meta<typeof BooleanField>;
|
|
10
|
+
|
|
11
|
+
export const Radio: Story = {
|
|
12
|
+
decorators: [
|
|
13
|
+
(Story) => {
|
|
14
|
+
const [value, setValue] = useState<boolean | undefined>();
|
|
15
|
+
return (
|
|
16
|
+
<Story
|
|
17
|
+
args={{
|
|
18
|
+
description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit.',
|
|
19
|
+
label: 'Boolean Field',
|
|
20
|
+
name: 'boolean',
|
|
21
|
+
setValue,
|
|
22
|
+
value,
|
|
23
|
+
variant: 'radio'
|
|
24
|
+
}}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Checkbox: Story = {
|
|
32
|
+
decorators: [
|
|
33
|
+
(Story) => {
|
|
34
|
+
const [value, setValue] = useState<boolean | undefined>(false);
|
|
35
|
+
return (
|
|
36
|
+
<Story
|
|
37
|
+
args={{
|
|
38
|
+
description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit.',
|
|
39
|
+
label: 'Boolean Field',
|
|
40
|
+
name: 'boolean',
|
|
41
|
+
setValue,
|
|
42
|
+
value,
|
|
43
|
+
variant: 'checkbox'
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { toBasicISOString } from '@douglasneuroinformatics/libjs';
|
|
4
|
+
import { getByText, render, screen } from '@testing-library/react';
|
|
5
|
+
import { userEvent } from '@testing-library/user-event';
|
|
6
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { DateField } from './DateField.js';
|
|
9
|
+
|
|
10
|
+
describe('DateField', () => {
|
|
11
|
+
const setError = vi.fn();
|
|
12
|
+
const setValue = vi.fn();
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should render an input with an empty string value ', () => {
|
|
19
|
+
render(<DateField label="Date" name="date" setError={setError} setValue={setValue} value={undefined} />);
|
|
20
|
+
const input: HTMLInputElement = screen.getByRole('textbox');
|
|
21
|
+
expect(input).toBeInTheDocument();
|
|
22
|
+
expect(input.value).toBe('');
|
|
23
|
+
});
|
|
24
|
+
it('should allow typing arbitrary text while focused', async () => {
|
|
25
|
+
render(<DateField label="Date" name="date" setError={setError} setValue={setValue} value={undefined} />);
|
|
26
|
+
const input: HTMLInputElement = screen.getByRole('textbox');
|
|
27
|
+
await userEvent.type(input, 'foo');
|
|
28
|
+
expect(input.value).toBe('foo');
|
|
29
|
+
});
|
|
30
|
+
it('should not initially show the datepicker', () => {
|
|
31
|
+
render(<DateField label="Date" name="date" setError={setError} setValue={setValue} value={undefined} />);
|
|
32
|
+
expect(() => screen.getByTestId('datepicker')).toThrow();
|
|
33
|
+
});
|
|
34
|
+
it('should show the datepicker when the input is clicked', async () => {
|
|
35
|
+
render(<DateField label="Date" name="date" setError={setError} setValue={setValue} value={undefined} />);
|
|
36
|
+
const input = screen.getByRole('textbox');
|
|
37
|
+
expect(() => screen.getByTestId('datepicker')).toThrow();
|
|
38
|
+
await userEvent.click(input);
|
|
39
|
+
expect(screen.getByTestId('datepicker')).toBeInTheDocument();
|
|
40
|
+
});
|
|
41
|
+
it('should discard invalid text when unfocused, once the date picker is closed', async () => {
|
|
42
|
+
render(<DateField label="Date" name="date" setError={setError} setValue={setValue} value={undefined} />);
|
|
43
|
+
const input: HTMLInputElement = screen.getByRole('textbox');
|
|
44
|
+
await userEvent.type(input, 'foo');
|
|
45
|
+
expect(input.value).toBe('foo');
|
|
46
|
+
const datepicker = screen.getByTestId('datepicker');
|
|
47
|
+
await userEvent.click(datepicker);
|
|
48
|
+
expect(datepicker).toBeInTheDocument();
|
|
49
|
+
await userEvent.type(input, 'foo');
|
|
50
|
+
await userEvent.click(screen.getByText('Date'));
|
|
51
|
+
expect(() => screen.getByTestId('datepicker')).toThrow();
|
|
52
|
+
expect(input.value).toBe('');
|
|
53
|
+
});
|
|
54
|
+
it('should not attempt to set the value if the date is invalid', async () => {
|
|
55
|
+
render(<DateField label="Date" name="date" setError={setError} setValue={setValue} value={undefined} />);
|
|
56
|
+
const input: HTMLInputElement = screen.getByRole('textbox');
|
|
57
|
+
await userEvent.type(input, 'foo');
|
|
58
|
+
expect(input.value).toBe('foo');
|
|
59
|
+
await userEvent.click(screen.getByText('Date'));
|
|
60
|
+
expect(input.value).toBe('');
|
|
61
|
+
expect(setValue).not.toHaveBeenCalled();
|
|
62
|
+
});
|
|
63
|
+
it('should attempt to set the value if the user enters a correct date', async () => {
|
|
64
|
+
render(<DateField label="Date" name="date" setError={setError} setValue={setValue} value={undefined} />);
|
|
65
|
+
const input: HTMLInputElement = screen.getByRole('textbox');
|
|
66
|
+
await userEvent.type(input, '2000-01-01');
|
|
67
|
+
await userEvent.click(screen.getByText('Date'));
|
|
68
|
+
expect(input.value).toBe('2000-01-01');
|
|
69
|
+
expect(setValue).toHaveBeenCalledOnce();
|
|
70
|
+
expect(setValue).toHaveBeenCalledWith(new Date('2000-01-01'));
|
|
71
|
+
});
|
|
72
|
+
it('should allow setting the date using the date picker', async () => {
|
|
73
|
+
render(<DateField label="Date" name="date" setError={setError} setValue={setValue} value={undefined} />);
|
|
74
|
+
const input: HTMLInputElement = screen.getByRole('textbox');
|
|
75
|
+
let datepicker: HTMLElement;
|
|
76
|
+
let expectedDate: Date;
|
|
77
|
+
let expectedDateString: string;
|
|
78
|
+
|
|
79
|
+
await userEvent.click(input);
|
|
80
|
+
datepicker = screen.getByTestId('datepicker');
|
|
81
|
+
await userEvent.click(getByText(datepicker, '1'));
|
|
82
|
+
expectedDate = new Date(new Date().setDate(1));
|
|
83
|
+
expectedDateString = toBasicISOString(expectedDate);
|
|
84
|
+
expect(toBasicISOString(setValue.mock.lastCall[0])).toBe(expectedDateString);
|
|
85
|
+
|
|
86
|
+
await userEvent.click(input);
|
|
87
|
+
datepicker = screen.getByTestId('datepicker');
|
|
88
|
+
await userEvent.click(getByText(datepicker, '2'));
|
|
89
|
+
expectedDate = new Date(new Date().setDate(2));
|
|
90
|
+
expectedDateString = toBasicISOString(expectedDate);
|
|
91
|
+
expect(toBasicISOString(setValue.mock.lastCall[0])).toBe(expectedDateString);
|
|
92
|
+
});
|
|
93
|
+
it('should render the value provided as a prop', () => {
|
|
94
|
+
const today = new Date();
|
|
95
|
+
render(<DateField label="Date" name="date" setError={setError} setValue={setValue} value={today} />);
|
|
96
|
+
const input: HTMLInputElement = screen.getByRole('textbox');
|
|
97
|
+
expect(input.value).toBe(toBasicISOString(today));
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
|
|
5
|
+
import { DateField } from './DateField.js';
|
|
6
|
+
|
|
7
|
+
type Story = StoryObj<typeof DateField>;
|
|
8
|
+
|
|
9
|
+
export default { component: DateField } as Meta<typeof DateField>;
|
|
10
|
+
|
|
11
|
+
export const Default: Story = {
|
|
12
|
+
decorators: [
|
|
13
|
+
(Story) => {
|
|
14
|
+
const [value, setValue] = useState<Date | undefined>();
|
|
15
|
+
return (
|
|
16
|
+
<Story
|
|
17
|
+
args={{
|
|
18
|
+
description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit.',
|
|
19
|
+
label: 'Date of Birth',
|
|
20
|
+
name: 'date-of-birth',
|
|
21
|
+
setValue,
|
|
22
|
+
value
|
|
23
|
+
}}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
};
|