@stack-spot/citric-react 0.37.0 → 0.37.1-beta.2
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/dist/components/Select/MultiSelect.d.ts +23 -1
- package/dist/components/Select/MultiSelect.d.ts.map +1 -1
- package/dist/components/Select/MultiSelect.js +71 -7
- package/dist/components/Select/MultiSelect.js.map +1 -1
- package/package.json +1 -2
- package/src/components/Select/MultiSelect.tsx +141 -6
|
@@ -33,6 +33,28 @@ export interface BaseMultiSelectProps<T> extends Omit<RichSelectProps<T>, 'value
|
|
|
33
33
|
* @default false
|
|
34
34
|
*/
|
|
35
35
|
showSelectAll?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Whether to render selected values as removable chips/tags.
|
|
38
|
+
*
|
|
39
|
+
* @default false
|
|
40
|
+
*/
|
|
41
|
+
showAsChips?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether to allow adding custom values that don't exist in options.
|
|
44
|
+
* When enabled, typing in the search and pressing Enter will add the value.
|
|
45
|
+
* The value will be added as a string to the list.
|
|
46
|
+
*
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
allowCustomOptions?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Function to create a new option from a string input.
|
|
52
|
+
* Required when `allowCustomOptions` is true.
|
|
53
|
+
*
|
|
54
|
+
* @param input the string input from the user
|
|
55
|
+
* @returns the new option of type T
|
|
56
|
+
*/
|
|
57
|
+
createOption?: (inputValue: string) => T;
|
|
36
58
|
}
|
|
37
59
|
export type MultiSelectProps<T> = Omit<React.JSX.IntrinsicElements['div'], 'ref' | 'onChange' | 'onFocus' | 'onBlur'> & BaseMultiSelectProps<T>;
|
|
38
60
|
/**
|
|
@@ -56,5 +78,5 @@ export type MultiSelectProps<T> = Omit<React.JSX.IntrinsicElements['div'], 'ref'
|
|
|
56
78
|
* return <MultiSelect options={options} renderLabel={o => o.name} renderKey={o => o.id} value={value} setValue={setValue} />
|
|
57
79
|
* ```
|
|
58
80
|
*/
|
|
59
|
-
export declare const MultiSelect: <T>({ ref, options, value, onChange, renderLabel, renderKey, disabled, loading, renderOption, renderHeader, searchable, maxHeight, style, className, showArrow, placeholder, showSelectAll, ...props }: MultiSelectProps<T>) => import("react/jsx-runtime.js").JSX.Element;
|
|
81
|
+
export declare const MultiSelect: <T>({ ref, options, value, onChange, renderLabel, renderKey, disabled, loading, renderOption, renderHeader, searchable, maxHeight, style, className, showArrow, placeholder, showSelectAll, showAsChips, allowCustomOptions, createOption, ...props }: MultiSelectProps<T>) => import("react/jsx-runtime.js").JSX.Element;
|
|
60
82
|
//# sourceMappingURL=MultiSelect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiSelect.d.ts","sourceRoot":"","sources":["../../../src/components/Select/MultiSelect.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MultiSelect.d.ts","sourceRoot":"","sources":["../../../src/components/Select/MultiSelect.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAEzC,MAAM,WAAW,oBAAoB,CAAC,CAAC,CAAE,SACvC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,cAAc,GAAG,aAAa,GAAG,cAAc,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IACpI,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC;IAC/B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9C;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,KAAK,CAAC,SAAS,CAAC;IAC/C;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,CAAC,CAAC;CAC1C;AAED,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC,GACnH,oBAAoB,CAAC,CAAC,CAAC,CAAA;AAEzB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,WAAW,GACD,CAAC,qPAsBnB,gBAAgB,CAAC,CAAC,CAAC,4CA8MvB,CAAA"}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime.js";
|
|
2
2
|
import { listToClass } from '@stack-spot/portal-theme';
|
|
3
3
|
import { useTranslate } from '@stack-spot/portal-translate';
|
|
4
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { useCheckboxGroupControls } from '../../utils/checkbox.js';
|
|
6
6
|
import { applyCSSVariable } from '../../utils/css.js';
|
|
7
7
|
import { defaultRenderKey, defaultRenderLabel } from '../../utils/options.js';
|
|
8
8
|
import { withRef } from '../../utils/react.js';
|
|
9
|
+
import { Badge } from '../Badge.js';
|
|
9
10
|
import { Checkbox } from '../Checkbox.js';
|
|
10
11
|
import { CheckboxGroup } from '../CheckboxGroup.js';
|
|
11
12
|
import { CitricComponent } from '../CitricComponent.js';
|
|
13
|
+
import { IconButton } from '../IconBox.js';
|
|
12
14
|
import { Input } from '../Input.js';
|
|
13
15
|
import { Row } from '../layout.js';
|
|
14
16
|
import { ProgressCircular } from '../ProgressCircular.js';
|
|
@@ -34,18 +36,27 @@ import { useDisabledEffect, useFocusEffect, useOpenPanelEffect } from './hooks.j
|
|
|
34
36
|
* return <MultiSelect options={options} renderLabel={o => o.name} renderKey={o => o.id} value={value} setValue={setValue} />
|
|
35
37
|
* ```
|
|
36
38
|
*/
|
|
37
|
-
export const MultiSelect = withRef(function MultiSelect({ ref, options, value = [], onChange, renderLabel = defaultRenderLabel, renderKey = defaultRenderKey, disabled, loading, renderOption, renderHeader, searchable, maxHeight, style, className, showArrow, placeholder, showSelectAll, ...props }) {
|
|
39
|
+
export const MultiSelect = withRef(function MultiSelect({ ref, options, value = [], onChange, renderLabel = defaultRenderLabel, renderKey = defaultRenderKey, disabled, loading, renderOption, renderHeader, searchable, maxHeight, style, className, showArrow, placeholder, showSelectAll, showAsChips = false, allowCustomOptions = false, createOption, ...props }) {
|
|
38
40
|
const t = useTranslate(dictionary);
|
|
39
41
|
const _element = useRef(null);
|
|
40
42
|
const element = ref ?? _element;
|
|
41
43
|
const [open, setOpen] = useState(false);
|
|
42
44
|
const [focused, setFocused] = useState(false);
|
|
45
|
+
// Merge options with selected values that are not in the original options
|
|
46
|
+
const mergedOptions = useMemo(() => {
|
|
47
|
+
const optionKeys = new Set(options.map(renderKey));
|
|
48
|
+
const extraValues = value.filter(v => !optionKeys.has(renderKey(v)));
|
|
49
|
+
return [...options, ...extraValues];
|
|
50
|
+
}, [options, value, renderKey]);
|
|
43
51
|
const controls = useCheckboxGroupControls({
|
|
44
|
-
options,
|
|
52
|
+
options: mergedOptions,
|
|
45
53
|
renderKey,
|
|
46
54
|
initialValue: value,
|
|
47
55
|
onChange,
|
|
48
|
-
applyFilter: (filter, option) =>
|
|
56
|
+
applyFilter: (filter, option) => {
|
|
57
|
+
const label = renderLabel(option);
|
|
58
|
+
return label.toLocaleLowerCase().includes(filter.toLocaleLowerCase());
|
|
59
|
+
},
|
|
49
60
|
});
|
|
50
61
|
useOpenPanelEffect({ open, setOpen, setSearch: controls.setFilter, element, searchable });
|
|
51
62
|
useFocusEffect({ element, focused, setFocused, setOpen });
|
|
@@ -54,27 +65,74 @@ export const MultiSelect = withRef(function MultiSelect({ ref, options, value =
|
|
|
54
65
|
if (value !== controls.value)
|
|
55
66
|
controls.setValue(value);
|
|
56
67
|
}, [value.map(renderKey).join(',')]);
|
|
68
|
+
const handleRemoveChip = useCallback((option) => {
|
|
69
|
+
const newValue = value.filter(v => renderKey(v) !== renderKey(option));
|
|
70
|
+
controls.setValue(newValue);
|
|
71
|
+
}, [controls, renderKey, value]);
|
|
72
|
+
const handleAddCustomValue = useCallback((e) => {
|
|
73
|
+
if (!allowCustomOptions || !createOption || !controls.filter)
|
|
74
|
+
return;
|
|
75
|
+
const filterValue = String(controls.filter).trim();
|
|
76
|
+
if (e.key === 'Enter' && filterValue && filterValue.length > 0) {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
const newOption = createOption(filterValue);
|
|
79
|
+
const exists = value.some(v => {
|
|
80
|
+
const key1 = renderKey(v);
|
|
81
|
+
const key2 = renderKey(newOption);
|
|
82
|
+
if (typeof key1 === 'string' && typeof key2 === 'string') {
|
|
83
|
+
return key1?.toLowerCase() === key2?.toLowerCase();
|
|
84
|
+
}
|
|
85
|
+
return JSON.stringify(key1) === JSON.stringify(key2);
|
|
86
|
+
});
|
|
87
|
+
if (!exists) {
|
|
88
|
+
controls.setValue([...value, newOption]);
|
|
89
|
+
}
|
|
90
|
+
controls.setFilter('');
|
|
91
|
+
}
|
|
92
|
+
}, [allowCustomOptions, controls.filter, mergedOptions, renderLabel]);
|
|
93
|
+
// Check if the current filter does not match any existing option
|
|
94
|
+
const hasTemporaryOption = useMemo(() => {
|
|
95
|
+
if (!allowCustomOptions || !controls.filter)
|
|
96
|
+
return false;
|
|
97
|
+
const filterValue = String(controls.filter).trim();
|
|
98
|
+
if (!filterValue || filterValue.length === 0)
|
|
99
|
+
return false;
|
|
100
|
+
const matchesExisting = mergedOptions.some(option => {
|
|
101
|
+
const label = renderLabel(option);
|
|
102
|
+
if (!label)
|
|
103
|
+
return false;
|
|
104
|
+
return label.toLowerCase() === filterValue.toLowerCase();
|
|
105
|
+
});
|
|
106
|
+
return !matchesExisting;
|
|
107
|
+
}, [allowCustomOptions, controls.filter, mergedOptions, renderLabel]);
|
|
57
108
|
const header = useMemo(() => {
|
|
58
109
|
if (value.length === 0)
|
|
59
110
|
return _jsx("span", { className: "placeholder header-text", children: placeholder });
|
|
60
111
|
const reversed = [...value].reverse();
|
|
112
|
+
if (showAsChips) {
|
|
113
|
+
return (_jsx(Row, { className: "header-chips", gap: "4px", flex: "1", style: { maxWidth: '100%', flexWrap: 'wrap' }, children: reversed.map(option => (_jsxs(Badge, { className: "chip", tag: "div", onClick: (e) => { e.stopPropagation(); }, children: [_jsx("span", { children: renderLabel(option) }), !disabled && (_jsx(IconButton, { icon: "Times", type: "button", className: "remove-button", size: "xs", disabled: disabled, onClick: (e) => {
|
|
114
|
+
e.stopPropagation();
|
|
115
|
+
handleRemoveChip(option);
|
|
116
|
+
}, "aria-label": `${t.remove} ${renderLabel(option)}` }))] }, renderKey(option)))) }));
|
|
117
|
+
}
|
|
61
118
|
return ((renderHeader?.(reversed)
|
|
62
119
|
?? (renderOption
|
|
63
|
-
? _jsx(Row, { className: "header-text", children: reversed.map(renderOption) })
|
|
120
|
+
? _jsx(Row, { className: "header-text", children: reversed.map((option, index) => (_jsx("span", { children: renderOption(option) }, renderKey(option) ?? index))) })
|
|
64
121
|
: _jsx("span", { className: "header-text", children: reversed.map(renderLabel).join(', ') }))) || _jsx("span", {}));
|
|
65
|
-
}, [value, placeholder]);
|
|
122
|
+
}, [value, placeholder, showAsChips, disabled, renderKey, renderLabel, handleRemoveChip, t, renderHeader, renderOption]);
|
|
66
123
|
return (_jsxs(CitricComponent, { tag: "div", component: "multi-select", style: maxHeight ? applyCSSVariable(style, 'max-height', `${maxHeight}px`) : style, className: listToClass([
|
|
67
124
|
className,
|
|
68
125
|
showArrow === false && 'hide-arrow',
|
|
69
126
|
open && 'open',
|
|
70
127
|
focused && 'focused',
|
|
71
128
|
disabled && 'disabled',
|
|
129
|
+
showAsChips && 'with-chips',
|
|
72
130
|
]), ref: element, "aria-busy": loading, ...props, children: [_jsxs("header", { onClick: () => {
|
|
73
131
|
if (disabled)
|
|
74
132
|
return;
|
|
75
133
|
setFocused(true);
|
|
76
134
|
setOpen(true);
|
|
77
|
-
}, onFocus: () => setFocused(true), "aria-label": t.accessibilityHelp, tabIndex: disabled ? undefined : 0, className: renderHeader ? 'custom' : undefined, children: [header, loading && _jsx(ProgressCircular, { size: "xs", className: "loader" })] }), _jsxs("div", { className: "selection-panel", "aria-hidden": !open, ...(open ? {} : { inert: 'true' }), children: [searchable && _jsx("div", { className: "search-bar", children: _jsxs("div", { "data-citric": "field-group", className: "auto", children: [_jsx("i", { "data-citric": "icon-box", className: "citric-icon outline Search" }), _jsx(Input, { type: "search", value: controls.filter, onChange: controls.setFilter, "aria-label": t.searchAccessibility })] }) }), showSelectAll && (_jsx(Checkbox, { className: "select-all", onChange: checked => checked ? controls.selectAll() : controls.removeSelection(), value: controls.isAllSelected, children: controls.isAllSelected ? t.removeSelection : t.selectAll })), _jsx(CheckboxGroup, { className: "options", gap: "0", options: controls.options, onChange: controls.setValue, value: controls.value, renderKey: controls.renderKey, focusable: false, renderItem: (checkbox, option) => (_jsxs(CitricComponent, { component: "checkbox-row", tag: "label", className: listToClass(['option', controls.isUnfilteredButChecked(option) && 'unfiltered']), children: [checkbox, renderOption?.(option) ?? renderLabel(option)] })) })] })] }));
|
|
135
|
+
}, onFocus: () => setFocused(true), "aria-label": t.accessibilityHelp, tabIndex: disabled ? undefined : 0, className: renderHeader ? 'custom' : undefined, children: [header, loading && _jsx(ProgressCircular, { size: "xs", className: "loader" })] }), _jsxs("div", { className: "selection-panel", "aria-hidden": !open, ...(open ? {} : { inert: 'true' }), children: [searchable && _jsx("div", { className: "search-bar", children: _jsxs("div", { "data-citric": "field-group", className: "auto", children: [_jsx("i", { "data-citric": "icon-box", className: "citric-icon outline Search" }), _jsx(Input, { type: "search", value: controls.filter, onChange: controls.setFilter, onKeyUp: handleAddCustomValue, "aria-label": t.searchAccessibility, placeholder: allowCustomOptions ? t.searchOrAddPlaceholder : undefined })] }) }), showSelectAll && (_jsx(Checkbox, { className: "select-all", onChange: checked => checked ? controls.selectAll() : controls.removeSelection(), value: controls.isAllSelected, children: controls.isAllSelected ? t.removeSelection : t.selectAll })), _jsx(CheckboxGroup, { className: "options", gap: "0", options: controls.options, onChange: controls.setValue, value: controls.value, renderKey: controls.renderKey, focusable: false, renderItem: (checkbox, option) => (_jsxs(CitricComponent, { component: "checkbox-row", tag: "label", className: listToClass(['option', controls.isUnfilteredButChecked(option) && 'unfiltered']), children: [checkbox, renderOption?.(option) ?? renderLabel(option)] })) }), hasTemporaryOption && (_jsxs("div", { className: "temporary-option", style: { fontStyle: 'italic', padding: '8px 16px', opacity: 0.7 }, children: [String(controls.filter).trim(), " (", t.pressEnterToAdd, ")"] }))] })] }));
|
|
78
136
|
});
|
|
79
137
|
const dictionary = {
|
|
80
138
|
en: {
|
|
@@ -82,12 +140,18 @@ const dictionary = {
|
|
|
82
140
|
searchAccessibility: 'Filter the options',
|
|
83
141
|
removeSelection: 'Remove selection',
|
|
84
142
|
selectAll: 'Select all',
|
|
143
|
+
remove: 'Remove',
|
|
144
|
+
searchOrAddPlaceholder: 'Search or press Enter to add',
|
|
145
|
+
pressEnterToAdd: 'press Enter to add',
|
|
85
146
|
},
|
|
86
147
|
pt: {
|
|
87
148
|
accessibilityHelp: 'Pressione a seta para baixo para selecionar múltiplas opções',
|
|
88
149
|
searchAccessibility: 'Filtre as opções',
|
|
89
150
|
removeSelection: 'Remover seleção',
|
|
90
151
|
selectAll: 'Selecionar todos',
|
|
152
|
+
remove: 'Remover',
|
|
153
|
+
searchOrAddPlaceholder: 'Busque ou pressione Enter para adicionar',
|
|
154
|
+
pressEnterToAdd: 'pressione Enter para adicionar',
|
|
91
155
|
},
|
|
92
156
|
};
|
|
93
157
|
//# sourceMappingURL=MultiSelect.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiSelect.js","sourceRoot":"","sources":["../../../src/components/Select/MultiSelect.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"MultiSelect.js","sourceRoot":"","sources":["../../../src/components/Select/MultiSelect.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAChC,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAC/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAiE/E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAChC,SAAS,WAAW,CAAI,EACtB,GAAG,EACH,OAAO,EACP,KAAK,GAAG,EAAE,EACV,QAAQ,EACR,WAAW,GAAG,kBAAkB,EAChC,SAAS,GAAG,gBAAgB,EAC5B,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,SAAS,EACT,KAAK,EACL,SAAS,EACT,SAAS,EACT,WAAW,EACX,aAAa,EACb,WAAW,GAAG,KAAK,EACnB,kBAAkB,GAAG,KAAK,EAC1B,YAAY,EACZ,GAAG,KAAK,EACY;IAEpB,MAAM,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,CAAA;IAClC,MAAM,QAAQ,GAAG,MAAM,CAAwB,IAAI,CAAC,CAAA;IACpD,MAAM,OAAO,GAAG,GAAG,IAAI,QAAQ,CAAA;IAC/B,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAE7C,0EAA0E;IAC1E,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,EAAE;QACjC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAA;QAClD,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QACpE,OAAO,CAAC,GAAG,OAAO,EAAE,GAAG,WAAW,CAAC,CAAA;IACrC,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAA;IAE/B,MAAM,QAAQ,GAAG,wBAAwB,CAAC;QACxC,OAAO,EAAE,aAAa;QACtB,SAAS;QACT,YAAY,EAAE,KAAK;QACnB,QAAQ;QACR,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE;YAC9B,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;YACjC,OAAO,KAAK,CAAC,iBAAiB,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAA;QACvE,CAAC;KACF,CAAC,CAAA;IAEF,kBAAkB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAA;IACzF,cAAc,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAA;IACzD,iBAAiB,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAA;IAEpD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,KAAK,KAAK,QAAQ,CAAC,KAAK;YAAE,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IACxD,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAEpC,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,MAAS,EAAE,EAAE;QACjD,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,MAAM,CAAC,CAAC,CAAA;QACtE,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAC7B,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,CAAA;IAEhC,MAAM,oBAAoB,GAAG,WAAW,CAAC,CAAC,CAAwC,EAAE,EAAE;QACpF,IAAI,CAAC,kBAAkB,IAAI,CAAC,YAAY,IAAI,CAAC,QAAQ,CAAC,MAAM;YAAE,OAAM;QACpE,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;QAClD,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,WAAW,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/D,CAAC,CAAC,cAAc,EAAE,CAAA;YAClB,MAAM,SAAS,GAAG,YAAY,CAAC,WAAW,CAAC,CAAA;YAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;gBAC5B,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;gBACzB,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,CAAA;gBACjC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;oBACzD,OAAO,IAAI,EAAE,WAAW,EAAE,KAAK,IAAI,EAAE,WAAW,EAAE,CAAA;gBACpD,CAAC;gBACD,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;YACtD,CAAC,CAAC,CAAA;YACF,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,KAAK,EAAE,SAAS,CAAC,CAAC,CAAA;YAC1C,CAAC;YACD,QAAQ,CAAC,SAAS,CAAC,EAAS,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC,EAAE,CAAC,kBAAkB,EAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC,CAAA;IAErE,iEAAiE;IACjE,MAAM,kBAAkB,GAAG,OAAO,CAAC,GAAG,EAAE;QACtC,IAAI,CAAC,kBAAkB,IAAI,CAAC,QAAQ,CAAC,MAAM;YAAE,OAAO,KAAK,CAAA;QACzD,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;QAClD,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAA;QAE1D,MAAM,eAAe,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;YAClD,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;YACjC,IAAI,CAAC,KAAK;gBAAE,OAAO,KAAK,CAAA;YACxB,OAAO,KAAK,CAAC,WAAW,EAAE,KAAK,WAAW,CAAC,WAAW,EAAE,CAAA;QAC1D,CAAC,CAAC,CAAA;QAEF,OAAO,CAAC,eAAe,CAAA;IACzB,CAAC,EAAE,CAAC,kBAAkB,EAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC,CAAA;IAErE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE;QAC1B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,eAAM,SAAS,EAAC,yBAAyB,YAAE,WAAW,GAAQ,CAAA;QAC7F,MAAM,QAAQ,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,EAAE,CAAA;QAErC,IAAI,WAAW,EAAE,CAAC;YAChB,OAAO,CACL,KAAC,GAAG,IAAC,SAAS,EAAC,cAAc,EAAC,GAAG,EAAC,KAAK,EAAC,IAAI,EAAC,GAAG,EAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAC3F,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CACtB,MAAC,KAAK,IAEJ,SAAS,EAAC,MAAM,EAChB,GAAG,EAAC,KAAK,EACT,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,eAAe,EAAE,CAAA,CAAC,CAAC,aAEvC,yBAAO,WAAW,CAAC,MAAM,CAAC,GAAQ,EACjC,CAAC,QAAQ,IAAI,CACZ,KAAC,UAAU,IACT,IAAI,EAAC,OAAO,EACZ,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,eAAe,EACzB,IAAI,EAAC,IAAI,EACT,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;gCACb,CAAC,CAAC,eAAe,EAAE,CAAA;gCACnB,gBAAgB,CAAC,MAAM,CAAC,CAAA;4BAC1B,CAAC,gBACW,GAAG,CAAC,CAAC,MAAM,IAAI,WAAW,CAAC,MAAM,CAAC,EAAE,GAChD,CACH,KAnBI,SAAS,CAAC,MAAM,CAAC,CAoBhB,CACT,CAAC,GACE,CACP,CAAA;QACH,CAAC;QAED,OAAO,CACL,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC;eACpB,CAAC,YAAY;gBACd,CAAC,CAAC,KAAC,GAAG,IAAC,SAAS,EAAC,aAAa,YAC3B,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAC/B,yBAAwC,YAAY,CAAC,MAAM,CAAC,IAAjD,SAAS,CAAC,MAAM,CAAC,IAAI,KAAK,CAA+B,CACrE,CAAC,GACE;gBACN,CAAC,CAAC,eAAM,SAAS,EAAC,aAAa,YAAE,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAQ,CAC9E,CACF,IAAI,gBAAa,CACnB,CAAA;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC,CAAA;IAExH,OAAO,CACL,MAAC,eAAe,IACd,GAAG,EAAC,KAAK,EACT,SAAS,EAAC,cAAc,EACxB,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,EAClF,SAAS,EAAE,WAAW,CAAC;YACrB,SAAS;YACT,SAAS,KAAK,KAAK,IAAI,YAAY;YACnC,IAAI,IAAI,MAAM;YACd,OAAO,IAAI,SAAS;YACpB,QAAQ,IAAI,UAAU;YACtB,WAAW,IAAI,YAAY;SAC5B,CAAC,EACF,GAAG,EAAE,OAAO,eACD,OAAO,KACd,KAAK,aAET,kBACE,OAAO,EAAE,GAAG,EAAE;oBACZ,IAAI,QAAQ;wBAAE,OAAM;oBACpB,UAAU,CAAC,IAAI,CAAC,CAAA;oBAChB,OAAO,CAAC,IAAI,CAAC,CAAA;gBACf,CAAC,EACD,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,gBACnB,CAAC,CAAC,iBAAiB,EAC/B,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAClC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,aAE7C,MAAM,EACN,OAAO,IAAI,KAAC,gBAAgB,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,QAAQ,GAAG,IACtD,EACT,eAAK,SAAS,EAAC,iBAAiB,iBAAc,CAAC,IAAI,KAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,aACrF,UAAU,IAAI,cAAK,SAAS,EAAC,YAAY,YACxC,8BAAiB,aAAa,EAAC,SAAS,EAAC,MAAM,aAC7C,2BAAe,UAAU,EAAC,SAAS,EAAC,4BAA4B,GAAK,EACrE,KAAC,KAAK,IACJ,IAAI,EAAC,QAAQ,EACb,KAAK,EAAE,QAAQ,CAAC,MAAM,EACtB,QAAQ,EAAE,QAAQ,CAAC,SAAS,EAC5B,OAAO,EAAE,oBAAoB,gBACjB,CAAC,CAAC,mBAAmB,EACjC,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,SAAS,GACtE,IACE,GACF,EACL,aAAa,IAAI,CAChB,KAAC,QAAQ,IACP,SAAS,EAAC,YAAY,EACtB,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,eAAe,EAAE,EAChF,KAAK,EAAE,QAAQ,CAAC,aAAa,YAE5B,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAChD,CACZ,EACD,KAAC,aAAa,IACZ,SAAS,EAAC,SAAS,EACnB,GAAG,EAAC,GAAG,EACP,OAAO,EAAE,QAAQ,CAAC,OAAO,EACzB,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAC3B,KAAK,EAAE,QAAQ,CAAC,KAAK,EACrB,SAAS,EAAE,QAAQ,CAAC,SAAS,EAC7B,SAAS,EAAE,KAAK,EAChB,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,CAChC,MAAC,eAAe,IACd,SAAS,EAAC,cAAc,EACxB,GAAG,EAAC,OAAO,EACX,SAAS,EAAE,WAAW,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,sBAAsB,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,CAAC,aAE1F,QAAQ,EACR,YAAY,EAAE,CAAC,MAAM,CAAC,IAAI,WAAW,CAAC,MAAM,CAAC,IAC9B,CACnB,GACD,EACD,kBAAkB,IAAI,CACrB,eAAK,SAAS,EAAC,kBAAkB,EAAC,KAAK,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,aAChG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,QAAI,CAAC,CAAC,eAAe,SAChD,CACP,IACG,IACU,CACnB,CAAA;AACH,CAAC,CACF,CAAA;AAED,MAAM,UAAU,GAAG;IACjB,EAAE,EAAE;QACF,iBAAiB,EAAE,iDAAiD;QACpE,mBAAmB,EAAE,oBAAoB;QACzC,eAAe,EAAE,kBAAkB;QACnC,SAAS,EAAE,YAAY;QACvB,MAAM,EAAE,QAAQ;QAChB,sBAAsB,EAAE,8BAA8B;QACtD,eAAe,EAAE,oBAAoB;KACtC;IACD,EAAE,EAAE;QACF,iBAAiB,EAAE,8DAA8D;QACjF,mBAAmB,EAAE,kBAAkB;QACvC,eAAe,EAAE,iBAAiB;QAClC,SAAS,EAAE,kBAAkB;QAC7B,MAAM,EAAE,SAAS;QACjB,sBAAsB,EAAE,0CAA0C;QAClE,eAAe,EAAE,gCAAgC;KAClD;CACF,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stack-spot/citric-react",
|
|
3
|
-
"version": "0.37.
|
|
3
|
+
"version": "0.37.1-beta.2",
|
|
4
4
|
"author": "StackSpot",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./dist/index.js",
|
|
9
|
-
"./package.json": "./package.json",
|
|
10
9
|
"./theme.css": "./dist/theme.css",
|
|
11
10
|
"./citric.css": "./dist/citric.css"
|
|
12
11
|
},
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { listToClass } from '@stack-spot/portal-theme'
|
|
2
2
|
import { useTranslate } from '@stack-spot/portal-translate'
|
|
3
|
-
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
4
4
|
import { useCheckboxGroupControls } from '../../utils/checkbox'
|
|
5
5
|
import { applyCSSVariable } from '../../utils/css'
|
|
6
6
|
import { defaultRenderKey, defaultRenderLabel } from '../../utils/options'
|
|
7
7
|
import { withRef } from '../../utils/react'
|
|
8
|
+
import { Badge } from '../Badge'
|
|
8
9
|
import { Checkbox } from '../Checkbox'
|
|
9
10
|
import { CheckboxGroup } from '../CheckboxGroup'
|
|
10
11
|
import { CitricComponent } from '../CitricComponent'
|
|
12
|
+
import { IconButton } from '../IconBox'
|
|
11
13
|
import { Input } from '../Input'
|
|
12
14
|
import { Row } from '../layout'
|
|
13
15
|
import { ProgressCircular } from '../ProgressCircular'
|
|
@@ -49,6 +51,28 @@ export interface BaseMultiSelectProps<T> extends
|
|
|
49
51
|
* @default false
|
|
50
52
|
*/
|
|
51
53
|
showSelectAll?: boolean,
|
|
54
|
+
/**
|
|
55
|
+
* Whether to render selected values as removable chips/tags.
|
|
56
|
+
*
|
|
57
|
+
* @default false
|
|
58
|
+
*/
|
|
59
|
+
showAsChips?: boolean,
|
|
60
|
+
/**
|
|
61
|
+
* Whether to allow adding custom values that don't exist in options.
|
|
62
|
+
* When enabled, typing in the search and pressing Enter will add the value.
|
|
63
|
+
* The value will be added as a string to the list.
|
|
64
|
+
*
|
|
65
|
+
* @default false
|
|
66
|
+
*/
|
|
67
|
+
allowCustomOptions?: boolean,
|
|
68
|
+
/**
|
|
69
|
+
* Function to create a new option from a string input.
|
|
70
|
+
* Required when `allowCustomOptions` is true.
|
|
71
|
+
*
|
|
72
|
+
* @param input the string input from the user
|
|
73
|
+
* @returns the new option of type T
|
|
74
|
+
*/
|
|
75
|
+
createOption?: (inputValue: string) => T,
|
|
52
76
|
}
|
|
53
77
|
|
|
54
78
|
export type MultiSelectProps<T> = Omit<React.JSX.IntrinsicElements['div'], 'ref' | 'onChange' | 'onFocus' | 'onBlur'> &
|
|
@@ -94,6 +118,9 @@ export const MultiSelect = withRef(
|
|
|
94
118
|
showArrow,
|
|
95
119
|
placeholder,
|
|
96
120
|
showSelectAll,
|
|
121
|
+
showAsChips = false,
|
|
122
|
+
allowCustomOptions = false,
|
|
123
|
+
createOption,
|
|
97
124
|
...props
|
|
98
125
|
}: MultiSelectProps<T>,
|
|
99
126
|
) {
|
|
@@ -102,12 +129,23 @@ export const MultiSelect = withRef(
|
|
|
102
129
|
const element = ref ?? _element
|
|
103
130
|
const [open, setOpen] = useState(false)
|
|
104
131
|
const [focused, setFocused] = useState(false)
|
|
132
|
+
|
|
133
|
+
// Merge options with selected values that are not in the original options
|
|
134
|
+
const mergedOptions = useMemo(() => {
|
|
135
|
+
const optionKeys = new Set(options.map(renderKey))
|
|
136
|
+
const extraValues = value.filter(v => !optionKeys.has(renderKey(v)))
|
|
137
|
+
return [...options, ...extraValues]
|
|
138
|
+
}, [options, value, renderKey])
|
|
139
|
+
|
|
105
140
|
const controls = useCheckboxGroupControls({
|
|
106
|
-
options,
|
|
141
|
+
options: mergedOptions,
|
|
107
142
|
renderKey,
|
|
108
143
|
initialValue: value,
|
|
109
144
|
onChange,
|
|
110
|
-
applyFilter: (filter, option) =>
|
|
145
|
+
applyFilter: (filter, option) => {
|
|
146
|
+
const label = renderLabel(option)
|
|
147
|
+
return label.toLocaleLowerCase().includes(filter.toLocaleLowerCase())
|
|
148
|
+
},
|
|
111
149
|
})
|
|
112
150
|
|
|
113
151
|
useOpenPanelEffect({ open, setOpen, setSearch: controls.setFilter, element, searchable })
|
|
@@ -118,17 +156,95 @@ export const MultiSelect = withRef(
|
|
|
118
156
|
if (value !== controls.value) controls.setValue(value)
|
|
119
157
|
}, [value.map(renderKey).join(',')])
|
|
120
158
|
|
|
159
|
+
const handleRemoveChip = useCallback((option: T) => {
|
|
160
|
+
const newValue = value.filter(v => renderKey(v) !== renderKey(option))
|
|
161
|
+
controls.setValue(newValue)
|
|
162
|
+
}, [controls, renderKey, value])
|
|
163
|
+
|
|
164
|
+
const handleAddCustomValue = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
165
|
+
if (!allowCustomOptions || !createOption || !controls.filter) return
|
|
166
|
+
const filterValue = String(controls.filter).trim()
|
|
167
|
+
if (e.key === 'Enter' && filterValue && filterValue.length > 0) {
|
|
168
|
+
e.preventDefault()
|
|
169
|
+
const newOption = createOption(filterValue)
|
|
170
|
+
const exists = value.some(v => {
|
|
171
|
+
const key1 = renderKey(v)
|
|
172
|
+
const key2 = renderKey(newOption)
|
|
173
|
+
if (typeof key1 === 'string' && typeof key2 === 'string') {
|
|
174
|
+
return key1?.toLowerCase() === key2?.toLowerCase()
|
|
175
|
+
}
|
|
176
|
+
return JSON.stringify(key1) === JSON.stringify(key2)
|
|
177
|
+
})
|
|
178
|
+
if (!exists) {
|
|
179
|
+
controls.setValue([...value, newOption])
|
|
180
|
+
}
|
|
181
|
+
controls.setFilter('' as any)
|
|
182
|
+
}
|
|
183
|
+
}, [allowCustomOptions, controls.filter, mergedOptions, renderLabel])
|
|
184
|
+
|
|
185
|
+
// Check if the current filter does not match any existing option
|
|
186
|
+
const hasTemporaryOption = useMemo(() => {
|
|
187
|
+
if (!allowCustomOptions || !controls.filter) return false
|
|
188
|
+
const filterValue = String(controls.filter).trim()
|
|
189
|
+
if (!filterValue || filterValue.length === 0) return false
|
|
190
|
+
|
|
191
|
+
const matchesExisting = mergedOptions.some(option => {
|
|
192
|
+
const label = renderLabel(option)
|
|
193
|
+
if (!label) return false
|
|
194
|
+
return label.toLowerCase() === filterValue.toLowerCase()
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
return !matchesExisting
|
|
198
|
+
}, [allowCustomOptions, controls.filter, mergedOptions, renderLabel])
|
|
199
|
+
|
|
121
200
|
const header = useMemo(() => {
|
|
122
201
|
if (value.length === 0) return <span className="placeholder header-text">{placeholder}</span>
|
|
123
202
|
const reversed = [...value].reverse()
|
|
203
|
+
|
|
204
|
+
if (showAsChips) {
|
|
205
|
+
return (
|
|
206
|
+
<Row className="header-chips" gap="4px" flex="1" style={{ maxWidth: '100%', flexWrap: 'wrap' }}>
|
|
207
|
+
{reversed.map(option => (
|
|
208
|
+
<Badge
|
|
209
|
+
key={renderKey(option)}
|
|
210
|
+
className="chip"
|
|
211
|
+
tag="div"
|
|
212
|
+
onClick={(e) => { e.stopPropagation() }}
|
|
213
|
+
>
|
|
214
|
+
<span>{renderLabel(option)}</span>
|
|
215
|
+
{!disabled && (
|
|
216
|
+
<IconButton
|
|
217
|
+
icon="Times"
|
|
218
|
+
type="button"
|
|
219
|
+
className="remove-button"
|
|
220
|
+
size="xs"
|
|
221
|
+
disabled={disabled}
|
|
222
|
+
onClick={(e) => {
|
|
223
|
+
e.stopPropagation()
|
|
224
|
+
handleRemoveChip(option)
|
|
225
|
+
}}
|
|
226
|
+
aria-label={`${t.remove} ${renderLabel(option)}`}
|
|
227
|
+
/>
|
|
228
|
+
)}
|
|
229
|
+
</Badge>
|
|
230
|
+
))}
|
|
231
|
+
</Row>
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
124
235
|
return (
|
|
125
236
|
(renderHeader?.(reversed)
|
|
126
237
|
?? (renderOption
|
|
127
|
-
? <Row className="header-text">
|
|
238
|
+
? <Row className="header-text">
|
|
239
|
+
{reversed.map((option, index) => (
|
|
240
|
+
<span key={renderKey(option) ?? index}>{renderOption(option)}</span>
|
|
241
|
+
))}
|
|
242
|
+
</Row>
|
|
128
243
|
: <span className="header-text">{reversed.map(renderLabel).join(', ')}</span>
|
|
129
244
|
)
|
|
130
245
|
) || <span></span>
|
|
131
|
-
)
|
|
246
|
+
)
|
|
247
|
+
}, [value, placeholder, showAsChips, disabled, renderKey, renderLabel, handleRemoveChip, t, renderHeader, renderOption])
|
|
132
248
|
|
|
133
249
|
return (
|
|
134
250
|
<CitricComponent
|
|
@@ -141,6 +257,7 @@ export const MultiSelect = withRef(
|
|
|
141
257
|
open && 'open',
|
|
142
258
|
focused && 'focused',
|
|
143
259
|
disabled && 'disabled',
|
|
260
|
+
showAsChips && 'with-chips',
|
|
144
261
|
])}
|
|
145
262
|
ref={element}
|
|
146
263
|
aria-busy={loading}
|
|
@@ -164,7 +281,14 @@ export const MultiSelect = withRef(
|
|
|
164
281
|
{searchable && <div className="search-bar">
|
|
165
282
|
<div data-citric="field-group" className="auto">
|
|
166
283
|
<i data-citric="icon-box" className="citric-icon outline Search"></i>
|
|
167
|
-
<Input
|
|
284
|
+
<Input
|
|
285
|
+
type="search"
|
|
286
|
+
value={controls.filter}
|
|
287
|
+
onChange={controls.setFilter}
|
|
288
|
+
onKeyUp={handleAddCustomValue}
|
|
289
|
+
aria-label={t.searchAccessibility}
|
|
290
|
+
placeholder={allowCustomOptions ? t.searchOrAddPlaceholder : undefined}
|
|
291
|
+
/>
|
|
168
292
|
</div>
|
|
169
293
|
</div>}
|
|
170
294
|
{showSelectAll && (
|
|
@@ -195,6 +319,11 @@ export const MultiSelect = withRef(
|
|
|
195
319
|
</CitricComponent>
|
|
196
320
|
)}
|
|
197
321
|
/>
|
|
322
|
+
{hasTemporaryOption && (
|
|
323
|
+
<div className="temporary-option" style={{ fontStyle: 'italic', padding: '8px 16px', opacity: 0.7 }}>
|
|
324
|
+
{String(controls.filter).trim()} ({t.pressEnterToAdd})
|
|
325
|
+
</div>
|
|
326
|
+
)}
|
|
198
327
|
</div>
|
|
199
328
|
</CitricComponent>
|
|
200
329
|
)
|
|
@@ -207,11 +336,17 @@ const dictionary = {
|
|
|
207
336
|
searchAccessibility: 'Filter the options',
|
|
208
337
|
removeSelection: 'Remove selection',
|
|
209
338
|
selectAll: 'Select all',
|
|
339
|
+
remove: 'Remove',
|
|
340
|
+
searchOrAddPlaceholder: 'Search or press Enter to add',
|
|
341
|
+
pressEnterToAdd: 'press Enter to add',
|
|
210
342
|
},
|
|
211
343
|
pt: {
|
|
212
344
|
accessibilityHelp: 'Pressione a seta para baixo para selecionar múltiplas opções',
|
|
213
345
|
searchAccessibility: 'Filtre as opções',
|
|
214
346
|
removeSelection: 'Remover seleção',
|
|
215
347
|
selectAll: 'Selecionar todos',
|
|
348
|
+
remove: 'Remover',
|
|
349
|
+
searchOrAddPlaceholder: 'Busque ou pressione Enter para adicionar',
|
|
350
|
+
pressEnterToAdd: 'pressione Enter para adicionar',
|
|
216
351
|
},
|
|
217
352
|
}
|