@drivenets/design-system 0.18.1 → 0.20.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 +48 -0
- package/dist/index.cjs +17038 -10114
- package/dist/index.d.cts +1355 -444
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1354 -443
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17073 -10172
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/package.json +41 -41
- 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-checkbox/ds-checkbox.figma.ts +3 -0
- package/src/components/ds-checkbox/ds-checkbox.module.scss +102 -20
- package/src/components/ds-checkbox/ds-checkbox.tsx +30 -2
- package/src/components/ds-checkbox/ds-checkbox.types.ts +10 -0
- package/src/components/ds-code-input/collapsed-code-field/collapsed-code-field.module.scss +169 -0
- package/src/components/ds-code-input/collapsed-code-field/collapsed-code-field.tsx +92 -0
- package/src/components/ds-code-input/collapsed-code-field/index.ts +1 -0
- package/src/components/ds-code-input/ds-code-input-panel/ds-code-input-panel.module.scss +110 -0
- package/src/components/ds-code-input/ds-code-input-panel/ds-code-input-panel.tsx +61 -0
- package/src/components/ds-code-input/ds-code-input-panel/index.ts +1 -0
- package/src/components/ds-code-input/ds-code-input.module.scss +25 -0
- package/src/components/ds-code-input/ds-code-input.tsx +401 -0
- package/src/components/ds-code-input/ds-code-input.types.ts +166 -0
- package/src/components/ds-code-input/ds-code-input.utils.ts +94 -0
- package/src/components/ds-code-input/ds-code-input.utils.unit.test.ts +166 -0
- package/src/components/ds-code-input/index.ts +2 -0
- package/src/components/ds-code-input/use-collapsed-viewport.ts +289 -0
- package/src/components/ds-code-input/use-editor-host.ts +87 -0
- 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-filters-bar/components/ds-filters-bar-builder/ds-filters-bar-builder.ts +11 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-builder/ds-filters-bar-builder.types.ts +41 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-builder/index.ts +1 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-clear-all.ts +11 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-conditions/ds-filters-bar-conditions.ts +11 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-conditions/ds-filters-bar-conditions.types.ts +30 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-conditions/index.ts +1 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-pinned.ts +31 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-query/ds-filters-bar-query.ts +11 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-query/ds-filters-bar-query.types.ts +22 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-query/index.ts +1 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-saved-filters.ts +19 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-search.ts +11 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-summary.ts +11 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-toolbar.ts +11 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-view-switch.ts +11 -0
- package/src/components/ds-filters-bar/components/ds-filters-bar-view.ts +11 -0
- package/src/components/ds-filters-bar/components/index.ts +11 -0
- package/src/components/ds-filters-bar/ds-filters-bar.context.ts +69 -0
- package/src/components/ds-filters-bar/ds-filters-bar.tsx +120 -0
- package/src/components/ds-filters-bar/ds-filters-bar.types.ts +440 -0
- package/src/components/ds-filters-bar/ds-filters-bar.unit.test.ts +216 -0
- package/src/components/ds-filters-bar/ds-filters-bar.utils.ts +141 -0
- package/src/components/ds-filters-bar/index.ts +1 -0
- package/src/components/ds-form-control/ds-form-control.module.scss +18 -0
- package/src/components/ds-form-control/ds-form-control.tsx +61 -19
- package/src/components/ds-form-control/ds-form-control.types.ts +2 -1
- package/src/components/ds-icon/custom-icons/index.ts +2 -0
- package/src/components/ds-icon/custom-icons/special-adv-filters.tsx +28 -0
- 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-pin-toggle/ds-pin-toggle.figma.ts +27 -0
- package/src/components/ds-pin-toggle/ds-pin-toggle.module.scss +41 -0
- package/src/components/ds-pin-toggle/ds-pin-toggle.tsx +43 -0
- package/src/components/ds-pin-toggle/ds-pin-toggle.types.ts +29 -0
- package/src/components/ds-pin-toggle/index.ts +2 -0
- package/src/components/ds-popover/components/popover-trigger/index.ts +1 -0
- package/src/components/ds-popover/components/popover-trigger/popover-trigger.tsx +15 -0
- package/src/components/ds-popover/ds-popover.context.ts +18 -0
- package/src/components/ds-popover/ds-popover.hover-intent.ts +65 -0
- package/src/components/ds-popover/ds-popover.tsx +164 -41
- package/src/components/ds-popover/ds-popover.types.ts +65 -0
- package/src/components/ds-popover/ds-popover.utils.ts +60 -0
- package/src/components/ds-popover/ds-popover.utils.unit.test.ts +25 -0
- package/src/components/ds-saved-filters/components/ds-saved-filters-delete-confirm.tsx +53 -0
- package/src/components/ds-saved-filters/components/ds-saved-filters-name-entry.tsx +64 -0
- package/src/components/ds-saved-filters/components/ds-saved-filters-name-modal.tsx +50 -0
- package/src/components/ds-saved-filters/components/ds-saved-filters-row/ds-saved-filters-row.module.scss +68 -0
- package/src/components/ds-saved-filters/components/ds-saved-filters-row/ds-saved-filters-row.tsx +83 -0
- package/src/components/ds-saved-filters/components/ds-saved-filters-row/index.ts +1 -0
- package/src/components/ds-saved-filters/components/ds-saved-filters-save.tsx +164 -0
- package/src/components/ds-saved-filters/components/ds-saved-filters-trigger/ds-saved-filters-trigger.module.scss +21 -0
- package/src/components/ds-saved-filters/components/ds-saved-filters-trigger/ds-saved-filters-trigger.tsx +237 -0
- package/src/components/ds-saved-filters/components/ds-saved-filters-trigger/index.ts +1 -0
- package/src/components/ds-saved-filters/components/index.ts +2 -0
- package/src/components/ds-saved-filters/ds-saved-filters.ts +6 -0
- package/src/components/ds-saved-filters/ds-saved-filters.types.ts +106 -0
- package/src/components/ds-saved-filters/ds-saved-filters.utils.ts +35 -0
- package/src/components/ds-saved-filters/hooks/index.ts +1 -0
- package/src/components/ds-saved-filters/hooks/use-picker-outside-dismiss.ts +42 -0
- package/src/components/ds-saved-filters/index.ts +2 -0
- package/src/components/ds-segment-group/ds-segment-group.module.scss +65 -37
- package/src/components/ds-segment-group/ds-segment-group.tsx +17 -5
- package/src/components/ds-segment-group/ds-segment-group.types.ts +20 -5
- package/src/components/ds-segment-group/figma/ds-segment-group-v2-item.figma.ts +109 -0
- package/src/components/ds-segment-group/figma/ds-segment-group-v2.figma.ts +31 -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/ds-tag.figma.ts +23 -3
- package/src/components/ds-tag/ds-tag.module.scss +71 -7
- package/src/components/ds-tag/ds-tag.tsx +115 -14
- package/src/components/ds-tag/ds-tag.types.ts +70 -3
- package/src/components/ds-tag/ds-tag.unit.test.ts +23 -4
- package/src/components/ds-tag/index.ts +2 -2
- package/src/components/ds-text-input/ds-input-field-v02.figma.ts +2 -0
- package/src/components/ds-toggle/ds-toggle.module.scss +18 -0
- package/src/components/ds-toggle/ds-toggle.tsx +15 -9
- package/src/components/ds-toggle/ds-toggle.types.ts +6 -0
- package/src/components/ds-toggle-filter-data/ds-toggle-filter-data.module.scss +69 -0
- package/src/components/ds-toggle-filter-data/ds-toggle-filter-data.tsx +55 -0
- package/src/components/ds-toggle-filter-data/ds-toggle-filter-data.types.ts +43 -0
- package/src/components/ds-toggle-filter-data/index.ts +1 -0
- package/src/components/ds-tooltip/ds-tooltip.module.scss +8 -0
- package/src/components/ds-tooltip/ds-tooltip.tsx +7 -3
- 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/components/ds-tree/ds-tree.tsx +10 -10
- package/src/components/ds-tree/ds-tree.types.ts +40 -7
- package/src/index.ts +8 -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
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
applyTextSelection,
|
|
4
|
+
canonicalizeLf,
|
|
5
|
+
getAdditionalLineCount,
|
|
6
|
+
getLineAtIndex,
|
|
7
|
+
getLineIndexAtOffset,
|
|
8
|
+
getLogicalLineCount,
|
|
9
|
+
getSelectedLineCount,
|
|
10
|
+
type TextSelection,
|
|
11
|
+
} from './ds-code-input.utils';
|
|
12
|
+
|
|
13
|
+
const multiline = 'SELECT *\nFROM devices\nWHERE id = 1';
|
|
14
|
+
|
|
15
|
+
const stubTextarea = (value: string) => {
|
|
16
|
+
const setSelectionRange = vi.fn();
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
element: { value, setSelectionRange } as unknown as HTMLTextAreaElement,
|
|
20
|
+
setSelectionRange,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('canonicalizeLf', () => {
|
|
25
|
+
it('rewrites CRLF to LF', () => {
|
|
26
|
+
expect(canonicalizeLf('a\r\nb')).toBe('a\nb');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('rewrites a lone CR to LF', () => {
|
|
30
|
+
expect(canonicalizeLf('a\rb')).toBe('a\nb');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('leaves LF unchanged', () => {
|
|
34
|
+
expect(canonicalizeLf('a\nb')).toBe('a\nb');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('normalizes mixed endings in one string', () => {
|
|
38
|
+
expect(canonicalizeLf('a\r\nb\rc\nd')).toBe('a\nb\nc\nd');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('getLogicalLineCount', () => {
|
|
43
|
+
it('counts an empty string as one logical line', () => {
|
|
44
|
+
expect(getLogicalLineCount('')).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('counts a trailing newline as an extra empty line', () => {
|
|
48
|
+
expect(getLogicalLineCount('a\n')).toBe(2);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('counts each logical line in multiline text', () => {
|
|
52
|
+
expect(getLogicalLineCount(multiline)).toBe(3);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('getAdditionalLineCount', () => {
|
|
57
|
+
it('is zero for a single line, including empty text', () => {
|
|
58
|
+
expect(getAdditionalLineCount('')).toBe(0);
|
|
59
|
+
expect(getAdditionalLineCount('SELECT *')).toBe(0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('is the number of lines beyond the first', () => {
|
|
63
|
+
expect(getAdditionalLineCount('a\n')).toBe(1);
|
|
64
|
+
expect(getAdditionalLineCount(multiline)).toBe(2);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('getLineAtIndex', () => {
|
|
69
|
+
it('returns the line at a valid index', () => {
|
|
70
|
+
expect(getLineAtIndex(multiline, 0)).toBe('SELECT *');
|
|
71
|
+
expect(getLineAtIndex(multiline, 1)).toBe('FROM devices');
|
|
72
|
+
expect(getLineAtIndex(multiline, 2)).toBe('WHERE id = 1');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('returns an empty string when the index is out of range', () => {
|
|
76
|
+
expect(getLineAtIndex(multiline, -1)).toBe('');
|
|
77
|
+
expect(getLineAtIndex(multiline, 3)).toBe('');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('splits CRLF text on logical lines', () => {
|
|
81
|
+
expect(getLineAtIndex('hello\r\nworld', 0)).toBe('hello');
|
|
82
|
+
expect(getLineAtIndex('hello\r\nworld', 1)).toBe('world');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('getLineIndexAtOffset', () => {
|
|
87
|
+
const text = 'hello\nworld';
|
|
88
|
+
|
|
89
|
+
it('returns the first line at the start of the text', () => {
|
|
90
|
+
expect(getLineIndexAtOffset(text, 0)).toBe(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('stays on the previous line when the offset sits on a newline', () => {
|
|
94
|
+
expect(getLineIndexAtOffset(text, 5)).toBe(0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('moves to the next line once the offset is past the newline', () => {
|
|
98
|
+
expect(getLineIndexAtOffset(text, 6)).toBe(1);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('clamps offsets below 0 and past the text length', () => {
|
|
102
|
+
expect(getLineIndexAtOffset(text, -4)).toBe(0);
|
|
103
|
+
expect(getLineIndexAtOffset(text, 100)).toBe(1);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('maps offsets in CRLF text onto the same logical lines as LF', () => {
|
|
107
|
+
const crlf = 'hello\r\nworld';
|
|
108
|
+
|
|
109
|
+
expect(getLineIndexAtOffset(crlf, 5)).toBe(0);
|
|
110
|
+
expect(getLineIndexAtOffset(crlf, 6)).toBe(1);
|
|
111
|
+
expect(getLineIndexAtOffset(crlf, crlf.length)).toBe(1);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('getSelectedLineCount', () => {
|
|
116
|
+
const text = 'a\nb\nc';
|
|
117
|
+
|
|
118
|
+
it('is zero when the range is collapsed, even in multiline text', () => {
|
|
119
|
+
expect(getSelectedLineCount(text, 0, 0)).toBe(0);
|
|
120
|
+
expect(getSelectedLineCount(text, 2, 2)).toBe(0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('is one when the range stays on a single line', () => {
|
|
124
|
+
expect(getSelectedLineCount(text, 0, 1)).toBe(1);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('counts each line the range crosses', () => {
|
|
128
|
+
expect(getSelectedLineCount(text, 0, 2)).toBe(2);
|
|
129
|
+
expect(getSelectedLineCount(text, 0, text.length)).toBe(3);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('treats a reversed range the same as start-to-end', () => {
|
|
133
|
+
expect(getSelectedLineCount(text, 2, 0)).toBe(getSelectedLineCount(text, 0, 2));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('stays on one line when the range ends on a newline', () => {
|
|
137
|
+
expect(getSelectedLineCount('hello\nworld', 0, 5)).toBe(1);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('applyTextSelection', () => {
|
|
142
|
+
it('restores the range and direction on the element', () => {
|
|
143
|
+
const { element, setSelectionRange } = stubTextarea('abc');
|
|
144
|
+
const selection: TextSelection = { start: 1, end: 2, direction: 'forward' };
|
|
145
|
+
|
|
146
|
+
applyTextSelection(element, selection);
|
|
147
|
+
|
|
148
|
+
expect(setSelectionRange).toHaveBeenCalledWith(1, 2, 'forward');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('clamps the range to the current value length', () => {
|
|
152
|
+
const { element, setSelectionRange } = stubTextarea('abc');
|
|
153
|
+
|
|
154
|
+
applyTextSelection(element, { start: 10, end: 20, direction: 'none' });
|
|
155
|
+
|
|
156
|
+
expect(setSelectionRange).toHaveBeenCalledWith(3, 3, 'none');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('clamps only the end when start is still in range', () => {
|
|
160
|
+
const { element, setSelectionRange } = stubTextarea('abc');
|
|
161
|
+
|
|
162
|
+
applyTextSelection(element, { start: 1, end: 99, direction: 'backward' });
|
|
163
|
+
|
|
164
|
+
expect(setSelectionRange).toHaveBeenCalledWith(1, 3, 'backward');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { type Dispatch, type RefObject, type SetStateAction, useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
getAdditionalLineCount,
|
|
4
|
+
getLineAtIndex,
|
|
5
|
+
getLineIndexAtOffset,
|
|
6
|
+
getLogicalLineCount,
|
|
7
|
+
getSelectedLineCount,
|
|
8
|
+
getVisibleLineIndex,
|
|
9
|
+
measureTextWidth,
|
|
10
|
+
scrollToLogicalLine,
|
|
11
|
+
} from './ds-code-input.utils';
|
|
12
|
+
|
|
13
|
+
const CARET_INLINE_PADDING_PX = 16;
|
|
14
|
+
|
|
15
|
+
export type CollapsedIndicator =
|
|
16
|
+
| { kind: 'plusN'; count: number }
|
|
17
|
+
| { kind: 'line'; current: number; total: number }
|
|
18
|
+
| { kind: 'selection'; selected: number; total: number };
|
|
19
|
+
|
|
20
|
+
export interface CollapsedViewport {
|
|
21
|
+
focused: boolean;
|
|
22
|
+
visibleLine: number;
|
|
23
|
+
caretLine: number;
|
|
24
|
+
totalLines: number;
|
|
25
|
+
indicator: CollapsedIndicator | null;
|
|
26
|
+
fadeStart: boolean;
|
|
27
|
+
fadeEnd: boolean;
|
|
28
|
+
showEllipsis: boolean;
|
|
29
|
+
echoText: string;
|
|
30
|
+
revealCaretLine: () => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CollapsedViewportHandlers {
|
|
34
|
+
onSelect: () => void;
|
|
35
|
+
onKeyUp: () => void;
|
|
36
|
+
onClick: () => void;
|
|
37
|
+
onScroll: () => void;
|
|
38
|
+
onFocus: () => void;
|
|
39
|
+
onBlur: () => void;
|
|
40
|
+
onBeforeInput: () => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ViewportSnapshot {
|
|
44
|
+
totalLines: number;
|
|
45
|
+
caretLine: number;
|
|
46
|
+
visibleLine: number;
|
|
47
|
+
selectedLines: number;
|
|
48
|
+
clipped: boolean;
|
|
49
|
+
fadeStart: boolean;
|
|
50
|
+
fadeEnd: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const initialSnapshot: ViewportSnapshot = {
|
|
54
|
+
totalLines: 1,
|
|
55
|
+
caretLine: 1,
|
|
56
|
+
visibleLine: 1,
|
|
57
|
+
selectedLines: 0,
|
|
58
|
+
clipped: false,
|
|
59
|
+
fadeStart: false,
|
|
60
|
+
fadeEnd: false,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const snapshotsEqual = (left: ViewportSnapshot, right: ViewportSnapshot) =>
|
|
64
|
+
left.totalLines === right.totalLines &&
|
|
65
|
+
left.caretLine === right.caretLine &&
|
|
66
|
+
left.visibleLine === right.visibleLine &&
|
|
67
|
+
left.selectedLines === right.selectedLines &&
|
|
68
|
+
left.clipped === right.clipped &&
|
|
69
|
+
left.fadeStart === right.fadeStart &&
|
|
70
|
+
left.fadeEnd === right.fadeEnd;
|
|
71
|
+
|
|
72
|
+
const readViewport = (textarea: HTMLTextAreaElement): ViewportSnapshot => {
|
|
73
|
+
const value = textarea.value;
|
|
74
|
+
const totalLines = getLogicalLineCount(value);
|
|
75
|
+
const caretLine = getLineIndexAtOffset(value, textarea.selectionStart) + 1;
|
|
76
|
+
const visibleLine = Math.min(getVisibleLineIndex(textarea) + 1, totalLines);
|
|
77
|
+
const selectedLines = getSelectedLineCount(value, textarea.selectionStart, textarea.selectionEnd);
|
|
78
|
+
const visibleText = getLineAtIndex(value, visibleLine - 1);
|
|
79
|
+
const clipped = measureTextWidth(textarea, visibleText) > textarea.clientWidth;
|
|
80
|
+
const fadeStart = textarea.scrollLeft > 0;
|
|
81
|
+
const fadeEnd = textarea.scrollLeft + textarea.clientWidth < textarea.scrollWidth - 1;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
totalLines,
|
|
85
|
+
caretLine,
|
|
86
|
+
visibleLine,
|
|
87
|
+
selectedLines,
|
|
88
|
+
clipped,
|
|
89
|
+
fadeStart,
|
|
90
|
+
fadeEnd,
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const commitViewport = (
|
|
95
|
+
textarea: HTMLTextAreaElement,
|
|
96
|
+
setSnapshot: Dispatch<SetStateAction<ViewportSnapshot>>,
|
|
97
|
+
) => {
|
|
98
|
+
const next = readViewport(textarea);
|
|
99
|
+
|
|
100
|
+
setSnapshot((current) => (snapshotsEqual(current, next) ? current : next));
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The collapsed field shows one logical line. This hook tracks which line is
|
|
105
|
+
* visible, where the caret is, overflow fades, and the +N / Ln x/y / selection chip.
|
|
106
|
+
*/
|
|
107
|
+
export const useCollapsedViewport = (
|
|
108
|
+
textareaRef: RefObject<HTMLTextAreaElement | null>,
|
|
109
|
+
value: string,
|
|
110
|
+
ready: boolean,
|
|
111
|
+
collapsed: boolean,
|
|
112
|
+
): CollapsedViewport & CollapsedViewportHandlers => {
|
|
113
|
+
const [focused, setFocused] = useState(false);
|
|
114
|
+
const [snapshot, setSnapshot] = useState(initialSnapshot);
|
|
115
|
+
|
|
116
|
+
const sync = () => {
|
|
117
|
+
const textarea = textareaRef.current;
|
|
118
|
+
|
|
119
|
+
if (textarea) {
|
|
120
|
+
commitViewport(textarea, setSnapshot);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Scroll so the caret line is the one visible row (used after collapse).
|
|
125
|
+
const revealCaretLine = () => {
|
|
126
|
+
const textarea = textareaRef.current;
|
|
127
|
+
|
|
128
|
+
if (!textarea) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const lineIndex = getLineIndexAtOffset(textarea.value, textarea.selectionStart);
|
|
133
|
+
|
|
134
|
+
scrollToLogicalLine(textarea, lineIndex);
|
|
135
|
+
textarea.scrollLeft = Math.max(0, textarea.scrollLeft - CARET_INLINE_PADDING_PX);
|
|
136
|
+
setSnapshot((current) => ({
|
|
137
|
+
...current,
|
|
138
|
+
visibleLine: lineIndex + 1,
|
|
139
|
+
caretLine: lineIndex + 1,
|
|
140
|
+
}));
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const onSelect = sync;
|
|
144
|
+
const onKeyUp = sync;
|
|
145
|
+
const onClick = sync;
|
|
146
|
+
|
|
147
|
+
const onScroll = () => {
|
|
148
|
+
if (collapsed) {
|
|
149
|
+
sync();
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const onFocus = () => {
|
|
154
|
+
if (!collapsed) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
setFocused(true);
|
|
159
|
+
revealCaretLine();
|
|
160
|
+
sync();
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const onBlur = () => {
|
|
164
|
+
if (!collapsed) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const textarea = textareaRef.current;
|
|
169
|
+
|
|
170
|
+
setFocused(false);
|
|
171
|
+
|
|
172
|
+
if (textarea) {
|
|
173
|
+
// Idle field always shows the start of the document.
|
|
174
|
+
scrollToLogicalLine(textarea, 0);
|
|
175
|
+
textarea.scrollLeft = 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
sync();
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const onBeforeInput = () => {
|
|
182
|
+
if (!collapsed) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const textarea = textareaRef.current;
|
|
187
|
+
|
|
188
|
+
if (!textarea) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const next = readViewport(textarea);
|
|
193
|
+
|
|
194
|
+
// Typing while looking at another line: jump to the caret line first.
|
|
195
|
+
if (next.caretLine !== next.visibleLine) {
|
|
196
|
+
revealCaretLine();
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
const textarea = textareaRef.current;
|
|
202
|
+
|
|
203
|
+
if (!textarea || !ready) {
|
|
204
|
+
setFocused(false);
|
|
205
|
+
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const syncFromDom = () => commitViewport(textarea, setSnapshot);
|
|
210
|
+
|
|
211
|
+
// React's onSelect is backed by selectionchange, so programmatic
|
|
212
|
+
// `select` events (tests, setSelectionRange) still need a native listener.
|
|
213
|
+
textarea.addEventListener('select', syncFromDom);
|
|
214
|
+
|
|
215
|
+
// Line-by-line, not native pixel scroll — a one-row textarea would
|
|
216
|
+
// otherwise feel broken. React's onWheel is passive, so this stays native.
|
|
217
|
+
const handleWheel = (event: WheelEvent) => {
|
|
218
|
+
if (document.activeElement !== textarea || event.deltaY === 0) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const next = readViewport(textarea);
|
|
223
|
+
const direction = event.deltaY > 0 ? 1 : -1;
|
|
224
|
+
const nextLine = next.visibleLine + direction;
|
|
225
|
+
|
|
226
|
+
if (nextLine < 1 || nextLine > next.totalLines) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
event.preventDefault();
|
|
231
|
+
scrollToLogicalLine(textarea, nextLine - 1);
|
|
232
|
+
syncFromDom();
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
let observer: ResizeObserver | undefined;
|
|
236
|
+
|
|
237
|
+
if (collapsed) {
|
|
238
|
+
textarea.addEventListener('wheel', handleWheel, { passive: false });
|
|
239
|
+
setFocused(document.activeElement === textarea);
|
|
240
|
+
observer = new ResizeObserver(syncFromDom);
|
|
241
|
+
observer.observe(textarea);
|
|
242
|
+
} else {
|
|
243
|
+
setFocused(false);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
syncFromDom();
|
|
247
|
+
|
|
248
|
+
return () => {
|
|
249
|
+
textarea.removeEventListener('select', syncFromDom);
|
|
250
|
+
textarea.removeEventListener('wheel', handleWheel);
|
|
251
|
+
observer?.disconnect();
|
|
252
|
+
};
|
|
253
|
+
}, [ready, collapsed, value, textareaRef]);
|
|
254
|
+
|
|
255
|
+
const additional = getAdditionalLineCount(value);
|
|
256
|
+
// Caret line, painted as a static stand-in while the real textarea lives in the overlay.
|
|
257
|
+
const echoText = getLineAtIndex(value, snapshot.caretLine - 1);
|
|
258
|
+
|
|
259
|
+
// Selection spanning lines > focused line position > idle +N.
|
|
260
|
+
let indicator: CollapsedIndicator | null = null;
|
|
261
|
+
|
|
262
|
+
if (snapshot.selectedLines > 1) {
|
|
263
|
+
indicator = { kind: 'selection', selected: snapshot.selectedLines, total: snapshot.totalLines };
|
|
264
|
+
} else if (focused && additional > 0) {
|
|
265
|
+
indicator = { kind: 'line', current: snapshot.visibleLine, total: snapshot.totalLines };
|
|
266
|
+
} else if (!focused && additional > 0) {
|
|
267
|
+
indicator = { kind: 'plusN', count: additional };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
focused,
|
|
272
|
+
visibleLine: snapshot.visibleLine,
|
|
273
|
+
caretLine: snapshot.caretLine,
|
|
274
|
+
totalLines: Math.max(snapshot.totalLines, getLogicalLineCount(value)),
|
|
275
|
+
indicator,
|
|
276
|
+
fadeStart: focused && snapshot.fadeStart,
|
|
277
|
+
fadeEnd: focused && snapshot.fadeEnd,
|
|
278
|
+
showEllipsis: (!focused || !collapsed || !ready) && snapshot.clipped,
|
|
279
|
+
echoText,
|
|
280
|
+
revealCaretLine,
|
|
281
|
+
onSelect,
|
|
282
|
+
onKeyUp,
|
|
283
|
+
onClick,
|
|
284
|
+
onScroll,
|
|
285
|
+
onFocus,
|
|
286
|
+
onBlur,
|
|
287
|
+
onBeforeInput,
|
|
288
|
+
};
|
|
289
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type RefObject, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
import { applyTextSelection, type TextSelection } from './ds-code-input.utils';
|
|
3
|
+
|
|
4
|
+
interface UseEditorHostOptions {
|
|
5
|
+
isExpanded: boolean;
|
|
6
|
+
collapsedSlotRef: RefObject<HTMLDivElement | null>;
|
|
7
|
+
overlaySlotRef: RefObject<HTMLDivElement | null>;
|
|
8
|
+
hostClassName?: string;
|
|
9
|
+
textareaRef: RefObject<HTMLTextAreaElement | null>;
|
|
10
|
+
pendingSelectionRef: RefObject<TextSelection | null>;
|
|
11
|
+
shouldFocus: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* One host div for the textarea. Attach it to the current slot (collapsed or
|
|
16
|
+
* overlay), then the component portals the same textarea into that host.
|
|
17
|
+
*/
|
|
18
|
+
export const useEditorHost = ({
|
|
19
|
+
isExpanded,
|
|
20
|
+
collapsedSlotRef,
|
|
21
|
+
overlaySlotRef,
|
|
22
|
+
hostClassName,
|
|
23
|
+
textareaRef,
|
|
24
|
+
pendingSelectionRef,
|
|
25
|
+
shouldFocus,
|
|
26
|
+
}: UseEditorHostOptions) => {
|
|
27
|
+
const [hostReady, setHostReady] = useState(false);
|
|
28
|
+
const hostRef = useRef<HTMLDivElement | null>(null);
|
|
29
|
+
// True only while appendChild runs, so the component can swallow the
|
|
30
|
+
// focus/blur that moving a focused node fires.
|
|
31
|
+
const isMovingHostRef = useRef(false);
|
|
32
|
+
|
|
33
|
+
useLayoutEffect(() => {
|
|
34
|
+
if (hostRef.current === null) {
|
|
35
|
+
hostRef.current = document.createElement('div');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const host = hostRef.current;
|
|
39
|
+
const dest = isExpanded ? overlaySlotRef.current : collapsedSlotRef.current;
|
|
40
|
+
|
|
41
|
+
if (!dest) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
isMovingHostRef.current = true;
|
|
46
|
+
host.className = hostClassName ?? '';
|
|
47
|
+
|
|
48
|
+
if (host.parentElement !== dest) {
|
|
49
|
+
dest.appendChild(host);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const textarea = textareaRef.current;
|
|
53
|
+
const pending = pendingSelectionRef.current;
|
|
54
|
+
|
|
55
|
+
// Put the caret back, then focus only if the user opened the overlay
|
|
56
|
+
// (not if it opened from outside).
|
|
57
|
+
if (textarea && pending) {
|
|
58
|
+
applyTextSelection(textarea, pending);
|
|
59
|
+
pendingSelectionRef.current = null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (textarea && shouldFocus) {
|
|
63
|
+
textarea.focus({ preventScroll: true });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
isMovingHostRef.current = false;
|
|
67
|
+
|
|
68
|
+
if (!hostReady) {
|
|
69
|
+
setHostReady(true);
|
|
70
|
+
}
|
|
71
|
+
}, [
|
|
72
|
+
isExpanded,
|
|
73
|
+
hostReady,
|
|
74
|
+
shouldFocus,
|
|
75
|
+
hostClassName,
|
|
76
|
+
collapsedSlotRef,
|
|
77
|
+
overlaySlotRef,
|
|
78
|
+
textareaRef,
|
|
79
|
+
pendingSelectionRef,
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
host: hostRef.current,
|
|
84
|
+
hostReady,
|
|
85
|
+
isMovingHostRef,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { DsIllustration } from '../ds-illustration';
|
|
3
|
+
import { DsStack } from '../ds-stack';
|
|
4
|
+
import { DsTypography } from '../ds-typography';
|
|
5
|
+
import styles from './ds-empty-state.module.scss';
|
|
6
|
+
import { defaultDsEmptyStateMessages, type DsEmptyStateProps } from './ds-empty-state.types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Centered empty-state cluster: illustration, message, and an optional action.
|
|
10
|
+
*
|
|
11
|
+
* Use `noData` when the collection has no records and `noMatches` when a search
|
|
12
|
+
* or filter yielded zero items. Pass `action` for a consumer-owned button.
|
|
13
|
+
*/
|
|
14
|
+
const DsEmptyState = ({
|
|
15
|
+
variant = 'noData',
|
|
16
|
+
message,
|
|
17
|
+
illustration,
|
|
18
|
+
action,
|
|
19
|
+
className,
|
|
20
|
+
style,
|
|
21
|
+
ref,
|
|
22
|
+
}: DsEmptyStateProps) => {
|
|
23
|
+
illustration ??= <DsIllustration variant={variant === 'noMatches' ? 'no-search-result' : 'empty-inbox'} />;
|
|
24
|
+
const resolvedMessage = message ?? defaultDsEmptyStateMessages[variant];
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div ref={ref} className={classNames(styles.root, className)} style={style} role="status">
|
|
28
|
+
<DsStack direction="column" alignItems="center" justifyContent="center" gap="var(--xs)">
|
|
29
|
+
<div className={styles.illustration} aria-hidden="true">
|
|
30
|
+
{illustration}
|
|
31
|
+
</div>
|
|
32
|
+
<DsStack direction="column" alignItems="center" gap="var(--lg)">
|
|
33
|
+
<DsTypography variant="body-md-reg" color="var(--color-dap-gray-700)">
|
|
34
|
+
{resolvedMessage}
|
|
35
|
+
</DsTypography>
|
|
36
|
+
{action}
|
|
37
|
+
</DsStack>
|
|
38
|
+
</DsStack>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
DsEmptyState.displayName = 'DsEmptyState';
|
|
44
|
+
|
|
45
|
+
export default DsEmptyState;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode, Ref } from 'react';
|
|
2
|
+
|
|
3
|
+
export const dsEmptyStateVariants = ['noData', 'noMatches'] as const;
|
|
4
|
+
export type DsEmptyStateVariant = (typeof dsEmptyStateVariants)[number];
|
|
5
|
+
|
|
6
|
+
export const defaultDsEmptyStateMessages: Record<DsEmptyStateVariant, string> = Object.freeze({
|
|
7
|
+
noData: 'No data to display.',
|
|
8
|
+
noMatches: 'No matching records found.',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export interface DsEmptyStateProps {
|
|
12
|
+
/**
|
|
13
|
+
* Which empty-state reason to present.
|
|
14
|
+
* @default 'noData'
|
|
15
|
+
*/
|
|
16
|
+
variant?: DsEmptyStateVariant;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Overrides the default copy for the active variant.
|
|
20
|
+
*/
|
|
21
|
+
message?: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Replaces the bundled illustration for the active variant.
|
|
25
|
+
*/
|
|
26
|
+
illustration?: ReactNode;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Optional action, typically a button. Omitted from the table's inferred default.
|
|
30
|
+
*/
|
|
31
|
+
action?: ReactNode;
|
|
32
|
+
|
|
33
|
+
className?: string;
|
|
34
|
+
|
|
35
|
+
style?: CSSProperties;
|
|
36
|
+
|
|
37
|
+
ref?: Ref<HTMLDivElement>;
|
|
38
|
+
}
|
|
@@ -4,7 +4,10 @@ import { FileUpload } from './components/file-upload';
|
|
|
4
4
|
import { useFileUpload } from './hooks';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* File upload with built-in state management (progress, retries, cancellation).
|
|
8
|
+
*
|
|
9
|
+
* Pass an `adapter` implementing `FileUploadAdapter` to connect it to your backend:
|
|
10
|
+
* `const adapter = getSimpleFileUploadAdapter('/api/upload')`.
|
|
8
11
|
*/
|
|
9
12
|
const DsFileUpload: React.FC<DsFileUploadProps> = ({
|
|
10
13
|
adapter,
|
package/src/components/ds-filters-bar/components/ds-filters-bar-builder/ds-filters-bar-builder.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import { useDsFiltersBarContext } from '../../ds-filters-bar.context';
|
|
3
|
+
import type { DsFiltersBarBuilderProps } from './ds-filters-bar-builder.types';
|
|
4
|
+
|
|
5
|
+
export const Builder: FC<DsFiltersBarBuilderProps> = () => {
|
|
6
|
+
useDsFiltersBarContext();
|
|
7
|
+
|
|
8
|
+
return null;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
Builder.displayName = 'DsFiltersBar.Builder';
|