@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.
- package/.gather/instructions/changesets.md +12 -0
- package/.gather/instructions/read-contributing.md +14 -0
- package/.storybook/DocsTemplate.tsx +3 -1
- package/.storybook/preview.ts +6 -2
- package/CHANGELOG.md +12 -0
- package/CONTRIBUTING.md +20 -0
- package/dist/components/avatarGroup/AvatarGroup.stories.d.ts.map +1 -1
- package/dist/components/avatarGroup/AvatarGroup.stories.js +4 -2
- package/dist/components/avatarGroup/AvatarGroup.stories.js.map +1 -1
- package/dist/components/card/Card.d.ts.map +1 -1
- package/dist/components/card/Card.js +2 -1
- package/dist/components/card/Card.js.map +1 -1
- package/dist/components/combobox/Combobox.d.ts.map +1 -1
- package/dist/components/combobox/Combobox.js +10 -5
- package/dist/components/combobox/Combobox.js.map +1 -1
- package/dist/components/combobox/Combobox.stories.d.ts.map +1 -1
- package/dist/components/combobox/Combobox.stories.js +2 -1
- package/dist/components/combobox/Combobox.stories.js.map +1 -1
- package/dist/components/combobox/Combobox.test.js +18 -9
- package/dist/components/combobox/Combobox.test.js.map +1 -1
- package/dist/components/combobox/ComboboxButtonTrigger.d.ts.map +1 -1
- package/dist/components/combobox/ComboboxButtonTrigger.js +3 -1
- package/dist/components/combobox/ComboboxButtonTrigger.js.map +1 -1
- package/dist/components/combobox/ComboboxListbox.d.ts.map +1 -1
- package/dist/components/combobox/ComboboxListbox.js +8 -4
- package/dist/components/combobox/ComboboxListbox.js.map +1 -1
- package/dist/components/combobox/buildListboxDisplayOptions.d.ts.map +1 -1
- package/dist/components/combobox/buildListboxDisplayOptions.js +4 -2
- package/dist/components/combobox/buildListboxDisplayOptions.js.map +1 -1
- package/dist/components/combobox/highlightLabel.d.ts.map +1 -1
- package/dist/components/combobox/highlightLabel.js +2 -1
- package/dist/components/combobox/highlightLabel.js.map +1 -1
- package/dist/components/combobox/useChipSelection.d.ts.map +1 -1
- package/dist/components/combobox/useChipSelection.js +2 -1
- package/dist/components/combobox/useChipSelection.js.map +1 -1
- package/dist/components/combobox/useComboboxListboxKeyboard.d.ts.map +1 -1
- package/dist/components/combobox/useComboboxListboxKeyboard.js +8 -4
- package/dist/components/combobox/useComboboxListboxKeyboard.js.map +1 -1
- package/dist/components/combobox/useComboboxPopoverBehavior.d.ts.map +1 -1
- package/dist/components/combobox/useComboboxPopoverBehavior.js +16 -8
- package/dist/components/combobox/useComboboxPopoverBehavior.js.map +1 -1
- package/dist/components/combobox/useComboboxState.d.ts.map +1 -1
- package/dist/components/combobox/useComboboxState.js +22 -11
- package/dist/components/combobox/useComboboxState.js.map +1 -1
- package/dist/components/datePicker/DatePickerCalendarHeader.d.ts.map +1 -1
- package/dist/components/datePicker/DatePickerCalendarHeader.js +4 -2
- package/dist/components/datePicker/DatePickerCalendarHeader.js.map +1 -1
- package/dist/components/dateTimePicker/DateTimePicker.d.ts.map +1 -1
- package/dist/components/dateTimePicker/DateTimePicker.js +2 -1
- package/dist/components/dateTimePicker/DateTimePicker.js.map +1 -1
- package/dist/components/dateTimePicker/DateTimePicker.test.js.map +1 -1
- package/dist/components/dropdown/Dropdown.stories.d.ts.map +1 -1
- package/dist/components/dropdown/Dropdown.stories.js +10 -5
- package/dist/components/dropdown/Dropdown.stories.js.map +1 -1
- package/dist/components/filterBar/FilterBar.d.ts.map +1 -1
- package/dist/components/filterBar/FilterBar.js +4 -2
- package/dist/components/filterBar/FilterBar.js.map +1 -1
- package/dist/components/filterBar/FilterBar.stories.d.ts.map +1 -1
- package/dist/components/filterBar/FilterBar.stories.js +6 -3
- package/dist/components/filterBar/FilterBar.stories.js.map +1 -1
- package/dist/components/filterBar/FilterBar.test.js +6 -3
- package/dist/components/filterBar/FilterBar.test.js.map +1 -1
- package/dist/components/formField/fieldset/Fieldset.stories.d.ts.map +1 -1
- package/dist/components/formField/fieldset/Fieldset.stories.js.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonGroup.js +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonGroup.js.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js +2 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -1
- package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/textArea/TextArea.stories.js +10 -5
- package/dist/components/formField/inputs/textArea/TextArea.stories.js.map +1 -1
- package/dist/components/formField/inputs/time/TimeInput.js +3 -4
- package/dist/components/formField/inputs/time/TimeInput.js.map +1 -1
- package/dist/components/formField/inputs/time/TimeInput.test.js.map +1 -1
- package/dist/components/heading/Heading.stories.d.ts.map +1 -1
- package/dist/components/heading/Heading.stories.js.map +1 -1
- package/dist/components/modal/Modal.stories.d.ts.map +1 -1
- package/dist/components/modal/Modal.stories.js.map +1 -1
- package/dist/components/progress/Progress.stories.d.ts.map +1 -1
- package/dist/components/progress/Progress.stories.js +2 -1
- package/dist/components/progress/Progress.stories.js.map +1 -1
- package/dist/components/section/Section.d.ts +3 -6
- package/dist/components/section/Section.d.ts.map +1 -1
- package/dist/components/section/Section.js +2 -2
- package/dist/components/section/Section.js.map +1 -1
- package/dist/components/section/Section.stories.d.ts +1 -0
- package/dist/components/section/Section.stories.d.ts.map +1 -1
- package/dist/components/section/Section.stories.js +125 -52
- package/dist/components/section/Section.stories.js.map +1 -1
- package/dist/components/section/Section.test.js +13 -2
- package/dist/components/section/Section.test.js.map +1 -1
- package/dist/components/sidebarNav/SidebarNavItem.d.ts.map +1 -1
- package/dist/components/sidebarNav/SidebarNavItem.js +12 -11
- package/dist/components/sidebarNav/SidebarNavItem.js.map +1 -1
- package/dist/components/sidebarNav/SidebarNavRailItem.d.ts.map +1 -1
- package/dist/components/sidebarNav/SidebarNavRailItem.js +11 -10
- package/dist/components/sidebarNav/SidebarNavRailItem.js.map +1 -1
- package/dist/components/sidebarNav/useControllableBoolean.d.ts.map +1 -1
- package/dist/components/sidebarNav/useControllableBoolean.js +2 -1
- package/dist/components/sidebarNav/useControllableBoolean.js.map +1 -1
- package/dist/components/table/Table.d.ts.map +1 -1
- package/dist/components/table/Table.js +11 -1
- package/dist/components/table/Table.js.map +1 -1
- package/dist/components/table/Table.stories.d.ts.map +1 -1
- package/dist/components/table/Table.stories.js +6 -2
- package/dist/components/table/Table.stories.js.map +1 -1
- package/dist/components/table/Table.test.js +9 -3
- package/dist/components/table/Table.test.js.map +1 -1
- package/dist/components/table/cellRenderers/ComboboxCellRenderer.d.ts.map +1 -1
- package/dist/components/table/cellRenderers/ComboboxCellRenderer.js +16 -8
- package/dist/components/table/cellRenderers/ComboboxCellRenderer.js.map +1 -1
- package/dist/components/table/columnFilters/TimeFilter/TimeFilter.d.ts.map +1 -1
- package/dist/components/table/columnFilters/TimeFilter/TimeFilter.js +2 -1
- package/dist/components/table/columnFilters/TimeFilter/TimeFilter.js.map +1 -1
- package/dist/components/table/tableControls/HideColumnsDropdown.d.ts.map +1 -1
- package/dist/components/table/tableControls/HideColumnsDropdown.js +2 -1
- package/dist/components/table/tableControls/HideColumnsDropdown.js.map +1 -1
- package/dist/components/table/tableControls/TableControls.d.ts.map +1 -1
- package/dist/components/table/tableControls/TableControls.js +2 -1
- package/dist/components/table/tableControls/TableControls.js.map +1 -1
- package/dist/components/tag/Tag.test.js.map +1 -1
- package/dist/components/tagList/TagList.d.ts.map +1 -1
- package/dist/components/tagList/TagList.js +8 -4
- package/dist/components/tagList/TagList.js.map +1 -1
- package/dist/components/tagList/TagList.test.js +6 -3
- package/dist/components/tagList/TagList.test.js.map +1 -1
- package/dist/components/treeRow/TreeRowSection.d.ts.map +1 -1
- package/dist/components/treeRow/TreeRowSection.js +2 -1
- package/dist/components/treeRow/TreeRowSection.js.map +1 -1
- package/dist/components/userDropdown/UserDropdown.stories.d.ts.map +1 -1
- package/dist/components/userDropdown/UserDropdown.stories.js +66 -22
- package/dist/components/userDropdown/UserDropdown.stories.js.map +1 -1
- package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.d.ts.map +1 -1
- package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.js +3 -1
- package/dist/components/userDropdown/internal/UserDropdownCollapsibleSection.js.map +1 -1
- package/dist/utils/getTextContrast.d.ts.map +1 -1
- package/dist/utils/getTextContrast.js +4 -2
- package/dist/utils/getTextContrast.js.map +1 -1
- package/dist/utils/hooks/useElementWidth.d.ts.map +1 -1
- package/dist/utils/hooks/useElementWidth.js +2 -1
- package/dist/utils/hooks/useElementWidth.js.map +1 -1
- package/dist/utils/hooks/useMeasuredChildWidths.d.ts.map +1 -1
- package/dist/utils/hooks/useMeasuredChildWidths.js +4 -2
- package/dist/utils/hooks/useMeasuredChildWidths.js.map +1 -1
- package/dist/utils/hooks/useRovingFocus.d.ts.map +1 -1
- package/dist/utils/hooks/useRovingFocus.js +8 -4
- package/dist/utils/hooks/useRovingFocus.js.map +1 -1
- package/dist/utils/spacedWidths.d.ts.map +1 -1
- package/dist/utils/spacedWidths.js +6 -3
- package/dist/utils/spacedWidths.js.map +1 -1
- package/eslint.config.mts +32 -1
- package/package.json +1 -1
- package/src/components/avatarGroup/AvatarGroup.stories.tsx +6 -2
- package/src/components/card/Card.tsx +3 -1
- package/src/components/combobox/Combobox.stories.tsx +3 -1
- package/src/components/combobox/Combobox.test.tsx +39 -11
- package/src/components/combobox/Combobox.tsx +15 -5
- package/src/components/combobox/ComboboxButtonTrigger.tsx +3 -1
- package/src/components/combobox/ComboboxListbox.tsx +12 -4
- package/src/components/combobox/buildListboxDisplayOptions.ts +6 -2
- package/src/components/combobox/highlightLabel.tsx +2 -1
- package/src/components/combobox/useChipSelection.ts +3 -1
- package/src/components/combobox/useComboboxListboxKeyboard.ts +12 -4
- package/src/components/combobox/useComboboxPopoverBehavior.ts +24 -8
- package/src/components/combobox/useComboboxState.ts +28 -11
- package/src/components/datePicker/DatePickerCalendarHeader.tsx +6 -2
- package/src/components/dateTimePicker/DateTimePicker.test.tsx +2 -0
- package/src/components/dateTimePicker/DateTimePicker.tsx +3 -1
- package/src/components/dropdown/Dropdown.stories.tsx +19 -8
- package/src/components/filterBar/FilterBar.stories.tsx +9 -3
- package/src/components/filterBar/FilterBar.test.tsx +9 -3
- package/src/components/filterBar/FilterBar.tsx +6 -2
- package/src/components/formField/fieldset/Fieldset.stories.tsx +18 -3
- package/src/components/formField/inputs/radio/RadioButtonGroup.tsx +1 -1
- package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +3 -1
- package/src/components/formField/inputs/textArea/TextArea.stories.tsx +10 -5
- package/src/components/formField/inputs/time/TimeInput.test.tsx +2 -0
- package/src/components/formField/inputs/time/TimeInput.tsx +4 -4
- package/src/components/heading/Heading.stories.tsx +12 -3
- package/src/components/modal/Modal.stories.tsx +15 -5
- package/src/components/progress/Progress.stories.tsx +3 -1
- package/src/components/section/Section.stories.tsx +198 -70
- package/src/components/section/Section.test.tsx +14 -8
- package/src/components/section/Section.tsx +7 -15
- package/src/components/sidebarNav/SidebarNavItem.tsx +27 -25
- package/src/components/sidebarNav/SidebarNavRailItem.tsx +34 -35
- package/src/components/sidebarNav/useControllableBoolean.ts +3 -1
- package/src/components/table/Table.stories.tsx +16 -12
- package/src/components/table/Table.test.tsx +10 -4
- package/src/components/table/Table.tsx +15 -5
- package/src/components/table/cellRenderers/ComboboxCellRenderer.tsx +19 -8
- package/src/components/table/columnFilters/TimeFilter/TimeFilter.tsx +3 -1
- package/src/components/table/tableControls/HideColumnsDropdown.tsx +3 -1
- package/src/components/table/tableControls/TableControls.tsx +3 -1
- package/src/components/tag/Tag.test.tsx +40 -8
- package/src/components/tagList/TagList.test.tsx +9 -3
- package/src/components/tagList/TagList.tsx +12 -4
- package/src/components/treeRow/TreeRowSection.tsx +3 -1
- package/src/components/userDropdown/UserDropdown.stories.tsx +66 -22
- package/src/components/userDropdown/internal/UserDropdownCollapsibleSection.tsx +3 -1
- package/src/utils/getTextContrast.ts +6 -2
- package/src/utils/hooks/useElementWidth.ts +3 -1
- package/src/utils/hooks/useMeasuredChildWidths.ts +6 -2
- package/src/utils/hooks/useRovingFocus.ts +12 -4
- 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)
|
|
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
|
@@ -372,11 +372,15 @@ const LiveOverflowCounterTemplate = () => {
|
|
|
372
372
|
|
|
373
373
|
const addItem = () => {
|
|
374
374
|
const next = extraItems[items.length - initialItems.length];
|
|
375
|
-
if (next)
|
|
375
|
+
if (next) {
|
|
376
|
+
setItems(prev => [...prev, next]);
|
|
377
|
+
}
|
|
376
378
|
};
|
|
377
379
|
|
|
378
380
|
const removeItem = () => {
|
|
379
|
-
if (items.length > 1)
|
|
381
|
+
if (items.length > 1) {
|
|
382
|
+
setItems(prev => prev.slice(0, -1));
|
|
383
|
+
}
|
|
380
384
|
};
|
|
381
385
|
|
|
382
386
|
return (
|
|
@@ -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)
|
|
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'))
|
|
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'))
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
else if (this.classList.contains('ds-
|
|
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'))
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
else if (this.classList.contains('ds-
|
|
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
|
|
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(
|
|
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)
|
|
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)
|
|
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))
|
|
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)
|
|
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)
|
|
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
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
9
|
-
|
|
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
|
-
?
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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))
|
|
142
|
-
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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)
|
|
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))
|
|
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)
|
|
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))
|
|
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))
|
|
45
|
+
if (Number.isNaN(nextYear)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
44
48
|
|
|
45
49
|
onMonthChange(new Date(nextYear, month.getMonth(), 1));
|
|
46
50
|
};
|
|
@@ -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 ?
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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={() => {}}>
|
|
454
|
-
|
|
455
|
-
|
|
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)
|
|
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)
|
|
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)
|
|
600
|
+
if (!removedItem.fieldId) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
597
603
|
updateFieldValue(removedItem.fieldId, '');
|
|
598
604
|
},
|
|
599
605
|
}];
|