@arbor-education/design-system.components 0.11.1 → 0.13.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.
Files changed (154) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/avatar/Avatar.d.ts +4 -0
  3. package/dist/components/avatar/Avatar.d.ts.map +1 -1
  4. package/dist/components/avatarGroup/AvatarGroup.d.ts +6 -0
  5. package/dist/components/avatarGroup/AvatarGroup.d.ts.map +1 -1
  6. package/dist/components/badge/Badge.d.ts +5 -0
  7. package/dist/components/badge/Badge.d.ts.map +1 -1
  8. package/dist/components/banner/Banner.d.ts +7 -1
  9. package/dist/components/banner/Banner.d.ts.map +1 -1
  10. package/dist/components/banner/Banner.js +4 -1
  11. package/dist/components/banner/Banner.js.map +1 -1
  12. package/dist/components/button/Button.d.ts +5 -0
  13. package/dist/components/button/Button.d.ts.map +1 -1
  14. package/dist/components/card/Card.d.ts +3 -0
  15. package/dist/components/card/Card.d.ts.map +1 -1
  16. package/dist/components/combobox/Combobox.d.ts +9 -1
  17. package/dist/components/combobox/Combobox.d.ts.map +1 -1
  18. package/dist/components/datePicker/DatePicker.d.ts +4 -0
  19. package/dist/components/datePicker/DatePicker.d.ts.map +1 -1
  20. package/dist/components/datePicker/DatePicker.js +2 -2
  21. package/dist/components/datePicker/DatePicker.js.map +1 -1
  22. package/dist/components/datePicker/DatePicker.stories.d.ts +1 -0
  23. package/dist/components/datePicker/DatePicker.stories.d.ts.map +1 -1
  24. package/dist/components/dateTimePicker/DateTimePicker.d.ts +4 -1
  25. package/dist/components/dateTimePicker/DateTimePicker.d.ts.map +1 -1
  26. package/dist/components/dateTimePicker/DateTimePicker.stories.d.ts +3 -3
  27. package/dist/components/dot/Dot.d.ts +4 -0
  28. package/dist/components/dot/Dot.d.ts.map +1 -1
  29. package/dist/components/dropdown/Dropdown.d.ts +15 -10
  30. package/dist/components/dropdown/Dropdown.d.ts.map +1 -1
  31. package/dist/components/dropdown/Dropdown.js.map +1 -1
  32. package/dist/components/dropdown/DropdownContent.d.ts +1 -2
  33. package/dist/components/dropdown/DropdownContent.d.ts.map +1 -1
  34. package/dist/components/dropdown/DropdownTrigger.d.ts +3 -2
  35. package/dist/components/dropdown/DropdownTrigger.d.ts.map +1 -1
  36. package/dist/components/dropdown/DropdownTrigger.js.map +1 -1
  37. package/dist/components/formField/FormField.d.ts +4 -2
  38. package/dist/components/formField/FormField.d.ts.map +1 -1
  39. package/dist/components/formField/fieldset/Fieldset.d.ts +3 -0
  40. package/dist/components/formField/fieldset/Fieldset.d.ts.map +1 -1
  41. package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts +4 -2
  42. package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts.map +1 -1
  43. package/dist/components/formField/inputs/checkbox/CheckboxInput.d.ts +3 -0
  44. package/dist/components/formField/inputs/checkbox/CheckboxInput.d.ts.map +1 -1
  45. package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.d.ts +3 -0
  46. package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.d.ts.map +1 -1
  47. package/dist/components/formField/inputs/number/NumberInput.d.ts +3 -0
  48. package/dist/components/formField/inputs/number/NumberInput.d.ts.map +1 -1
  49. package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts +3 -0
  50. package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts.map +1 -1
  51. package/dist/components/formField/inputs/radio/RadioButtonInput.d.ts +3 -0
  52. package/dist/components/formField/inputs/radio/RadioButtonInput.d.ts.map +1 -1
  53. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts +3 -0
  54. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts.map +1 -1
  55. package/dist/components/formField/inputs/text/TextInput.d.ts +3 -0
  56. package/dist/components/formField/inputs/text/TextInput.d.ts.map +1 -1
  57. package/dist/components/formField/inputs/textArea/TextArea.d.ts +3 -0
  58. package/dist/components/formField/inputs/textArea/TextArea.d.ts.map +1 -1
  59. package/dist/components/formField/inputs/time/TimeInput.d.ts +5 -0
  60. package/dist/components/formField/inputs/time/TimeInput.d.ts.map +1 -1
  61. package/dist/components/formField/inputs/time/TimeInput.stories.d.ts +1 -1
  62. package/dist/components/heading/Heading.d.ts +5 -0
  63. package/dist/components/heading/Heading.d.ts.map +1 -1
  64. package/dist/components/icon/Icon.d.ts +7 -1
  65. package/dist/components/icon/Icon.d.ts.map +1 -1
  66. package/dist/components/modal/Modal.d.ts +16 -4
  67. package/dist/components/modal/Modal.d.ts.map +1 -1
  68. package/dist/components/modal/Modal.js.map +1 -1
  69. package/dist/components/progress/Progress.d.ts +3 -0
  70. package/dist/components/progress/Progress.d.ts.map +1 -1
  71. package/dist/components/row/Row.d.ts +3 -0
  72. package/dist/components/row/Row.d.ts.map +1 -1
  73. package/dist/components/singleUser/SingleUser.d.ts +3 -0
  74. package/dist/components/singleUser/SingleUser.d.ts.map +1 -1
  75. package/dist/components/slideover/Slideover.d.ts +3 -0
  76. package/dist/components/slideover/Slideover.d.ts.map +1 -1
  77. package/dist/components/table/Table.d.ts +21 -4
  78. package/dist/components/table/Table.d.ts.map +1 -1
  79. package/dist/components/table/Table.js +10 -1
  80. package/dist/components/table/Table.js.map +1 -1
  81. package/dist/components/table/Table.stories.d.ts +1 -0
  82. package/dist/components/table/Table.stories.d.ts.map +1 -1
  83. package/dist/components/table/Table.stories.js +87 -0
  84. package/dist/components/table/Table.stories.js.map +1 -1
  85. package/dist/components/table/Table.test.js +55 -7
  86. package/dist/components/table/Table.test.js.map +1 -1
  87. package/dist/components/table/cellEditors/DateCellEditor.d.ts +3 -0
  88. package/dist/components/table/cellEditors/DateCellEditor.d.ts.map +1 -0
  89. package/dist/components/table/cellEditors/DateCellEditor.js +13 -0
  90. package/dist/components/table/cellEditors/DateCellEditor.js.map +1 -0
  91. package/dist/components/table/cellEditors/DateCellEditor.test.d.ts +2 -0
  92. package/dist/components/table/cellEditors/DateCellEditor.test.d.ts.map +1 -0
  93. package/dist/components/table/cellEditors/DateCellEditor.test.js +81 -0
  94. package/dist/components/table/cellEditors/DateCellEditor.test.js.map +1 -0
  95. package/dist/components/tag/Tag.d.ts +4 -0
  96. package/dist/components/tag/Tag.d.ts.map +1 -1
  97. package/dist/components/toast/Toast.d.ts +5 -0
  98. package/dist/components/toast/Toast.d.ts.map +1 -1
  99. package/dist/components/tooltip/Tooltip.d.ts +7 -1
  100. package/dist/components/tooltip/Tooltip.d.ts.map +1 -1
  101. package/dist/components/tooltip/Tooltip.js.map +1 -1
  102. package/dist/components/tooltip/TooltipWrapper.d.ts +3 -0
  103. package/dist/components/tooltip/TooltipWrapper.d.ts.map +1 -1
  104. package/dist/components/userDropdown/UserDropdown.d.ts +7 -0
  105. package/dist/components/userDropdown/UserDropdown.d.ts.map +1 -1
  106. package/dist/index.d.ts +19 -26
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +17 -17
  109. package/dist/index.js.map +1 -1
  110. package/eslint.config.mts +1 -0
  111. package/package.json +1 -1
  112. package/src/components/avatar/Avatar.tsx +5 -0
  113. package/src/components/avatarGroup/AvatarGroup.tsx +7 -0
  114. package/src/components/badge/Badge.tsx +6 -0
  115. package/src/components/banner/Banner.tsx +10 -1
  116. package/src/components/button/Button.tsx +6 -0
  117. package/src/components/card/Card.tsx +4 -0
  118. package/src/components/combobox/Combobox.tsx +10 -1
  119. package/src/components/datePicker/DatePicker.tsx +7 -0
  120. package/src/components/dateTimePicker/DateTimePicker.tsx +4 -1
  121. package/src/components/dot/Dot.tsx +5 -0
  122. package/src/components/dropdown/Dropdown.tsx +13 -3
  123. package/src/components/dropdown/DropdownContent.tsx +1 -1
  124. package/src/components/dropdown/DropdownTrigger.tsx +3 -1
  125. package/src/components/formField/FormField.tsx +5 -1
  126. package/src/components/formField/fieldset/Fieldset.tsx +4 -0
  127. package/src/components/formField/inputs/checkbox/CheckboxGroup.tsx +5 -1
  128. package/src/components/formField/inputs/checkbox/CheckboxInput.tsx +4 -0
  129. package/src/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.tsx +4 -0
  130. package/src/components/formField/inputs/number/NumberInput.tsx +4 -0
  131. package/src/components/formField/inputs/radio/RadioButtonGroup.tsx +4 -0
  132. package/src/components/formField/inputs/radio/RadioButtonInput.tsx +4 -0
  133. package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +4 -0
  134. package/src/components/formField/inputs/text/TextInput.tsx +4 -0
  135. package/src/components/formField/inputs/textArea/TextArea.tsx +4 -0
  136. package/src/components/formField/inputs/time/TimeInput.tsx +6 -0
  137. package/src/components/heading/Heading.tsx +6 -0
  138. package/src/components/icon/Icon.tsx +8 -1
  139. package/src/components/modal/Modal.tsx +13 -3
  140. package/src/components/progress/Progress.tsx +4 -0
  141. package/src/components/row/Row.tsx +4 -0
  142. package/src/components/singleUser/SingleUser.tsx +4 -0
  143. package/src/components/slideover/Slideover.tsx +4 -0
  144. package/src/components/table/Table.stories.tsx +102 -0
  145. package/src/components/table/Table.test.tsx +88 -9
  146. package/src/components/table/Table.tsx +22 -2
  147. package/src/components/table/cellEditors/DateCellEditor.test.tsx +109 -0
  148. package/src/components/table/cellEditors/DateCellEditor.tsx +27 -0
  149. package/src/components/tag/Tag.tsx +5 -0
  150. package/src/components/toast/Toast.tsx +6 -0
  151. package/src/components/tooltip/Tooltip.tsx +7 -1
  152. package/src/components/tooltip/TooltipWrapper.tsx +4 -0
  153. package/src/components/userDropdown/UserDropdown.tsx +8 -0
  154. package/src/index.ts +19 -39
