@arbor-education/design-system.components 0.9.0 → 0.11.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 (168) hide show
  1. package/.claude/agent-memory/blanche-designspert/MEMORY.md +64 -0
  2. package/.claude/agent-memory/blanche-designspert/token-review-patterns.md +29 -0
  3. package/.claude/agent-memory/dorothy-fact-checker/MEMORY.md +129 -0
  4. package/.claude/agent-memory/rose-storybookspert/MEMORY.md +29 -0
  5. package/.claude/agent-memory/rose-storybookspert/patterns.md +132 -0
  6. package/.claude/agent-memory/sophia-componentspert/MEMORY.md +14 -0
  7. package/.claude/agent-memory/sophia-componentspert/components.md +367 -0
  8. package/.claude/agents/blanche-designspert.md +150 -0
  9. package/.claude/agents/dorothy-fact-checker.md +145 -0
  10. package/.claude/agents/rose-storybookspert.md +148 -0
  11. package/.claude/agents/sophia-componentspert.md +133 -0
  12. package/.claude/component-library.md +1107 -0
  13. package/.claude/design-assessment-daily-attendance-2026-04-10.md +566 -0
  14. package/.claude/figma-assessment-7154-58899.md +404 -0
  15. package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-11086-97537.md +392 -0
  16. package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-41974.md +474 -0
  17. package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-43094.md +462 -0
  18. package/.claude/figma-assessment-fcFK4CGzkz2fVyY3koX8ZE-7154-59061.md +440 -0
  19. package/.claude/migration-report-custom-report-writer-2026-02-19.md +591 -0
  20. package/.claude/skills/analyze-design/README.md +295 -0
  21. package/.claude/skills/analyze-design/SKILL.md +741 -0
  22. package/.claude/skills/create-page/README.md +246 -0
  23. package/.claude/skills/create-page/SKILL.md +634 -0
  24. package/.claude/skills/create-page/design-analysis-template.md +333 -0
  25. package/.claude/skills/create-page/page-template.scss +118 -0
  26. package/.claude/skills/create-page/page-template.tsx +230 -0
  27. package/.claude/skills/map-legacy/README.md +87 -0
  28. package/.claude/skills/map-legacy/SKILL.md +465 -0
  29. package/.claude/skills/migrate-page/README.md +125 -0
  30. package/.claude/skills/migrate-page/SKILL.md +374 -0
  31. package/.github/CODEOWNERS +1 -0
  32. package/.github/pull_request_template.md +39 -0
  33. package/.github/workflows/release.yml +1 -1
  34. package/CHANGELOG.md +16 -0
  35. package/CLAUDE.md +31 -0
  36. package/CONTRIBUTING.md +191 -0
  37. package/README.md +110 -20
  38. package/dist/components/button/Button.d.ts.map +1 -1
  39. package/dist/components/button/Button.js +2 -2
  40. package/dist/components/button/Button.js.map +1 -1
  41. package/dist/components/combobox/Combobox.d.ts.map +1 -1
  42. package/dist/components/combobox/Combobox.js +2 -1
  43. package/dist/components/combobox/Combobox.js.map +1 -1
  44. package/dist/components/combobox/Combobox.test.js +98 -61
  45. package/dist/components/combobox/Combobox.test.js.map +1 -1
  46. package/dist/components/combobox/useComboboxPopoverBehavior.d.ts +3 -1
  47. package/dist/components/combobox/useComboboxPopoverBehavior.d.ts.map +1 -1
  48. package/dist/components/combobox/useComboboxPopoverBehavior.js +7 -6
  49. package/dist/components/combobox/useComboboxPopoverBehavior.js.map +1 -1
  50. package/dist/components/combobox/useComboboxState.d.ts.map +1 -1
  51. package/dist/components/combobox/useComboboxState.js +4 -1
  52. package/dist/components/combobox/useComboboxState.js.map +1 -1
  53. package/dist/components/datePicker/DatePicker.d.ts +4 -1
  54. package/dist/components/datePicker/DatePicker.d.ts.map +1 -1
  55. package/dist/components/datePicker/DatePicker.js +77 -37
  56. package/dist/components/datePicker/DatePicker.js.map +1 -1
  57. package/dist/components/datePicker/DatePicker.stories.d.ts +28 -3
  58. package/dist/components/datePicker/DatePicker.stories.d.ts.map +1 -1
  59. package/dist/components/datePicker/DatePicker.stories.js +62 -9
  60. package/dist/components/datePicker/DatePicker.stories.js.map +1 -1
  61. package/dist/components/datePicker/DatePicker.test.js +133 -66
  62. package/dist/components/datePicker/DatePicker.test.js.map +1 -1
  63. package/dist/components/datePicker/DatePickerCalendarHeader.d.ts +8 -0
  64. package/dist/components/datePicker/DatePickerCalendarHeader.d.ts.map +1 -0
  65. package/dist/components/datePicker/DatePickerCalendarHeader.js +36 -0
  66. package/dist/components/datePicker/DatePickerCalendarHeader.js.map +1 -0
  67. package/dist/components/datePicker/dateInputUtils.d.ts +25 -0
  68. package/dist/components/datePicker/dateInputUtils.d.ts.map +1 -0
  69. package/dist/components/datePicker/dateInputUtils.js +60 -0
  70. package/dist/components/datePicker/dateInputUtils.js.map +1 -0
  71. package/dist/components/datePicker/datePickerTestUtils.test-helpers.d.ts +2 -0
  72. package/dist/components/datePicker/datePickerTestUtils.test-helpers.d.ts.map +1 -0
  73. package/dist/components/datePicker/datePickerTestUtils.test-helpers.js +4 -0
  74. package/dist/components/datePicker/datePickerTestUtils.test-helpers.js.map +1 -0
  75. package/dist/components/dateTimePicker/DateTimePicker.d.ts +22 -0
  76. package/dist/components/dateTimePicker/DateTimePicker.d.ts.map +1 -0
  77. package/dist/components/dateTimePicker/DateTimePicker.js +132 -0
  78. package/dist/components/dateTimePicker/DateTimePicker.js.map +1 -0
  79. package/dist/components/dateTimePicker/DateTimePicker.stories.d.ts +77 -0
  80. package/dist/components/dateTimePicker/DateTimePicker.stories.d.ts.map +1 -0
  81. package/dist/components/dateTimePicker/DateTimePicker.stories.js +163 -0
  82. package/dist/components/dateTimePicker/DateTimePicker.stories.js.map +1 -0
  83. package/dist/components/dateTimePicker/DateTimePicker.test.d.ts +2 -0
  84. package/dist/components/dateTimePicker/DateTimePicker.test.d.ts.map +1 -0
  85. package/dist/components/dateTimePicker/DateTimePicker.test.js +235 -0
  86. package/dist/components/dateTimePicker/DateTimePicker.test.js.map +1 -0
  87. package/dist/components/formField/FormField.test.d.ts.map +1 -1
  88. package/dist/components/formField/FormField.test.js +5 -5
  89. package/dist/components/formField/FormField.test.js.map +1 -1
  90. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts +1 -0
  91. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts.map +1 -1
  92. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js +7 -3
  93. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -1
  94. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.js +12 -0
  95. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.js.map +1 -1
  96. package/dist/components/formField/inputs/text/TextInput.d.ts +4 -1
  97. package/dist/components/formField/inputs/text/TextInput.d.ts.map +1 -1
  98. package/dist/components/formField/inputs/text/TextInput.js +5 -4
  99. package/dist/components/formField/inputs/text/TextInput.js.map +1 -1
  100. package/dist/components/formField/inputs/text/TextInput.stories.d.ts +4 -1
  101. package/dist/components/formField/inputs/text/TextInput.stories.d.ts.map +1 -1
  102. package/dist/components/table/DSDefaultColDef.js +2 -2
  103. package/dist/components/table/DSDefaultColDef.js.map +1 -1
  104. package/dist/components/table/Table.d.ts.map +1 -1
  105. package/dist/components/table/Table.js +4 -0
  106. package/dist/components/table/Table.js.map +1 -1
  107. package/dist/components/table/Table.stories.d.ts +2 -0
  108. package/dist/components/table/Table.stories.d.ts.map +1 -1
  109. package/dist/components/table/Table.stories.js +132 -3
  110. package/dist/components/table/Table.stories.js.map +1 -1
  111. package/dist/components/table/Table.test.js +106 -5
  112. package/dist/components/table/Table.test.js.map +1 -1
  113. package/dist/components/table/cellRenderers/BooleanCellRenderer.d.ts +3 -0
  114. package/dist/components/table/cellRenderers/BooleanCellRenderer.d.ts.map +1 -0
  115. package/dist/components/table/cellRenderers/BooleanCellRenderer.js +15 -0
  116. package/dist/components/table/cellRenderers/BooleanCellRenderer.js.map +1 -0
  117. package/dist/components/table/cellRenderers/BooleanCellRenderer.test.d.ts +2 -0
  118. package/dist/components/table/cellRenderers/BooleanCellRenderer.test.d.ts.map +1 -0
  119. package/dist/components/table/cellRenderers/BooleanCellRenderer.test.js +31 -0
  120. package/dist/components/table/cellRenderers/BooleanCellRenderer.test.js.map +1 -0
  121. package/dist/components/table/cellRenderers/CheckboxCellRenderer.d.ts +3 -0
  122. package/dist/components/table/cellRenderers/CheckboxCellRenderer.d.ts.map +1 -0
  123. package/dist/components/table/cellRenderers/CheckboxCellRenderer.js +12 -0
  124. package/dist/components/table/cellRenderers/CheckboxCellRenderer.js.map +1 -0
  125. package/dist/components/table/cellRenderers/CheckboxCellRenderer.test.d.ts +2 -0
  126. package/dist/components/table/cellRenderers/CheckboxCellRenderer.test.d.ts.map +1 -0
  127. package/dist/components/table/cellRenderers/CheckboxCellRenderer.test.js +65 -0
  128. package/dist/components/table/cellRenderers/CheckboxCellRenderer.test.js.map +1 -0
  129. package/dist/index.css +259 -4
  130. package/dist/index.css.map +1 -1
  131. package/dist/index.d.ts +4 -0
  132. package/dist/index.d.ts.map +1 -1
  133. package/dist/index.js +3 -0
  134. package/dist/index.js.map +1 -1
  135. package/package.json +1 -1
  136. package/src/components/button/Button.tsx +2 -1
  137. package/src/components/combobox/Combobox.test.tsx +104 -61
  138. package/src/components/combobox/Combobox.tsx +3 -1
  139. package/src/components/combobox/useComboboxPopoverBehavior.ts +10 -5
  140. package/src/components/combobox/useComboboxState.ts +4 -1
  141. package/src/components/datePicker/DatePicker.stories.tsx +67 -9
  142. package/src/components/datePicker/DatePicker.test.tsx +157 -72
  143. package/src/components/datePicker/DatePicker.tsx +163 -69
  144. package/src/components/datePicker/DatePickerCalendarHeader.tsx +82 -0
  145. package/src/components/datePicker/date-field-hint.scss +152 -0
  146. package/src/components/datePicker/dateInputUtils.ts +117 -0
  147. package/src/components/datePicker/datePicker.scss +53 -29
  148. package/src/components/datePicker/datePickerTestUtils.test-helpers.ts +6 -0
  149. package/src/components/dateTimePicker/DateTimePicker.stories.tsx +202 -0
  150. package/src/components/dateTimePicker/DateTimePicker.test.tsx +295 -0
  151. package/src/components/dateTimePicker/DateTimePicker.tsx +293 -0
  152. package/src/components/dateTimePicker/dateTimePicker.scss +17 -0
  153. package/src/components/formField/FormField.test.tsx +5 -5
  154. package/src/components/formField/inputs/selectDropdown/SelectDropdown.test.tsx +28 -0
  155. package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +8 -2
  156. package/src/components/formField/inputs/text/TextInput.tsx +6 -3
  157. package/src/components/table/DSDefaultColDef.ts +2 -2
  158. package/src/components/table/Table.stories.tsx +147 -3
  159. package/src/components/table/Table.test.tsx +131 -5
  160. package/src/components/table/Table.tsx +4 -0
  161. package/src/components/table/cellRenderers/BooleanCellRenderer.test.tsx +37 -0
  162. package/src/components/table/cellRenderers/BooleanCellRenderer.tsx +34 -0
  163. package/src/components/table/cellRenderers/CheckboxCellRenderer.test.tsx +74 -0
  164. package/src/components/table/cellRenderers/CheckboxCellRenderer.tsx +28 -0
  165. package/src/components/table/cellRenderers/booleanCellRenderer.scss +7 -0
  166. package/src/components/table/table.scss +1 -1
  167. package/src/index.scss +2 -0
  168. package/src/index.ts +4 -0
