@stack-spot/citric-react 0.37.1-beta.4 → 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 +6 -75
- 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 +5 -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":";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;AAEtC,MAAM,YAAY,GAAG,CAAC,CAAA;AACtB,MAAM,yBAAyB,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AAoC9C;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC,CAChC,EACE,KAAK,EACL,QAAQ,EACR,UAAU,EACV,eAAe,GAAG,yBAAyB,EAC3C,GAAG,KAAK,EACQ,EAClB,EAAE;IACF,MAAM,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,CAAA;IAClC,MAAM,WAAW,GAAG,OAAO,CACzB,GAAG,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,iBAAgB,QAAQ,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,YAAG,CAAC,IAAjC,CAAC,CAA0C,CAAC,EACxF,CAAC,eAAe,CAAC,CAClB,CAAA;IACD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE;QAC/B,MAAM,OAAO,GAAyB,EAAE,CAAA;QACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,OAAO,CAAC,IAAI,CAAC,iBAAgB,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,YAAG,CAAC,IAA3C,CAAC,CAAoD,CAAC,CAAA;QAClF,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAA;IAE5B,OAAO,CACL,MAAC,eAAe,IAAC,GAAG,EAAC,KAAK,EAAC,SAAS,EAAC,YAAY,KAAK,KAAK,aACzD,cAAK,SAAS,EAAC,WAAW,YACxB,4BACG,CAAC,CAAC,YAAY,OACf,iBACE,IAAI,EAAC,cAAc,EACnB,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,YAE9E,WAAW,GACL,IACH,GACJ,EACN,eAAK,SAAS,EAAC,aAAa,aAC1B,0BACE,iBACE,IAAI,EAAC,MAAM,EACX,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,EAC7E,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,YAAY,YAEhC,WAAW,GACL,GACH,EACP,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,EAC9E,KAAC,UAAU,IACT,IAAI,EAAC,aAAa,gBACP,UAAU,EACrB,KAAK,EAAC,UAAU,EAChB,QAAQ,EAAE,KAAK,CAAC,IAAI,KAAK,YAAY,EACrC,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,GAC3F,EACF,KAAC,UAAU,IACT,IAAI,EAAC,cAAc,gBACR,MAAM,EACjB,KAAK,EAAC,MAAM,EACZ,QAAQ,EAAE,KAAK,CAAC,IAAI,KAAK,UAAU,EACnC,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,GACzF,IACE,IACU,CACnB,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,MAAM,UAAU,GAAG;IACjB,EAAE,EAAE;QACF,YAAY,EAAE,gBAAgB;QAC9B,eAAe,EAAE,WAAW;QAC5B,aAAa,EAAE,aAAa;KAC7B;IACD,EAAE,EAAE;QACF,YAAY,EAAE,kBAAkB;QAChC,eAAe,EAAE,aAAa;QAC9B,aAAa,EAAE,eAAe;KAC/B;CACmB,CAAA"}
|
|
@@ -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 });
|
|
@@ -65,79 +54,27 @@ export const MultiSelect = withRef(function MultiSelect({ ref, options, value =
|
|
|
65
54
|
if (value !== controls.value)
|
|
66
55
|
controls.setValue(value);
|
|
67
56
|
}, [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, 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]);
|
|
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 })
|
|
@@ -156,93 +118,9 @@ export const MultiSelect = withRef(
|
|
|
156
118
|
if (value !== controls.value) controls.setValue(value)
|
|
157
119
|
}, [value.map(renderKey).join(',')])
|
|
158
120
|
|
|
159
|
-
|
|
160
|
-
const handleRemoveChip = useCallback((option: T) => {
|
|
161
|
-
const newValue = value.filter(v => renderKey(v) !== renderKey(option))
|
|
162
|
-
controls.setValue(newValue)
|
|
163
|
-
}, [controls, renderKey, value])
|
|
164
|
-
|
|
165
|
-
const handleAddCustomValue = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
166
|
-
if (!allowCustomOptions || !createOption || !controls.filter) return
|
|
167
|
-
const filterValue = String(controls.filter).trim()
|
|
168
|
-
if (e.key === 'Enter' && filterValue && filterValue.length > 0) {
|
|
169
|
-
e.preventDefault()
|
|
170
|
-
const newOption = createOption(filterValue)
|
|
171
|
-
const exists = value.some(v => {
|
|
172
|
-
const key1 = renderKey(v)
|
|
173
|
-
const key2 = renderKey(newOption)
|
|
174
|
-
if (typeof key1 === 'string' && typeof key2 === 'string') {
|
|
175
|
-
return key1?.toLowerCase() === key2?.toLowerCase()
|
|
176
|
-
}
|
|
177
|
-
return JSON.stringify(key1) === JSON.stringify(key2)
|
|
178
|
-
})
|
|
179
|
-
if (!exists) {
|
|
180
|
-
controls.setValue([...value, newOption])
|
|
181
|
-
}
|
|
182
|
-
controls.setFilter('' as any)
|
|
183
|
-
}
|
|
184
|
-
}, [allowCustomOptions, createOption, controls, value, renderKey])
|
|
185
|
-
|
|
186
|
-
// Check if the current filter does not match any existing option
|
|
187
|
-
const hasTemporaryOption = useMemo(() => {
|
|
188
|
-
if (!allowCustomOptions || !controls.filter) return false
|
|
189
|
-
const filterValue = String(controls.filter).trim()
|
|
190
|
-
if (!filterValue || filterValue.length === 0) return false
|
|
191
|
-
|
|
192
|
-
const matchesExisting = mergedOptions.some(option => {
|
|
193
|
-
const label = renderLabel(option)
|
|
194
|
-
if (!label) return false
|
|
195
|
-
return label.toLowerCase() === filterValue.toLowerCase()
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
return !matchesExisting
|
|
199
|
-
}, [allowCustomOptions, controls.filter, mergedOptions, renderLabel])
|
|
200
|
-
|
|
201
121
|
const header = useMemo(() => {
|
|
202
122
|
if (value.length === 0) return <span className="placeholder header-text">{placeholder}</span>
|
|
203
123
|
const reversed = [...value].reverse()
|
|
204
|
-
|
|
205
|
-
if (showAsChips) {
|
|
206
|
-
return (
|
|
207
|
-
<Row
|
|
208
|
-
className="header-chips"
|
|
209
|
-
gap="4px"
|
|
210
|
-
flex="1"
|
|
211
|
-
style={{ maxWidth: '100%', flexWrap: 'wrap' }}
|
|
212
|
-
>
|
|
213
|
-
{reversed.map(option => (
|
|
214
|
-
<Badge
|
|
215
|
-
key={renderKey(option)}
|
|
216
|
-
className="chip"
|
|
217
|
-
tag="div"
|
|
218
|
-
onClick={(e) => e.stopPropagation()}
|
|
219
|
-
>
|
|
220
|
-
<span>{renderLabel(option)}</span>
|
|
221
|
-
{!disabled && (
|
|
222
|
-
<span
|
|
223
|
-
className="remove-button"
|
|
224
|
-
onClick={(e) => {
|
|
225
|
-
e.stopPropagation()
|
|
226
|
-
handleRemoveChip(option)
|
|
227
|
-
}}
|
|
228
|
-
onKeyDown={(e) => {
|
|
229
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
230
|
-
e.stopPropagation()
|
|
231
|
-
handleRemoveChip(option)
|
|
232
|
-
}
|
|
233
|
-
}}
|
|
234
|
-
tabIndex={0}
|
|
235
|
-
aria-label={`${t.remove} ${renderLabel(option)}`}
|
|
236
|
-
>
|
|
237
|
-
<Icon icon="Times" />
|
|
238
|
-
</span>
|
|
239
|
-
)}
|
|
240
|
-
</Badge>
|
|
241
|
-
))}
|
|
242
|
-
</Row>
|
|
243
|
-
)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
124
|
return (
|
|
247
125
|
(renderHeader?.(reversed)
|
|
248
126
|
?? (renderOption
|
|
@@ -250,8 +128,7 @@ export const MultiSelect = withRef(
|
|
|
250
128
|
: <span className="header-text">{reversed.map(renderLabel).join(', ')}</span>
|
|
251
129
|
)
|
|
252
130
|
) || <span></span>
|
|
253
|
-
)
|
|
254
|
-
}, [value, placeholder, showAsChips, disabled, renderKey, renderLabel, handleRemoveChip, t, renderHeader, renderOption])
|
|
131
|
+
)}, [value, placeholder])
|
|
255
132
|
|
|
256
133
|
return (
|
|
257
134
|
<CitricComponent
|
|
@@ -264,7 +141,6 @@ export const MultiSelect = withRef(
|
|
|
264
141
|
open && 'open',
|
|
265
142
|
focused && 'focused',
|
|
266
143
|
disabled && 'disabled',
|
|
267
|
-
showAsChips && 'with-chips',
|
|
268
144
|
])}
|
|
269
145
|
ref={element}
|
|
270
146
|
aria-busy={loading}
|
|
@@ -288,14 +164,7 @@ export const MultiSelect = withRef(
|
|
|
288
164
|
{searchable && <div className="search-bar">
|
|
289
165
|
<div data-citric="field-group" className="auto">
|
|
290
166
|
<i data-citric="icon-box" className="citric-icon outline Search"></i>
|
|
291
|
-
<Input
|
|
292
|
-
type="search"
|
|
293
|
-
value={controls.filter}
|
|
294
|
-
onChange={controls.setFilter}
|
|
295
|
-
onKeyUp={handleAddCustomValue}
|
|
296
|
-
aria-label={t.searchAccessibility}
|
|
297
|
-
placeholder={allowCustomOptions ? t.searchOrAddPlaceholder : undefined}
|
|
298
|
-
/>
|
|
167
|
+
<Input type="search" value={controls.filter} onChange={controls.setFilter} aria-label={t.searchAccessibility} />
|
|
299
168
|
</div>
|
|
300
169
|
</div>}
|
|
301
170
|
{showSelectAll && (
|
|
@@ -326,11 +195,6 @@ export const MultiSelect = withRef(
|
|
|
326
195
|
</CitricComponent>
|
|
327
196
|
)}
|
|
328
197
|
/>
|
|
329
|
-
{hasTemporaryOption && (
|
|
330
|
-
<div className="temporary-option" style={{ fontStyle: 'italic', padding: '8px 16px', opacity: 0.7 }}>
|
|
331
|
-
{String(controls.filter).trim()} ({t.pressEnterToAdd})
|
|
332
|
-
</div>
|
|
333
|
-
)}
|
|
334
198
|
</div>
|
|
335
199
|
</CitricComponent>
|
|
336
200
|
)
|
|
@@ -343,17 +207,11 @@ const dictionary = {
|
|
|
343
207
|
searchAccessibility: 'Filter the options',
|
|
344
208
|
removeSelection: 'Remove selection',
|
|
345
209
|
selectAll: 'Select all',
|
|
346
|
-
remove: 'Remove',
|
|
347
|
-
searchOrAddPlaceholder: 'Search or press Enter to add',
|
|
348
|
-
pressEnterToAdd: 'press Enter to add',
|
|
349
210
|
},
|
|
350
211
|
pt: {
|
|
351
212
|
accessibilityHelp: 'Pressione a seta para baixo para selecionar múltiplas opções',
|
|
352
213
|
searchAccessibility: 'Filtre as opções',
|
|
353
214
|
removeSelection: 'Remover seleção',
|
|
354
215
|
selectAll: 'Selecionar todos',
|
|
355
|
-
remove: 'Remover',
|
|
356
|
-
searchOrAddPlaceholder: 'Busque ou pressione Enter para adicionar',
|
|
357
|
-
pressEnterToAdd: 'pressione Enter para adicionar',
|
|
358
216
|
},
|
|
359
217
|
}
|