@stack-spot/citric-react 0.37.1-beta.3 → 0.37.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/dist/citric.css +0 -14
- package/dist/components/Pagination.d.ts +1 -1
- package/dist/components/Pagination.d.ts.map +1 -1
- package/dist/components/Pagination.js +4 -2
- package/dist/components/Pagination.js.map +1 -1
- package/dist/components/Select/MultiSelect.d.ts +1 -23
- package/dist/components/Select/MultiSelect.d.ts.map +1 -1
- package/dist/components/Select/MultiSelect.js +7 -76
- package/dist/components/Select/MultiSelect.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Pagination.tsx +26 -6
- package/src/components/Select/MultiSelect.tsx +6 -147
package/dist/citric.css
CHANGED
|
@@ -1632,8 +1632,6 @@ input[type="range"][data-citric="slider"] {
|
|
|
1632
1632
|
}
|
|
1633
1633
|
[data-citric="checkbox-row"] {
|
|
1634
1634
|
gap: 8px;
|
|
1635
|
-
display: flex !important;
|
|
1636
|
-
flex-direction: row !important;
|
|
1637
1635
|
}
|
|
1638
1636
|
label {
|
|
1639
1637
|
cursor: pointer;
|
|
@@ -1647,18 +1645,6 @@ input[type="range"][data-citric="slider"] {
|
|
|
1647
1645
|
.select-all {
|
|
1648
1646
|
padding: var(--padding, var(--default-padding));
|
|
1649
1647
|
}
|
|
1650
|
-
.remove-button {
|
|
1651
|
-
width: 15px;
|
|
1652
|
-
height: 15px;
|
|
1653
|
-
border-radius: 150px;
|
|
1654
|
-
display: flex;
|
|
1655
|
-
align-items: center;
|
|
1656
|
-
justify-content: center;
|
|
1657
|
-
cursor: pointer;
|
|
1658
|
-
}
|
|
1659
|
-
.remove-button[aria-disabled="true"] {
|
|
1660
|
-
cursor: not-allowed;
|
|
1661
|
-
}
|
|
1662
1648
|
}
|
|
1663
1649
|
|
|
1664
1650
|
|
|
@@ -42,5 +42,5 @@ export type PaginationProps = Omit<React.JSX.IntrinsicElements['div'], 'onChange
|
|
|
42
42
|
* />
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
|
-
export declare const Pagination: ({
|
|
45
|
+
export declare const Pagination: ({ value, onChange, totalPages, pageSizeOptions, ...props }: PaginationProps) => import("react/jsx-runtime.js").JSX.Element;
|
|
46
46
|
//# sourceMappingURL=Pagination.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pagination.d.ts","sourceRoot":"","sources":["../../src/components/Pagination.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Pagination.d.ts","sourceRoot":"","sources":["../../src/components/Pagination.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,KAAK,EAAE,eAAe,CAAC;IACvB;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,GAAG,mBAAmB,CAAA;AAExG;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU,+DAOlB,eAAe,4CAwDlB,CAAA"}
|
|
@@ -4,6 +4,8 @@ import { useMemo } from 'react';
|
|
|
4
4
|
import { withRef } from '../utils/react.js';
|
|
5
5
|
import { CitricComponent } from './CitricComponent.js';
|
|
6
6
|
import { IconButton } from './IconBox.js';
|
|
7
|
+
const DEFAULT_PAGE = 1;
|
|
8
|
+
const DEFAULT_PAGE_SIZE_OPTIONS = [10, 20, 30];
|
|
7
9
|
/**
|
|
8
10
|
* Renders a pagination UI, letting the user chose one among multiple pages. This is generally rendered at the bottom of a table.
|
|
9
11
|
*
|
|
@@ -17,7 +19,7 @@ import { IconButton } from './IconBox.js';
|
|
|
17
19
|
* />
|
|
18
20
|
* ```
|
|
19
21
|
*/
|
|
20
|
-
export const Pagination = withRef(({
|
|
22
|
+
export const Pagination = withRef(({ value, onChange, totalPages, pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS, ...props }) => {
|
|
21
23
|
const t = useTranslate(dictionary);
|
|
22
24
|
const sizeOptions = useMemo(() => pageSizeOptions.map(o => _jsx("option", { selected: value.size === o, children: o }, o)), [pageSizeOptions]);
|
|
23
25
|
const pageOptions = useMemo(() => {
|
|
@@ -27,7 +29,7 @@ export const Pagination = withRef(({ pageSizeOptions = [10, 20, 30], totalPages,
|
|
|
27
29
|
}
|
|
28
30
|
return options;
|
|
29
31
|
}, [value.page, totalPages]);
|
|
30
|
-
return (_jsxs(CitricComponent, { tag: "div", component: "pagination", ...props, children: [_jsx("div", { className: "page-size", children: _jsxs("label", { children: [t.itemsPerPage, ":", _jsx("select", { name: "itemsPerPage", onChange: e => onChange({ page:
|
|
32
|
+
return (_jsxs(CitricComponent, { tag: "div", component: "pagination", ...props, children: [_jsx("div", { className: "page-size", children: _jsxs("label", { children: [t.itemsPerPage, ":", _jsx("select", { name: "itemsPerPage", onChange: e => onChange({ page: DEFAULT_PAGE, size: parseInt(e.target.value) }), children: sizeOptions })] }) }), _jsxs("div", { className: "page-number", children: [_jsx("label", { children: _jsx("select", { name: "page", onChange: e => onChange({ page: parseInt(e.target.value), size: value.size }), value: value.page || DEFAULT_PAGE, children: pageOptions }) }), totalPages > 1 ? interpolate(t.ofTotalPlural, totalPages) : t.ofTotalSingular, _jsx(IconButton, { icon: "ChevronLeft", "aria-label": "previous", title: "previous", disabled: value.page === DEFAULT_PAGE, onClick: () => onChange({ page: Math.max(value.page - 1, DEFAULT_PAGE), size: value.size }) }), _jsx(IconButton, { icon: "ChevronRight", "aria-label": "next", title: "next", disabled: value.page === totalPages, onClick: () => onChange({ page: Math.min(totalPages, value.page + 1), size: value.size }) })] })] }));
|
|
31
33
|
});
|
|
32
34
|
const dictionary = {
|
|
33
35
|
en: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pagination.js","sourceRoot":"","sources":["../../src/components/Pagination.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAc,WAAW,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AACpF,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAC/B,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"Pagination.js","sourceRoot":"","sources":["../../src/components/Pagination.tsx"],"names":[],"mappings":"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"}
|
|
@@ -33,28 +33,6 @@ 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;
|
|
58
36
|
}
|
|
59
37
|
export type MultiSelectProps<T> = Omit<React.JSX.IntrinsicElements['div'], 'ref' | 'onChange' | 'onFocus' | 'onBlur'> & BaseMultiSelectProps<T>;
|
|
60
38
|
/**
|
|
@@ -78,5 +56,5 @@ export type MultiSelectProps<T> = Omit<React.JSX.IntrinsicElements['div'], 'ref'
|
|
|
78
56
|
* return <MultiSelect options={options} renderLabel={o => o.name} renderKey={o => o.id} value={value} setValue={setValue} />
|
|
79
57
|
* ```
|
|
80
58
|
*/
|
|
81
|
-
export declare const MultiSelect: <T>({ ref, options, value, onChange, renderLabel, renderKey, disabled, loading, renderOption, renderHeader, searchable, maxHeight, style, className, showArrow, placeholder, showSelectAll,
|
|
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;
|
|
82
60
|
//# 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":"AAcA,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;CACzB;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,sMAmBnB,gBAAgB,CAAC,CAAC,CAAC,4CAwGvB,CAAA"}
|
|
@@ -1,16 +1,14 @@
|
|
|
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 {
|
|
4
|
+
import { 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';
|
|
10
9
|
import { Checkbox } from '../Checkbox.js';
|
|
11
10
|
import { CheckboxGroup } from '../CheckboxGroup.js';
|
|
12
11
|
import { CitricComponent } from '../CitricComponent.js';
|
|
13
|
-
import { Icon } from '../Icon.js';
|
|
14
12
|
import { Input } from '../Input.js';
|
|
15
13
|
import { Row } from '../layout.js';
|
|
16
14
|
import { ProgressCircular } from '../ProgressCircular.js';
|
|
@@ -36,27 +34,18 @@ import { useDisabledEffect, useFocusEffect, useOpenPanelEffect } from './hooks.j
|
|
|
36
34
|
* return <MultiSelect options={options} renderLabel={o => o.name} renderKey={o => o.id} value={value} setValue={setValue} />
|
|
37
35
|
* ```
|
|
38
36
|
*/
|
|
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,
|
|
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 }) {
|
|
40
38
|
const t = useTranslate(dictionary);
|
|
41
39
|
const _element = useRef(null);
|
|
42
40
|
const element = ref ?? _element;
|
|
43
41
|
const [open, setOpen] = useState(false);
|
|
44
42
|
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]);
|
|
51
43
|
const controls = useCheckboxGroupControls({
|
|
52
|
-
options
|
|
44
|
+
options,
|
|
53
45
|
renderKey,
|
|
54
46
|
initialValue: value,
|
|
55
47
|
onChange,
|
|
56
|
-
applyFilter: (filter, option) =>
|
|
57
|
-
const label = renderLabel(option);
|
|
58
|
-
return label.toLocaleLowerCase().includes(filter.toLocaleLowerCase());
|
|
59
|
-
},
|
|
48
|
+
applyFilter: (filter, option) => renderLabel(option).toLocaleLowerCase().includes(filter.toLocaleLowerCase()),
|
|
60
49
|
});
|
|
61
50
|
useOpenPanelEffect({ open, setOpen, setSearch: controls.setFilter, element, searchable });
|
|
62
51
|
useFocusEffect({ element, focused, setFocused, setOpen });
|
|
@@ -64,80 +53,28 @@ export const MultiSelect = withRef(function MultiSelect({ ref, options, value =
|
|
|
64
53
|
useEffect(() => {
|
|
65
54
|
if (value !== controls.value)
|
|
66
55
|
controls.setValue(value);
|
|
67
|
-
}, [value,
|
|
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, createOption, controls, value, renderKey]);
|
|
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]);
|
|
56
|
+
}, [value.map(renderKey).join(',')]);
|
|
108
57
|
const header = useMemo(() => {
|
|
109
58
|
if (value.length === 0)
|
|
110
59
|
return _jsx("span", { className: "placeholder header-text", children: placeholder });
|
|
111
60
|
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("span", { className: "remove-button", onClick: (e) => {
|
|
114
|
-
e.stopPropagation();
|
|
115
|
-
handleRemoveChip(option);
|
|
116
|
-
}, onKeyDown: (e) => {
|
|
117
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
118
|
-
e.stopPropagation();
|
|
119
|
-
handleRemoveChip(option);
|
|
120
|
-
}
|
|
121
|
-
}, tabIndex: 0, "aria-label": `${t.remove} ${renderLabel(option)}`, children: _jsx(Icon, { icon: "Times" }) }))] }, renderKey(option)))) }));
|
|
122
|
-
}
|
|
123
61
|
return ((renderHeader?.(reversed)
|
|
124
62
|
?? (renderOption
|
|
125
63
|
? _jsx(Row, { className: "header-text", children: reversed.map(renderOption) })
|
|
126
64
|
: _jsx("span", { className: "header-text", children: reversed.map(renderLabel).join(', ') }))) || _jsx("span", {}));
|
|
127
|
-
}, [value, placeholder
|
|
65
|
+
}, [value, placeholder]);
|
|
128
66
|
return (_jsxs(CitricComponent, { tag: "div", component: "multi-select", style: maxHeight ? applyCSSVariable(style, 'max-height', `${maxHeight}px`) : style, className: listToClass([
|
|
129
67
|
className,
|
|
130
68
|
showArrow === false && 'hide-arrow',
|
|
131
69
|
open && 'open',
|
|
132
70
|
focused && 'focused',
|
|
133
71
|
disabled && 'disabled',
|
|
134
|
-
showAsChips && 'with-chips',
|
|
135
72
|
]), ref: element, "aria-busy": loading, ...props, children: [_jsxs("header", { onClick: () => {
|
|
136
73
|
if (disabled)
|
|
137
74
|
return;
|
|
138
75
|
setFocused(true);
|
|
139
76
|
setOpen(true);
|
|
140
|
-
}, 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,
|
|
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)] })) })] })] }));
|
|
141
78
|
});
|
|
142
79
|
const dictionary = {
|
|
143
80
|
en: {
|
|
@@ -145,18 +82,12 @@ const dictionary = {
|
|
|
145
82
|
searchAccessibility: 'Filter the options',
|
|
146
83
|
removeSelection: 'Remove selection',
|
|
147
84
|
selectAll: 'Select all',
|
|
148
|
-
remove: 'Remove',
|
|
149
|
-
searchOrAddPlaceholder: 'Search or press Enter to add',
|
|
150
|
-
pressEnterToAdd: 'press Enter to add',
|
|
151
85
|
},
|
|
152
86
|
pt: {
|
|
153
87
|
accessibilityHelp: 'Pressione a seta para baixo para selecionar múltiplas opções',
|
|
154
88
|
searchAccessibility: 'Filtre as opções',
|
|
155
89
|
removeSelection: 'Remover seleção',
|
|
156
90
|
selectAll: 'Selecionar todos',
|
|
157
|
-
remove: 'Remover',
|
|
158
|
-
searchOrAddPlaceholder: 'Busque ou pressione Enter para adicionar',
|
|
159
|
-
pressEnterToAdd: 'pressione Enter para adicionar',
|
|
160
91
|
},
|
|
161
92
|
};
|
|
162
93
|
//# 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,
|
|
1
|
+
{"version":3,"file":"MultiSelect.js","sourceRoot":"","sources":["../../../src/components/Select/MultiSelect.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -4,6 +4,9 @@ import { withRef } from '../utils/react'
|
|
|
4
4
|
import { CitricComponent } from './CitricComponent'
|
|
5
5
|
import { IconButton } from './IconBox'
|
|
6
6
|
|
|
7
|
+
const DEFAULT_PAGE = 1
|
|
8
|
+
const DEFAULT_PAGE_SIZE_OPTIONS = [10, 20, 30]
|
|
9
|
+
|
|
7
10
|
export interface PaginationValue {
|
|
8
11
|
/**
|
|
9
12
|
* The first page is 1. If "0" is provided, it will be treated as if it was "1".
|
|
@@ -52,7 +55,13 @@ export type PaginationProps = Omit<React.JSX.IntrinsicElements['div'], 'onChange
|
|
|
52
55
|
* ```
|
|
53
56
|
*/
|
|
54
57
|
export const Pagination = withRef((
|
|
55
|
-
{
|
|
58
|
+
{
|
|
59
|
+
value,
|
|
60
|
+
onChange,
|
|
61
|
+
totalPages,
|
|
62
|
+
pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
|
|
63
|
+
...props
|
|
64
|
+
}: PaginationProps,
|
|
56
65
|
) => {
|
|
57
66
|
const t = useTranslate(dictionary)
|
|
58
67
|
const sizeOptions = useMemo(
|
|
@@ -72,27 +81,38 @@ export const Pagination = withRef((
|
|
|
72
81
|
<div className="page-size">
|
|
73
82
|
<label>
|
|
74
83
|
{t.itemsPerPage}:
|
|
75
|
-
<select
|
|
84
|
+
<select
|
|
85
|
+
name="itemsPerPage"
|
|
86
|
+
onChange={e => onChange({ page: DEFAULT_PAGE, size: parseInt(e.target.value) })}
|
|
87
|
+
>
|
|
88
|
+
{sizeOptions}
|
|
89
|
+
</select>
|
|
76
90
|
</label>
|
|
77
91
|
</div>
|
|
78
92
|
<div className="page-number">
|
|
79
93
|
<label>
|
|
80
|
-
<select
|
|
94
|
+
<select
|
|
95
|
+
name="page"
|
|
96
|
+
onChange={e => onChange({ page: parseInt(e.target.value), size: value.size })}
|
|
97
|
+
value={value.page || DEFAULT_PAGE}
|
|
98
|
+
>
|
|
99
|
+
{pageOptions}
|
|
100
|
+
</select>
|
|
81
101
|
</label>
|
|
82
102
|
{totalPages > 1 ? interpolate(t.ofTotalPlural, totalPages) : t.ofTotalSingular}
|
|
83
103
|
<IconButton
|
|
84
104
|
icon="ChevronLeft"
|
|
85
105
|
aria-label="previous"
|
|
86
106
|
title="previous"
|
|
87
|
-
disabled={value.page ===
|
|
88
|
-
onClick={() => onChange({ page: value.page - 1, size: value.size })}
|
|
107
|
+
disabled={value.page === DEFAULT_PAGE}
|
|
108
|
+
onClick={() => onChange({ page: Math.max(value.page - 1, DEFAULT_PAGE), size: value.size })}
|
|
89
109
|
/>
|
|
90
110
|
<IconButton
|
|
91
111
|
icon="ChevronRight"
|
|
92
112
|
aria-label="next"
|
|
93
113
|
title="next"
|
|
94
114
|
disabled={value.page === totalPages}
|
|
95
|
-
onClick={() => onChange({ page: value.page + 1, size: value.size })}
|
|
115
|
+
onClick={() => onChange({ page: Math.min(totalPages, value.page + 1), size: value.size })}
|
|
96
116
|
/>
|
|
97
117
|
</div>
|
|
98
118
|
</CitricComponent>
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { listToClass } from '@stack-spot/portal-theme'
|
|
2
2
|
import { useTranslate } from '@stack-spot/portal-translate'
|
|
3
|
-
import {
|
|
3
|
+
import { 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'
|
|
9
8
|
import { Checkbox } from '../Checkbox'
|
|
10
9
|
import { CheckboxGroup } from '../CheckboxGroup'
|
|
11
10
|
import { CitricComponent } from '../CitricComponent'
|
|
12
|
-
import { Icon } from '../Icon'
|
|
13
11
|
import { Input } from '../Input'
|
|
14
12
|
import { Row } from '../layout'
|
|
15
13
|
import { ProgressCircular } from '../ProgressCircular'
|
|
@@ -51,28 +49,6 @@ export interface BaseMultiSelectProps<T> extends
|
|
|
51
49
|
* @default false
|
|
52
50
|
*/
|
|
53
51
|
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,
|
|
76
52
|
}
|
|
77
53
|
|
|
78
54
|
export type MultiSelectProps<T> = Omit<React.JSX.IntrinsicElements['div'], 'ref' | 'onChange' | 'onFocus' | 'onBlur'> &
|
|
@@ -118,9 +94,6 @@ export const MultiSelect = withRef(
|
|
|
118
94
|
showArrow,
|
|
119
95
|
placeholder,
|
|
120
96
|
showSelectAll,
|
|
121
|
-
showAsChips = false,
|
|
122
|
-
allowCustomOptions = false,
|
|
123
|
-
createOption,
|
|
124
97
|
...props
|
|
125
98
|
}: MultiSelectProps<T>,
|
|
126
99
|
) {
|
|
@@ -129,23 +102,12 @@ export const MultiSelect = withRef(
|
|
|
129
102
|
const element = ref ?? _element
|
|
130
103
|
const [open, setOpen] = useState(false)
|
|
131
104
|
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
|
-
|
|
140
105
|
const controls = useCheckboxGroupControls({
|
|
141
|
-
options
|
|
106
|
+
options,
|
|
142
107
|
renderKey,
|
|
143
108
|
initialValue: value,
|
|
144
109
|
onChange,
|
|
145
|
-
applyFilter: (filter, option) =>
|
|
146
|
-
const label = renderLabel(option)
|
|
147
|
-
return label.toLocaleLowerCase().includes(filter.toLocaleLowerCase())
|
|
148
|
-
},
|
|
110
|
+
applyFilter: (filter, option) => renderLabel(option).toLocaleLowerCase().includes(filter.toLocaleLowerCase()),
|
|
149
111
|
})
|
|
150
112
|
|
|
151
113
|
useOpenPanelEffect({ open, setOpen, setSearch: controls.setFilter, element, searchable })
|
|
@@ -154,94 +116,11 @@ export const MultiSelect = withRef(
|
|
|
154
116
|
|
|
155
117
|
useEffect(() => {
|
|
156
118
|
if (value !== controls.value) controls.setValue(value)
|
|
157
|
-
}, [value,
|
|
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, createOption, controls, value, renderKey])
|
|
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])
|
|
119
|
+
}, [value.map(renderKey).join(',')])
|
|
199
120
|
|
|
200
121
|
const header = useMemo(() => {
|
|
201
122
|
if (value.length === 0) return <span className="placeholder header-text">{placeholder}</span>
|
|
202
123
|
const reversed = [...value].reverse()
|
|
203
|
-
|
|
204
|
-
if (showAsChips) {
|
|
205
|
-
return (
|
|
206
|
-
<Row
|
|
207
|
-
className="header-chips"
|
|
208
|
-
gap="4px"
|
|
209
|
-
flex="1"
|
|
210
|
-
style={{ maxWidth: '100%', flexWrap: 'wrap' }}
|
|
211
|
-
>
|
|
212
|
-
{reversed.map(option => (
|
|
213
|
-
<Badge
|
|
214
|
-
key={renderKey(option)}
|
|
215
|
-
className="chip"
|
|
216
|
-
tag="div"
|
|
217
|
-
onClick={(e) => e.stopPropagation()}
|
|
218
|
-
>
|
|
219
|
-
<span>{renderLabel(option)}</span>
|
|
220
|
-
{!disabled && (
|
|
221
|
-
<span
|
|
222
|
-
className="remove-button"
|
|
223
|
-
onClick={(e) => {
|
|
224
|
-
e.stopPropagation()
|
|
225
|
-
handleRemoveChip(option)
|
|
226
|
-
}}
|
|
227
|
-
onKeyDown={(e) => {
|
|
228
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
229
|
-
e.stopPropagation()
|
|
230
|
-
handleRemoveChip(option)
|
|
231
|
-
}
|
|
232
|
-
}}
|
|
233
|
-
tabIndex={0}
|
|
234
|
-
aria-label={`${t.remove} ${renderLabel(option)}`}
|
|
235
|
-
>
|
|
236
|
-
<Icon icon="Times" />
|
|
237
|
-
</span>
|
|
238
|
-
)}
|
|
239
|
-
</Badge>
|
|
240
|
-
))}
|
|
241
|
-
</Row>
|
|
242
|
-
)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
124
|
return (
|
|
246
125
|
(renderHeader?.(reversed)
|
|
247
126
|
?? (renderOption
|
|
@@ -249,8 +128,7 @@ export const MultiSelect = withRef(
|
|
|
249
128
|
: <span className="header-text">{reversed.map(renderLabel).join(', ')}</span>
|
|
250
129
|
)
|
|
251
130
|
) || <span></span>
|
|
252
|
-
)
|
|
253
|
-
}, [value, placeholder, showAsChips, disabled, renderKey, renderLabel, handleRemoveChip, t, renderHeader, renderOption])
|
|
131
|
+
)}, [value, placeholder])
|
|
254
132
|
|
|
255
133
|
return (
|
|
256
134
|
<CitricComponent
|
|
@@ -263,7 +141,6 @@ export const MultiSelect = withRef(
|
|
|
263
141
|
open && 'open',
|
|
264
142
|
focused && 'focused',
|
|
265
143
|
disabled && 'disabled',
|
|
266
|
-
showAsChips && 'with-chips',
|
|
267
144
|
])}
|
|
268
145
|
ref={element}
|
|
269
146
|
aria-busy={loading}
|
|
@@ -287,14 +164,7 @@ export const MultiSelect = withRef(
|
|
|
287
164
|
{searchable && <div className="search-bar">
|
|
288
165
|
<div data-citric="field-group" className="auto">
|
|
289
166
|
<i data-citric="icon-box" className="citric-icon outline Search"></i>
|
|
290
|
-
<Input
|
|
291
|
-
type="search"
|
|
292
|
-
value={controls.filter}
|
|
293
|
-
onChange={controls.setFilter}
|
|
294
|
-
onKeyUp={handleAddCustomValue}
|
|
295
|
-
aria-label={t.searchAccessibility}
|
|
296
|
-
placeholder={allowCustomOptions ? t.searchOrAddPlaceholder : undefined}
|
|
297
|
-
/>
|
|
167
|
+
<Input type="search" value={controls.filter} onChange={controls.setFilter} aria-label={t.searchAccessibility} />
|
|
298
168
|
</div>
|
|
299
169
|
</div>}
|
|
300
170
|
{showSelectAll && (
|
|
@@ -325,11 +195,6 @@ export const MultiSelect = withRef(
|
|
|
325
195
|
</CitricComponent>
|
|
326
196
|
)}
|
|
327
197
|
/>
|
|
328
|
-
{hasTemporaryOption && (
|
|
329
|
-
<div className="temporary-option" style={{ fontStyle: 'italic', padding: '8px 16px', opacity: 0.7 }}>
|
|
330
|
-
{String(controls.filter).trim()} ({t.pressEnterToAdd})
|
|
331
|
-
</div>
|
|
332
|
-
)}
|
|
333
198
|
</div>
|
|
334
199
|
</CitricComponent>
|
|
335
200
|
)
|
|
@@ -342,17 +207,11 @@ const dictionary = {
|
|
|
342
207
|
searchAccessibility: 'Filter the options',
|
|
343
208
|
removeSelection: 'Remove selection',
|
|
344
209
|
selectAll: 'Select all',
|
|
345
|
-
remove: 'Remove',
|
|
346
|
-
searchOrAddPlaceholder: 'Search or press Enter to add',
|
|
347
|
-
pressEnterToAdd: 'press Enter to add',
|
|
348
210
|
},
|
|
349
211
|
pt: {
|
|
350
212
|
accessibilityHelp: 'Pressione a seta para baixo para selecionar múltiplas opções',
|
|
351
213
|
searchAccessibility: 'Filtre as opções',
|
|
352
214
|
removeSelection: 'Remover seleção',
|
|
353
215
|
selectAll: 'Selecionar todos',
|
|
354
|
-
remove: 'Remover',
|
|
355
|
-
searchOrAddPlaceholder: 'Busque ou pressione Enter para adicionar',
|
|
356
|
-
pressEnterToAdd: 'pressione Enter para adicionar',
|
|
357
216
|
},
|
|
358
217
|
}
|