@@ -16,6 +16,7 @@ export type SelectDropdownInputProps = {
16
16
  'id'?: string;
17
17
  'alwaysShowPlaceholder'?: boolean;
18
18
  'initialSelectedValues'?: string[];
19
+ 'selectedValues'?: string[];
19
20
  'open'?: boolean;
20
21
  'onOpenChange'?: (open: boolean) => void;
21
22
  };
@@ -33,11 +34,14 @@ export const SelectDropdown = (props: SelectDropdownInputProps) => {
33
34
  'aria-invalid': ariaInvalid,
34
35
  alwaysShowPlaceholder = false,
35
36
  initialSelectedValues = [],
37
+ selectedValues: controlledSelectedValues,
36
38
  open,
37
39
  onOpenChange,
38
40
  } = props;
39
41
 
40
- const [selectedValues, setSelectedValues] = useState<string[]>(initialSelectedValues);
42
+ const isControlled = controlledSelectedValues !== undefined;
43
+ const [internalSelectedValues, setInternalSelectedValues] = useState<string[]>(initialSelectedValues);
44
+ const selectedValues = isControlled ? controlledSelectedValues : internalSelectedValues;
41
45
  const [renderedSelectContent, setRenderedSelectContent] = useState('');
42
46
  const selectedValuesRef = useRef(selectedValues);
