material-react-table 0.12.1 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,2 @@
1
- /// <reference types="react" />
2
1
  import { MaterialReactTableProps } from '../MaterialReactTable';
3
2
  export declare const MRT_TableRoot: <D extends Record<string, any> = {}>(props: MaterialReactTableProps<D>) => JSX.Element;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.12.1",
2
+ "version": "0.13.1",
3
3
  "license": "MIT",
4
4
  "name": "material-react-table",
5
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.",
@@ -121,7 +121,7 @@ export type MRT_TableInstance<D extends Record<string, any> = {}> = Omit<
121
121
  }>
122
122
  >;
123
123
  setCurrentGlobalFilterFn: Dispatch<SetStateAction<MRT_FilterFn>>;
124
- setIsDensePadding: Dispatch<SetStateAction<boolean>>;
124
+ setDensity: Dispatch<SetStateAction<'comfortable' | 'compact' | 'spacious'>>;
125
125
  setIsFullScreen: Dispatch<SetStateAction<boolean>>;
126
126
  setShowFilters: Dispatch<SetStateAction<boolean>>;
127
127
  setShowGlobalFilter: Dispatch<SetStateAction<boolean>>;
@@ -132,7 +132,7 @@ export type MRT_TableState<D extends Record<string, any> = {}> = TableState & {
132
132
  currentEditingRow: MRT_Row<D> | null;
133
133
  currentFilterFns: Record<string, string | Function>;
134
134
  currentGlobalFilterFn: Record<string, string | Function>;
135
- isDensePadding: boolean;
135
+ density: 'comfortable' | 'compact' | 'spacious';
136
136
  isLoading: boolean;
137
137
  isFullScreen: boolean;
138
138
  showFilters: boolean;
@@ -294,19 +294,23 @@ export type MRT_ColumnDef<D extends Record<string, any> = {}> = Omit<
294
294
  column,
295
295
  event,
296
296
  filterValue,
297
+ instance,
297
298
  }: {
298
299
  column: MRT_Column<D>;
299
300
  event: ChangeEvent<HTMLInputElement>;
300
301
  filterValue: any;
302
+ instance: MRT_TableInstance<D>;
301
303
  }) => void;
302
304
  onColumnFilterValueChangedDebounced?: ({
303
305
  column,
304
306
  event,
305
307
  filterValue,
308
+ instance,
306
309
  }: {
307
310
  column: MRT_Column<D>;
308
311
  event: ChangeEvent<HTMLInputElement>;
309
312
  filterValue: any;
313
+ instance: MRT_TableInstance<D>;
310
314
  }) => void;
311
315
  };
312
316
 
@@ -382,7 +386,7 @@ export type MaterialReactTableProps<D extends Record<string, any> = {}> =
382
386
  enableClickToCopy?: boolean;
383
387
  enableColumnActions?: boolean;
384
388
  enableColumnOrdering?: boolean;
385
- enableDensePaddingToggle?: boolean;
389
+ enableDensityToggle?: boolean;
386
390
  enableEditing?: boolean;
387
391
  enableExpandAll?: boolean;
388
392
  enableFullScreenToggle?: boolean;
@@ -600,19 +604,23 @@ export type MaterialReactTableProps<D extends Record<string, any> = {}> =
600
604
  column,
601
605
  event,
602
606
  filterValue,
607
+ instance,
603
608
  }: {
604
609
  column: MRT_Column<D>;
605
610
  event: ChangeEvent<HTMLInputElement>;
606
611
  filterValue: any;
612
+ instance: MRT_TableInstance<D>;
607
613
  }) => void;
608
614
  onColumnFilterValueChangedDebounced?: ({
609
615
  column,
610
616
  event,
611
617
  filterValue,
618
+ instance,
612
619
  }: {
613
620
  column: MRT_Column<D>;
614
621
  event: ChangeEvent<HTMLInputElement>;
615
622
  filterValue: any;
623
+ instance: MRT_TableInstance<D>;
616
624
  }) => void;
617
625
  onColumnVisibilityChanged?: ({
618
626
  column,
@@ -665,14 +673,14 @@ export type MaterialReactTableProps<D extends Record<string, any> = {}> =
665
673
  event: ChangeEvent<HTMLInputElement>;
666
674
  instance: MRT_TableInstance<D>;
667
675
  }) => void;
