@douglasneuroinformatics/libui 3.1.2 → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components.d.ts +5 -2
- package/dist/components.js +15 -40
- package/dist/components.js.map +1 -1
- package/dist/douglasneuroinformatics-libui-3.1.4.tgz +0 -0
- package/package.json +2 -1
- package/src/components/Accordion/Accordion.spec.tsx +6 -5
- package/src/components/ActionDropdown/ActionDropdown.spec.tsx +35 -0
- package/src/components/ActionDropdown/ActionDropdown.tsx +10 -2
- package/src/components/ClientTable/ClientTable.tsx +4 -2
- package/src/components/Form/DateField/DateField.tsx +0 -1
- package/src/components/Form/Form.tsx +2 -9
- package/src/components/Form/NumberField/NumberFieldSelect.tsx +3 -7
- package/src/components/Form/StringField/StringFieldSelect.tsx +3 -7
- package/src/components/ListboxDropdown/ListboxDropdown.tsx +1 -1
- package/src/testing/mocks.ts +9 -0
- package/src/testing/setup-tests.ts +3 -0
- package/dist/douglasneuroinformatics-libui-3.1.2.tgz +0 -0
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@douglasneuroinformatics/libui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.1.
|
|
4
|
+
"version": "3.1.4",
|
|
5
5
|
"packageManager": "pnpm@9.3.0",
|
|
6
6
|
"description": "Generic UI components for DNP projects, built using React and Tailwind CSS",
|
|
7
7
|
"author": "Joshua Unrau",
|
|
@@ -119,6 +119,7 @@
|
|
|
119
119
|
"@douglasneuroinformatics/prettier-config": "^0.0.1",
|
|
120
120
|
"@douglasneuroinformatics/semantic-release": "^0.0.1",
|
|
121
121
|
"@douglasneuroinformatics/tsconfig": "^1.0.2",
|
|
122
|
+
"@faker-js/faker": "^9.0.1",
|
|
122
123
|
"@storybook/addon-essentials": "^8.2.9",
|
|
123
124
|
"@storybook/addon-interactions": "^8.2.9",
|
|
124
125
|
"@storybook/addon-links": "^8.2.9",
|
|
@@ -5,8 +5,8 @@ import { Accordion } from './Accordion';
|
|
|
5
5
|
|
|
6
6
|
const TEST_ID = 'accordion';
|
|
7
7
|
|
|
8
|
-
const TestAccordion = () => (
|
|
9
|
-
<Accordion collapsible type="single">
|
|
8
|
+
const TestAccordion: React.FC<{ [key: string]: any }> = (props) => (
|
|
9
|
+
<Accordion collapsible type="single" {...props}>
|
|
10
10
|
<Accordion.Item value="item-1">
|
|
11
11
|
<Accordion.Trigger>T1</Accordion.Trigger>
|
|
12
12
|
<Accordion.Content>C1</Accordion.Content>
|
|
@@ -19,16 +19,17 @@ describe('Accordion', () => {
|
|
|
19
19
|
render(<TestAccordion />);
|
|
20
20
|
expect(screen.getByTestId(TEST_ID)).toBeDefined();
|
|
21
21
|
});
|
|
22
|
+
it('should include custom data attributes', () => {
|
|
23
|
+
render(<TestAccordion data-foo="bar" />);
|
|
24
|
+
expect(screen.getByTestId(TEST_ID)).toHaveAttribute('data-foo', 'bar');
|
|
25
|
+
});
|
|
22
26
|
it('should open and close an item', () => {
|
|
23
27
|
render(<TestAccordion />);
|
|
24
|
-
|
|
25
28
|
const toggle = screen.getByText('T1');
|
|
26
29
|
expect(toggle).toBeInTheDocument();
|
|
27
30
|
expect(() => screen.getByText('C1')).toThrow();
|
|
28
|
-
|
|
29
31
|
fireEvent.click(toggle);
|
|
30
32
|
expect(screen.getByText('C1')).toBeInTheDocument();
|
|
31
|
-
|
|
32
33
|
fireEvent.click(toggle);
|
|
33
34
|
expect(() => screen.getByText('C1')).toThrow();
|
|
34
35
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { ActionDropdown } from './ActionDropdown';
|
|
5
|
+
|
|
6
|
+
type Props = React.ComponentPropsWithoutRef<typeof ActionDropdown>;
|
|
7
|
+
|
|
8
|
+
const TEST_ID = 'ActionDropdown';
|
|
9
|
+
|
|
10
|
+
const TestActionDropdown: React.FC<Partial<Props>> = (props) => {
|
|
11
|
+
return (
|
|
12
|
+
<ActionDropdown
|
|
13
|
+
data-testid={TEST_ID}
|
|
14
|
+
options={['Option 1', 'Option 2']}
|
|
15
|
+
title="Action Dropdown"
|
|
16
|
+
onSelection={vi.fn()}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('ActionDropdown', () => {
|
|
23
|
+
it('should render', () => {
|
|
24
|
+
render(<TestActionDropdown />);
|
|
25
|
+
expect(screen.getByTestId(TEST_ID)).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
it('should include custom data attributes', () => {
|
|
28
|
+
render(<TestActionDropdown data-foo="bar" />);
|
|
29
|
+
expect(screen.getByTestId(TEST_ID)).toHaveAttribute('data-foo', 'bar');
|
|
30
|
+
});
|
|
31
|
+
it('should contain a custom class name', () => {
|
|
32
|
+
render(<TestActionDropdown className="foo" />);
|
|
33
|
+
expect(screen.getByTestId(TEST_ID)).toHaveClass('foo');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { cn } from '@/utils';
|
|
2
|
+
|
|
1
3
|
import { DropdownButton } from '../DropdownButton';
|
|
2
4
|
import { DropdownMenu } from '../DropdownMenu';
|
|
3
5
|
|
|
@@ -12,8 +14,12 @@ type ActionDropdownOptionKey<T> = T extends readonly string[]
|
|
|
12
14
|
: never;
|
|
13
15
|
|
|
14
16
|
export type ActionDropdownProps<T extends ActionDropdownOptions> = {
|
|
17
|
+
[key: `data-${string}`]: unknown;
|
|
18
|
+
|
|
15
19
|
align?: DropdownMenuContentProps['align'];
|
|
16
20
|
|
|
21
|
+
className?: string;
|
|
22
|
+
|
|
17
23
|
contentClassName?: string;
|
|
18
24
|
|
|
19
25
|
disabled?: boolean;
|
|
@@ -35,18 +41,20 @@ export type ActionDropdownProps<T extends ActionDropdownOptions> = {
|
|
|
35
41
|
// eslint-disable-next-line react/function-component-definition
|
|
36
42
|
export function ActionDropdown<const T extends ActionDropdownOptions>({
|
|
37
43
|
align = 'start',
|
|
44
|
+
className,
|
|
38
45
|
contentClassName,
|
|
39
46
|
disabled,
|
|
40
47
|
onSelection,
|
|
41
48
|
options,
|
|
42
49
|
title,
|
|
43
50
|
triggerClassName,
|
|
44
|
-
widthFull
|
|
51
|
+
widthFull,
|
|
52
|
+
...props
|
|
45
53
|
}: ActionDropdownProps<T>) {
|
|
46
54
|
const optionKeys: readonly string[] = options instanceof Array ? options : Object.keys(options);
|
|
47
55
|
return (
|
|
48
56
|
<DropdownMenu>
|
|
49
|
-
<div className=
|
|
57
|
+
<div className={cn('w-full', className)} {...props}>
|
|
50
58
|
<DropdownMenu.Trigger asChild>
|
|
51
59
|
<DropdownButton className={triggerClassName} disabled={disabled}>
|
|
52
60
|
{title}
|
|
@@ -53,6 +53,7 @@ export type ClientTableColumnProps<T extends ClientTableEntry> = {
|
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
export type ClientTableProps<T extends ClientTableEntry> = {
|
|
56
|
+
[key: `data-${string}`]: unknown;
|
|
56
57
|
className?: string;
|
|
57
58
|
columnDropdownOptions?: ClientTableDropdownOptions<T>;
|
|
58
59
|
columns: ClientTableColumn<T>[];
|
|
@@ -71,7 +72,8 @@ export const ClientTable = <T extends ClientTableEntry>({
|
|
|
71
72
|
entriesPerPage = 10,
|
|
72
73
|
minRows,
|
|
73
74
|
noWrap,
|
|
74
|
-
onEntryClick
|
|
75
|
+
onEntryClick,
|
|
76
|
+
...props
|
|
75
77
|
}: ClientTableProps<T>) => {
|
|
76
78
|
const [currentPage, setCurrentPage] = useState(1);
|
|
77
79
|
|
|
@@ -83,7 +85,7 @@ export const ClientTable = <T extends ClientTableEntry>({
|
|
|
83
85
|
const nRows = Math.max(currentEntries.length, minRows ?? -1);
|
|
84
86
|
|
|
85
87
|
return (
|
|
86
|
-
<div className={className}>
|
|
88
|
+
<div className={className} {...props}>
|
|
87
89
|
<div className="rounded-md border bg-card tracking-tight text-muted-foreground shadow-sm">
|
|
88
90
|
<Table>
|
|
89
91
|
<Table.Header>
|
|
@@ -167,19 +167,12 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
|
|
|
167
167
|
)}
|
|
168
168
|
<div className="flex w-full gap-3">
|
|
169
169
|
{/** Note - aria-label is used for testing in downstream packages */}
|
|
170
|
-
<Button
|
|
171
|
-
aria-label="Submit Button"
|
|
172
|
-
className="block w-full"
|
|
173
|
-
data-cy="submit-form"
|
|
174
|
-
disabled={readOnly}
|
|
175
|
-
type="submit"
|
|
176
|
-
variant="primary"
|
|
177
|
-
>
|
|
170
|
+
<Button aria-label="Submit" className="block w-full" disabled={readOnly} type="submit" variant="primary">
|
|
178
171
|
{submitBtnLabel ?? t('form.submit')}
|
|
179
172
|
</Button>
|
|
180
173
|
{resetBtn && (
|
|
181
174
|
<Button
|
|
182
|
-
aria-label="Reset
|
|
175
|
+
aria-label="Reset"
|
|
183
176
|
className="block w-full"
|
|
184
177
|
disabled={readOnly}
|
|
185
178
|
type="button"
|
|
@@ -30,19 +30,15 @@ export const NumberFieldSelect = <T extends number = number>({
|
|
|
30
30
|
<FieldGroup.Description description={description} />
|
|
31
31
|
</FieldGroup.Row>
|
|
32
32
|
<Select name={name} value={value?.toString() ?? ''} onValueChange={(value) => setValue(parseFloat(value) as T)}>
|
|
33
|
-
<Select.Trigger
|
|
34
|
-
data-cy={`${name}-select-trigger`}
|
|
35
|
-
data-testid={`${name}-select-trigger`}
|
|
36
|
-
disabled={disabled || readOnly}
|
|
37
|
-
>
|
|
33
|
+
<Select.Trigger data-testid={`${name}-select-trigger`} disabled={disabled || readOnly}>
|
|
38
34
|
<Select.Value />
|
|
39
35
|
</Select.Trigger>
|
|
40
|
-
<Select.Content data-
|
|
36
|
+
<Select.Content data-testid={`${name}-select-content`}>
|
|
41
37
|
{Object.keys(options).map((option) => {
|
|
42
38
|
// Option needs to be type number (this was a design flaw), but is actually always coerced to string anyways
|
|
43
39
|
const text = (disableAutoPrefix ? '' : `${option} - `) + options[option as any as T];
|
|
44
40
|
return (
|
|
45
|
-
<Select.Item data-
|
|
41
|
+
<Select.Item data-testid={`${name}-select-item-${option}`} key={option} value={option}>
|
|
46
42
|
{text}
|
|
47
43
|
</Select.Item>
|
|
48
44
|
);
|
|
@@ -29,16 +29,12 @@ export const StringFieldSelect = <T extends string = string>({
|
|
|
29
29
|
<FieldGroup.Description description={description} />
|
|
30
30
|
</FieldGroup.Row>
|
|
31
31
|
<Select name={name} value={value ?? ''} onValueChange={(value: T) => setValue(value)}>
|
|
32
|
-
<Select.Trigger
|
|
33
|
-
data-cy={`${name}-select-trigger`}
|
|
34
|
-
data-testid={`${name}-select-trigger`}
|
|
35
|
-
disabled={disabled || readOnly}
|
|
36
|
-
>
|
|
32
|
+
<Select.Trigger data-testid={`${name}-select-trigger`} disabled={disabled || readOnly}>
|
|
37
33
|
<Select.Value />
|
|
38
34
|
</Select.Trigger>
|
|
39
|
-
<Select.Content data-
|
|
35
|
+
<Select.Content data-testid={`${name}-select-content`}>
|
|
40
36
|
{Object.keys(options).map((option) => (
|
|
41
|
-
<Select.Item data-
|
|
37
|
+
<Select.Item data-testid={`${name}-select-item-${option}`} key={option} value={option}>
|
|
42
38
|
{options[option as T]}
|
|
43
39
|
</Select.Item>
|
|
44
40
|
))}
|
|
@@ -47,7 +47,7 @@ export const ListboxDropdown = <T extends ListboxDropdownOption>({
|
|
|
47
47
|
<DropdownMenu.CheckboxItem
|
|
48
48
|
checked={checked}
|
|
49
49
|
className="flex w-full items-center whitespace-nowrap bg-slate-50 p-2 text-sm hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700"
|
|
50
|
-
data-
|
|
50
|
+
data-testid="select-dropdown-option"
|
|
51
51
|
key={option.key}
|
|
52
52
|
onSelect={(event) => {
|
|
53
53
|
event.preventDefault();
|
package/src/testing/mocks.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker';
|
|
1
2
|
import { vi } from 'vitest';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -51,3 +52,11 @@ export const mockStorage = (name: 'localStorage' | 'sessionStorage'): void => {
|
|
|
51
52
|
value: new StorageMock()
|
|
52
53
|
});
|
|
53
54
|
};
|
|
55
|
+
|
|
56
|
+
export const mockTranslationStore = () => {
|
|
57
|
+
vi.mock('@/hooks/useTranslation', () => ({
|
|
58
|
+
useTranslation: () => ({
|
|
59
|
+
t: () => faker.word.sample()
|
|
60
|
+
})
|
|
61
|
+
}));
|
|
62
|
+
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { cleanup } from '@testing-library/react';
|
|
2
2
|
import { afterEach, vi } from 'vitest';
|
|
3
3
|
|
|
4
|
+
import { mockTranslationStore } from './mocks';
|
|
5
|
+
|
|
4
6
|
import '@testing-library/jest-dom/vitest';
|
|
5
7
|
|
|
6
8
|
vi.mock('zustand');
|
|
9
|
+
mockTranslationStore();
|
|
7
10
|
|
|
8
11
|
// Since we're not using vitest globals, we need to explicitly call cleanup()
|
|
9
12
|
// for testing-library. See:
|
|
Binary file
|