@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.
Files changed (134) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/CLAUDE.md +9 -0
  3. package/dist/components/avatar/Avatar.d.ts +11 -0
  4. package/dist/components/avatar/Avatar.d.ts.map +1 -0
  5. package/dist/components/avatar/Avatar.js +17 -0
  6. package/dist/components/avatar/Avatar.js.map +1 -0
  7. package/dist/components/avatar/Avatar.stories.d.ts +14 -0
  8. package/dist/components/avatar/Avatar.stories.d.ts.map +1 -0
  9. package/dist/components/avatar/Avatar.stories.js +66 -0
  10. package/dist/components/avatar/Avatar.stories.js.map +1 -0
  11. package/dist/components/avatar/Avatar.test.d.ts +2 -0
  12. package/dist/components/avatar/Avatar.test.d.ts.map +1 -0
  13. package/dist/components/avatar/Avatar.test.js +51 -0
  14. package/dist/components/avatar/Avatar.test.js.map +1 -0
  15. package/dist/components/dropdown/Dropdown.d.ts +2 -0
  16. package/dist/components/dropdown/Dropdown.d.ts.map +1 -1
  17. package/dist/components/dropdown/Dropdown.js +5 -1
  18. package/dist/components/dropdown/Dropdown.js.map +1 -1
  19. package/dist/components/dropdown/items/DropdownGroup.d.ts +3 -0
  20. package/dist/components/dropdown/items/DropdownGroup.d.ts.map +1 -0
  21. package/dist/components/dropdown/items/DropdownGroup.js +8 -0
  22. package/dist/components/dropdown/items/DropdownGroup.js.map +1 -0
  23. package/dist/components/dropdown/items/DropdownSeparator.d.ts +3 -0
  24. package/dist/components/dropdown/items/DropdownSeparator.d.ts.map +1 -0
  25. package/dist/components/dropdown/items/DropdownSeparator.js +8 -0
  26. package/dist/components/dropdown/items/DropdownSeparator.js.map +1 -0
  27. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts +2 -0
  28. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts.map +1 -1
  29. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js +2 -2
  30. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -1
  31. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts +12 -0
  32. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts.map +1 -1
  33. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js +13 -0
  34. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js.map +1 -1
  35. package/dist/components/table/Table.d.ts.map +1 -1
  36. package/dist/components/table/Table.js +0 -2
  37. package/dist/components/table/Table.js.map +1 -1
  38. package/dist/components/table/Table.stories.d.ts.map +1 -1
  39. package/dist/components/table/Table.stories.js +2 -10
  40. package/dist/components/table/Table.stories.js.map +1 -1
  41. package/dist/components/table/Table.test.js +2 -40
  42. package/dist/components/table/Table.test.js.map +1 -1
  43. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.d.ts.map +1 -1
  44. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.js +22 -12
  45. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.js.map +1 -1
  46. package/dist/components/userDropdown/UserDropdown.d.ts +47 -0
  47. package/dist/components/userDropdown/UserDropdown.d.ts.map +1 -0
  48. package/dist/components/userDropdown/UserDropdown.js +13 -0
  49. package/dist/components/userDropdown/UserDropdown.js.map +1 -0
  50. package/dist/components/userDropdown/UserDropdown.stories.d.ts +12 -0
  51. package/dist/components/userDropdown/UserDropdown.stories.d.ts.map +1 -0
  52. package/dist/components/userDropdown/UserDropdown.stories.js +222 -0
  53. package/dist/components/userDropdown/UserDropdown.stories.js.map +1 -0
  54. package/dist/components/userDropdown/UserDropdown.test.d.ts +2 -0
  55. package/dist/components/userDropdown/UserDropdown.test.d.ts.map +1 -0
  56. package/dist/components/userDropdown/UserDropdown.test.js +197 -0
  57. package/dist/components/userDropdown/UserDropdown.test.js.map +1 -0
  58. package/dist/components/userDropdown/assets/arbor.png +0 -0
  59. package/dist/components/userDropdown/assets/govhub.png +0 -0
  60. package/dist/components/userDropdown/assets/key.png +0 -0
  61. package/dist/components/userDropdown/assets/logos.d.ts +7 -0
  62. package/dist/components/userDropdown/assets/logos.d.ts.map +1 -0
  63. package/dist/components/userDropdown/assets/logos.js +13 -0
  64. package/dist/components/userDropdown/assets/logos.js.map +1 -0
  65. package/dist/components/userDropdown/assets/robin.png +0 -0
  66. package/dist/components/userDropdown/assets/sampeople.png +0 -0
  67. package/dist/components/userDropdown/assets/timetabler.png +0 -0
  68. package/dist/components/userDropdown/internal/UserDropdownAppItem.d.ts +3 -0
  69. package/dist/components/userDropdown/internal/UserDropdownAppItem.d.ts.map +1 -0
  70. package/dist/components/userDropdown/internal/UserDropdownAppItem.js +9 -0
  71. package/dist/components/userDropdown/internal/UserDropdownAppItem.js.map +1 -0
  72. package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.d.ts +9 -0
  73. package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.d.ts.map +1 -0
  74. package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.js +11 -0
  75. package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.js.map +1 -0
  76. package/dist/components/userDropdown/internal/UserDropdownSignOut.d.ts +7 -0
  77. package/dist/components/userDropdown/internal/UserDropdownSignOut.d.ts.map +1 -0
  78. package/dist/components/userDropdown/internal/UserDropdownSignOut.js +9 -0
  79. package/dist/components/userDropdown/internal/UserDropdownSignOut.js.map +1 -0
  80. package/dist/components/userDropdown/internal/UserDropdownTrigger.d.ts +11 -0
  81. package/dist/components/userDropdown/internal/UserDropdownTrigger.d.ts.map +1 -0
  82. package/dist/components/userDropdown/internal/UserDropdownTrigger.js +10 -0
  83. package/dist/components/userDropdown/internal/UserDropdownTrigger.js.map +1 -0
  84. package/dist/components/userDropdown/internal/UserDropdownUserInfo.d.ts +8 -0
  85. package/dist/components/userDropdown/internal/UserDropdownUserInfo.d.ts.map +1 -0
  86. package/dist/components/userDropdown/internal/UserDropdownUserInfo.js +17 -0
  87. package/dist/components/userDropdown/internal/UserDropdownUserInfo.js.map +1 -0
  88. package/dist/index.css +401 -1
  89. package/dist/index.css.map +1 -1
  90. package/dist/index.d.ts +4 -0
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +3 -0
  93. package/dist/index.js.map +1 -1
  94. package/package.json +2 -2
  95. package/src/components/avatar/Avatar.stories.tsx +84 -0
  96. package/src/components/avatar/Avatar.test.tsx +60 -0
  97. package/src/components/avatar/Avatar.tsx +68 -0
  98. package/src/components/avatar/avatar.scss +71 -0
  99. package/src/components/dropdown/Dropdown.tsx +5 -1
  100. package/src/components/dropdown/dropdown.scss +4 -1
  101. package/src/components/dropdown/items/DropdownGroup.tsx +11 -0
  102. package/src/components/dropdown/items/DropdownSeparator.tsx +9 -0
  103. package/src/components/formField/inputs/selectDropdown/SelectDropdown.stories.tsx +15 -0
  104. package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +5 -1
  105. package/src/components/table/Table.stories.tsx +2 -10
  106. package/src/components/table/Table.test.tsx +2 -49
  107. package/src/components/table/Table.tsx +0 -2
  108. package/src/components/table/cellRenderers/SelectDropdownCellRenderer.tsx +34 -19
  109. package/src/components/table/table.scss +4 -2
  110. package/src/components/userDropdown/UserDropdown.stories.tsx +237 -0
  111. package/src/components/userDropdown/UserDropdown.test.tsx +349 -0
  112. package/src/components/userDropdown/UserDropdown.tsx +110 -0
  113. package/src/components/userDropdown/assets/arbor.png +0 -0
  114. package/src/components/userDropdown/assets/govhub.png +0 -0
  115. package/src/components/userDropdown/assets/key.png +0 -0
  116. package/src/components/userDropdown/assets/logos.ts +13 -0
  117. package/src/components/userDropdown/assets/robin.png +0 -0
  118. package/src/components/userDropdown/assets/sampeople.png +0 -0
  119. package/src/components/userDropdown/assets/timetabler.png +0 -0
  120. package/src/components/userDropdown/internal/UserDropdownAppItem.tsx +21 -0
  121. package/src/components/userDropdown/internal/UserDropdownCollapsibleSection.tsx +38 -0
  122. package/src/components/userDropdown/internal/UserDropdownSignOut.tsx +19 -0
  123. package/src/components/userDropdown/internal/UserDropdownTrigger.tsx +42 -0
  124. package/src/components/userDropdown/internal/UserDropdownUserInfo.tsx +60 -0
  125. package/src/components/userDropdown/userDropdown.scss +377 -0
  126. package/src/index.scss +2 -0
  127. package/src/index.ts +4 -0
  128. package/tsconfig.json +1 -1
  129. package/vite-env.d.ts +31 -0
  130. package/dist/components/table/cellRenderers/SelectDropdownCellEditor.d.ts +0 -8
  131. package/dist/components/table/cellRenderers/SelectDropdownCellEditor.d.ts.map +0 -1
  132. package/dist/components/table/cellRenderers/SelectDropdownCellEditor.js +0 -19
  133. package/dist/components/table/cellRenderers/SelectDropdownCellEditor.js.map +0 -1
  134. 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
