@arbor-education/design-system.components 0.22.0 → 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 (83) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/component-library.md +62 -0
  3. package/dist/components/combobox/Combobox.js +1 -1
  4. package/dist/components/combobox/Combobox.js.map +1 -1
  5. package/dist/components/combobox/Combobox.stories.d.ts +4 -0
  6. package/dist/components/combobox/Combobox.stories.d.ts.map +1 -1
  7. package/dist/components/combobox/Combobox.stories.js +144 -12
  8. package/dist/components/combobox/Combobox.stories.js.map +1 -1
  9. package/dist/components/combobox/Combobox.test.js +22 -0
  10. package/dist/components/combobox/Combobox.test.js.map +1 -1
  11. package/dist/components/combobox/ComboboxButtonTrigger.d.ts +4 -4
  12. package/dist/components/combobox/ComboboxButtonTrigger.d.ts.map +1 -1
  13. package/dist/components/combobox/ComboboxButtonTrigger.js +35 -40
  14. package/dist/components/combobox/ComboboxButtonTrigger.js.map +1 -1
  15. package/dist/components/combobox/ComboboxTrigger.d.ts.map +1 -1
  16. package/dist/components/combobox/ComboboxTrigger.js +11 -4
  17. package/dist/components/combobox/ComboboxTrigger.js.map +1 -1
  18. package/dist/components/combobox/useVisibleTriggerTags.d.ts +21 -0
  19. package/dist/components/combobox/useVisibleTriggerTags.d.ts.map +1 -0
  20. package/dist/components/combobox/useVisibleTriggerTags.js +46 -0
  21. package/dist/components/combobox/useVisibleTriggerTags.js.map +1 -0
  22. package/dist/components/combobox/useVisibleTriggerTags.test.d.ts +2 -0
  23. package/dist/components/combobox/useVisibleTriggerTags.test.d.ts.map +1 -0
  24. package/dist/components/combobox/useVisibleTriggerTags.test.js +81 -0
  25. package/dist/components/combobox/useVisibleTriggerTags.test.js.map +1 -0
  26. package/dist/components/filterBar/FilterBar.d.ts +71 -0
  27. package/dist/components/filterBar/FilterBar.d.ts.map +1 -0
  28. package/dist/components/filterBar/FilterBar.js +89 -0
  29. package/dist/components/filterBar/FilterBar.js.map +1 -0
  30. package/dist/components/filterBar/FilterBar.stories.d.ts +170 -0
  31. package/dist/components/filterBar/FilterBar.stories.d.ts.map +1 -0
  32. package/dist/components/filterBar/FilterBar.stories.js +894 -0
  33. package/dist/components/filterBar/FilterBar.stories.js.map +1 -0
  34. package/dist/components/filterBar/FilterBar.test.d.ts +2 -0
  35. package/dist/components/filterBar/FilterBar.test.d.ts.map +1 -0
  36. package/dist/components/filterBar/FilterBar.test.js +164 -0
  37. package/dist/components/filterBar/FilterBar.test.js.map +1 -0
  38. package/dist/components/icon/allowedIcons.d.ts +1 -0
  39. package/dist/components/icon/allowedIcons.d.ts.map +1 -1
  40. package/dist/components/icon/allowedIcons.js +2 -1
  41. package/dist/components/icon/allowedIcons.js.map +1 -1
  42. package/dist/components/table/cellRenderers/ComboboxCellRenderer.test.d.ts.map +1 -1
  43. package/dist/components/table/cellRenderers/ComboboxCellRenderer.test.js +13 -2
  44. package/dist/components/table/cellRenderers/ComboboxCellRenderer.test.js.map +1 -1
  45. package/dist/index.css +142 -3
  46. package/dist/index.css.map +1 -1
  47. package/dist/index.d.ts +1 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +1 -0
  50. package/dist/index.js.map +1 -1
  51. package/package.json +1 -1
  52. package/src/components/combobox/Combobox.stories.tsx +186 -12
  53. package/src/components/combobox/Combobox.test.tsx +53 -0
  54. package/src/components/combobox/Combobox.tsx +3 -3
  55. package/src/components/combobox/ComboboxButtonTrigger.tsx +52 -56
  56. package/src/components/combobox/ComboboxTrigger.tsx +19 -16
  57. package/src/components/combobox/combobox.scss +8 -3
  58. package/src/components/combobox/useVisibleTriggerTags.test.tsx +91 -0
  59. package/src/components/combobox/useVisibleTriggerTags.ts +83 -0
  60. package/src/components/filterBar/FilterBar.stories.tsx +1199 -0
  61. package/src/components/filterBar/FilterBar.test.tsx +248 -0
  62. package/src/components/filterBar/FilterBar.tsx +298 -0
  63. package/src/components/filterBar/filterBar.scss +143 -0
  64. package/src/components/icon/allowedIcons.tsx +3 -1
  65. package/src/components/table/cellRenderers/ComboboxCellRenderer.test.tsx +20 -3
  66. package/src/index.scss +1 -0
  67. package/src/index.ts +10 -0
  68. package/src/tokens.scss +1 -0
  69. package/dist/components/combobox/useElementWidth.d.ts +0 -2
  70. package/dist/components/combobox/useElementWidth.d.ts.map +0 -1
  71. package/dist/components/combobox/useElementWidth.js +0 -31
  72. package/dist/components/combobox/useElementWidth.js.map +0 -1
  73. package/dist/components/combobox/useVisibleChips.d.ts +0 -21
  74. package/dist/components/combobox/useVisibleChips.d.ts.map +0 -1
  75. package/dist/components/combobox/useVisibleChips.js +0 -59
  76. package/dist/components/combobox/useVisibleChips.js.map +0 -1
  77. package/dist/components/combobox/useVisibleChips.test.d.ts +0 -2
  78. package/dist/components/combobox/useVisibleChips.test.d.ts.map +0 -1
  79. package/dist/components/combobox/useVisibleChips.test.js +0 -81
  80. package/dist/components/combobox/useVisibleChips.test.js.map +0 -1
  81. package/src/components/combobox/useElementWidth.ts +0 -40
  82. package/src/components/combobox/useVisibleChips.test.tsx +0 -91
  83. package/src/components/combobox/useVisibleChips.ts +0 -100
