@arbor-education/design-system.components 0.24.3 → 0.25.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 (208) hide show
  1. package/.gather/instructions/changesets.md +12 -0
  2. package/.gather/instructions/read-contributing.md +14 -0
  3. package/.storybook/DocsTemplate.tsx +3 -1
  4. package/.storybook/preview.ts +6 -2
  5. package/CHANGELOG.md +12 -0
  6. package/CONTRIBUTING.md +20 -0
  7. package/dist/components/avatarGroup/AvatarGroup.stories.d.ts.map +1 -1
  8. package/dist/components/avatarGroup/AvatarGroup.stories.js +4 -2
  9. package/dist/components/avatarGroup/AvatarGroup.stories.js.map +1 -1
  10. package/dist/components/card/Card.d.ts.map +1 -1
  11. package/dist/components/card/Card.js +2 -1
  12. package/dist/components/card/Card.js.map +1 -1
  13. package/dist/components/combobox/Combobox.d.ts.map +1 -1
  14. package/dist/components/combobox/Combobox.js +10 -5
  15. package/dist/components/combobox/Combobox.js.map +1 -1
  16. package/dist/components/combobox/Combobox.stories.d.ts.map +1 -1
  17. package/dist/components/combobox/Combobox.stories.js +2 -1
  18. package/dist/components/combobox/Combobox.stories.js.map +1 -1
  19. package/dist/components/combobox/Combobox.test.js +18 -9
  20. package/dist/components/combobox/Combobox.test.js.map +1 -1
  21. package/dist/components/combobox/ComboboxButtonTrigger.d.ts.map +1 -1
  22. package/dist/components/combobox/ComboboxButtonTrigger.js +3 -1
  23. package/dist/components/combobox/ComboboxButtonTrigger.js.map +1 -1
  24. package/dist/components/combobox/ComboboxListbox.d.ts.map +1 -1
  25. package/dist/components/combobox/ComboboxListbox.js +8 -4
  26. package/dist/components/combobox/ComboboxListbox.js.map +1 -1
  27. package/dist/components/combobox/buildListboxDisplayOptions.d.ts.map +1 -1
  28. package/dist/components/combobox/buildListboxDisplayOptions.js +4 -2
  29. package/dist/components/combobox/buildListboxDisplayOptions.js.map +1 -1
  30. package/dist/components/combobox/highlightLabel.d.ts.map +1 -1
  31. package/dist/components/combobox/highlightLabel.js +2 -1
  32. package/dist/components/combobox/highlightLabel.js.map +1 -1
  33. package/dist/components/combobox/useChipSelection.d.ts.map +1 -1
  34. package/dist/components/combobox/useChipSelection.js +2 -1
  35. package/dist/components/combobox/useChipSelection.js.map +1 -1
  36. package/dist/components/combobox/useComboboxListboxKeyboard.d.ts.map +1 -1
  37. package/dist/components/combobox/useComboboxListboxKeyboard.js +8 -4
  38. package/dist/components/combobox/useComboboxListboxKeyboard.js.map +1 -1
  39. package/dist/components/combobox/useComboboxPopoverBehavior.d.ts.map +1 -1
  40. package/dist/components/combobox/useComboboxPopoverBehavior.js +16 -8
  41. package/dist/components/combobox/useComboboxPopoverBehavior.js.map +1 -1
  42. package/dist/components/combobox/useComboboxState.d.ts.map +1 -1
  43. package/dist/components/combobox/useComboboxState.js +22 -11
  44. package/dist/components/combobox/useComboboxState.js.map +1 -1
  45. package/dist/components/datePicker/DatePickerCalendarHeader.d.ts.map +1 -1
  46. package/dist/components/datePicker/DatePickerCalendarHeader.js +4 -2
  47. package/dist/components/datePicker/DatePickerCalendarHeader.js.map +1 -1
  48. package/dist/components/dateTimePicker/DateTimePicker.d.ts.map +1 -1
  49. package/dist/components/dateTimePicker/DateTimePicker.js +2 -1
  50. package/dist/components/dateTimePicker/DateTimePicker.js.map +1 -1
  51. package/dist/components/dateTimePicker/DateTimePicker.test.js.map +1 -1
  52. package/dist/components/dropdown/Dropdown.stories.d.ts.map +1 -1
  53. package/dist/components/dropdown/Dropdown.stories.js +10 -5
  54. package/dist/components/dropdown/Dropdown.stories.js.map +1 -1
  55. package/dist/components/filterBar/FilterBar.d.ts.map +1 -1
  56. package/dist/components/filterBar/FilterBar.js +4 -2
  57. package/dist/components/filterBar/FilterBar.js.map +1 -1
  58. package/dist/components/filterBar/FilterBar.stories.d.ts.map +1 -1
  59. package/dist/components/filterBar/FilterBar.stories.js +6 -3
  60. package/dist/components/filterBar/FilterBar.stories.js.map +1 -1
  61. package/dist/components/filterBar/FilterBar.test.js +6 -3
  62. package/dist/components/filterBar/FilterBar.test.js.map +1 -1
  63. package/dist/components/formField/fieldset/Fieldset.stories.d.ts.map +1 -1
  64. package/dist/components/formField/fieldset/Fieldset.stories.js.map +1 -1
  65. package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts +1 -1
  66. package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts.map +1 -1
  67. package/dist/components/formField/inputs/radio/RadioButtonGroup.js +1 -1
  68. package/dist/components/formField/inputs/radio/RadioButtonGroup.js.map +1 -1
  69. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts.map +1 -1
  70. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js +2 -1
  71. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -1
  72. package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts.map +1 -1
  73. package/dist/components/formField/inputs/textArea/TextArea.stories.js +10 -5
  74. package/dist/components/formField/inputs/textArea/TextArea.stories.js.map +1 -1
  75. package/dist/components/formField/inputs/time/TimeInput.js +3 -4
  76. package/dist/components/formField/inputs/time/TimeInput.js.map +1 -1
  77. package/dist/components/formField/inputs/time/TimeInput.test.js.map +1 -1
  78. package/dist/components/heading/Heading.stories.d.ts.map +1 -1
  79. package/dist/components/heading/Heading.stories.js.map +1 -1
  80. package/dist/components/modal/Modal.stories.d.ts.map +1 -1
  81. package/dist/components/modal/Modal.stories.js.map +1 -1
  82. package/dist/components/progress/Progress.stories.d.ts.map +1 -1
  83. package/dist/components/progress/Progress.stories.js +2 -1
  84. package/dist/components/progress/Progress.stories.js.map +1 -1
  85. package/dist/components/section/Section.d.ts +3 -6
  86. package/dist/components/section/Section.d.ts.map +1 -1
  87. package/dist/components/section/Section.js +2 -2
  88. package/dist/components/section/Section.js.map +1 -1
  89. package/dist/components/section/Section.stories.d.ts +1 -0
  90. package/dist/components/section/Section.stories.d.ts.map +1 -1
  91. package/dist/components/section/Section.stories.js +125 -52
  92. package/dist/components/section/Section.stories.js.map +1 -1
  93. package/dist/components/section/Section.test.js +13 -2
  94. package/dist/components/section/Section.test.js.map +1 -1
  95. package/dist/components/sidebarNav/SidebarNavItem.d.ts.map +1 -1
  96. package/dist/components/sidebarNav/SidebarNavItem.js +12 -11
  97. package/dist/components/sidebarNav/SidebarNavItem.js.map +1 -1
  98. package/dist/components/sidebarNav/SidebarNavRailItem.d.ts.map +1 -1
  99. package/dist/components/sidebarNav/SidebarNavRailItem.js +11 -10
  100. package/dist/components/sidebarNav/SidebarNavRailItem.js.map +1 -1
  101. package/dist/components/sidebarNav/useControllableBoolean.d.ts.map +1 -1
  102. package/dist/components/sidebarNav/useControllableBoolean.js +2 -1
  103. package/dist/components/sidebarNav/useControllableBoolean.js.map +1 -1
  104. package/dist/components/table/Table.d.ts.map +1 -1
  105. package/dist/components/table/Table.js +11 -1
  106. package/dist/components/table/Table.js.map +1 -1
  107. package/dist/components/table/Table.stories.d.ts.map +1 -1
  108. package/dist/components/table/Table.stories.js +6 -2
  109. package/dist/components/table/Table.stories.js.map +1 -1
  110. package/dist/components/table/Table.test.js +9 -3
  111. package/dist/components/table/Table.test.js.map +1 -1
  112. package/dist/components/table/cellRenderers/ComboboxCellRenderer.d.ts.map +1 -1
  113. package/dist/components/table/cellRenderers/ComboboxCellRenderer.js +16 -8
  114. package/dist/components/table/cellRenderers/ComboboxCellRenderer.js.map +1 -1
  115. package/dist/components/table/columnFilters/TimeFilter/TimeFilter.d.ts.map +1 -1
  116. package/dist/components/table/columnFilters/TimeFilter/TimeFilter.js +2 -1
  117. package/dist/components/table/columnFilters/TimeFilter/TimeFilter.js.map +1 -1
  118. package/dist/components/table/tableControls/HideColumnsDropdown.d.ts.map +1 -1
  119. package/dist/components/table/tableControls/HideColumnsDropdown.js +2 -1
  120. package/dist/components/table/tableControls/HideColumnsDropdown.js.map +1 -1
  121. package/dist/components/table/tableControls/TableControls.d.ts.map +1 -1
  122. package/dist/components/table/tableControls/TableControls.js +2 -1
  123. package/dist/components/table/tableControls/TableControls.js.map +1 -1
  124. package/dist/components/tag/Tag.test.js.map +1 -1
  125. package/dist/components/tagList/TagList.d.ts.map +1 -1
  126. package/dist/components/tagList/TagList.js +8 -4
  127. package/dist/components/tagList/TagList.js.map +1 -1
  128. package/dist/components/tagList/TagList.test.js +6 -3
  129. package/dist/components/tagList/TagList.test.js.map +1 -1
  130. package/dist/components/treeRow/TreeRowSection.d.ts.map +1 -1
  131. package/dist/components/treeRow/TreeRowSection.js +2 -1
  132. package/dist/components/treeRow/TreeRowSection.js.map +1 -1
  133. package/dist/components/userDropdown/UserDropdown.stories.d.ts.map +1 -1
  134. package/dist/components/userDropdown/UserDropdown.stories.js +66 -22
  135. package/dist/components/userDropdown/UserDropdown.stories.js.map +1 -1
  136. package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.d.ts.map +1 -1
  137. package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.js +3 -1
  138. package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.js.map +1 -1
  139. package/dist/utils/getTextContrast.d.ts.map +1 -1
  140. package/dist/utils/getTextContrast.js +4 -2
  141. package/dist/utils/getTextContrast.js.map +1 -1
  142. package/dist/utils/hooks/useElementWidth.d.ts.map +1 -1
  143. package/dist/utils/hooks/useElementWidth.js +2 -1
  144. package/dist/utils/hooks/useElementWidth.js.map +1 -1
  145. package/dist/utils/hooks/useMeasuredChildWidths.d.ts.map +1 -1
  146. package/dist/utils/hooks/useMeasuredChildWidths.js +4 -2
  147. package/dist/utils/hooks/useMeasuredChildWidths.js.map +1 -1
  148. package/dist/utils/hooks/useRovingFocus.d.ts.map +1 -1
  149. package/dist/utils/hooks/useRovingFocus.js +8 -4
  150. package/dist/utils/hooks/useRovingFocus.js.map +1 -1
  151. package/dist/utils/spacedWidths.d.ts.map +1 -1
  152. package/dist/utils/spacedWidths.js +6 -3
  153. package/dist/utils/spacedWidths.js.map +1 -1
  154. package/eslint.config.mts +32 -1
  155. package/package.json +1 -1
  156. package/src/components/avatarGroup/AvatarGroup.stories.tsx +6 -2
  157. package/src/components/card/Card.tsx +3 -1
  158. package/src/components/combobox/Combobox.stories.tsx +3 -1
  159. package/src/components/combobox/Combobox.test.tsx +39 -11
  160. package/src/components/combobox/Combobox.tsx +15 -5
  161. package/src/components/combobox/ComboboxButtonTrigger.tsx +3 -1
  162. package/src/components/combobox/ComboboxListbox.tsx +12 -4
  163. package/src/components/combobox/buildListboxDisplayOptions.ts +6 -2
  164. package/src/components/combobox/highlightLabel.tsx +2 -1
  165. package/src/components/combobox/useChipSelection.ts +3 -1
  166. package/src/components/combobox/useComboboxListboxKeyboard.ts +12 -4
  167. package/src/components/combobox/useComboboxPopoverBehavior.ts +24 -8
  168. package/src/components/combobox/useComboboxState.ts +28 -11
  169. package/src/components/datePicker/DatePickerCalendarHeader.tsx +6 -2
  170. package/src/components/dateTimePicker/DateTimePicker.test.tsx +2 -0
  171. package/src/components/dateTimePicker/DateTimePicker.tsx +3 -1
  172. package/src/components/dropdown/Dropdown.stories.tsx +19 -8
  173. package/src/components/filterBar/FilterBar.stories.tsx +9 -3
  174. package/src/components/filterBar/FilterBar.test.tsx +9 -3
  175. package/src/components/filterBar/FilterBar.tsx +6 -2
  176. package/src/components/formField/fieldset/Fieldset.stories.tsx +18 -3
  177. package/src/components/formField/inputs/radio/RadioButtonGroup.tsx +1 -1
  178. package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +3 -1
  179. package/src/components/formField/inputs/textArea/TextArea.stories.tsx +10 -5
  180. package/src/components/formField/inputs/time/TimeInput.test.tsx +2 -0
  181. package/src/components/formField/inputs/time/TimeInput.tsx +4 -4
  182. package/src/components/heading/Heading.stories.tsx +12 -3
  183. package/src/components/modal/Modal.stories.tsx +15 -5
  184. package/src/components/progress/Progress.stories.tsx +3 -1
  185. package/src/components/section/Section.stories.tsx +198 -70
  186. package/src/components/section/Section.test.tsx +14 -8
  187. package/src/components/section/Section.tsx +7 -15
  188. package/src/components/sidebarNav/SidebarNavItem.tsx +27 -25
  189. package/src/components/sidebarNav/SidebarNavRailItem.tsx +34 -35
  190. package/src/components/sidebarNav/useControllableBoolean.ts +3 -1
  191. package/src/components/table/Table.stories.tsx +16 -12
  192. package/src/components/table/Table.test.tsx +10 -4
  193. package/src/components/table/Table.tsx +15 -5
  194. package/src/components/table/cellRenderers/ComboboxCellRenderer.tsx +19 -8
  195. package/src/components/table/columnFilters/TimeFilter/TimeFilter.tsx +3 -1
  196. package/src/components/table/tableControls/HideColumnsDropdown.tsx +3 -1
  197. package/src/components/table/tableControls/TableControls.tsx +3 -1
  198. package/src/components/tag/Tag.test.tsx +40 -8
  199. package/src/components/tagList/TagList.test.tsx +9 -3
  200. package/src/components/tagList/TagList.tsx +12 -4
  201. package/src/components/treeRow/TreeRowSection.tsx +3 -1
  202. package/src/components/userDropdown/UserDropdown.stories.tsx +66 -22
  203. package/src/components/userDropdown/internal/UserDropdownCollapsibleSection.tsx +3 -1
  204. package/src/utils/getTextContrast.ts +6 -2
  205. package/src/utils/hooks/useElementWidth.ts +3 -1
  206. package/src/utils/hooks/useMeasuredChildWidths.ts +6 -2
  207. package/src/utils/hooks/useRovingFocus.ts +12 -4
  208. package/src/utils/spacedWidths.ts +9 -3
