@arbor-education/design-system.components 0.12.0 → 0.13.1

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 (177) hide show
  1. package/.agent-memory/blanche-designspert/MEMORY.md +189 -0
  2. package/.agent-memory/dorothy-fact-checker/MEMORY.md +228 -0
  3. package/.agent-memory/dorothy-fact-checker/numberinput_component.md +53 -0
  4. package/.agent-memory/dorothy-fact-checker/progress_component.md +36 -0
  5. package/.agent-memory/rose-storybookspert/MEMORY.md +105 -0
  6. package/.agent-memory/sophia-componentspert/MEMORY.md +34 -0
  7. package/{.claude/agent-memory → .agent-memory}/sophia-componentspert/components.md +170 -17
  8. package/{.claude → .gather}/agents/blanche-designspert.md +7 -2
  9. package/{.claude → .gather}/agents/dorothy-fact-checker.md +7 -2
  10. package/{.claude → .gather}/agents/rose-storybookspert.md +80 -11
  11. package/{.claude → .gather}/agents/sophia-componentspert.md +9 -4
  12. package/.gather/gather.yaml +9 -0
  13. package/{CLAUDE.md → .gather/instructions/project-overview.md} +42 -9
  14. package/{.claude → .gather}/skills/analyze-design/README.md +5 -0
  15. package/{.claude → .gather}/skills/analyze-design/SKILL.md +1 -1
  16. package/.gather/skills/analyze-design/meta.md +4 -0
  17. package/{.claude → .gather}/skills/create-page/README.md +5 -0
  18. package/{.claude → .gather}/skills/create-page/design-analysis-template.md +5 -0
  19. package/.gather/skills/create-page/meta.md +4 -0
  20. package/{.claude → .gather}/skills/create-page/page-template.scss +5 -0
  21. package/{.claude → .gather}/skills/create-page/page-template.tsx +5 -0
  22. package/{.claude → .gather}/skills/map-legacy/README.md +5 -0
  23. package/.gather/skills/map-legacy/meta.md +4 -0
  24. package/{.claude → .gather}/skills/migrate-page/README.md +5 -0
  25. package/.gather/skills/migrate-page/meta.md +4 -0
  26. package/.gather/skills/write-stories/README.md +157 -0
  27. package/.gather/skills/write-stories/SKILL.md +841 -0
  28. package/.gather/skills/write-stories/meta.md +4 -0
  29. package/.ralph/storybook-upgrade/knowledge.md +308 -0
  30. package/.ralph/storybook-upgrade/prd.json +777 -0
  31. package/.ralph/storybook-upgrade/progress.md +342 -0
  32. package/.storybook/DocsTemplate.tsx +122 -0
  33. package/.storybook/preview.ts +40 -0
  34. package/.stylelintignore +2 -0
  35. package/CHANGELOG.md +12 -0
  36. package/{.claude/component-library.md → component-library.md} +27 -10
  37. package/dist/components/badge/Badge.stories.d.ts +85 -6
  38. package/dist/components/badge/Badge.stories.d.ts.map +1 -1
  39. package/dist/components/badge/Badge.stories.js +626 -27
  40. package/dist/components/badge/Badge.stories.js.map +1 -1
  41. package/dist/components/banner/Banner.stories.d.ts +129 -63
  42. package/dist/components/banner/Banner.stories.d.ts.map +1 -1
  43. package/dist/components/banner/Banner.stories.js +855 -39
  44. package/dist/components/banner/Banner.stories.js.map +1 -1
  45. package/dist/components/button/Button.stories.d.ts +148 -8
  46. package/dist/components/button/Button.stories.d.ts.map +1 -1
  47. package/dist/components/button/Button.stories.js +1089 -80
  48. package/dist/components/button/Button.stories.js.map +1 -1
  49. package/dist/components/datePicker/DatePicker.d.ts +1 -0
  50. package/dist/components/datePicker/DatePicker.d.ts.map +1 -1
  51. package/dist/components/datePicker/DatePicker.js +2 -2
  52. package/dist/components/datePicker/DatePicker.js.map +1 -1
  53. package/dist/components/datePicker/DatePicker.stories.d.ts +1 -0
  54. package/dist/components/datePicker/DatePicker.stories.d.ts.map +1 -1
  55. package/dist/components/dot/Dot.stories.d.ts +46 -11
  56. package/dist/components/dot/Dot.stories.d.ts.map +1 -1
  57. package/dist/components/dot/Dot.stories.js +504 -15
  58. package/dist/components/dot/Dot.stories.js.map +1 -1
  59. package/dist/components/dropdown/Dropdown.stories.d.ts +89 -14
  60. package/dist/components/dropdown/Dropdown.stories.d.ts.map +1 -1
  61. package/dist/components/dropdown/Dropdown.stories.js +769 -17
  62. package/dist/components/dropdown/Dropdown.stories.js.map +1 -1
  63. package/dist/components/formField/FormField.stories.d.ts +95 -35
  64. package/dist/components/formField/FormField.stories.d.ts.map +1 -1
  65. package/dist/components/formField/FormField.stories.js +1174 -69
  66. package/dist/components/formField/FormField.stories.js.map +1 -1
  67. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts +96 -9
  68. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts.map +1 -1
  69. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js +717 -10
  70. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js.map +1 -1
  71. package/dist/components/formField/inputs/number/NumberInput.stories.d.ts +149 -11
  72. package/dist/components/formField/inputs/number/NumberInput.stories.d.ts.map +1 -1
  73. package/dist/components/formField/inputs/number/NumberInput.stories.js +624 -10
  74. package/dist/components/formField/inputs/number/NumberInput.stories.js.map +1 -1
  75. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts +74 -1
  76. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
  77. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +673 -44
  78. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
  79. package/dist/components/formField/inputs/text/TextInput.stories.d.ts +119 -1
  80. package/dist/components/formField/inputs/text/TextInput.stories.d.ts.map +1 -1
  81. package/dist/components/formField/inputs/text/TextInput.stories.js +549 -10
  82. package/dist/components/formField/inputs/text/TextInput.stories.js.map +1 -1
  83. package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts +129 -4
  84. package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts.map +1 -1
  85. package/dist/components/formField/inputs/textArea/TextArea.stories.js +577 -3
  86. package/dist/components/formField/inputs/textArea/TextArea.stories.js.map +1 -1
  87. package/dist/components/heading/Heading.stories.d.ts +449 -50
  88. package/dist/components/heading/Heading.stories.d.ts.map +1 -1
  89. package/dist/components/heading/Heading.stories.js +536 -60
  90. package/dist/components/heading/Heading.stories.js.map +1 -1
  91. package/dist/components/icon/Icon.stories.d.ts +81 -10
  92. package/dist/components/icon/Icon.stories.d.ts.map +1 -1
  93. package/dist/components/icon/Icon.stories.js +979 -8
  94. package/dist/components/icon/Icon.stories.js.map +1 -1
  95. package/dist/components/pill/Pill.stories.d.ts +71 -19
  96. package/dist/components/pill/Pill.stories.d.ts.map +1 -1
  97. package/dist/components/pill/Pill.stories.js +573 -14
  98. package/dist/components/pill/Pill.stories.js.map +1 -1
  99. package/dist/components/progress/Progress.stories.d.ts +75 -298
  100. package/dist/components/progress/Progress.stories.d.ts.map +1 -1
  101. package/dist/components/progress/Progress.stories.js +449 -52
  102. package/dist/components/progress/Progress.stories.js.map +1 -1
  103. package/dist/components/separator/Separator.stories.d.ts +58 -5
  104. package/dist/components/separator/Separator.stories.d.ts.map +1 -1
  105. package/dist/components/separator/Separator.stories.js +443 -4
  106. package/dist/components/separator/Separator.stories.js.map +1 -1
  107. package/dist/components/table/Table.d.ts +7 -0
  108. package/dist/components/table/Table.d.ts.map +1 -1
  109. package/dist/components/table/Table.js +9 -0
  110. package/dist/components/table/Table.js.map +1 -1
  111. package/dist/components/table/Table.stories.d.ts +1 -0
  112. package/dist/components/table/Table.stories.d.ts.map +1 -1
  113. package/dist/components/table/Table.stories.js +87 -0
  114. package/dist/components/table/Table.stories.js.map +1 -1
  115. package/dist/components/table/Table.test.js +49 -1
  116. package/dist/components/table/Table.test.js.map +1 -1
  117. package/dist/components/table/cellEditors/DateCellEditor.d.ts +3 -0
  118. package/dist/components/table/cellEditors/DateCellEditor.d.ts.map +1 -0
  119. package/dist/components/table/cellEditors/DateCellEditor.js +13 -0
  120. package/dist/components/table/cellEditors/DateCellEditor.js.map +1 -0
  121. package/dist/components/table/cellEditors/DateCellEditor.test.d.ts +2 -0
  122. package/dist/components/table/cellEditors/DateCellEditor.test.d.ts.map +1 -0
  123. package/dist/components/table/cellEditors/DateCellEditor.test.js +81 -0
  124. package/dist/components/table/cellEditors/DateCellEditor.test.js.map +1 -0
  125. package/dist/components/tag/Tag.stories.d.ts +116 -5
  126. package/dist/components/tag/Tag.stories.d.ts.map +1 -1
  127. package/dist/components/tag/Tag.stories.js +581 -28
  128. package/dist/components/tag/Tag.stories.js.map +1 -1
  129. package/dist/index.css +8 -1
  130. package/dist/index.css.map +1 -1
  131. package/dist/index.d.ts +0 -5
  132. package/dist/index.d.ts.map +1 -1
  133. package/dist/index.js +0 -5
  134. package/dist/index.js.map +1 -1
  135. package/eslint.config.mts +5 -1
  136. package/package.json +3 -3
  137. package/src/components/badge/Badge.stories.tsx +869 -42
  138. package/src/components/banner/Banner.stories.tsx +1081 -63
  139. package/src/components/button/Button.stories.tsx +1394 -99
  140. package/src/components/datePicker/DatePicker.tsx +3 -0
  141. package/src/components/dot/Dot.stories.tsx +723 -32
  142. package/src/components/dropdown/Dropdown.stories.tsx +1174 -35
  143. package/src/components/formField/FormField.stories.tsx +1522 -105
  144. package/src/components/formField/inputs/checkbox/CheckboxInput.stories.tsx +1020 -15
  145. package/src/components/formField/inputs/number/NumberInput.stories.tsx +908 -15
  146. package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +932 -51
  147. package/src/components/formField/inputs/text/TextInput.stories.tsx +773 -13
  148. package/src/components/formField/inputs/textArea/TextArea.stories.tsx +756 -8
  149. package/src/components/heading/Heading.stories.tsx +752 -120
  150. package/src/components/icon/Icon.stories.tsx +1446 -12
  151. package/src/components/pill/Pill.stories.tsx +867 -21
  152. package/src/components/progress/Progress.stories.tsx +625 -58
  153. package/src/components/separator/Separator.stories.tsx +730 -8
  154. package/src/components/separator/separator.scss +12 -3
  155. package/src/components/table/Table.stories.tsx +102 -0
  156. package/src/components/table/Table.test.tsx +82 -3
  157. package/src/components/table/Table.tsx +9 -0
  158. package/src/components/table/cellEditors/DateCellEditor.test.tsx +109 -0
  159. package/src/components/table/cellEditors/DateCellEditor.tsx +27 -0
  160. package/src/components/tag/Tag.stories.tsx +755 -53
  161. package/src/index.ts +0 -5
  162. package/.claude/agent-memory/blanche-designspert/MEMORY.md +0 -64
  163. package/.claude/agent-memory/dorothy-fact-checker/MEMORY.md +0 -129
  164. package/.claude/agent-memory/rose-storybookspert/MEMORY.md +0 -29
  165. package/.claude/agent-memory/sophia-componentspert/MEMORY.md +0 -14
  166. package/.claude/design-assessment-daily-attendance-2026-04-10.md +0 -566
  167. package/.claude/figma-assessment-7154-58899.md +0 -404
  168. package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-11086-97537.md +0 -392
  169. package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-41974.md +0 -474
  170. package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-43094.md +0 -462
  171. package/.claude/figma-assessment-fcFK4CGzkz2fVyY3koX8ZE-7154-59061.md +0 -440
  172. package/.claude/migration-report-custom-report-writer-2026-02-19.md +0 -591
  173. /package/{.claude/agent-memory → .agent-memory}/blanche-designspert/token-review-patterns.md +0 -0
  174. /package/{.claude/agent-memory → .agent-memory}/rose-storybookspert/patterns.md +0 -0
  175. /package/{.claude → .gather}/skills/create-page/SKILL.md +0 -0
  176. /package/{.claude → .gather}/skills/map-legacy/SKILL.md +0 -0
  177. /package/{.claude → .gather}/skills/migrate-page/SKILL.md +0 -0
