@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.
Files changed (88) hide show
  1. package/dist/demo/filters/async-select-filter.js +1 -1
  2. package/dist/demo/filters/async-select-filter.js.map +1 -1
  3. package/dist/demo/filters/categories.d.ts.map +1 -1
  4. package/dist/demo/filters/categories.js +24 -0
  5. package/dist/demo/filters/categories.js.map +1 -1
  6. package/dist/demo/filters/multiselect-filter.d.ts.map +1 -1
  7. package/dist/demo/filters/multiselect-filter.js +2 -1
  8. package/dist/demo/filters/multiselect-filter.js.map +1 -1
  9. package/dist/demo/filters/select-filter.d.ts +6 -0
  10. package/dist/demo/filters/select-filter.d.ts.map +1 -0
  11. package/dist/demo/filters/select-filter.js +58 -0
  12. package/dist/demo/filters/select-filter.js.map +1 -0
  13. package/dist/demo/filters/single-select-filter.d.ts.map +1 -1
  14. package/dist/demo/filters/single-select-filter.js +2 -1
  15. package/dist/demo/filters/single-select-filter.js.map +1 -1
  16. package/dist/demo/filters/table.store.d.ts +8 -1
  17. package/dist/demo/filters/table.store.d.ts.map +1 -1
  18. package/dist/demo/filters/table.store.js +35 -1
  19. package/dist/demo/filters/table.store.js.map +1 -1
  20. package/dist/demo/overview/product.d.ts +5 -0
  21. package/dist/demo/overview/product.d.ts.map +1 -1
  22. package/dist/demo/overview/products.d.ts.map +1 -1
  23. package/dist/demo/overview/products.js +12 -0
  24. package/dist/demo/overview/products.js.map +1 -1
  25. package/dist/demo/overview/table.store.d.ts.map +1 -1
  26. package/dist/demo/overview/table.store.js +1 -0
  27. package/dist/demo/overview/table.store.js.map +1 -1
  28. package/dist/filters/async-select/async-select-filter.d.ts +7 -3
  29. package/dist/filters/async-select/async-select-filter.d.ts.map +1 -1
  30. package/dist/filters/async-select/async-select-filter.js +21 -6
  31. package/dist/filters/async-select/async-select-filter.js.map +1 -1
  32. package/dist/filters/column-menu-filters.d.ts +10 -1
  33. package/dist/filters/column-menu-filters.d.ts.map +1 -1
  34. package/dist/filters/column-menu-filters.js +11 -3
  35. package/dist/filters/column-menu-filters.js.map +1 -1
  36. package/dist/filters/index.d.ts +1 -0
  37. package/dist/filters/index.d.ts.map +1 -1
  38. package/dist/filters/index.js +1 -0
  39. package/dist/filters/index.js.map +1 -1
  40. package/dist/filters/multiselect-filter/multiselect-filter.d.ts +4 -3
  41. package/dist/filters/multiselect-filter/multiselect-filter.d.ts.map +1 -1
  42. package/dist/filters/multiselect-filter/multiselect-filter.js +7 -7
  43. package/dist/filters/multiselect-filter/multiselect-filter.js.map +1 -1
  44. package/dist/filters/select-filter/object-search.d.ts +2 -0
  45. package/dist/filters/select-filter/object-search.d.ts.map +1 -0
  46. package/dist/filters/select-filter/object-search.js +19 -0
  47. package/dist/filters/select-filter/object-search.js.map +1 -0
  48. package/dist/filters/select-filter/select-filter.d.ts +32 -0
  49. package/dist/filters/select-filter/select-filter.d.ts.map +1 -0
  50. package/dist/filters/select-filter/select-filter.js +214 -0
  51. package/dist/filters/select-filter/select-filter.js.map +1 -0
  52. package/dist/filters/select-filter/select-filter.stories.d.ts +8 -0
  53. package/dist/filters/select-filter/select-filter.stories.d.ts.map +1 -0
  54. package/dist/filters/select-filter/select-filter.stories.js +8 -0
  55. package/dist/filters/select-filter/select-filter.stories.js.map +1 -0
  56. package/dist/filters/select-filter/value-getter.d.ts +3 -0
  57. package/dist/filters/select-filter/value-getter.d.ts.map +1 -0
  58. package/dist/filters/select-filter/value-getter.js +10 -0
  59. package/dist/filters/select-filter/value-getter.js.map +1 -0
  60. package/dist/filters/single-select/single-select-filter.d.ts +3 -2
  61. package/dist/filters/single-select/single-select-filter.d.ts.map +1 -1
  62. package/dist/filters/single-select/single-select-filter.js +17 -7
  63. package/dist/filters/single-select/single-select-filter.js.map +1 -1
  64. package/dist/index.d.ts +1 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +1 -1
  67. package/dist/index.js.map +1 -1
  68. package/package.json +6 -6
  69. package/src/demo/filters/async-select-filter.tsx +1 -1
  70. package/src/demo/filters/categories.tsx +24 -0
  71. package/src/demo/filters/multiselect-filter.tsx +15 -0
  72. package/src/demo/filters/select-filter.tsx +147 -0
  73. package/src/demo/filters/single-select-filter.tsx +2 -1
  74. package/src/demo/filters/table.store.ts +45 -2
  75. package/src/demo/overview/product.ts +6 -0
  76. package/src/demo/overview/products.ts +12 -0
  77. package/src/demo/overview/table.store.ts +1 -0
  78. package/src/filters/async-select/async-select-filter.tsx +26 -15
  79. package/src/filters/column-menu-filters.tsx +44 -21
  80. package/src/filters/index.ts +1 -0
  81. package/src/filters/multiselect-filter/multiselect-filter.tsx +16 -7
  82. package/src/filters/select-filter/__tests__/object-search.test.ts +32 -0
  83. package/src/filters/select-filter/object-search.ts +25 -0
  84. package/src/filters/select-filter/select-filter.stories.tsx +8 -0
  85. package/src/filters/select-filter/select-filter.tsx +320 -0
  86. package/src/filters/select-filter/value-getter.ts +13 -0
  87. package/src/filters/single-select/single-select-filter.tsx +18 -10
  88. package/src/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicetitan/table",