668
- onIsDensePaddingChange?: OnChangeFn<boolean>;
669
- onIsDensePaddingChanged?: ({
676
+ onDensityChange?: OnChangeFn<boolean>;
677
+ onDensityChanged?: ({
670
678
  event,
671
- isDensePadding,
679
+ density,
672
680
  instance,
673
681
  }: {
674
682
  event: MouseEvent<HTMLButtonElement>;
675
- isDensePadding: boolean;
683
+ density: 'comfortable' | 'compact' | 'spacious';
676
684
  instance: MRT_TableInstance<D>;
677
685
  }) => void;
678
686
  onIsFullScreenChange?: OnChangeFn<boolean>;
@@ -808,7 +816,7 @@ export default <D extends Record<string, any> = {}>({
808
816
  enableColumnFilters = true,
809
817
  enableColumnOrdering = false,
810
818
  enableColumnResizing = false,
811
- enableDensePaddingToggle = true,
819
+ enableDensityToggle = true,
812
820
  enableExpandAll = true,
813
821
  enableFilters = true,
814
822
  enableFullScreenToggle = true,
@@ -847,7 +855,7 @@ export default <D extends Record<string, any> = {}>({
847
855
  enableColumnFilters={enableColumnFilters}
848
856
  enableColumnOrdering={enableColumnOrdering}
849
857
  enableColumnResizing={enableColumnResizing}
850
- enableDensePaddingToggle={enableDensePaddingToggle}
858
+ enableDensityToggle={enableDensityToggle}
851
859
  enableExpandAll={enableExpandAll}
852
860
  enableFilters={enableFilters}
853
861
  enableFullScreenToggle={enableFullScreenToggle}
@@ -22,7 +22,7 @@ export const MRT_TableBody: FC<Props> = ({ instance, tableContainerRef }) => {
22
22
  },
23
23
  } = instance;
24
24
 
25
- const { isDensePadding } = getState();
25
+ const { density } = getState();
26
26
 
27
27
  const tableBodyProps =
28
28
  muiTableBodyProps instanceof Function
@@ -35,7 +35,7 @@ export const MRT_TableBody: FC<Props> = ({ instance, tableContainerRef }) => {
35
35
 
36
36
  const rowVirtualizer = enableRowVirtualization
37
37
  ? useVirtual({
38
- overscan: isDensePadding ? 15 : 5,
38
+ overscan: density === 'compact' ? 15 : 5,
39
39
  size: rows.length,
40
40
  parentRef: tableContainerRef,
41
41
  ...virtualizerProps,
@@ -41,7 +41,7 @@ export const MRT_TableBodyCell: FC<Props> = ({
41
41
  columnOrder,
42
42
  currentEditingCell,
43
43
  currentEditingRow,
44
- isDensePadding,
44
+ density,
45
45
  isLoading,
46
46
  showSkeletons,
47
47
  } = getState();
@@ -152,23 +152,35 @@ export const MRT_TableBodyCell: FC<Props> = ({
152
152
  ? `${column.getStart('left')}px`
153
153
  : undefined,
154
154
  overflow: 'hidden',
155
- p: isDensePadding
156
- ? columnDefType === 'display'
157
- ? '0 0.5rem'
158
- : '0.5rem'
159
- : columnDefType === 'display'
160
- ? '0.5rem 0.75rem'
161
- : '1rem',
155
+ p:
156
+ density === 'compact'
157
+ ? columnDefType === 'display'
158
+ ? '0 0.5rem'
159
+ : '0.5rem'
160
+ : density === 'comfortable'
161
+ ? columnDefType === 'display'
162
+ ? '0.5rem 0.75rem'
163
+ : '1rem'
164
+ : columnDefType === 'display'
165
+ ? '1rem 1.25rem'
166
+ : '1.5rem',
162
167
  pl:
163
168
  column.id === 'mrt-expand'
164
- ? `${row.depth + (isDensePadding ? 0.5 : 0.75)}rem`
169
+ ? `${
170
+ row.depth +
171
+ (density === 'compact'
172
+ ? 0.5
173
+ : density === 'comfortable'
174
+ ? 0.75
175
+ : 1.25)
176
+ }rem`
165
177
  : undefined,
166
178
  position: column.getIsPinned() ? 'sticky' : 'relative',
167
179
  right:
168
180
  column.getIsPinned() === 'right' ? `${getTotalRight()}px` : undefined,
169
181
  textOverflow: columnDefType !== 'display' ? 'ellipsis' : undefined,
170
182
  transition: 'all 0.2s ease-in-out',
171
- whiteSpace: isDensePadding ? 'nowrap' : 'normal',
183
+ whiteSpace: density === 'compact' ? 'nowrap' : 'normal',
172
184
  zIndex: column.getIsPinned() ? 1 : undefined,
173
185
  '&:hover': {
174
186
  backgroundColor:
@@ -20,7 +20,7 @@ export const MRT_ExpandAllButton: FC<Props> = ({ instance }) => {
20
20
  toggleAllRowsExpanded,
21
21
  } = instance;
22
22
 
23
- const { isDensePadding } = getState();
23
+ const { density } = getState();
24
24
 
25
25
  return (
26
26
  <Tooltip
@@ -34,8 +34,8 @@ export const MRT_ExpandAllButton: FC<Props> = ({ instance }) => {
34
34
  disabled={!getCanSomeRowsExpand() && !renderDetailPanel}
35
35
  onClick={() => toggleAllRowsExpanded(!getIsAllRowsExpanded())}
36
36
  sx={{
37
- height: isDensePadding ? '1.75rem' : '2.25rem',
38
- width: isDensePadding ? '1.75rem' : '2.25rem',
37
+ height: density === 'compact' ? '1.75rem' : '2.25rem',
38
+ width: density === 'compact' ? '1.75rem' : '2.25rem',
39
39
  }}
40
40
  >
41
41
  <KeyboardDoubleArrowDownIcon
@@ -18,7 +18,7 @@ export const MRT_ExpandButton: FC<Props> = ({ row, instance }) => {
18
18
  },
19
19
  } = instance;
20
20
 
21
- const { isDensePadding } = getState();
21
+ const { density } = getState();
22
22
 
23
23
  const handleToggleExpand = (event: MouseEvent<HTMLButtonElement>) => {
24
24
  row.toggleExpanded();
@@ -37,8 +37,8 @@ export const MRT_ExpandButton: FC<Props> = ({ row, instance }) => {
37
37
  disabled={!row.getCanExpand() && !renderDetailPanel}
38
38
  onClick={handleToggleExpand}
39
39
  sx={{
40
- height: isDensePadding ? '1.75rem' : '2.25rem',
41
- width: isDensePadding ? '1.75rem' : '2.25rem',
40
+ height: density === 'compact' ? '1.75rem' : '2.25rem',
41
+ width: density === 'compact' ? '1.75rem' : '2.25rem',
42
42
  }}
43
43
  >
44
44
  <ExpandMoreIcon
@@ -13,32 +13,44 @@ export const MRT_ToggleDensePaddingButton: FC<Props> = ({
13
13
  const {
14
14
  getState,
15
15
  options: {
16
- icons: { DensityMediumIcon, DensitySmallIcon },
16
+ icons: { DensityLargeIcon, DensityMediumIcon, DensitySmallIcon },
17
17
  localization,
18
- onIsDensePaddingChanged,
18
+ onDensityChanged,
19
19
  },
20
- setIsDensePadding,
20
+ setDensity,
21
21
  } = instance;
22
22
 
23
- const { isDensePadding } = getState();
23
+ const { density } = getState();
24
24
 
25
25
  const handleToggleDensePadding = (event: MouseEvent<HTMLButtonElement>) => {
26
- onIsDensePaddingChanged?.({
26
+ const nextDensity =
27
+ density === 'comfortable'
28
+ ? 'compact'
29
+ : density === 'compact'
30
+ ? 'spacious'
31
+ : 'comfortable';
32
+ onDensityChanged?.({
27
33
  event,
28
- isDensePadding: !isDensePadding,
34
+ density: nextDensity,
29
35
  instance,
30
36
  });
31
- setIsDensePadding(!isDensePadding);
37
+ setDensity(nextDensity);
32
38
  };
33
39
 
34
40
  return (
35
- <Tooltip arrow title={localization.toggleDensePadding}>
41
+ <Tooltip arrow title={localization.toggleDensity}>
36
42
  <IconButton
37
- aria-label={localization.toggleDensePadding}
43
+ aria-label={localization.toggleDensity}
38
44
  onClick={handleToggleDensePadding}
39
45
  {...rest}
40
46
  >
41
- {isDensePadding ? <DensitySmallIcon /> : <DensityMediumIcon />}
47
+ {density === 'compact' ? (
48
+ <DensitySmallIcon />
49
+ ) : density === 'comfortable' ? (
50
+ <DensityMediumIcon />
51
+ ) : (
52
+ <DensityLargeIcon />
53
+ )}
42
54
  </IconButton>
43
55
  </Tooltip>
44
56
  );
@@ -13,7 +13,7 @@ export const MRT_TableFooterCell: FC<Props> = ({ footer, instance }) => {
13
13
  options: { muiTableFooterCellProps, enableColumnResizing },
14
14
  } = instance;
15
15
 
16
- const { isDensePadding } = getState();
16
+ const { density } = getState();
17
17
 
18
18
  const { column } = footer;
19
19
 
@@ -49,7 +49,12 @@ export const MRT_TableFooterCell: FC<Props> = ({ footer, instance }) => {
49
49
  fontWeight: 'bold',
50
50
  maxWidth: `${column.getSize()}px`,
51
51
  minWidth: `${column.getSize()}px`,
52
- p: isDensePadding ? '0.5rem' : '1rem',
52
+ p:
53
+ density === 'compact'
54
+ ? '0.5rem'
55
+ : density === 'comfortable'
56
+ ? '1rem'
57
+ : '1.5rem',
53
58
  transition: `all ${enableColumnResizing ? '10ms' : '0.2s'} ease-in-out`,
54
59
  width: column.getSize(),
55
60
  verticalAlign: 'text-top',
@@ -37,7 +37,7 @@ export const MRT_TableHeadCell: FC<Props> = ({
37
37
  },
38
38
  } = instance;
39
39
 
40
- const { isDensePadding } = getState();
40
+ const { density } = getState();
41
41
 
42
42
  const { column } = header;
43
43
 
@@ -111,19 +111,31 @@ export const MRT_TableHeadCell: FC<Props> = ({
111
111
  ? `${column.getStart('left')}px`
112
112
  : undefined,
113
113
  overflow: 'visible',
114
- p: isDensePadding
115
- ? columnDefType === 'display'
116
- ? '0 0.5rem'
117
- : '0.5rem'
118
- : columnDefType === 'display'
119
- ? '0.5rem 0.75rem'
120
- : '1rem',
114
+ p:
115
+ density === 'compact'
116
+ ? columnDefType === 'display'
117
+ ? '0 0.5rem'
118
+ : '0.5rem'
119
+ : density === 'comfortable'
120
+ ? columnDefType === 'display'
121
+ ? '0.5rem 0.75rem'
122
+ : '1rem'
123
+ : columnDefType === 'display'
124
+ ? '1rem 1.25rem'
125
+ : '1.5rem',
121
126
  pb: columnDefType === 'display' ? 0 : undefined,
122
127
  position:
123
128
  column.getIsPinned() && columnDefType !== 'group'
124
129
  ? 'sticky'
125
130
  : undefined,
126
- pt: columnDefType !== 'data' ? 0 : isDensePadding ? '0.25' : '.75rem',
131
+ pt:
132
+ columnDefType !== 'data'
133
+ ? 0
134
+ : density === 'compact'
135
+ ? '0.25'
136
+ : density === 'comfortable'
137
+ ? '.75rem'
138
+ : '1.25rem',
127
139
  right:
128
140
  column.getIsPinned() === 'right' ? `${getTotalRight()}px` : undefined,
129
141
  transition: `all ${enableColumnResizing ? 0 : '0.2s'} ease-in-out`,
package/src/icons.ts CHANGED
@@ -4,9 +4,9 @@ import {
4
4
  CheckBox,
5
5
  ClearAll,
6
6
  Close,
7
+ DensityLarge,
7
8
  DensityMedium,
8
9
  DensitySmall,
9
- KeyboardDoubleArrowDown,
10
10
  DragHandle,
11
11
  DynamicFeed,
12
12
  Edit,
@@ -16,8 +16,9 @@ import {
16
16
  FilterAltOff,
17
17
  FilterList,
18
18
  FilterListOff,
19
- FullscreenExit,
20
19
  Fullscreen,
20
+ FullscreenExit,
21
+ KeyboardDoubleArrowDown,
21
22
  MoreHoriz,
22
23
  MoreVert,
23
24
  PushPin,
@@ -36,6 +37,7 @@ export interface MRT_Icons {
36
37
  CheckBoxIcon: any;
37
38
  ClearAllIcon: any;
38
39
  CloseIcon: any;
40
+ DensityLargeIcon: any;
39
41
  DensityMediumIcon: any;
40
42
  DensitySmallIcon: any;
41
43
  KeyboardDoubleArrowDownIcon: any;
@@ -68,9 +70,9 @@ export const MRT_Default_Icons: MRT_Icons = {
68
70
  CheckBoxIcon: CheckBox,
69
71
  ClearAllIcon: ClearAll,
70
72
  CloseIcon: Close,
73
+ DensityLargeIcon: DensityLarge,
71
74
  DensityMediumIcon: DensityMedium,
72
75
  DensitySmallIcon: DensitySmall,
73
- KeyboardDoubleArrowDownIcon: KeyboardDoubleArrowDown,
74
76
  DragHandleIcon: DragHandle,
75
77
  DynamicFeedIcon: DynamicFeed,
76
78
  EditIcon: Edit,
@@ -82,6 +84,7 @@ export const MRT_Default_Icons: MRT_Icons = {
82
84
  FilterListOffIcon: FilterListOff,
83
85
  FullscreenExitIcon: FullscreenExit,
84
86
  FullscreenIcon: Fullscreen,
87
+ KeyboardDoubleArrowDownIcon: KeyboardDoubleArrowDown,
85
88
  MoreHorizIcon: MoreHoriz,
86
89
  MoreVertIcon: MoreVert,
87
90
  PushPinIcon: PushPin,
@@ -90,11 +90,13 @@ export const MRT_FilterTextField: FC<Props> = ({
90
90
  column,
91
91
  event,
92
92
  filterValue: event.target.value,
93
+ instance,
93
94
  });
94
95
  columnDef.onColumnFilterValueChangedDebounced?.({
95
96
  column,
96
97
  event,
97
98
  filterValue: event.target.value,
99
+ instance,
98
100
  });
99
101
  }, 200),
100
102
  [],
@@ -107,11 +109,13 @@ export const MRT_FilterTextField: FC<Props> = ({
107
109
  column,
108
110
  event,
109
111
  filterValue: event.target.value,
112
+ instance,
110
113
  });
111
114
  columnDef.onColumnFilterValueChanged?.({
112
115
  column,
113
116
  event,
114
117
  filterValue: event.target.value,
118
+ instance,
115
119
  });
116
120
  };
117
121
 
@@ -22,7 +22,7 @@ export const MRT_SelectCheckbox: FC<Props> = ({ row, selectAll, instance }) => {
22
22
  },
23
23
  } = instance;
24
24
 
25
- const { isDensePadding } = getState();
25
+ const { density } = getState();
26
26
 
27
27
  const handleSelectChange = (event: ChangeEvent<HTMLInputElement>) => {
28
28
  if (selectAll) {
@@ -80,11 +80,11 @@ export const MRT_SelectCheckbox: FC<Props> = ({ row, selectAll, instance }) => {
80
80
  : localization.toggleSelectRow,
81
81
  }}
82
82
  onChange={handleSelectChange}
83
- size={isDensePadding ? 'small' : 'medium'}
83
+ size={density === 'compact' ? 'small' : 'medium'}
84
84
  {...checkboxProps}
85
85
  sx={{
86
- height: isDensePadding ? '1.75rem' : '2.25rem',
87
- width: isDensePadding ? '1.75rem' : '2.25rem',
86
+ height: density === 'compact' ? '1.75rem' : '2.25rem',
87
+ width: density === 'compact' ? '1.75rem' : '2.25rem',
88
88
  ...checkboxProps?.sx,
89
89
  }}
90
90
  />
@@ -56,7 +56,7 @@ export interface MRT_Localization {
56
56
  sortedByColumnDesc: string;
57
57
  thenBy: string;
58
58
  to: string;
59
- toggleDensePadding: string;
59
+ toggleDensity: string;
60
60
  toggleFullScreen: string;
61
61
  toggleSelectAll: string;
62
62
  toggleSelectRow: string;
@@ -125,7 +125,7 @@ export const MRT_DefaultLocalization_EN: MRT_Localization = {
125
125
  sortedByColumnDesc: 'Sorted by {column} descending',
126
126
  thenBy: ', then by ',
127
127
  to: 'to',
128
- toggleDensePadding: 'Toggle dense padding',
128
+ toggleDensity: 'Toggle density',
129
129
  toggleFullScreen: 'Toggle full screen',
130
130
  toggleSelectAll: 'Toggle select all',
131
131
  toggleSelectRow: 'Toggle select row',
@@ -62,7 +62,7 @@ export const MRT_ColumnActionMenu: FC<Props> = ({
62
62
 
63
63
  const { columnDef } = column;
64
64
 
65
- const { columnSizing, columnVisibility, isDensePadding } = getState();
65
+ const { columnSizing, columnVisibility, density } = getState();
66
66
 
67
67
  const [filterMenuAnchorEl, setFilterMenuAnchorEl] =
68
68
  useState<null | HTMLElement>(null);
@@ -150,7 +150,7 @@ export const MRT_ColumnActionMenu: FC<Props> = ({
150
150
  open={!!anchorEl}
151
151
  onClose={() => setAnchorEl(null)}
152
152
  MenuListProps={{
153
- dense: isDensePadding,
153
+ dense: density === 'compact',
154
154
  }}
155
155
  >
156
156
  {enableSorting &&
@@ -52,8 +52,7 @@ export const MRT_FilterOptionMenu: FC<Props> = ({
52
52
  setCurrentGlobalFilterFn,
53
53
  } = instance;
54
54
 
55
- const { isDensePadding, currentFilterFns, currentGlobalFilterFn } =
56
- getState();
55
+ const { density, currentFilterFns, currentGlobalFilterFn } = getState();
57
56
 
58
57
  const { column } = header ?? {};
59
58
 
@@ -178,7 +177,7 @@ export const MRT_FilterOptionMenu: FC<Props> = ({
178
177
  onClose={() => setAnchorEl(null)}
179
178
  open={!!anchorEl}
180
179
  MenuListProps={{
181
- dense: isDensePadding,
180
+ dense: density === 'compact',
182
181
  }}
183
182
  >
184
183
  {filterOptions.map(({ option, label, divider, fn }, index) => (
@@ -31,7 +31,7 @@ export const MRT_RowActionMenu: FC<Props> = ({
31
31
  },
32
32
  } = instance;
33
33
 
34
- const { isDensePadding } = getState();
34
+ const { density } = getState();
35
35
 
36
36
  return (
37
37
  <Menu
@@ -39,7 +39,7 @@ export const MRT_RowActionMenu: FC<Props> = ({
39
39
  open={!!anchorEl}
40
40
  onClose={() => setAnchorEl(null)}
41
41
  MenuListProps={{
42
- dense: isDensePadding,
42
+ dense: density === 'compact',
43
43
  }}
44
44
  >
45
45
  {enableEditing && (
@@ -30,7 +30,7 @@ export const MRT_ShowHideColumnsMenu: FC<Props> = ({
30
30
  options: { localization, enablePinning, enableColumnOrdering },
31
31
  } = instance;
32
32
 
33
- const { isDensePadding, columnOrder, columnPinning } = getState();
33
+ const { density, columnOrder, columnPinning } = getState();
34
34
 
35
35
  const hideAllColumns = () => {
36
36
  getAllLeafColumns()
@@ -68,7 +68,7 @@ export const MRT_ShowHideColumnsMenu: FC<Props> = ({
68
68
  open={!!anchorEl}
69
69
  onClose={() => setAnchorEl(null)}
70
70
  MenuListProps={{
71
- dense: isDensePadding,
71
+ dense: density === 'compact',
72
72
  }}
73
73
  >
74
74
  <Box