@@ -55,6 +55,7 @@ import {
55
55
  Link,
56
56
  List,
57
57
  ListFilterPlus,
58
+ ListTree,
58
59
  LoaderCircle,
59
60
  Lock,
60
61
  LockOpen,
@@ -90,10 +91,10 @@ import {
90
91
  UsersRound, X,
91
92
  } from 'lucide-react';
92
93
  import { AskArbor } from './customIcons/AskArbor.js';
93
- import type { CustomIconProps } from './types.js';
94
94
  import { CheckSolid } from './customIcons/CheckSolid.js';
95
95
  import { Google } from './customIcons/Google.js';
96
96
  import { XSolid } from './customIcons/XSolid.js';
97
+ import type { CustomIconProps } from './types.js';
97
98
 
98
99
  export const allowedIcons = {
99
100
  // lucide icons
@@ -156,6 +157,7 @@ export const allowedIcons = {
156
157
  'link': Link,
157
158
  'list-filter-plus': ListFilterPlus,
158
159
  'list': List,
160
+ 'list-tree': ListTree,
159
161
  'loader': LoaderCircle,
160
162
  'lock-open': LockOpen,
161
163
  'lock': Lock,
@@ -1,11 +1,11 @@
1
- import { describe, expect, test, vi, afterEach } from 'vitest';
2
- import { render, screen } from '@testing-library/react';
3
1
  import '@testing-library/jest-dom/vitest';
2
+ import { render, screen } from '@testing-library/react';
4
3
  import userEvent from '@testing-library/user-event';
5
4
  import type { SuppressKeyboardEventParams } from 'ag-grid-community';
6
5
  import type { CustomCellRendererProps } from 'ag-grid-react';
7
- import { ComboboxCellRenderer } from './ComboboxCellRenderer.js';
8
6
  import type { ComboboxOption, ComboboxProps } from 'Components/combobox/types';
7
+ import { afterEach, describe, expect, test, vi } from 'vitest';
8
+ import { ComboboxCellRenderer } from './ComboboxCellRenderer.js';
9
9
 
10
10
  const options: ComboboxOption[] = [
11
11
  { value: 'opt1', label: 'Option 1' },
@@ -104,6 +104,23 @@ describe('ComboboxCellRenderer', () => {
104
104
  expect(screen.getByRole('combobox')).toBeInTheDocument();
105
105
  });
106
106
 
107
+ test('renders selected tags for multiple button-trigger values', () => {
108
+ const { container } = render(
109
+ <ComboboxCellRenderer
110
+ {...createMockProps({
111
+ 'value': ['opt1', 'opt2'],
112
+ 'multiple': true,
113
+ 'triggerVariant': 'button',
114
+ 'aria-label': 'Choose tags',
115
+ })}
116
+ />,
117
+ );
118
+
119
+ expect(screen.getByRole('button', { name: 'Choose tags' })).toBeInTheDocument();
120
+ expect(container.querySelector('.ds-combobox__button-tags-viewport .ds-tag-list')).toBeInTheDocument();
121
+ expect(container.querySelectorAll('.ds-combobox__button-tags-viewport .ds-tag')).toHaveLength(2);
122
+ });
123
+
107
124
  test('registers cellKeyDown listener on mount', () => {
108
125
  const addEventListener = vi.fn();
109
126
  const removeEventListener = vi.fn();
package/src/index.scss CHANGED
@@ -20,6 +20,7 @@
20
20
  @use "components/formField/inputs/colourPickerDropdown/colourPickerDropdown.scss";
21
21
  @use "components/tag/tag.scss";
22
22
  @use "components/tagList/tagList.scss";
23
+ @use "components/filterBar/filterBar.scss";
23
24
  @use "components/dot/dot.scss";
24
25
  @use "components/badge/badge.scss";
25
26
  @use "components/pill/pill.scss";
package/src/index.ts CHANGED
@@ -11,6 +11,16 @@ export { DateTimePicker } from 'Components/dateTimePicker/DateTimePicker';
11
11
  export { Dot } from 'Components/dot/Dot';
12
12
  export { Dropdown } from 'Components/dropdown/Dropdown';
13
13
  export { EditableText } from 'Components/editableText/EditableText';
14
+ export {
15
+ FilterBar,
16
+ type FilterBarActiveListProps,
17
+ type FilterBarButtonProps,
18
+ type FilterBarProps,
19
+ type FilterBarTagItem,
20
+ type FilterBarTagProps,
21
+ type FilterBarToolbarProps,
22
+ type FilterBarType,
23
+ } from 'Components/filterBar/FilterBar';
14
24
  export { Fieldset } from 'Components/formField/fieldset/Fieldset';
15
25
  export { FormField } from 'Components/formField/FormField';
16
26
  export { CheckboxGroup } from 'Components/formField/inputs/checkbox/CheckboxGroup';
package/src/tokens.scss CHANGED
@@ -476,6 +476,7 @@
476
476
  --banner-spacing-vertical: var(--spacing-large);
477
477
  --banner-spacing-horizontal: var(--spacing-large);
478
478
  --button-small-radius: var(--border-radius-round);
479
+ --button-toolbar-radius: var(--border-radius-small);
479
480
  --button-small-primary-focus-color-icon: var(--color-mono-white);
480
481
  --button-small-primary-focus-color-text: var(--color-mono-white);
481
482
  --button-small-primary-focus-color-background: var(--color-brand-600);
@@ -1,2 +0,0 @@
1
- export declare const useElementWidth: (ref: React.RefObject<HTMLElement | null>, watchKey: string) => number;
2
- //# sourceMappingURL=useElementWidth.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useElementWidth.d.ts","sourceRoot":"","sources":["../../../src/components/combobox/useElementWidth.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe,GAC1B,KAAK,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EACxC,UAAU,MAAM,KACf,MAkCF,CAAC"}
@@ -1,31 +0,0 @@
1
- import { useCallback, useLayoutEffect, useState } from 'react';
2
- export const useElementWidth = (ref, watchKey) => {
3
- const [measurement, setMeasurement] = useState({ width: 0, hasMeasured: false });
4
- const element = ref.current;
5
- const update = useCallback(() => {
6
- const currentElement = ref.current;
7
- if (!currentElement) {
8
- setMeasurement({ width: 0, hasMeasured: true });
9
- return;
10
- }
11
- setMeasurement({
12
- width: currentElement.getBoundingClientRect().width,
13
- hasMeasured: true,
14
- });
15
- }, [ref]);
16
- useLayoutEffect(() => {
17
- // Re-run measurement when callers know the element's contents/layout inputs changed.
18
- update();
19
- }, [update, watchKey]);
20
- useLayoutEffect(() => {
21
- if (!element)
22
- return;
23
- const observer = new ResizeObserver(update);
24
- observer.observe(element);
25
- return () => {
26
- observer.disconnect();
27
- };
28
- }, [element, update]);
29
- return measurement.width;
30
- };
31
- //# sourceMappingURL=useElementWidth.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useElementWidth.js","sourceRoot":"","sources":["../../../src/components/combobox/useElementWidth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE/D,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,GAAwC,EACxC,QAAgB,EACR,EAAE;IACV,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IACjF,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;IAE5B,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;QAC9B,MAAM,cAAc,GAAG,GAAG,CAAC,OAAO,CAAC;QACnC,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,cAAc,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;YAChD,OAAO;QACT,CAAC;QAED,cAAc,CAAC;YACb,KAAK,EAAE,cAAc,CAAC,qBAAqB,EAAE,CAAC,KAAK;YACnD,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAEV,eAAe,CAAC,GAAG,EAAE;QACnB,qFAAqF;QACrF,MAAM,EAAE,CAAC;IACX,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEvB,eAAe,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;QAC5C,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAE1B,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,UAAU,EAAE,CAAC;QACxB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IAEtB,OAAO,WAAW,CAAC,KAAK,CAAC;AAC3B,CAAC,CAAC"}
@@ -1,21 +0,0 @@
1
- type ComputeTriggerLayoutModelParams = {
2
- containerWidth: number;
3
- chipWidths: number[];
4
- chipGap: number;
5
- badgeWidth: number;
6
- ellipsisWidth: number;
7
- showBadge: boolean;
8
- safetyBuffer?: number;
9
- };
10
- export type TriggerLayoutModel = {
11
- visibleChipIndices: number[];
12
- hiddenChipCount: number;
13
- showBadge: boolean;
14
- showEllipsis: boolean;
15
- hasOverflow: boolean;
16
- };
17
- export declare const computeTriggerLayoutModel: ({ containerWidth, chipWidths, chipGap, badgeWidth, ellipsisWidth, showBadge, safetyBuffer, }: ComputeTriggerLayoutModelParams) => TriggerLayoutModel;
18
- type UseVisibleChipsParams = ComputeTriggerLayoutModelParams;
19
- export declare const useVisibleChips: (params: UseVisibleChipsParams) => TriggerLayoutModel;
20
- export {};
21
- //# sourceMappingURL=useVisibleChips.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useVisibleChips.d.ts","sourceRoot":"","sources":["../../../src/components/combobox/useVisibleChips.ts"],"names":[],"mappings":"AAEA,KAAK,+BAA+B,GAAG;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAoBF,eAAO,MAAM,yBAAyB,GAAI,8FAQvC,+BAA+B,KAAG,kBAmCpC,CAAC;AAEF,KAAK,qBAAqB,GAAG,+BAA+B,CAAC;AAE7D,eAAO,MAAM,eAAe,GAAI,QAAQ,qBAAqB,KAAG,kBAc7D,CAAC"}
@@ -1,59 +0,0 @@
1
- import { useMemo } from 'react';
2
- const fitCount = (availableWidth, chipWidths, chipGap) => {
3
- if (availableWidth <= 0 || chipWidths.length === 0)
4
- return 0;
5
- let used = 0;
6
- let count = 0;
7
- for (let i = 0; i < chipWidths.length; i += 1) {
8
- const required = chipWidths[i] + (i > 0 ? chipGap : 0);
9
- if (used + required > availableWidth)
10
- break;
11
- used += required;
12
- count += 1;
13
- }
14
- return count;
15
- };
16
- export const computeTriggerLayoutModel = ({ containerWidth, chipWidths, chipGap, badgeWidth, ellipsisWidth, showBadge, safetyBuffer = 1, }) => {
17
- const selectedCount = chipWidths.length;
18
- const shouldShowBadge = showBadge && selectedCount > 0;
19
- if (selectedCount === 0 || containerWidth <= 0) {
20
- return {
21
- visibleChipIndices: [],
22
- hiddenChipCount: 0,
23
- showBadge: shouldShowBadge,
24
- showEllipsis: false,
25
- hasOverflow: false,
26
- };
27
- }
28
- const badgeReserve = shouldShowBadge
29
- ? badgeWidth + chipGap
30
- : 0;
31
- const availableWithoutEllipsis = Math.max(0, containerWidth - badgeReserve - safetyBuffer);
32
- const countWithoutEllipsis = fitCount(availableWithoutEllipsis, chipWidths, chipGap);
33
- const hasOverflow = countWithoutEllipsis < selectedCount;
34
- const ellipsisReserve = hasOverflow
35
- ? ellipsisWidth + chipGap
36
- : 0;
37
- const availableForChips = Math.max(0, containerWidth - badgeReserve - ellipsisReserve - safetyBuffer);
38
- const visibleCount = fitCount(availableForChips, chipWidths, chipGap);
39
- return {
40
- visibleChipIndices: Array.from({ length: visibleCount }, (_, index) => index),
41
- hiddenChipCount: Math.max(0, selectedCount - visibleCount),
42
- showBadge: shouldShowBadge,
43
- showEllipsis: visibleCount < selectedCount,
44
- hasOverflow: visibleCount < selectedCount,
45
- };
46
- };
47
- export const useVisibleChips = (params) =>
48
- // Deps list each field instead of `[params]`: callers often pass a new `params` object every
49
- // render; depending on object identity would rerun the layout every time even when values are unchanged.
50
- useMemo(() => computeTriggerLayoutModel(params), [
51
- params.badgeWidth,
52
- params.chipGap,
53
- params.chipWidths,
54
- params.containerWidth,
55
- params.ellipsisWidth,
56
- params.safetyBuffer,
57
- params.showBadge,
58
- ]);
59
- //# sourceMappingURL=useVisibleChips.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useVisibleChips.js","sourceRoot":"","sources":["../../../src/components/combobox/useVisibleChips.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=useVisibleChips.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useVisibleChips.test.d.ts","sourceRoot":"","sources":["../../../src/components/combobox/useVisibleChips.test.tsx"],"names":[],"mappings":""}
@@ -1,81 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { computeTriggerLayoutModel } from './useVisibleChips.js';
3
- describe('computeTriggerLayoutModel', () => {
4
- test('shows all chips and no ellipsis when there is no overflow', () => {
5
- const result = computeTriggerLayoutModel({
6
- containerWidth: 300,
7
- chipWidths: [70, 80, 60],
8
- chipGap: 4,
9
- badgeWidth: 24,
10
- ellipsisWidth: 12,
11
- showBadge: true,
12
- });
13
- expect(result).toEqual({
14
- visibleChipIndices: [0, 1, 2],
15
- hiddenChipCount: 0,
16
- showBadge: true,
17
- showEllipsis: false,
18
- hasOverflow: false,
19
- });
20
- });
21
- test('reserves badge and ellipsis before fitting chips when overflow exists', () => {
22
- const result = computeTriggerLayoutModel({
23
- containerWidth: 160,
24
- chipWidths: [60, 60, 60],
25
- chipGap: 4,
26
- badgeWidth: 24,
27
- ellipsisWidth: 12,
28
- showBadge: true,
29
- });
30
- expect(result.visibleChipIndices).toEqual([0]);
31
- expect(result.hiddenChipCount).toBe(2);
32
- expect(result.showEllipsis).toBe(true);
33
- expect(result.hasOverflow).toBe(true);
34
- });
35
- test('allows zero chips when badge and ellipsis take priority in tiny space', () => {
36
- const result = computeTriggerLayoutModel({
37
- containerWidth: 36,
38
- chipWidths: [80, 80],
39
- chipGap: 4,
40
- badgeWidth: 24,
41
- ellipsisWidth: 12,
42
- showBadge: true,
43
- });
44
- expect(result.visibleChipIndices).toEqual([]);
45
- expect(result.hiddenChipCount).toBe(2);
46
- expect(result.showEllipsis).toBe(true);
47
- });
48
- test('reserving badge space reduces visible count', () => {
49
- const withBadge = computeTriggerLayoutModel({
50
- containerWidth: 180,
51
- chipWidths: [60, 60, 60],
52
- chipGap: 4,
53
- badgeWidth: 24,
54
- ellipsisWidth: 12,
55
- showBadge: true,
56
- });
57
- const withoutBadge = computeTriggerLayoutModel({
58
- containerWidth: 180,
59
- chipWidths: [60, 60, 60],
60
- chipGap: 4,
61
- badgeWidth: 24,
62
- ellipsisWidth: 12,
63
- showBadge: false,
64
- });
65
- expect(withoutBadge.visibleChipIndices.length).toBeGreaterThanOrEqual(withBadge.visibleChipIndices.length);
66
- });
67
- test('uses safety buffer to avoid a clipped final chip on boundary widths', () => {
68
- const result = computeTriggerLayoutModel({
69
- containerWidth: 195.5,
70
- chipWidths: [73.4, 73.4, 20],
71
- chipGap: 4,
72
- badgeWidth: 24.2,
73
- ellipsisWidth: 11.6,
74
- showBadge: true,
75
- safetyBuffer: 1,
76
- });
77
- expect(result.visibleChipIndices).toEqual([0]);
78
- expect(result.showEllipsis).toBe(true);
79
- });
80
- });
81
- //# sourceMappingURL=useVisibleChips.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useVisibleChips.test.js","sourceRoot":"","sources":["../../../src/components/combobox/useVisibleChips.test.tsx"],"names":[],"mappings":"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"}
@@ -1,40 +0,0 @@
1
- import { useCallback, useLayoutEffect, useState } from 'react';
2
-
3
- export const useElementWidth = (
4
- ref: React.RefObject<HTMLElement | null>,
5
- watchKey: string,
6
- ): number => {
7
- const [measurement, setMeasurement] = useState({ width: 0, hasMeasured: false });
8
- const element = ref.current;
9
-
10
- const update = useCallback(() => {
11
- const currentElement = ref.current;
12
- if (!currentElement) {
13
- setMeasurement({ width: 0, hasMeasured: true });
14
- return;
15
- }
16
-
17
- setMeasurement({
18
- width: currentElement.getBoundingClientRect().width,
19
- hasMeasured: true,
20
- });
21
- }, [ref]);
22
-
23
- useLayoutEffect(() => {
24
- // Re-run measurement when callers know the element's contents/layout inputs changed.
25
- update();
26
- }, [update, watchKey]);
27
-
28
- useLayoutEffect(() => {
29
- if (!element) return;
30
-
31
- const observer = new ResizeObserver(update);
32
- observer.observe(element);
33
-
34
- return () => {
35
- observer.disconnect();
36
- };
37
- }, [element, update]);
38
-
39
- return measurement.width;
40
- };
@@ -1,91 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { computeTriggerLayoutModel } from './useVisibleChips.js';
3
-
4
- describe('computeTriggerLayoutModel', () => {
5
- test('shows all chips and no ellipsis when there is no overflow', () => {
6
- const result = computeTriggerLayoutModel({
7
- containerWidth: 300,
8
- chipWidths: [70, 80, 60],
9
- chipGap: 4,
10
- badgeWidth: 24,
11
- ellipsisWidth: 12,
12
- showBadge: true,
13
- });
14
-
15
- expect(result).toEqual({
16
- visibleChipIndices: [0, 1, 2],
17
- hiddenChipCount: 0,
18
- showBadge: true,
19
- showEllipsis: false,
20
- hasOverflow: false,
21
- });
22
- });
23
-
24
- test('reserves badge and ellipsis before fitting chips when overflow exists', () => {
25
- const result = computeTriggerLayoutModel({
26
- containerWidth: 160,
27
- chipWidths: [60, 60, 60],
28
- chipGap: 4,
29
- badgeWidth: 24,
30
- ellipsisWidth: 12,
31
- showBadge: true,
32
- });
33
-
34
- expect(result.visibleChipIndices).toEqual([0]);
35
- expect(result.hiddenChipCount).toBe(2);
36
- expect(result.showEllipsis).toBe(true);
37
- expect(result.hasOverflow).toBe(true);
38
- });
39
-
40
- test('allows zero chips when badge and ellipsis take priority in tiny space', () => {
41
- const result = computeTriggerLayoutModel({
42
- containerWidth: 36,
43
- chipWidths: [80, 80],
44
- chipGap: 4,
45
- badgeWidth: 24,
46
- ellipsisWidth: 12,
47
- showBadge: true,
48
- });
49
-
50
- expect(result.visibleChipIndices).toEqual([]);
51
- expect(result.hiddenChipCount).toBe(2);
52
- expect(result.showEllipsis).toBe(true);
53
- });
54
-
55
- test('reserving badge space reduces visible count', () => {
56
- const withBadge = computeTriggerLayoutModel({
57
- containerWidth: 180,
58
- chipWidths: [60, 60, 60],
59
- chipGap: 4,
60
- badgeWidth: 24,
61
- ellipsisWidth: 12,
62
- showBadge: true,
63
- });
64
-
65
- const withoutBadge = computeTriggerLayoutModel({
66
- containerWidth: 180,
67
- chipWidths: [60, 60, 60],
68
- chipGap: 4,
69
- badgeWidth: 24,
70
- ellipsisWidth: 12,
71
- showBadge: false,
72
- });
73
-
74
- expect(withoutBadge.visibleChipIndices.length).toBeGreaterThanOrEqual(withBadge.visibleChipIndices.length);
75
- });
76
-
77
- test('uses safety buffer to avoid a clipped final chip on boundary widths', () => {
78
- const result = computeTriggerLayoutModel({
79
- containerWidth: 195.5,
80
- chipWidths: [73.4, 73.4, 20],
81
- chipGap: 4,
82
- badgeWidth: 24.2,
83
- ellipsisWidth: 11.6,
84
- showBadge: true,
85
- safetyBuffer: 1,
86
- });
87
-
88
- expect(result.visibleChipIndices).toEqual([0]);
89
- expect(result.showEllipsis).toBe(true);
90
- });
91
- });
@@ -1,100 +0,0 @@
1
- import { useMemo } from 'react';
2
-
3
- type ComputeTriggerLayoutModelParams = {
4
- containerWidth: number;
5
- chipWidths: number[];
6
- chipGap: number;
7
- badgeWidth: number;
8
- ellipsisWidth: number;
9
- showBadge: boolean;
10
- safetyBuffer?: number;
11
- };
12
-
13
- export type TriggerLayoutModel = {
14
- visibleChipIndices: number[];
15
- hiddenChipCount: number;
16
- showBadge: boolean;
17
- showEllipsis: boolean;
18
- hasOverflow: boolean;
19
- };
20
-
21
- const fitCount = (
22
- availableWidth: number,
23
- chipWidths: number[],
24
- chipGap: number,
25
- ): number => {
26
- if (availableWidth <= 0 || chipWidths.length === 0) return 0;
27
-
28
- let used = 0;
29
- let count = 0;
30
- for (let i = 0; i < chipWidths.length; i += 1) {
31
- const required = chipWidths[i]! + (i > 0 ? chipGap : 0);
32
- if (used + required > availableWidth) break;
33
- used += required;
34
- count += 1;
35
- }
36
- return count;
37
- };
38
-
39
- export const computeTriggerLayoutModel = ({
40
- containerWidth,
41
- chipWidths,
42
- chipGap,
43
- badgeWidth,
44
- ellipsisWidth,
45
- showBadge,
46
- safetyBuffer = 1,
47
- }: ComputeTriggerLayoutModelParams): TriggerLayoutModel => {
48
- const selectedCount = chipWidths.length;
49
- const shouldShowBadge = showBadge && selectedCount > 0;
50
-
51
- if (selectedCount === 0 || containerWidth <= 0) {
52
- return {
53
- visibleChipIndices: [],
54
- hiddenChipCount: 0,
55
- showBadge: shouldShowBadge,
56
- showEllipsis: false,
57
- hasOverflow: false,
58
- };
59
- }
60
-
61
- const badgeReserve = shouldShowBadge
62
- ? badgeWidth + chipGap
63
- : 0;
64
-
65
- const availableWithoutEllipsis = Math.max(0, containerWidth - badgeReserve - safetyBuffer);
66
- const countWithoutEllipsis = fitCount(availableWithoutEllipsis, chipWidths, chipGap);
67
- const hasOverflow = countWithoutEllipsis < selectedCount;
68
-
69
- const ellipsisReserve = hasOverflow
70
- ? ellipsisWidth + chipGap
71
- : 0;
72
- const availableForChips = Math.max(0, containerWidth - badgeReserve - ellipsisReserve - safetyBuffer);
73
- const visibleCount = fitCount(availableForChips, chipWidths, chipGap);
74
-
75
- return {
76
- visibleChipIndices: Array.from({ length: visibleCount }, (_, index) => index),
77
- hiddenChipCount: Math.max(0, selectedCount - visibleCount),
78
- showBadge: shouldShowBadge,
79
- showEllipsis: visibleCount < selectedCount,
80
- hasOverflow: visibleCount < selectedCount,
81
- };
82
- };
83
-
84
- type UseVisibleChipsParams = ComputeTriggerLayoutModelParams;
85
-
86
- export const useVisibleChips = (params: UseVisibleChipsParams): TriggerLayoutModel =>
87
- // Deps list each field instead of `[params]`: callers often pass a new `params` object every
88
- // render; depending on object identity would rerun the layout every time even when values are unchanged.
89
- useMemo(
90
- () => computeTriggerLayoutModel(params),
91
- [
92
- params.badgeWidth,
93
- params.chipGap,
94
- params.chipWidths,
95
- params.containerWidth,
96
- params.ellipsisWidth,
97
- params.safetyBuffer,
98
- params.showBadge,
99
- ],
100
- );