@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.
Files changed (189) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/component-library.md +77 -14
  3. package/dist/components/articleCard/ArticleCard.d.ts +2 -2
  4. package/dist/components/articleCard/ArticleCard.d.ts.map +1 -1
  5. package/dist/components/articleCard/ArticleCard.js +3 -3
  6. package/dist/components/articleCard/ArticleCard.js.map +1 -1
  7. package/dist/components/articleCard/ArticleCard.stories.d.ts +11 -3
  8. package/dist/components/articleCard/ArticleCard.stories.d.ts.map +1 -1
  9. package/dist/components/articleCard/ArticleCard.stories.js +16 -11
  10. package/dist/components/articleCard/ArticleCard.stories.js.map +1 -1
  11. package/dist/components/combobox/Combobox.js +1 -1
  12. package/dist/components/combobox/Combobox.js.map +1 -1
  13. package/dist/components/combobox/Combobox.stories.d.ts +4 -0
  14. package/dist/components/combobox/Combobox.stories.d.ts.map +1 -1
  15. package/dist/components/combobox/Combobox.stories.js +144 -12
  16. package/dist/components/combobox/Combobox.stories.js.map +1 -1
  17. package/dist/components/combobox/Combobox.test.js +22 -0
  18. package/dist/components/combobox/Combobox.test.js.map +1 -1
  19. package/dist/components/combobox/ComboboxButtonTrigger.d.ts +4 -4
  20. package/dist/components/combobox/ComboboxButtonTrigger.d.ts.map +1 -1
  21. package/dist/components/combobox/ComboboxButtonTrigger.js +35 -40
  22. package/dist/components/combobox/ComboboxButtonTrigger.js.map +1 -1
  23. package/dist/components/combobox/ComboboxTrigger.d.ts.map +1 -1
  24. package/dist/components/combobox/ComboboxTrigger.js +11 -4
  25. package/dist/components/combobox/ComboboxTrigger.js.map +1 -1
  26. package/dist/components/combobox/useVisibleTriggerTags.d.ts +21 -0
  27. package/dist/components/combobox/useVisibleTriggerTags.d.ts.map +1 -0
  28. package/dist/components/combobox/useVisibleTriggerTags.js +46 -0
  29. package/dist/components/combobox/useVisibleTriggerTags.js.map +1 -0
  30. package/dist/components/combobox/useVisibleTriggerTags.test.d.ts +2 -0
  31. package/dist/components/combobox/useVisibleTriggerTags.test.d.ts.map +1 -0
  32. package/dist/components/combobox/useVisibleTriggerTags.test.js +81 -0
  33. package/dist/components/combobox/useVisibleTriggerTags.test.js.map +1 -0
  34. package/dist/components/filterBar/FilterBar.d.ts +71 -0
  35. package/dist/components/filterBar/FilterBar.d.ts.map +1 -0
  36. package/dist/components/filterBar/FilterBar.js +89 -0
  37. package/dist/components/filterBar/FilterBar.js.map +1 -0
  38. package/dist/components/filterBar/FilterBar.stories.d.ts +170 -0
  39. package/dist/components/filterBar/FilterBar.stories.d.ts.map +1 -0
  40. package/dist/components/filterBar/FilterBar.stories.js +894 -0
  41. package/dist/components/filterBar/FilterBar.stories.js.map +1 -0
  42. package/dist/components/filterBar/FilterBar.test.d.ts +2 -0
  43. package/dist/components/filterBar/FilterBar.test.d.ts.map +1 -0
  44. package/dist/components/filterBar/FilterBar.test.js +164 -0
  45. package/dist/components/filterBar/FilterBar.test.js.map +1 -0
  46. package/dist/components/icon/allowedIcons.d.ts +1 -0
  47. package/dist/components/icon/allowedIcons.d.ts.map +1 -1
  48. package/dist/components/icon/allowedIcons.js +2 -1
  49. package/dist/components/icon/allowedIcons.js.map +1 -1
  50. package/dist/components/iconText/IconText.d.ts +43 -0
  51. package/dist/components/iconText/IconText.d.ts.map +1 -0
  52. package/dist/components/iconText/IconText.js +29 -0
  53. package/dist/components/iconText/IconText.js.map +1 -0
  54. package/dist/components/{icoText/IcoText.stories.d.ts → iconText/IconText.stories.d.ts} +8 -9
  55. package/dist/components/iconText/IconText.stories.d.ts.map +1 -0
  56. package/dist/components/{icoText/IcoText.stories.js → iconText/IconText.stories.js} +81 -81
  57. package/dist/components/iconText/IconText.stories.js.map +1 -0
  58. package/dist/components/iconText/IconText.test.d.ts +2 -0
  59. package/dist/components/iconText/IconText.test.d.ts.map +1 -0
  60. package/dist/components/{icoText/IcoText.test.js → iconText/IconText.test.js} +6 -6
  61. package/dist/components/iconText/IconText.test.js.map +1 -0
  62. package/dist/components/modal/Modal.d.ts +1 -0
  63. package/dist/components/modal/Modal.d.ts.map +1 -1
  64. package/dist/components/modal/Modal.js +2 -2
  65. package/dist/components/modal/Modal.js.map +1 -1
  66. package/dist/components/table/cellRenderers/ComboboxCellRenderer.test.d.ts.map +1 -1
  67. package/dist/components/table/cellRenderers/ComboboxCellRenderer.test.js +13 -2
  68. package/dist/components/table/cellRenderers/ComboboxCellRenderer.test.js.map +1 -1
  69. package/dist/components/tag/Tag.d.ts +14 -1
  70. package/dist/components/tag/Tag.d.ts.map +1 -1
  71. package/dist/components/tag/Tag.js +9 -3
  72. package/dist/components/tag/Tag.js.map +1 -1
  73. package/dist/components/tag/Tag.stories.d.ts +1 -1
  74. package/dist/components/tag/Tag.stories.d.ts.map +1 -1
  75. package/dist/components/tag/Tag.stories.js +3 -3
  76. package/dist/components/tag/Tag.stories.js.map +1 -1
  77. package/dist/components/tag/Tag.test.js +36 -5
  78. package/dist/components/tag/Tag.test.js.map +1 -1
  79. package/dist/components/tagList/TagList.d.ts +49 -0
  80. package/dist/components/tagList/TagList.d.ts.map +1 -0
  81. package/dist/components/tagList/TagList.js +114 -0
  82. package/dist/components/tagList/TagList.js.map +1 -0
  83. package/dist/components/tagList/TagList.stories.d.ts +130 -0
  84. package/dist/components/tagList/TagList.stories.d.ts.map +1 -0
  85. package/dist/components/tagList/TagList.stories.js +443 -0
  86. package/dist/components/tagList/TagList.stories.js.map +1 -0
  87. package/dist/components/{icoText/IcoText.test.d.ts → tagList/TagList.test.d.ts} +1 -1
  88. package/dist/components/tagList/TagList.test.d.ts.map +1 -0
  89. package/dist/components/tagList/TagList.test.js +246 -0
  90. package/dist/components/tagList/TagList.test.js.map +1 -0
  91. package/dist/components/tagList/useTagListCollapsedLayout.d.ts +19 -0
  92. package/dist/components/tagList/useTagListCollapsedLayout.d.ts.map +1 -0
  93. package/dist/components/tagList/useTagListCollapsedLayout.js +48 -0
  94. package/dist/components/tagList/useTagListCollapsedLayout.js.map +1 -0
  95. package/dist/components/tagList/useVisibleTags.d.ts +18 -0
  96. package/dist/components/tagList/useVisibleTags.d.ts.map +1 -0
  97. package/dist/components/tagList/useVisibleTags.js +41 -0
  98. package/dist/components/tagList/useVisibleTags.js.map +1 -0
  99. package/dist/index.css +272 -13
  100. package/dist/index.css.map +1 -1
  101. package/dist/index.d.ts +4 -1
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +3 -1
  104. package/dist/index.js.map +1 -1
  105. package/dist/utils/hooks/useElementWidth.d.ts.map +1 -0
  106. package/dist/{components/combobox → utils/hooks}/useElementWidth.js +0 -1
  107. package/dist/utils/hooks/useElementWidth.js.map +1 -0
  108. package/dist/utils/hooks/useMeasuredChildWidths.d.ts +8 -0
  109. package/dist/utils/hooks/useMeasuredChildWidths.d.ts.map +1 -0
  110. package/dist/utils/hooks/useMeasuredChildWidths.js +26 -0
  111. package/dist/utils/hooks/useMeasuredChildWidths.js.map +1 -0
  112. package/dist/utils/hooks/useRovingFocus.d.ts +18 -0
  113. package/dist/utils/hooks/useRovingFocus.d.ts.map +1 -0
  114. package/dist/utils/hooks/useRovingFocus.js +130 -0
  115. package/dist/utils/hooks/useRovingFocus.js.map +1 -0
  116. package/dist/utils/hooks/useRovingFocus.test.d.ts +2 -0
  117. package/dist/utils/hooks/useRovingFocus.test.d.ts.map +1 -0
  118. package/dist/utils/hooks/useRovingFocus.test.js +59 -0
  119. package/dist/utils/hooks/useRovingFocus.test.js.map +1 -0
  120. package/dist/utils/spacedWidths.d.ts +3 -0
  121. package/dist/utils/spacedWidths.d.ts.map +1 -0
  122. package/dist/utils/spacedWidths.js +28 -0
  123. package/dist/utils/spacedWidths.js.map +1 -0
  124. package/dist/utils/spacedWidths.test.d.ts +2 -0
  125. package/dist/utils/spacedWidths.test.d.ts.map +1 -0
  126. package/dist/utils/spacedWidths.test.js +17 -0
  127. package/dist/utils/spacedWidths.test.js.map +1 -0
  128. package/package.json +1 -1
  129. package/src/components/articleCard/ArticleCard.stories.tsx +17 -12
  130. package/src/components/articleCard/ArticleCard.tsx +9 -9
  131. package/src/components/combobox/Combobox.stories.tsx +186 -12
  132. package/src/components/combobox/Combobox.test.tsx +53 -0
  133. package/src/components/combobox/Combobox.tsx +3 -3
  134. package/src/components/combobox/ComboboxButtonTrigger.tsx +52 -56
  135. package/src/components/combobox/ComboboxTrigger.tsx +19 -16
  136. package/src/components/combobox/combobox.scss +8 -3
  137. package/src/components/combobox/useVisibleTriggerTags.test.tsx +91 -0
  138. package/src/components/combobox/useVisibleTriggerTags.ts +83 -0
  139. package/src/components/filterBar/FilterBar.stories.tsx +1199 -0
  140. package/src/components/filterBar/FilterBar.test.tsx +248 -0
  141. package/src/components/filterBar/FilterBar.tsx +298 -0
  142. package/src/components/filterBar/filterBar.scss +143 -0
  143. package/src/components/icon/allowedIcons.tsx +3 -1
  144. package/src/components/{icoText/IcoText.stories.tsx → iconText/IconText.stories.tsx} +112 -112
  145. package/src/components/{icoText/IcoText.test.tsx → iconText/IconText.test.tsx} +10 -10
  146. package/src/components/{icoText/IcoText.tsx → iconText/IconText.tsx} +27 -20
  147. package/src/components/modal/Modal.tsx +5 -1
  148. package/src/components/table/cellRenderers/ComboboxCellRenderer.test.tsx +20 -3
  149. package/src/components/tag/Tag.stories.tsx +4 -4
  150. package/src/components/tag/Tag.test.tsx +62 -5
  151. package/src/components/tag/Tag.tsx +61 -3
  152. package/src/components/tag/tag.scss +80 -9
  153. package/src/components/tagList/TagList.stories.tsx +564 -0
  154. package/src/components/tagList/TagList.test.tsx +342 -0
  155. package/src/components/tagList/TagList.tsx +296 -0
  156. package/src/components/tagList/tagList.scss +56 -0
  157. package/src/components/tagList/useTagListCollapsedLayout.ts +83 -0
  158. package/src/components/tagList/useVisibleTags.ts +74 -0
  159. package/src/index.scss +3 -1
  160. package/src/index.ts +13 -1
  161. package/src/tokens.scss +3 -1
  162. package/src/{components/combobox → utils/hooks}/useElementWidth.ts +0 -1
  163. package/src/utils/hooks/useMeasuredChildWidths.ts +39 -0
  164. package/src/utils/hooks/useRovingFocus.test.tsx +105 -0
  165. package/src/utils/hooks/useRovingFocus.ts +163 -0
  166. package/src/utils/spacedWidths.test.ts +20 -0
  167. package/src/utils/spacedWidths.ts +37 -0
  168. package/dist/components/combobox/useElementWidth.d.ts.map +0 -1
  169. package/dist/components/combobox/useElementWidth.js.map +0 -1
  170. package/dist/components/combobox/useVisibleChips.d.ts +0 -21
  171. package/dist/components/combobox/useVisibleChips.d.ts.map +0 -1
  172. package/dist/components/combobox/useVisibleChips.js +0 -59
  173. package/dist/components/combobox/useVisibleChips.js.map +0 -1
  174. package/dist/components/combobox/useVisibleChips.test.d.ts +0 -2
  175. package/dist/components/combobox/useVisibleChips.test.d.ts.map +0 -1
  176. package/dist/components/combobox/useVisibleChips.test.js +0 -81
  177. package/dist/components/combobox/useVisibleChips.test.js.map +0 -1
  178. package/dist/components/icoText/IcoText.d.ts +0 -37
  179. package/dist/components/icoText/IcoText.d.ts.map +0 -1
  180. package/dist/components/icoText/IcoText.js +0 -29
  181. package/dist/components/icoText/IcoText.js.map +0 -1
  182. package/dist/components/icoText/IcoText.stories.d.ts.map +0 -1
  183. package/dist/components/icoText/IcoText.stories.js.map +0 -1
  184. package/dist/components/icoText/IcoText.test.d.ts.map +0 -1
  185. package/dist/components/icoText/IcoText.test.js.map +0 -1
  186. package/src/components/combobox/useVisibleChips.test.tsx +0 -91
  187. package/src/components/combobox/useVisibleChips.ts +0 -100
  188. /package/dist/{components/combobox → utils/hooks}/useElementWidth.d.ts +0 -0
  189. /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,2 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+ //# sourceMappingURL=useRovingFocus.test.d.ts.map