package/eslint.config.mts CHANGED
@@ -11,7 +11,9 @@ const relativeImportExtensions: Rule.RuleModule = {
11
11
  create(context) {
12
12
  function check(node: Parameters<Rule.RuleListener['ImportDeclaration']>[0] | NonNullable<Parameters<Rule.RuleListener['ExportNamedDeclaration']>[0]>) {
13
13
  const source = 'source' in node && node.source ? node.source : null;
14
- if (!source) return;
14
+ if (!source) {
15
+ return;
16
+ }
15
17
  const path = source.value as string;
16
18
  if ((path.startsWith('./') || path.startsWith('../')) && !/\.[a-z]{2,5}$/.test(path)) {
17
19
  context.report({ node: source, message: `Relative import '${path}' must include a file extension (e.g. '${path}.js').` });
@@ -23,6 +25,7 @@ const relativeImportExtensions: Rule.RuleModule = {
23
25
 
24
26
  export default defineConfig([
25
27
  globalIgnores([
28
+ '.claude/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
26
29
  '.gather/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
27
30
  'dist/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
28
31
  ]),
@@ -48,6 +51,34 @@ export default defineConfig([
48
51
  '@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
49
52
  '@typescript-eslint/no-namespace': 'off',
50
53
  'local/relative-import-extensions': 'error',
54
+ '@typescript-eslint/consistent-type-definitions': ['error', 'type'],
55
+ 'no-nested-ternary': 'error',
56
+ 'curly': ['error', 'all'],
57
+ '@stylistic/curly-newline': ['error', { minElements: 1 }],
58
+ 'no-restricted-imports': [
59
+ 'error',
60
+ {
61
+ patterns: [
62
+ {
63
+ group: ['../../*'],
64
+ message: 'Use a path alias (Components/*, Utils/*) instead of a deep relative import.',
65
+ },
66
+ ],
67
+ },
68
+ ],
69
+ 'no-restricted-syntax': [
70
+ 'error',
71
+ {
72
+ selector: "CallExpression[callee.name='useEffect'][arguments.length<2]",
73
+ message: 'useEffect must always declare a dependency array, even if empty.',
74
+ },
75
+ ],
76
+ },
77
+ },
78
+ {
79
+ files: ['**/*.stories.tsx', '**/*.story.tsx', '**/*.test.{ts,tsx}'],
80
+ rules: {
81
+ 'no-restricted-imports': 'off',
51
82
  },
52
83
  },
53
84
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arbor-education/design-system.components",
3
- "version": "0.24.3",
3
+ "version": "0.25.1",
4
4
  "description": "The component library for the design system (the baby)",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -372,11 +372,15 @@ const LiveOverflowCounterTemplate = () => {
372
372
 
373
373
  const addItem = () => {
374
374
  const next = extraItems[items.length - initialItems.length];
375
- if (next) setItems(prev => [...prev, next]);
375
+ if (next) {
376
+ setItems(prev => [...prev, next]);
377
+ }
376
378
  };
377
379
 
378
380
  const removeItem = () => {
379
- if (items.length > 1) setItems(prev => prev.slice(0, -1));
381
+ if (items.length > 1) {
382
+ setItems(prev => prev.slice(0, -1));
383
+ }
380
384
  };
381
385
 
382
386
  return (
@@ -115,7 +115,9 @@ export const Card = ({
115
115
  }
116
116
  }}
117
117
  onKeyDown={(e) => {
118
- if (!isCardInteractive) return;
118
+ if (!isCardInteractive) {
119
+ return;
120
+ }
119
121
 
120
122
  if (e.key === 'Enter' || e.key === ' ') {
121
123
  e.preventDefault();
@@ -850,7 +850,9 @@ function AsyncSearchTemplate() {
850
850
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
851
851
 
852
852
  const handleSearch = (query: string) => {
853
- if (timerRef.current) clearTimeout(timerRef.current);
853
+ if (timerRef.current) {
854
+ clearTimeout(timerRef.current);
855
+ }
854
856
  timerRef.current = setTimeout(() => {
855
857
  setOptions(
856
858
  comboboxAsyncSearchOptions.filter(o =>
@@ -13,7 +13,9 @@ const people: ComboboxOption[] = [
13
13
 
14
14
  describe('Combobox', () => {
15
15
  const openWithChevron = async () => {
16
- if (screen.queryByRole('listbox')) return;
16
+ if (screen.queryByRole('listbox')) {
17
+ return;
18
+ }
17
19
  await userEvent.click(screen.getByRole('button', { name: 'Open suggestions' }));
18
20
  };
19
21
 
@@ -240,10 +242,18 @@ describe('Combobox', () => {
240
242
  const rectSpy = vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(function mockRect(this: HTMLElement) {
241
243
  let width = 0;
242
244
 
243
- if (this.classList.contains('ds-combobox__button-content')) width = 420;
244
- else if (this.classList.contains('ds-combobox__measure-chip')) width = 80;
245
- else if (this.classList.contains('ds-combobox__button-ellipsis')) width = 11.6;
246
- else if (this.classList.contains('ds-badge')) width = 24.2;
245
+ if (this.classList.contains('ds-combobox__button-content')) {
246
+ width = 420;
247
+ }
248
+ else if (this.classList.contains('ds-combobox__measure-chip')) {
249
+ width = 80;
250
+ }
251
+ else if (this.classList.contains('ds-combobox__button-ellipsis')) {
252
+ width = 11.6;
253
+ }
254
+ else if (this.classList.contains('ds-badge')) {
255
+ width = 24.2;
256
+ }
247
257
 
248
258
  return {
249
259
  width,
@@ -281,10 +291,18 @@ describe('Combobox', () => {
281
291
  const rectSpy = vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(function mockRect(this: HTMLElement) {
282
292
  let width = 0;
283
293
 
284
- if (this.classList.contains('ds-combobox__button-content')) width = 220;
285
- else if (this.classList.contains('ds-combobox__measure-chip')) width = 120.4;
286
- else if (this.classList.contains('ds-combobox__button-ellipsis')) width = 11.6;
287
- else if (this.classList.contains('ds-badge')) width = 24.2;
294
+ if (this.classList.contains('ds-combobox__button-content')) {
295
+ width = 220;
296
+ }
297
+ else if (this.classList.contains('ds-combobox__measure-chip')) {
298
+ width = 120.4;
299
+ }
300
+ else if (this.classList.contains('ds-combobox__button-ellipsis')) {
301
+ width = 11.6;
302
+ }
303
+ else if (this.classList.contains('ds-badge')) {
304
+ width = 24.2;
305
+ }
288
306
 
289
307
  return {
290
308
  width,
@@ -943,7 +961,12 @@ describe('Combobox', () => {
943
961
 
944
962
  test('loading state sets aria-busy, role=status (spinner only, accessible name), and hides options', async () => {
945
963
  render(
946
- <Combobox options={people} loading onSearch={() => {}} aria-label="People" />,
964
+ <Combobox
965
+ options={people}
966
+ loading
967
+ onSearch={() => {}}
968
+ aria-label="People"
969
+ />,
947
970
  );
948
971
  await openWithChevron();
949
972
 
@@ -958,7 +981,12 @@ describe('Combobox', () => {
958
981
  });
959
982
 
960
983
  test('loading is false does not set aria-busy on listbox', async () => {
961
- render(<Combobox options={people} onSearch={() => {}} />);
984
+ render(
985
+ <Combobox
986
+ options={people}
987
+ onSearch={() => {}}
988
+ />,
989
+ );
962
990
  await openWithChevron();
963
991
 
964
992
  expect(screen.getByRole('listbox')).not.toHaveAttribute('aria-busy');
@@ -154,7 +154,9 @@ const ComboboxRoot = (props: ComboboxProps): React.JSX.Element => {
154
154
 
155
155
  const resolveTagLabel = useCallback(
156
156
  (opt: { tagLabel?: string; label: string }): string => {
157
- if (getTagLabel) return getTagLabel(opt as Parameters<typeof getTagLabel>[0]);
157
+ if (getTagLabel) {
158
+ return getTagLabel(opt as Parameters<typeof getTagLabel>[0]);
159
+ }
158
160
  return opt.tagLabel ?? opt.label;
159
161
  },
160
162
  [getTagLabel],
@@ -170,13 +172,19 @@ const ComboboxRoot = (props: ComboboxProps): React.JSX.Element => {
170
172
  }, []);
171
173
 
172
174
  const focusDeleteButtonForHighlighted = useCallback((): boolean => {
173
- if (highlightIndex < 0 || highlightIndex >= filteredOptions.length) return false;
175
+ if (highlightIndex < 0 || highlightIndex >= filteredOptions.length) {
176
+ return false;
177
+ }
174
178
 
175
179
  const highlightedOption = filteredOptions[highlightIndex];
176
- if (!highlightedOption || !createdValuesSet.has(highlightedOption.value)) return false;
180
+ if (!highlightedOption || !createdValuesSet.has(highlightedOption.value)) {
181
+ return false;
182
+ }
177
183
 
178
184
  const button = deleteButtonRefs.current.get(highlightedOption.value);
179
- if (!button) return false;
185
+ if (!button) {
186
+ return false;
187
+ }
180
188
 
181
189
  button.focus();
182
190
  return true;
@@ -281,7 +289,9 @@ const ComboboxRoot = (props: ComboboxProps): React.JSX.Element => {
281
289
 
282
290
  const handleButtonTriggerKeyDown = useCallback(
283
291
  (e: React.KeyboardEvent<HTMLDivElement>) => {
284
- if (disabled) return;
292
+ if (disabled) {
293
+ return;
294
+ }
285
295
  if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
286
296
  e.preventDefault();
287
297
  handleTriggerClick();
@@ -202,7 +202,9 @@ export const ComboboxButtonTrigger = ({
202
202
  <div className="ds-combobox__button-tags-track" ref={measureTrackRef}>
203
203
  {selectedTags.map((opt, tagIdx) => (
204
204
  <span key={`measure-${opt.value}`} className="ds-combobox__measure-chip">
205
- {renderSelectionTag(opt, tagIdx, disabled ? undefined : () => {})}
205
+ {renderSelectionTag(opt, tagIdx, disabled
206
+ ? undefined
207
+ : () => {})}
206
208
  </span>
207
209
  ))}
208
210
  </div>
@@ -100,7 +100,9 @@ export const ComboboxListbox = (props: ComboboxListboxProps): React.JSX.Element
100
100
  onMouseEnter={() => setHighlightIndex(idx)}
101
101
  onMouseDown={preventMouseDefault}
102
102
  onClick={() => {
103
- if (!opt.disabled) selectOption(opt.value);
103
+ if (!opt.disabled) {
104
+ selectOption(opt.value);
105
+ }
104
106
  }}
105
107
  onDeleteCreated={(value) => {
106
108
  deleteCreatedOption(value, idx);
@@ -128,7 +130,9 @@ export const ComboboxListbox = (props: ComboboxListboxProps): React.JSX.Element
128
130
  className="ds-combobox__listbox-search-input-wrap"
129
131
  onMouseDown={(e) => {
130
132
  const el = inputRef.current;
131
- if (!el || e.button !== 0) return;
133
+ if (!el || e.button !== 0) {
134
+ return;
135
+ }
132
136
  if (e.target !== el && !el.contains(e.target as Node)) {
133
137
  el.focus();
134
138
  }
@@ -178,11 +182,15 @@ export const ComboboxListbox = (props: ComboboxListboxProps): React.JSX.Element
178
182
  : (
179
183
  <>
180
184
  {Array.from(optionGroups.entries()).map(([groupName, groupOptions]) => {
181
- if (groupOptions.length === 0) return null;
185
+ if (groupOptions.length === 0) {
186
+ return null;
187
+ }
182
188
 
183
189
  const groupItems = groupOptions.map(opt => renderOptionRow(opt));
184
190
 
185
- if (!groupName) return groupItems;
191
+ if (!groupName) {
192
+ return groupItems;
193
+ }
186
194
 
187
195
  return (
188
196
  <li key={groupName} role="presentation" className="ds-combobox__group">
@@ -5,8 +5,12 @@ export function buildOptionGroups(options: ComboboxOption[]): Map<string, Combob
5
5
  for (const opt of options) {
6
6
  const key = opt.group ?? '';
7
7
  const list = map.get(key);
8
- if (list) list.push(opt);
9
- else map.set(key, [opt]);
8
+ if (list) {
9
+ list.push(opt);
10
+ }
11
+ else {
12
+ map.set(key, [opt]);
13
+ }
10
14
  }
11
15
  return map;
12
16
  }
@@ -20,9 +20,10 @@ export const highlightLabel = ({
20
20
 
21
21
  const lowerLabel = label.toLowerCase();
22
22
  const lowerQuery = trimmedQuery.toLowerCase();
23
+ const prefixMatchStart = lowerLabel.startsWith(lowerQuery) ? 0 : -1;
23
24
  const matchStart
24
25
  = searchType === 'prefix'
25
- ? (lowerLabel.startsWith(lowerQuery) ? 0 : -1)
26
+ ? prefixMatchStart
26
27
  : lowerLabel.indexOf(lowerQuery);
27
28
 
28
29
  if (matchStart === -1) {
@@ -30,7 +30,9 @@ export const useChipSelection = (selectedValues: string[]): UseChipSelectionRetu
30
30
 
31
31
  useEffect(() => {
32
32
  setSelectedChipValues((prev) => {
33
- if (prev.length === 0) return prev;
33
+ if (prev.length === 0) {
34
+ return prev;
35
+ }
34
36
  return prev.filter(value => selectedValues.includes(value));
35
37
  });
36
38
  }, [selectedValues]);
@@ -64,7 +64,9 @@ export const useComboboxListboxKeyboard = ({
64
64
  exitChipNav();
65
65
  return;
66
66
  }
67
- if (!isOpen) return;
67
+ if (!isOpen) {
68
+ return;
69
+ }
68
70
  if (showListboxLoading) {
69
71
  return;
70
72
  }
@@ -74,14 +76,18 @@ export const useComboboxListboxKeyboard = ({
74
76
  break;
75
77
  }
76
78
  case 'ArrowRight': {
77
- if (inChipNav || !isOpen || showListboxLoading) return;
79
+ if (inChipNav || !isOpen || showListboxLoading) {
80
+ return;
81
+ }
78
82
  if (focusDeleteButtonForHighlighted()) {
79
83
  e.preventDefault();
80
84
  }
81
85
  break;
82
86
  }
83
87
  case 'Tab': {
84
- if (e.shiftKey || inChipNav || !isOpen || showListboxLoading) return;
88
+ if (e.shiftKey || inChipNav || !isOpen || showListboxLoading) {
89
+ return;
90
+ }
85
91
  if (focusDeleteButtonForHighlighted()) {
86
92
  e.preventDefault();
87
93
  }
@@ -102,7 +108,9 @@ export const useComboboxListboxKeyboard = ({
102
108
  }
103
109
  if (highlightIndex >= 0 && highlightIndex < listboxOptions.length) {
104
110
  const opt = listboxOptions[highlightIndex];
105
- if (opt && !opt.disabled) selectOption(opt.value);
111
+ if (opt && !opt.disabled) {
112
+ selectOption(opt.value);
113
+ }
106
114
  }
107
115
  else if (showCreateRow && highlightIndex === listboxOptions.length) {
108
116
  handleCreate();
@@ -84,7 +84,9 @@ export const useComboboxPopoverBehavior = ({
84
84
  else if (!isOpen) {
85
85
  openPopover();
86
86
  }
87
- if (isAsync) onSearch?.(val);
87
+ if (isAsync) {
88
+ onSearch?.(val);
89
+ }
88
90
  },
89
91
  [
90
92
  clearChipSelection,
@@ -101,7 +103,9 @@ export const useComboboxPopoverBehavior = ({
101
103
  );
102
104
 
103
105
  useEffect(() => {
104
- if (loading) return;
106
+ if (loading) {
107
+ return;
108
+ }
105
109
  if (isOpen && showCreateRow && highlightIndex === -1) {
106
110
  setHighlightIndex(filteredOptionsLength);
107
111
  }
@@ -114,14 +118,18 @@ export const useComboboxPopoverBehavior = ({
114
118
  }, [showListboxLoading, setHighlightIndex]);
115
119
 
116
120
  const requestOpen = useCallback(() => {
117
- if (disabled) return;
121
+ if (disabled) {
122
+ return;
123
+ }
118
124
  if (canOpenPopover) {
119
125
  openPopover();
120
126
  }
121
127
  }, [disabled, openPopover, canOpenPopover]);
122
128
 
123
129
  const handleTriggerClick = useCallback(() => {
124
- if (disabled) return;
130
+ if (disabled) {
131
+ return;
132
+ }
125
133
  if (triggerContainsInput) {
126
134
  inputRef.current?.focus();
127
135
  }
@@ -131,15 +139,21 @@ export const useComboboxPopoverBehavior = ({
131
139
  }, [disabled, dropdownOnFocus, inputRef, requestOpen, triggerContainsInput]);
132
140
 
133
141
  const handleInputFocus = useCallback(() => {
134
- if (!dropdownOnFocus || disabled) return;
142
+ if (!dropdownOnFocus || disabled) {
143
+ return;
144
+ }
135
145
  requestOpen();
136
146
  }, [disabled, dropdownOnFocus, requestOpen]);
137
147
 
138
148
  const handleInputBlur = useCallback(
139
149
  (e: React.FocusEvent<HTMLInputElement>) => {
140
150
  const relatedTarget = e.relatedTarget as Node | null;
141
- if (relatedTarget && triggerRef.current?.contains(relatedTarget)) return;
142
- if (relatedTarget && listboxRef.current?.contains(relatedTarget)) return;
151
+ if (relatedTarget && triggerRef.current?.contains(relatedTarget)) {
152
+ return;
153
+ }
154
+ if (relatedTarget && listboxRef.current?.contains(relatedTarget)) {
155
+ return;
156
+ }
143
157
  clearChipSelection();
144
158
  exitChipNav();
145
159
  },
@@ -149,7 +163,9 @@ export const useComboboxPopoverBehavior = ({
149
163
  const handleChevronClick = useCallback(
150
164
  (e: React.MouseEvent) => {
151
165
  e.stopPropagation();
152
- if (disabled) return;
166
+ if (disabled) {
167
+ return;
168
+ }
153
169
  if (triggerContainsInput) {
154
170
  inputRef.current?.focus();
155
171
  }
@@ -73,7 +73,9 @@ export const useComboboxState = (params: UseComboboxStateParams): UseComboboxSta
73
73
 
74
74
  const updateValue = useCallback(
75
75
  (next: string[]) => {
76
- if (!isControlled) setInternalValue(next);
76
+ if (!isControlled) {
77
+ setInternalValue(next);
78
+ }
77
79
  onValueChange?.(next);
78
80
  },
79
81
  [isControlled, onValueChange],
@@ -91,14 +93,18 @@ export const useComboboxState = (params: UseComboboxStateParams): UseComboboxSta
91
93
  );
92
94
 
93
95
  const mergedOptions = useMemo(() => {
94
- if (createdOptions.length === 0) return options;
96
+ if (createdOptions.length === 0) {
97
+ return options;
98
+ }
95
99
  const existingValues = new Set(options.map(opt => opt.value));
96
100
  const extras = createdOptions.filter(opt => !existingValues.has(opt.value));
97
101
  return [...options, ...extras];
98
102
  }, [options, createdOptions]);
99
103
 
100
104
  const filteredOptions = useMemo(() => {
101
- if (isAsync || !query) return mergedOptions;
105
+ if (isAsync || !query) {
106
+ return mergedOptions;
107
+ }
102
108
  return mergedOptions.filter(opt => filter(opt, query));
103
109
  }, [mergedOptions, query, isAsync, filter]);
104
110
 
@@ -122,11 +128,16 @@ export const useComboboxState = (params: UseComboboxStateParams): UseComboboxSta
122
128
 
123
129
  const selectOption = useCallback(
124
130
  (optionValue: string) => {
125
- const next = multiple
126
- ? selectedValuesSet.has(optionValue)
127
- ? selectedValues.filter(v => v !== optionValue)
128
- : [...selectedValues, optionValue]
129
- : [optionValue];
131
+ let next: string[];
132
+ if (!multiple) {
133
+ next = [optionValue];
134
+ }
135
+ else if (selectedValuesSet.has(optionValue)) {
136
+ next = selectedValues.filter(v => v !== optionValue);
137
+ }
138
+ else {
139
+ next = [...selectedValues, optionValue];
140
+ }
130
141
  updateValue(next);
131
142
  setQuery('');
132
143
  if (!multiple) {
@@ -149,7 +160,9 @@ export const useComboboxState = (params: UseComboboxStateParams): UseComboboxSta
149
160
  }, [updateValue]);
150
161
 
151
162
  const openPopover = useCallback(() => {
152
- if (!disabled) setIsOpen(true);
163
+ if (!disabled) {
164
+ setIsOpen(true);
165
+ }
153
166
  }, [disabled]);
154
167
 
155
168
  const ensureCreatedOption = useCallback(
@@ -167,7 +180,9 @@ export const useComboboxState = (params: UseComboboxStateParams): UseComboboxSta
167
180
  const existingByValue = mergedOptions.find(opt => opt.value === created.value);
168
181
  if (!existingByValue) {
169
182
  setCreatedOptions((prev) => {
170
- if (prev.some(opt => opt.value === created.value)) return prev;
183
+ if (prev.some(opt => opt.value === created.value)) {
184
+ return prev;
185
+ }
171
186
  return [...prev, created];
172
187
  });
173
188
  }
@@ -179,7 +194,9 @@ export const useComboboxState = (params: UseComboboxStateParams): UseComboboxSta
179
194
 
180
195
  const handleCreate = useCallback(() => {
181
196
  const trimmed = query.trim();
182
- if (!trimmed) return;
197
+ if (!trimmed) {
198
+ return;
199
+ }
183
200
 
184
201
  const existing = mergedOptions.find(
185
202
  opt => normaliseComboboxQuery(opt.label) === normaliseComboboxQuery(trimmed),
@@ -33,14 +33,18 @@ export const DatePickerCalendarHeader = ({
33
33
 
34
34
  const handleMonthSelection = (selectedValues: string[]) => {
35
35
  const nextMonthIndex = Number.parseInt(selectedValues[0] ?? String(month.getMonth()), 10);
36
- if (Number.isNaN(nextMonthIndex)) return;
36
+ if (Number.isNaN(nextMonthIndex)) {
37
+ return;
38
+ }
37
39
 
38
40
  onMonthChange(new Date(month.getFullYear(), nextMonthIndex, 1));
39
41
  };
40
42
 
41
43
  const handleYearSelection = (selectedValues: string[]) => {
42
44
  const nextYear = Number.parseInt(selectedValues[0] ?? String(month.getFullYear()), 10);
43
- if (Number.isNaN(nextYear)) return;
45
+ if (Number.isNaN(nextYear)) {
46
+ return;
47
+ }
44
48
 
45
49
  onMonthChange(new Date(nextYear, month.getMonth(), 1));
46
50
  };
@@ -9,7 +9,9 @@ describe('DateTimePicker', () => {
9
9
  beforeEach(() => {
10
10
  globalThis.ResizeObserver = class {
11
11
  observe() {}
12
+
12
13
  unobserve() {}
14
+
13
15
  disconnect() {}
14
16
  } as unknown as typeof ResizeObserver;
15
17
  });
@@ -199,6 +199,8 @@ export const DateTimePicker = (props: DateTimePickerProps) => {
199
199
 
200
200
  const popupParentRef = useContext(PopupParentContext);
201
201
 
202
+ const nativeStep = granularity === 'second' ? 1 : 60;
203
+
202
204
  return (
203
205
  <div className={classNames('ds-date-time-picker', className)}>
204
206
  <div
@@ -215,7 +217,7 @@ export const DateTimePicker = (props: DateTimePickerProps) => {
215
217
  onPointerDown={onNativeFieldHintPointerDown}
216
218
  readOnly={hasCustomEmptyHint}
217
219
  id={id}
218
- step={isNative ? (granularity === 'second' ? 1 : 60) : undefined}
220
+ step={isNative ? nativeStep : undefined}
219
221
  placeholder={showFieldHint ? '' : resolvedPlaceholder}
220
222
  hasError={hasError}
221
223
  aria-describedby={ariaDescribedBy}
@@ -392,11 +392,16 @@ const SchoolContextExampleTemplate = () => {
392
392
  year11 && 'Year 11',
393
393
  ].filter(Boolean);
394
394
 
395
- const label = allYears
396
- ? 'All year groups'
397
- : selectedYears.length === 0
398
- ? 'Select year groups'
399
- : selectedYears.join(', ');
395
+ let label: string;
396
+ if (allYears) {
397
+ label = 'All year groups';
398
+ }
399
+ else if (selectedYears.length === 0) {
400
+ label = 'Select year groups';
401
+ }
402
+ else {
403
+ label = selectedYears.join(', ');
404
+ }
400
405
 
401
406
  return (
402
407
  <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)', padding: 'var(--spacing-xlarge)', minWidth: '280px' }}>
@@ -450,9 +455,15 @@ export const Default: Story = withDescription(
450
455
  <Button variant="primary">Open menu</Button>
451
456
  </Dropdown.Trigger>
452
457
  <Dropdown.Content>
453
- <Dropdown.Item onSelect={() => {}}>View student profile</Dropdown.Item>
454
- <Dropdown.Item onSelect={() => {}}>Edit assessment details</Dropdown.Item>
455
- <Dropdown.Item onSelect={() => {}}>Archive record</Dropdown.Item>
458
+ <Dropdown.Item onSelect={() => {}}>
459
+ View student profile
460
+ </Dropdown.Item>
461
+ <Dropdown.Item onSelect={() => {}}>
462
+ Edit assessment details
463
+ </Dropdown.Item>
464
+ <Dropdown.Item onSelect={() => {}}>
465
+ Archive record
466
+ </Dropdown.Item>
456
467
  </Dropdown.Content>
457
468
  </Dropdown>
458
469
  </div>
@@ -553,7 +553,9 @@ const ModalWiringTemplate = () => {
553
553
  };
554
554
 
555
555
  useEffect(() => {
556
- if (!open) return;
556
+ if (!open) {
557
+ return;
558
+ }
557
559
 
558
560
  const frame = requestAnimationFrame(() => {
559
561
  document.getElementById(pendingFocusFieldId)?.focus();
@@ -582,7 +584,9 @@ const ModalWiringTemplate = () => {
582
584
 
583
585
  const items: FilterBar.TagItem[] = APPLIED_ITEM_SEEDS.flatMap((item) => {
584
586
  const value = fieldValues[item.fieldId]?.trim();
585
- if (!value) return [];
587
+ if (!value) {
588
+ return [];
589
+ }
586
590
 
587
591
  return [{
588
592
  ...item,
@@ -593,7 +597,9 @@ const ModalWiringTemplate = () => {
593
597
  removeLabel: `Remove ${item.label.toLowerCase()} ${item.type}`,
594
598
  onClick: clickedItem => openModal(clickedItem.type, clickedItem.fieldId ?? PANEL_FIELD_IDS[clickedItem.type]),
595
599
  onRemove: (removedItem) => {
596
- if (!removedItem.fieldId) return;
600
+ if (!removedItem.fieldId) {
601
+ return;
602
+ }
597
603
  updateFieldValue(removedItem.fieldId, '');
598
604
  },
599
605
  }];