material-react-table 0.28.0 → 0.29.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.
@@ -1,5 +1,12 @@
1
1
  import { FC } from 'react';
2
2
  import type { MRT_Header, MRT_TableInstance } from '..';
3
+ import { MRT_Localization } from '../localization';
4
+ export declare const internalFilterOptions: (localization: MRT_Localization) => {
5
+ option: string;
6
+ symbol: string;
7
+ label: string;
8
+ divider: boolean;
9
+ }[];
3
10
  interface Props {
4
11
  anchorEl: HTMLElement | null;
5
12
  header?: MRT_Header;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
- "version": "0.28.0",
2
+ "version": "0.29.0",
3
3
  "license": "MIT",
4
4
  "name": "material-react-table",
5
- "description": "A fully featured Material UI implementation of TanStack React Table, inspired by material-table and the MUI X DataGrid, written from the ground up in TypeScript.",
5
+ "description": "A fully featured Material UI V5 implementation of TanStack React Table V8, written from the ground up in TypeScript.",
6
6
  "author": "Kevin Vandy",
7
7
  "keywords": [
8
8
  "react-table",
@@ -62,7 +62,7 @@
62
62
  "@emotion/styled": "^11.9.3",
63
63
  "@faker-js/faker": "^7.3.0",
64
64
  "@mui/icons-material": "^5.8.4",
65
- "@mui/material": "^5.9.1",
65
+ "@mui/material": "^5.9.2",
66
66
  "@size-limit/preset-small-lib": "^7.0.8",
67
67
  "@storybook/addon-a11y": "^6.5.9",
68
68
  "@storybook/addon-actions": "^6.5.9",
@@ -225,6 +225,7 @@ export type MRT_ColumnDef<TData extends Record<string, any> = {}> = Omit<
225
225
  enableEditing?: boolean;
226
226
  filterFn?: MRT_FilterFn<TData>;
227
227
  filterSelectOptions?: (string | { text: string; value: string })[];
228
+ filterVariant?: 'text' | 'select' | 'multiSelect' | 'range';
228
229
  /**
229
230
  * footer must be a string. If you want custom JSX to render the footer, you can also specify a `Footer` option. (Capital F)
230
231
  */
@@ -302,9 +303,11 @@ export type MRT_ColumnDef<TData extends Record<string, any> = {}> = Omit<
302
303
  | (({
303
304
  table,
304
305
  column,
306
+ rangeFilterIndex,
305
307
  }: {
306
308
  table: MRT_TableInstance<TData>;
307
309
  column: MRT_Column<TData>;
310
+ rangeFilterIndex?: number;
308
311
  }) => TextFieldProps);
309
312
  muiTableHeadCellProps?:
310
313
  | TableCellProps
@@ -341,6 +344,7 @@ export type MRT_DefinedColumnDef<TData extends Record<string, any> = {}> = Omit<
341
344
  'id'
342
345
  > & {
343
346
  id: string;
347
+ _filterFn: MRT_FilterOption;
344
348
  };
345
349
 
346
350
  export type MRT_Column<TData extends Record<string, any> = {}> = Omit<
@@ -607,9 +611,11 @@ export type MaterialReactTableProps<TData extends Record<string, any> = {}> =
607
611
  | (({
608
612
  table,
609
613
  column,
614
+ rangeFilterIndex,
610
615
  }: {
611
616
  table: MRT_TableInstance<TData>;
612
617
  column: MRT_Column<TData>;
618
+ rangeFilterIndex?: number;
613
619
  }) => TextFieldProps);
614
620
  muiTableHeadCellProps?:
615
621
  | TableCellProps
@@ -52,6 +52,8 @@ export const prepareColumns = <TData extends Record<string, any> = {}>(
52
52
  if (Object.keys(MRT_FilterFns).includes(currentFilterFns[columnDef.id])) {
53
53
  columnDef.filterFn =
54
54
  MRT_FilterFns[currentFilterFns[columnDef.id]] ?? MRT_FilterFns.fuzzy;
55
+ //@ts-ignore
56
+ columnDef._filterFn = currentFilterFns[columnDef.id];
55
57
  }
56
58
  if (Object.keys(MRT_SortingFns).includes(columnDef.sortingFn as string)) {
57
59
  // @ts-ignore
@@ -123,3 +125,14 @@ export const getDefaultColumnOrderIds = <
123
125
  ),
124
126
  ...getTrailingDisplayColumnIds(props),
125
127
  ].filter(Boolean) as string[];
128
+
129
+ export const getDefaultColumnFilterFn = <
130
+ TData extends Record<string, any> = {},
131
+ >(
132
+ columnDef: MRT_ColumnDef<TData>,
133
+ ): MRT_FilterOption => {
134
+ if (columnDef.filterVariant === 'multiSelect') return 'arrIncludesSome';
135
+ if (columnDef.filterVariant === 'select') return 'equals';
136
+ if (columnDef.filterVariant === 'range') return 'betweenInclusive';
137
+ return 'fuzzy';
138
+ };
@@ -19,7 +19,9 @@ export const MRT_TableHeadCellFilterContainer: FC<Props> = ({
19
19
 
20
20
  return (
21
21
  <Collapse in={showColumnFilters} mountOnEnter unmountOnExit>
22
- {currentFilterFns[column.id] === 'between' ? (
22
+ {['between', 'betweenInclusive', 'inNumberRange'].includes(
23
+ currentFilterFns[column.id],
24
+ ) ? (
23
25
  <MRT_FilterRangeFields header={header} table={table} />
24
26
  ) : (
25
27
  <MRT_FilterTextField header={header} table={table} />
@@ -19,6 +19,11 @@ export const MRT_TableHeadCellFilterLabel: FC<Props> = ({ header, table }) => {
19
19
  const { column } = header;
20
20
  const { columnDef } = column;
21
21
 
22
+ const isRangeFilter = [
23
+ 'between',
24
+ 'betweenInclusive',
25
+ 'inNumberRange',
26
+ ].includes(columnDef._filterFn);
22
27
  const currentFilterOption = currentFilterFns?.[header.id];
23
28
  const filterTooltip = localization.filteringByColumn
24
29
  .replace('{column}', String(columnDef.header))
@@ -37,7 +42,7 @@ export const MRT_TableHeadCellFilterLabel: FC<Props> = ({ header, table }) => {
37
42
  `"${
38
43
  Array.isArray(column.getFilterValue())
39
44
  ? (column.getFilterValue() as [string, string]).join(
40
- `" ${localization.and} "`,
45
+ `" ${isRangeFilter ? localization.and : localization.or} "`,
41
46
  )
42
47
  : (column.getFilterValue() as string)
43
48
  }"`,
@@ -48,8 +53,8 @@ export const MRT_TableHeadCellFilterLabel: FC<Props> = ({ header, table }) => {
48
53
  <Grow
49
54
  unmountOnExit
50
55
  in={
51
- (!!column.getFilterValue() && currentFilterOption !== 'between') ||
52
- (currentFilterOption === 'between' && // @ts-ignore
56
+ (!!column.getFilterValue() && isRangeFilter) ||
57
+ (!isRangeFilter && // @ts-ignore
53
58
  (!!column.getFilterValue()?.[0] || !!column.getFilterValue()?.[1]))
54
59
  }
55
60
  >
@@ -11,8 +11,8 @@ interface Props {
11
11
  export const MRT_FilterRangeFields: FC<Props> = ({ header, table }) => {
12
12
  return (
13
13
  <Box sx={{ display: 'grid', gridTemplateColumns: '6fr 6fr', gap: '1rem' }}>
14
- <MRT_FilterTextField header={header} inputIndex={0} table={table} />
15
- <MRT_FilterTextField header={header} inputIndex={1} table={table} />
14
+ <MRT_FilterTextField header={header} rangeFilterIndex={0} table={table} />
15
+ <MRT_FilterTextField header={header} rangeFilterIndex={1} table={table} />
16
16
  </Box>
17
17
  );
18
18
  };
@@ -6,10 +6,13 @@ import React, {
6
6
  useState,
7
7
  } from 'react';
8
8
  import {
9
+ Box,
10
+ Checkbox,
9
11
  Chip,
10
12
  debounce,
11
13
  IconButton,
12
14
  InputAdornment,
15
+ ListItemText,
13
16
  MenuItem,
14
17
  TextField,
15
18
  TextFieldProps,
@@ -20,13 +23,13 @@ import type { MRT_Header, MRT_TableInstance } from '..';
20
23
 
21
24
  interface Props {
22
25
  header: MRT_Header;
23
- inputIndex?: number;
26
+ rangeFilterIndex?: number;
24
27
  table: MRT_TableInstance;
25
28
  }
26
29
 
27
30
  export const MRT_FilterTextField: FC<Props> = ({
28
31
  header,
29
- inputIndex,
32
+ rangeFilterIndex,
30
33
  table,
31
34
  }) => {
32
35
  const {
@@ -45,11 +48,13 @@ export const MRT_FilterTextField: FC<Props> = ({
45
48
  const { columnDef } = column;
46
49
  const { currentFilterFns } = getState();
47
50
 
48
- const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
49
-
50
51
  const mTableHeadCellFilterTextFieldProps =
51
52
  muiTableHeadCellFilterTextFieldProps instanceof Function
52
- ? muiTableHeadCellFilterTextFieldProps({ column, table })
53
+ ? muiTableHeadCellFilterTextFieldProps({
54
+ column,
55
+ table,
56
+ rangeFilterIndex,
57
+ })
53
58
  : muiTableHeadCellFilterTextFieldProps;
54
59
 
55
60
  const mcTableHeadCellFilterTextFieldProps =
@@ -57,6 +62,7 @@ export const MRT_FilterTextField: FC<Props> = ({
57
62
  ? columnDef.muiTableHeadCellFilterTextFieldProps({
58
63
  column,
59
64
  table,
65
+ rangeFilterIndex,
60
66
  })
61
67
  : columnDef.muiTableHeadCellFilterTextFieldProps;
62
68
 
@@ -65,28 +71,71 @@ export const MRT_FilterTextField: FC<Props> = ({
65
71
  ...mcTableHeadCellFilterTextFieldProps,
66
72
  } as TextFieldProps;
67
73
 
68
- const [filterValue, setFilterValue] = useState<string>(() =>
69
- inputIndex !== undefined
70
- ? (column.getFilterValue() as [string, string])?.[inputIndex] ?? ''
74
+ const isRangeFilter =
75
+ columnDef.filterVariant === 'range' || rangeFilterIndex !== undefined;
76
+ const isSelectFilter = columnDef.filterVariant === 'select';
77
+ const isMultiSelectFilter = columnDef.filterVariant === 'multiSelect';
78
+ const isTextboxFilter = !isSelectFilter && !isMultiSelectFilter;
79
+
80
+ const currentFilterOption = currentFilterFns?.[header.id];
81
+ const filterId = `mrt-${tableId}-${header.id}-filter-text-field${
82
+ rangeFilterIndex ?? ''
83
+ }`;
84
+ const filterChipLabel = ['empty', 'notEmpty'].includes(currentFilterOption)
85
+ ? //@ts-ignore
86
+ localization[
87
+ `filter${
88
+ currentFilterOption?.charAt?.(0)?.toUpperCase() +
89
+ currentFilterOption?.slice(1)
90
+ }`
91
+ ]
92
+ : '';
93
+ const filterPlaceholder = !isRangeFilter
94
+ ? localization.filterByColumn?.replace('{column}', String(columnDef.header))
95
+ : rangeFilterIndex === 0
96
+ ? localization.min
97
+ : rangeFilterIndex === 1
98
+ ? localization.max
99
+ : '';
100
+ const allowedColumnFilterOptions =
101
+ columnDef?.columnFilterModeOptions ?? columnFilterModeOptions;
102
+ const showChangeModeButton =
103
+ enableColumnFilterChangeMode &&
104
+ columnDef.enableColumnFilterChangeMode !== false &&
105
+ !rangeFilterIndex &&
106
+ (allowedColumnFilterOptions === undefined ||
107
+ !!allowedColumnFilterOptions?.length);
108
+
109
+ const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
110
+ const [filterValue, setFilterValue] = useState<string | string[]>(() =>
111
+ isMultiSelectFilter
112
+ ? (column.getFilterValue() as string[]) || []
113
+ : isRangeFilter
114
+ ? (column.getFilterValue() as [string, string])?.[
115
+ rangeFilterIndex as number
116
+ ] || []
71
117
  : (column.getFilterValue() as string) ?? '',
72
118
  );
73
119
 
74
120
  const handleChangeDebounced = useCallback(
75
- debounce((event: ChangeEvent<HTMLInputElement>) => {
76
- let value =
77
- textFieldProps.type === 'date'
78
- ? new Date(event.target.value)
79
- : event.target.value;
80
- if (inputIndex !== undefined) {
81
- column.setFilterValue((old: [string, string | Date]) => {
82
- const newFilterValues = old ?? ['', ''];
83
- newFilterValues[inputIndex] = value;
84
- return newFilterValues;
85
- });
86
- } else {
87
- column.setFilterValue(value ?? undefined);
88
- }
89
- }, 200),
121
+ debounce(
122
+ (event: ChangeEvent<HTMLInputElement>) => {
123
+ let value =
124
+ textFieldProps.type === 'date'
125
+ ? new Date(event.target.value)
126
+ : event.target.value;
127
+ if (isRangeFilter) {
128
+ column.setFilterValue((old: [string, string | Date]) => {
129
+ const newFilterValues = old ?? ['', ''];
130
+ newFilterValues[rangeFilterIndex as number] = value;
131
+ return newFilterValues;
132
+ });
133
+ } else {
134
+ column.setFilterValue(value ?? undefined);
135
+ }
136
+ },
137
+ isTextboxFilter ? 200 : 1,
138
+ ),
90
139
  [],
91
140
  );
92
141
 
@@ -95,24 +144,24 @@ export const MRT_FilterTextField: FC<Props> = ({
95
144
  handleChangeDebounced(event);
96
145
  };
97
146
 
98
- const handleFilterMenuOpen = (event: MouseEvent<HTMLElement>) => {
99
- setAnchorEl(event.currentTarget);
100
- };
101
-
102
147
  const handleClear = () => {
103
- setFilterValue('');
104
- if (inputIndex !== undefined) {
148
+ if (isMultiSelectFilter) {
149
+ setFilterValue([]);
150
+ column.setFilterValue([]);
151
+ } else if (isRangeFilter) {
152
+ setFilterValue('');
105
153
  column.setFilterValue((old: [string | undefined, string | undefined]) => {
106
154
  const newFilterValues = old ?? ['', ''];
107
- newFilterValues[inputIndex] = undefined;
155
+ newFilterValues[rangeFilterIndex as number] = undefined;
108
156
  return newFilterValues;
109
157
  });
110
158
  } else {
159
+ setFilterValue('');
111
160
  column.setFilterValue(undefined);
112
161
  }
113
162
  };
114
163
 
115
- const handleClearFilterChip = () => {
164
+ const handleClearEmptyFilterChip = () => {
116
165
  setFilterValue('');
117
166
  column.setFilterValue(undefined);
118
167
  setCurrentFilterFns((prev) => ({
@@ -121,47 +170,14 @@ export const MRT_FilterTextField: FC<Props> = ({
121
170
  }));
122
171
  };
123
172
 
173
+ const handleFilterMenuOpen = (event: MouseEvent<HTMLElement>) => {
174
+ setAnchorEl(event.currentTarget);
175
+ };
176
+
124
177
  if (columnDef.Filter) {
125
178
  return <>{columnDef.Filter?.({ header, table })}</>;
126
179
  }
127
180
 
128
- const filterId = `mrt-${tableId}-${header.id}-filter-text-field${
129
- inputIndex ?? ''
130
- }`;
131
- const currentFilterOption = currentFilterFns?.[header.id];
132
- const isSelectFilter = !!columnDef.filterSelectOptions;
133
- const filterChipLabel = ['empty', 'notEmpty'].includes(currentFilterOption)
134
- ? //@ts-ignore
135
- localization[
136
- `filter${
137
- currentFilterOption?.charAt(0)?.toUpperCase() +
138
- currentFilterOption?.slice(1)
139
- }`
140
- ]
141
- : '';
142
- const filterPlaceholder =
143
- inputIndex === undefined
144
- ? localization.filterByColumn?.replace(
145
- '{column}',
146
- String(columnDef.header),
147
- )
148
- : inputIndex === 0
149
- ? localization.min
150
- : inputIndex === 1
151
- ? localization.max
152
- : '';
153
-
154
- const allowedColumnFilterOptions =
155
- columnDef?.columnFilterModeOptions ?? columnFilterModeOptions;
156
-
157
- const showChangeModeButton =
158
- enableColumnFilterChangeMode &&
159
- columnDef.enableColumnFilterChangeMode !== false &&
160
- !isSelectFilter &&
161
- !inputIndex &&
162
- (allowedColumnFilterOptions === undefined ||
163
- !!allowedColumnFilterOptions?.length);
164
-
165
181
  return (
166
182
  <>
167
183
  <TextField
@@ -200,12 +216,14 @@ export const MRT_FilterTextField: FC<Props> = ({
200
216
  }}
201
217
  margin="none"
202
218
  placeholder={
203
- filterChipLabel || isSelectFilter ? undefined : filterPlaceholder
219
+ filterChipLabel || isSelectFilter || isMultiSelectFilter
220
+ ? undefined
221
+ : filterPlaceholder
204
222
  }
205
223
  onChange={handleChange}
206
224
  onClick={(e: MouseEvent<HTMLInputElement>) => e.stopPropagation()}
207
- select={isSelectFilter}
208
- value={filterValue ?? ''}
225
+ select={isSelectFilter || isMultiSelectFilter}
226
+ value={filterValue}
209
227
  variant="standard"
210
228
  InputProps={{
211
229
  startAdornment: showChangeModeButton ? (
@@ -224,7 +242,7 @@ export const MRT_FilterTextField: FC<Props> = ({
224
242
  </Tooltip>
225
243
  {filterChipLabel && (
226
244
  <Chip
227
- onDelete={handleClearFilterChip}
245
+ onDelete={handleClearEmptyFilterChip}
228
246
  label={filterChipLabel}
229
247
  />
230
248
  )}
@@ -236,7 +254,6 @@ export const MRT_FilterTextField: FC<Props> = ({
236
254
  <InputAdornment position="end">
237
255
  <Tooltip
238
256
  arrow
239
- disableHoverListener={isSelectFilter}
240
257
  placement="right"
241
258
  title={localization.clearFilter ?? ''}
242
259
  >
@@ -258,10 +275,26 @@ export const MRT_FilterTextField: FC<Props> = ({
258
275
  </InputAdornment>
259
276
  ),
260
277
  }}
278
+ SelectProps={{
279
+ displayEmpty: true,
280
+ multiple: isMultiSelectFilter,
281
+ renderValue: isMultiSelectFilter
282
+ ? (selected: any) =>
283
+ !selected?.length ? (
284
+ <Box sx={{ opacity: 0.5 }}>{filterPlaceholder}</Box>
285
+ ) : (
286
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: '2px' }}>
287
+ {(selected as string[])?.map((value) => (
288
+ <Chip key={value} label={value} />
289
+ ))}
290
+ </Box>
291
+ )
292
+ : undefined,
293
+ }}
261
294
  {...textFieldProps}
262
295
  sx={(theme) => ({
263
296
  p: 0,
264
- minWidth: !filterChipLabel ? '8rem' : 'auto',
297
+ minWidth: !filterChipLabel ? '6rem' : 'auto',
265
298
  width: 'calc(100% + 0.5rem)',
266
299
  '& .MuiSelect-icon': {
267
300
  mr: '1.5rem',
@@ -271,24 +304,32 @@ export const MRT_FilterTextField: FC<Props> = ({
271
304
  : (textFieldProps?.sx as any)),
272
305
  })}
273
306
  >
274
- {isSelectFilter && (
275
- <MenuItem divider disabled={!filterValue} value="">
276
- {localization.clearFilter}
307
+ {(isSelectFilter || isMultiSelectFilter) && (
308
+ <MenuItem divider disabled hidden value="">
309
+ <Box sx={{ opacity: 0.5 }}>{filterPlaceholder}</Box>
277
310
  </MenuItem>
278
311
  )}
279
312
  {columnDef?.filterSelectOptions?.map((option) => {
280
- let value;
281
- let text;
282
- if (typeof option === 'string') {
313
+ let value: string;
314
+ let text: string;
315
+ if (typeof option !== 'object') {
283
316
  value = option;
284
317
  text = option;
285
- } else if (typeof option === 'object') {
318
+ } else {
286
319
  value = option.value;
287
320
  text = option.text;
288
321
  }
289
322
  return (
290
323
  <MenuItem key={value} value={value}>
291
- {text}
324
+ {isMultiSelectFilter && (
325
+ <Checkbox
326
+ checked={(
327
+ (column.getFilterValue() ?? []) as string[]
328
+ ).includes(value)}
329
+ sx={{ mr: '0.5rem' }}
330
+ />
331
+ )}
332
+ <ListItemText>{text}</ListItemText>
292
333
  </MenuItem>
293
334
  );
294
335
  })}
@@ -13,6 +13,9 @@ export interface MRT_Localization {
13
13
  edit: string;
14
14
  expand: string;
15
15
  expandAll: string;
16
+ filterArrIncludes: string;
17
+ filterArrIncludesAll: string;
18
+ filterArrIncludesSome: string;
16
19
  filterBetween: string;
17
20
  filterBetweenInclusive: string;
18
21
  filterByColumn: string;
@@ -20,15 +23,20 @@ export interface MRT_Localization {
20
23
  filterEmpty: string;
21
24
  filterEndsWith: string;
22
25
  filterEquals: string;
26
+ filterEqualsString: string;
23
27
  filterFuzzy: string;
24
28
  filterGreaterThan: string;
25
29
  filterGreaterThanOrEqualTo: string;
30
+ filterInNumberRange: string;
31
+ filterIncludesString: string;
32
+ filterIncludesStringSensitive: string;
26
33
  filterLessThan: string;
27
34
  filterLessThanOrEqualTo: string;
28
35
  filterMode: string;
29
36
  filterNotEmpty: string;
30
37
  filterNotEquals: string;
31
38
  filterStartsWith: string;
39
+ filterWeakEquals: string;
32
40
  filteringByColumn: string;
33
41
  grab: string;
34
42
  groupByColumn: string;
@@ -38,6 +46,7 @@ export interface MRT_Localization {
38
46
  max: string;
39
47
  min: string;
40
48
  move: string;
49
+ or: string;
41
50
  pinToLeft: string;
42
51
  pinToRight: string;
43
52
  resetColumnSize: string;
@@ -85,6 +94,9 @@ export const MRT_DefaultLocalization_EN: MRT_Localization = {
85
94
  edit: 'Edit',
86
95
  expand: 'Expand',
87
96
  expandAll: 'Expand all',
97
+ filterArrIncludes: 'Includes',
98
+ filterArrIncludesAll: 'Includes all',
99
+ filterArrIncludesSome: 'Includes',
88
100
  filterBetween: 'Between',
89
101
  filterBetweenInclusive: 'Between Inclusive',
90
102
  filterByColumn: 'Filter by {column}',
@@ -92,15 +104,20 @@ export const MRT_DefaultLocalization_EN: MRT_Localization = {
92
104
  filterEmpty: 'Empty',
93
105
  filterEndsWith: 'Ends With',
94
106
  filterEquals: 'Equals',
107
+ filterEqualsString: 'Equals',
95
108
  filterFuzzy: 'Fuzzy',
96
109
  filterGreaterThan: 'Greater Than',
97
110
  filterGreaterThanOrEqualTo: 'Greater Than Or Equal To',
111
+ filterInNumberRange: 'Between',
112
+ filterIncludesString: 'Contains',
113
+ filterIncludesStringSensitive: 'Contains',
98
114
  filterLessThan: 'Less Than',
99
115
  filterLessThanOrEqualTo: 'Less Than Or Equal To',
100
116
  filterMode: 'Filter Mode: {filterType}',
101
117
  filterNotEmpty: 'Not Empty',
102
118
  filterNotEquals: 'Not Equals',
103
119
  filterStartsWith: 'Starts With',
120
+ filterWeakEquals: 'Equals',
104
121
  filteringByColumn: 'Filtering by {column} - {filterType} {filterValue}',
105
122
  grab: 'Grab',
106
123
  groupByColumn: 'Group by {column}',
@@ -110,6 +127,7 @@ export const MRT_DefaultLocalization_EN: MRT_Localization = {
110
127
  max: 'Max',
111
128
  min: 'Min',
112
129
  move: 'Move',
130
+ or: 'or',
113
131
  pinToLeft: 'Pin to left',
114
132
  pinToRight: 'Pin to right',
115
133
  resetColumnSize: 'Reset column size',