@@ -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,3 @@
1
+ export declare const sumSpacedWidths: (widths: number[], gap: number, visibleCount?: number) => number;
2
+ export declare const fitSpacedWidths: (availableWidth: number, widths: number[], gap: number) => number;
3
+ //# sourceMappingURL=spacedWidths.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=spacedWidths.test.d.ts.map
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arbor-education/design-system.components",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "The component library for the design system (the baby)",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -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 | [`IcoText`](?path=/docs/components-icotext--docs) |',
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
- ' If the title text is not self-descriptive, use `aria-label` to override the accessible name.',
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) · [IcoText](?path=/docs/components-icotext--docs) · [Tag](?path=/docs/components-tag--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 `IcoText.Icon`. Omit for icon-free layout.',
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
- 'href': {
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 ArticleCard>;
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={() => console.log('navigate to report')}
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={fn()}
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={() => console.log('card clicked')}
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={fn()}
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={fn()}
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 all interaction. In interactive shell mode, `onClick` does not fire. The card is removed from the tab order.',
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 { IcoTextIconProps } from 'Components/icoText/IcoText';
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 { IcoText } from 'Components/icoText/IcoText';
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?: IcoTextIconProps['color'];
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
- <IcoText>
65
+ <IconText>
66
66
  {icon && (
67
- <IcoText.Icon
67
+ <IconText.Icon
68
68
  color={iconColor}
69
69
  name={icon}
70
70
  screenReaderText={iconScreenReaderText}
71
71
  />
72
72
  )}
73
73
  {title && (
74
- <IcoText.Heading>
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
- </IcoText.Heading>
82
+ </IconText.Heading>
83
83
  )}
84
- {paragraph && <IcoText.Paragraph>{paragraph}</IcoText.Paragraph>}
84
+ {paragraph && <IconText.Paragraph>{paragraph}</IconText.Paragraph>}
85
85
  {tagText && <Tag color={tagColor}>{tagText}</Tag>}
86
- </IcoText>
86
+ </IconText>
87
87
  </article>
88
88
  );
89
89