@drivenets/design-system 0.0.1
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 +9 -0
- package/LICENSE +201 -0
- package/dist/index.cjs +5394 -0
- package/dist/index.css +1 -0
- package/dist/index.d.cts +9157 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +9157 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5335 -0
- package/dist/index.js.map +1 -0
- package/package.json +104 -0
- package/src/components/ds-alert-banner/ds-alert-banner.module.scss +116 -0
- package/src/components/ds-alert-banner/ds-alert-banner.tsx +115 -0
- package/src/components/ds-alert-banner/ds-alert-banner.types.tsx +47 -0
- package/src/components/ds-alert-banner/index.tsx +2 -0
- package/src/components/ds-breadcrumb/ds-breadcrumb.module.scss +99 -0
- package/src/components/ds-breadcrumb/ds-breadcrumb.tsx +94 -0
- package/src/components/ds-breadcrumb/ds-breadcrumb.types.tsx +32 -0
- package/src/components/ds-breadcrumb/index.tsx +2 -0
- package/src/components/ds-button/ds-button.tsx +12 -0
- package/src/components/ds-button/ds-button.types.ts +6 -0
- package/src/components/ds-button/index.ts +2 -0
- package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.module.scss +257 -0
- package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.tsx +34 -0
- package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.types.ts +41 -0
- package/src/components/ds-button/versions/ds-button-legacy/index.ts +2 -0
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss +580 -0
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.tsx +49 -0
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.types.ts +41 -0
- package/src/components/ds-button/versions/ds-button-new/index.ts +2 -0
- package/src/components/ds-checkbox/ds-checkbox.module.scss +130 -0
- package/src/components/ds-checkbox/ds-checkbox.tsx +55 -0
- package/src/components/ds-checkbox/ds-checkbox.types.tsx +27 -0
- package/src/components/ds-checkbox/index.tsx +2 -0
- package/src/components/ds-chip/ds-chip.module.scss +71 -0
- package/src/components/ds-chip/ds-chip.tsx +73 -0
- package/src/components/ds-chip/ds-chip.types.tsx +46 -0
- package/src/components/ds-chip/index.tsx +2 -0
- package/src/components/ds-chip-group/ds-chip-group.module.scss +59 -0
- package/src/components/ds-chip-group/ds-chip-group.tsx +122 -0
- package/src/components/ds-chip-group/ds-chip-group.types.ts +52 -0
- package/src/components/ds-chip-group/hooks/use-chip-row-calculation.ts +76 -0
- package/src/components/ds-chip-group/index.ts +2 -0
- package/src/components/ds-confirmation/ds-confirmation.module.scss +58 -0
- package/src/components/ds-confirmation/ds-confirmation.tsx +121 -0
- package/src/components/ds-confirmation/ds-confirmation.types.tsx +28 -0
- package/src/components/ds-confirmation/index.ts +2 -0
- package/src/components/ds-dialog/ds-dialog.module.scss +50 -0
- package/src/components/ds-dialog/ds-dialog.tsx +70 -0
- package/src/components/ds-dialog/ds-dialog.types.tsx +48 -0
- package/src/components/ds-dialog/index.tsx +2 -0
- package/src/components/ds-drawer/ds-drawer.module.scss +212 -0
- package/src/components/ds-drawer/ds-drawer.tsx +160 -0
- package/src/components/ds-drawer/ds-drawer.types.tsx +54 -0
- package/src/components/ds-drawer/index.ts +2 -0
- package/src/components/ds-dropdown-menu/ds-dropdown-menu.module.scss +123 -0
- package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +396 -0
- package/src/components/ds-dropdown-menu/ds-dropdown-menu.types.tsx +262 -0
- package/src/components/ds-dropdown-menu/index.tsx +2 -0
- package/src/components/ds-expandable-text-input/ds-expandable-text-input.module.scss +70 -0
- package/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx +106 -0
- package/src/components/ds-expandable-text-input/ds-expandable-text-input.types.tsx +19 -0
- package/src/components/ds-expandable-text-input/index.ts +2 -0
- package/src/components/ds-file-upload/components/file-upload/file-upload.module.scss +90 -0
- package/src/components/ds-file-upload/components/file-upload/file-upload.tsx +106 -0
- package/src/components/ds-file-upload/components/file-upload/file-upload.types.tsx +94 -0
- package/src/components/ds-file-upload/components/file-upload/index.ts +2 -0
- package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.module.scss +125 -0
- package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.tsx +143 -0
- package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.types.tsx +26 -0
- package/src/components/ds-file-upload/components/file-upload-item/index.ts +2 -0
- package/src/components/ds-file-upload/ds-file-upload-api.types.ts +38 -0
- package/src/components/ds-file-upload/ds-file-upload.tsx +46 -0
- package/src/components/ds-file-upload/ds-file-upload.types.tsx +54 -0
- package/src/components/ds-file-upload/errors/file-upload-errors.ts +27 -0
- package/src/components/ds-file-upload/hooks/index.ts +2 -0
- package/src/components/ds-file-upload/hooks/use-file-upload.ts +350 -0
- package/src/components/ds-file-upload/index.ts +7 -0
- package/src/components/ds-file-upload/types/accept-types.ts +106 -0
- package/src/components/ds-file-upload/utils/accept-utils.ts +25 -0
- package/src/components/ds-file-upload/utils/constants.ts +21 -0
- package/src/components/ds-file-upload/utils/file-factory.ts +18 -0
- package/src/components/ds-file-upload/utils/format-file-size.ts +15 -0
- package/src/components/ds-file-upload/utils/generate-helper-text.ts +39 -0
- package/src/components/ds-file-upload/utils/get-error-message.ts +17 -0
- package/src/components/ds-file-upload/utils/is-file-equal.ts +8 -0
- package/src/components/ds-form-control/ds-form-control.module.scss +135 -0
- package/src/components/ds-form-control/ds-form-control.tsx +94 -0
- package/src/components/ds-form-control/ds-form-control.types.tsx +58 -0
- package/src/components/ds-form-control/index.tsx +2 -0
- package/src/components/ds-grid/ds-grid.module.scss +0 -0
- package/src/components/ds-grid/ds-grid.tsx +43 -0
- package/src/components/ds-grid/ds-grid.types.ts +55 -0
- package/src/components/ds-grid/index.ts +2 -0
- package/src/components/ds-icon/ds-icon.module.scss +25 -0
- package/src/components/ds-icon/ds-icon.tsx +33 -0
- package/src/components/ds-icon/ds-icon.types.ts +53 -0
- package/src/components/ds-icon/index.ts +2 -0
- package/src/components/ds-icon/material-icons.ts +6203 -0
- package/src/components/ds-modal/ds-modal.module.scss +100 -0
- package/src/components/ds-modal/ds-modal.tsx +136 -0
- package/src/components/ds-modal/ds-modal.types.tsx +44 -0
- package/src/components/ds-modal/index.tsx +2 -0
- package/src/components/ds-number-input/ds-number-input.module.scss +15 -0
- package/src/components/ds-number-input/ds-number-input.tsx +74 -0
- package/src/components/ds-number-input/ds-number-input.types.tsx +76 -0
- package/src/components/ds-number-input/index.tsx +2 -0
- package/src/components/ds-panel/ds-panel.module.scss +95 -0
- package/src/components/ds-panel/ds-panel.tsx +46 -0
- package/src/components/ds-panel/ds-panel.types.ts +17 -0
- package/src/components/ds-panel/index.tsx +2 -0
- package/src/components/ds-password-input/ds-password-input.module.scss +10 -0
- package/src/components/ds-password-input/ds-password-input.tsx +67 -0
- package/src/components/ds-password-input/ds-password-input.types.tsx +62 -0
- package/src/components/ds-password-input/index.tsx +2 -0
- package/src/components/ds-popover/ds-popover.module.scss +23 -0
- package/src/components/ds-popover/ds-popover.tsx +33 -0
- package/src/components/ds-popover/ds-popover.types.tsx +24 -0
- package/src/components/ds-popover/index.tsx +2 -0
- package/src/components/ds-radio-group/ds-radio-group.module.scss +138 -0
- package/src/components/ds-radio-group/ds-radio-group.tsx +115 -0
- package/src/components/ds-radio-group/ds-radio-group.types.tsx +93 -0
- package/src/components/ds-radio-group/index.tsx +2 -0
- package/src/components/ds-select/ds-select.module.scss +66 -0
- package/src/components/ds-select/ds-select.tsx +239 -0
- package/src/components/ds-select/ds-select.types.tsx +80 -0
- package/src/components/ds-select/index.tsx +2 -0
- package/src/components/ds-select/select-items-chips.tsx +56 -0
- package/src/components/ds-smart-tabs/ds-smart-tabs.module.scss +69 -0
- package/src/components/ds-smart-tabs/ds-smart-tabs.tsx +113 -0
- package/src/components/ds-smart-tabs/ds-smart-tabs.types.tsx +74 -0
- package/src/components/ds-smart-tabs/index.tsx +2 -0
- package/src/components/ds-spinner/ds-spinner.module.scss +39 -0
- package/src/components/ds-spinner/ds-spinner.tsx +51 -0
- package/src/components/ds-spinner/ds-spinner.types.tsx +20 -0
- package/src/components/ds-spinner/index.tsx +2 -0
- package/src/components/ds-status-badge/ds-status-badge.module.scss +79 -0
- package/src/components/ds-status-badge/ds-status-badge.tsx +42 -0
- package/src/components/ds-status-badge/ds-status-badge.types.tsx +46 -0
- package/src/components/ds-status-badge/index.tsx +2 -0
- package/src/components/ds-stepper/components/ds-step-content.tsx +32 -0
- package/src/components/ds-stepper/components/ds-step-description.tsx +6 -0
- package/src/components/ds-stepper/components/ds-step-label.tsx +6 -0
- package/src/components/ds-stepper/components/ds-step-next-button.tsx +13 -0
- package/src/components/ds-stepper/components/ds-step.tsx +59 -0
- package/src/components/ds-stepper/ds-stepper.module.scss +134 -0
- package/src/components/ds-stepper/ds-stepper.tsx +34 -0
- package/src/components/ds-stepper/ds-stepper.types.ts +12 -0
- package/src/components/ds-stepper/hooks/use-stepper.ts +20 -0
- package/src/components/ds-stepper/hooks/use-steps.ts +18 -0
- package/src/components/ds-stepper/index.tsx +6 -0
- package/src/components/ds-system-status/ds-system-status.module.scss +87 -0
- package/src/components/ds-system-status/ds-system-status.tsx +33 -0
- package/src/components/ds-system-status/ds-system-status.types.tsx +37 -0
- package/src/components/ds-system-status/index.tsx +10 -0
- package/src/components/ds-table/components/core-table/core-table.module.scss +32 -0
- package/src/components/ds-table/components/core-table/core-table.tsx +83 -0
- package/src/components/ds-table/components/core-table/index.ts +1 -0
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +115 -0
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx +70 -0
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.types.ts +41 -0
- package/src/components/ds-table/components/ds-table-bulk-actions/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +53 -0
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.tsx +95 -0
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.types.ts +67 -0
- package/src/components/ds-table/components/ds-table-cell/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +81 -0
- package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +99 -0
- package/src/components/ds-table/components/ds-table-header/ds-table-header.types.ts +11 -0
- package/src/components/ds-table/components/ds-table-header/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +137 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +205 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.types.ts +22 -0
- package/src/components/ds-table/components/ds-table-row/index.ts +2 -0
- package/src/components/ds-table/context/ds-table-context.tsx +39 -0
- package/src/components/ds-table/ds-table.module.scss +60 -0
- package/src/components/ds-table/ds-table.tsx +312 -0
- package/src/components/ds-table/ds-table.types.ts +377 -0
- package/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx +116 -0
- package/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx +171 -0
- package/src/components/ds-table/filters/adapters/create-filter-adapter.ts +131 -0
- package/src/components/ds-table/filters/adapters/index.ts +12 -0
- package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.tsx +43 -0
- package/src/components/ds-table/filters/components/checkbox-filter/index.ts +2 -0
- package/src/components/ds-table/filters/components/range-filter/index.ts +2 -0
- package/src/components/ds-table/filters/components/range-filter/range-filter.module.scss +25 -0
- package/src/components/ds-table/filters/components/range-filter/range-filter.tsx +79 -0
- package/src/components/ds-table/filters/hooks/index.ts +1 -0
- package/src/components/ds-table/filters/hooks/use-table-filters.ts +234 -0
- package/src/components/ds-table/filters/index.ts +50 -0
- package/src/components/ds-table/filters/types/filter-adapter.types.ts +111 -0
- package/src/components/ds-table/hooks/use-drag-and-drop.tsx +107 -0
- package/src/components/ds-table/index.ts +4 -0
- package/src/components/ds-table/styles/_variables.scss +17 -0
- package/src/components/ds-table/styles/shared/ds-table-shared.module.scss +16 -0
- package/src/components/ds-table/utils/column-styling.ts +30 -0
- package/src/components/ds-table/utils/create-columns-grid-template.ts +23 -0
- package/src/components/ds-table/utils/merge-refs.ts +15 -0
- package/src/components/ds-table/utils/story-data-generator.ts +91 -0
- package/src/components/ds-text-input/ds-text-input.module.scss +27 -0
- package/src/components/ds-text-input/ds-text-input.tsx +75 -0
- package/src/components/ds-text-input/ds-text-input.types.tsx +110 -0
- package/src/components/ds-text-input/index.ts +2 -0
- package/src/components/ds-textarea/ds-textarea.module.scss +5 -0
- package/src/components/ds-textarea/ds-textarea.tsx +49 -0
- package/src/components/ds-textarea/ds-textarea.types.tsx +67 -0
- package/src/components/ds-textarea/index.tsx +2 -0
- package/src/components/ds-toast/ds-toast-context.tsx +79 -0
- package/src/components/ds-toast/ds-toast.module.scss +134 -0
- package/src/components/ds-toast/ds-toast.tsx +54 -0
- package/src/components/ds-toast/ds-toast.types.tsx +53 -0
- package/src/components/ds-toast/index.ts +3 -0
- package/src/components/ds-tooltip/ds-tooltip.module.scss +19 -0
- package/src/components/ds-tooltip/ds-tooltip.tsx +33 -0
- package/src/components/ds-tooltip/ds-tooltip.types.tsx +12 -0
- package/src/components/ds-tooltip/index.tsx +2 -0
- package/src/components/ds-typography/ds-typography.config.ts +32 -0
- package/src/components/ds-typography/ds-typography.module.scss +93 -0
- package/src/components/ds-typography/ds-typography.tsx +27 -0
- package/src/components/ds-typography/ds-typography.types.tsx +27 -0
- package/src/components/ds-typography/index.tsx +2 -0
- package/src/components/ds-vertical-tabs/ds-vertical-tabs.module.scss +60 -0
- package/src/components/ds-vertical-tabs/ds-vertical-tabs.tsx +80 -0
- package/src/components/ds-vertical-tabs/ds-vertical-tabs.types.ts +69 -0
- package/src/components/ds-vertical-tabs/index.ts +7 -0
- package/src/index.ts +34 -0
- package/src/styles/_colors.scss +65 -0
- package/src/styles/_grid.scss +111 -0
- package/src/styles/_mixin.scss +1 -0
- package/src/styles/_reset.scss +38 -0
- package/src/styles/_root.scss +390 -0
- package/src/styles/_scrollbars.scss +41 -0
- package/src/styles/_spacing.scss +47 -0
- package/src/styles/_typography.scss +301 -0
- package/src/styles/_utilities.scss +33 -0
- package/src/styles/_variables.scss +44 -0
- package/src/styles/mixins/_bounding-box.scss +53 -0
- package/src/styles/mixins/_list.scss +74 -0
- package/src/styles/shared/_dropdown.scss +89 -0
- package/src/styles/shared/_input.scss +365 -0
- package/src/styles/shared/_modal.scss +123 -0
- package/src/styles/styles.scss +12 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { type UseSelectReturn, Select, createListCollection, useSelect } from '@ark-ui/react/select';
|
|
3
|
+
import { Portal } from '@ark-ui/react/portal';
|
|
4
|
+
import classNames from 'classnames';
|
|
5
|
+
import styles from './ds-select.module.scss';
|
|
6
|
+
import type { DsSelectOption, DsSelectProps } from './ds-select.types';
|
|
7
|
+
import { DsIcon } from '../ds-icon';
|
|
8
|
+
import { type DsCheckboxProps, DsCheckbox } from '../ds-checkbox';
|
|
9
|
+
import { SelectItemsChips } from './select-items-chips';
|
|
10
|
+
import { DsTypography } from '../ds-typography';
|
|
11
|
+
import { DsTextInput } from '../ds-text-input';
|
|
12
|
+
|
|
13
|
+
const SEARCH_THRESHOLD = 13;
|
|
14
|
+
const SELECT_ALL_VALUE = '__INTERNAL_SELECT_ALL_VALUE__';
|
|
15
|
+
|
|
16
|
+
const SELECT_ALL: DsSelectOption = {
|
|
17
|
+
label: 'All',
|
|
18
|
+
value: SELECT_ALL_VALUE,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const DsSelect = ({
|
|
22
|
+
id,
|
|
23
|
+
options: userOptions,
|
|
24
|
+
value,
|
|
25
|
+
style,
|
|
26
|
+
size,
|
|
27
|
+
clearable,
|
|
28
|
+
onClear,
|
|
29
|
+
onBlur,
|
|
30
|
+
className,
|
|
31
|
+
placeholder = 'Click to select a value',
|
|
32
|
+
disabled,
|
|
33
|
+
...multiselectProps
|
|
34
|
+
}: DsSelectProps) => {
|
|
35
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
36
|
+
const [showAllItems, setShowAllItems] = useState(false);
|
|
37
|
+
|
|
38
|
+
const internalOptions = multiselectProps.multiple ? [SELECT_ALL, ...userOptions] : userOptions;
|
|
39
|
+
|
|
40
|
+
const collection = createListCollection({
|
|
41
|
+
items: internalOptions,
|
|
42
|
+
itemToValue: (item) => item.value,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const filteredOptions = internalOptions.filter((option) =>
|
|
46
|
+
option.label.toLowerCase().includes(searchTerm.toLowerCase()),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const normalizedValue = Array.isArray(value) ? value : [value].filter((value) => !!value);
|
|
50
|
+
|
|
51
|
+
const select = useSelect({
|
|
52
|
+
collection,
|
|
53
|
+
disabled,
|
|
54
|
+
multiple: multiselectProps.multiple,
|
|
55
|
+
value: normalizedValue,
|
|
56
|
+
|
|
57
|
+
// Override Ark's auto-generated trigger id if an id provided by the user.
|
|
58
|
+
// See: https://github.com/chakra-ui/zag/blob/f6e079095/packages/machines/select/src/select.dom.ts#L5
|
|
59
|
+
ids: { trigger: id },
|
|
60
|
+
|
|
61
|
+
onOpenChange: () => {
|
|
62
|
+
setSearchTerm('');
|
|
63
|
+
setShowAllItems(false);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
onValueChange: (details) => {
|
|
67
|
+
// Single select mode.
|
|
68
|
+
if (!multiselectProps.multiple) {
|
|
69
|
+
multiselectProps.onValueChange?.(details.value[0]);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// "Select All" clicked in multi select mode.
|
|
74
|
+
const isSelectAllClicked = details.value.includes(SELECT_ALL_VALUE);
|
|
75
|
+
|
|
76
|
+
if (isSelectAllClicked) {
|
|
77
|
+
const areAllOptionsSelected = select.selectedItems.length === userOptions.length;
|
|
78
|
+
|
|
79
|
+
const newValues = areAllOptionsSelected ? [] : userOptions.map((opt) => opt.value);
|
|
80
|
+
|
|
81
|
+
multiselectProps.onValueChange?.(newValues);
|
|
82
|
+
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// "Regular" multi select mode.
|
|
87
|
+
const newValueWithoutSelectAll = details.value.filter((val) => val !== SELECT_ALL_VALUE);
|
|
88
|
+
|
|
89
|
+
multiselectProps.onValueChange?.(newValueWithoutSelectAll);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Select.RootProvider
|
|
95
|
+
value={select}
|
|
96
|
+
// When rendering the Select inside a Modal, it has an `aria-hidden="true"` attribute
|
|
97
|
+
// which causes screen readers to ignore it.
|
|
98
|
+
//
|
|
99
|
+
// See: https://github.com/chakra-ui/ark/issues/3728
|
|
100
|
+
lazyMount
|
|
101
|
+
>
|
|
102
|
+
<Select.Control
|
|
103
|
+
className={classNames(styles.control, size === 'small' && styles.small, className)}
|
|
104
|
+
style={style}
|
|
105
|
+
onBlur={onBlur}
|
|
106
|
+
onKeyDown={(e) => {
|
|
107
|
+
if (!clearable) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (select.open) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (e.key === 'Backspace' || e.key === 'Delete') {
|
|
116
|
+
if (multiselectProps.multiple) {
|
|
117
|
+
multiselectProps.onValueChange?.([]);
|
|
118
|
+
} else {
|
|
119
|
+
multiselectProps.onValueChange?.('');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
onClear?.();
|
|
123
|
+
}
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
<Select.Trigger
|
|
127
|
+
id={id}
|
|
128
|
+
className={styles.trigger}
|
|
129
|
+
// Ark expects a `Select.Label` component to exist and automatically label the trigger
|
|
130
|
+
// with it. We're using the `DsFormControl` label handling instead, so we disable the
|
|
131
|
+
// automatic labelling here.
|
|
132
|
+
aria-labelledby={null as never}
|
|
133
|
+
>
|
|
134
|
+
<DsTypography className={styles.valueText} variant="body-sm-reg" asChild>
|
|
135
|
+
<Select.ValueText placeholder={placeholder} />
|
|
136
|
+
</DsTypography>
|
|
137
|
+
|
|
138
|
+
<Select.Indicator className={styles.triggerIcon}>
|
|
139
|
+
<DsIcon icon="keyboard_arrow_down" size={size === 'small' ? 'small' : 'medium'} />
|
|
140
|
+
</Select.Indicator>
|
|
141
|
+
</Select.Trigger>
|
|
142
|
+
|
|
143
|
+
{clearable && (
|
|
144
|
+
<Select.ClearTrigger className={styles.clearIcon} onClick={onClear}>
|
|
145
|
+
<DsIcon icon="close" size={size === 'small' ? 'small' : 'medium'} />
|
|
146
|
+
</Select.ClearTrigger>
|
|
147
|
+
)}
|
|
148
|
+
</Select.Control>
|
|
149
|
+
<Portal>
|
|
150
|
+
<Select.Positioner className={styles.viewport}>
|
|
151
|
+
<Select.Content className={styles.content}>
|
|
152
|
+
{userOptions.length > SEARCH_THRESHOLD && (
|
|
153
|
+
<div className={styles.searchInput}>
|
|
154
|
+
<DsTextInput
|
|
155
|
+
placeholder="Search"
|
|
156
|
+
value={searchTerm}
|
|
157
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
158
|
+
onKeyDown={(e) => {
|
|
159
|
+
// Prevent the select from opening/closing when pressing space in the search input.
|
|
160
|
+
if (e.key === ' ') {
|
|
161
|
+
e.stopPropagation();
|
|
162
|
+
}
|
|
163
|
+
}}
|
|
164
|
+
slots={{
|
|
165
|
+
startAdornment: <DsIcon icon="search" size="tiny" />,
|
|
166
|
+
}}
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
{multiselectProps.multiple && (
|
|
172
|
+
<SelectItemsChips
|
|
173
|
+
onValueChange={multiselectProps.onValueChange}
|
|
174
|
+
showAll={showAllItems}
|
|
175
|
+
onShowAll={() => setShowAllItems(true)}
|
|
176
|
+
// TODO: Find a way to calculate this based on the size of the select.
|
|
177
|
+
count={6}
|
|
178
|
+
/>
|
|
179
|
+
)}
|
|
180
|
+
|
|
181
|
+
{filteredOptions.map((item) => {
|
|
182
|
+
const checked = getItemCheckedState({ item, select, options: internalOptions });
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<DsTypography
|
|
186
|
+
variant="body-sm-reg"
|
|
187
|
+
asChild
|
|
188
|
+
key={collection.getItemValue(item)}
|
|
189
|
+
className={styles.item}
|
|
190
|
+
>
|
|
191
|
+
<Select.Item item={item}>
|
|
192
|
+
{multiselectProps.multiple && <DsCheckbox checked={checked} />}
|
|
193
|
+
{item.icon && <DsIcon className={styles.itemIcon} icon={item.icon} aria-hidden="true" />}
|
|
194
|
+
<Select.ItemText>{item.label}</Select.ItemText>
|
|
195
|
+
</Select.Item>
|
|
196
|
+
</DsTypography>
|
|
197
|
+
);
|
|
198
|
+
})}
|
|
199
|
+
</Select.Content>
|
|
200
|
+
</Select.Positioner>
|
|
201
|
+
</Portal>
|
|
202
|
+
|
|
203
|
+
{/*
|
|
204
|
+
This element isn't properly hidden by Zag, so we hide it manually.
|
|
205
|
+
For more information, see: https://github.com/chakra-ui/zag/issues/2865
|
|
206
|
+
*/}
|
|
207
|
+
<Select.HiddenSelect style={{ display: 'none' }} />
|
|
208
|
+
</Select.RootProvider>
|
|
209
|
+
);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
function getItemCheckedState({
|
|
213
|
+
item,
|
|
214
|
+
select,
|
|
215
|
+
options,
|
|
216
|
+
}: {
|
|
217
|
+
item: DsSelectOption;
|
|
218
|
+
select: UseSelectReturn<DsSelectOption>;
|
|
219
|
+
options: DsSelectOption[];
|
|
220
|
+
}): DsCheckboxProps['checked'] {
|
|
221
|
+
const isRegularItem = item.value !== SELECT_ALL_VALUE;
|
|
222
|
+
|
|
223
|
+
if (isRegularItem) {
|
|
224
|
+
return select.getItemState({ item }).selected;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Doing -1 since it contains the "Select All" option itself.
|
|
228
|
+
const allSelected = select.selectedItems.length === options.length - 1;
|
|
229
|
+
|
|
230
|
+
if (allSelected) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const someSelected = select.hasSelectedItems;
|
|
235
|
+
|
|
236
|
+
return someSelected ? 'indeterminate' : false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export default DsSelect;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { IconName } from '../ds-icon';
|
|
3
|
+
|
|
4
|
+
export type SelectSize = 'default' | 'small';
|
|
5
|
+
|
|
6
|
+
export interface DsSelectOption {
|
|
7
|
+
/**
|
|
8
|
+
* Label to display in the select
|
|
9
|
+
*/
|
|
10
|
+
label: string;
|
|
11
|
+
/**
|
|
12
|
+
* Value to return when the option is selected
|
|
13
|
+
*/
|
|
14
|
+
value: SelectOptionValue;
|
|
15
|
+
/**
|
|
16
|
+
* Optional icon to display next to the label
|
|
17
|
+
*/
|
|
18
|
+
icon?: IconName;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type SelectOptionValue = string;
|
|
22
|
+
|
|
23
|
+
export type DsSelectProps = {
|
|
24
|
+
/**
|
|
25
|
+
* Unique identifier for the select component
|
|
26
|
+
*/
|
|
27
|
+
id?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Options to display in the select dropdown
|
|
30
|
+
*/
|
|
31
|
+
options: DsSelectOption[];
|
|
32
|
+
/**
|
|
33
|
+
* Additional styles to apply to the icon
|
|
34
|
+
*/
|
|
35
|
+
style?: React.CSSProperties;
|
|
36
|
+
/**
|
|
37
|
+
* Additional CSS class names
|
|
38
|
+
*/
|
|
39
|
+
className?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Event handler called when the select loses focus
|
|
42
|
+
*
|
|
43
|
+
* @param event
|
|
44
|
+
*/
|
|
45
|
+
onBlur?: (event: React.FocusEvent) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Placeholder text to display when no option is selected
|
|
48
|
+
*/
|
|
49
|
+
placeholder?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the select is disabled
|
|
52
|
+
*/
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* The size of the select component
|
|
56
|
+
* @default 'default'
|
|
57
|
+
*/
|
|
58
|
+
size?: SelectSize;
|
|
59
|
+
} & (
|
|
60
|
+
| {
|
|
61
|
+
clearable?: undefined | false;
|
|
62
|
+
onClear?: never;
|
|
63
|
+
}
|
|
64
|
+
| {
|
|
65
|
+
clearable: true;
|
|
66
|
+
onClear?: () => void;
|
|
67
|
+
}
|
|
68
|
+
) &
|
|
69
|
+
(
|
|
70
|
+
| {
|
|
71
|
+
multiple?: undefined | false;
|
|
72
|
+
value: SelectOptionValue;
|
|
73
|
+
onValueChange?: (value: SelectOptionValue) => void;
|
|
74
|
+
}
|
|
75
|
+
| {
|
|
76
|
+
multiple: true;
|
|
77
|
+
value: SelectOptionValue[];
|
|
78
|
+
onValueChange?: (value: SelectOptionValue[]) => void;
|
|
79
|
+
}
|
|
80
|
+
);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useSelectContext, type UseSelectContext } from '@ark-ui/react/select';
|
|
2
|
+
import styles from './ds-select.module.scss';
|
|
3
|
+
import DsButton from '../ds-button/ds-button';
|
|
4
|
+
import DsChip from '../ds-chip/ds-chip';
|
|
5
|
+
import type { DsSelectOption, SelectOptionValue } from './ds-select.types';
|
|
6
|
+
|
|
7
|
+
type SelectItemsChipsProps = {
|
|
8
|
+
showAll: boolean;
|
|
9
|
+
onValueChange?: (value: SelectOptionValue[]) => void;
|
|
10
|
+
onShowAll: () => void;
|
|
11
|
+
count: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function SelectItemsChips({ showAll, onShowAll, onValueChange, count }: SelectItemsChipsProps) {
|
|
15
|
+
const { collection, value: selectedItems } = useSelectContext() as UseSelectContext<DsSelectOption>;
|
|
16
|
+
|
|
17
|
+
if (!selectedItems.length) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const itemsToShow = showAll ? selectedItems : selectedItems.slice(0, count);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className={styles.multiSelectItemsContainer}>
|
|
25
|
+
{itemsToShow.map((itemValue) => {
|
|
26
|
+
const item = collection.find(itemValue);
|
|
27
|
+
|
|
28
|
+
if (!item) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const onDelete = () => {
|
|
33
|
+
const filteredValue = selectedItems.filter((v) => v !== item.value);
|
|
34
|
+
|
|
35
|
+
onValueChange?.(filteredValue);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return <DsChip key={itemValue} label={item.label} onDelete={onDelete} />;
|
|
39
|
+
})}
|
|
40
|
+
|
|
41
|
+
{!showAll && selectedItems.length > count && (
|
|
42
|
+
<DsChip label={`+${String(selectedItems.length - count)}`} onClick={onShowAll} size="small" />
|
|
43
|
+
)}
|
|
44
|
+
|
|
45
|
+
<DsButton
|
|
46
|
+
design="v1.2"
|
|
47
|
+
buttonType="tertiary"
|
|
48
|
+
variant="ghost"
|
|
49
|
+
size="small"
|
|
50
|
+
onClick={() => onValueChange?.([])}
|
|
51
|
+
>
|
|
52
|
+
Clear All
|
|
53
|
+
</DsButton>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
@use '../../styles/typography';
|
|
2
|
+
|
|
3
|
+
.container {
|
|
4
|
+
display: flex;
|
|
5
|
+
gap: 8px;
|
|
6
|
+
|
|
7
|
+
.tab {
|
|
8
|
+
@include typography.body-sm-reg;
|
|
9
|
+
@include typography.ellipsis;
|
|
10
|
+
display: flex;
|
|
11
|
+
height: 48px;
|
|
12
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: 8px;
|
|
15
|
+
border-radius: 4px;
|
|
16
|
+
border: 1px solid var(--color-border);
|
|
17
|
+
background: var(--color-background);
|
|
18
|
+
min-width: 250px;
|
|
19
|
+
|
|
20
|
+
&.active {
|
|
21
|
+
@include typography.body-sm-md;
|
|
22
|
+
border-color: var(--color-border-action-secondary);
|
|
23
|
+
|
|
24
|
+
.icon {
|
|
25
|
+
color: var(--color-background);
|
|
26
|
+
background: var(--tab-color, var(--color-background-action));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.value {
|
|
30
|
+
color: var(--color-font-main);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&:hover:not(.active):not(:disabled) {
|
|
35
|
+
border-color: var(--color-border-action-primary-hover);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&:focus {
|
|
39
|
+
outline: auto var(--color-border-action-primary-hover);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&.disabled {
|
|
43
|
+
opacity: 0.5;
|
|
44
|
+
cursor: not-allowed;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.icon {
|
|
48
|
+
display: flex;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
align-items: center;
|
|
51
|
+
color: var(--color-background-action);
|
|
52
|
+
background: var(--color-background-tertiary);
|
|
53
|
+
border-radius: 50%;
|
|
54
|
+
width: var(--icon-width-extra-large);
|
|
55
|
+
height: var(--icon-width-extra-large);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.label {
|
|
59
|
+
flex: 1;
|
|
60
|
+
white-space: nowrap;
|
|
61
|
+
text-align: start;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.content {
|
|
65
|
+
padding-right: 4px;
|
|
66
|
+
color: var(--color-font-secondary);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { type CSSProperties, createContext, useContext } from 'react';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import { DsIcon } from '../ds-icon';
|
|
5
|
+
import styles from './ds-smart-tabs.module.scss';
|
|
6
|
+
import type { Color, DsSmartTabProps, DsSmartTabsProps } from './ds-smart-tabs.types';
|
|
7
|
+
|
|
8
|
+
const colorMap: Record<Color, string> = {
|
|
9
|
+
'dark-blue': 'var(--color-background-info-strong)',
|
|
10
|
+
'light-blue': 'var(--color-background-active-moderate)',
|
|
11
|
+
'dark-gray': 'var(--color-background-brand)',
|
|
12
|
+
green: 'var(--color-background-success-strong)',
|
|
13
|
+
fuchsia: 'var(--color-data-fuchsia)',
|
|
14
|
+
blue: 'var(--color-icon-information-secondary)',
|
|
15
|
+
gray: 'var(--color-icon-information-main)',
|
|
16
|
+
red: 'var(--color-icon-warning)',
|
|
17
|
+
amber: 'var(--color-data-amber)',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
interface SmartTabsContextType {
|
|
21
|
+
activeTab: string;
|
|
22
|
+
onTabClick: (value: string) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const SmartTabsContext = createContext<SmartTabsContextType | null>(null);
|
|
26
|
+
|
|
27
|
+
const useSmartTabsContext = () => {
|
|
28
|
+
const context = useContext(SmartTabsContext);
|
|
29
|
+
if (!context) {
|
|
30
|
+
throw new Error('DsTab must be used within DsSmartTabs');
|
|
31
|
+
}
|
|
32
|
+
return context;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Individual tab component
|
|
37
|
+
*/
|
|
38
|
+
const DsSmartTab: React.FC<DsSmartTabProps> = ({
|
|
39
|
+
label,
|
|
40
|
+
value,
|
|
41
|
+
icon,
|
|
42
|
+
content,
|
|
43
|
+
color,
|
|
44
|
+
disabled,
|
|
45
|
+
className,
|
|
46
|
+
style = {},
|
|
47
|
+
...props
|
|
48
|
+
}) => {
|
|
49
|
+
const { activeTab, onTabClick } = useSmartTabsContext();
|
|
50
|
+
|
|
51
|
+
const tabClass = classNames(
|
|
52
|
+
styles.tab,
|
|
53
|
+
{
|
|
54
|
+
[styles.active]: activeTab === value,
|
|
55
|
+
[styles.disabled]: disabled,
|
|
56
|
+
},
|
|
57
|
+
className,
|
|
58
|
+
);
|
|
59
|
+
const tabStyle = {
|
|
60
|
+
...style,
|
|
61
|
+
...(color && ({ '--tab-color': colorMap[color] } as CSSProperties)),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const handleClick = () => {
|
|
65
|
+
if (!disabled) {
|
|
66
|
+
onTabClick(value);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
className={tabClass}
|
|
74
|
+
style={tabStyle}
|
|
75
|
+
onClick={handleClick}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
<DsIcon className={styles.icon} icon={icon} size="small" />
|
|
80
|
+
<span className={styles.label}>{label}</span>
|
|
81
|
+
<span className={styles.content}>{content}</span>
|
|
82
|
+
</button>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Design system SmartTabs component
|
|
88
|
+
*/
|
|
89
|
+
const DsSmartTabs = ({
|
|
90
|
+
activeTab,
|
|
91
|
+
onTabClick,
|
|
92
|
+
className,
|
|
93
|
+
style = {},
|
|
94
|
+
children,
|
|
95
|
+
...props
|
|
96
|
+
}: DsSmartTabsProps) => {
|
|
97
|
+
const contextValue: SmartTabsContextType = {
|
|
98
|
+
activeTab,
|
|
99
|
+
onTabClick,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<SmartTabsContext.Provider value={contextValue}>
|
|
104
|
+
<div className={classNames(styles.container, className)} style={style} {...props}>
|
|
105
|
+
{children}
|
|
106
|
+
</div>
|
|
107
|
+
</SmartTabsContext.Provider>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
DsSmartTabs.Tab = DsSmartTab;
|
|
112
|
+
|
|
113
|
+
export default DsSmartTabs;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
import type { IconType } from '../ds-icon/ds-icon.types';
|
|
4
|
+
|
|
5
|
+
export const colors = [
|
|
6
|
+
'dark-blue',
|
|
7
|
+
'light-blue',
|
|
8
|
+
'dark-gray',
|
|
9
|
+
'green',
|
|
10
|
+
'fuchsia',
|
|
11
|
+
'blue',
|
|
12
|
+
'gray',
|
|
13
|
+
'red',
|
|
14
|
+
'amber',
|
|
15
|
+
] as const;
|
|
16
|
+
export type Color = (typeof colors)[number];
|
|
17
|
+
|
|
18
|
+
export interface DsSmartTabProps {
|
|
19
|
+
/**
|
|
20
|
+
* The label of the tab
|
|
21
|
+
*/
|
|
22
|
+
label: string;
|
|
23
|
+
/**
|
|
24
|
+
* The value of the tab (used for filtering/identification)
|
|
25
|
+
*/
|
|
26
|
+
value: string;
|
|
27
|
+
/**
|
|
28
|
+
* The icon to display in the tab
|
|
29
|
+
*/
|
|
30
|
+
icon: IconType;
|
|
31
|
+
/**
|
|
32
|
+
* The content to display in the tab (typically a count or text)
|
|
33
|
+
*/
|
|
34
|
+
content: number | string;
|
|
35
|
+
/**
|
|
36
|
+
* Color for the tab (limited to predefined color values)
|
|
37
|
+
*/
|
|
38
|
+
color?: Color;
|
|
39
|
+
/**
|
|
40
|
+
* Whether the tab is disabled
|
|
41
|
+
*/
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Additional CSS class names
|
|
45
|
+
*/
|
|
46
|
+
className?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Additional styles to apply to the tab
|
|
49
|
+
*/
|
|
50
|
+
style?: React.CSSProperties;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface DsSmartTabsProps {
|
|
54
|
+
/**
|
|
55
|
+
* Currently active tab value
|
|
56
|
+
*/
|
|
57
|
+
activeTab: string;
|
|
58
|
+
/**
|
|
59
|
+
* Callback function when a tab is clicked
|
|
60
|
+
*/
|
|
61
|
+
onTabClick: (value: string) => void;
|
|
62
|
+
/**
|
|
63
|
+
* Additional CSS class names
|
|
64
|
+
*/
|
|
65
|
+
className?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Additional styles to apply to the component
|
|
68
|
+
*/
|
|
69
|
+
style?: React.CSSProperties;
|
|
70
|
+
/**
|
|
71
|
+
* The tab components as children
|
|
72
|
+
*/
|
|
73
|
+
children: ReactNode;
|
|
74
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
@use '../../styles/typography';
|
|
2
|
+
|
|
3
|
+
.spinnerContainer {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.progressCircle {
|
|
10
|
+
button &,
|
|
11
|
+
[role='button'] & {
|
|
12
|
+
width: 1em;
|
|
13
|
+
height: 1em;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.spin {
|
|
18
|
+
animation: spin 2s linear infinite;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.progressArc {
|
|
22
|
+
transition: stroke-dasharray 0.3s ease-in-out;
|
|
23
|
+
stroke: var(--color-background-selected);
|
|
24
|
+
|
|
25
|
+
button &,
|
|
26
|
+
[role='button'] & {
|
|
27
|
+
stroke: currentColor;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@keyframes spin {
|
|
32
|
+
0% {
|
|
33
|
+
transform: rotate(0deg);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
100% {
|
|
37
|
+
transform: rotate(360deg);
|
|
38
|
+
}
|
|
39
|
+
}
|