Skip to content

Commit 49cffd1

Browse files
committed
feat(design-system): put measurment wrapper in portal to avoid duplicated dom [AR-41960]
1 parent a03f7b7 commit 49cffd1

File tree

10 files changed

+268
-151
lines changed

10 files changed

+268
-151
lines changed

.changeset/cyan-kids-taste.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@drivenets/design-system': minor
3+
---
4+
5+
- Added `DsTag` component
6+
- Added `DsTagFilter` component
7+
- Deprecated `DsChip` component
8+
- Deprecated `DsChipGroup` component

packages/design-system/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@
6666
"classnames": "^2.5.1"
6767
},
6868
"peerDependencies": {
69-
"react": "^19"
69+
"react": "^19",
70+
"react-dom": "^19"
7071
},
7172
"devDependencies": {
7273
"@arethetypeswrong/cli": "^0.18.2",
@@ -84,6 +85,7 @@
8485
"@tanstack/react-router": "^1.141.2",
8586
"@types/eslint-plugin-jsx-a11y": "^6.10.1",
8687
"@types/react": "^19.2.7",
88+
"@types/react-dom": "^19.2.3",
8789
"@vitest/browser": "^4.0.16",
8890
"@vitest/browser-playwright": "^4.0.15",
8991
"babel-plugin-react-compiler": "^1.0.0",
@@ -97,6 +99,7 @@
9799
"postcss-modules": "^6.0.1",
98100
"publint": "^0.3.16",
99101
"react": "^19.2.3",
102+
"react-dom": "^19.2.3",
100103
"react-hook-form": "^7.55.0",
101104
"rollup-plugin-sass": "^1.15.3",
102105
"sass-embedded": "^1.97.0",

packages/design-system/src/components/ds-chip-group/ds-chip-group.stories.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import type { ChipItem } from './ds-chip-group.types';
55
import styles from './ds-chip-group.stories.module.scss';
66

77
/**
8-
* @deprecated This component is deprecated. Use `DsTagGroup` instead.
9-
* @see {@link ../ds-tag-group/ds-tag-group.stories} for examples of the replacement component.
8+
* @deprecated This component is deprecated. Use `DsTagFilter` instead.
9+
* @see {@link ../ds-tag-filter/ds-tag-filter.stories} for examples of the replacement component.
1010
*/
1111
const meta: Meta<typeof DsChipGroup> = {
1212
title: 'Design System/Chip Group (Deprecated)',
@@ -16,7 +16,7 @@ const meta: Meta<typeof DsChipGroup> = {
1616
docs: {
1717
description: {
1818
component:
19-
'**Deprecated**: This component is deprecated. Please use `DsTagGroup` instead. See the TagGroup stories for the replacement component.',
19+
'**Deprecated**: This component is deprecated. Please use `DsTagFilter` instead. See the TagGroup stories for the replacement component.',
2020
},
2121
},
2222
},

packages/design-system/src/components/ds-chip-group/ds-chip-group.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import { DsButton } from '../ds-button';
1313
import { DsIcon } from '../ds-icon';
1414

1515
/**
16-
* @deprecated This component is deprecated. Use `DsTagGroup` instead.
17-
* @see {@link ../ds-tag-group} for the replacement component.
16+
* @deprecated This component is deprecated. Use `DsTagFilter` instead.
17+
* @see {@link ../ds-tag-filter} for the replacement component.
1818
*/
1919
const DsChipGroup: React.FC<DsChipGroupProps> = ({
2020
items,

packages/design-system/src/components/ds-chip-group/ds-chip-group.types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { CSSProperties } from 'react';
22

33
/**
4-
* @deprecated This interface is deprecated. Use `DsTagGroup` types instead.
5-
* @see {@link ../ds-tag-group} for the replacement types.
4+
* @deprecated This interface is deprecated. Use `DsTagFilter` types instead.
5+
* @see {@link ../ds-tag-filter} for the replacement types.
66
*/
77
export interface ChipItem {
88
/**
@@ -24,8 +24,8 @@ export interface ChipItem {
2424
}
2525

2626
/**
27-
* @deprecated This interface is deprecated. Use `DsTagGroupProps` from `ds-tag-group` instead.
28-
* @see {@link ../ds-tag-group/ds-tag-group.types} for the replacement interface.
27+
* @deprecated This interface is deprecated. Use `DsTagFilterProps` from `ds-tag-filter` instead.
28+
* @see {@link ../ds-tag-filter/ds-tag-filter.types} for the replacement interface.
2929
*/
3030
export interface DsChipGroupProps {
3131
/**
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/**
2-
* @deprecated DsChipGroup is deprecated. Use DsTagGroup instead.
3-
* @see {@link ../ds-tag-group} for the replacement component.
2+
* @deprecated DsChipGroup is deprecated. Use DsTagFilter instead.
3+
* @see {@link ../ds-tag-filter} for the replacement component.
44
*/
55
export { default as DsChipGroup } from './ds-chip-group';
66
/**
7-
* @deprecated These types are deprecated. Use DsTagGroup types instead.
8-
* @see {@link ../ds-tag-group} for the replacement types.
7+
* @deprecated These types are deprecated. Use DsTagFilter types instead.
8+
* @see {@link ../ds-tag-filter} for the replacement types.
99
*/
1010
export type { DsChipGroupProps, ChipItem } from './ds-chip-group.types';

packages/design-system/src/components/ds-form-control/stories/ds-form-control-input-number.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ const sanityCheck = async (canvasElement: HTMLElement) => {
7777

7878
// Test typing
7979
await userEvent.clear(input);
80+
await userEvent.click(input); // ensure focus + caret placement
8081
await userEvent.type(input, '25');
8182
await waitFor(async () => {
8283
await expect(input).toHaveValue('25');

packages/design-system/src/components/ds-tag-filter/ds-tag-filter.stories.tsx

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Meta, StoryObj } from '@storybook/react-vite';
22
import { useState } from 'react';
3-
import { expect, userEvent, waitFor, within } from 'storybook/test';
3+
import { expect, fn, userEvent, waitFor, within } from 'storybook/test';
44
import DsTagFilter from './ds-tag-filter';
55
import type { TagFilterItem } from './ds-tag-filter.types';
66
import styles from './ds-tag-filter.stories.module.scss';
@@ -39,6 +39,10 @@ const meta: Meta<typeof DsTagFilter> = {
3939
action: 'select-item',
4040
description: 'Callback when item is selected',
4141
},
42+
onExpand: {
43+
action: 'expand',
44+
description: 'Callback when expand/collapse is clicked',
45+
},
4246
},
4347
};
4448

@@ -57,8 +61,8 @@ const sampleFilters: TagFilterItem[] = [
5761
{ id: '9', label: 'Version: 000.0001-4' },
5862
{ id: '10', label: 'Version: 000.0001-5' },
5963
{ id: '11', label: 'Version: 000.0001-6' },
60-
{ id: '12', label: 'Last editor: Maren Levin' },
61-
{ id: '13', label: 'Last editor: Emery Franci' },
64+
{ id: '12', label: 'Last editor: Kevin Levin' },
65+
{ id: '13', label: 'Last editor: Emery Dance' },
6266
];
6367

6468
/**
@@ -125,7 +129,7 @@ export const Default: Story = {
125129
await expect(canvas.getByRole('button', { name: 'Status: Active' })).toBeInTheDocument();
126130
});
127131

128-
await expect(canvas.getAllByText('Filtered by:')[0]).toBeInTheDocument();
132+
await expect(canvas.getByText('Filtered by:')).toBeInTheDocument();
129133
await expect(canvas.getByRole('button', { name: /Clear all filters/ })).toBeInTheDocument();
130134

131135
const firstTag = canvas.getByRole('button', { name: 'Status: Active' });
@@ -297,11 +301,13 @@ export const ReadOnly: Story = {
297301
play: async ({ canvasElement }) => {
298302
const canvas = within(canvasElement);
299303

300-
// Verify custom label is shown (use getAllByText and take first to avoid measurement container duplicate)
301-
await expect(canvas.getAllByText('Applied filters:')[0]).toBeInTheDocument();
304+
// Wait for layout calculation to complete and tags to be rendered
305+
await waitFor(async () => {
306+
await expect(canvas.getByText('Status: Active')).toBeInTheDocument();
307+
});
302308

303-
// Verify filters are visible
304-
await expect(canvas.getAllByText('Status: Active')[0]).toBeInTheDocument();
309+
// Verify custom label is shown
310+
await expect(canvas.getByText('Applied filters:')).toBeInTheDocument();
305311

306312
// Verify delete buttons are NOT visible (read-only)
307313
await expect(canvas.queryByRole('button', { name: 'Delete tag' })).not.toBeInTheDocument();
@@ -402,8 +408,8 @@ export const CustomLocale: Story = {
402408
await expect(canvas.getByRole('button', { name: 'Status: Active' })).toBeInTheDocument();
403409
});
404410

405-
// Verify custom label is rendered (use getAllByText and take first to avoid measurement container duplicate)
406-
await expect(canvas.getAllByText('Active criteria:')[0]).toBeInTheDocument();
411+
// Verify custom label is rendered
412+
await expect(canvas.getByText('Active criteria:')).toBeInTheDocument();
407413

408414
await expect(canvas.getByRole('button', { name: /Reset all/ })).toBeInTheDocument();
409415

@@ -412,6 +418,54 @@ export const CustomLocale: Story = {
412418
},
413419
};
414420

421+
/**
422+
* Story testing the expand/collapse functionality and onExpand callback.
423+
*/
424+
export const ExpandCollapse: Story = {
425+
args: {
426+
items: sampleFilters,
427+
onExpand: fn(),
428+
},
429+
play: async ({ canvasElement, args }) => {
430+
const canvas = within(canvasElement);
431+
432+
// Wait for layout calculation to complete and overflow tag to appear
433+
await waitFor(async () => {
434+
await expect(canvas.getByRole('button', { name: /\+\d+ filters?/ })).toBeInTheDocument();
435+
});
436+
437+
const expandButton = canvas.getByRole('button', { name: /\+\d+ filters?/ });
438+
439+
// Click expand button
440+
await userEvent.click(expandButton);
441+
442+
// Verify onExpand was called with true (expanded)
443+
await waitFor(async () => {
444+
await expect(args.onExpand).toHaveBeenCalledWith(true);
445+
});
446+
447+
// Verify the button now shows "Collapse"
448+
await waitFor(async () => {
449+
await expect(canvas.getByRole('button', { name: 'Collapse' })).toBeInTheDocument();
450+
});
451+
452+
const collapseButton = canvas.getByRole('button', { name: 'Collapse' });
453+
454+
// Click collapse button
455+
await userEvent.click(collapseButton);
456+
457+
// Verify onExpand was called with false (collapsed)
458+
await waitFor(async () => {
459+
await expect(args.onExpand).toHaveBeenCalledWith(false);
460+
});
461+
462+
// Verify the expand button is back
463+
await waitFor(async () => {
464+
await expect(canvas.getByRole('button', { name: /\+\d+ filters?/ })).toBeInTheDocument();
465+
});
466+
},
467+
};
468+
415469
/**
416470
* Story showing TagFilter with pre-selected items.
417471
*/

0 commit comments

Comments
 (0)