@buerokratt-ria/common-gui-components 0.0.60 → 0.0.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/templates/history-page/src/History.scss +29 -1
- package/templates/history-page/src/components/HeaderCombobox/index.tsx +5 -0
- package/templates/history-page/src/constants.ts +1 -0
- package/templates/history-page/src/index.tsx +27 -26
- package/translations/en/common.json +1 -0
- package/translations/et/common.json +1 -0
- package/ui-components/DataTable/DataTable.scss +8 -0
- package/ui-components/DataTable/index.tsx +61 -55
- package/ui-components/FormElements/FormCombobox/FormCombobox.scss +34 -3
- package/ui-components/FormElements/FormCombobox/index.tsx +97 -10
- package/ui-components/FormElements/FormSelect/FormMultiselect.tsx +108 -36
- package/ui-components/FormElements/FormSelect/FormSelect.scss +32 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ All changes to this project will be documented in this file.
|
|
|
5
5
|
## Template [MajorVersion.MediterraneanVersion.MinorVersion] - DD-MM-YYYY
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
## [0.0.61] - 11.06.2026
|
|
9
|
+
|
|
10
|
+
- Removed redundant scroll from the History table
|
|
11
|
+
- Added Apply action to the History table header multi-option filter
|
|
12
|
+
- Made the History table header sticky
|
|
13
|
+
|
|
8
14
|
## [0.0.60] - 09.06.2026
|
|
9
15
|
|
|
10
16
|
- History table: theme, follow up, quality sorting
|
package/package.json
CHANGED
|
@@ -69,9 +69,37 @@
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
.card-wrapper {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
72
74
|
flex: 1;
|
|
73
|
-
|
|
75
|
+
min-height: 0;
|
|
76
|
+
overflow: hidden;
|
|
74
77
|
transition: flex 0.3s ease;
|
|
78
|
+
|
|
79
|
+
.card {
|
|
80
|
+
box-sizing: border-box;
|
|
81
|
+
display: flex;
|
|
82
|
+
flex: 1;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
max-height: none;
|
|
85
|
+
min-height: 0;
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.card__body {
|
|
90
|
+
display: flex;
|
|
91
|
+
flex: 1;
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
min-height: 0;
|
|
94
|
+
overflow: hidden;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.data-table__scrollWrapper {
|
|
98
|
+
flex: 1;
|
|
99
|
+
max-height: none;
|
|
100
|
+
min-height: 0;
|
|
101
|
+
overflow: auto;
|
|
102
|
+
}
|
|
75
103
|
}
|
|
76
104
|
|
|
77
105
|
.drawer-container {
|
|
@@ -6,6 +6,7 @@ type HeaderComboboxBaseProps = {
|
|
|
6
6
|
readonly label: string;
|
|
7
7
|
readonly options?: ComponentProps<typeof FormCombobox>['options'];
|
|
8
8
|
readonly isSearchEnabled?: ComponentProps<typeof FormCombobox>['isSearchEnabled'];
|
|
9
|
+
readonly allOptionValue?: ComponentProps<typeof FormCombobox>['allOptionValue'];
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
type HeaderComboboxSingleProps = HeaderComboboxBaseProps & {
|
|
@@ -18,6 +19,7 @@ type HeaderComboboxMultipleProps = HeaderComboboxBaseProps & {
|
|
|
18
19
|
readonly multiple?: true;
|
|
19
20
|
readonly value?: string[];
|
|
20
21
|
readonly onChange: (value: string[]) => void;
|
|
22
|
+
readonly isApplyBtnVisible?: boolean;
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
type HeaderComboboxProps = HeaderComboboxSingleProps | HeaderComboboxMultipleProps;
|
|
@@ -27,6 +29,7 @@ const HeaderCombobox: FC<HeaderComboboxProps> = (props) => {
|
|
|
27
29
|
label,
|
|
28
30
|
options = [],
|
|
29
31
|
isSearchEnabled = true,
|
|
32
|
+
allOptionValue,
|
|
30
33
|
} = props;
|
|
31
34
|
const sharedProps = {
|
|
32
35
|
hideInputStyle: true,
|
|
@@ -37,6 +40,7 @@ const HeaderCombobox: FC<HeaderComboboxProps> = (props) => {
|
|
|
37
40
|
options,
|
|
38
41
|
isSearchEnabled,
|
|
39
42
|
isMenuPortaled: true,
|
|
43
|
+
allOptionValue,
|
|
40
44
|
};
|
|
41
45
|
|
|
42
46
|
return (
|
|
@@ -58,6 +62,7 @@ const HeaderCombobox: FC<HeaderComboboxProps> = (props) => {
|
|
|
58
62
|
multiple={true}
|
|
59
63
|
value={props.value}
|
|
60
64
|
onChange={props.onChange}
|
|
65
|
+
isApplyBtnVisible={props.isApplyBtnVisible}
|
|
61
66
|
/>
|
|
62
67
|
)}
|
|
63
68
|
</span>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ALL_COLUMNS_VALUE = '__all__';
|
|
@@ -16,9 +16,9 @@ import {
|
|
|
16
16
|
Dialog,
|
|
17
17
|
Drawer,
|
|
18
18
|
FormCheckbox,
|
|
19
|
+
FormCombobox,
|
|
19
20
|
FormDatepicker,
|
|
20
21
|
FormInput,
|
|
21
|
-
FormMultiselect,
|
|
22
22
|
HistoricalChat,
|
|
23
23
|
Icon,
|
|
24
24
|
Tooltip,
|
|
@@ -40,6 +40,7 @@ import {StoreState} from "../../../store";
|
|
|
40
40
|
import {saveFile} from "../../../services/file";
|
|
41
41
|
import {ChatMetadataPanel, HeaderCombobox, QualitySettings, SelectedFilterTags} from './components';
|
|
42
42
|
import { CharMeasurementType } from './types';
|
|
43
|
+
import { ALL_COLUMNS_VALUE } from './constants';
|
|
43
44
|
|
|
44
45
|
type HistoryProps = {
|
|
45
46
|
user: UserInfo | null;
|
|
@@ -162,7 +163,6 @@ const formatChatAnalysisCell = (
|
|
|
162
163
|
return value.join(', ');
|
|
163
164
|
};
|
|
164
165
|
|
|
165
|
-
const ALL_COLUMNS_VALUE = '__all__';
|
|
166
166
|
// Boolean -> truthy values before falsy
|
|
167
167
|
// For other columns -> desc before asc - populated before empty
|
|
168
168
|
const NON_EMPTY_FIRST_SORT_COLUMN_IDS = new Set([
|
|
@@ -436,7 +436,6 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
436
436
|
const isChatAnalysisEnabled = useMemo(() => {
|
|
437
437
|
return qualitySettingsConfigQuery.data?.chatAnalysisEnabled ?? false;
|
|
438
438
|
}, [qualitySettingsConfigQuery.data]);
|
|
439
|
-
console.log("IS CHAT ANALYSIS ENABLED", isChatAnalysisEnabled);
|
|
440
439
|
|
|
441
440
|
const getAllEndedChats = useMutation({
|
|
442
441
|
mutationFn: (data: {
|
|
@@ -580,21 +579,6 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
580
579
|
return realSelectedColumns;
|
|
581
580
|
};
|
|
582
581
|
|
|
583
|
-
const normalizeSelectedColumns = (selection: string[]) => {
|
|
584
|
-
const currentAllSelected = selectedColumns.includes(ALL_COLUMNS_VALUE) || areAllColumnsSelected(selectedColumns);
|
|
585
|
-
const nextAllSelected = selection.includes(ALL_COLUMNS_VALUE);
|
|
586
|
-
|
|
587
|
-
if (nextAllSelected && !currentAllSelected) {
|
|
588
|
-
return [ALL_COLUMNS_VALUE, ...getAllColumnValues()];
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
if (!nextAllSelected && currentAllSelected) {
|
|
592
|
-
return [];
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
return getUiSelectedColumns(selection);
|
|
596
|
-
};
|
|
597
|
-
|
|
598
582
|
const chatStatusChangeMutation = useMutation({
|
|
599
583
|
mutationFn: async (data: { chatId: string | number; event: string }) => {
|
|
600
584
|
const changeableTo = [
|
|
@@ -1268,10 +1252,12 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
1268
1252
|
label={t('chat.history.csaName')}
|
|
1269
1253
|
options={customerSupportAgentsQuery.data ?? []}
|
|
1270
1254
|
value={csaIdCodesFilter}
|
|
1255
|
+
allOptionValue={ALL_COLUMNS_VALUE}
|
|
1271
1256
|
onChange={(value) => {
|
|
1272
1257
|
const normalizedValue = normalizeCsaFilterValues(value);
|
|
1273
1258
|
tableHeaderForm.setValue('csaIdCodesFilter', normalizedValue);
|
|
1274
1259
|
}}
|
|
1260
|
+
isApplyBtnVisible
|
|
1275
1261
|
/>
|
|
1276
1262
|
);
|
|
1277
1263
|
},
|
|
@@ -1365,6 +1351,7 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
1365
1351
|
label={t('chat.history.rating') ?? ''}
|
|
1366
1352
|
options={ratingOptions}
|
|
1367
1353
|
value={feedbackRatings}
|
|
1354
|
+
allOptionValue={ALL_COLUMNS_VALUE}
|
|
1368
1355
|
onChange={(value) => {
|
|
1369
1356
|
setTableHeaderValue(
|
|
1370
1357
|
'feedbackRatings',
|
|
@@ -1375,6 +1362,7 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
1375
1362
|
)
|
|
1376
1363
|
);
|
|
1377
1364
|
}}
|
|
1365
|
+
isApplyBtnVisible
|
|
1378
1366
|
/>
|
|
1379
1367
|
);
|
|
1380
1368
|
},
|
|
@@ -1399,6 +1387,7 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
1399
1387
|
label={t('global.status') ?? ''}
|
|
1400
1388
|
options={statusOptions}
|
|
1401
1389
|
value={status}
|
|
1390
|
+
allOptionValue={ALL_COLUMNS_VALUE}
|
|
1402
1391
|
onChange={(value) => {
|
|
1403
1392
|
setTableHeaderValue(
|
|
1404
1393
|
'status',
|
|
@@ -1409,6 +1398,7 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
1409
1398
|
)
|
|
1410
1399
|
);
|
|
1411
1400
|
}}
|
|
1401
|
+
isApplyBtnVisible
|
|
1412
1402
|
isSearchEnabled={true}
|
|
1413
1403
|
/>
|
|
1414
1404
|
);
|
|
@@ -1447,12 +1437,14 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
1447
1437
|
label={t('chat.history.www') ?? ''}
|
|
1448
1438
|
value={domains}
|
|
1449
1439
|
options={domainOptions}
|
|
1440
|
+
allOptionValue={ALL_COLUMNS_VALUE}
|
|
1450
1441
|
onChange={(value) => {
|
|
1451
1442
|
setTableHeaderValue(
|
|
1452
1443
|
'domains',
|
|
1453
1444
|
normalizeAllOptionFilterValues(value, domains, currentDomains)
|
|
1454
1445
|
);
|
|
1455
1446
|
}}
|
|
1447
|
+
isApplyBtnVisible
|
|
1456
1448
|
/>
|
|
1457
1449
|
);
|
|
1458
1450
|
},
|
|
@@ -1472,6 +1464,7 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
1472
1464
|
label={t('chat.history.theme')}
|
|
1473
1465
|
options={themeOptions}
|
|
1474
1466
|
value={theme}
|
|
1467
|
+
allOptionValue={ALL_COLUMNS_VALUE}
|
|
1475
1468
|
onChange={(value) => {
|
|
1476
1469
|
setTableHeaderValue(
|
|
1477
1470
|
'theme',
|
|
@@ -1482,6 +1475,7 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
1482
1475
|
)
|
|
1483
1476
|
);
|
|
1484
1477
|
}}
|
|
1478
|
+
isApplyBtnVisible
|
|
1485
1479
|
/>
|
|
1486
1480
|
),
|
|
1487
1481
|
enableSorting: true,
|
|
@@ -1499,6 +1493,7 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
1499
1493
|
label={t('chat.history.responseQuality')}
|
|
1500
1494
|
options={responseQualityOptions}
|
|
1501
1495
|
value={responseQuality}
|
|
1496
|
+
allOptionValue={ALL_COLUMNS_VALUE}
|
|
1502
1497
|
onChange={(value) => {
|
|
1503
1498
|
setTableHeaderValue(
|
|
1504
1499
|
'responseQuality',
|
|
@@ -1509,6 +1504,7 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
1509
1504
|
)
|
|
1510
1505
|
);
|
|
1511
1506
|
}}
|
|
1507
|
+
isApplyBtnVisible
|
|
1512
1508
|
/>
|
|
1513
1509
|
),
|
|
1514
1510
|
enableSorting: true,
|
|
@@ -1526,6 +1522,7 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
1526
1522
|
label={t('chat.history.followUpStatus')}
|
|
1527
1523
|
options={followUpStatusOptions}
|
|
1528
1524
|
value={followUpStatus}
|
|
1525
|
+
allOptionValue={ALL_COLUMNS_VALUE}
|
|
1529
1526
|
onChange={(value) => {
|
|
1530
1527
|
setTableHeaderValue(
|
|
1531
1528
|
'followUpStatus',
|
|
@@ -1536,6 +1533,7 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
1536
1533
|
)
|
|
1537
1534
|
);
|
|
1538
1535
|
}}
|
|
1536
|
+
isApplyBtnVisible
|
|
1539
1537
|
/>
|
|
1540
1538
|
),
|
|
1541
1539
|
enableSorting: true,
|
|
@@ -2057,25 +2055,27 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
2057
2055
|
</>
|
|
2058
2056
|
)}
|
|
2059
2057
|
<Track style={{width: '240px'}}>
|
|
2060
|
-
<
|
|
2058
|
+
<FormCombobox
|
|
2061
2059
|
key={counterKey}
|
|
2062
2060
|
name="visibleColumns"
|
|
2063
2061
|
label={t('')}
|
|
2064
2062
|
placeholder={t('chat.history.chosenColumn')}
|
|
2065
2063
|
options={visibleColumnOptions}
|
|
2066
|
-
|
|
2067
|
-
selectedColumns.includes(o.value)
|
|
2068
|
-
)}
|
|
2064
|
+
value={selectedColumns}
|
|
2069
2065
|
selectedOptionsCount={getRealSelectedColumns(selectedColumns).length}
|
|
2070
|
-
|
|
2071
|
-
|
|
2066
|
+
multiple={true}
|
|
2067
|
+
allOptionValue={ALL_COLUMNS_VALUE}
|
|
2068
|
+
direction="down"
|
|
2069
|
+
onChange={(selection) => {
|
|
2070
|
+
const columns = getUiSelectedColumns(selection);
|
|
2072
2071
|
setSelectedColumns(columns);
|
|
2073
2072
|
setCounterKey(prev => prev + 1);
|
|
2074
2073
|
updatePagePreferences.mutate({
|
|
2075
2074
|
page_results: pagination.pageSize,
|
|
2076
|
-
selected_columns: getRealSelectedColumns(columns)
|
|
2075
|
+
selected_columns: getRealSelectedColumns(columns),
|
|
2077
2076
|
})
|
|
2078
2077
|
}}
|
|
2078
|
+
isApplyBtnVisible
|
|
2079
2079
|
/>
|
|
2080
2080
|
</Track>
|
|
2081
2081
|
</Track>
|
|
@@ -2105,12 +2105,13 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
2105
2105
|
onRemove={removeSelectedFilterTag}
|
|
2106
2106
|
onClearFiltersClick={onClearFilersClick}
|
|
2107
2107
|
/>
|
|
2108
|
-
<div className="card-drawer-container"
|
|
2108
|
+
<div className="card-drawer-container">
|
|
2109
2109
|
<div className="card-wrapper">
|
|
2110
2110
|
<Card>
|
|
2111
2111
|
<DataTable
|
|
2112
2112
|
data={filteredEndedChatsList}
|
|
2113
2113
|
sortable
|
|
2114
|
+
stickyHeader
|
|
2114
2115
|
columns={getFilteredColumns()}
|
|
2115
2116
|
selectedRow={(row) => row.original.id === selectedChat?.id}
|
|
2116
2117
|
pagination={pagination}
|
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
position: relative;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
&--sticky-header {
|
|
34
|
+
th {
|
|
35
|
+
background-color: get-color(white);
|
|
36
|
+
border-bottom-color: transparent;
|
|
37
|
+
box-shadow: inset 0 -1px 0 get-color(black-coral-10);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
td {
|
|
34
42
|
padding: 12px 24px 12px 16px;
|
|
35
43
|
border-bottom: 1px solid get-color(black-coral-2);
|
|
@@ -55,6 +55,7 @@ type DataTableProps = {
|
|
|
55
55
|
pagesCount?: number;
|
|
56
56
|
meta?: TableMeta<any>;
|
|
57
57
|
selectedRow?: (row: Row<any>) => boolean;
|
|
58
|
+
stickyHeader?: boolean;
|
|
58
59
|
totalCountLabel?: string | null;
|
|
59
60
|
};
|
|
60
61
|
|
|
@@ -116,6 +117,7 @@ const DataTable: FC<DataTableProps> = ({
|
|
|
116
117
|
pagesCount,
|
|
117
118
|
meta,
|
|
118
119
|
selectedRow,
|
|
120
|
+
stickyHeader,
|
|
119
121
|
totalCountLabel,
|
|
120
122
|
}) => {
|
|
121
123
|
const id = useId();
|
|
@@ -189,68 +191,72 @@ const DataTable: FC<DataTableProps> = ({
|
|
|
189
191
|
return (
|
|
190
192
|
<>
|
|
191
193
|
<div className="data-table__scrollWrapper">
|
|
192
|
-
<table className=
|
|
194
|
+
<table className={clsx('data-table', stickyHeader && 'data-table--sticky-header')}>
|
|
193
195
|
{!disableHead && (
|
|
194
196
|
<thead>
|
|
195
197
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
196
198
|
<tr key={headerGroup.id}>
|
|
197
|
-
{headerGroup.headers.map((header) =>
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
199
|
+
{headerGroup.headers.map((header) => {
|
|
200
|
+
const stickyColumn = header.column.columnDef.meta?.sticky;
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<th
|
|
204
|
+
key={header.id}
|
|
205
|
+
style={{
|
|
206
|
+
width: header.column.columnDef.meta?.size,
|
|
207
|
+
position:
|
|
208
|
+
stickyHeader || stickyColumn ? 'sticky' : undefined,
|
|
209
|
+
top: stickyHeader ? 0 : undefined,
|
|
210
|
+
left:
|
|
211
|
+
stickyColumn === 'left'
|
|
212
|
+
? `${header.column.getAfter('left') * 0.675}px`
|
|
213
|
+
: undefined,
|
|
214
|
+
right:
|
|
215
|
+
stickyColumn === 'right'
|
|
216
|
+
? `${header.column.getAfter('right') * 0.675}px`
|
|
217
|
+
: undefined,
|
|
218
|
+
zIndex: stickyHeader ? (stickyColumn ? 3 : 1) : stickyColumn ? 1 : 0,
|
|
219
|
+
}}
|
|
220
|
+
>
|
|
221
|
+
{header.isPlaceholder ? null : (
|
|
222
|
+
<Track gap={8}>
|
|
223
|
+
{sortable && header.column.getCanSort() && (
|
|
224
|
+
<button
|
|
225
|
+
onClick={header.column.getToggleSortingHandler()}
|
|
226
|
+
>
|
|
227
|
+
{{
|
|
228
|
+
asc: (
|
|
229
|
+
<Icon
|
|
230
|
+
icon={<MdExpandMore fontSize={20} />}
|
|
231
|
+
size="medium"
|
|
232
|
+
/>
|
|
233
|
+
),
|
|
234
|
+
desc: (
|
|
235
|
+
<Icon
|
|
236
|
+
icon={<MdExpandLess fontSize={20} />}
|
|
237
|
+
size="medium"
|
|
238
|
+
/>
|
|
239
|
+
),
|
|
240
|
+
}[header.column.getIsSorted() as string] ?? (
|
|
230
241
|
<Icon
|
|
231
|
-
icon={<
|
|
242
|
+
icon={<MdUnfoldMore fontSize={22} />}
|
|
232
243
|
size="medium"
|
|
233
244
|
/>
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
)}
|
|
250
|
-
</Track>
|
|
251
|
-
)}
|
|
252
|
-
</th>
|
|
253
|
-
))}
|
|
245
|
+
)}
|
|
246
|
+
</button>
|
|
247
|
+
)}
|
|
248
|
+
{flexRender(
|
|
249
|
+
header.column.columnDef.header,
|
|
250
|
+
header.getContext()
|
|
251
|
+
)}
|
|
252
|
+
{filterable && header.column.getCanFilter() && (
|
|
253
|
+
<Filter column={header.column} table={table} />
|
|
254
|
+
)}
|
|
255
|
+
</Track>
|
|
256
|
+
)}
|
|
257
|
+
</th>
|
|
258
|
+
);
|
|
259
|
+
})}
|
|
254
260
|
</tr>
|
|
255
261
|
))}
|
|
256
262
|
</thead>
|
|
@@ -49,6 +49,10 @@
|
|
|
49
49
|
display: block;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
+ #{$self}__menu--combobox {
|
|
53
|
+
display: flex;
|
|
54
|
+
}
|
|
55
|
+
|
|
52
56
|
+#{$self}__menu_up {
|
|
53
57
|
display: block;
|
|
54
58
|
}
|
|
@@ -124,7 +128,8 @@
|
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
#{$self}__search,
|
|
127
|
-
#{$self}__options
|
|
131
|
+
#{$self}__options,
|
|
132
|
+
#{$self}__actions {
|
|
128
133
|
background-color: get-color(white);
|
|
129
134
|
}
|
|
130
135
|
}
|
|
@@ -152,15 +157,24 @@
|
|
|
152
157
|
}
|
|
153
158
|
|
|
154
159
|
&__menu--combobox {
|
|
160
|
+
flex-direction: column;
|
|
155
161
|
top: auto;
|
|
156
162
|
bottom: 100%;
|
|
163
|
+
max-height: none;
|
|
157
164
|
overflow: hidden;
|
|
158
165
|
margin-top: 0;
|
|
159
166
|
margin-bottom: 3px;
|
|
160
167
|
}
|
|
161
168
|
|
|
169
|
+
&__menu--down {
|
|
170
|
+
top: 100%;
|
|
171
|
+
bottom: auto;
|
|
172
|
+
margin-top: 3px;
|
|
173
|
+
margin-bottom: 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
162
176
|
&__menu--portal {
|
|
163
|
-
display:
|
|
177
|
+
display: flex;
|
|
164
178
|
position: fixed;
|
|
165
179
|
top: 100%;
|
|
166
180
|
bottom: auto;
|
|
@@ -171,13 +185,28 @@
|
|
|
171
185
|
}
|
|
172
186
|
|
|
173
187
|
&__options {
|
|
174
|
-
|
|
188
|
+
flex: 1 1 auto;
|
|
189
|
+
max-height: 220px;
|
|
190
|
+
min-height: 0;
|
|
175
191
|
overflow: auto;
|
|
176
192
|
list-style: none;
|
|
177
193
|
margin: 0;
|
|
178
194
|
padding: 0;
|
|
179
195
|
}
|
|
180
196
|
|
|
197
|
+
&__actions {
|
|
198
|
+
display: flex;
|
|
199
|
+
flex: 0 0 auto;
|
|
200
|
+
justify-content: center;
|
|
201
|
+
padding: 12px 16px 16px;
|
|
202
|
+
|
|
203
|
+
> .btn.btn--s {
|
|
204
|
+
justify-content: center;
|
|
205
|
+
min-width: 106px;
|
|
206
|
+
padding: 4px 16px;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
181
210
|
&__search {
|
|
182
211
|
padding: get-spacing(paldiski);
|
|
183
212
|
position: relative;
|
|
@@ -238,7 +267,9 @@
|
|
|
238
267
|
}
|
|
239
268
|
|
|
240
269
|
&__option--combobox {
|
|
270
|
+
box-sizing: border-box;
|
|
241
271
|
cursor: pointer;
|
|
272
|
+
min-height: 40px;
|
|
242
273
|
|
|
243
274
|
&[aria-selected=true] {
|
|
244
275
|
background-color: get-color(white);
|
|
@@ -15,7 +15,7 @@ import clsx from 'clsx';
|
|
|
15
15
|
import { useTranslation } from 'react-i18next';
|
|
16
16
|
import { MdArrowDropDown, MdExpandMore, MdSearch } from 'react-icons/md';
|
|
17
17
|
|
|
18
|
-
import { Icon } from '../..';
|
|
18
|
+
import { Button, Icon } from '../..';
|
|
19
19
|
import './FormCombobox.scss';
|
|
20
20
|
|
|
21
21
|
type FormComboboxOption = {
|
|
@@ -35,6 +35,9 @@ type FormComboboxBaseProps = {
|
|
|
35
35
|
readonly isSearchEnabled?: boolean;
|
|
36
36
|
readonly hideInputStyle?: boolean;
|
|
37
37
|
readonly isMenuPortaled?: boolean;
|
|
38
|
+
readonly allOptionValue?: string;
|
|
39
|
+
readonly direction?: 'down' | 'up';
|
|
40
|
+
readonly selectedOptionsCount?: number;
|
|
38
41
|
};
|
|
39
42
|
|
|
40
43
|
type FormComboboxSingleProps = FormComboboxBaseProps & {
|
|
@@ -51,6 +54,7 @@ type FormComboboxMultipleProps = FormComboboxBaseProps & {
|
|
|
51
54
|
readonly defaultValue?: string[];
|
|
52
55
|
readonly onChange?: (value: string[]) => void;
|
|
53
56
|
readonly onSelectionChange?: (selection: FormComboboxOption[] | null) => void;
|
|
57
|
+
readonly isApplyBtnVisible?: boolean;
|
|
54
58
|
};
|
|
55
59
|
|
|
56
60
|
type FormComboboxProps = FormComboboxSingleProps | FormComboboxMultipleProps;
|
|
@@ -125,6 +129,39 @@ const orderSelectedOptionsFirst = (
|
|
|
125
129
|
return [...selectedOptions, ...unselectedOptions];
|
|
126
130
|
};
|
|
127
131
|
|
|
132
|
+
const getNextMultipleValues = (
|
|
133
|
+
option: FormComboboxOption,
|
|
134
|
+
options: FormComboboxOption[],
|
|
135
|
+
selectedValues: string[],
|
|
136
|
+
allOptionValue?: string
|
|
137
|
+
): string[] => {
|
|
138
|
+
if (!allOptionValue) {
|
|
139
|
+
return selectedValues.includes(option.value)
|
|
140
|
+
? selectedValues.filter((value) => value !== option.value)
|
|
141
|
+
: [...selectedValues, option.value];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const optionValues = options.map((item) => item.value);
|
|
145
|
+
const realOptionValues = optionValues.filter((value) => value !== allOptionValue);
|
|
146
|
+
|
|
147
|
+
if (option.value === allOptionValue) {
|
|
148
|
+
return selectedValues.includes(allOptionValue) ? [] : [allOptionValue, ...realOptionValues];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const nextRealValues = selectedValues.includes(option.value)
|
|
152
|
+
? selectedValues.filter((value) => value !== option.value && value !== allOptionValue)
|
|
153
|
+
: [...selectedValues.filter((value) => value !== allOptionValue), option.value];
|
|
154
|
+
|
|
155
|
+
if (
|
|
156
|
+
realOptionValues.length > 0 &&
|
|
157
|
+
realOptionValues.every((value) => nextRealValues.includes(value))
|
|
158
|
+
) {
|
|
159
|
+
return [allOptionValue, ...realOptionValues];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return nextRealValues;
|
|
163
|
+
};
|
|
164
|
+
|
|
128
165
|
export const FormCombobox: FC<FormComboboxProps> = ({
|
|
129
166
|
label,
|
|
130
167
|
hideLabel,
|
|
@@ -136,6 +173,8 @@ export const FormCombobox: FC<FormComboboxProps> = ({
|
|
|
136
173
|
isSearchEnabled = false,
|
|
137
174
|
hideInputStyle = false,
|
|
138
175
|
isMenuPortaled = false,
|
|
176
|
+
allOptionValue,
|
|
177
|
+
direction = 'up',
|
|
139
178
|
...props
|
|
140
179
|
}) => {
|
|
141
180
|
const id = useId();
|
|
@@ -155,11 +194,16 @@ export const FormCombobox: FC<FormComboboxProps> = ({
|
|
|
155
194
|
const searchInputRef = useRef<HTMLInputElement | null>(null);
|
|
156
195
|
|
|
157
196
|
const isMultiple = props.multiple === true;
|
|
158
|
-
const
|
|
197
|
+
const isApplyBtnVisible = props.multiple === true ? props.isApplyBtnVisible : false;
|
|
198
|
+
const selectedValues = useMemo(() => (
|
|
199
|
+
getSelectedValues(props, internalSingleValue, internalMultipleValue)
|
|
200
|
+
), [props.multiple, props.value, internalSingleValue, internalMultipleValue]);
|
|
201
|
+
const [draftMultipleValue, setDraftMultipleValue] = useState<string[]>(selectedValues);
|
|
202
|
+
const menuSelectedValues = isApplyBtnVisible ? draftMultipleValue : selectedValues;
|
|
159
203
|
|
|
160
204
|
const orderedOptions = useMemo(() => (
|
|
161
|
-
orderSelectedOptionsFirst(options,
|
|
162
|
-
), [options,
|
|
205
|
+
orderSelectedOptionsFirst(options, menuSelectedValues)
|
|
206
|
+
), [options, menuSelectedValues]);
|
|
163
207
|
|
|
164
208
|
const filteredOptions = useMemo(() => {
|
|
165
209
|
const normalizedQuery = query.trim().toLowerCase();
|
|
@@ -179,7 +223,7 @@ export const FormCombobox: FC<FormComboboxProps> = ({
|
|
|
179
223
|
|
|
180
224
|
if (!triggerRect) return;
|
|
181
225
|
|
|
182
|
-
const offset =
|
|
226
|
+
const offset = 10;
|
|
183
227
|
|
|
184
228
|
setMenuStyle({
|
|
185
229
|
left: triggerRect.left,
|
|
@@ -208,6 +252,12 @@ export const FormCombobox: FC<FormComboboxProps> = ({
|
|
|
208
252
|
}
|
|
209
253
|
}, [isOpen]);
|
|
210
254
|
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
if (isOpen && isApplyBtnVisible) {
|
|
257
|
+
setDraftMultipleValue(selectedValues);
|
|
258
|
+
}
|
|
259
|
+
}, [isOpen, isApplyBtnVisible]);
|
|
260
|
+
|
|
211
261
|
useEffect(() => {
|
|
212
262
|
const handleDocumentClick = (event: MouseEvent) => {
|
|
213
263
|
const target = event.target as Node;
|
|
@@ -260,11 +310,15 @@ export const FormCombobox: FC<FormComboboxProps> = ({
|
|
|
260
310
|
if (!props.multiple) return;
|
|
261
311
|
const multipleProps = props as FormComboboxMultipleProps;
|
|
262
312
|
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
: [...selectedValues, option.value];
|
|
313
|
+
const currentValues = isApplyBtnVisible ? draftMultipleValue : selectedValues;
|
|
314
|
+
const nextValues = getNextMultipleValues(option, options, currentValues, allOptionValue);
|
|
266
315
|
const nextSelection = options.filter((item) => nextValues.includes(item.value));
|
|
267
316
|
|
|
317
|
+
if (isApplyBtnVisible) {
|
|
318
|
+
setDraftMultipleValue(nextValues);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
268
322
|
if (multipleProps.value === undefined) {
|
|
269
323
|
setInternalMultipleValue(nextValues);
|
|
270
324
|
}
|
|
@@ -284,10 +338,29 @@ export const FormCombobox: FC<FormComboboxProps> = ({
|
|
|
284
338
|
setSingleValue(option);
|
|
285
339
|
};
|
|
286
340
|
|
|
341
|
+
const applyMultipleValue = () => {
|
|
342
|
+
if (!props.multiple) return;
|
|
343
|
+
|
|
344
|
+
const multipleProps = props as FormComboboxMultipleProps;
|
|
345
|
+
|
|
346
|
+
if (multipleProps.value === undefined) {
|
|
347
|
+
setInternalMultipleValue(draftMultipleValue);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
multipleProps.onChange?.(draftMultipleValue);
|
|
351
|
+
const nextSelection = options.filter((item) => draftMultipleValue.includes(item.value));
|
|
352
|
+
|
|
353
|
+
multipleProps.onSelectionChange?.(nextSelection.length ? nextSelection : null);
|
|
354
|
+
setIsOpen(false);
|
|
355
|
+
setQuery('');
|
|
356
|
+
};
|
|
357
|
+
|
|
287
358
|
const placeholderValue = placeholder || t('global.choose');
|
|
288
359
|
const triggerLabel = isMultiple
|
|
289
360
|
? selectedOptions.length > 0
|
|
290
|
-
?
|
|
361
|
+
? props.selectedOptionsCount !== undefined
|
|
362
|
+
? `${placeholder ?? t('global.chosen')} (${props.selectedOptionsCount})`
|
|
363
|
+
: selectedOptions.map((option) => option.label).join(', ')
|
|
291
364
|
: placeholderValue
|
|
292
365
|
: selectedOptions[0]?.label ?? placeholderValue;
|
|
293
366
|
const triggerContent = hideInputStyle ? label ?? triggerLabel : triggerLabel;
|
|
@@ -304,6 +377,7 @@ export const FormCombobox: FC<FormComboboxProps> = ({
|
|
|
304
377
|
ref={menuRef}
|
|
305
378
|
className={clsx(
|
|
306
379
|
'select__menu select__menu--combobox',
|
|
380
|
+
`select__menu--${direction}`,
|
|
307
381
|
isMenuPortaled && 'select__menu--portal'
|
|
308
382
|
)}
|
|
309
383
|
style={isMenuPortaled ? menuStyle : undefined}
|
|
@@ -330,7 +404,7 @@ export const FormCombobox: FC<FormComboboxProps> = ({
|
|
|
330
404
|
<ul className='select__options' role='listbox' aria-label={searchPlaceholder ?? t('global.search')}>
|
|
331
405
|
{filteredOptions.length > 0 ? (
|
|
332
406
|
filteredOptions.map((option) => {
|
|
333
|
-
const isSelected =
|
|
407
|
+
const isSelected = menuSelectedValues.includes(option.value);
|
|
334
408
|
|
|
335
409
|
return (
|
|
336
410
|
<li
|
|
@@ -354,6 +428,19 @@ export const FormCombobox: FC<FormComboboxProps> = ({
|
|
|
354
428
|
})
|
|
355
429
|
) : null}
|
|
356
430
|
</ul>
|
|
431
|
+
|
|
432
|
+
{isApplyBtnVisible && (
|
|
433
|
+
<div className='select__actions'>
|
|
434
|
+
<Button
|
|
435
|
+
size='s'
|
|
436
|
+
type='button'
|
|
437
|
+
onClick={applyMultipleValue}
|
|
438
|
+
disabled={disabled}
|
|
439
|
+
>
|
|
440
|
+
{t('global.apply')}
|
|
441
|
+
</Button>
|
|
442
|
+
</div>
|
|
443
|
+
)}
|
|
357
444
|
</div>
|
|
358
445
|
);
|
|
359
446
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import React, { FC, ReactNode, SelectHTMLAttributes, useId, useState } from 'react';
|
|
1
|
+
import React, { FC, ReactNode, SelectHTMLAttributes, useEffect, useId, useState } from 'react';
|
|
2
2
|
import { useSelect } from 'downshift';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { MdArrowDropDown } from 'react-icons/md';
|
|
6
6
|
|
|
7
|
-
import { Icon } from '../..';
|
|
7
|
+
import { Button, Icon } from '../..';
|
|
8
8
|
import './FormSelect.scss';
|
|
9
9
|
|
|
10
10
|
type SelectOption = { label: string, value: string };
|
|
@@ -18,6 +18,58 @@ type FormMultiselectProps = SelectHTMLAttributes<HTMLSelectElement> & {
|
|
|
18
18
|
selectedOptions?: SelectOption[];
|
|
19
19
|
selectedOptionsCount?: number;
|
|
20
20
|
onSelectionChange?: (selection: SelectOption[] | null) => void;
|
|
21
|
+
isApplyBtnVisible?: boolean;
|
|
22
|
+
allOptionValue?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const getNextSelectedItems = (
|
|
26
|
+
selectedItem: SelectOption,
|
|
27
|
+
options: SelectOption[],
|
|
28
|
+
selectedItems: SelectOption[],
|
|
29
|
+
allOptionValue?: string
|
|
30
|
+
): SelectOption[] => {
|
|
31
|
+
if (!allOptionValue) {
|
|
32
|
+
const index = selectedItems.findIndex((item) => item.value === selectedItem.value);
|
|
33
|
+
const items: SelectOption[] = [];
|
|
34
|
+
|
|
35
|
+
if (index > 0) {
|
|
36
|
+
items.push(
|
|
37
|
+
...selectedItems.slice(0, index),
|
|
38
|
+
...selectedItems.slice(index + 1)
|
|
39
|
+
);
|
|
40
|
+
} else if (index === 0) {
|
|
41
|
+
items.push(...selectedItems.slice(1));
|
|
42
|
+
} else {
|
|
43
|
+
items.push(...selectedItems, selectedItem);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return items;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const realOptions = options.filter((option) => option.value !== allOptionValue);
|
|
50
|
+
const allOption = options.find((option) => option.value === allOptionValue);
|
|
51
|
+
|
|
52
|
+
if (selectedItem.value === allOptionValue) {
|
|
53
|
+
return selectedItems.some((item) => item.value === allOptionValue)
|
|
54
|
+
? []
|
|
55
|
+
: [...(allOption ? [allOption] : []), ...realOptions];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const selectedWithoutAll = selectedItems.filter((item) => item.value !== allOptionValue);
|
|
59
|
+
const isSelected = selectedWithoutAll.some((item) => item.value === selectedItem.value);
|
|
60
|
+
const nextRealItems = isSelected
|
|
61
|
+
? selectedWithoutAll.filter((item) => item.value !== selectedItem.value)
|
|
62
|
+
: [...selectedWithoutAll, selectedItem];
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
allOption &&
|
|
66
|
+
realOptions.length > 0 &&
|
|
67
|
+
realOptions.every((option) => nextRealItems.some((item) => item.value === option.value))
|
|
68
|
+
) {
|
|
69
|
+
return [allOption, ...realOptions];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return nextRealItems;
|
|
21
73
|
};
|
|
22
74
|
|
|
23
75
|
const FormMultiselect: FC<FormMultiselectProps> = (
|
|
@@ -31,6 +83,8 @@ const FormMultiselect: FC<FormMultiselectProps> = (
|
|
|
31
83
|
selectedOptions,
|
|
32
84
|
selectedOptionsCount,
|
|
33
85
|
onSelectionChange,
|
|
86
|
+
isApplyBtnVisible = false,
|
|
87
|
+
allOptionValue,
|
|
34
88
|
...rest
|
|
35
89
|
},
|
|
36
90
|
) => {
|
|
@@ -63,30 +117,35 @@ const FormMultiselect: FC<FormMultiselectProps> = (
|
|
|
63
117
|
if (!selectedItem) {
|
|
64
118
|
return;
|
|
65
119
|
}
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
if (index > 0) {
|
|
69
|
-
items.push(
|
|
70
|
-
...selectedItems.slice(0, index),
|
|
71
|
-
...selectedItems.slice(index + 1)
|
|
72
|
-
);
|
|
73
|
-
} else if (index === 0) {
|
|
74
|
-
items.push(...selectedItems.slice(1));
|
|
75
|
-
} else {
|
|
76
|
-
items.push(...selectedItems, selectedItem);
|
|
77
|
-
}
|
|
120
|
+
const items = getNextSelectedItems(selectedItem, options, selectedItems, allOptionValue);
|
|
121
|
+
|
|
78
122
|
setSelectedItems(items);
|
|
79
|
-
if (
|
|
123
|
+
if (!isApplyBtnVisible) {
|
|
124
|
+
onSelectionChange?.(items.length ? items : null);
|
|
125
|
+
}
|
|
80
126
|
},
|
|
81
127
|
});
|
|
82
128
|
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
setSelectedItems(selectedOptions ?? []);
|
|
131
|
+
}, [selectedOptions]);
|
|
132
|
+
|
|
133
|
+
const applySelection = () => {
|
|
134
|
+
onSelectionChange?.(selectedItems.length ? selectedItems : null);
|
|
135
|
+
};
|
|
136
|
+
|
|
83
137
|
const selectClasses = clsx(
|
|
84
138
|
'select',
|
|
85
139
|
disabled && 'select--disabled',
|
|
86
140
|
);
|
|
87
141
|
|
|
88
142
|
const placeholderValue = placeholder || t('global.choose');
|
|
89
|
-
const
|
|
143
|
+
const selectedItemsCount = allOptionValue
|
|
144
|
+
? selectedItems.filter((item) => item.value !== allOptionValue).length
|
|
145
|
+
: selectedItems.length;
|
|
146
|
+
const displaySelectedCount = isApplyBtnVisible
|
|
147
|
+
? selectedItemsCount
|
|
148
|
+
: selectedOptionsCount ?? selectedItems.length;
|
|
90
149
|
|
|
91
150
|
return (
|
|
92
151
|
<div className={selectClasses} style={rest.style}>
|
|
@@ -97,27 +156,40 @@ const FormMultiselect: FC<FormMultiselectProps> = (
|
|
|
97
156
|
<Icon label='Dropdown icon' size='medium' icon={<MdArrowDropDown color='#5D6071' />} />
|
|
98
157
|
</div>
|
|
99
158
|
|
|
100
|
-
<
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
159
|
+
<div className='select__menu select__menu--multiselect'>
|
|
160
|
+
<ul className='select__options' {...getMenuProps()}>
|
|
161
|
+
{isOpen &&
|
|
162
|
+
options.map((item, index) => (
|
|
163
|
+
<li
|
|
164
|
+
key={`${item.label}-${index}`}
|
|
165
|
+
className={clsx('select__option', { 'select__option--selected': highlightedIndex === index })}
|
|
166
|
+
{...getItemProps({
|
|
167
|
+
item,
|
|
168
|
+
index,
|
|
169
|
+
})}
|
|
170
|
+
>
|
|
171
|
+
<input
|
|
172
|
+
type='checkbox'
|
|
173
|
+
checked={selectedItems.map((s) => s.value).includes(item.value)}
|
|
174
|
+
value={item.value}
|
|
175
|
+
onChange={() => null}
|
|
176
|
+
/>
|
|
177
|
+
<span>{item.label}</span>
|
|
178
|
+
</li>
|
|
179
|
+
))}
|
|
180
|
+
</ul>
|
|
181
|
+
{isOpen && isApplyBtnVisible && (
|
|
182
|
+
<div className='select__actions'>
|
|
183
|
+
<Button
|
|
184
|
+
size='s'
|
|
185
|
+
type='button'
|
|
186
|
+
onClick={applySelection}
|
|
110
187
|
>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
/>
|
|
117
|
-
<span>{item.label}</span>
|
|
118
|
-
</li>
|
|
119
|
-
))}
|
|
120
|
-
</ul>
|
|
188
|
+
{t('global.apply')}
|
|
189
|
+
</Button>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
121
193
|
</div>
|
|
122
194
|
</div>
|
|
123
195
|
);
|
|
@@ -48,6 +48,10 @@
|
|
|
48
48
|
display: block;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
+ #{$self}__menu--multiselect {
|
|
52
|
+
display: flex;
|
|
53
|
+
}
|
|
54
|
+
|
|
51
55
|
+#{$self}__menu_up {
|
|
52
56
|
display: block;
|
|
53
57
|
}
|
|
@@ -74,6 +78,21 @@
|
|
|
74
78
|
margin-top: 3px;
|
|
75
79
|
}
|
|
76
80
|
|
|
81
|
+
&__menu--multiselect {
|
|
82
|
+
flex-direction: column;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
&__options {
|
|
87
|
+
flex: 1 1 auto;
|
|
88
|
+
max-height: 224px;
|
|
89
|
+
min-height: 0;
|
|
90
|
+
overflow: auto;
|
|
91
|
+
list-style: none;
|
|
92
|
+
margin: 0;
|
|
93
|
+
padding: 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
77
96
|
&__menu_up {
|
|
78
97
|
display: none;
|
|
79
98
|
position: absolute;
|
|
@@ -118,4 +137,17 @@
|
|
|
118
137
|
color: get-color(black-coral-20);
|
|
119
138
|
}
|
|
120
139
|
}
|
|
140
|
+
|
|
141
|
+
&__actions {
|
|
142
|
+
display: flex;
|
|
143
|
+
flex: 0 0 auto;
|
|
144
|
+
justify-content: center;
|
|
145
|
+
padding: 12px 16px 16px;
|
|
146
|
+
|
|
147
|
+
> .btn.btn--s {
|
|
148
|
+
justify-content: center;
|
|
149
|
+
min-width: 106px;
|
|
150
|
+
padding: 4px 16px;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
121
153
|
}
|