@arbor-education/design-system.components 0.21.1 → 0.23.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.
- package/CHANGELOG.md +22 -0
- package/component-library.md +77 -14
- package/dist/components/articleCard/ArticleCard.d.ts +2 -2
- package/dist/components/articleCard/ArticleCard.d.ts.map +1 -1
- package/dist/components/articleCard/ArticleCard.js +3 -3
- package/dist/components/articleCard/ArticleCard.js.map +1 -1
- package/dist/components/articleCard/ArticleCard.stories.d.ts +11 -3
- package/dist/components/articleCard/ArticleCard.stories.d.ts.map +1 -1
- package/dist/components/articleCard/ArticleCard.stories.js +16 -11
- package/dist/components/articleCard/ArticleCard.stories.js.map +1 -1
- package/dist/components/combobox/Combobox.js +1 -1
- package/dist/components/combobox/Combobox.js.map +1 -1
- package/dist/components/combobox/Combobox.stories.d.ts +4 -0
- package/dist/components/combobox/Combobox.stories.d.ts.map +1 -1
- package/dist/components/combobox/Combobox.stories.js +144 -12
- package/dist/components/combobox/Combobox.stories.js.map +1 -1
- package/dist/components/combobox/Combobox.test.js +22 -0
- package/dist/components/combobox/Combobox.test.js.map +1 -1
- package/dist/components/combobox/ComboboxButtonTrigger.d.ts +4 -4
- package/dist/components/combobox/ComboboxButtonTrigger.d.ts.map +1 -1
- package/dist/components/combobox/ComboboxButtonTrigger.js +35 -40
- package/dist/components/combobox/ComboboxButtonTrigger.js.map +1 -1
- package/dist/components/combobox/ComboboxTrigger.d.ts.map +1 -1
- package/dist/components/combobox/ComboboxTrigger.js +11 -4
- package/dist/components/combobox/ComboboxTrigger.js.map +1 -1
- package/dist/components/combobox/useVisibleTriggerTags.d.ts +21 -0
- package/dist/components/combobox/useVisibleTriggerTags.d.ts.map +1 -0
- package/dist/components/combobox/useVisibleTriggerTags.js +46 -0
- package/dist/components/combobox/useVisibleTriggerTags.js.map +1 -0
- package/dist/components/combobox/useVisibleTriggerTags.test.d.ts +2 -0
- package/dist/components/combobox/useVisibleTriggerTags.test.d.ts.map +1 -0
- package/dist/components/combobox/useVisibleTriggerTags.test.js +81 -0
- package/dist/components/combobox/useVisibleTriggerTags.test.js.map +1 -0
- package/dist/components/filterBar/FilterBar.d.ts +71 -0
- package/dist/components/filterBar/FilterBar.d.ts.map +1 -0
- package/dist/components/filterBar/FilterBar.js +89 -0
- package/dist/components/filterBar/FilterBar.js.map +1 -0
- package/dist/components/filterBar/FilterBar.stories.d.ts +170 -0
- package/dist/components/filterBar/FilterBar.stories.d.ts.map +1 -0
- package/dist/components/filterBar/FilterBar.stories.js +894 -0
- package/dist/components/filterBar/FilterBar.stories.js.map +1 -0
- package/dist/components/filterBar/FilterBar.test.d.ts +2 -0
- package/dist/components/filterBar/FilterBar.test.d.ts.map +1 -0
- package/dist/components/filterBar/FilterBar.test.js +164 -0
- package/dist/components/filterBar/FilterBar.test.js.map +1 -0
- package/dist/components/icon/allowedIcons.d.ts +1 -0
- package/dist/components/icon/allowedIcons.d.ts.map +1 -1
- package/dist/components/icon/allowedIcons.js +2 -1
- package/dist/components/icon/allowedIcons.js.map +1 -1
- package/dist/components/iconText/IconText.d.ts +43 -0
- package/dist/components/iconText/IconText.d.ts.map +1 -0
- package/dist/components/iconText/IconText.js +29 -0
- package/dist/components/iconText/IconText.js.map +1 -0
- package/dist/components/{icoText/IcoText.stories.d.ts → iconText/IconText.stories.d.ts} +8 -9
- package/dist/components/iconText/IconText.stories.d.ts.map +1 -0
- package/dist/components/{icoText/IcoText.stories.js → iconText/IconText.stories.js} +81 -81
- package/dist/components/iconText/IconText.stories.js.map +1 -0
- package/dist/components/iconText/IconText.test.d.ts +2 -0
- package/dist/components/iconText/IconText.test.d.ts.map +1 -0
- package/dist/components/{icoText/IcoText.test.js → iconText/IconText.test.js} +6 -6
- package/dist/components/iconText/IconText.test.js.map +1 -0
- package/dist/components/modal/Modal.d.ts +1 -0
- package/dist/components/modal/Modal.d.ts.map +1 -1
- package/dist/components/modal/Modal.js +2 -2
- package/dist/components/modal/Modal.js.map +1 -1
- package/dist/components/table/cellRenderers/ComboboxCellRenderer.test.d.ts.map +1 -1
- package/dist/components/table/cellRenderers/ComboboxCellRenderer.test.js +13 -2
- package/dist/components/table/cellRenderers/ComboboxCellRenderer.test.js.map +1 -1
- package/dist/components/tag/Tag.d.ts +14 -1
- package/dist/components/tag/Tag.d.ts.map +1 -1
- package/dist/components/tag/Tag.js +9 -3
- package/dist/components/tag/Tag.js.map +1 -1
- package/dist/components/tag/Tag.stories.d.ts +1 -1
- package/dist/components/tag/Tag.stories.d.ts.map +1 -1
- package/dist/components/tag/Tag.stories.js +3 -3
- package/dist/components/tag/Tag.stories.js.map +1 -1
- package/dist/components/tag/Tag.test.js +36 -5
- package/dist/components/tag/Tag.test.js.map +1 -1
- package/dist/components/tagList/TagList.d.ts +49 -0
- package/dist/components/tagList/TagList.d.ts.map +1 -0
- package/dist/components/tagList/TagList.js +114 -0
- package/dist/components/tagList/TagList.js.map +1 -0
- package/dist/components/tagList/TagList.stories.d.ts +130 -0
- package/dist/components/tagList/TagList.stories.d.ts.map +1 -0
- package/dist/components/tagList/TagList.stories.js +443 -0
- package/dist/components/tagList/TagList.stories.js.map +1 -0
- package/dist/components/{icoText/IcoText.test.d.ts → tagList/TagList.test.d.ts} +1 -1
- package/dist/components/tagList/TagList.test.d.ts.map +1 -0
- package/dist/components/tagList/TagList.test.js +246 -0
- package/dist/components/tagList/TagList.test.js.map +1 -0
- package/dist/components/tagList/useTagListCollapsedLayout.d.ts +19 -0
- package/dist/components/tagList/useTagListCollapsedLayout.d.ts.map +1 -0
- package/dist/components/tagList/useTagListCollapsedLayout.js +48 -0
- package/dist/components/tagList/useTagListCollapsedLayout.js.map +1 -0
- package/dist/components/tagList/useVisibleTags.d.ts +18 -0
- package/dist/components/tagList/useVisibleTags.d.ts.map +1 -0
- package/dist/components/tagList/useVisibleTags.js +41 -0
- package/dist/components/tagList/useVisibleTags.js.map +1 -0
- package/dist/index.css +272 -13
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/hooks/useElementWidth.d.ts.map +1 -0
- package/dist/{components/combobox → utils/hooks}/useElementWidth.js +0 -1
- package/dist/utils/hooks/useElementWidth.js.map +1 -0
- package/dist/utils/hooks/useMeasuredChildWidths.d.ts +8 -0
- package/dist/utils/hooks/useMeasuredChildWidths.d.ts.map +1 -0
- package/dist/utils/hooks/useMeasuredChildWidths.js +26 -0
- package/dist/utils/hooks/useMeasuredChildWidths.js.map +1 -0
- package/dist/utils/hooks/useRovingFocus.d.ts +18 -0
- package/dist/utils/hooks/useRovingFocus.d.ts.map +1 -0
- package/dist/utils/hooks/useRovingFocus.js +130 -0
- package/dist/utils/hooks/useRovingFocus.js.map +1 -0
- package/dist/utils/hooks/useRovingFocus.test.d.ts +2 -0
- package/dist/utils/hooks/useRovingFocus.test.d.ts.map +1 -0
- package/dist/utils/hooks/useRovingFocus.test.js +59 -0
- package/dist/utils/hooks/useRovingFocus.test.js.map +1 -0
- package/dist/utils/spacedWidths.d.ts +3 -0
- package/dist/utils/spacedWidths.d.ts.map +1 -0
- package/dist/utils/spacedWidths.js +28 -0
- package/dist/utils/spacedWidths.js.map +1 -0
- package/dist/utils/spacedWidths.test.d.ts +2 -0
- package/dist/utils/spacedWidths.test.d.ts.map +1 -0
- package/dist/utils/spacedWidths.test.js +17 -0
- package/dist/utils/spacedWidths.test.js.map +1 -0
- package/package.json +1 -1
- package/src/components/articleCard/ArticleCard.stories.tsx +17 -12
- package/src/components/articleCard/ArticleCard.tsx +9 -9
- package/src/components/combobox/Combobox.stories.tsx +186 -12
- package/src/components/combobox/Combobox.test.tsx +53 -0
- package/src/components/combobox/Combobox.tsx +3 -3
- package/src/components/combobox/ComboboxButtonTrigger.tsx +52 -56
- package/src/components/combobox/ComboboxTrigger.tsx +19 -16
- package/src/components/combobox/combobox.scss +8 -3
- package/src/components/combobox/useVisibleTriggerTags.test.tsx +91 -0
- package/src/components/combobox/useVisibleTriggerTags.ts +83 -0
- package/src/components/filterBar/FilterBar.stories.tsx +1199 -0
- package/src/components/filterBar/FilterBar.test.tsx +248 -0
- package/src/components/filterBar/FilterBar.tsx +298 -0
- package/src/components/filterBar/filterBar.scss +143 -0
- package/src/components/icon/allowedIcons.tsx +3 -1
- package/src/components/{icoText/IcoText.stories.tsx → iconText/IconText.stories.tsx} +112 -112
- package/src/components/{icoText/IcoText.test.tsx → iconText/IconText.test.tsx} +10 -10
- package/src/components/{icoText/IcoText.tsx → iconText/IconText.tsx} +27 -20
- package/src/components/modal/Modal.tsx +5 -1
- package/src/components/table/cellRenderers/ComboboxCellRenderer.test.tsx +20 -3
- package/src/components/tag/Tag.stories.tsx +4 -4
- package/src/components/tag/Tag.test.tsx +62 -5
- package/src/components/tag/Tag.tsx +61 -3
- package/src/components/tag/tag.scss +80 -9
- package/src/components/tagList/TagList.stories.tsx +564 -0
- package/src/components/tagList/TagList.test.tsx +342 -0
- package/src/components/tagList/TagList.tsx +296 -0
- package/src/components/tagList/tagList.scss +56 -0
- package/src/components/tagList/useTagListCollapsedLayout.ts +83 -0
- package/src/components/tagList/useVisibleTags.ts +74 -0
- package/src/index.scss +3 -1
- package/src/index.ts +13 -1
- package/src/tokens.scss +3 -1
- package/src/{components/combobox → utils/hooks}/useElementWidth.ts +0 -1
- package/src/utils/hooks/useMeasuredChildWidths.ts +39 -0
- package/src/utils/hooks/useRovingFocus.test.tsx +105 -0
- package/src/utils/hooks/useRovingFocus.ts +163 -0
- package/src/utils/spacedWidths.test.ts +20 -0
- package/src/utils/spacedWidths.ts +37 -0
- package/dist/components/combobox/useElementWidth.d.ts.map +0 -1
- package/dist/components/combobox/useElementWidth.js.map +0 -1
- package/dist/components/combobox/useVisibleChips.d.ts +0 -21
- package/dist/components/combobox/useVisibleChips.d.ts.map +0 -1
- package/dist/components/combobox/useVisibleChips.js +0 -59
- package/dist/components/combobox/useVisibleChips.js.map +0 -1
- package/dist/components/combobox/useVisibleChips.test.d.ts +0 -2
- package/dist/components/combobox/useVisibleChips.test.d.ts.map +0 -1
- package/dist/components/combobox/useVisibleChips.test.js +0 -81
- package/dist/components/combobox/useVisibleChips.test.js.map +0 -1
- package/dist/components/icoText/IcoText.d.ts +0 -37
- package/dist/components/icoText/IcoText.d.ts.map +0 -1
- package/dist/components/icoText/IcoText.js +0 -29
- package/dist/components/icoText/IcoText.js.map +0 -1
- package/dist/components/icoText/IcoText.stories.d.ts.map +0 -1
- package/dist/components/icoText/IcoText.stories.js.map +0 -1
- package/dist/components/icoText/IcoText.test.d.ts.map +0 -1
- package/dist/components/icoText/IcoText.test.js.map +0 -1
- package/src/components/combobox/useVisibleChips.test.tsx +0 -91
- package/src/components/combobox/useVisibleChips.ts +0 -100
- /package/dist/{components/combobox → utils/hooks}/useElementWidth.d.ts +0 -0
- /package/src/components/{icoText/icoText.scss → iconText/iconText.scss} +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
type UseRovingFocusOptions<Target extends string> = {
|
|
3
|
+
targets: readonly Target[];
|
|
4
|
+
returnFocusRef?: RefObject<HTMLElement | null>;
|
|
5
|
+
onActiveTargetChange?: (target: Target | null) => void;
|
|
6
|
+
onDeleteKey?: (target: Target, event: React.KeyboardEvent<HTMLElement>) => boolean | void;
|
|
7
|
+
};
|
|
8
|
+
export declare const useRovingFocus: <Target extends string>({ targets, returnFocusRef, onActiveTargetChange, onDeleteKey, }: UseRovingFocusOptions<Target>) => {
|
|
9
|
+
activeTarget: Target | null;
|
|
10
|
+
focusTarget: (target: Target) => void;
|
|
11
|
+
getTargetTabIndex: (target: Target) => 0 | -1;
|
|
12
|
+
handleTargetKeyDown: (event: React.KeyboardEvent<HTMLElement>, target: Target) => void;
|
|
13
|
+
queueFocusRecovery: (target: Target) => void;
|
|
14
|
+
registerTarget: (target: Target, node: HTMLElement | null) => void;
|
|
15
|
+
setActiveTarget: (target: Target | null) => void;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=useRovingFocus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRovingFocus.d.ts","sourceRoot":"","sources":["../../../src/utils/hooks/useRovingFocus.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6D,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAMlG,KAAK,qBAAqB,CAAC,MAAM,SAAS,MAAM,IAAI;IAClD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,cAAc,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC/C,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACvD,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC,KAAK,OAAO,GAAG,IAAI,CAAC;CAC3F,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,SAAS,MAAM,EAAE,iEAKnD,qBAAqB,CAAC,MAAM,CAAC;;0BAaW,MAAM;gCAkHA,MAAM,KAAG,CAAC,GAAG,CAAC,CAAC;iCAlDd,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC,UAAU,MAAM;iCA9ChD,MAAM;6BATV,MAAM,QAAQ,WAAW,GAAG,IAAI;8BAd/B,MAAM,GAAG,IAAI;CAwI3D,CAAC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
export const useRovingFocus = ({ targets, returnFocusRef, onActiveTargetChange, onDeleteKey, }) => {
|
|
3
|
+
const [activeTarget, setActiveTargetState] = useState(null);
|
|
4
|
+
const targetRefs = useRef(new Map());
|
|
5
|
+
const pendingFocusRecoveryRef = useRef(null);
|
|
6
|
+
const returnFocusRefRef = useRef(returnFocusRef);
|
|
7
|
+
returnFocusRefRef.current = returnFocusRef;
|
|
8
|
+
const setActiveTarget = useCallback((target) => {
|
|
9
|
+
setActiveTargetState(target);
|
|
10
|
+
onActiveTargetChange?.(target);
|
|
11
|
+
}, [onActiveTargetChange]);
|
|
12
|
+
const focusTarget = useCallback((target) => {
|
|
13
|
+
targetRefs.current.get(target)?.focus();
|
|
14
|
+
}, []);
|
|
15
|
+
const moveFocus = useCallback((target) => {
|
|
16
|
+
focusTarget(target);
|
|
17
|
+
setActiveTarget(target);
|
|
18
|
+
}, [focusTarget, setActiveTarget]);
|
|
19
|
+
const registerTarget = useCallback((target, node) => {
|
|
20
|
+
if (node) {
|
|
21
|
+
targetRefs.current.set(target, node);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
targetRefs.current.delete(target);
|
|
25
|
+
}
|
|
26
|
+
}, []);
|
|
27
|
+
const queueFocusRecovery = useCallback((target) => {
|
|
28
|
+
const targetIndex = targets.indexOf(target);
|
|
29
|
+
if (targetIndex < 0)
|
|
30
|
+
return;
|
|
31
|
+
pendingFocusRecoveryRef.current = { targetIndex };
|
|
32
|
+
}, [targets]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (targets.length === 0) {
|
|
35
|
+
if (activeTarget !== null) {
|
|
36
|
+
setActiveTarget(null);
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (activeTarget === null || !targets.includes(activeTarget)) {
|
|
41
|
+
setActiveTarget(targets[0] ?? null);
|
|
42
|
+
}
|
|
43
|
+
}, [activeTarget, setActiveTarget, targets]);
|
|
44
|
+
useLayoutEffect(() => {
|
|
45
|
+
const pendingFocusRecovery = pendingFocusRecoveryRef.current;
|
|
46
|
+
if (!pendingFocusRecovery)
|
|
47
|
+
return;
|
|
48
|
+
pendingFocusRecoveryRef.current = null;
|
|
49
|
+
if (targets.length > 0) {
|
|
50
|
+
const nextTarget = targets[Math.min(pendingFocusRecovery.targetIndex, targets.length - 1)];
|
|
51
|
+
if (nextTarget !== undefined) {
|
|
52
|
+
moveFocus(nextTarget);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
returnFocusRef?.current?.focus();
|
|
57
|
+
setActiveTarget(null);
|
|
58
|
+
}, [moveFocus, returnFocusRef, setActiveTarget, targets]);
|
|
59
|
+
useLayoutEffect(() => () => {
|
|
60
|
+
if (!pendingFocusRecoveryRef.current)
|
|
61
|
+
return;
|
|
62
|
+
pendingFocusRecoveryRef.current = null;
|
|
63
|
+
returnFocusRefRef.current?.current?.focus();
|
|
64
|
+
}, []);
|
|
65
|
+
const handleTargetKeyDown = useCallback((event, target) => {
|
|
66
|
+
const targetIndex = targets.indexOf(target);
|
|
67
|
+
if (targetIndex < 0)
|
|
68
|
+
return;
|
|
69
|
+
switch (event.key) {
|
|
70
|
+
case 'ArrowLeft':
|
|
71
|
+
case 'ArrowUp': {
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
const previousTarget = targets[Math.max(0, targetIndex - 1)];
|
|
74
|
+
if (previousTarget !== undefined) {
|
|
75
|
+
moveFocus(previousTarget);
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case 'ArrowRight':
|
|
80
|
+
case 'ArrowDown': {
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
const nextTarget = targets[Math.min(targets.length - 1, targetIndex + 1)];
|
|
83
|
+
if (nextTarget !== undefined) {
|
|
84
|
+
moveFocus(nextTarget);
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case 'Home':
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
if (targets[0] !== undefined) {
|
|
91
|
+
moveFocus(targets[0]);
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
case 'End': {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
const lastTarget = targets[targets.length - 1];
|
|
97
|
+
if (lastTarget !== undefined) {
|
|
98
|
+
moveFocus(lastTarget);
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case 'Backspace':
|
|
103
|
+
case 'Delete': {
|
|
104
|
+
const shouldRecoverFocus = onDeleteKey?.(target, event);
|
|
105
|
+
if (shouldRecoverFocus) {
|
|
106
|
+
queueFocusRecovery(target);
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
default:
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}, [moveFocus, onDeleteKey, queueFocusRecovery, targets]);
|
|
114
|
+
const getTargetTabIndex = useCallback((target) => {
|
|
115
|
+
if (activeTarget === null) {
|
|
116
|
+
return targets[0] === target ? 0 : -1;
|
|
117
|
+
}
|
|
118
|
+
return activeTarget === target ? 0 : -1;
|
|
119
|
+
}, [activeTarget, targets]);
|
|
120
|
+
return {
|
|
121
|
+
activeTarget,
|
|
122
|
+
focusTarget,
|
|
123
|
+
getTargetTabIndex,
|
|
124
|
+
handleTargetKeyDown,
|
|
125
|
+
queueFocusRecovery,
|
|
126
|
+
registerTarget,
|
|
127
|
+
setActiveTarget,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=useRovingFocus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRovingFocus.js","sourceRoot":"","sources":["../../../src/utils/hooks/useRovingFocus.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRovingFocus.test.d.ts","sourceRoot":"","sources":["../../../src/utils/hooks/useRovingFocus.test.tsx"],"names":[],"mappings":"AAAA,OAAO,kCAAkC,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import '@testing-library/jest-dom/vitest';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import { useRef, useState } from 'react';
|
|
6
|
+
import { describe, expect, test } from 'vitest';
|
|
7
|
+
import { useRovingFocus } from './useRovingFocus.js';
|
|
8
|
+
const labels = {
|
|
9
|
+
one: 'One',
|
|
10
|
+
two: 'Two',
|
|
11
|
+
three: 'Three',
|
|
12
|
+
};
|
|
13
|
+
const RovingExample = ({ initialTargets = ['one', 'two', 'three'], returnFocusOnEmpty = false, }) => {
|
|
14
|
+
const [targets, setTargets] = useState(initialTargets);
|
|
15
|
+
const returnFocusRef = useRef(null);
|
|
16
|
+
const { getTargetTabIndex, handleTargetKeyDown, registerTarget, } = useRovingFocus({
|
|
17
|
+
targets,
|
|
18
|
+
returnFocusRef,
|
|
19
|
+
onDeleteKey: (target) => {
|
|
20
|
+
setTargets(currentTargets => currentTargets.filter(currentTarget => currentTarget !== target));
|
|
21
|
+
return true;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
return (_jsxs(_Fragment, { children: [targets.map(target => (_jsx("button", { ref: node => registerTarget(target, node), tabIndex: getTargetTabIndex(target), type: "button", onKeyDown: event => handleTargetKeyDown(event, target), children: labels[target] }, target))), returnFocusOnEmpty && (_jsx("button", { ref: returnFocusRef, type: "button", children: "Return target" }))] }));
|
|
25
|
+
};
|
|
26
|
+
describe('useRovingFocus', () => {
|
|
27
|
+
test('moves focus through targets with arrow keys and Home/End', async () => {
|
|
28
|
+
const user = userEvent.setup();
|
|
29
|
+
render(_jsx(RovingExample, {}));
|
|
30
|
+
expect(screen.getByRole('button', { name: 'One' })).toHaveAttribute('tabindex', '0');
|
|
31
|
+
expect(screen.getByRole('button', { name: 'Two' })).toHaveAttribute('tabindex', '-1');
|
|
32
|
+
await user.tab();
|
|
33
|
+
expect(screen.getByRole('button', { name: 'One' })).toHaveFocus();
|
|
34
|
+
await user.keyboard('{ArrowRight}');
|
|
35
|
+
expect(screen.getByRole('button', { name: 'Two' })).toHaveFocus();
|
|
36
|
+
expect(screen.getByRole('button', { name: 'Two' })).toHaveAttribute('tabindex', '0');
|
|
37
|
+
await user.keyboard('{End}');
|
|
38
|
+
expect(screen.getByRole('button', { name: 'Three' })).toHaveFocus();
|
|
39
|
+
await user.keyboard('{Home}');
|
|
40
|
+
expect(screen.getByRole('button', { name: 'One' })).toHaveFocus();
|
|
41
|
+
});
|
|
42
|
+
test('recovers focus to the next target after deletion', async () => {
|
|
43
|
+
const user = userEvent.setup();
|
|
44
|
+
render(_jsx(RovingExample, { initialTargets: ['one', 'two'] }));
|
|
45
|
+
await user.tab();
|
|
46
|
+
expect(screen.getByRole('button', { name: 'One' })).toHaveFocus();
|
|
47
|
+
await user.keyboard('{Delete}');
|
|
48
|
+
expect(screen.getByRole('button', { name: 'Two' })).toHaveFocus();
|
|
49
|
+
});
|
|
50
|
+
test('returns focus to the fallback element when all targets are removed', async () => {
|
|
51
|
+
const user = userEvent.setup();
|
|
52
|
+
render(_jsx(RovingExample, { initialTargets: ['one'], returnFocusOnEmpty: true }));
|
|
53
|
+
await user.tab();
|
|
54
|
+
expect(screen.getByRole('button', { name: 'One' })).toHaveFocus();
|
|
55
|
+
await user.keyboard('{Delete}');
|
|
56
|
+
expect(screen.getByRole('button', { name: 'Return target' })).toHaveFocus();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=useRovingFocus.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRovingFocus.test.js","sourceRoot":"","sources":["../../../src/utils/hooks/useRovingFocus.test.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spacedWidths.d.ts","sourceRoot":"","sources":["../../src/utils/spacedWidths.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,GAC1B,QAAQ,MAAM,EAAE,EAChB,KAAK,MAAM,EACX,eAAc,MAAsB,KACnC,MAaF,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,gBAAgB,MAAM,EACtB,QAAQ,MAAM,EAAE,EAChB,KAAK,MAAM,KACV,MAaF,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const sumSpacedWidths = (widths, gap, visibleCount = widths.length) => {
|
|
2
|
+
const boundedCount = Math.max(0, Math.min(visibleCount, widths.length));
|
|
3
|
+
if (boundedCount === 0)
|
|
4
|
+
return 0;
|
|
5
|
+
let usedWidth = 0;
|
|
6
|
+
for (let index = 0; index < boundedCount; index += 1) {
|
|
7
|
+
usedWidth += widths[index] ?? 0;
|
|
8
|
+
if (index > 0) {
|
|
9
|
+
usedWidth += gap;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return usedWidth;
|
|
13
|
+
};
|
|
14
|
+
export const fitSpacedWidths = (availableWidth, widths, gap) => {
|
|
15
|
+
if (availableWidth <= 0 || widths.length === 0)
|
|
16
|
+
return 0;
|
|
17
|
+
let usedWidth = 0;
|
|
18
|
+
let visibleCount = 0;
|
|
19
|
+
for (let index = 0; index < widths.length; index += 1) {
|
|
20
|
+
const requiredWidth = (widths[index] ?? 0) + (index > 0 ? gap : 0);
|
|
21
|
+
if (usedWidth + requiredWidth > availableWidth)
|
|
22
|
+
break;
|
|
23
|
+
usedWidth += requiredWidth;
|
|
24
|
+
visibleCount += 1;
|
|
25
|
+
}
|
|
26
|
+
return visibleCount;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=spacedWidths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spacedWidths.js","sourceRoot":"","sources":["../../src/utils/spacedWidths.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,MAAgB,EAChB,GAAW,EACX,eAAuB,MAAM,CAAC,MAAM,EAC5B,EAAE;IACV,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACxE,IAAI,YAAY,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAEjC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,YAAY,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACrD,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,SAAS,IAAI,GAAG,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,cAAsB,EACtB,MAAgB,EAChB,GAAW,EACH,EAAE;IACV,IAAI,cAAc,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAEzD,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACtD,MAAM,aAAa,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACnE,IAAI,SAAS,GAAG,aAAa,GAAG,cAAc;YAAE,MAAM;QACtD,SAAS,IAAI,aAAa,CAAC;QAC3B,YAAY,IAAI,CAAC,CAAC;IACpB,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spacedWidths.test.d.ts","sourceRoot":"","sources":["../../src/utils/spacedWidths.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { fitSpacedWidths, sumSpacedWidths } from './spacedWidths.js';
|
|
3
|
+
describe('spacedWidths', () => {
|
|
4
|
+
test('sums widths with gaps between visible items', () => {
|
|
5
|
+
expect(sumSpacedWidths([40, 50, 60], 8, 2)).toBe(98);
|
|
6
|
+
});
|
|
7
|
+
test('returns zero summed width when no items are visible', () => {
|
|
8
|
+
expect(sumSpacedWidths([40, 50, 60], 8, 0)).toBe(0);
|
|
9
|
+
});
|
|
10
|
+
test('fits the number of items that can be shown within the available width', () => {
|
|
11
|
+
expect(fitSpacedWidths(102, [40, 50, 60], 8)).toBe(2);
|
|
12
|
+
});
|
|
13
|
+
test('returns zero fitted items when no width is available', () => {
|
|
14
|
+
expect(fitSpacedWidths(0, [40, 50], 8)).toBe(0);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
//# sourceMappingURL=spacedWidths.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spacedWidths.test.js","sourceRoot":"","sources":["../../src/utils/spacedWidths.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAErE,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,IAAI,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,uEAAuE,EAAE,GAAG,EAAE;QACjF,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -37,7 +37,7 @@ const USAGE_GUIDANCE = [
|
|
|
37
37
|
'|---|---|',
|
|
38
38
|
'| Displaying a metric or statistic | [`KPICard`](?path=/docs/components-card-kpicard--docs) |',
|
|
39
39
|
'| Full control over interior layout | [`Card`](?path=/docs/components-card--docs) directly |',
|
|
40
|
-
'| Icon + text without a card shell | [`
|
|
40
|
+
'| Icon + text without a card shell | [`IconText`](?path=/docs/components-icontext--docs) |',
|
|
41
41
|
'| Selecting from a list (radio/checkbox semantics) | `RadioGroup` or selection list pattern |',
|
|
42
42
|
].join('\n');
|
|
43
43
|
|
|
@@ -64,7 +64,7 @@ const DEVELOPER_NOTES = [
|
|
|
64
64
|
'### Accessibility',
|
|
65
65
|
'',
|
|
66
66
|
'- **Linked mode**: the `<a>` wrapping `title` is the accessible link target.',
|
|
67
|
-
'
|
|
67
|
+
' Use self-descriptive link text in `title`, because the outer `aria-label` does not rename the inner link.',
|
|
68
68
|
'- **Shell mode**: `aria-label` or `aria-labelledby` is required when `onClick` is provided.',
|
|
69
69
|
'- **Decorative icons**: omit `iconScreenReaderText` — the icon is `aria-hidden="true"` automatically.',
|
|
70
70
|
'',
|
|
@@ -81,7 +81,7 @@ const DEVELOPER_NOTES = [
|
|
|
81
81
|
const RELATED_COMPONENTS = [
|
|
82
82
|
'## Related components',
|
|
83
83
|
'',
|
|
84
|
-
'[Card](?path=/docs/components-card--docs) · [KPICard](?path=/docs/components-card-kpicard--docs) · [
|
|
84
|
+
'[Card](?path=/docs/components-card--docs) · [KPICard](?path=/docs/components-card-kpicard--docs) · [IconText](?path=/docs/components-icontext--docs) · [Tag](?path=/docs/components-tag--docs)',
|
|
85
85
|
].join('\n');
|
|
86
86
|
|
|
87
87
|
const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
|
|
@@ -137,7 +137,7 @@ const meta = {
|
|
|
137
137
|
'icon': {
|
|
138
138
|
control: { type: 'select' },
|
|
139
139
|
options: [undefined, 'eye', 'guardians', 'date', 'book-open', 'file', 'settings', 'info', 'triangle-alert'],
|
|
140
|
-
description: 'Icon displayed in the left rail via `
|
|
140
|
+
description: 'Icon displayed in the left rail via `IconText.Icon`. Omit for icon-free layout.',
|
|
141
141
|
table: { type: { summary: 'IconName' } },
|
|
142
142
|
},
|
|
143
143
|
'iconColor': {
|
|
@@ -176,8 +176,13 @@ const meta = {
|
|
|
176
176
|
description: 'Accessible name. Required on interactive shell cards (when `onClick` is provided).',
|
|
177
177
|
table: { type: { summary: 'string' } },
|
|
178
178
|
},
|
|
179
|
-
'
|
|
179
|
+
'aria-labelledby': {
|
|
180
180
|
control: 'text',
|
|
181
|
+
description: 'Alternative accessible name for interactive shell cards when a visible label already exists in the card content.',
|
|
182
|
+
table: { type: { summary: 'string' } },
|
|
183
|
+
},
|
|
184
|
+
'href': {
|
|
185
|
+
control: false,
|
|
181
186
|
description: 'URL. Mutually exclusive with `onClick`. When provided with `title` + `!disabled`, the title renders as a navigable `<a>`.',
|
|
182
187
|
table: { type: { summary: 'string' } },
|
|
183
188
|
},
|
|
@@ -190,7 +195,7 @@ const meta = {
|
|
|
190
195
|
} satisfies Meta<typeof ArticleCard>;
|
|
191
196
|
|
|
192
197
|
export default meta;
|
|
193
|
-
type Story = StoryObj<typeof
|
|
198
|
+
type Story = StoryObj<typeof meta>;
|
|
194
199
|
|
|
195
200
|
// ---------------------------------------------------------------------------
|
|
196
201
|
// Helper: attach a per-story description to docs
|
|
@@ -356,7 +361,7 @@ function FullCompositionExample() {
|
|
|
356
361
|
iconColor="var(--color-semantic-warning-600)"
|
|
357
362
|
tagText="Pending review"
|
|
358
363
|
tagColor="yellow"
|
|
359
|
-
onClick={() =>
|
|
364
|
+
onClick={() => {}}
|
|
360
365
|
aria-label="Spring term behaviour report"
|
|
361
366
|
/>
|
|
362
367
|
);
|
|
@@ -375,7 +380,7 @@ export default FullCompositionExample;
|
|
|
375
380
|
iconColor="var(--color-semantic-warning-600)"
|
|
376
381
|
tagText="Pending review"
|
|
377
382
|
tagColor="yellow"
|
|
378
|
-
onClick={
|
|
383
|
+
onClick={() => {}}
|
|
379
384
|
aria-label="Spring term behaviour report"
|
|
380
385
|
/>
|
|
381
386
|
</div>
|
|
@@ -440,7 +445,7 @@ import { ArticleCard } from '@arbor-education/design-system.components';
|
|
|
440
445
|
function InteractiveShellExample() {
|
|
441
446
|
return (
|
|
442
447
|
<ArticleCard
|
|
443
|
-
onClick={() =>
|
|
448
|
+
onClick={() => {}}
|
|
444
449
|
aria-label="Year 10 progress report"
|
|
445
450
|
title="Year 10 progress report"
|
|
446
451
|
paragraph="Mid-year review — targets, attainment, and next steps."
|
|
@@ -456,7 +461,7 @@ export default InteractiveShellExample;
|
|
|
456
461
|
render: () => (
|
|
457
462
|
<div style={{ maxWidth: '400px' }}>
|
|
458
463
|
<ArticleCard
|
|
459
|
-
onClick={
|
|
464
|
+
onClick={() => {}}
|
|
460
465
|
aria-label="Year 10 progress report"
|
|
461
466
|
title="Year 10 progress report"
|
|
462
467
|
paragraph="Mid-year review — targets, attainment, and next steps."
|
|
@@ -498,7 +503,7 @@ export default DisabledExample;
|
|
|
498
503
|
render: () => (
|
|
499
504
|
<div style={{ maxWidth: '400px' }}>
|
|
500
505
|
<ArticleCard
|
|
501
|
-
onClick={
|
|
506
|
+
onClick={() => {}}
|
|
502
507
|
aria-label="Summer term report — not yet available"
|
|
503
508
|
title="Summer term report"
|
|
504
509
|
paragraph="This report will be available at the end of term."
|
|
@@ -508,7 +513,7 @@ export default DisabledExample;
|
|
|
508
513
|
</div>
|
|
509
514
|
),
|
|
510
515
|
},
|
|
511
|
-
'`disabled=true` dims the card and suppresses
|
|
516
|
+
'`disabled=true` dims the card and suppresses activation. In interactive shell mode, `onClick` no longer fires, but the card still renders with `role="button"`, `tabIndex={0}`, and `aria-disabled={true}`.',
|
|
512
517
|
);
|
|
513
518
|
|
|
514
519
|
export const LinkedDisabled: Story = withDescription(
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
|
-
import type {
|
|
2
|
+
import type { IconTextIconProps } from 'Components/iconText/IconText';
|
|
3
3
|
import type { TagColor } from 'Components/tag/Tag';
|
|
4
4
|
import { Tag } from 'Components/tag/Tag';
|
|
5
5
|
import type { IconName } from 'Components/icon/allowedIcons';
|
|
6
6
|
import { Card, getCardInteractionProps } from 'Components/card/Card';
|
|
7
|
-
import {
|
|
7
|
+
import { IconText } from 'Components/iconText/IconText';
|
|
8
8
|
|
|
9
9
|
type ArticleCardBaseProps = {
|
|
10
10
|
className?: string;
|
|
11
11
|
paragraph?: React.ReactNode;
|
|
12
12
|
icon?: IconName;
|
|
13
|
-
iconColor?:
|
|
13
|
+
iconColor?: IconTextIconProps['color'];
|
|
14
14
|
disabled?: boolean;
|
|
15
15
|
tagText?: string;
|
|
16
16
|
tagColor?: TagColor;
|
|
@@ -62,16 +62,16 @@ export const ArticleCard = (props: ArticleCardProps): React.JSX.Element => {
|
|
|
62
62
|
|
|
63
63
|
const content = (
|
|
64
64
|
<article className="ds-article-card">
|
|
65
|
-
<
|
|
65
|
+
<IconText>
|
|
66
66
|
{icon && (
|
|
67
|
-
<
|
|
67
|
+
<IconText.Icon
|
|
68
68
|
color={iconColor}
|
|
69
69
|
name={icon}
|
|
70
70
|
screenReaderText={iconScreenReaderText}
|
|
71
71
|
/>
|
|
72
72
|
)}
|
|
73
73
|
{title && (
|
|
74
|
-
<
|
|
74
|
+
<IconText.Heading>
|
|
75
75
|
{hasPrimaryLink
|
|
76
76
|
? (
|
|
77
77
|
<a className="ds-article-card__primary-link" href={href}>
|
|
@@ -79,11 +79,11 @@ export const ArticleCard = (props: ArticleCardProps): React.JSX.Element => {
|
|
|
79
79
|
</a>
|
|
80
80
|
)
|
|
81
81
|
: title}
|
|
82
|
-
</
|
|
82
|
+
</IconText.Heading>
|
|
83
83
|
)}
|
|
84
|
-
{paragraph && <
|
|
84
|
+
{paragraph && <IconText.Paragraph>{paragraph}</IconText.Paragraph>}
|
|
85
85
|
{tagText && <Tag color={tagColor}>{tagText}</Tag>}
|
|
86
|
-
</
|
|
86
|
+
</IconText>
|
|
87
87
|
</article>
|
|
88
88
|
);
|
|
89
89
|
|