3
- "version": "25.1.0",
3
+ "version": "25.2.0",
4
4
  "description": "",
5
5
  "homepage": "https://docs.st.dev/docs/frontend/table",
6
6
  "repository": {
@@ -37,9 +37,9 @@
37
37
  "memoize-one": "~6.0.0"
38
38
  },
39
39
  "devDependencies": {
40
- "@servicetitan/data-query": "^25.1.0",
40
+ "@servicetitan/data-query": "^25.2.0",
41
41
  "@servicetitan/design-system": ">=12.4.1",
42
- "@servicetitan/form": "^25.1.0",
42
+ "@servicetitan/form": "^25.2.0",
43
43
  "@servicetitan/react-ioc": "^21.6.0",
44
44
  "@servicetitan/suppress-warnings": "^21.6.0",
45
45
  "@types/accounting": "~0.4.2",
@@ -53,9 +53,9 @@
53
53
  "react": "~17.0.2"
54
54
  },
55
55
  "peerDependencies": {
56
- "@servicetitan/data-query": "^25.1.0",
56
+ "@servicetitan/data-query": "^25.2.0",
57
57
  "@servicetitan/design-system": ">=12.4.1",
58
- "@servicetitan/form": "^25.1.0",
58
+ "@servicetitan/form": "^25.2.0",
59
59
  "@servicetitan/react-ioc": ">21.0.0",
60
60
  "@servicetitan/suppress-warnings": ">21.0.0",
61
61
  "accounting": "~0.4.1",
@@ -72,5 +72,5 @@
72
72
  "cli": {
73
73
  "webpack": false
74
74
  },
75
- "gitHead": "bb6fc7bc0d532fce0512a7cc6e008cf0a5bfc91f"
75
+ "gitHead": "5ce3b7761ea46df59cee9e036aec6eefadd3e0ad"
76
76
  }