@@ -15,3 +15,7 @@ export const Fieldset = (props: FieldsetProps) => {
15
15
  </fieldset>
16
16
  );
17
17
  };
18
+
19
+ export namespace Fieldset {
20
+ export type Props = FieldsetProps;
21
+ }
@@ -1,7 +1,7 @@
1
1
  import { Fieldset, type FieldsetProps } from 'Components/formField/fieldset/Fieldset';
2
2
  import { CheckboxInput, type CheckboxInputProps } from './CheckboxInput';
3
3
 
4
- type CheckboxGroupProps = {
4
+ export type CheckboxGroupProps = {
5
5
  options: CheckboxInputProps[];
6
6
  } & FieldsetProps;
7
7
 
@@ -15,3 +15,7 @@ export const CheckboxGroup = (props: CheckboxGroupProps) => {
15
15
  </Fieldset>
16
16
  );
17
17
  };
18
+
19
+ export namespace CheckboxGroup {
20
+ export type Props = CheckboxGroupProps;
21
+ }
@@ -79,3 +79,7 @@ export const CheckboxInput = (props: CheckboxInputProps) => {
79
79
  </label>
80
80
  );
81
81
  };
82
+
83
+ export namespace CheckboxInput {
84
+ export type Props = CheckboxInputProps;
85
+ }
@@ -51,3 +51,7 @@ export const ColourPickerDropdown = ({ value = '#3cad51', onChange, hasError, di
51
51
  </>
52
52
  );
53
53
  };
