@drivenets/design-system 0.18.0 → 0.19.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 +26 -0
- package/dist/index.cjs +15850 -10821
- package/dist/index.d.cts +785 -239
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +785 -239
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15835 -10822
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/package.json +13 -13
- package/src/components/ds-bot-button/ds-bot-button.figma.ts +38 -0
- package/src/components/ds-bot-button/ds-bot-button.module.scss +41 -0
- package/src/components/ds-bot-button/ds-bot-button.tsx +48 -0
- package/src/components/ds-bot-button/ds-bot-button.types.ts +33 -0
- package/src/components/ds-bot-button/index.ts +2 -0
- package/src/components/ds-bot-button/net-gen-logo.tsx +46 -0
- package/src/components/ds-bulk-actions/components/action-button.tsx +42 -0
- package/src/components/ds-bulk-actions/components/menu-entries.tsx +76 -0
- package/src/components/ds-bulk-actions/components/overflow.tsx +75 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.figma.ts +28 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.module.scss +199 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.tsx +220 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.types.ts +170 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.unit.test.ts +195 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.utils.ts +91 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.utils.unit.test.ts +134 -0
- package/src/components/ds-bulk-actions/hooks/compute-bulk-actions-overflow.ts +85 -0
- package/src/components/ds-bulk-actions/hooks/use-bulk-actions-overflow.ts +153 -0
- package/src/components/ds-bulk-actions/index.ts +18 -0
- package/src/components/ds-button-v3/ds-button-v3.module.scss +2 -2
- package/src/components/ds-catalog-layout/ds-catalog-layout.figma.ts +1 -1
- package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +2 -0
- package/src/components/ds-empty-state/ds-empty-state.module.scss +16 -0
- package/src/components/ds-empty-state/ds-empty-state.tsx +45 -0
- package/src/components/ds-empty-state/ds-empty-state.types.ts +38 -0
- package/src/components/ds-empty-state/index.ts +3 -0
- package/src/components/ds-file-upload/ds-file-upload.tsx +4 -1
- package/src/components/ds-form-control/ds-form-control.tsx +18 -10
- package/src/components/ds-illustration/ds-illustration.figma.ts +34 -0
- package/src/components/ds-illustration/ds-illustration.module.scss +6 -0
- package/src/components/ds-illustration/ds-illustration.tsx +29 -0
- package/src/components/ds-illustration/ds-illustration.types.ts +45 -0
- package/src/components/ds-illustration/illustrations/configuration.tsx +51 -0
- package/src/components/ds-illustration/illustrations/device.tsx +133 -0
- package/src/components/ds-illustration/illustrations/done.tsx +107 -0
- package/src/components/{ds-catalog-layout/catalog-layout-empty-illustration.tsx → ds-illustration/illustrations/empty-inbox.tsx} +10 -14
- package/src/components/ds-illustration/illustrations/error.tsx +86 -0
- package/src/components/ds-illustration/illustrations/go-filter.tsx +51 -0
- package/src/components/ds-illustration/illustrations/illustration-clouds.tsx +10 -0
- package/src/components/ds-illustration/illustrations/index.ts +39 -0
- package/src/components/ds-illustration/illustrations/no-connection.tsx +54 -0
- package/src/components/ds-illustration/illustrations/no-credit-card.tsx +48 -0
- package/src/components/ds-illustration/illustrations/no-device.tsx +61 -0
- package/src/components/ds-illustration/illustrations/no-documents.tsx +52 -0
- package/src/components/ds-illustration/illustrations/no-gps.tsx +57 -0
- package/src/components/ds-illustration/illustrations/no-images.tsx +82 -0
- package/src/components/ds-illustration/illustrations/no-items-cart.tsx +86 -0
- package/src/components/ds-illustration/illustrations/no-messages.tsx +51 -0
- package/src/components/ds-illustration/illustrations/no-search-result.tsx +76 -0
- package/src/components/ds-illustration/illustrations/no-tasks.tsx +61 -0
- package/src/components/ds-illustration/illustrations/site.tsx +80 -0
- package/src/components/ds-illustration/index.ts +3 -0
- package/src/components/ds-main-menu/components/expanded-item/expanded-item.tsx +108 -0
- package/src/components/ds-main-menu/components/expanded-item/expanded-item.types.ts +8 -0
- package/src/components/ds-main-menu/components/expanded-item/index.ts +1 -0
- package/src/components/ds-main-menu/components/menu-graphic/index.ts +1 -0
- package/src/components/ds-main-menu/components/menu-graphic/menu-graphic.tsx +16 -0
- package/src/components/ds-main-menu/components/menu-graphic/menu-graphic.types.ts +7 -0
- package/src/components/ds-main-menu/components/tile-link/index.ts +1 -0
- package/src/components/ds-main-menu/components/tile-link/tile-link.tsx +85 -0
- package/src/components/ds-main-menu/components/tile-link/tile-link.types.ts +8 -0
- package/src/components/ds-main-menu/components/utility-link/index.ts +1 -0
- package/src/components/ds-main-menu/components/utility-link/utility-link.tsx +37 -0
- package/src/components/ds-main-menu/components/utility-link/utility-link.types.ts +7 -0
- package/src/components/ds-main-menu/ds-main-menu.figma.ts +17 -0
- package/src/components/ds-main-menu/ds-main-menu.module.scss +349 -0
- package/src/components/ds-main-menu/ds-main-menu.tsx +102 -0
- package/src/components/ds-main-menu/ds-main-menu.types.ts +86 -0
- package/src/components/ds-main-menu/ds-main-menu.utils.ts +40 -0
- package/src/components/ds-main-menu/index.ts +9 -0
- package/src/components/ds-modal/figma/ds-modal-footer.figma.ts +1 -1
- package/src/components/ds-popover/ds-popover.tsx +2 -1
- package/src/components/ds-popover/ds-popover.types.ts +7 -0
- package/src/components/ds-site-node/ds-site-node.tsx +2 -2
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +12 -2
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +2 -3
- package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.module.scss +1 -6
- package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx +6 -3
- package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +18 -7
- package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +27 -21
- package/src/components/ds-table/components/ds-table-header-label.module.scss +12 -0
- package/src/components/ds-table/components/ds-table-header-label.tsx +28 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +0 -1
- package/src/components/ds-table/context/ds-table-context.tsx +8 -0
- package/src/components/ds-table/ds-table.module.scss +26 -16
- package/src/components/ds-table/ds-table.tsx +46 -14
- package/src/components/ds-table/ds-table.types.ts +33 -3
- package/src/components/ds-table/hooks/use-column-resize.ts +114 -11
- package/src/components/ds-table/hooks/use-scrollbar-spacer.ts +108 -0
- package/src/components/ds-table/hooks/use-scrollbar-spacer.unit.test.ts +26 -0
- package/src/components/ds-table/styles/_variables.scss +0 -2
- package/src/components/ds-table/utils/column-size.ts +274 -1
- package/src/components/ds-table/utils/column-size.unit.test.ts +157 -0
- package/src/components/ds-table/utils/constants.ts +13 -5
- package/src/components/ds-table/utils/table-columns.tsx +27 -13
- package/src/components/ds-table/utils/table-columns.unit.test.ts +44 -0
- package/src/components/ds-tag-filter/ds-tag-filter.module.scss +7 -35
- package/src/components/ds-tag-filter/ds-tag-filter.tsx +34 -57
- package/src/components/ds-tag-filter/ds-tag-filter.types.ts +2 -1
- package/src/components/ds-tag-filter/hooks/use-tag-overflow-calculation.ts +13 -18
- package/src/components/ds-toggle/ds-toggle.module.scss +18 -0
- package/src/components/ds-toggle/ds-toggle.tsx +15 -13
- package/src/components/ds-tooltip/ds-tooltip.tsx +6 -2
- package/src/components/ds-tooltip/ds-tooltip.types.ts +17 -0
- package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.figma.ts +35 -0
- package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.module.scss +150 -0
- package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.tsx +161 -0
- package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.types.ts +117 -0
- package/src/components/ds-top-bar-navigation/index.ts +2 -0
- package/src/index.ts +6 -0
- package/src/utils/use-is-horizontally-overflowing.ts +35 -0
- package/src/components/ds-site-node/hooks/use-is-overflowing.ts +0 -31
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +0 -112
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx +0 -67
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.types.ts +0 -41
- package/src/components/ds-table/components/ds-table-bulk-actions/index.ts +0 -2
|
@@ -13,8 +13,13 @@ import {
|
|
|
13
13
|
getResizableColumnStyle,
|
|
14
14
|
getResizableHeaderStyle,
|
|
15
15
|
getResizeOriginSize,
|
|
16
|
+
getUtilityColumnSizing,
|
|
17
|
+
growFillLeavesToContainer,
|
|
16
18
|
isExplicitColumnWidth,
|
|
17
19
|
omitBuiltinColumnSizing,
|
|
20
|
+
resolveUtilityColumnWidth,
|
|
21
|
+
shiftColumnTrack,
|
|
22
|
+
withUtilityColumnSizing,
|
|
18
23
|
} from './column-size';
|
|
19
24
|
import {
|
|
20
25
|
EXPANDER_COLUMN_ID,
|
|
@@ -179,6 +184,115 @@ describe('collectMissingLeafSizing', () => {
|
|
|
179
184
|
it('returns an empty map when every measured id already has a sizing entry', () => {
|
|
180
185
|
expect(collectMissingLeafSizing({ a: 100 }, { a: 90 })).toEqual({});
|
|
181
186
|
});
|
|
187
|
+
|
|
188
|
+
it('reconciles independent rounding so seeded widths match the measured total', () => {
|
|
189
|
+
const next = collectMissingLeafSizing({ a: 500.6, b: 500.6 }, {});
|
|
190
|
+
|
|
191
|
+
expect(next).toEqual({ a: 501, b: 500 });
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('absorbs rounding drift into missing ids and leaves existing widths unchanged', () => {
|
|
195
|
+
expect(collectMissingLeafSizing({ a: 500.6, b: 500.6 }, { b: 501 }, 1001)).toEqual({ a: 500 });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('does not snap seeded widths to the container when measured columns overflow it', () => {
|
|
199
|
+
expect(collectMissingLeafSizing({ a: 1000, b: 1000 }, {}, 1116)).toEqual({ a: 1000, b: 1000 });
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('snaps a 1px measured overflow to the container width', () => {
|
|
203
|
+
const next = collectMissingLeafSizing({ a: 558.5, b: 558.5 }, {}, 1116);
|
|
204
|
+
const total = Object.values(next).reduce((sum, width) => sum + width, 0);
|
|
205
|
+
|
|
206
|
+
expect(total).toBe(1116);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('distributes container underflow across fill leaves by their measured share', () => {
|
|
210
|
+
const next = collectMissingLeafSizing({ a: 200, b: 200 }, {}, 1200, ['a', 'b']);
|
|
211
|
+
|
|
212
|
+
expect(next).toEqual({ a: 600, b: 600 });
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('grows only fill leaves when the container is wider than the measured total', () => {
|
|
216
|
+
const next = collectMissingLeafSizing({ a: 200, b: 200, c: 200 }, {}, 1200, ['b', 'c']);
|
|
217
|
+
|
|
218
|
+
expect(next).toEqual({ a: 200, b: 500, c: 500 });
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('growFillLeavesToContainer', () => {
|
|
223
|
+
it('moves leftover space onto uncapped fill leaves after a sibling hits maxSize', () => {
|
|
224
|
+
const next = growFillLeavesToContainer(
|
|
225
|
+
{ a: 280, b: 360, c: 360 },
|
|
226
|
+
{},
|
|
227
|
+
['a', 'b', 'c'],
|
|
228
|
+
1200,
|
|
229
|
+
['a', 'b', 'c'],
|
|
230
|
+
[
|
|
231
|
+
{ id: 'a', columnDef: { maxSize: 280 } },
|
|
232
|
+
{ id: 'b', columnDef: {} },
|
|
233
|
+
{ id: 'c', columnDef: {} },
|
|
234
|
+
],
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
expect(next.a).toBe(280);
|
|
238
|
+
expect(next.b).toBeGreaterThan(360);
|
|
239
|
+
expect(next.c).toBeGreaterThan(360);
|
|
240
|
+
expect((next.a ?? 0) + (next.b ?? 0) + (next.c ?? 0)).toBe(1200);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('does not grow an explicit leaf when filling leftover space', () => {
|
|
244
|
+
const next = growFillLeavesToContainer(
|
|
245
|
+
{ a: 280, b: 360, c: 360 },
|
|
246
|
+
{},
|
|
247
|
+
['a', 'b', 'c'],
|
|
248
|
+
1200,
|
|
249
|
+
['b', 'c'],
|
|
250
|
+
[
|
|
251
|
+
{ id: 'a', columnDef: { maxSize: 280 } },
|
|
252
|
+
{ id: 'b', columnDef: {} },
|
|
253
|
+
{ id: 'c', columnDef: {} },
|
|
254
|
+
],
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(next).toEqual({ a: 280, b: 460, c: 460 });
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe('shiftColumnTrack', () => {
|
|
262
|
+
const bounds = [
|
|
263
|
+
{ id: 'a', columnDef: {} },
|
|
264
|
+
{ id: 'b', columnDef: {} },
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
it('returns the same map when delta is 0', () => {
|
|
268
|
+
const sizing = { a: 200, b: 200 };
|
|
269
|
+
|
|
270
|
+
expect(shiftColumnTrack(sizing, ['a', 'b'], 0, bounds)).toBe(sizing);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('shrinks from the last leaf when the spacer appears', () => {
|
|
274
|
+
const next = shiftColumnTrack({ a: 500, b: 500 }, ['a', 'b'], -10, bounds);
|
|
275
|
+
|
|
276
|
+
expect(next.a).toBe(500);
|
|
277
|
+
expect(next.b).toBe(490);
|
|
278
|
+
expect((next.a ?? 0) + (next.b ?? 0)).toBe(990);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('grows leaves when the spacer disappears', () => {
|
|
282
|
+
const next = shiftColumnTrack({ a: 500, b: 490 }, ['a', 'b'], 10, bounds);
|
|
283
|
+
|
|
284
|
+
expect((next.a ?? 0) + (next.b ?? 0)).toBe(1000);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('does not shrink a leaf below minSize', () => {
|
|
288
|
+
const next = shiftColumnTrack({ a: 200, b: 52 }, ['a', 'b'], -10, [
|
|
289
|
+
{ id: 'a', columnDef: {} },
|
|
290
|
+
{ id: 'b', columnDef: { minSize: 52 } },
|
|
291
|
+
]);
|
|
292
|
+
|
|
293
|
+
expect(next.b).toBe(52);
|
|
294
|
+
expect(next.a).toBe(190);
|
|
295
|
+
});
|
|
182
296
|
});
|
|
183
297
|
|
|
184
298
|
describe('omitBuiltinColumnSizing', () => {
|
|
@@ -211,3 +325,46 @@ describe('omitBuiltinColumnSizing', () => {
|
|
|
211
325
|
expect(next).not.toBe(sizing);
|
|
212
326
|
});
|
|
213
327
|
});
|
|
328
|
+
|
|
329
|
+
describe('resolveUtilityColumnWidth', () => {
|
|
330
|
+
it('returns the fallback when the width is omitted, non-finite, or not positive', () => {
|
|
331
|
+
expect(resolveUtilityColumnWidth(undefined, 36)).toBe(36);
|
|
332
|
+
expect(resolveUtilityColumnWidth(0, 36)).toBe(36);
|
|
333
|
+
expect(resolveUtilityColumnWidth(-8, 36)).toBe(36);
|
|
334
|
+
expect(resolveUtilityColumnWidth(Number.NaN, 36)).toBe(36);
|
|
335
|
+
expect(resolveUtilityColumnWidth(Number.POSITIVE_INFINITY, 36)).toBe(36);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('returns a positive finite width as-is', () => {
|
|
339
|
+
expect(resolveUtilityColumnWidth(48, 36)).toBe(48);
|
|
340
|
+
expect(resolveUtilityColumnWidth(1, 36)).toBe(1);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe('withUtilityColumnSizing', () => {
|
|
345
|
+
it('strips inbound utility ids and overlays the authored widths', () => {
|
|
346
|
+
expect(
|
|
347
|
+
withUtilityColumnSizing(
|
|
348
|
+
{ [SELECT_COLUMN_ID]: 80, firstName: 200 },
|
|
349
|
+
{ [SELECT_COLUMN_ID]: 48, [EXPANDER_COLUMN_ID]: 40 },
|
|
350
|
+
),
|
|
351
|
+
).toEqual({ firstName: 200, [SELECT_COLUMN_ID]: 48, [EXPANDER_COLUMN_ID]: 40 });
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
describe('getUtilityColumnSizing', () => {
|
|
356
|
+
it('collects size entries for injected utility leaves only', () => {
|
|
357
|
+
expect(
|
|
358
|
+
getUtilityColumnSizing([
|
|
359
|
+
{ id: REORDER_COLUMN_ID, size: 80 },
|
|
360
|
+
{ id: EXPANDER_COLUMN_ID, size: 40 },
|
|
361
|
+
{ id: SELECT_COLUMN_ID, size: 48 },
|
|
362
|
+
{ id: 'firstName', size: 200 },
|
|
363
|
+
]),
|
|
364
|
+
).toEqual({
|
|
365
|
+
[REORDER_COLUMN_ID]: 80,
|
|
366
|
+
[EXPANDER_COLUMN_ID]: 40,
|
|
367
|
+
[SELECT_COLUMN_ID]: 48,
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
});
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default text for empty table state
|
|
3
|
-
*/
|
|
4
|
-
export const EMPTY_TABLE_STATE_TEXT = 'No results.';
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
2
|
* Column id used for the synthetic expander column injected when `expandable` is set.
|
|
8
3
|
*/
|
|
@@ -55,6 +50,19 @@ export const SKELETON_ROW_COUNT = 5;
|
|
|
55
50
|
*/
|
|
56
51
|
export const RESIZE_MIN_COLUMN_WIDTH = 52;
|
|
57
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Pixel width of the full-height resize overlay divider. Must match
|
|
55
|
+
* `$resize-divider-width` in `styles/_variables.scss`.
|
|
56
|
+
*/
|
|
57
|
+
export const RESIZE_DIVIDER_WIDTH = 2;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Rest width of the header **Scrollbar spacer** when the body overflows
|
|
61
|
+
* vertically. Must match `$scrollbar-default-size` in `styles/_scrollbars.scss`.
|
|
62
|
+
* Hover (12px) is intentionally not tracked.
|
|
63
|
+
*/
|
|
64
|
+
export const SCROLLBAR_SPACER_WIDTH = 10;
|
|
65
|
+
|
|
58
66
|
/**
|
|
59
67
|
* Width change (in px) applied per Arrow key press when a resize handle is
|
|
60
68
|
* focused.
|
|
@@ -3,6 +3,7 @@ import { DsSkeletonText } from '../../ds-skeleton';
|
|
|
3
3
|
import { DsTableHeaderSelectableCell } from '../components/ds-table-header-selectable-cell';
|
|
4
4
|
import { DsTableRowExpandableCell } from '../components/ds-table-row-expandable-cell';
|
|
5
5
|
import { DsTableRowSelectableCell } from '../components/ds-table-row-selectable-cell';
|
|
6
|
+
import { resolveUtilityColumnWidth } from './column-size';
|
|
6
7
|
import {
|
|
7
8
|
EXPANDER_COLUMN_ID,
|
|
8
9
|
EXPANDER_COLUMN_WIDTH,
|
|
@@ -19,6 +20,9 @@ export type GetAugmentedColumnsOptions = {
|
|
|
19
20
|
reorderable: boolean;
|
|
20
21
|
virtualized: boolean;
|
|
21
22
|
showSelectAllCheckbox: boolean;
|
|
23
|
+
selectableColumnWidth?: number;
|
|
24
|
+
expandableColumnWidth?: number;
|
|
25
|
+
reorderableColumnWidth?: number;
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
const EXPLICIT_SIZE_META = { hasExplicitSize: true } as const;
|
|
@@ -41,6 +45,13 @@ const stampExplicitSize = <TData, TValue>(columns: ColumnDef<TData, TValue>[]):
|
|
|
41
45
|
return { ...column, meta: { ...column.meta, ...EXPLICIT_SIZE_META } };
|
|
42
46
|
});
|
|
43
47
|
|
|
48
|
+
/** Pin min/max so the table-wide resize `minSize` cannot stretch this column. */
|
|
49
|
+
const pinUtilityColumnSize = (width: number) => ({
|
|
50
|
+
size: width,
|
|
51
|
+
minSize: width,
|
|
52
|
+
maxSize: width,
|
|
53
|
+
});
|
|
54
|
+
|
|
44
55
|
/**
|
|
45
56
|
* Prepends the builtin select / expander / reorder columns when those features
|
|
46
57
|
* are on. Reorder is omitted for virtualized tables. Unshift order yields
|
|
@@ -48,19 +59,24 @@ const stampExplicitSize = <TData, TValue>(columns: ColumnDef<TData, TValue>[]):
|
|
|
48
59
|
*/
|
|
49
60
|
export const getAugmentedColumns = <TData, TValue>(
|
|
50
61
|
columns: ColumnDef<TData, TValue>[],
|
|
51
|
-
{
|
|
62
|
+
{
|
|
63
|
+
selectable,
|
|
64
|
+
expandable,
|
|
65
|
+
reorderable,
|
|
66
|
+
virtualized,
|
|
67
|
+
showSelectAllCheckbox,
|
|
68
|
+
selectableColumnWidth,
|
|
69
|
+
expandableColumnWidth,
|
|
70
|
+
reorderableColumnWidth,
|
|
71
|
+
}: GetAugmentedColumnsOptions,
|
|
52
72
|
): ColumnDef<TData, TValue>[] => {
|
|
53
73
|
const augmentedColumns: ColumnDef<TData, TValue>[] = stampExplicitSize(columns);
|
|
54
74
|
|
|
55
75
|
if (selectable) {
|
|
76
|
+
const width = resolveUtilityColumnWidth(selectableColumnWidth, SELECT_COLUMN_WIDTH);
|
|
56
77
|
augmentedColumns.unshift({
|
|
57
78
|
id: SELECT_COLUMN_ID,
|
|
58
|
-
|
|
59
|
-
// Pin min/max to the fixed width: `enableResizing: false` blocks the
|
|
60
|
-
// handle but not the table-wide resize `minSize`, which would otherwise
|
|
61
|
-
// stretch this column.
|
|
62
|
-
minSize: SELECT_COLUMN_WIDTH,
|
|
63
|
-
maxSize: SELECT_COLUMN_WIDTH,
|
|
79
|
+
...pinUtilityColumnSize(width),
|
|
64
80
|
enableSorting: false,
|
|
65
81
|
enableResizing: false,
|
|
66
82
|
meta: EXPLICIT_SIZE_META,
|
|
@@ -70,11 +86,10 @@ export const getAugmentedColumns = <TData, TValue>(
|
|
|
70
86
|
}
|
|
71
87
|
|
|
72
88
|
if (expandable) {
|
|
89
|
+
const width = resolveUtilityColumnWidth(expandableColumnWidth, EXPANDER_COLUMN_WIDTH);
|
|
73
90
|
augmentedColumns.unshift({
|
|
74
91
|
id: EXPANDER_COLUMN_ID,
|
|
75
|
-
|
|
76
|
-
minSize: EXPANDER_COLUMN_WIDTH,
|
|
77
|
-
maxSize: EXPANDER_COLUMN_WIDTH,
|
|
92
|
+
...pinUtilityColumnSize(width),
|
|
78
93
|
enableSorting: false,
|
|
79
94
|
enableResizing: false,
|
|
80
95
|
meta: EXPLICIT_SIZE_META,
|
|
@@ -84,13 +99,12 @@ export const getAugmentedColumns = <TData, TValue>(
|
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
if (reorderable && !virtualized) {
|
|
102
|
+
const width = resolveUtilityColumnWidth(reorderableColumnWidth, REORDER_COLUMN_WIDTH);
|
|
87
103
|
// Cell is rendered inline by DsTableRow when it encounters REORDER_COLUMN_ID,
|
|
88
104
|
// since the drag handle needs row-level useSortable state.
|
|
89
105
|
augmentedColumns.unshift({
|
|
90
106
|
id: REORDER_COLUMN_ID,
|
|
91
|
-
|
|
92
|
-
minSize: REORDER_COLUMN_WIDTH,
|
|
93
|
-
maxSize: REORDER_COLUMN_WIDTH,
|
|
107
|
+
...pinUtilityColumnSize(width),
|
|
94
108
|
enableSorting: false,
|
|
95
109
|
enableResizing: false,
|
|
96
110
|
meta: EXPLICIT_SIZE_META,
|
|
@@ -100,6 +100,50 @@ describe('getAugmentedColumns', () => {
|
|
|
100
100
|
}
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
it('pins custom utility column widths and ignores them when the feature is off', () => {
|
|
104
|
+
const columns = getAugmentedColumns([nameColumn], {
|
|
105
|
+
...noFeatures,
|
|
106
|
+
selectable: true,
|
|
107
|
+
expandable: true,
|
|
108
|
+
reorderable: true,
|
|
109
|
+
selectableColumnWidth: 48,
|
|
110
|
+
expandableColumnWidth: 40,
|
|
111
|
+
reorderableColumnWidth: 80,
|
|
112
|
+
});
|
|
113
|
+
const boundsById = new Map(columns.map((column) => [column.id, column]));
|
|
114
|
+
|
|
115
|
+
expect(boundsById.get(SELECT_COLUMN_ID)?.size).toBe(48);
|
|
116
|
+
expect(boundsById.get(SELECT_COLUMN_ID)?.minSize).toBe(48);
|
|
117
|
+
expect(boundsById.get(SELECT_COLUMN_ID)?.maxSize).toBe(48);
|
|
118
|
+
expect(boundsById.get(EXPANDER_COLUMN_ID)?.size).toBe(40);
|
|
119
|
+
expect(boundsById.get(EXPANDER_COLUMN_ID)?.minSize).toBe(40);
|
|
120
|
+
expect(boundsById.get(EXPANDER_COLUMN_ID)?.maxSize).toBe(40);
|
|
121
|
+
expect(boundsById.get(REORDER_COLUMN_ID)?.size).toBe(80);
|
|
122
|
+
expect(boundsById.get(REORDER_COLUMN_ID)?.minSize).toBe(80);
|
|
123
|
+
expect(boundsById.get(REORDER_COLUMN_ID)?.maxSize).toBe(80);
|
|
124
|
+
|
|
125
|
+
expect(
|
|
126
|
+
columnIds(getAugmentedColumns([nameColumn], { ...noFeatures, selectableColumnWidth: 48 })),
|
|
127
|
+
).toEqual(['name']);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('falls back to the default width when the custom width is invalid', () => {
|
|
131
|
+
const columns = getAugmentedColumns([nameColumn], {
|
|
132
|
+
...noFeatures,
|
|
133
|
+
selectable: true,
|
|
134
|
+
expandable: true,
|
|
135
|
+
reorderable: true,
|
|
136
|
+
selectableColumnWidth: 0,
|
|
137
|
+
expandableColumnWidth: Number.NaN,
|
|
138
|
+
reorderableColumnWidth: -10,
|
|
139
|
+
});
|
|
140
|
+
const boundsById = new Map(columns.map((column) => [column.id, column]));
|
|
141
|
+
|
|
142
|
+
expect(boundsById.get(SELECT_COLUMN_ID)?.size).toBe(SELECT_COLUMN_WIDTH);
|
|
143
|
+
expect(boundsById.get(EXPANDER_COLUMN_ID)?.size).toBe(EXPANDER_COLUMN_WIDTH);
|
|
144
|
+
expect(boundsById.get(REORDER_COLUMN_ID)?.size).toBe(REORDER_COLUMN_WIDTH);
|
|
145
|
+
});
|
|
146
|
+
|
|
103
147
|
it('stamps hasExplicitSize on authored leaves and builtins, not on unsized leaves', () => {
|
|
104
148
|
const columns = getAugmentedColumns(
|
|
105
149
|
[
|
|
@@ -1,61 +1,33 @@
|
|
|
1
1
|
.container {
|
|
2
2
|
display: flex;
|
|
3
|
-
|
|
3
|
+
align-items: flex-start;
|
|
4
4
|
gap: var(--xs);
|
|
5
5
|
padding: var(--xs) var(--standard);
|
|
6
6
|
background: var(--background-secondary-hover);
|
|
7
7
|
border: 1px solid var(--outline-weak);
|
|
8
8
|
border-radius: var(--3xs);
|
|
9
9
|
overflow: hidden;
|
|
10
|
-
|
|
11
|
-
&.expanded {
|
|
12
|
-
max-height: none;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.header {
|
|
17
|
-
display: flex;
|
|
18
|
-
align-items: center;
|
|
19
|
-
width: 100%;
|
|
20
|
-
gap: var(--xs);
|
|
21
|
-
flex-shrink: 0;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.headerActions {
|
|
25
|
-
display: flex;
|
|
26
|
-
align-items: center;
|
|
27
|
-
gap: var(--xs);
|
|
28
|
-
flex-shrink: 0;
|
|
29
|
-
margin-left: auto;
|
|
30
10
|
}
|
|
31
11
|
|
|
32
12
|
.tagsArea {
|
|
33
13
|
display: flex;
|
|
34
14
|
align-items: center;
|
|
35
|
-
|
|
15
|
+
flex: 1;
|
|
16
|
+
min-width: 0;
|
|
36
17
|
gap: var(--xs);
|
|
37
18
|
flex-wrap: wrap;
|
|
38
19
|
}
|
|
39
20
|
|
|
40
|
-
.
|
|
41
|
-
color: var(--font-secondary);
|
|
42
|
-
white-space: nowrap;
|
|
43
|
-
flex-shrink: 0;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.itemCheckbox {
|
|
21
|
+
.actions {
|
|
47
22
|
display: flex;
|
|
48
23
|
align-items: center;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
.ghostButtonContent {
|
|
52
|
-
// TODO: remove once migrated 1.2 button to the ghost with transparent bg
|
|
53
|
-
background: transparent !important;
|
|
24
|
+
gap: var(--xs);
|
|
25
|
+
flex-shrink: 0;
|
|
54
26
|
}
|
|
55
27
|
|
|
56
28
|
.actionButton {
|
|
57
|
-
padding: 0;
|
|
58
29
|
flex-shrink: 0;
|
|
30
|
+
white-space: nowrap;
|
|
59
31
|
}
|
|
60
32
|
|
|
61
33
|
.measurementContainer {
|
|
@@ -4,17 +4,12 @@ import classNames from 'classnames';
|
|
|
4
4
|
import styles from './ds-tag-filter.module.scss';
|
|
5
5
|
import type { DsTagFilterProps, TagFilterItem } from './ds-tag-filter.types';
|
|
6
6
|
import { useTagOverflowCalculation } from './hooks/use-tag-overflow-calculation';
|
|
7
|
-
import { DsTypography } from '../ds-typography';
|
|
8
7
|
import { DsTag, type DsTagProps } from '../ds-tag';
|
|
9
|
-
import {
|
|
8
|
+
import { DsButtonV3 } from '../ds-button-v3';
|
|
10
9
|
import { DsIcon } from '../ds-icon';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* Design system TagFilter component
|
|
14
|
-
*
|
|
15
|
-
* A component for displaying active filters as tags with overflow handling.
|
|
16
|
-
* Non-tag elements (label, expand/collapse, clear) sit in a header row.
|
|
17
|
-
* Tags occupy the full width below, wrapping as needed.
|
|
18
13
|
*/
|
|
19
14
|
const DsTagFilter = ({
|
|
20
15
|
items,
|
|
@@ -27,22 +22,17 @@ const DsTagFilter = ({
|
|
|
27
22
|
onExpand,
|
|
28
23
|
}: DsTagFilterProps) => {
|
|
29
24
|
const [expanded, setExpanded] = useState(false);
|
|
30
|
-
const
|
|
25
|
+
const tagsAreaRef = useRef<HTMLDivElement>(null);
|
|
31
26
|
const measurementRef = useRef<HTMLDivElement>(null);
|
|
32
27
|
|
|
33
28
|
const { visibleTagCount, hasOverflow } = useTagOverflowCalculation({
|
|
34
|
-
|
|
29
|
+
tagsAreaRef,
|
|
35
30
|
measurementRef,
|
|
36
31
|
totalItems: items.length,
|
|
37
32
|
expanded,
|
|
38
33
|
});
|
|
39
34
|
|
|
40
|
-
const {
|
|
41
|
-
label = 'Filtered by:',
|
|
42
|
-
clearButton = 'Clear all filters',
|
|
43
|
-
showMore = 'Show more',
|
|
44
|
-
showLess = 'Show less',
|
|
45
|
-
} = locale;
|
|
35
|
+
const { clearButton = 'Clear all filters', showMore = 'Show more', showLess = 'Show less' } = locale;
|
|
46
36
|
|
|
47
37
|
const visibleTags = expanded ? items : items.slice(0, visibleTagCount);
|
|
48
38
|
const hiddenCount = items.length - visibleTagCount;
|
|
@@ -79,54 +69,41 @@ const DsTagFilter = ({
|
|
|
79
69
|
</div>
|
|
80
70
|
);
|
|
81
71
|
|
|
72
|
+
const toggleLabel = expanded ? showLess : showMore;
|
|
73
|
+
|
|
82
74
|
return (
|
|
83
75
|
<>
|
|
84
|
-
<div
|
|
85
|
-
ref={
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
>
|
|
89
|
-
<div className={styles.header}>
|
|
90
|
-
{label && (
|
|
91
|
-
<DsTypography variant="body-sm-reg" className={styles.label}>
|
|
92
|
-
{label}
|
|
93
|
-
</DsTypography>
|
|
94
|
-
)}
|
|
76
|
+
<div className={classNames(styles.container, className)} style={style}>
|
|
77
|
+
<div ref={tagsAreaRef} className={styles.tagsArea} aria-live="polite">
|
|
78
|
+
{visibleTags.map((item) => renderTag(item))}
|
|
79
|
+
</div>
|
|
95
80
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
</DsButton>
|
|
110
|
-
)}
|
|
81
|
+
<div className={styles.actions}>
|
|
82
|
+
{hasOverflow && (
|
|
83
|
+
<DsButtonV3
|
|
84
|
+
variant="tertiary"
|
|
85
|
+
size="small"
|
|
86
|
+
className={styles.actionButton}
|
|
87
|
+
aria-expanded={expanded}
|
|
88
|
+
onClick={handleExpandToggle}
|
|
89
|
+
>
|
|
90
|
+
{`${toggleLabel} (${String(hiddenCount)})`}
|
|
91
|
+
<DsIcon icon={expanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down'} size="tiny" aria-hidden />
|
|
92
|
+
</DsButtonV3>
|
|
93
|
+
)}
|
|
111
94
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
{clearButton}
|
|
124
|
-
</DsButton>
|
|
125
|
-
)}
|
|
126
|
-
</div>
|
|
95
|
+
{onClearAll && (
|
|
96
|
+
<DsButtonV3
|
|
97
|
+
variant="tertiary"
|
|
98
|
+
size="small"
|
|
99
|
+
icon="close"
|
|
100
|
+
className={styles.actionButton}
|
|
101
|
+
onClick={onClearAll}
|
|
102
|
+
>
|
|
103
|
+
{clearButton}
|
|
104
|
+
</DsButtonV3>
|
|
105
|
+
)}
|
|
127
106
|
</div>
|
|
128
|
-
|
|
129
|
-
<div className={styles.tagsArea}>{visibleTags.map((item) => renderTag(item))}</div>
|
|
130
107
|
</div>
|
|
131
108
|
{createPortal(measurementContainer, document.body)}
|
|
132
109
|
</>
|
|
@@ -40,7 +40,8 @@ export interface DsTagFilterProps {
|
|
|
40
40
|
*/
|
|
41
41
|
locale?: {
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* @deprecated No longer rendered. Retained for backwards compatibility;
|
|
44
|
+
* Will be removed in the future.
|
|
44
45
|
*/
|
|
45
46
|
label?: string;
|
|
46
47
|
/**
|
|
@@ -2,7 +2,7 @@ import { type RefObject, useCallback, useLayoutEffect, useState } from 'react';
|
|
|
2
2
|
import { fitTagsInRow, getContainerAvailableWidth, getElementMeasurements } from '../utils';
|
|
3
3
|
|
|
4
4
|
interface UseTagOverflowCalculationOptions {
|
|
5
|
-
|
|
5
|
+
tagsAreaRef: RefObject<HTMLDivElement | null>;
|
|
6
6
|
measurementRef: RefObject<HTMLDivElement | null>;
|
|
7
7
|
totalItems: number;
|
|
8
8
|
expanded: boolean;
|
|
@@ -14,13 +14,10 @@ interface UseTagOverflowCalculationResult {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Custom hook to calculate how many tags fit
|
|
18
|
-
*
|
|
19
|
-
* Tags occupy the full container width. Non-tag elements (label, clear button,
|
|
20
|
-
* expand control) live in a separate header row and don't affect the calculation.
|
|
17
|
+
* Custom hook to calculate how many tags fit on the first row of the tags-area.
|
|
21
18
|
*/
|
|
22
19
|
export const useTagOverflowCalculation = ({
|
|
23
|
-
|
|
20
|
+
tagsAreaRef,
|
|
24
21
|
measurementRef,
|
|
25
22
|
totalItems,
|
|
26
23
|
expanded,
|
|
@@ -31,11 +28,11 @@ export const useTagOverflowCalculation = ({
|
|
|
31
28
|
});
|
|
32
29
|
|
|
33
30
|
const calculateLayout = useCallback(() => {
|
|
34
|
-
if (!
|
|
31
|
+
if (!tagsAreaRef.current || !measurementRef.current) {
|
|
35
32
|
return;
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
const
|
|
35
|
+
const tagsArea = tagsAreaRef.current;
|
|
39
36
|
const measurementContainer = measurementRef.current;
|
|
40
37
|
|
|
41
38
|
const { tagWidths, gap } = getElementMeasurements(measurementContainer);
|
|
@@ -45,13 +42,13 @@ export const useTagOverflowCalculation = ({
|
|
|
45
42
|
return;
|
|
46
43
|
}
|
|
47
44
|
|
|
48
|
-
const
|
|
45
|
+
const availableWidth = getContainerAvailableWidth(tagsArea);
|
|
49
46
|
|
|
50
|
-
const { count } = fitTagsInRow(tagWidths,
|
|
47
|
+
const { count } = fitTagsInRow(tagWidths, availableWidth, gap);
|
|
51
48
|
const hasOverflow = count < tagWidths.length;
|
|
52
49
|
|
|
53
50
|
setState({ visibleTagCount: count, hasOverflow });
|
|
54
|
-
}, [
|
|
51
|
+
}, [tagsAreaRef, measurementRef]);
|
|
55
52
|
|
|
56
53
|
useLayoutEffect(() => {
|
|
57
54
|
const rafId = requestAnimationFrame(() => {
|
|
@@ -64,21 +61,19 @@ export const useTagOverflowCalculation = ({
|
|
|
64
61
|
});
|
|
65
62
|
});
|
|
66
63
|
|
|
67
|
-
if (
|
|
68
|
-
resizeObserver.observe(
|
|
64
|
+
if (tagsAreaRef.current) {
|
|
65
|
+
resizeObserver.observe(tagsAreaRef.current);
|
|
69
66
|
}
|
|
70
67
|
|
|
71
68
|
return () => {
|
|
72
69
|
cancelAnimationFrame(rafId);
|
|
73
70
|
resizeObserver.disconnect();
|
|
74
71
|
};
|
|
75
|
-
}, [
|
|
72
|
+
}, [tagsAreaRef, measurementRef, totalItems, expanded, calculateLayout]);
|
|
76
73
|
|
|
74
|
+
// While expanded, keep hasOverflow true so a width edge case can't hide the collapse control.
|
|
77
75
|
if (expanded) {
|
|
78
|
-
return {
|
|
79
|
-
visibleTagCount: totalItems,
|
|
80
|
-
hasOverflow: true,
|
|
81
|
-
};
|
|
76
|
+
return { ...state, hasOverflow: true };
|
|
82
77
|
}
|
|
83
78
|
|
|
84
79
|
return state;
|
|
@@ -144,6 +144,24 @@
|
|
|
144
144
|
pointer-events: none;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
// Overlay the native input over the control so `getByRole('checkbox')` clicks
|
|
148
|
+
// land on a real interactive surface. Ark applies `visuallyHiddenStyle` inline
|
|
149
|
+
// (1x1, clipped, off-screen) which Playwright refuses to click; `!important`
|
|
150
|
+
// is required to beat inline-style specificity.
|
|
151
|
+
.hiddenInput {
|
|
152
|
+
position: absolute !important;
|
|
153
|
+
inset-block-start: 0 !important;
|
|
154
|
+
inset-inline-start: 0 !important;
|
|
155
|
+
width: calc(var(--switch-track-width) + var(--thumb-gap) * 2) !important;
|
|
156
|
+
height: calc(var(--thumb-size) + var(--thumb-gap) * 2) !important;
|
|
157
|
+
margin: 0 !important;
|
|
158
|
+
padding: 0 !important;
|
|
159
|
+
clip: auto !important;
|
|
160
|
+
overflow: visible !important;
|
|
161
|
+
opacity: 0 !important;
|
|
162
|
+
cursor: pointer !important;
|
|
163
|
+
}
|
|
164
|
+
|
|
147
165
|
.iconCheck {
|
|
148
166
|
left: var(--icon-spacing);
|
|
149
167
|
}
|