@servicetitan/table 25.1.0 → 25.2.0
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/demo/filters/async-select-filter.js +1 -1
- package/dist/demo/filters/async-select-filter.js.map +1 -1
- package/dist/demo/filters/categories.d.ts.map +1 -1
- package/dist/demo/filters/categories.js +24 -0
- package/dist/demo/filters/categories.js.map +1 -1
- package/dist/demo/filters/multiselect-filter.d.ts.map +1 -1
- package/dist/demo/filters/multiselect-filter.js +2 -1
- package/dist/demo/filters/multiselect-filter.js.map +1 -1
- package/dist/demo/filters/select-filter.d.ts +6 -0
- package/dist/demo/filters/select-filter.d.ts.map +1 -0
- package/dist/demo/filters/select-filter.js +58 -0
- package/dist/demo/filters/select-filter.js.map +1 -0
- package/dist/demo/filters/single-select-filter.d.ts.map +1 -1
- package/dist/demo/filters/single-select-filter.js +2 -1
- package/dist/demo/filters/single-select-filter.js.map +1 -1
- package/dist/demo/filters/table.store.d.ts +8 -1
- package/dist/demo/filters/table.store.d.ts.map +1 -1
- package/dist/demo/filters/table.store.js +35 -1
- package/dist/demo/filters/table.store.js.map +1 -1
- package/dist/demo/overview/product.d.ts +5 -0
- package/dist/demo/overview/product.d.ts.map +1 -1
- package/dist/demo/overview/products.d.ts.map +1 -1
- package/dist/demo/overview/products.js +12 -0
- package/dist/demo/overview/products.js.map +1 -1
- package/dist/demo/overview/table.store.d.ts.map +1 -1
- package/dist/demo/overview/table.store.js +1 -0
- package/dist/demo/overview/table.store.js.map +1 -1
- package/dist/filters/async-select/async-select-filter.d.ts +7 -3
- package/dist/filters/async-select/async-select-filter.d.ts.map +1 -1
- package/dist/filters/async-select/async-select-filter.js +21 -6
- package/dist/filters/async-select/async-select-filter.js.map +1 -1
- package/dist/filters/column-menu-filters.d.ts +10 -1
- package/dist/filters/column-menu-filters.d.ts.map +1 -1
- package/dist/filters/column-menu-filters.js +11 -3
- package/dist/filters/column-menu-filters.js.map +1 -1
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/index.d.ts.map +1 -1
- package/dist/filters/index.js +1 -0
- package/dist/filters/index.js.map +1 -1
- package/dist/filters/multiselect-filter/multiselect-filter.d.ts +4 -3
- package/dist/filters/multiselect-filter/multiselect-filter.d.ts.map +1 -1
- package/dist/filters/multiselect-filter/multiselect-filter.js +7 -7
- package/dist/filters/multiselect-filter/multiselect-filter.js.map +1 -1
- package/dist/filters/select-filter/object-search.d.ts +2 -0
- package/dist/filters/select-filter/object-search.d.ts.map +1 -0
- package/dist/filters/select-filter/object-search.js +19 -0
- package/dist/filters/select-filter/object-search.js.map +1 -0
- package/dist/filters/select-filter/select-filter.d.ts +32 -0
- package/dist/filters/select-filter/select-filter.d.ts.map +1 -0
- package/dist/filters/select-filter/select-filter.js +214 -0
- package/dist/filters/select-filter/select-filter.js.map +1 -0
- package/dist/filters/select-filter/select-filter.stories.d.ts +8 -0
- package/dist/filters/select-filter/select-filter.stories.d.ts.map +1 -0
- package/dist/filters/select-filter/select-filter.stories.js +8 -0
- package/dist/filters/select-filter/select-filter.stories.js.map +1 -0
- package/dist/filters/select-filter/value-getter.d.ts +3 -0
- package/dist/filters/select-filter/value-getter.d.ts.map +1 -0
- package/dist/filters/select-filter/value-getter.js +10 -0
- package/dist/filters/select-filter/value-getter.js.map +1 -0
- package/dist/filters/single-select/single-select-filter.d.ts +3 -2
- package/dist/filters/single-select/single-select-filter.d.ts.map +1 -1
- package/dist/filters/single-select/single-select-filter.js +17 -7
- package/dist/filters/single-select/single-select-filter.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/demo/filters/async-select-filter.tsx +1 -1
- package/src/demo/filters/categories.tsx +24 -0
- package/src/demo/filters/multiselect-filter.tsx +15 -0
- package/src/demo/filters/select-filter.tsx +147 -0
- package/src/demo/filters/single-select-filter.tsx +2 -1
- package/src/demo/filters/table.store.ts +45 -2
- package/src/demo/overview/product.ts +6 -0
- package/src/demo/overview/products.ts +12 -0
- package/src/demo/overview/table.store.ts +1 -0
- package/src/filters/async-select/async-select-filter.tsx +26 -15
- package/src/filters/column-menu-filters.tsx +44 -21
- package/src/filters/index.ts +1 -0
- package/src/filters/multiselect-filter/multiselect-filter.tsx +16 -7
- package/src/filters/select-filter/__tests__/object-search.test.ts +32 -0
- package/src/filters/select-filter/object-search.ts +25 -0
- package/src/filters/select-filter/select-filter.stories.tsx +8 -0
- package/src/filters/select-filter/select-filter.tsx +320 -0
- package/src/filters/select-filter/value-getter.ts +13 -0
- package/src/filters/single-select/single-select-filter.tsx +18 -10
- package/src/index.ts +1 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const objectSearch = (search: string) => {
|
|
2
|
+
const s = search.toLowerCase().trim();
|
|
3
|
+
|
|
4
|
+
return (item: any): boolean => {
|
|
5
|
+
if (!s) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (item === null) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (typeof item === 'string') {
|
|
14
|
+
return item.toLowerCase().includes(s);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (typeof item === 'object') {
|
|
18
|
+
return Object.values(item).some(value =>
|
|
19
|
+
typeof value === 'string' ? value.toLowerCase().includes(s) : false
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return false;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SelectAsyncExample, SelectSyncExample } from '../../demo/filters/select-filter';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Table/Filters/Select',
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const AsyncSelect = () => <SelectAsyncExample />;
|
|
8
|
+
export const SyncSelect = () => <SelectSyncExample />;
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { Component, SyntheticEvent, ReactNode, Fragment, KeyboardEvent, FC } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Checkbox,
|
|
5
|
+
TableFilterCellProps,
|
|
6
|
+
Radio,
|
|
7
|
+
Input,
|
|
8
|
+
Spinner,
|
|
9
|
+
BodyText,
|
|
10
|
+
} from '@servicetitan/design-system';
|
|
11
|
+
import { IdType } from '@servicetitan/data-query';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
CustomColumnMenuFilterSingleOpts,
|
|
15
|
+
renderCustomColumnMenuFilter,
|
|
16
|
+
} from '../column-menu-filters';
|
|
17
|
+
import { makeObservable, observable, runInAction, toJS } from 'mobx';
|
|
18
|
+
import { observer } from 'mobx-react';
|
|
19
|
+
import { getSimpleValue } from './value-getter';
|
|
20
|
+
import { objectSearch } from './object-search';
|
|
21
|
+
|
|
22
|
+
export type SelectFilterDataFetcher<TO> = (opts: { search?: string }) => Promise<{ data: TO[] }>;
|
|
23
|
+
|
|
24
|
+
export interface SelectFilterSearchOptions<TO = any> {
|
|
25
|
+
placeholder?: string;
|
|
26
|
+
filter(search: string): (item: TO) => boolean | undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface SelectorProps<TO> {
|
|
30
|
+
search?: SelectFilterSearchOptions<TO>;
|
|
31
|
+
selected: TO[];
|
|
32
|
+
data?: TO[];
|
|
33
|
+
dataFetcher?: SelectFilterDataFetcher<TO>;
|
|
34
|
+
itemComponent: FC<SelectorItemProps<TO>>;
|
|
35
|
+
onChange(option: TO, checked: boolean, event: SyntheticEvent<HTMLInputElement>): void;
|
|
36
|
+
valueSelector(item: TO): IdType;
|
|
37
|
+
renderItem(item: TO): ReactNode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface SelectorItemProps<TO> {
|
|
41
|
+
option: TO;
|
|
42
|
+
checked: boolean;
|
|
43
|
+
onChange(option: TO, checked: boolean, event: SyntheticEvent<HTMLInputElement>): void;
|
|
44
|
+
renderer?(item: TO): ReactNode;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@observer
|
|
48
|
+
class SelectorAsync<TO> extends Component<SelectorProps<TO>> {
|
|
49
|
+
@observable shownOptions: TO[] = [];
|
|
50
|
+
@observable search = '';
|
|
51
|
+
@observable error = false;
|
|
52
|
+
@observable loading = false;
|
|
53
|
+
|
|
54
|
+
constructor(props: SelectorProps<TO>) {
|
|
55
|
+
super(props);
|
|
56
|
+
makeObservable(this);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
componentDidMount() {
|
|
60
|
+
this.searchOptions().catch();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
64
|
+
if (event.key === 'Enter') {
|
|
65
|
+
event.stopPropagation();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
handleSearch = (_0: SyntheticEvent<HTMLInputElement>, data: { value: string }) => {
|
|
70
|
+
runInAction(() => (this.search = data.value));
|
|
71
|
+
this.searchOptions().catch();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
searchOptions = async () => {
|
|
75
|
+
if (this.props.dataFetcher) {
|
|
76
|
+
runInAction(() => {
|
|
77
|
+
this.loading = true;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const data = await this.getData();
|
|
83
|
+
|
|
84
|
+
runInAction(() => {
|
|
85
|
+
this.shownOptions = data;
|
|
86
|
+
this.error = false;
|
|
87
|
+
this.loading = false;
|
|
88
|
+
});
|
|
89
|
+
} catch {
|
|
90
|
+
runInAction(() => {
|
|
91
|
+
this.error = true;
|
|
92
|
+
this.loading = false;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
render() {
|
|
98
|
+
const selectedOptions = this.props.selected;
|
|
99
|
+
const selected = new Set(selectedOptions.map(opt => this.props.valueSelector(opt)));
|
|
100
|
+
const ItemComponent = this.props.itemComponent;
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<Fragment>
|
|
104
|
+
{!!this.props.search && (
|
|
105
|
+
<Input
|
|
106
|
+
className="m-x-half m-t-half m-b-2"
|
|
107
|
+
placeholder={this.props.search?.placeholder}
|
|
108
|
+
onChange={this.handleSearch}
|
|
109
|
+
onKeyDown={this.handleKeyDown}
|
|
110
|
+
size="xsmall"
|
|
111
|
+
value={this.search}
|
|
112
|
+
/>
|
|
113
|
+
)}
|
|
114
|
+
<div className="p-x-half position-relative" onKeyDown={this.handleKeyDown}>
|
|
115
|
+
{!!selectedOptions.length && (
|
|
116
|
+
<div className="border-bottom m-y-half">
|
|
117
|
+
{selectedOptions.map((option, index) => (
|
|
118
|
+
<ItemComponent
|
|
119
|
+
key={this.getItemKey(option, index)}
|
|
120
|
+
option={option}
|
|
121
|
+
checked
|
|
122
|
+
renderer={this.props.renderItem}
|
|
123
|
+
onChange={this.props.onChange}
|
|
124
|
+
/>
|
|
125
|
+
))}
|
|
126
|
+
</div>
|
|
127
|
+
)}
|
|
128
|
+
{this.error ? (
|
|
129
|
+
<BodyText className="c-red-500">Unable to load options</BodyText>
|
|
130
|
+
) : this.shownOptions.length ? (
|
|
131
|
+
this.shownOptions
|
|
132
|
+
.filter(opt => !selected.has(this.props.valueSelector(opt)))
|
|
133
|
+
.map((option, index) => (
|
|
134
|
+
<ItemComponent
|
|
135
|
+
key={this.getItemKey(option, index)}
|
|
136
|
+
option={option}
|
|
137
|
+
checked={false}
|
|
138
|
+
renderer={this.props.renderItem}
|
|
139
|
+
onChange={this.props.onChange}
|
|
140
|
+
/>
|
|
141
|
+
))
|
|
142
|
+
) : this.loading ? (
|
|
143
|
+
<BodyText> </BodyText>
|
|
144
|
+
) : (
|
|
145
|
+
<BodyText subdued>No options match search criteria</BodyText>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{this.loading && (
|
|
149
|
+
<div className="position-absolute top-0 bottom-0 left-0 right-0 opacity-disabled bg-white d-f justify-content-center">
|
|
150
|
+
<Spinner size="tiny" />
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
</Fragment>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private async getData(): Promise<TO[]> {
|
|
159
|
+
if (this.props.dataFetcher) {
|
|
160
|
+
const { data } = await this.props.dataFetcher({
|
|
161
|
+
search: this.search,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return data;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let data = this.props.data ?? [];
|
|
168
|
+
|
|
169
|
+
if (this.props.search?.filter) {
|
|
170
|
+
data = data.filter(this.props.search.filter(this.search));
|
|
171
|
+
}
|
|
172
|
+
return data;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private getItemKey = (item: TO, index: number) => {
|
|
176
|
+
return `${index}__${this.props.valueSelector(item)}`;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const SelectorItemSingle: FC<SelectorItemProps<any>> = ({
|
|
181
|
+
option,
|
|
182
|
+
renderer,
|
|
183
|
+
checked,
|
|
184
|
+
onChange,
|
|
185
|
+
}) => (
|
|
186
|
+
<Radio
|
|
187
|
+
label={renderer?.(option) ?? `${option}`}
|
|
188
|
+
checked={checked}
|
|
189
|
+
onChange={(_, event) => onChange(option, true, event)}
|
|
190
|
+
className="m-b-1"
|
|
191
|
+
/>
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const SelectorItemMultiple: FC<SelectorItemProps<any>> = ({
|
|
195
|
+
option,
|
|
196
|
+
renderer,
|
|
197
|
+
checked,
|
|
198
|
+
onChange,
|
|
199
|
+
}) => (
|
|
200
|
+
<Checkbox
|
|
201
|
+
label={renderer?.(option) ?? `${option}`}
|
|
202
|
+
checked={checked}
|
|
203
|
+
onChange={(_, checked, event) => onChange(option, checked, event)}
|
|
204
|
+
className="m-b-1"
|
|
205
|
+
/>
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
export interface SelectFilterOptions<TO> extends CustomColumnMenuFilterSingleOpts {
|
|
209
|
+
/** Can select multiple options in filter */
|
|
210
|
+
multiple?: boolean;
|
|
211
|
+
/** Ability to search options in filter */
|
|
212
|
+
search?: boolean | Partial<SelectFilterSearchOptions>;
|
|
213
|
+
/** Static options to show in filter */
|
|
214
|
+
data?: TO[];
|
|
215
|
+
/** Method to fetch filter options asynchronously */
|
|
216
|
+
dataFetcher?: SelectFilterDataFetcher<TO>;
|
|
217
|
+
/** Search operator passed to table state */
|
|
218
|
+
operator?: ((value: any, options?: TO[]) => boolean) | ((value: any, options?: TO) => boolean);
|
|
219
|
+
/** Select item value (ex id) for complex items */
|
|
220
|
+
valueSelector?(item: TO): IdType;
|
|
221
|
+
/** Select row item value (from table source row field) for complex items */
|
|
222
|
+
rowValueSelector?(item: any): IdType | undefined;
|
|
223
|
+
/** Render option label */
|
|
224
|
+
renderItem?(item: TO): ReactNode;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
interface TableFilterCellPropsTyped<T = any> extends Omit<TableFilterCellProps, 'value'> {
|
|
228
|
+
value?: T;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function selectColumnMenuFilter<TO>({
|
|
232
|
+
dataFetcher,
|
|
233
|
+
data,
|
|
234
|
+
search,
|
|
235
|
+
multiple,
|
|
236
|
+
valueSelector = getSimpleValue,
|
|
237
|
+
rowValueSelector = getSimpleValue,
|
|
238
|
+
renderItem,
|
|
239
|
+
operator,
|
|
240
|
+
...opts
|
|
241
|
+
}: SelectFilterOptions<TO>) {
|
|
242
|
+
const renderer = renderItem ?? (item => valueSelector(item));
|
|
243
|
+
|
|
244
|
+
const contains = (value: any, options?: TO[]) =>
|
|
245
|
+
options?.some(opt => valueSelector(opt) === rowValueSelector(value));
|
|
246
|
+
|
|
247
|
+
const equals = (value: any, option?: TO) =>
|
|
248
|
+
option === undefined ? false : valueSelector(option) === rowValueSelector(value);
|
|
249
|
+
|
|
250
|
+
const searchOptions = search
|
|
251
|
+
? {
|
|
252
|
+
filter: objectSearch,
|
|
253
|
+
...(typeof search === 'boolean' ? {} : search),
|
|
254
|
+
}
|
|
255
|
+
: undefined;
|
|
256
|
+
|
|
257
|
+
if (multiple) {
|
|
258
|
+
const FilterCell = ({ value, onChange }: TableFilterCellPropsTyped<TO[]>) => {
|
|
259
|
+
const handleChange = (
|
|
260
|
+
option: TO,
|
|
261
|
+
checked: boolean,
|
|
262
|
+
event: SyntheticEvent<HTMLInputElement>
|
|
263
|
+
) => {
|
|
264
|
+
const val = checked
|
|
265
|
+
? (value ?? []).concat(option)
|
|
266
|
+
: (value ?? []).filter(opt =>
|
|
267
|
+
valueSelector
|
|
268
|
+
? valueSelector(opt) !== valueSelector(option)
|
|
269
|
+
: option !== opt
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
onChange({
|
|
273
|
+
value: val.length ? toJS(val) : undefined,
|
|
274
|
+
operator: val.length ? operator ?? contains : '',
|
|
275
|
+
syntheticEvent: event,
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<SelectorAsync
|
|
281
|
+
selected={value ?? []}
|
|
282
|
+
itemComponent={SelectorItemMultiple}
|
|
283
|
+
onChange={handleChange}
|
|
284
|
+
search={searchOptions}
|
|
285
|
+
renderItem={renderer}
|
|
286
|
+
dataFetcher={dataFetcher}
|
|
287
|
+
data={data}
|
|
288
|
+
valueSelector={valueSelector}
|
|
289
|
+
/>
|
|
290
|
+
);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
return renderCustomColumnMenuFilter(FilterCell, opts);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const FilterCell = ({ value, onChange }: TableFilterCellPropsTyped<TO>) => {
|
|
297
|
+
const handleChange = (option: TO, _: boolean, event: SyntheticEvent<HTMLInputElement>) => {
|
|
298
|
+
onChange({
|
|
299
|
+
value: toJS(option),
|
|
300
|
+
operator: operator ?? equals,
|
|
301
|
+
syntheticEvent: event,
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<SelectorAsync
|
|
307
|
+
selected={value ? [value] : []}
|
|
308
|
+
onChange={handleChange}
|
|
309
|
+
itemComponent={SelectorItemSingle}
|
|
310
|
+
search={searchOptions}
|
|
311
|
+
renderItem={renderer}
|
|
312
|
+
dataFetcher={dataFetcher}
|
|
313
|
+
data={data}
|
|
314
|
+
valueSelector={valueSelector}
|
|
315
|
+
/>
|
|
316
|
+
);
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return renderCustomColumnMenuFilter(FilterCell, opts);
|
|
320
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { IdType } from '@servicetitan/data-query';
|
|
2
|
+
|
|
3
|
+
export const getSimpleValue = (item: any): IdType => {
|
|
4
|
+
if (item === null) {
|
|
5
|
+
return item;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (typeof item === 'object' || typeof item === 'function') {
|
|
9
|
+
return `${item}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return item;
|
|
13
|
+
};
|
|
@@ -3,7 +3,10 @@ import { Component, SyntheticEvent, ReactNode, FC } from 'react';
|
|
|
3
3
|
import { IdType } from '@servicetitan/data-query';
|
|
4
4
|
import { TableFilterCellProps, Radio } from '@servicetitan/design-system';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
CustomColumnMenuFilterSingleOpts,
|
|
8
|
+
renderCustomColumnMenuFilter,
|
|
9
|
+
} from '../column-menu-filters';
|
|
7
10
|
|
|
8
11
|
interface SelectorProps<TV, TO> {
|
|
9
12
|
options: TO[];
|
|
@@ -33,16 +36,21 @@ class SelectorRadio<TV, TO> extends Component<SelectorProps<TV, TO>> {
|
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
export interface SingleSelectColumnMenuOptions<TV, TO = TV>
|
|
39
|
+
export interface SingleSelectColumnMenuOptions<TV, TO = TV>
|
|
40
|
+
extends CustomColumnMenuFilterSingleOpts {
|
|
37
41
|
options: TO[];
|
|
38
42
|
valueSelector?: (item: TO) => TV;
|
|
39
43
|
renderItem?: (item: TO) => ReactNode | string;
|
|
40
44
|
filterOperator?: (listItem: any, value: TV) => boolean;
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
export function singleSelectColumnMenuFilter<TV extends IdType, TO = TV>(
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
export function singleSelectColumnMenuFilter<TV extends IdType, TO = TV>({
|
|
48
|
+
options,
|
|
49
|
+
valueSelector,
|
|
50
|
+
renderItem,
|
|
51
|
+
filterOperator,
|
|
52
|
+
...opts
|
|
53
|
+
}: SingleSelectColumnMenuOptions<TV, TO>) {
|
|
46
54
|
const FilterCell: FC<TableFilterCellProps> = ({ value, onChange }) => {
|
|
47
55
|
const handleChange = (value: TV | undefined, event: SyntheticEvent<HTMLInputElement>) => {
|
|
48
56
|
const filter =
|
|
@@ -50,7 +58,7 @@ export function singleSelectColumnMenuFilter<TV extends IdType, TO = TV>(
|
|
|
50
58
|
? { value: '', operator: '' }
|
|
51
59
|
: {
|
|
52
60
|
value,
|
|
53
|
-
operator:
|
|
61
|
+
operator: filterOperator ?? 'equals',
|
|
54
62
|
};
|
|
55
63
|
|
|
56
64
|
onChange({
|
|
@@ -61,14 +69,14 @@ export function singleSelectColumnMenuFilter<TV extends IdType, TO = TV>(
|
|
|
61
69
|
|
|
62
70
|
return (
|
|
63
71
|
<SelectorRadio
|
|
64
|
-
options={
|
|
72
|
+
options={options}
|
|
65
73
|
value={value === '' ? undefined : value}
|
|
66
74
|
onChange={handleChange}
|
|
67
|
-
renderer={
|
|
68
|
-
valueSelector={
|
|
75
|
+
renderer={renderItem}
|
|
76
|
+
valueSelector={valueSelector ?? (option => option)}
|
|
69
77
|
/>
|
|
70
78
|
);
|
|
71
79
|
};
|
|
72
80
|
|
|
73
|
-
return renderCustomColumnMenuFilter(FilterCell);
|
|
81
|
+
return renderCustomColumnMenuFilter(FilterCell, opts);
|
|
74
82
|
}
|