54
+
55
+ export namespace ColourPickerDropdown {
56
+ export type Props = ColourPickerDropdownProps;
57
+ }
@@ -131,3 +131,7 @@ export const NumberInput = (props: NumberInputProps) => {
131
131
 
132
132
  );
133
133
  };
134
+
135
+ export namespace NumberInput {
136
+ export type Props = NumberInputProps;
137
+ }
@@ -20,3 +20,7 @@ export const RadioButtonGroup = (props: RadioButtonGroupProps) => {
20
20
  </Fieldset>
21
21
  );
22
22
  };
23
+
24
+ export namespace RadioButtonGroup {
25
+ export type Props = RadioButtonGroupProps;
26
+ }
@@ -44,3 +44,7 @@ export const RadioButtonInput = (props: RadioButtonInputProps) => {
44
44
  </label>
45
45
  );
46
46
  };
47
+
48
+ export namespace RadioButtonInput {
49
+ export type Props = RadioButtonInputProps;
50
+ }
@@ -142,3 +142,7 @@ export const SelectDropdown = (props: SelectDropdownInputProps) => {
142
142
  </>
143
143
  );
144
144
  };
145
+
146
+ export namespace SelectDropdown {
147
+ export type Props = SelectDropdownInputProps;
148
+ }
@@ -36,3 +36,7 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>((props, re
36
36
  });
37
37
 
38
38
  TextInput.displayName = 'TextInput';
39
+
40
+ export namespace TextInput {
41
+ export type Props = TextInputProps;
42
+ }
@@ -54,3 +54,7 @@ export const TextArea = (props: TextAreaProps) => {
54
54
  />
55
55
  );
56
56
  };
57
+
58
+ export namespace TextArea {
59
+ export type Props = TextAreaProps;
60
+ }
@@ -166,3 +166,9 @@ export const TimeInput = forwardRef<HTMLInputElement, TimeInputProps>((props, re
166
166
  });
167
167
 
168
168
  TimeInput.displayName = 'TimeInput';
169
+
170
+ export namespace TimeInput {
171
+ export type Props = TimeInputProps;
172
+ export type Value = TimeValue;
173
+ export type Granularity = TimeGranularity;
174
+ }
@@ -36,3 +36,9 @@ export const Heading = (props: HeadingProps) => {
36
36
  };
37
37
 
38
38
  Heading.InnerContainer = HeadingInnerContainer;