- cellEditorParams: { options: assessmentComponentOptions, placeholder: 'Select' },
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
- cellEditorParams: { options: visibilityOptions, placeholder: 'Select' },
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 dropdownButton = container.querySelector('.ds-table__select-dropdown-cell.ds-button--dropdown');
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 { Button } from 'Components/button/Button';
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, valueFormatted, placeholder = 'Select' } = props;
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 option = options.find((opt: SelectDropdownItemProps) => opt.value === valueStr);
18
- const displayText = selectedValue != null && selectedValue !== ''
19
- ? (option?.label ?? option?.value ?? String(selectedValue))
20
- : placeholder;
20
+ const [isOpen, setIsOpen] = useState(false);
21
21
 
22
- const handleMouseDown = (e: React.MouseEvent) => {
23
- e.preventDefault();
24
- e.stopPropagation();
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
- <Button
29
- variant="dropdown"
30
- iconRightName="chevron-down"
31
- className="ds-table__select-dropdown-cell"
32
- onMouseDown={handleMouseDown}
33
- >
34
- {displayText}
35
- </Button>
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
  };
@@ -7,8 +7,10 @@
7
7
  color: var(--form-field-text-default-color-text);
8
8
  }
9
9
 
10
- &__select-dropdown-cell {
11
- width: 100%;
10
+ &__select-dropdown {
11
+ .ds-button--dropdown {
12
+ width: 100%;
13
+ }
12
14
  }
13
15
 
14
16
  &__container {