@@ -27,8 +27,8 @@ export const AsyncSelectDefaultExample: FC = provide({
27
27
  () =>
28
28
  asyncSelectColumnMenuFilter({
29
29
  dataFetcher: categoryFetcher,
30
- placeholder: 'Search for Categories',
31
30
  multiple: true,
31
+ contentMaxHeight: '250px',
32
32
  }),
33
33
  [categoryFetcher]
34
34
  );
@@ -31,6 +31,30 @@ export const categories: Category[] = [
31
31
  CategoryID: 10,
32
32
  CategoryName: 'Sashimi',
33
33
  },
34
+ {
35
+ CategoryID: 21,
36
+ CategoryName: 'Borsh',
37
+ },
38
+ {
39
+ CategoryID: 22,
40
+ CategoryName: 'Pasta Carbonara',
41
+ },
42
+ {
43
+ CategoryID: 23,
44
+ CategoryName: 'Caesar salad',
45
+ },
46
+ {
47
+ CategoryID: 24,
48
+ CategoryName: 'Ravioli',
49
+ },
50
+ {
51
+ CategoryID: 25,
52
+ CategoryName: 'Pizza',
53
+ },
54
+ {
55
+ CategoryID: 26,
56
+ CategoryName: 'Steak',
57
+ },
34
58
  ];
35
59
 