39
+
40
+ export namespace Heading {
41
+ export type Props = HeadingProps;
42
+ export type Level = HeadingLevel;
43
+ export type InnerContainerProps = HeadingSubContainerProps;
44
+ }
@@ -1,4 +1,4 @@
1
- import type { IconSize } from './types';
1
+ import type { CustomIconProps, IconSize } from './types';
2
2
  import { allowedIcons, type IconName } from './allowedIcons';
3
3
  import classNames from 'classnames';
4
4
 
@@ -24,3 +24,10 @@ export const Icon = (props: IconProps) => {
24
24
  </>
25
25
  );
26
26
  };
27
+
28
+ export namespace Icon {
29
+ export type Props = IconProps;
30
+ export type Name = IconName;
31
+ export type Size = IconSize;
32
+ export type CustomProps = CustomIconProps;
33
+ }
@@ -1,12 +1,13 @@
1
1
  import classNames from 'classnames';
2
2
  import { Dialog } from 'radix-ui';
3
- import { ModalHeader } from './ModalHeader';
4
- import { ModalFooter } from './ModalFooter';
5
- import { ModalBody } from './ModalBody';
3
+ import { ModalHeader, type ModalHeaderProps } from './ModalHeader';
4
+ import { ModalFooter, type ModalFooterProps } from './ModalFooter';
5
+ import { ModalBody, type ModalBodyProps } from './ModalBody';
6
6
  import { PopupParentContext } from 'Utils/PopupParentContext';
7
7
  import { createContext, useRef } from 'react';
8
8
  import { ModalCloseButon } from './ModalCloseButton';
9
9
  import { ModalTitle } from './ModalTitle';
10
+ import type { ButtonProps } from 'Components/button/Button';
10
11
 
11
12
  type ModalContextValue = {
12
13
  closeHandler?: () => void;
@@ -63,3 +64,12 @@ Modal.Body = ModalBody;
63
64
  Modal.Footer = ModalFooter;
64
65
  Modal.CloseButton = ModalCloseButon;
65
66
  Modal.Title = ModalTitle;
67
+
68
+ export namespace Modal {
69
+ export type Props = ModalProps;
70
+ export type HeaderProps = ModalHeaderProps;
71
+ export type BodyProps = ModalBodyProps;
72
+ export type FooterProps = ModalFooterProps;
73
+ export type CloseButtonProps = ButtonProps;
74
+ export type TitleProps = Dialog.DialogTitleProps;
75
+ }
@@ -14,3 +14,7 @@ export const Progress = (props: ProgressProps) => {
14
14
  </RadixProgress.Root>
15
15
  );
16
16
  };
17
+
18
+ export namespace Progress {
19
+ export type Props = ProgressProps;
20
+ }
@@ -52,3 +52,7 @@ export const Row = (props: RowProps) => {
52
52
  </div>
53
53
  );
54
54
  };
55
+
56
+ export namespace Row {
57
+ export type Props = RowProps;
58
+ }
@@ -43,3 +43,7 @@ export const SingleUser = React.forwardRef<HTMLSpanElement, SingleUserProps>(
43
43
  );
44
44
 
45
45
  SingleUser.displayName = 'SingleUser';
46
+
47
+ export namespace SingleUser {
48
+ export type Props = SingleUserProps;
49
+ }
@@ -60,3 +60,7 @@ export const Slideover = (props: SlideoverProps) => {
60
60
  </aside>
61
61
  );
62
62
  };
63
+
64
+ export namespace Slideover {
65
+ export type Props = SlideoverProps;
66
+ }
@@ -1432,6 +1432,108 @@ export const TableWithSemanticColors: Story = {
1432
1432
  },
1433
1433
  };
1434
1434
 
