Skip to content

Commit a7f2606

Browse files
committed
feat(design-system): CR changes [AR-41960]
1 parent 49cffd1 commit a7f2606

File tree

12 files changed

+147
-103
lines changed

12 files changed

+147
-103
lines changed

.changeset/odd-hats-shine.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@drivenets/eslint-plugin-design-system': patch
3+
---
4+
5+
- Add depracation rule for `DsChip`
6+
- Add deprecation rule for `DsChipGroup`
Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
/**
2-
* @deprecated DsChipGroup is deprecated. Use DsTagFilter instead.
3-
* @see {@link ../ds-tag-filter} for the replacement component.
4-
*/
51
export { default as DsChipGroup } from './ds-chip-group';
6-
/**
7-
* @deprecated These types are deprecated. Use DsTagFilter types instead.
8-
* @see {@link ../ds-tag-filter} for the replacement types.
9-
*/
2+
103
export type { DsChipGroupProps, ChipItem } from './ds-chip-group.types';

packages/design-system/src/components/ds-tag-filter/ds-tag-filter.module.scss

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
display: flex;
33
flex-direction: column;
44
gap: var(--spacing-xs);
5-
65
padding: var(--spacing-xs) var(--spacing-standard);
76
background: var(--color-background-selected-weak);
87
border: 1px solid var(--color-outline-weak);
@@ -60,13 +59,14 @@
6059
// Hidden container for measuring element widths
6160
// Must render all elements to get accurate measurements
6261
.measurementContainer {
63-
position: absolute;
64-
visibility: hidden;
6562
pointer-events: none;
6663
display: flex;
6764
align-items: center;
6865
gap: var(--spacing-xs);
66+
6967
// Ensure it doesn't affect layout
7068
height: 0;
7169
overflow: hidden;
70+
position: absolute;
71+
visibility: hidden;
7272
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,8 @@ export const CustomLocale: Story = {
392392
<DsTagFilter
393393
items={filters}
394394
locale={{
395-
label: 'Active criteria:',
396-
clearButton: 'Reset all',
395+
label: 'Aktywne filtry:',
396+
clearButton: 'Zresetuj',
397397
}}
398398
onClearAll={handleClearAll}
399399
onItemDelete={handleFilterDelete}
@@ -409,9 +409,9 @@ export const CustomLocale: Story = {
409409
});
410410

411411
// Verify custom label is rendered
412-
await expect(canvas.getByText('Active criteria:')).toBeInTheDocument();
412+
await expect(canvas.getByText('Aktywne filtry:')).toBeInTheDocument();
413413

414-
await expect(canvas.getByRole('button', { name: /Reset all/ })).toBeInTheDocument();
414+
await expect(canvas.getByRole('button', { name: /Zresetuj/ })).toBeInTheDocument();
415415

416416
await expect(canvas.queryByText('Filtered by:')).not.toBeInTheDocument();
417417
await expect(canvas.queryByRole('button', { name: /Clear all filters/ })).not.toBeInTheDocument();

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

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { useState, useRef, useMemo } from 'react';
1+
import { useRef, useState } from 'react';
22
import { createPortal } from 'react-dom';
33
import classNames from 'classnames';
44
import styles from './ds-tag-filter.module.scss';
5-
import type { DsTagFilterProps } from './ds-tag-filter.types';
5+
import type { DsTagFilterProps, TagFilterItem } from './ds-tag-filter.types';
66
import { useTagOverflowCalculation } from './hooks/use-tag-overflow-calculation';
77
import { DsTypography } from '../ds-typography';
88
import { DsTag } from '../ds-tag';
@@ -36,20 +36,18 @@ const DsTagFilter = ({
3636
expanded,
3737
});
3838

39-
const { label = 'Filtered by:', clearButton = 'Clear all filters' } = locale;
39+
const {
40+
label = 'Filtered by:',
41+
clearButton = 'Clear all filters',
42+
hiddenCountPlural = 'filters',
43+
hiddenCountSingular = 'filter',
44+
collapseTagLabel = 'Collapse',
45+
} = locale;
4046

4147
// Split items into row sections
42-
const { row1Tags, row2Tags, hiddenTags } = useMemo(() => {
43-
const row1Tags = items.slice(0, row1TagCount);
44-
const row2Tags = items.slice(row1TagCount, row1TagCount + row2TagCount);
45-
const hiddenTags = expanded ? [] : items.slice(row1TagCount + row2TagCount);
46-
47-
return {
48-
row1Tags,
49-
row2Tags,
50-
hiddenTags,
51-
};
52-
}, [expanded, items, row1TagCount, row2TagCount]);
48+
const row1Tags = items.slice(0, row1TagCount);
49+
const row2Tags = items.slice(row1TagCount, row1TagCount + row2TagCount);
50+
const hiddenTags = expanded ? [] : items.slice(row1TagCount + row2TagCount);
5351

5452
if (items.length === 0) {
5553
return null;
@@ -58,24 +56,30 @@ const DsTagFilter = ({
5856
const hiddenCount = hiddenTags.length;
5957
const hasRow2Content = row2Tags.length > 0 || hasOverflow;
6058

61-
const renderTag = (item: (typeof items)[0]) => (
62-
<DsTag
63-
key={item.id}
64-
label={item.label}
65-
selected={item.selected}
66-
onClick={onItemSelect ? () => onItemSelect(item) : undefined}
67-
onDelete={onItemDelete ? () => onItemDelete(item) : undefined}
68-
/>
69-
);
59+
const renderTag = (item: TagFilterItem) => {
60+
const tagProps = item.tagProps || {};
61+
62+
return (
63+
<DsTag
64+
{...tagProps}
65+
key={item.id}
66+
label={item.label}
67+
selected={item.selected}
68+
onClick={onItemSelect ? () => onItemSelect(item) : undefined}
69+
onDelete={onItemDelete ? () => onItemDelete(item) : undefined}
70+
/>
71+
);
72+
};
7073

7174
const handleExpandToggle = () => {
7275
const newExpanded = !expanded;
7376
setExpanded(newExpanded);
7477
onExpand?.(newExpanded);
7578
};
7679

77-
// Measurement container rendered via portal to keep it outside component's DOM tree
80+
// Measurement container rendered via portal to keep it outside the component's DOM tree
7881
// This prevents Testing Library from finding duplicate elements
82+
// We use it to calculate the number of remaining tags to render
7983
const measurementContainer = (
8084
<div ref={measurementRef} className={styles.measurementContainer} aria-hidden="true">
8185
{label && (
@@ -86,36 +90,38 @@ const DsTagFilter = ({
8690
</span>
8791
)}
8892
{items.map((item) => (
89-
<span key={item.id} data-measure-tag="">
90-
<DsTag
91-
label={item.label}
92-
selected={item.selected}
93-
onClick={onItemSelect ? () => onItemSelect(item) : undefined}
94-
onDelete={onItemDelete ? () => onItemDelete(item) : undefined}
95-
/>
96-
</span>
93+
<DsTag
94+
key={item.id}
95+
data-measure-tag=""
96+
label={item.label}
97+
selected={item.selected}
98+
onClick={onItemSelect ? () => onItemSelect(item) : undefined}
99+
onDelete={onItemDelete ? () => onItemDelete(item) : undefined}
100+
/>
97101
))}
98102
{onClearAll && (
99-
<span data-measure-clear="">
100-
<DsButton
101-
design="v1.2"
102-
buttonType="tertiary"
103-
variant="ghost"
104-
className={styles.clearButton}
105-
contentClassName={styles.clearContent}
106-
size="small"
107-
>
108-
<DsIcon icon="close" />
109-
{clearButton}
110-
</DsButton>
111-
</span>
103+
<DsButton
104+
data-measure-clear=""
105+
design="v1.2"
106+
buttonType="tertiary"
107+
variant="ghost"
108+
className={styles.clearButton}
109+
contentClassName={styles.clearContent}
110+
size="small"
111+
>
112+
<DsIcon icon="close" />
113+
{clearButton}
114+
</DsButton>
112115
)}
113-
<span data-measure-expand="">
114-
<DsTag label="+99 filters" className={styles.expandTag} />
115-
</span>
116+
<DsTag data-measure-expand="" label="+99 filters" className={styles.expandTag} />
116117
</div>
117118
);
118119

120+
/**
121+
* IMPORTANT!
122+
* If you make any changes to this layout (styling, JSX, anything that affects it.)
123+
* Please, apply the exact same changes to the "measurementContainer"
124+
*/
119125
return (
120126
<>
121127
<div
@@ -153,8 +159,8 @@ const DsTagFilter = ({
153159
<DsTag
154160
label={
155161
expanded
156-
? 'Collapse'
157-
: `+${String(hiddenCount)} ${hiddenCount === 1 ? 'filter' : 'filters'}`
162+
? collapseTagLabel
163+
: `+${String(hiddenCount)} ${hiddenCount === 1 ? hiddenCountSingular : hiddenCountPlural}`
158164
}
159165
selected={expanded}
160166
className={styles.expandTag}

packages/design-system/src/components/ds-tag-filter/ds-tag-filter.types.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { CSSProperties, ReactNode } from 'react';
1+
import type { CSSProperties } from 'react';
2+
import type { DsTagProps } from '../ds-tag';
23

34
export interface TagFilterItem {
45
/**
@@ -17,20 +18,26 @@ export interface TagFilterItem {
1718
* Whether the tag is selected/checked
1819
*/
1920
selected?: boolean;
21+
/**
22+
* Additional props to pass to the tag component
23+
*/
24+
tagProps?: Partial<DsTagProps>;
2025
}
2126

2227
export interface DsTagFilterProps {
2328
/**
2429
* Array of tag items to display
2530
*/
2631
items: TagFilterItem[];
27-
2832
/**
29-
* Represents locale-specific options
33+
* Locale object (you can pass custom strings for localization)
3034
*/
3135
locale?: {
32-
label?: ReactNode;
33-
clearButton?: ReactNode;
36+
label?: string;
37+
clearButton?: string;
38+
collapseTagLabel?: string;
39+
hiddenCountSingular?: string;
40+
hiddenCountPlural?: string;
3441
};
3542
/**
3643
* Callback when "Clear all" is clicked

packages/design-system/src/components/ds-tag-filter/hooks/use-tag-overflow-calculation.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type RefObject, useLayoutEffect, useState, useCallback } from 'react';
1+
import { type RefObject, useCallback, useLayoutEffect, useState } from 'react';
22

33
interface UseTagOverflowCalculationProps {
44
containerRef: RefObject<HTMLDivElement | null>;
@@ -11,7 +11,6 @@ interface UseTagOverflowCalculationResult {
1111
row1TagCount: number;
1212
row2TagCount: number;
1313
hasOverflow: boolean;
14-
isCalculating: boolean;
1514
}
1615

1716
// Extra space needed per row for animated delete button that appears on hover
@@ -34,7 +33,6 @@ export const useTagOverflowCalculation = ({
3433
const [row1TagCount, setRow1TagCount] = useState(0);
3534
const [row2TagCount, setRow2TagCount] = useState(0);
3635
const [hasOverflow, setHasOverflow] = useState(false);
37-
const [isCalculating, setIsCalculating] = useState(true);
3836

3937
const calculateLayout = useCallback(() => {
4038
if (!containerRef.current || !measurementRef.current) {
@@ -54,7 +52,6 @@ export const useTagOverflowCalculation = ({
5452
setRow1TagCount(0);
5553
setRow2TagCount(0);
5654
setHasOverflow(false);
57-
setIsCalculating(false);
5855
return;
5956
}
6057

@@ -65,17 +62,14 @@ export const useTagOverflowCalculation = ({
6562
const paddingRight = parseFloat(containerStyle.paddingRight) || 0;
6663
const containerWidth = containerRect.width - paddingLeft - paddingRight;
6764

68-
// Get computed gap from CSS
6965
const computedStyle = getComputedStyle(measurementContainer);
7066
const gap = parseFloat(computedStyle.gap) || 8;
7167

72-
// Measure fixed elements
7368
const labelWidth = label ? label.getBoundingClientRect().width + gap : 0;
7469
const clearButtonWidth = clearButton ? clearButton.getBoundingClientRect().width + gap : 0;
7570
// Fallback width for expand tag (~100px for "+99 filters" text) if measurement fails
7671
const expandTagWidth = expandTag ? expandTag.getBoundingClientRect().width + gap : 100;
7772

78-
// Measure all tag widths
7973
const tagWidths = tags.map((tag) => tag.getBoundingClientRect().width);
8074

8175
// Calculate Row 1 available space (Label + Tags + Clear Button)
@@ -103,7 +97,6 @@ export const useTagOverflowCalculation = ({
10397
setRow1TagCount(row1Count);
10498
setRow2TagCount(0);
10599
setHasOverflow(false);
106-
setIsCalculating(false);
107100
return;
108101
}
109102

@@ -157,13 +150,9 @@ export const useTagOverflowCalculation = ({
157150
setHasOverflow(false);
158151
}
159152
}
160-
161-
setIsCalculating(false);
162153
}, [containerRef, measurementRef]);
163154

164155
useLayoutEffect(() => {
165-
setIsCalculating(true);
166-
167156
// Use requestAnimationFrame to ensure DOM is fully laid out
168157
const rafId = requestAnimationFrame(() => {
169158
calculateLayout();
@@ -191,14 +180,12 @@ export const useTagOverflowCalculation = ({
191180
row1TagCount,
192181
row2TagCount: totalItems - row1TagCount,
193182
hasOverflow: true, // Keep expand button visible to allow collapse
194-
isCalculating,
195183
};
196184
}
197185

198186
return {
199187
row1TagCount,
200188
row2TagCount,
201189
hasOverflow,
202-
isCalculating,
203190
};
204191
};

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useState } from 'react';
22
import type { Meta, StoryObj } from '@storybook/react-vite';
33
import { expect, fn, userEvent, waitFor, within } from 'storybook/test';
4-
54
import DsTag from './ds-tag';
65
import { tagSizes, tagVariants } from './ds-tag.types';
76

0 commit comments

Comments
 (0)