36
60
  export const CategoryCell: FC<TableCellProps> = props => {
@@ -19,6 +19,16 @@ export const MultiSelectDefaultExample: FC = provide({
19
19
  [madeInOptions]
20
20
  );
21
21
 
22
+ const categoryColumnMenu = useMemo(
23
+ () =>
24
+ multiSelectColumnMenuFilter(
25
+ new Array(20).fill(0).map((_, index) => index),
26
+ undefined,
27
+ { contentMaxHeight: '250px' }
28
+ ),
29
+ []
30
+ );
31
+
22
32
  return (
23
33
  <Table tableState={tableState}>
24
34
  <TableColumn field="ProductID" title="ID" editable={false} width="100px" />
@@ -26,6 +36,11 @@ export const MultiSelectDefaultExample: FC = provide({
26
36
  <TableColumn field="ProductName" title="Product Name" width="240px" />
27
37
 
28
38
  <TableColumn field="MadeIn" title="Made In" columnMenu={madeInColumnMenu} />
39
+ <TableColumn
40
+ field="CategoryID"
41
+ title="CategoryID"
42
+ columnMenu={categoryColumnMenu}
43
+ />
29
44
  </Table>
30
45
  );
31
46
  })
@@ -0,0 +1,147 @@
1
+ import { useMemo, FC, Fragment } from 'react';
2
+ import { observer } from 'mobx-react';
3
+ import { Banner } from '@servicetitan/design-system';
4
+ import { provide, useDependencies } from '@servicetitan/react-ioc';
5
+
6
+ import { Table, TableColumn, selectColumnMenuFilter, TableCellProps } from '../..';
7
+
8
+ import { TableStore } from './table.store';
9
+ import { categories, CategoryCell } from './categories';
10
+ import { Product } from '../overview/product';
11
+
12
+ export const PackageCell: FC<TableCellProps> = props => {
13
+ const { dataItem } = props;
14
+
15
+ return <td>{dataItem?.Package?.PackageName}</td>;
16
+ };
17
+
18
+ export const SelectAsyncExample: FC = provide({
19
+ singletons: [TableStore],
20
+ })(
21
+ observer(() => {
22
+ const [{ categoryFetcherClean, madeInFetcherClean, tableState }] =
23
+ useDependencies(TableStore);
24
+
25
+ const madeInColumnMenu = useMemo(
26
+ () =>
27
+ selectColumnMenuFilter({
28
+ dataFetcher: madeInFetcherClean,
29
+ search: true,
30
+ }),
31
+ [madeInFetcherClean]
32
+ );
33
+
34
+ const categoryColumnMenu = useMemo(
35
+ () =>
36
+ selectColumnMenuFilter({
37
+ dataFetcher: categoryFetcherClean,
38
+ multiple: true,
39
+ contentMaxHeight: '250px',
40
+ valueSelector: item => item.CategoryID,
41
+ }),
42
+ [categoryFetcherClean]
43
+ );
44
+
45
+ return (
46
+ <Fragment>
47
+ <Banner title="Default filter with async select" className="m-b-2">
48
+ Async select filter allows to choose one or several option from the list that
49
+ will be fetched from BE. The list size is not limited, so you can also use
50
+ search to find desired option.
51
+ <br />
52
+ Use it when there can be greater than 20 possible options
53
+ </Banner>
54
+ <Table tableState={tableState} striped={false}>
55
+ <TableColumn field="ProductID" title="ID" editable={false} width="100px" />
56
+ <TableColumn field="ProductName" title="Product Name" width="240px" />
57
+ <TableColumn
58
+ field="MadeIn"
59
+ title="Made In (async single-select filter)"
60
+ columnMenu={madeInColumnMenu}
61
+ />
62
+ <TableColumn
63
+ field="CategoryID"
64
+ title="Category (async multi-select filter)"
65
+ columnMenu={categoryColumnMenu}
66
+ cell={CategoryCell}
67
+ />
68
+ </Table>
69
+ </Fragment>
70
+ );
71
+ })
72
+ );
73
+
74
+ export const SelectSyncExample: FC = provide({
75
+ singletons: [TableStore],
76
+ })(
77
+ observer(() => {
78
+ const [{ madeInOptions, packageOptions, tableState }] = useDependencies(TableStore);
79
+
80
+ const madeInColumnMenu = useMemo(
81
+ () =>
82
+ selectColumnMenuFilter({
83
+ data: madeInOptions,
84
+ search: {
85
+ placeholder: 'Search for made-ins',
86
+ filter: search => item => item.toLowerCase().includes(search),
87
+ },
88
+ }),
89
+ [madeInOptions]
90
+ );
91
+
92
+ const categoryColumnMenu = useMemo(
93
+ () =>
94
+ selectColumnMenuFilter({
95
+ data: categories,
96
+ multiple: true,
97
+ contentMaxHeight: '250px',
98
+ valueSelector: item => item.CategoryID,
99
+ renderItem: item => item.CategoryName,
100
+ search: true,
101
+ }),
102
+ []
103
+ );
104
+
105
+ const packageColumnMenu = useMemo(
106
+ () =>
107
+ selectColumnMenuFilter({
108
+ data: packageOptions,
109
+ multiple: false,
110
+ valueSelector: item => item.Id,
111
+ renderItem: item => item.Name,
112
+ rowValueSelector: (item: Product['Package']) => item?.PackageId,
113
+ }),
114
+ [packageOptions]
115
+ );
116
+
117
+ return (
118
+ <Fragment>
119
+ <Banner title="Default filter with sync select" className="m-b-2">
120
+ Select filter allows to choose one or multiple option(s) from the provided list.
121
+ You can pass simple options (string or number) or complex objects
122
+ </Banner>
123
+ <Table tableState={tableState} striped={false}>
124
+ <TableColumn field="ProductID" title="ID" editable={false} width="100px" />
125
+ <TableColumn field="ProductName" title="Product Name" width="240px" />
126
+ <TableColumn
127
+ field="MadeIn"
128
+ title="Made In (sync single-select filter)"
129
+ columnMenu={madeInColumnMenu}
130
+ />
131
+ <TableColumn
132
+ field="CategoryID"
133
+ title="Category (sync multi-select filter)"
134
+ columnMenu={categoryColumnMenu}
135
+ cell={CategoryCell}
136
+ />
137
+ <TableColumn
138
+ field="Package"
139
+ title="Package (sync single-select filter)"
140
+ columnMenu={packageColumnMenu}
141
+ cell={PackageCell}
142
+ />
143
+ </Table>
144
+ </Fragment>
145
+ );
146
+ })
147
+ );
@@ -27,10 +27,11 @@ export const SingleSelectDefaultExample: FC = provide({
27
27
  options: categories,
28
28
  renderItem: cat => (
29
29
  <BodyText>
30
- <span className="fw-bold">cat.CategoryName</span> (#{cat.CategoryID})
30
+ <span className="fw-bold">{cat.CategoryName}</span> (#{cat.CategoryID})
31
31
  </BodyText>
32
32
  ),
33
33
  valueSelector: cat => cat.CategoryID,
34
+ contentMaxHeight: '200px',
34
35
  }),
35
36
  []
36
37
  );
@@ -1,8 +1,13 @@
1
1
  import { injectable } from '@servicetitan/react-ioc';
2
2
 
3
- import { InMemoryDataSource, TableState, AsyncSelectFilterDataFetcher } from '../..';
3
+ import {
4
+ InMemoryDataSource,
5
+ TableState,
6
+ AsyncSelectFilterDataFetcher,
7
+ SelectFilterDataFetcher,
8
+ } from '../..';
4
9
  import { products } from '../overview/products';
5
- import { categories } from './categories';
10
+ import { categories, Category } from './categories';
6
11
 
7
12
  @injectable()
8
13
  export class TableStore {
@@ -15,6 +20,22 @@ export class TableStore {
15
20
  return Array.from(new Set(products.map(p => p.MadeIn)));
16
21
  }
17
22
 
23
+ get packageOptions() {
24
+ const map = products.reduce(
25
+ (out, product) => ({
26
+ ...out,
27
+ ...(product.Package
28
+ ? { [product.Package.PackageId]: product.Package.PackageName }
29
+ : {}),
30
+ }),
31
+ {} as Record<number, string>
32
+ );
33
+
34
+ return Object.keys(map)
35
+ .map(Number)
36
+ .map(id => ({ Id: id, Name: map[id] }));
37
+ }
38
+
18
39
  categoryFetcher: AsyncSelectFilterDataFetcher<number> = async opts => {
19
40
  await new Promise(resolve => setTimeout(resolve, 1500));
20
41
 
@@ -39,6 +60,28 @@ export class TableStore {
39
60
  };
40
61
  };
41
62
 
63
+ categoryFetcherClean: SelectFilterDataFetcher<Category> = async opts => {
64
+ await new Promise(resolve => setTimeout(resolve, 1500));
65
+
66
+ const sv = opts.search?.trim().toLowerCase();
67
+
68
+ return {
69
+ data: categories.filter(cat =>
70
+ sv ? cat.CategoryName.toLowerCase().includes(sv) : true
71
+ ),
72
+ };
73
+ };
74
+
75
+ madeInFetcherClean: SelectFilterDataFetcher<string> = async opts => {
76
+ await new Promise(resolve => setTimeout(resolve, 1500));
77
+
78
+ const sv = opts.search?.trim().toLowerCase();
79
+
80
+ return {
81
+ data: this.madeInOptions.filter(opt => (sv ? opt.toLowerCase().includes(sv) : true)),
82
+ };
83
+ };
84
+
42
85
  private getDataSource() {
43
86
  return new InMemoryDataSource(products, row => row.ProductID);
44
87
  }
@@ -13,6 +13,11 @@ export enum Supplier {
13
13
  Benjamin,
14
14
  }
15
15
 
16
+ export interface ProductPackage {
17
+ PackageId: number;
18
+ PackageName: string;
19
+ }
20
+
16
21
  export interface Product {
17
22
  ProductID: number;
18
23
  ProductName: string;
@@ -25,4 +30,5 @@ export interface Product {
25
30
  UnitsOnOrder: Date;
26
31
  Discontinued: boolean;
27
32
  AvailableFor?: UserRole;
33
+ Package?: ProductPackage;
28
34
  }
@@ -13,6 +13,10 @@ export const products: Product[] = [
13
13
  UnitsOnOrder: new Date('1/11/2023'),
14
14
  Discontinued: false,
15
15
  AvailableFor: UserRole.Admin,
16
+ Package: {
17
+ PackageId: 1,
18
+ PackageName: 'Standard package',
19
+ },
16
20
  },
17
21
  {
18
22
  ProductID: 2,
@@ -25,6 +29,10 @@ export const products: Product[] = [
25
29
  UnitsInStock: 17,
26
30
  UnitsOnOrder: new Date('2/11/2023'),
27
31
  Discontinued: false,
32
+ Package: {
33
+ PackageId: 1,
34
+ PackageName: 'Standard package',
35
+ },
28
36
  },
29
37
  {
30
38
  ProductID: 3,
@@ -63,6 +71,10 @@ export const products: Product[] = [
63
71
  UnitsOnOrder: new Date('5/11/2023'),
64
72
  Discontinued: true,
65
73
  AvailableFor: UserRole.GeneralOffice,
74
+ Package: {
75
+ PackageId: 2,
76
+ PackageName: 'Extended package',
77
+ },
66
78
  },
67
79
  {
68
80
  ProductID: 6,
@@ -65,6 +65,7 @@ export class TableStore {
65
65
  ),
66
66
  Discontinued: new FieldState(false),
67
67
  AvailableFor: new FieldState<UserRole | undefined>(undefined),
68
+ Package: new FieldState<Product['Package'] | undefined>(undefined),
68
69
  }),
69
70
  row
70
71
  );
@@ -10,7 +10,10 @@ import {
10
10
  } from '@servicetitan/design-system';
11
11
  import { IdType } from '@servicetitan/data-query';
12
12
 
13
- import { renderCustomColumnMenuFilter } from '../column-menu-filters';
13
+ import {
14
+ CustomColumnMenuFilterSingleOpts,
15
+ renderCustomColumnMenuFilter,
16
+ } from '../column-menu-filters';
14
17
  import { makeObservable, observable, runInAction } from 'mobx';
15
18
  import { observer } from 'mobx-react';
16
19
 
@@ -25,7 +28,7 @@ export interface AsyncSelectItem<TV extends IdType = IdType> {
25
28
  }
26
29
 
27
30
  interface SelectorProps<TID extends IdType, TO extends AsyncSelectItem<TID>> {
28
- placeholder: string;
31
+ placeholder?: string;
29
32
  selected: TO[];
30
33
  dataFetcher: AsyncSelectFilterDataFetcher<TID, TO>;
31
34
  itemComponent: FC<SelectorItemProps>;
@@ -174,9 +177,10 @@ const SelectorItemMultiple: FC<SelectorItemProps> = ({ option, renderer, checked
174
177
  />
175
178
  );
176
179
 
177
- export interface AsyncSelectFilterOptions<TID extends IdType, TO extends AsyncSelectItem<TID>> {
180
+ export interface AsyncSelectFilterOptions<TID extends IdType, TO extends AsyncSelectItem<TID>>
181
+ extends CustomColumnMenuFilterSingleOpts {
178
182
  dataFetcher: AsyncSelectFilterDataFetcher<TID, TO>;
179
- placeholder: string;
183
+ placeholder?: string;
180
184
  multiple?: boolean;
181
185
  renderItem?: (item: TO) => ReactNode;
182
186
  }
@@ -185,13 +189,20 @@ interface TableFilterCellPropsTyped<T = any> extends Omit<TableFilterCellProps,
185
189
  value?: T;
186
190
  }
187
191
 
188
- export function asyncSelectColumnMenuFilter<TID extends IdType, TO extends AsyncSelectItem<TID>>(
189
- options: AsyncSelectFilterOptions<TID, TO>
190
- ) {
192
+ /**
193
+ * @deprecated use selectColumnMenuFilter instead
194
+ */
195
+ export function asyncSelectColumnMenuFilter<TID extends IdType, TO extends AsyncSelectItem<TID>>({
196
+ dataFetcher,
197
+ placeholder,
198
+ multiple,
199
+ renderItem,
200
+ ...opts
201
+ }: AsyncSelectFilterOptions<TID, TO>) {
191
202
  const contains = (value: TID, options?: TO[]) => options?.some(opt => opt.value === value);
192
203
  const equals = (value: TID, option?: TO) => option?.value === value;
193
204
 
194
- const FilterCell = options.multiple
205
+ const FilterCell = multiple
195
206
  ? ({ value, onChange }: TableFilterCellPropsTyped<TO[]>) => {
196
207
  const handleChange = (
197
208
  option: TO,
@@ -214,9 +225,9 @@ export function asyncSelectColumnMenuFilter<TID extends IdType, TO extends Async
214
225
  selected={value ?? []}
215
226
  itemComponent={SelectorItemMultiple}
216
227
  onChange={handleChange}
217
- placeholder={options.placeholder}
218
- renderer={options.renderItem}
219
- dataFetcher={options.dataFetcher}
228
+ placeholder={placeholder}
229
+ renderer={renderItem}
230
+ dataFetcher={dataFetcher}
220
231
  />
221
232
  );
222
233
  }
@@ -238,12 +249,12 @@ export function asyncSelectColumnMenuFilter<TID extends IdType, TO extends Async
238
249
  selected={value ? [value] : []}
239
250
  onChange={handleChange}
240
251
  itemComponent={SelectorItemSingle}
241
- placeholder={options.placeholder}
242
- renderer={options.renderItem}
243
- dataFetcher={options.dataFetcher}
252
+ placeholder={placeholder}
253
+ renderer={renderItem}
254
+ dataFetcher={dataFetcher}
244
255
  />
245
256
  );
246
257
  };
247
258
 
248
- return renderCustomColumnMenuFilter(FilterCell);
259
+ return renderCustomColumnMenuFilter(FilterCell, opts);
249
260
  }
@@ -1,4 +1,4 @@
1
- import { ComponentType, FC } from 'react';
1
+ import { ComponentType, CSSProperties, FC } from 'react';
2
2
  import {
3
3
  TableColumnMenuFilter,
4
4
  TableColumnMenuProps,
@@ -11,35 +11,58 @@ export const StandardColumnMenuFilter = (props: TableColumnMenuProps) => (
11
11
  <TableColumnMenuFilter {...props} expanded />
12
12
  );
13
13
 
14
+ export interface CustomColumnMenuFilterSingleOpts {
15
+ contentMaxHeight?: string;
16
+ contentClassName?: string;
17
+ }
18
+ export interface CustomColumnMenuFilterDoubleOpts {
19
+ double?: boolean;
20
+ doubleContentClassName?: string;
21
+ }
22
+
23
+ export type CustomColumnMenuFilterOpts = CustomColumnMenuFilterSingleOpts &
24
+ CustomColumnMenuFilterDoubleOpts;
25
+
14
26
  export function renderCustomColumnMenuFilter(
15
27
  FilterCell: ComponentType<TableFilterCellProps>,
16
- double?: boolean
28
+ doubleOrOptions?: boolean | CustomColumnMenuFilterOpts
17
29
  ) {
18
- const filterUI: FC<TableColumnMenuFilterUIProps> = ({
30
+ const opts: CustomColumnMenuFilterOpts =
31
+ typeof doubleOrOptions === 'boolean' ? { double: doubleOrOptions } : doubleOrOptions ?? {};
32
+ const FilterUI: FC<TableColumnMenuFilterUIProps> = ({
19
33
  firstFilterProps,
20
34
  secondFilterProps,
21
35
  logicData,
22
36
  logicValue,
23
37
  onLogicChange,
24
- }) => (
25
- <div>
26
- <FilterCell {...firstFilterProps} />
27
- {double && (
28
- <div>
29
- <DropDownList
30
- data={logicData}
31
- value={logicValue}
32
- onChange={onLogicChange}
33
- className="k-filter-and m-b-1"
34
- textField="text"
35
- />
36
- <FilterCell {...secondFilterProps} />
37
- </div>
38
- )}
39
- </div>
40
- );
38
+ }) => {
39
+ const contentStyles: CSSProperties = {};
40
+
41
+ if (opts.contentMaxHeight) {
42
+ contentStyles.maxHeight = opts.contentMaxHeight;
43
+ contentStyles.overflowY = 'auto';
44
+ }
45
+
46
+ return (
47
+ <div className={opts.contentClassName} style={contentStyles}>
48
+ <FilterCell {...firstFilterProps} />
49
+ {!!opts.double && (
50
+ <div className={opts.doubleContentClassName}>
51
+ <DropDownList
52
+ data={logicData}
53
+ value={logicValue}
54
+ onChange={onLogicChange}
55
+ className="k-filter-and m-b-1"
56
+ textField="text"
57
+ />
58
+ <FilterCell {...secondFilterProps} />
59
+ </div>
60
+ )}
61
+ </div>
62
+ );
63
+ };
41
64
 
42
65
  return (props: TableColumnMenuProps) => (
43
- <TableColumnMenuFilter {...props} filterUI={filterUI} expanded />
66
+ <TableColumnMenuFilter {...props} filterUI={FilterUI} expanded />
44
67
  );
45
68
  }
@@ -8,3 +8,4 @@ export * from './multiselect-filter/multiselect-filter';
8
8
  export * from './numeric-filter-extended/numeric-filter-extended';
9
9
  export * from './single-select/single-select-filter';
10
10
  export * from './async-select/async-select-filter';
11
+ export * from './select-filter/select-filter';
@@ -2,7 +2,10 @@ import { Component, SyntheticEvent, ReactNode, FC } from 'react';
2
2
 
3
3
  import { TableFilterCellProps, Checkbox } from '@servicetitan/design-system';
4
4
 
5
- import { renderCustomColumnMenuFilter } from '../column-menu-filters';
5
+ import {
6
+ CustomColumnMenuFilterSingleOpts,
7
+ renderCustomColumnMenuFilter,
8
+ } from '../column-menu-filters';
6
9
 
7
10
  interface SelectorProps<T> {
8
11
  options: T[];
@@ -37,7 +40,11 @@ class Selector<T> extends Component<SelectorProps<T>> {
37
40
  }
38
41
  }
39
42
 
40
- export function multiSelectColumnMenuFilter<T>(data: T[], renderItem?: (item: T) => ReactNode) {
43
+ export function multiSelectColumnMenuFilter<T>(
44
+ data: T[],
45
+ renderItem?: (item: T) => ReactNode,
46
+ opts?: CustomColumnMenuFilterSingleOpts
47
+ ) {
41
48
  const FilterCell: FC<TableFilterCellProps> = ({ value, onChange }) => {
42
49
  const handleChange = (value: T[] | undefined, event: SyntheticEvent<HTMLInputElement>) => {
43
50
  onChange({
@@ -57,12 +64,13 @@ export function multiSelectColumnMenuFilter<T>(data: T[], renderItem?: (item: T)
57
64
  );
58
65
  };
59
66
 
60
- return renderCustomColumnMenuFilter(FilterCell);
67
+ return renderCustomColumnMenuFilter(FilterCell, opts);
61
68
  }
62
69
 
63
70
  export function complexItemMultiSelectColumnMenuFilter<T>(
64
71
  data: T[],
65
- renderItem?: (item: T) => ReactNode
72
+ renderItem?: (item: T) => ReactNode,
73
+ opts?: CustomColumnMenuFilterSingleOpts
66
74
  ) {
67
75
  class FilterCell extends Component<TableFilterCellProps> {
68
76
  contains = (item: any) => {
@@ -93,12 +101,13 @@ export function complexItemMultiSelectColumnMenuFilter<T>(
93
101
  }
94
102
  }
95
103
 
96
- return renderCustomColumnMenuFilter(FilterCell);
104
+ return renderCustomColumnMenuFilter(FilterCell, opts);
97
105
  }
98
106
 
99
107
  export function multiItemMultiSelectColumnMenuFilter<T>(
100
108
  data: T[],
101
- renderItem?: (item: T) => ReactNode
109
+ renderItem?: (item: T) => ReactNode,
110
+ opts?: CustomColumnMenuFilterSingleOpts
102
111
  ) {
103
112
  class FilterCell extends Component<TableFilterCellProps> {
104
113
  contains = (item: any) => {
@@ -129,5 +138,5 @@ export function multiItemMultiSelectColumnMenuFilter<T>(
129
138
  }
130
139
  }
131
140
 
132
- return renderCustomColumnMenuFilter(FilterCell);
141
+ return renderCustomColumnMenuFilter(FilterCell, opts);
133
142
  }
@@ -0,0 +1,32 @@
1
+ import { objectSearch } from '../object-search';
2
+
3
+ describe('objectSearch', () => {
4
+ const obj = () => ({
5
+ id: 12345,
6
+ name: 'Some Name',
7
+ active: true,
8
+ category: null,
9
+ });
10
+
11
+ test('search and find in simple string', () => {
12
+ expect(objectSearch('iMPl')('sImplE String')).toBe(true);
13
+ });
14
+ test('search and not find in simple string', () => {
15
+ expect(objectSearch('iMoPl')('sImplE String')).toBe(false);
16
+ });
17
+
18
+ test('search simple number', () => {
19
+ expect(objectSearch('23')(12345)).toBe(false);
20
+ });
21
+
22
+ test('search null', () => {
23
+ expect(objectSearch('23')(null)).toBe(false);
24
+ });
25
+
26
+ test('search and find in object', () => {
27
+ expect(objectSearch('Me ')(obj())).toBe(true);
28
+ });
29
+ test('search and not find in object', () => {
30
+ expect(objectSearch('Mee')(obj)).toBe(false);
31
+ });
32
+ });