1435
+ interface DateEditorRowData {
1436
+ id: number;
1437
+ name: { value: string };
1438
+ dateOfBirth: Date;
1439
+ enrollmentDate: Date;
1440
+ graduationDate: Date | null;
1441
+ }
1442
+
1443
+ const dateEditorSampleData: DateEditorRowData[] = [
1444
+ {
1445
+ id: 1,
1446
+ name: { value: 'Alice Johnson' },
1447
+ dateOfBirth: new Date(2008, 4, 15),
1448
+ enrollmentDate: new Date(2022, 8, 1),
1449
+ graduationDate: new Date(2026, 6, 20),
1450
+ },
1451
+ {
1452
+ id: 2,
1453
+ name: { value: 'Bob Smith' },
1454
+ dateOfBirth: new Date(2009, 1, 28),
1455
+ enrollmentDate: new Date(2023, 8, 1),
1456
+ graduationDate: null,
1457
+ },
1458
+ {
1459
+ id: 3,
1460
+ name: { value: 'Charlie Brown' },
1461
+ dateOfBirth: new Date(2007, 10, 3),
1462
+ enrollmentDate: new Date(2021, 8, 1),
1463
+ graduationDate: new Date(2025, 6, 18),
1464
+ },
1465
+ {
1466
+ id: 4,
1467
+ name: { value: 'Diana Prince' },
1468
+ dateOfBirth: new Date(2008, 7, 22),
1469
+ enrollmentDate: new Date(2022, 8, 1),
1470
+ graduationDate: new Date(2026, 6, 20),
1471
+ },
1472
+ {
1473
+ id: 5,
1474
+ name: { value: 'Ethan Hunt' },
1475
+ dateOfBirth: new Date(2009, 11, 10),
1476
+ enrollmentDate: new Date(2023, 8, 1),
1477
+ graduationDate: null,
1478
+ },
1479
+ ];
1480
+
1481
+ const dateEditorColDefs: (ColDef | ColGroupDef)[] = [
1482
+ {
1483
+ field: 'name',
1484
+ headerName: 'Student Name',
1485
+ editable: false,
1486
+ valueFormatter: Table.DefaultValueFormatter,
1487
+ },
1488
+ {
1489
+ field: 'dateOfBirth',
1490
+ headerName: 'Date of Birth',
1491
+ cellEditor: 'dsDateCellEditor',
1492
+ valueFormatter: params =>
1493
+ params.value instanceof Date
1494
+ ? params.value.toLocaleDateString('en-GB')
1495
+ : params.value ?? '',
1496
+ },
1497
+ {
1498
+ field: 'enrollmentDate',
1499
+ headerName: 'Enrollment Date',
1500
+ cellEditor: 'dsDateCellEditor',
1501
+ valueFormatter: params =>
1502
+ params.value instanceof Date
1503
+ ? params.value.toLocaleDateString('en-GB')
1504
+ : params.value ?? '',
1505
+ },
1506
+ {
1507
+ field: 'graduationDate',
1508
+ headerName: 'Graduation Date',
1509
+ cellEditor: 'dsDateCellEditor',
1510
+ valueFormatter: params =>
1511
+ params.value instanceof Date
1512
+ ? params.value.toLocaleDateString('en-GB')
1513
+ : params.value ?? '',
1514
+ },
1515
+ ];
1516
+
1517
+ export const WithDateCellEditor: Story = {
1518
+ parameters: {
1519
+ docs: {
1520
+ description: {
1521
+ story:
1522
+ 'Date columns can use the `dsDateCellEditor` cell editor to provide an inline date picker. Double-click or press Enter on a date cell to open the editor.',
1523
+ },
1524
+ },
1525
+ },
1526
+ args: {
1527
+ rowData: dateEditorSampleData,
1528
+ columnDefs: dateEditorColDefs,
1529
+ defaultColDef: {
1530
+ ...defaultColDef,
1531
+ editable: true,
1532
+ },
1533
+ domLayout: 'autoHeight',
1534
+ },
1535
+ };
1536
+
1435
1537
  interface BooleanRowData {
1436
1538
  id: number;
1437
1539
  name: string;
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, expectTypeOf, test, vi } from 'vitest';
2
- import { render, screen, waitFor } from '@testing-library/react';
3
- import { Table, TABLE_SPACING } from './Table';
2
+ import { render, screen, waitFor, fireEvent } from '@testing-library/react';
3
+ import { Table } from './Table';
4
4
  import '@testing-library/jest-dom/vitest';
5
5
  import { BulkActionsDropdown } from 'Components/table/tableControls/BulkActionsDropdown';
6
6
  import { HideColumnsDropdown } from 'Components/table/tableControls/HideColumnsDropdown';