@@ -1,6 +1,15 @@
1
1
  .ds-separator {
2
- width: 100%;
3
- height: 1px;
4
2
  background-color: var(--page-base-color-border);
5
- margin: var(--spacing-small) 0;
3
+
4
+ &[data-orientation='horizontal'] {
5
+ width: 100%;
6
+ height: 1px;
7
+ margin: var(--spacing-small) 0;
8
+ }
9
+
10
+ &[data-orientation='vertical'] {
11
+ width: 1px;
12
+ height: 100%;
13
+ margin: 0 var(--spacing-small);
14
+ }
6
15
  }
@@ -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,5 +1,5 @@
1
1
  import { describe, expect, expectTypeOf, test, vi } from 'vitest';
2
- import { render, screen, waitFor } from '@testing-library/react';
2
+ import { render, screen, waitFor, fireEvent } from '@testing-library/react';
3
3
  import { Table } from './Table';
4
4
  import '@testing-library/jest-dom/vitest';
5
5
  import { BulkActionsDropdown } from 'Components/table/tableControls/BulkActionsDropdown';
@@ -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
  />
@@ -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,6 +23,7 @@ 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
28
  import { TableControls, type TableControlsProps, type BulkAction as BulkActionType } from './tableControls/TableControls';
28
29
  import type { HideColumnsDropdownProps } from './tableControls/HideColumnsDropdown';
@@ -30,6 +31,7 @@ import { TABLE_SPACING } from './tableConsts';
30
31
  import { TableSettingsContext } from './TableSettingsContext';
31
32
  import { BooleanCellRenderer } from './cellRenderers/BooleanCellRenderer';
32
33
  import { CheckboxCellRenderer } from './cellRenderers/CheckboxCellRenderer';
34
+ import { DefaultCellRenderer } from './cellRenderers/DefaultCellRenderer';
33
35
 
34
36
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
37
  type TableProps<TData = any> = {
@@ -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,12 @@ 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;
@@ -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
+ };