43
47
  selectedValuesRef.current = selectedValues;
@@ -90,7 +94,9 @@ export const SelectDropdown = (props: SelectDropdownInputProps) => {
90
94
  else {
91
95
  nextValues = [...prev, value];
92
96
  }
93
- setSelectedValues(nextValues);
97
+ if (!isControlled) {
98
+ setInternalSelectedValues(nextValues);
99
+ }
94
100
  onSelectionChange?.(nextValues);
95
101
  };
96
102
 
@@ -1,12 +1,12 @@
1
1
  import classNames from 'classnames';
2
- import { type InputHTMLAttributes } from 'react';
2
+ import { forwardRef, type InputHTMLAttributes } from 'react';
3
3
 
4
4
  export type TextInputProps = {
5
5
  size?: 'M' | 'S';
6
6
  hasError?: boolean;
7
7
  } & Omit<InputHTMLAttributes<HTMLInputElement>, 'size'>;
8
8
 
9
- export const TextInput = (props: TextInputProps) => {
9
+ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>((props, ref) => {
10
10
  const {
11
11
  size = 'M',
12
12
  hasError,
@@ -27,9 +27,12 @@ export const TextInput = (props: TextInputProps) => {
27
27
 
28
28
  return (
29
29
  <input
30
+ ref={ref}
30
31
  className={inputClasses}
31
32
  disabled={disabled}
32
33
  {...rest}
33
34
  />
34
35
  );
35
- };
36
+ });
37
+
38
+ TextInput.displayName = 'TextInput';
@@ -19,7 +19,7 @@ export const shouldSuppressFocus = (params: SuppressMouseEventHandlingParams | C
19
19
  if (typeof params.column !== 'object') {
20
20
  return false;
21
21
  }
22
- return params.column?.getColDef().cellRendererParams.supressCellFocusAndFocusFirstElement ?? false;
22
+ return params.column?.getColDef().cellRendererParams.suppressCellFocusAndFocusFirstElement ?? false;
23
23
  };
24
24
 
25
25
  export const DSDefaultColDef: ColDef = {
@@ -54,7 +54,7 @@ export const DSDefaultColDef: ColDef = {
54
54
  useFormatter: true,
55
55
  },
56
56
  cellClassRules: {
57
- 'ds-table__cell--supress-focus': shouldSuppressFocus,
57
+ 'ds-table__cell--suppress-focus': shouldSuppressFocus,
58
58
  },
59
59
  cellRendererParams: {
60
60
  suppressMouseEventHandling: (params: SuppressMouseEventHandlingParams) => {
@@ -490,7 +490,7 @@ const sampleColumnDefsWithButtonCellRenderer: (ColDef | ColGroupDef)[] = [
490
490
  headerTooltip: 'The name of the user',
491
491
  cellRenderer: 'dsButtonCellRenderer',
492
492
  cellRendererParams: {
493
- supressCellFocusAndFocusFirstElement: true,
493
+ suppressCellFocusAndFocusFirstElement: true,
494
494
  },
495
495
  editable: false,
496
496
  },
@@ -818,7 +818,7 @@ export const WithInlineTextCellRenderer: Story = {
818
818
  field: 'name',
819
819
  cellRenderer: 'dsInlineTextCellRenderer',
820
820
  cellRendererParams: {
821
- supressCellFocusAndFocusFirstElement: true,
821
+ suppressCellFocusAndFocusFirstElement: true,
822
822
  suppressCount: true,
823
823
  suppressDoubleClickExpand: true,
824
824
  suppressEnterExpand: true,
@@ -1171,7 +1171,7 @@ export const TidyTable: Story = {
1171
1171
  editable: false,
1172
1172
  cellRenderer: 'agGroupCellRenderer',
1173
1173
  cellRendererParams: {
1174
- supressCellFocusAndFocusFirstElement: true,
1174
+ suppressCellFocusAndFocusFirstElement: true,
1175
1175
  suppressCount: true,
1176
1176
  suppressDoubleClickExpand: true,
1177
1177
  suppressEnterExpand: true,
@@ -1361,4 +1361,148 @@ export const TableWithSemanticColors: Story = {
1361
1361
  },
1362
1362
  };
1363
1363
 
1364
+ interface BooleanRowData {
1365
+ id: number;
1366
+ name: string;
1367
+ hasPet: boolean | null | undefined;
1368
+ isEnrolled: boolean | null | undefined;
1369
+ }
1370
+
1371
+ const booleanCellRendererData: BooleanRowData[] = [
1372
+ { id: 1, name: 'Alice Johnson', hasPet: true, isEnrolled: true },
1373
+ { id: 2, name: 'Bob Smith', hasPet: false, isEnrolled: false },
1374
+ { id: 3, name: 'Charlie Brown', hasPet: null, isEnrolled: undefined },
1375
+ { id: 4, name: 'Diana Prince', hasPet: true, isEnrolled: null },
1376
+ { id: 5, name: 'Ethan Hunt', hasPet: undefined, isEnrolled: true },
1377
+ ];
1378
+
1379
+ const booleanCellRendererColumnDefs: ColDef<BooleanRowData>[] = [
1380
+ { field: 'name', headerName: 'Name' },
1381
+ {
1382
+ field: 'hasPet',
1383
+ headerName: 'Has Pet',
1384
+ cellRenderer: 'dsBooleanCellRenderer',
1385
+ editable: false,
1386
+ },
1387
+ {
1388
+ field: 'isEnrolled',
1389
+ headerName: 'Is Enrolled',
1390
+ cellRenderer: 'dsBooleanCellRenderer',
1391
+ editable: false,
1392
+ },
1393
+ ];
1394
+
1395
+ export const WithBooleanCellRenderer: Story = {
1396
+ parameters: {
1397
+ docs: {
1398
+ description: {
1399
+ story:
1400
+ 'The BooleanCellRenderer displays a true or false icon for boolean values, and renders nothing for any other value regardless of truthiness. Only explicit true and false are accepted, anything else is treated as nullish',
1401
+ },
1402
+ },
1403
+ },
1404
+ args: {
1405
+ rowData: booleanCellRendererData,
1406
+ columnDefs: booleanCellRendererColumnDefs,
1407
+ defaultColDef,
1408
+ domLayout: 'autoHeight',
1409
+ },
1410
+ };
1411
+
1412
+ const checkboxRowData = [
1413
+ {
1414
+ id: 1,
1415
+ name: { value: 'Alice Johnson' },
1416
+ email: { value: 'alice.johnson@example.com' },
1417
+ role: { value: 'Developer' },
1418
+ attended: true,
1419
+ consented: false,
1420
+ },
1421
+ {
1422
+ id: 2,
1423
+ name: { value: 'Bob Smith' },
1424
+ email: { value: 'bob.smith@example.com' },
1425
+ role: { value: 'Designer' },
1426
+ attended: false,
1427
+ consented: true,
1428
+ },
1429
+ {
1430
+ id: 3,
1431
+ name: { value: 'Charlie Brown' },
1432
+ email: { value: 'charlie.brown@example.com' },
1433
+ role: { value: 'Manager' },
1434
+ attended: true,
1435
+ consented: true,
1436
+ },
1437
+ {
1438
+ id: 4,
1439
+ name: { value: 'Diana Prince' },
1440
+ email: { value: 'diana.prince@example.com' },
1441
+ role: { value: 'Developer' },
1442
+ attended: false,
1443
+ consented: false,
1444
+ },
1445
+ {
1446
+ id: 5,
1447
+ name: { value: 'Ethan Hunt' },
1448
+ email: { value: 'ethan.hunt@example.com' },
1449
+ role: { value: 'Analyst' },
1450
+ attended: true,
1451
+ consented: false,
1452
+ },
1453
+ ];
1454
+
1455
+ const checkboxColumnDefs: ColDef[] = [
1456
+ {
1457
+ field: 'name',
1458
+ headerName: 'Name',
1459
+ valueFormatter: Table.DefaultValueFormatter,
1460
+ },
1461
+ {
1462
+ field: 'email',
1463
+ headerName: 'Email',
1464
+ valueFormatter: Table.DefaultValueFormatter,
1465
+ },
1466
+ {
1467
+ field: 'role',
1468
+ headerName: 'Role',
1469
+ valueFormatter: Table.DefaultValueFormatter,
1470
+ },
1471
+ {
1472
+ field: 'attended',
1473
+ headerName: 'Attended',
1474
+ cellRenderer: 'dsCheckboxCellRenderer',
1475
+ cellRendererParams: {
1476
+ suppressCellFocusAndFocusFirstElement: true,
1477
+ },
1478
+ editable: false,
1479
+ },
1480
+ {
1481
+ field: 'consented',
1482
+ headerName: 'Consented',
1483
+ cellRenderer: 'dsCheckboxCellRenderer',
1484
+ cellRendererParams: {
1485
+ suppressCellFocusAndFocusFirstElement: true,
1486
+ },
1487
+ editable: false,
1488
+ },
1489
+ ];
1490
+
1491
+ export const WithCheckboxCellRenderer: Story = {
1492
+ parameters: {
1493
+ docs: {
1494
+ description: {
1495
+ story:
1496
+ 'The CheckboxCellRenderer renders a checkbox input inside a table cell. Pass a boolean value to the cell data to control the checked state.',
1497
+ },
1498
+ },
1499
+ },
1500
+ args: {
1501
+ rowData: checkboxRowData,
1502
+ columnDefs: checkboxColumnDefs,
1503
+ defaultColDef,
1504
+ domLayout: 'autoHeight',
1505
+ },
1506
+ };
1507
+
1364
1508
  export default meta;
@@ -1487,14 +1487,140 @@ describe('Table', () => {
1487
1487
  });
1488
1488
  });
1489
1489
 
1490
- describe('supressCellFocusAndFocusFirstElement', () => {
1490
+ describe('CheckboxCellRenderer', () => {
1491
+ test('renders checkboxes in table cells', async () => {
1492
+ const columnDefs = [
1493
+ { field: 'name', headerName: 'Name' },
1494
+ {
1495
+ field: 'attended',
1496
+ headerName: 'Attended',
1497
+ cellRenderer: 'dsCheckboxCellRenderer',
1498
+ editable: false,
1499
+ },
1500
+ ];
1501
+ const rowData = [
1502
+ { name: 'Alice', attended: true },
1503
+ { name: 'Bob', attended: false },
1504
+ ];
1505
+
1506
+ render(<Table columnDefs={columnDefs} rowData={rowData} />);
1507
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1508
+ await waitFor(() => {
1509
+ const checkboxes = screen.getAllByRole('checkbox');
1510
+ expect(checkboxes.length).toBe(2);
1511
+ });
1512
+ });
1513
+
1514
+ test('checked state reflects row data value', async () => {
1515
+ const columnDefs = [{
1516
+ field: 'active',
1517
+ headerName: 'Active',
1518
+ cellRenderer: 'dsCheckboxCellRenderer',
1519
+ editable: false,
1520
+ }];
1521
+ const rowData = [
1522
+ { active: true },
1523
+ { active: false },
1524
+ ];
1525
+
1526
+ render(<Table columnDefs={columnDefs} rowData={rowData} />);
1527
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1528
+ await waitFor(() => {
1529
+ const checkboxes = screen.getAllByRole('checkbox');
1530
+ expect(checkboxes[0]).toBeChecked();
1531
+ expect(checkboxes[1]).not.toBeChecked();
1532
+ });
1533
+ });
1534
+
1535
+ test('clicking checkbox triggers onCellValueChanged', async () => {
1536
+ const onCellValueChanged = vi.fn();
1537
+ const columnDefs = [{
1538
+ field: 'active',
1539
+ headerName: 'Active',
1540
+ cellRenderer: 'dsCheckboxCellRenderer',
1541
+ editable: false,
1542
+ }];
1543
+ const rowData = [{ active: false }];
1544
+
1545
+ render(
1546
+ <Table
1547
+ columnDefs={columnDefs}
1548
+ rowData={rowData}
1549
+ onCellValueChanged={onCellValueChanged}
1550
+ />,
1551
+ );
1552
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1553
+ await waitFor(() => expect(screen.getByRole('checkbox')).toBeInTheDocument());
1554
+
1555
+ await userEvent.click(screen.getByRole('checkbox'));
1556
+
1557
+ await waitFor(() => {
1558
+ expect(onCellValueChanged).toHaveBeenCalledWith(expect.objectContaining({ oldValue: false, newValue: true }));
1559
+ });
1560
+ });
1561
+
1562
+ test('works with multiple checkbox columns', async () => {
1563
+ const columnDefs = [
1564
+ { field: 'name', headerName: 'Name' },
1565
+ {
1566
+ field: 'attended',
1567
+ headerName: 'Attended',
1568
+ cellRenderer: 'dsCheckboxCellRenderer',
1569
+ editable: false,
1570
+ },
1571
+ {
1572
+ field: 'consented',
1573
+ headerName: 'Consented',
1574
+ cellRenderer: 'dsCheckboxCellRenderer',
1575
+ editable: false,
1576
+ },
1577
+ ];
1578
+ const rowData = [
1579
+ { name: 'Alice', attended: true, consented: false },
1580
+ ];
1581
+
1582
+ render(<Table columnDefs={columnDefs} rowData={rowData} />);
1583
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1584
+ await waitFor(() => {
1585
+ const checkboxes = screen.getAllByRole('checkbox');
1586
+ expect(checkboxes[0]).toBeChecked();
1587
+ expect(checkboxes[1]).not.toBeChecked();
1588
+ });
1589
+ });
1590
+
1591
+ test('disabled flag via cellRendererParams', async () => {
1592
+ const onCellValueChanged = vi.fn();
1593
+ const columnDefs = [{
1594
+ field: 'active',
1595
+ headerName: 'Active',
1596
+ cellRenderer: 'dsCheckboxCellRenderer',
1597
+ editable: false,
1598
+ cellRendererParams: { disabled: true },
1599
+ }];
1600
+ const rowData = [{ active: false }];
1601
+
1602
+ render(
1603
+ <Table
1604
+ columnDefs={columnDefs}
1605
+ rowData={rowData}
1606
+ onCellValueChanged={onCellValueChanged}
1607
+ />,
1608
+ );
1609
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1610
+ await waitFor(() => expect(screen.getByRole('checkbox')).toBeInTheDocument());
1611
+
1612
+ expect(screen.getByRole('checkbox')).toBeDisabled();
1613
+ });
1614
+ });
1615
+
1616
+ describe('suppressCellFocusAndFocusFirstElement', () => {
1491
1617
  const handleClick = vi.fn();
1492
1618
  const columnDefs = [{
1493
1619
  field: 'action',
1494
1620
  headerName: 'Action',
1495
1621
  cellRenderer: 'dsButtonCellRenderer',
1496
1622
  cellRendererParams: {
1497
- supressCellFocusAndFocusFirstElement: true,
1623
+ suppressCellFocusAndFocusFirstElement: true,
1498
1624
  },
1499
1625
  }];
1500
1626
  const rowData = [{
@@ -1504,14 +1630,14 @@ describe('Table', () => {
1504
1630
  },
1505
1631
  }];
1506
1632
 
1507
- test('setting supressCellFocusAndFocusFirstElement to true should add the ds-table__cell--supress-focus class', async () => {
1633
+ test('setting suppressCellFocusAndFocusFirstElement to true should add the ds-table__cell--suppress-focus class', async () => {
1508
1634
  const { container } = render(<Table columnDefs={columnDefs} rowData={rowData} />);
1509
1635
  await screen.findByRole('grid', {}, { timeout: 10000 });
1510
- const cellWithClass = container.querySelector('.ds-table__cell--supress-focus');
1636
+ const cellWithClass = container.querySelector('.ds-table__cell--suppress-focus');
1511
1637
  expect(cellWithClass).toBeInTheDocument();
1512
1638
  });
1513
1639
 
1514
- test('setting supressCellFocusAndFocusFirstElement to true should suppress cell focus and focus the first element', async () => {
1640
+ test('setting suppressCellFocusAndFocusFirstElement to true should suppress cell focus and focus the first element', async () => {
1515
1641
  const focusFirstFocusableElementSpy = vi.spyOn(
1516
1642
  focusFirstFocusableElementModule,
1517
1643
  'focusFirstFocusableElement',
@@ -24,6 +24,8 @@ import { focusFirstFocusableElement } from 'Utils/focusFirstFocusableElement';
24
24
  import { BooleanFilter } from './columnFilters/BooleanFilter/BooleanFilter';
25
25
  import { TimeFilter } from './columnFilters/TimeFilter/TimeFilter';
26
26
  import { TableSettingsDropdown } from './TableSettingsDropdown';
27
+ import { CheckboxCellRenderer } from './cellRenderers/CheckboxCellRenderer';
28
+ import { BooleanCellRenderer } from './cellRenderers/BooleanCellRenderer';
27
29
 
28
30
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
31
  type TableProps<TData = any> = {
@@ -194,6 +196,8 @@ export const Table = (props: TableProps) => {
194
196
  dsSelectDropdownCellRenderer: SelectDropdownCellRenderer,
195
197
  dsBooleanFilter: BooleanFilter,
196
198
  dsTimeFilter: TimeFilter,
199
+ dsCheckboxCellRenderer: CheckboxCellRenderer,
200
+ dsBooleanCellRenderer: BooleanCellRenderer,
197
201
  ...components,
198
202
  }}
199
203
  {...rest}
@@ -0,0 +1,37 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+ import { describe, expect, test } from 'vitest';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { BooleanCellRenderer } from './BooleanCellRenderer';
5
+ import type { CustomCellRendererProps } from 'ag-grid-react';
6
+
7
+ const renderWithValue = (value: unknown) =>
8
+ render(<BooleanCellRenderer {...{ value } as CustomCellRendererProps} />);
9
+
10
+ describe('BooleanCellRenderer', () => {
11
+ test('renders a check icon when value is true', () => {
12
+ renderWithValue(true);
13
+ expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument();
14
+ expect(screen.getByText('true')).toBeInTheDocument();
15
+ });
16
+
17
+ test('renders an x icon when value is false', () => {
18
+ renderWithValue(false);
19
+ expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument();
20
+ expect(screen.getByText('false')).toBeInTheDocument();
21
+ });
22
+
23
+ test('renders nothing when value is null', () => {
24
+ const { container } = renderWithValue(null);
25
+ expect(container.innerHTML).toBeFalsy();
26
+ });
27
+
28
+ test('renders nothing when value is undefined', () => {
29
+ const { container } = renderWithValue(undefined);
30
+ expect(container.innerHTML).toBeFalsy();
31
+ });
32
+
33
+ test('renders nothing when value is a non-boolean', () => {
34
+ const { container } = renderWithValue('some string');
35
+ expect(container.innerHTML).toBeFalsy();
36
+ });
37
+ });
@@ -0,0 +1,34 @@
1
+ import type { CustomCellRendererProps } from 'ag-grid-react';
2
+ import { Icon } from 'Components/icon/Icon';
3
+
4
+ export const BooleanCellRenderer = (props: CustomCellRendererProps) => {
5
+ const { value } = props;
6
+
7
+ if (value === true) {
8
+ return (
9
+ <div className="ds-boolean-cell-renderer">
10
+ <Icon
11
+ size={24}
12
+ name="check-solid"
13
+ color="var(--color-semantic-success-700)"
14
+ screenReaderText="true"
15
+ />
16
+ </div>
17
+ );
18
+ }
19
+ else if (value === false) {
20
+ return (
21
+ <div className="ds-boolean-cell-renderer">
22
+ <Icon
23
+ size={24}
24
+ name="x-solid"
25
+ color="var(--color-semantic-destructive-700)"
26
+ screenReaderText="false"
27
+ />
28
+ </div>
29
+ );
30
+ }
31
+ else {
32
+ return null;
33
+ }
34
+ };
@@ -0,0 +1,74 @@
1
+ import { describe, expect, test, vi } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom/vitest';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { CheckboxCellRenderer } from './CheckboxCellRenderer';
6
+ import type { CustomCellRendererProps } from 'ag-grid-react';
7
+
8
+ const createMockProps = (overrides: Partial<CustomCellRendererProps> = {}): CustomCellRendererProps => ({
9
+ value: false,
10
+ node: {
11
+ setDataValue: vi.fn(),
12
+ },
13
+ column: 'testField',
14
+ ...overrides,
15
+ } as unknown as CustomCellRendererProps);
16
+
17
+ describe('CheckboxCellRenderer', () => {
18
+ test('renders a checkbox', () => {
19
+ const props = createMockProps();
20
+ render(<CheckboxCellRenderer {...props} />);
21
+ expect(screen.getByRole('checkbox')).toBeInTheDocument();
22
+ });
23
+
24
+ test('renders unchecked when value is false', () => {
25
+ const props = createMockProps({ value: false });
26
+ render(<CheckboxCellRenderer {...props} />);
27
+ expect(screen.getByRole('checkbox')).not.toBeChecked();
28
+ });
29
+
30
+ test('renders checked when value is true', () => {
31
+ const props = createMockProps({ value: true });
32
+ render(<CheckboxCellRenderer {...props} />);
33
+ expect(screen.getByRole('checkbox')).toBeChecked();
34
+ });
35
+
36
+ test('calls node.setDataValue with toggled value on change', async () => {
37
+ const setDataValue = vi.fn();
38
+ const props = createMockProps({
39
+ value: false,
40
+ node: { setDataValue } as unknown as CustomCellRendererProps['node'],
41
+ column: 'attended' as unknown as CustomCellRendererProps['column'],
42
+ });
43
+
44
+ render(<CheckboxCellRenderer {...props} />);
45
+ await userEvent.click(screen.getByRole('checkbox'));
46
+ expect(setDataValue).toHaveBeenCalledWith('attended', true);
47
+ });
48
+
49
+ test('toggles from true to false on change', async () => {
50
+ const setDataValue = vi.fn();
51
+ const props = createMockProps({
52
+ value: true,
53
+ node: { setDataValue } as unknown as CustomCellRendererProps['node'],
54
+ column: 'attended' as unknown as CustomCellRendererProps['column'],
55
+ });
56
+
57
+ render(<CheckboxCellRenderer {...props} />);
58
+ await userEvent.click(screen.getByRole('checkbox'));
59
+ expect(setDataValue).toHaveBeenCalledWith('attended', false);
60
+ });
61
+
62
+ test('does not call setDataValue when column is undefined', async () => {
63
+ const setDataValue = vi.fn();
64
+ const props = createMockProps({
65
+ value: false,
66
+ node: { setDataValue } as unknown as CustomCellRendererProps['node'],
67
+ column: undefined as unknown as CustomCellRendererProps['column'],
68
+ });
69
+
70
+ render(<CheckboxCellRenderer {...props} />);
71
+ await userEvent.click(screen.getByRole('checkbox'));
72
+ expect(setDataValue).not.toHaveBeenCalled();
73
+ });
74
+ });
@@ -0,0 +1,28 @@
1
+ import type { CustomCellRendererProps } from 'ag-grid-react';
2
+ import { CheckboxInput } from 'Components/formField/inputs/checkbox/CheckboxInput';
3
+
4
+ export const CheckboxCellRenderer = (props: CustomCellRendererProps) => {
5
+ const {
6
+ value,
7
+ node,
8
+ column,
9
+ colDef = {},
10
+ } = props;
11
+
12
+ const { cellRendererParams, headerName } = colDef;
13
+
14
+ return (
15
+ <div className="ds-checkbox-cell-renderer">
16
+ <CheckboxInput
17
+ checked={value}
18
+ aria-label={headerName && `Checkbox: ${headerName}`}
19
+ onChange={() => {
20
+ if (column) {
21
+ node.setDataValue(column, !value);
22
+ }
23
+ }}
24
+ {...cellRendererParams}
25
+ />
26
+ </div>
27
+ );
28
+ };
@@ -0,0 +1,7 @@
1
+ .ds-boolean-cell-renderer {
2
+ display: flex;
3
+ justify-content: center;
4
+ align-items: center;
5
+ width: 100%;
6
+ height: 100%;
7
+ }
@@ -58,6 +58,6 @@
58
58
  }
59
59
  }
60
60
 
61
- .ag-cell-focus.ds-table__cell--supress-focus:focus-within {
61
+ .ag-cell-focus.ds-table__cell--suppress-focus:focus-within {
62
62
  border-color: transparent;
63
63
  }
package/src/index.scss CHANGED
@@ -37,10 +37,12 @@
37
37
  @use "components/progress/progress.scss";
38
38
  @use "components/toast/toast.scss";
39
39
  @use "components/datePicker/datePicker.scss";
40
+ @use "components/dateTimePicker/dateTimePicker.scss";
40
41
  @use "components/avatar/avatar.scss";
41
42
  @use "components/singleUser/singleUser.scss";
42
43
  @use "components/avatarGroup/avatarGroup.scss";
43
44
  @use "components/userDropdown/userDropdown.scss";
45
+ @use "components/table/cellRenderers/booleanCellRenderer.scss";
44
46
  @use "components/row/row.scss";
45
47
  @use "components/combobox/combobox.scss";
46
48
  @use "components/toggle/toggle.scss";
package/src/index.ts CHANGED
@@ -10,6 +10,8 @@ export { Banner, BANNER_LEVEL, type BannerProps } from 'Components/banner/Banner
10
10
  export { Button } from 'Components/button/Button';
11
11
  export { Card } from 'Components/card/Card';
12
12
  export { DatePicker } from 'Components/datePicker/DatePicker';
13
+ export { DateTimePicker } from 'Components/dateTimePicker/DateTimePicker';
14
+ export type { DateTimePickerDisplayFormat, DateTimePickerProps } from 'Components/dateTimePicker/DateTimePicker';
13
15
  export { Dropdown } from 'Components/dropdown/Dropdown';
14
16
  export { Tag } from 'Components/tag/Tag';
15
17
  export type { TagProps, TagColor } from 'Components/tag/Tag';
@@ -42,6 +44,7 @@ export { SingleUser, type SingleUserProps } from 'Components/singleUser/SingleUs
42
44
  export { Slideover, type SlideoverProps } from 'Components/slideover/Slideover';
43
45
  export { SlideoverManager } from 'Components/slideoverManager/SlideoverManager';
44
46
  export { DefaultCellRenderer } from 'Components/table/cellRenderers/DefaultCellRenderer';
47
+ export { BooleanCellRenderer } from 'Components/table/cellRenderers/BooleanCellRenderer';
45
48
  export { DSDefaultColDef } from 'Components/table/DSDefaultColDef';
46
49
  export { GridApiContext } from 'Components/table/GridApiContext';
47
50
  export { Table } from 'Components/table/Table';
@@ -63,3 +66,4 @@ export type {
63
66
  } from 'Components/combobox/types';
64
67
  export { Toggle } from 'Components/toggle/Toggle';
65
68
  export { SlideoverUtils } from 'Utils/SlideoverUtils';
69
+ export { CheckboxCellRenderer } from 'Components/table/cellRenderers/CheckboxCellRenderer';