@@ -753,7 +753,7 @@ describe('Table', () => {
753
753
  actions={[
754
754
  {
755
755
  displayName: 'Test Action',
756
- callback: () => {},
756
+ callback: () => { },
757
757
  },
758
758
  ]}
759
759
  />
@@ -817,7 +817,7 @@ describe('Table', () => {
817
817
  expect((largeRadio as HTMLInputElement).checked).toBe(true);
818
818
  });
819
819
  expect(onTableSettingsChanged).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({
820
- tableSpacing: TABLE_SPACING.L,
820
+ tableSpacing: Table.Spacing.L,
821
821
  }));
822
822
 
823
823
  const columnBordersCheckbox = screen.getByLabelText('Column borders');
@@ -896,7 +896,7 @@ describe('Table', () => {
896
896
  await userEvent.click(largeRadio);
897
897
 
898
898
  await waitFor(() => {
899
- expect(onTableSpacingChanged).toHaveBeenCalledExactlyOnceWith(TABLE_SPACING.L);
899
+ expect(onTableSpacingChanged).toHaveBeenCalledExactlyOnceWith(Table.Spacing.L);
900
900
  });
901
901
 
902
902
  const xsmallRadio = screen.getByLabelText('X-Small');
@@ -904,7 +904,7 @@ describe('Table', () => {
904
904
 
905
905
  await waitFor(() => {
906
906
  expect(onTableSpacingChanged).toHaveBeenCalledTimes(2);
907
- expect(onTableSpacingChanged).toHaveBeenNthCalledWith(2, TABLE_SPACING.XS);
907
+ expect(onTableSpacingChanged).toHaveBeenNthCalledWith(2, Table.Spacing.XS);
908
908
  });
909
909
  });
910
910
 
@@ -924,14 +924,14 @@ describe('Table', () => {
924
924
  await userEvent.click(largeRadio);
925
925
 
926
926
  await waitFor(() => {
927
- expect(onTableSpacingChanged).toHaveBeenCalledWith(TABLE_SPACING.L);
927
+ expect(onTableSpacingChanged).toHaveBeenCalledWith(Table.Spacing.L);
928
928
  });
929
929
 
930
930
  const resetButton = screen.getByRole('button', { name: /Reset Table Settings/i });
931
931
  await userEvent.click(resetButton);
932
932
 
933
933
  await waitFor(() => {
934
- expect(onTableSpacingChanged).toHaveBeenCalledWith(TABLE_SPACING.M);
934
+ expect(onTableSpacingChanged).toHaveBeenCalledWith(Table.Spacing.M);
935
935
  });
936
936
  });
937
937
 
@@ -1049,7 +1049,7 @@ describe('Table', () => {
1049
1049
  ];
1050
1050
 
1051
1051
  const rowData = [
1052
- { },
1052
+ {},
1053
1053
  ];
1054
1054
  await expect(async () => {
1055
1055
  render(
@@ -1487,6 +1487,85 @@ describe('Table', () => {
1487
1487
  });
1488
1488
  });
1489
1489
 
1490
+ describe('DateCellEditor', () => {
1491
+ const columnDefs = [{
1492
+ field: 'dateOfBirth',
1493
+ headerName: 'Date of Birth',
1494
+ editable: true,
1495
+ cellEditor: 'dsDateCellEditor',
1496
+ }];
1497
+
1498
+ test('opens the date picker editor via grid API', async () => {
1499
+ let gridApi: GridApi;
1500
+ const rowData = [{ dateOfBirth: new Date(2024, 5, 15) }];
1501
+ render(
1502
+ <Table
1503
+ columnDefs={columnDefs}
1504
+ rowData={rowData}
1505
+ onGridReady={(event) => { gridApi = event.api; }}
1506
+ />,
1507
+ );
1508
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1509
+
1510
+ gridApi!.startEditingCell({ rowIndex: 0, colKey: 'dateOfBirth' });
1511
+
1512
+ await waitFor(() => expect(screen.getByRole('button', { name: 'Open date picker' })).toBeInTheDocument());
1513
+ });
1514
+
1515
+ test('autofocuses the date input so a user can type a date without clicking', async () => {
1516
+ let gridApi: GridApi;
1517
+ const onCellValueChanged = vi.fn();
1518
+ const rowData = [{ dateOfBirth: new Date(2024, 5, 15) }];
1519
+ const { container } = render(
1520
+ <Table
1521
+ columnDefs={columnDefs}
1522
+ rowData={rowData}
1523
+ onGridReady={(event) => { gridApi = event.api; }}
1524
+ onCellValueChanged={onCellValueChanged}
1525
+ />,
1526
+ );
1527
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1528
+
1529
+ gridApi!.setFocusedCell(0, 'dateOfBirth');
1530
+ await userEvent.keyboard('{Enter}');
1531
+
1532
+ await waitFor(() => expect(container.querySelector('input[type="date"]')).toBeInTheDocument());
1533
+ const input = container.querySelector('input[type="date"]') as HTMLInputElement;
1534
+ expect(input).toHaveFocus();
1535
+
1536
+ fireEvent.change(input, { target: { value: '2024-07-20' } });
1537
+ await userEvent.keyboard('{Enter}');
1538
+
1539
+ await waitFor(() => {
1540
+ expect(onCellValueChanged).toHaveBeenCalled();
1541
+ });
1542
+ const lastDate: Date = onCellValueChanged.mock.lastCall![0].newValue;
1543
+ expect(lastDate.getFullYear()).toBe(2024);
1544
+ expect(lastDate.getMonth()).toBe(6);
1545
+ expect(lastDate.getDate()).toBe(20);
1546
+ });
1547
+
1548
+ test('opens the editor with the current value and closes it cleanly', async () => {
1549
+ let gridApi: GridApi;
1550
+ const rowData = [{ dateOfBirth: new Date(2024, 5, 15) }];
1551
+ const { container } = render(
1552
+ <Table
1553
+ columnDefs={columnDefs}
1554
+ rowData={rowData}
1555
+ onGridReady={(event) => { gridApi = event.api; }}
1556
+ />,
1557
+ );
1558
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1559
+
1560
+ gridApi!.startEditingCell({ rowIndex: 0, colKey: 'dateOfBirth' });
1561
+ await waitFor(() => expect(container.querySelector('input[type="date"]')).toBeInTheDocument());
1562
+ expect(container.querySelector('input[type="date"]')).toHaveValue('2024-06-15');
1563
+
1564
+ gridApi!.stopEditing();
1565
+ await waitFor(() => expect(container.querySelector('input[type="date"]')).not.toBeInTheDocument());
1566
+ });
1567
+ });
1568
+
1490
1569
  describe('CheckboxCellRenderer', () => {
1491
1570
  test('renders checkboxes in table cells', async () => {
1492
1571
  const columnDefs = [
@@ -23,12 +23,15 @@ import { tidyTheme } from './theme/tidyTheme';
23
23
  import { focusFirstFocusableElement } from 'Utils/focusFirstFocusableElement';
24
24
  import { BooleanFilter } from './columnFilters/BooleanFilter/BooleanFilter';
25
25
  import { TimeFilter } from './columnFilters/TimeFilter/TimeFilter';
26
+ import { DateCellEditor } from './cellEditors/DateCellEditor';
26
27
  import { TableSettingsDropdown } from './tableControls/TableSettingsDropdown';
27
- import { TableControls } from './tableControls/TableControls';
28
+ import { TableControls, type TableControlsProps, type BulkAction as BulkActionType } from './tableControls/TableControls';
29
+ import type { HideColumnsDropdownProps } from './tableControls/HideColumnsDropdown';
28
30
  import { TABLE_SPACING } from './tableConsts';
29
31
  import { TableSettingsContext } from './TableSettingsContext';
30
32
  import { BooleanCellRenderer } from './cellRenderers/BooleanCellRenderer';
31
33
  import { CheckboxCellRenderer } from './cellRenderers/CheckboxCellRenderer';
34
+ import { DefaultCellRenderer } from './cellRenderers/DefaultCellRenderer';
32
35
 
33
36
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
37
  type TableProps<TData = any> = {
@@ -48,7 +51,6 @@ setAgGridLicenseKey();
48
51
 
49
52
  ModuleRegistry.registerModules([AllEnterpriseModule]);
50
53
 
51
- export { TABLE_SPACING } from './tableConsts';
52
54
  export { TableSettingsContext } from './TableSettingsContext';
53
55
 
54
56
  export const Table = (props: TableProps) => {
@@ -183,6 +185,7 @@ export const Table = (props: TableProps) => {
183
185
  dsSelectDropdownCellRenderer: SelectDropdownCellRenderer,
184
186
  dsBooleanFilter: BooleanFilter,
185
187
  dsTimeFilter: TimeFilter,
188
+ dsDateCellEditor: DateCellEditor,
186
189
  dsCheckboxCellRenderer: CheckboxCellRenderer,
187
190
  dsBooleanCellRenderer: BooleanCellRenderer,
188
191
  ...components,
@@ -208,6 +211,23 @@ Table.RowCountInfo = RowCountInfo;
208
211
  Table.BulkActionsDropdown = BulkActionsDropdown;
209
212
  Table.HideColumnsDropdown = HideColumnsDropdown;
210
213
  Table.ButtonCellRenderer = ButtonCellRenderer;
214
+ Table.BooleanCellRenderer = BooleanCellRenderer;
215
+ Table.DefaultCellRenderer = DefaultCellRenderer;
216
+ Table.DateCellEditor = DateCellEditor;
217
+ Table.DefaultColDef = DSDefaultColDef;
218
+ Table.GridApiContext = GridApiContext;
219
+ Table.CheckboxCellRenderer = CheckboxCellRenderer;
211
220
  Table.DefaultValueFormatter = defaultValueFormatter;
212
221
  Table.TableSettingsDropdown = TableSettingsDropdown;
213
222
  Table.TableControls = TableControls;
223
+
224
+ Table.Spacing = TABLE_SPACING;
225
+
226
+ export namespace Table {
227
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
228
+ export type Props<TData = any> = TableProps<TData>;
229
+ export type ControlsProps = TableControlsProps;
230
+ export type BulkAction = BulkActionType;
231
+ export type HideColumnsProps = HideColumnsDropdownProps;
232
+ export type Spacing = TABLE_SPACING;
233
+ }
@@ -0,0 +1,109 @@
1
+ import { describe, expect, test, vi } from 'vitest';
2
+ import { render, screen, within, fireEvent } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import '@testing-library/jest-dom/vitest';
5
+ import { DateCellEditor } from './DateCellEditor';
6
+ import type { CustomCellEditorProps } from 'ag-grid-react';
7
+
8
+ const createMockProps = (overrides: Partial<CustomCellEditorProps> = {}): CustomCellEditorProps => ({
9
+ value: undefined,
10
+ initialValue: undefined,
11
+ onValueChange: vi.fn(),
12
+ colDef: { cellEditorParams: {} },
13
+ ...overrides,
14
+ } as CustomCellEditorProps);
15
+
16
+ const getDateField = (container: HTMLElement) => (
17
+ container.querySelector('input[type="date"]') as HTMLInputElement
18
+ );
19
+
20
+ describe('DateCellEditor', () => {
21
+ test('renders a date picker', () => {
22
+ const { container } = render(<DateCellEditor {...createMockProps()} />);
23
+ expect(getDateField(container)).toBeInTheDocument();
24
+ expect(screen.getByRole('button', { name: 'Open date picker' })).toBeInTheDocument();
25
+ });
26
+
27
+ test('displays the provided value', () => {
28
+ const { container } = render(<DateCellEditor {...createMockProps({ value: new Date(2024, 5, 15) })} />);
29
+ expect(getDateField(container)).toHaveValue('2024-06-15');
30
+ });
31
+
32
+ test('displays empty when value is undefined', () => {
33
+ const { container } = render(<DateCellEditor {...createMockProps({ value: undefined })} />);
34
+ expect(getDateField(container)).toHaveValue('');
35
+ });
36
+
37
+ test('displays empty when value is null', () => {
38
+ const { container } = render(<DateCellEditor {...createMockProps({ value: null })} />);
39
+ expect(getDateField(container)).toHaveValue('');
40
+ });
41
+
42
+ test('calls onValueChange when a date is typed', () => {
43
+ const onValueChange = vi.fn();
44
+ const { container } = render(<DateCellEditor {...createMockProps({ onValueChange })} />);
45
+ onValueChange.mockClear();
46
+
47
+ fireEvent.change(getDateField(container), { target: { value: '2024-06-15' } });
48
+
49
+ expect(onValueChange).toHaveBeenCalledWith(expect.any(Date));
50
+ const lastDate: Date = onValueChange.mock.lastCall![0];
51
+ expect(lastDate.getFullYear()).toBe(2024);
52
+ expect(lastDate.getMonth()).toBe(5);
53
+ expect(lastDate.getDate()).toBe(15);
54
+ });
55
+
56
+ test('calls onValueChange when a date is picked from the calendar', async () => {
57
+ const onValueChange = vi.fn();
58
+ const { container } = render(<DateCellEditor {...createMockProps({ onValueChange })} />);
59
+
60
+ // Navigate to a known month via text input first
61
+ fireEvent.change(getDateField(container), { target: { value: '2024-06-01' } });
62
+ onValueChange.mockClear();
63
+
64
+ await userEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
65
+ await userEvent.click(within(screen.getByRole('application')).getByRole('button', { name: /June 20/ }));
66
+
67
+ expect(onValueChange).toHaveBeenCalledWith(expect.any(Date));
68
+ const lastDate: Date = onValueChange.mock.lastCall![0];
69
+ expect(lastDate.getFullYear()).toBe(2024);
70
+ expect(lastDate.getMonth()).toBe(5);
71
+ expect(lastDate.getDate()).toBe(20);
72
+ });
73
+
74
+ test('calls onValueChange with undefined when the date is cleared', () => {
75
+ const onValueChange = vi.fn();
76
+ const { container } = render(
77
+ <DateCellEditor {...createMockProps({ value: new Date(2024, 5, 15), onValueChange })} />,
78
+ );
79
+ onValueChange.mockClear();
80
+
81
+ fireEvent.change(getDateField(container), { target: { value: '' } });
82
+
83
+ expect(onValueChange).toHaveBeenCalledWith(undefined);
84
+ });
85
+
86
+ test('autofocuses the date input on mount', () => {
87
+ const { container } = render(<DateCellEditor {...createMockProps()} />);
88
+ expect(getDateField(container)).toHaveFocus();
89
+ });
90
+
91
+ test('autofocuses the date input even when a value is already set', () => {
92
+ const { container } = render(
93
+ <DateCellEditor {...createMockProps({ value: new Date(2024, 5, 15) })} />,
94
+ );
95
+ expect(getDateField(container)).toHaveFocus();
96
+ });
97
+
98
+ test('passes cellEditorParams through to DatePicker', () => {
99
+ render(
100
+ <DateCellEditor
101
+ {...createMockProps({
102
+ value: undefined,
103
+ colDef: { cellEditorParams: { placeholder: 'Pick a birthday' } },
104
+ })}
105
+ />,
106
+ );
107
+ expect(screen.getByText('Pick a birthday')).toBeInTheDocument();
108
+ });
109
+ });
@@ -0,0 +1,27 @@
1
+ import type { CustomCellEditorProps } from 'ag-grid-react';
2
+ import { DatePicker } from 'Components/datePicker/DatePicker';
3
+
4
+ export const DateCellEditor = (props: CustomCellEditorProps) => {
5
+ const {
6
+ value,
7
+ onValueChange,
8
+ colDef: {
9
+ cellEditorParams,
10
+ },
11
+ } = props;
12
+
13
+ return (
14
+ <DatePicker
15
+ value={value}
16
+ onChange={(newValue) => {
17
+ // intentionally not updating always, because AG Grid controls the component and
18
+ // ends up interfering with things, leading to lots of jank
19
+ if (newValue !== value) {
20
+ onValueChange(newValue);
21
+ }
22
+ }}
23
+ autoFocus
24
+ {...cellEditorParams}
25
+ />
26
+ );
27
+ };
@@ -56,3 +56,8 @@ export const Tag = ({
56
56
  </span>
57
57
  );
58
58
  };
59
+
60
+ export namespace Tag {
61
+ export type Color = TagColor;
62
+ export type Props = TagProps;
63
+ }