@arbor-education/design-system.components 0.4.0 → 0.4.2
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/CHANGELOG.md +12 -0
- package/CLAUDE.md +9 -0
- package/dist/components/avatar/Avatar.d.ts +11 -0
- package/dist/components/avatar/Avatar.d.ts.map +1 -0
- package/dist/components/avatar/Avatar.js +17 -0
- package/dist/components/avatar/Avatar.js.map +1 -0
- package/dist/components/avatar/Avatar.stories.d.ts +14 -0
- package/dist/components/avatar/Avatar.stories.d.ts.map +1 -0
- package/dist/components/avatar/Avatar.stories.js +66 -0
- package/dist/components/avatar/Avatar.stories.js.map +1 -0
- package/dist/components/avatar/Avatar.test.d.ts +2 -0
- package/dist/components/avatar/Avatar.test.d.ts.map +1 -0
- package/dist/components/avatar/Avatar.test.js +51 -0
- package/dist/components/avatar/Avatar.test.js.map +1 -0
- package/dist/components/dropdown/Dropdown.d.ts +2 -0
- package/dist/components/dropdown/Dropdown.d.ts.map +1 -1
- package/dist/components/dropdown/Dropdown.js +5 -1
- package/dist/components/dropdown/Dropdown.js.map +1 -1
- package/dist/components/dropdown/items/DropdownGroup.d.ts +3 -0
- package/dist/components/dropdown/items/DropdownGroup.d.ts.map +1 -0
- package/dist/components/dropdown/items/DropdownGroup.js +8 -0
- package/dist/components/dropdown/items/DropdownGroup.js.map +1 -0
- package/dist/components/dropdown/items/DropdownSeparator.d.ts +3 -0
- package/dist/components/dropdown/items/DropdownSeparator.d.ts.map +1 -0
- package/dist/components/dropdown/items/DropdownSeparator.js +8 -0
- package/dist/components/dropdown/items/DropdownSeparator.js.map +1 -0
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts +2 -0
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js +2 -2
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts +12 -0
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js +13 -0
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js.map +1 -1
- package/dist/components/table/Table.d.ts.map +1 -1
- package/dist/components/table/Table.js +0 -2
- package/dist/components/table/Table.js.map +1 -1
- package/dist/components/table/Table.stories.d.ts.map +1 -1
- package/dist/components/table/Table.stories.js +2 -10
- package/dist/components/table/Table.stories.js.map +1 -1
- package/dist/components/table/Table.test.js +2 -40
- package/dist/components/table/Table.test.js.map +1 -1
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.d.ts.map +1 -1
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.js +22 -12
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.js.map +1 -1
- package/dist/components/userDropdown/UserDropdown.d.ts +47 -0
- package/dist/components/userDropdown/UserDropdown.d.ts.map +1 -0
- package/dist/components/userDropdown/UserDropdown.js +13 -0
- package/dist/components/userDropdown/UserDropdown.js.map +1 -0
- package/dist/components/userDropdown/UserDropdown.stories.d.ts +12 -0
- package/dist/components/userDropdown/UserDropdown.stories.d.ts.map +1 -0
- package/dist/components/userDropdown/UserDropdown.stories.js +222 -0
- package/dist/components/userDropdown/UserDropdown.stories.js.map +1 -0
- package/dist/components/userDropdown/UserDropdown.test.d.ts +2 -0
- package/dist/components/userDropdown/UserDropdown.test.d.ts.map +1 -0
- package/dist/components/userDropdown/UserDropdown.test.js +197 -0
- package/dist/components/userDropdown/UserDropdown.test.js.map +1 -0
- package/dist/components/userDropdown/assets/arbor.png +0 -0
- package/dist/components/userDropdown/assets/govhub.png +0 -0
- package/dist/components/userDropdown/assets/key.png +0 -0
- package/dist/components/userDropdown/assets/logos.d.ts +7 -0
- package/dist/components/userDropdown/assets/logos.d.ts.map +1 -0
- package/dist/components/userDropdown/assets/logos.js +13 -0
- package/dist/components/userDropdown/assets/logos.js.map +1 -0
- package/dist/components/userDropdown/assets/robin.png +0 -0
- package/dist/components/userDropdown/assets/sampeople.png +0 -0
- package/dist/components/userDropdown/assets/timetabler.png +0 -0
- package/dist/components/userDropdown/internal/UserDropdownAppItem.d.ts +3 -0
- package/dist/components/userDropdown/internal/UserDropdownAppItem.d.ts.map +1 -0
- package/dist/components/userDropdown/internal/UserDropdownAppItem.js +9 -0
- package/dist/components/userDropdown/internal/UserDropdownAppItem.js.map +1 -0
- package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.d.ts +9 -0
- package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.d.ts.map +1 -0
- package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.js +11 -0
- package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.js.map +1 -0
- package/dist/components/userDropdown/internal/UserDropdownSignOut.d.ts +7 -0
- package/dist/components/userDropdown/internal/UserDropdownSignOut.d.ts.map +1 -0
- package/dist/components/userDropdown/internal/UserDropdownSignOut.js +9 -0
- package/dist/components/userDropdown/internal/UserDropdownSignOut.js.map +1 -0
- package/dist/components/userDropdown/internal/UserDropdownTrigger.d.ts +11 -0
- package/dist/components/userDropdown/internal/UserDropdownTrigger.d.ts.map +1 -0
- package/dist/components/userDropdown/internal/UserDropdownTrigger.js +10 -0
- package/dist/components/userDropdown/internal/UserDropdownTrigger.js.map +1 -0
- package/dist/components/userDropdown/internal/UserDropdownUserInfo.d.ts +8 -0
- package/dist/components/userDropdown/internal/UserDropdownUserInfo.d.ts.map +1 -0
- package/dist/components/userDropdown/internal/UserDropdownUserInfo.js +17 -0
- package/dist/components/userDropdown/internal/UserDropdownUserInfo.js.map +1 -0
- package/dist/index.css +401 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/avatar/Avatar.stories.tsx +84 -0
- package/src/components/avatar/Avatar.test.tsx +60 -0
- package/src/components/avatar/Avatar.tsx +68 -0
- package/src/components/avatar/avatar.scss +71 -0
- package/src/components/dropdown/Dropdown.tsx +5 -1
- package/src/components/dropdown/dropdown.scss +4 -1
- package/src/components/dropdown/items/DropdownGroup.tsx +11 -0
- package/src/components/dropdown/items/DropdownSeparator.tsx +9 -0
- package/src/components/formField/inputs/selectDropdown/SelectDropdown.stories.tsx +15 -0
- package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +5 -1
- package/src/components/table/Table.stories.tsx +2 -10
- package/src/components/table/Table.test.tsx +2 -49
- package/src/components/table/Table.tsx +0 -2
- package/src/components/table/cellRenderers/SelectDropdownCellRenderer.tsx +34 -19
- package/src/components/table/table.scss +4 -2
- package/src/components/userDropdown/UserDropdown.stories.tsx +237 -0
- package/src/components/userDropdown/UserDropdown.test.tsx +349 -0
- package/src/components/userDropdown/UserDropdown.tsx +110 -0
- package/src/components/userDropdown/assets/arbor.png +0 -0
- package/src/components/userDropdown/assets/govhub.png +0 -0
- package/src/components/userDropdown/assets/key.png +0 -0
- package/src/components/userDropdown/assets/logos.ts +13 -0
- package/src/components/userDropdown/assets/robin.png +0 -0
- package/src/components/userDropdown/assets/sampeople.png +0 -0
- package/src/components/userDropdown/assets/timetabler.png +0 -0
- package/src/components/userDropdown/internal/UserDropdownAppItem.tsx +21 -0
- package/src/components/userDropdown/internal/UserDropdownCollapsibleSection.tsx +38 -0
- package/src/components/userDropdown/internal/UserDropdownSignOut.tsx +19 -0
- package/src/components/userDropdown/internal/UserDropdownTrigger.tsx +42 -0
- package/src/components/userDropdown/internal/UserDropdownUserInfo.tsx +60 -0
- package/src/components/userDropdown/userDropdown.scss +377 -0
- package/src/index.scss +2 -0
- package/src/index.ts +4 -0
- package/tsconfig.json +1 -1
- package/vite-env.d.ts +31 -0
- package/dist/components/table/cellRenderers/SelectDropdownCellEditor.d.ts +0 -8
- package/dist/components/table/cellRenderers/SelectDropdownCellEditor.d.ts.map +0 -1
- package/dist/components/table/cellRenderers/SelectDropdownCellEditor.js +0 -19
- package/dist/components/table/cellRenderers/SelectDropdownCellEditor.js.map +0 -1
- package/src/components/table/cellRenderers/SelectDropdownCellEditor.tsx +0 -43
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { Avatar } from './Avatar';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Avatar> = {
|
|
5
|
+
title: 'Components/Avatar',
|
|
6
|
+
component: Avatar,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
argTypes: {
|
|
9
|
+
size: {
|
|
10
|
+
control: 'select',
|
|
11
|
+
options: ['small', 'medium', 'large', 'extra-large'],
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type Story = StoryObj<typeof Avatar>;
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {
|
|
19
|
+
args: {
|
|
20
|
+
size: 'medium',
|
|
21
|
+
src: 'https://i.pravatar.cc/150?img=1',
|
|
22
|
+
alt: 'User avatar',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const WithInitials: Story = {
|
|
27
|
+
args: {
|
|
28
|
+
size: 'medium',
|
|
29
|
+
initials: 'CM',
|
|
30
|
+
alt: 'Christine Montgomery',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const Placeholder: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
size: 'medium',
|
|
37
|
+
alt: 'User avatar',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const Small: Story = {
|
|
42
|
+
args: {
|
|
43
|
+
size: 'small',
|
|
44
|
+
src: 'https://i.pravatar.cc/150?img=2',
|
|
45
|
+
alt: 'User avatar',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const Medium: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
size: 'medium',
|
|
52
|
+
src: 'https://i.pravatar.cc/150?img=3',
|
|
53
|
+
alt: 'User avatar',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const Large: Story = {
|
|
58
|
+
args: {
|
|
59
|
+
size: 'large',
|
|
60
|
+
src: 'https://i.pravatar.cc/150?img=4',
|
|
61
|
+
alt: 'User avatar',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const ExtraLarge: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
size: 'extra-large',
|
|
68
|
+
src: 'https://i.pravatar.cc/150?img=5',
|
|
69
|
+
alt: 'User avatar',
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const AllSizes: Story = {
|
|
74
|
+
render: () => (
|
|
75
|
+
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
|
|
76
|
+
<Avatar size="small" src="https://i.pravatar.cc/150?img=6" alt="Small avatar" />
|
|
77
|
+
<Avatar size="medium" src="https://i.pravatar.cc/150?img=7" alt="Medium avatar" />
|
|
78
|
+
<Avatar size="large" src="https://i.pravatar.cc/150?img=8" alt="Large avatar" />
|
|
79
|
+
<Avatar size="extra-large" src="https://i.pravatar.cc/150?img=9" alt="Extra large avatar" />
|
|
80
|
+
</div>
|
|
81
|
+
),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export default meta;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { expect, test, describe } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { Avatar } from './Avatar';
|
|
4
|
+
import '@testing-library/jest-dom/vitest';
|
|
5
|
+
|
|
6
|
+
describe('Avatar', () => {
|
|
7
|
+
test('renders with image when src is provided', () => {
|
|
8
|
+
render(<Avatar src="https://example.com/avatar.jpg" alt="Test User" />);
|
|
9
|
+
const image = screen.getByRole('img', { hidden: true });
|
|
10
|
+
expect(image).toBeInTheDocument();
|
|
11
|
+
expect(image).toHaveAttribute('src', 'https://example.com/avatar.jpg');
|
|
12
|
+
expect(image).toHaveAttribute('alt', 'Test User');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('renders with initials when no src is provided', () => {
|
|
16
|
+
render(<Avatar initials="CM" alt="Christine Montgomery" />);
|
|
17
|
+
expect(screen.getByText('CM')).toBeInTheDocument();
|
|
18
|
+
expect(screen.getByLabelText('Christine Montgomery')).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('renders placeholder when no src or initials provided', () => {
|
|
22
|
+
render(<Avatar alt="User avatar" />);
|
|
23
|
+
expect(screen.getByLabelText('User avatar')).toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('renders placeholder with default label when no alt provided', () => {
|
|
27
|
+
render(<Avatar />);
|
|
28
|
+
expect(screen.getByLabelText('User avatar')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('applies size variant class', () => {
|
|
32
|
+
const { container, rerender } = render(<Avatar size="small" />);
|
|
33
|
+
expect(container.firstChild).toHaveClass('ds-avatar--small');
|
|
34
|
+
|
|
35
|
+
rerender(<Avatar size="medium" />);
|
|
36
|
+
expect(container.firstChild).toHaveClass('ds-avatar--medium');
|
|
37
|
+
|
|
38
|
+
rerender(<Avatar size="large" />);
|
|
39
|
+
expect(container.firstChild).toHaveClass('ds-avatar--large');
|
|
40
|
+
|
|
41
|
+
rerender(<Avatar size="extra-large" />);
|
|
42
|
+
expect(container.firstChild).toHaveClass('ds-avatar--extra-large');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('applies default medium size when no size specified', () => {
|
|
46
|
+
const { container } = render(<Avatar />);
|
|
47
|
+
expect(container.firstChild).toHaveClass('ds-avatar--medium');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('applies custom className', () => {
|
|
51
|
+
const { container } = render(<Avatar className="custom-class" />);
|
|
52
|
+
expect(container.firstChild).toHaveClass('ds-avatar');
|
|
53
|
+
expect(container.firstChild).toHaveClass('custom-class');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('spreads additional HTML attributes', () => {
|
|
57
|
+
const { container } = render(<Avatar data-testid="avatar-test" />);
|
|
58
|
+
expect(container.firstChild).toHaveAttribute('data-testid', 'avatar-test');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
export type AvatarSize = 'small' | 'medium' | 'large' | 'extra-large';
|
|
5
|
+
|
|
6
|
+
export type AvatarProps = {
|
|
7
|
+
size?: AvatarSize;
|
|
8
|
+
src?: string;
|
|
9
|
+
alt?: string;
|
|
10
|
+
initials?: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
} & React.HTMLAttributes<HTMLDivElement>;
|
|
13
|
+
|
|
14
|
+
export const Avatar = (props: AvatarProps) => {
|
|
15
|
+
const {
|
|
16
|
+
size = 'medium',
|
|
17
|
+
src,
|
|
18
|
+
alt = '',
|
|
19
|
+
initials,
|
|
20
|
+
className,
|
|
21
|
+
...rest
|
|
22
|
+
} = props;
|
|
23
|
+
|
|
24
|
+
const renderContent = () => {
|
|
25
|
+
if (src) {
|
|
26
|
+
return (
|
|
27
|
+
<img
|
|
28
|
+
src={src}
|
|
29
|
+
alt={alt}
|
|
30
|
+
className="ds-avatar__image"
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (initials) {
|
|
36
|
+
return (
|
|
37
|
+
<span className="ds-avatar__initials" aria-label={alt}>
|
|
38
|
+
{initials}
|
|
39
|
+
</span>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<span className="ds-avatar__placeholder" aria-label={alt || 'User avatar'}>
|
|
45
|
+
<svg
|
|
46
|
+
viewBox="0 0 24 24"
|
|
47
|
+
fill="currentColor"
|
|
48
|
+
className="ds-avatar__placeholder-icon"
|
|
49
|
+
>
|
|
50
|
+
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" />
|
|
51
|
+
</svg>
|
|
52
|
+
</span>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
className={classNames(
|
|
59
|
+
'ds-avatar',
|
|
60
|
+
`ds-avatar--${size}`,
|
|
61
|
+
className,
|
|
62
|
+
)}
|
|
63
|
+
{...rest}
|
|
64
|
+
>
|
|
65
|
+
{renderContent()}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
.ds-avatar {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: center;
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
position: relative;
|
|
7
|
+
flex-shrink: 0;
|
|
8
|
+
|
|
9
|
+
// Size variants
|
|
10
|
+
&--small {
|
|
11
|
+
width: var(--avatar-small-size);
|
|
12
|
+
height: var(--avatar-small-size);
|
|
13
|
+
border-radius: var(--avatar-small-radius);
|
|
14
|
+
border: 1px solid var(--avatar-small-color-border);
|
|
15
|
+
background-color: var(--avatar-small-color-background);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&--medium {
|
|
19
|
+
width: var(--avatar-medium-size);
|
|
20
|
+
height: var(--avatar-medium-size);
|
|
21
|
+
border-radius: var(--avatar-medium-radius);
|
|
22
|
+
border: 1px solid var(--avatar-medium-color-border);
|
|
23
|
+
background-color: var(--avatar-medium-color-background);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&--large {
|
|
27
|
+
width: var(--avatar-large-size);
|
|
28
|
+
height: var(--avatar-large-size);
|
|
29
|
+
border-radius: var(--avatar-large-radius);
|
|
30
|
+
border: 1px solid var(--avatar-large-color-border);
|
|
31
|
+
background-color: var(--avatar-large-color-background);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&--extra-large {
|
|
35
|
+
width: var(--avatar-extra-large-size);
|
|
36
|
+
height: var(--avatar-extra-large-size);
|
|
37
|
+
border-radius: var(--avatar-extra-large-radius);
|
|
38
|
+
border: 1px solid var(--avatar-extra-large-color-border);
|
|
39
|
+
background-color: var(--avatar-extra-large-color-background);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&__image {
|
|
43
|
+
width: 100%;
|
|
44
|
+
height: 100%;
|
|
45
|
+
object-fit: cover;
|
|
46
|
+
pointer-events: none;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&__initials {
|
|
50
|
+
font-family: var(--type-body-bold-family);
|
|
51
|
+
font-weight: var(--type-body-bold-weight);
|
|
52
|
+
font-size: var(--type-body-bold-size);
|
|
53
|
+
color: var(--avatar-medium-color-text);
|
|
54
|
+
line-height: 1;
|
|
55
|
+
text-align: center;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&__placeholder {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: center;
|
|
62
|
+
width: 100%;
|
|
63
|
+
height: 100%;
|
|
64
|
+
color: var(--color-grey-400);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&__placeholder-icon {
|
|
68
|
+
width: 60%;
|
|
69
|
+
height: 60%;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -3,11 +3,13 @@ import { DropdownTrigger } from './DropdownTrigger';
|
|
|
3
3
|
import { DropdownContent } from './DropdownContent';
|
|
4
4
|
import { DropdownItem } from './items/DropdownItem';
|
|
5
5
|
import { DropdownSelectItem } from './items/DropdownSelectItem';
|
|
6
|
+
import { DropdownSeparator } from './items/DropdownSeparator';
|
|
7
|
+
import { DropdownGroup } from './items/DropdownGroup';
|
|
6
8
|
|
|
7
9
|
export const Dropdown = (props: DropdownMenu.DropdownMenuProps) => {
|
|
8
10
|
const { children, ...rest } = props;
|
|
9
11
|
return (
|
|
10
|
-
<DropdownMenu.Root {...rest}>
|
|
12
|
+
<DropdownMenu.Root modal={false} {...rest}>
|
|
11
13
|
{children}
|
|
12
14
|
</DropdownMenu.Root>
|
|
13
15
|
);
|
|
@@ -17,3 +19,5 @@ Dropdown.Trigger = DropdownTrigger;
|
|
|
17
19
|
Dropdown.Content = DropdownContent;
|
|
18
20
|
Dropdown.Item = DropdownItem;
|
|
19
21
|
Dropdown.SelectItem = DropdownSelectItem;
|
|
22
|
+
Dropdown.Separator = DropdownSeparator;
|
|
23
|
+
Dropdown.Group = DropdownGroup;
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
border: 1px solid var(--color-grey-200);
|
|
8
8
|
font-style: normal;
|
|
9
9
|
line-height: 150%;
|
|
10
|
+
max-height: 300px;
|
|
11
|
+
max-height: var(--radix-popper-available-height);
|
|
12
|
+
overflow-y: auto;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
.ds-dropdown__item {
|
|
@@ -42,4 +45,4 @@
|
|
|
42
45
|
.ds-dropdown__item--check-icon {
|
|
43
46
|
margin-left: auto;
|
|
44
47
|
}
|
|
45
|
-
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DropdownMenu } from 'radix-ui';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
|
|
4
|
+
export const DropdownGroup = (props: DropdownMenu.DropdownMenuGroupProps) => {
|
|
5
|
+
const { children, className = '', ...rest } = props;
|
|
6
|
+
return (
|
|
7
|
+
<DropdownMenu.Group className={classNames('ds-dropdown__group', className)} {...rest}>
|
|
8
|
+
{children}
|
|
9
|
+
</DropdownMenu.Group>
|
|
10
|
+
);
|
|
11
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DropdownMenu } from 'radix-ui';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
|
|
4
|
+
export const DropdownSeparator = (props: DropdownMenu.DropdownMenuSeparatorProps) => {
|
|
5
|
+
const { className = '', ...rest } = props;
|
|
6
|
+
return (
|
|
7
|
+
<DropdownMenu.Separator className={classNames('ds-dropdown__separator', className)} {...rest} />
|
|
8
|
+
);
|
|
9
|
+
};
|
|
@@ -182,4 +182,19 @@ export const MultilineItemsGroupedMultiSelect = {
|
|
|
182
182
|
onSelectionChange: (value: string[]) => { console.log(value); },
|
|
183
183
|
},
|
|
184
184
|
};
|
|
185
|
+
|
|
186
|
+
export const ControlledOpen = {
|
|
187
|
+
args: {
|
|
188
|
+
title: 'titleValue',
|
|
189
|
+
options: [
|
|
190
|
+
{ label: 'Option 1', value: 'option1' },
|
|
191
|
+
{ label: 'Option 2', value: 'option2' },
|
|
192
|
+
{ label: 'Option 3', value: 'option3' },
|
|
193
|
+
],
|
|
194
|
+
open: true,
|
|
195
|
+
onOpenChange: (open: boolean) => { console.log('open changed:', open); },
|
|
196
|
+
onSelectionChange: (value: string[]) => { console.log(value); },
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
185
200
|
export default meta;
|
|
@@ -16,6 +16,8 @@ export type SelectDropdownInputProps = {
|
|
|
16
16
|
'id'?: string;
|
|
17
17
|
'alwaysShowPlaceholder'?: boolean;
|
|
18
18
|
'initialSelectedValues'?: string[];
|
|
19
|
+
'open'?: boolean;
|
|
20
|
+
'onOpenChange'?: (open: boolean) => void;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
export const SelectDropdown = (props: SelectDropdownInputProps) => {
|
|
@@ -31,6 +33,8 @@ export const SelectDropdown = (props: SelectDropdownInputProps) => {
|
|
|
31
33
|
'aria-invalid': ariaInvalid,
|
|
32
34
|
alwaysShowPlaceholder = false,
|
|
33
35
|
initialSelectedValues = [],
|
|
36
|
+
open,
|
|
37
|
+
onOpenChange,
|
|
34
38
|
} = props;
|
|
35
39
|
|
|
36
40
|
const [selectedValues, setSelectedValues] = useState<string[]>(initialSelectedValues);
|
|
@@ -93,7 +97,7 @@ export const SelectDropdown = (props: SelectDropdownInputProps) => {
|
|
|
93
97
|
return (
|
|
94
98
|
<>
|
|
95
99
|
<input type="hidden" name={id} value={selectedValues.join(',')} />
|
|
96
|
-
<Dropdown>
|
|
100
|
+
<Dropdown open={open} onOpenChange={onOpenChange}>
|
|
97
101
|
<Dropdown.Trigger disabled={disabled}>
|
|
98
102
|
<Button
|
|
99
103
|
variant="dropdown"
|
|
@@ -723,29 +723,23 @@ const marksheetTidyTableColumnDefs: (ColDef | ColGroupDef)[] = [
|
|
|
723
723
|
headerName: 'Assessment Component',
|
|
724
724
|
field: 'assessmentComponent',
|
|
725
725
|
cellRenderer: 'dsSelectDropdownCellRenderer',
|
|
726
|
-
cellEditor: 'dsSelectDropdownCellEditor',
|
|
727
726
|
cellRendererParams: { options: assessmentComponentOptions, placeholder: 'Select' },
|
|
728
|
-
|
|
729
|
-
editable: params => !params.node.group,
|
|
727
|
+
editable: false,
|
|
730
728
|
},
|
|
731
729
|
{
|
|
732
730
|
headerName: 'Visibility',
|
|
733
731
|
field: 'visibility',
|
|
734
732
|
cellRenderer: 'dsSelectDropdownCellRenderer',
|
|
735
|
-
cellEditor: 'dsSelectDropdownCellEditor',
|
|
736
733
|
cellRendererParams: { options: visibilityOptions, placeholder: 'Select' },
|
|
737
|
-
|
|
738
|
-
editable: params => !params.node.group,
|
|
734
|
+
editable: false,
|
|
739
735
|
},
|
|
740
736
|
{
|
|
741
737
|
headerName: 'Editable',
|
|
742
738
|
field: 'editable',
|
|
743
|
-
editable: true,
|
|
744
739
|
},
|
|
745
740
|
{
|
|
746
741
|
headerName: 'Formatting',
|
|
747
742
|
field: 'formatting',
|
|
748
|
-
editable: false,
|
|
749
743
|
},
|
|
750
744
|
];
|
|
751
745
|
|
|
@@ -796,8 +790,6 @@ export const TidyTable: Story = {
|
|
|
796
790
|
tableTheme: 'tidy',
|
|
797
791
|
treeData: true,
|
|
798
792
|
treeDataChildrenField: 'children',
|
|
799
|
-
singleClickEdit: true,
|
|
800
|
-
stopEditingWhenCellsLoseFocus: true,
|
|
801
793
|
groupDefaultExpanded: -1,
|
|
802
794
|
icons: {
|
|
803
795
|
groupExpanded: '',
|
|
@@ -1297,59 +1297,12 @@ describe('Table', () => {
|
|
|
1297
1297
|
rowData={rowData}
|
|
1298
1298
|
/>,
|
|
1299
1299
|
);
|
|
1300
|
-
const
|
|
1300
|
+
const dropdownWrapper = container.querySelector('.ds-table__select-dropdown');
|
|
1301
|
+
const dropdownButton = dropdownWrapper?.querySelector('.ds-button--dropdown');
|
|
1301
1302
|
expect(dropdownButton).toBeInTheDocument();
|
|
1302
1303
|
});
|
|
1303
1304
|
});
|
|
1304
1305
|
|
|
1305
|
-
describe('SelectDropdownCellEditor', () => {
|
|
1306
|
-
const options = [
|
|
1307
|
-
{ label: 'Option 1', value: 'option1' },
|
|
1308
|
-
{ label: 'Option 2', value: 'option2' },
|
|
1309
|
-
{ label: 'Option 3', value: 'option3' },
|
|
1310
|
-
];
|
|
1311
|
-
const editableSelectColumnDefs = [{
|
|
1312
|
-
field: 'selectField',
|
|
1313
|
-
headerName: 'Select Field',
|
|
1314
|
-
cellRenderer: 'dsSelectDropdownCellRenderer',
|
|
1315
|
-
cellEditor: 'dsSelectDropdownCellEditor',
|
|
1316
|
-
cellRendererParams: { options, placeholder: 'Placeholder Text' },
|
|
1317
|
-
cellEditorParams: { options, placeholder: 'Placeholder Text' },
|
|
1318
|
-
editable: true,
|
|
1319
|
-
}];
|
|
1320
|
-
|
|
1321
|
-
async function renderAndStartEditing(rowData: { selectField: string }[]) {
|
|
1322
|
-
render(
|
|
1323
|
-
<Table
|
|
1324
|
-
columnDefs={editableSelectColumnDefs}
|
|
1325
|
-
rowData={rowData}
|
|
1326
|
-
/>,
|
|
1327
|
-
);
|
|
1328
|
-
await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
|
|
1329
|
-
const cell = screen.getByText('Option 1');
|
|
1330
|
-
await userEvent.dblClick(cell);
|
|
1331
|
-
const editorWrapper = await waitFor(() => document.querySelector('.ds-table__select-dropdown-editor'));
|
|
1332
|
-
return editorWrapper!.querySelector('button')!;
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
test('opens editor with dropdown; selecting option updates cell', async () => {
|
|
1336
|
-
const trigger = await renderAndStartEditing([{ selectField: 'option1' }]);
|
|
1337
|
-
expect(trigger).toHaveTextContent('Option 1');
|
|
1338
|
-
await userEvent.click(trigger);
|
|
1339
|
-
await userEvent.click(screen.getByText('Option 2'));
|
|
1340
|
-
await waitFor(() => expect(screen.getByText('Option 2')).toBeInTheDocument());
|
|
1341
|
-
});
|
|
1342
|
-
|
|
1343
|
-
test('Escape closes dropdown without changing value', async () => {
|
|
1344
|
-
const trigger = await renderAndStartEditing([{ selectField: 'option1' }]);
|
|
1345
|
-
await userEvent.click(trigger);
|
|
1346
|
-
await waitFor(() => expect(screen.getByText('Option 2')).toBeInTheDocument());
|
|
1347
|
-
await userEvent.keyboard('{Escape}');
|
|
1348
|
-
expect(screen.queryByText('Option 2')).not.toBeInTheDocument();
|
|
1349
|
-
expect(screen.getByText('Option 1')).toBeInTheDocument();
|
|
1350
|
-
});
|
|
1351
|
-
});
|
|
1352
|
-
|
|
1353
1306
|
describe('Cell Editing', () => {
|
|
1354
1307
|
describe('with literal data values', () => {
|
|
1355
1308
|
test('supports editing text fields', async () => {
|
|
@@ -19,7 +19,6 @@ import { defaultValueFormatter, DSDefaultColDef, shouldSuppressFocus } from './D
|
|
|
19
19
|
import { ButtonCellRenderer } from './cellRenderers/ButtonCellRenderer';
|
|
20
20
|
import { InlineTextCellRenderer } from './cellRenderers/InlineTextCellRenderer';
|
|
21
21
|
import { SelectDropdownCellRenderer } from './cellRenderers/SelectDropdownCellRenderer';
|
|
22
|
-
import { SelectDropdownCellEditor } from './cellRenderers/SelectDropdownCellEditor';
|
|
23
22
|
import { tidyTheme } from './theme/tidyTheme';
|
|
24
23
|
import { focusFirstFocusableElement } from 'Utils/focusFirstFocusableElement';
|
|
25
24
|
import { BooleanFilter } from './columnFilters/BooleanFilter/BooleanFilter';
|
|
@@ -192,7 +191,6 @@ export const Table = (props: TableProps) => {
|
|
|
192
191
|
dsButtonCellRenderer: ButtonCellRenderer,
|
|
193
192
|
dsInlineTextCellRenderer: InlineTextCellRenderer,
|
|
194
193
|
dsSelectDropdownCellRenderer: SelectDropdownCellRenderer,
|
|
195
|
-
dsSelectDropdownCellEditor: SelectDropdownCellEditor,
|
|
196
194
|
dsBooleanFilter: BooleanFilter,
|
|
197
195
|
dsTimeFilter: TimeFilter,
|
|
198
196
|
...components,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { CellKeyDownEvent, FullWidthCellKeyDownEvent } from 'ag-grid-community';
|
|
1
3
|
import type { CustomCellRendererProps } from 'ag-grid-react';
|
|
2
4
|
import type { SelectDropdownItemProps } from 'Components/formField/inputs/selectDropdown/items/item/SelectDropdownItem';
|
|
3
|
-
import {
|
|
5
|
+
import { SelectDropdown } from 'Components/formField/inputs/selectDropdown/SelectDropdown';
|
|
6
|
+
import { useComponentDidMount } from 'Utils/hooks/useComponentDidMount';
|
|
4
7
|
|
|
5
8
|
export type SelectDropdownCellRendererProps = CustomCellRendererProps & {
|
|
6
9
|
options?: SelectDropdownItemProps[];
|
|
@@ -8,30 +11,42 @@ export type SelectDropdownCellRendererProps = CustomCellRendererProps & {
|
|
|
8
11
|
};
|
|
9
12
|
|
|
10
13
|
export const SelectDropdownCellRenderer = (props: SelectDropdownCellRendererProps) => {
|
|
11
|
-
const { value,
|
|
14
|
+
const { value, placeholder = 'Select', node, column, api } = props;
|
|
12
15
|
const options = props.options ?? [];
|
|
13
16
|
|
|
14
|
-
const selectedValue = valueFormatted ?? value;
|
|
15
17
|
const valueStr = value != null && value !== '' ? String(value) : '';
|
|
18
|
+
const initialSelectedValues = valueStr ? [valueStr] : [];
|
|
16
19
|
|
|
17
|
-
const
|
|
18
|
-
const displayText = selectedValue != null && selectedValue !== ''
|
|
19
|
-
? (option?.label ?? option?.value ?? String(selectedValue))
|
|
20
|
-
: placeholder;
|
|
20
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
useComponentDidMount(() => {
|
|
23
|
+
const handleCellKeyDown = (event: CellKeyDownEvent | FullWidthCellKeyDownEvent) => {
|
|
24
|
+
if ('column' in event && event.node === node && event.column === column && (event.event as KeyboardEvent).key === 'Enter') {
|
|
25
|
+
setIsOpen(true);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
api.addEventListener('cellKeyDown', handleCellKeyDown);
|
|
30
|
+
return () => {
|
|
31
|
+
api.removeEventListener('cellKeyDown', handleCellKeyDown);
|
|
32
|
+
};
|
|
33
|
+
});
|
|
26
34
|
|
|
27
35
|
return (
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
<div className="ds-table__select-dropdown">
|
|
37
|
+
<SelectDropdown
|
|
38
|
+
options={options}
|
|
39
|
+
placeholder={placeholder}
|
|
40
|
+
initialSelectedValues={initialSelectedValues}
|
|
41
|
+
open={isOpen}
|
|
42
|
+
onOpenChange={setIsOpen}
|
|
43
|
+
multiple={false}
|
|
44
|
+
onSelectionChange={(newValue) => {
|
|
45
|
+
if (column) {
|
|
46
|
+
node.setDataValue(column, newValue[0]);
|
|
47
|
+
}
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
36
51
|
);
|
|
37
52
|
};
|