Skip to content

Commit c4706c9

Browse files
committed
add column groupable property
1 parent 23be0cf commit c4706c9

17 files changed

Lines changed: 395 additions & 2 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import * as React from 'react';
2+
3+
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
4+
5+
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
6+
7+
type Developer = {
8+
id: number;
9+
10+
firstName: string;
11+
lastName: string;
12+
country: string;
13+
city: string;
14+
currency: string;
15+
preferredLanguage: string;
16+
stack: string;
17+
canDesign: 'yes' | 'no';
18+
hobby: string;
19+
salary: number;
20+
age: number;
21+
};
22+
23+
const dataSource = () => {
24+
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
25+
.then((r) => r.json())
26+
.then((data: Developer[]) => data);
27+
};
28+
29+
const columns: InfiniteTablePropColumns<Developer> = {
30+
id: { field: 'id' },
31+
32+
firstName: {
33+
field: 'firstName',
34+
columnGroup: 'info',
35+
},
36+
37+
preferredLanguage: { field: 'preferredLanguage', columnGroup: 'info' },
38+
stack: { field: 'stack', columnGroup: 'other' },
39+
country: { field: 'country', columnGroup: 'other' },
40+
};
41+
42+
const domProps = {
43+
style: {
44+
height: '80vh',
45+
margin: 10,
46+
},
47+
};
48+
49+
function Cmp() {
50+
return <InfiniteTable.Body />;
51+
}
52+
53+
export default function App() {
54+
const [colWidth, setColWidth] = React.useState(200);
55+
return (
56+
<>
57+
<button
58+
onClick={() => {
59+
setColWidth(colWidth === 200 ? 400 : colWidth === 400 ? 100 : 200);
60+
}}
61+
>
62+
toggle col width
63+
</button>
64+
<DataSource<Developer>
65+
primaryKey="id"
66+
data={dataSource}
67+
defaultGroupBy={[{ field: 'country' }, { field: 'stack' }]}
68+
selectionMode="multi-cell"
69+
>
70+
<InfiniteTable<Developer>
71+
domProps={domProps}
72+
columns={columns}
73+
columnGroups={{
74+
info: {
75+
header: 'info',
76+
},
77+
other: {
78+
header: 'other',
79+
},
80+
}}
81+
keyboardNavigation={'cell'}
82+
columnDefaultWidth={colWidth}
83+
>
84+
<InfiniteTable.GroupingToolbar />
85+
<InfiniteTable.Header />
86+
87+
<Cmp />
88+
89+
<InfiniteTable.Header />
90+
{/* <Cmp /> */}
91+
</InfiniteTable>
92+
</DataSource>
93+
</>
94+
);
95+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {
2+
InfiniteTableColumn,
3+
InfiniteTable,
4+
DataSource,
5+
InfiniteTablePropColumns,
6+
DataSourceData,
7+
} from '@infinite-table/infinite-react';
8+
import * as React from 'react';
9+
10+
type Developer = {
11+
id: number;
12+
firstName: string;
13+
lastName: string;
14+
country: string;
15+
city: string;
16+
currency: string;
17+
18+
email: string;
19+
preferredLanguage: string;
20+
stack: string;
21+
canDesign: 'yes' | 'no';
22+
hobby: string;
23+
salary: number;
24+
age: number;
25+
};
26+
27+
const columns: InfiniteTablePropColumns<Developer> = {
28+
preferredLanguage: {
29+
field: 'preferredLanguage',
30+
header: 'This is my preferred language',
31+
},
32+
salary: {
33+
field: 'salary',
34+
type: 'number',
35+
defaultGroupable: false,
36+
},
37+
age: { field: 'age' },
38+
canDesign: { field: 'canDesign', defaultGroupable: true },
39+
country: { field: 'country' },
40+
firstName: { field: 'firstName' },
41+
stack: { field: 'stack' },
42+
id: { field: 'id' },
43+
};
44+
45+
const dataSource: DataSourceData<Developer> = ({}) => {
46+
return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers10k-sql`)
47+
.then((r) => r.json())
48+
.then((data: Developer[]) => data);
49+
};
50+
51+
export default function App() {
52+
return (
53+
<DataSource<Developer> data={dataSource} primaryKey="id">
54+
<InfiniteTable<Developer>
55+
domProps={{
56+
style: {
57+
margin: '5px',
58+
height: '80vh',
59+
border: '1px solid gray',
60+
position: 'relative',
61+
},
62+
}}
63+
columns={columns}
64+
columnDefaultWidth={200}
65+
columnDefaultGroupable
66+
/>
67+
</DataSource>
68+
);
69+
}

source/src/components/DataSource/getDataSourceApi.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,16 @@ class DataSourceApiImpl<T> implements DataSourceApi<T> {
949949
this.actions.groupBy = groupBy;
950950
};
951951

952+
toggleGroupByField = (field: keyof T) => {
953+
const groupBy = this.getState().groupBy;
954+
const index = groupBy.findIndex((g) => g.field === field);
955+
if (index === -1) {
956+
this.actions.groupBy = [...groupBy, { field: field as any }];
957+
} else {
958+
this.actions.groupBy = groupBy.filter((g) => g.field !== field);
959+
}
960+
};
961+
952962
isRowDisabledAt = (rowIndex: number) => {
953963
const rowInfo = this.getRowInfoByIndex(rowIndex);
954964

source/src/components/DataSource/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,8 @@ export interface DataSourceApi<T> {
664664
areAllRowsDisabled: () => boolean;
665665

666666
setGroupBy: (groupBy: DataSourceState<T>['groupBy']) => void;
667+
668+
toggleGroupByField: (field: keyof T) => void;
667669
}
668670

669671
export type DataSourcePropRowInfoReducers<T> = Record<
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { style } from '@vanilla-extract/css';
2+
3+
import { ThemeVars } from '../../vars.css';
4+
import { recipe } from '@vanilla-extract/recipes';
5+
import { display } from '../../utilities.css';
6+
7+
export const GroupingToolbarBaseCls = style(
8+
[
9+
display.flex,
10+
{
11+
gap: ThemeVars.components.GroupingToolbar.gap,
12+
color: ThemeVars.components.GroupingToolbar.color,
13+
background: ThemeVars.components.GroupingToolbar.background,
14+
padding: ThemeVars.components.GroupingToolbar.padding,
15+
border: ThemeVars.components.GroupingToolbar.border,
16+
},
17+
],
18+
'GroupingToolbar',
19+
);
20+
21+
export const GroupingToolbarPlaceholderCls = style({
22+
opacity: 0.5,
23+
});
24+
25+
export const GroupingToolbarItemCls = style({
26+
display: 'flex',
27+
alignItems: 'center',
28+
border: `1px solid ${ThemeVars.color.accent}`,
29+
padding: ThemeVars.spacing[2],
30+
});
31+
32+
export const GroupingToolbarRecipe = recipe({
33+
base: [GroupingToolbarBaseCls],
34+
variants: {
35+
active: {
36+
true: {
37+
display: 'block',
38+
},
39+
false: {
40+
display: 'none',
41+
},
42+
},
43+
},
44+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import * as React from 'react';
2+
import {
3+
GroupingToolbarBaseCls,
4+
GroupingToolbarItemCls,
5+
GroupingToolbarPlaceholderCls,
6+
} from './index.css';
7+
import { useDataSourceState } from '../../../DataSource';
8+
import { useInfiniteTable } from '../../hooks/useInfiniteTable';
9+
import { ClearIcon } from '../icons/ClearIcon';
10+
11+
type GroupingToolbarProps = {};
12+
13+
export function GroupingToolbar<T = any>(_props: GroupingToolbarProps) {
14+
const { groupBy } = useDataSourceState<T>();
15+
16+
const { getComputed, dataSourceApi } = useInfiniteTable<T>();
17+
18+
const { fieldsToColumn } = getComputed();
19+
20+
const [over, setOver] = React.useState(false);
21+
22+
const placeholderMessage = over ? 'Drop to group' : 'Drag columns to group';
23+
24+
const children = !groupBy.length ? (
25+
<div className={GroupingToolbarPlaceholderCls}>{placeholderMessage}</div>
26+
) : (
27+
<>
28+
{groupBy.map((group) => {
29+
const column = group.field
30+
? fieldsToColumn.get(group.field)
31+
: undefined;
32+
33+
return (
34+
<div key={group.field} className={GroupingToolbarItemCls}>
35+
{column?.header && typeof column.header !== 'function'
36+
? column.header
37+
: column?.name || column?.id || ''}
38+
39+
<div
40+
tabIndex={-1}
41+
onClick={() => {
42+
dataSourceApi.setGroupBy(groupBy.filter((g) => g !== group));
43+
}}
44+
>
45+
<ClearIcon />
46+
</div>
47+
</div>
48+
);
49+
})}
50+
</>
51+
);
52+
53+
return (
54+
<div className={GroupingToolbarBaseCls}>
55+
<button onClick={() => setOver(!over)}>toggle over</button>
56+
{children}
57+
</div>
58+
);
59+
}

source/src/components/InfiniteTable/hooks/useComputed.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export function useComputed<T>(): InfiniteTableComputedValues<T> {
3333
editable,
3434
columnDefaultEditable,
3535
columnDefaultFilterable,
36+
columnDefaultGroupable,
3637
columnTypes,
3738
rowHeight,
3839
rowDetailHeight,
@@ -76,7 +77,7 @@ export function useComputed<T>(): InfiniteTableComputedValues<T> {
7677
showSeparatePivotColumnForSingleAggregation;
7778
}, [showSeparatePivotColumnForSingleAggregation]);
7879

79-
const { multiSort, filterValue, filterTypes } = dataSourceState;
80+
const { multiSort, filterValue, filterTypes, groupBy } = dataSourceState;
8081

8182
const { toggleGroupRow } = useColumnsWhen<T>();
8283

@@ -131,6 +132,7 @@ export function useComputed<T>(): InfiniteTableComputedValues<T> {
131132
sortInfo: dataSourceState.sortInfo ?? undefined,
132133
multiSort,
133134

135+
groupBy,
134136
columnOrder,
135137

136138
columnVisibility,
@@ -140,6 +142,7 @@ export function useComputed<T>(): InfiniteTableComputedValues<T> {
140142
editable,
141143
columnDefaultEditable,
142144
columnDefaultFilterable,
145+
columnDefaultGroupable,
143146
filterValue,
144147
filterTypes,
145148

source/src/components/InfiniteTable/hooks/useComputedColumns.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useMemo } from 'react';
22

33
import type {
44
DataSourcePropFilterValue,
5+
DataSourcePropGroupBy,
56
DataSourceProps,
67
DataSourceSingleSortInfo,
78
} from '../../DataSource/types';
@@ -47,9 +48,12 @@ type UseComputedColumnsParam<T> = {
4748
filterValue?: DataSourcePropFilterValue<T>;
4849
filterTypes?: DataSourceProps<T>['filterTypes'];
4950

51+
groupBy: DataSourcePropGroupBy<T>;
52+
5053
editable: InfiniteTablePropEditable<T>;
5154
columnDefaultEditable?: InfiniteTableProps<T>['columnDefaultEditable'];
5255
columnDefaultFilterable?: InfiniteTableProps<T>['columnDefaultFilterable'];
56+
columnDefaultGroupable?: InfiniteTableProps<T>['columnDefaultGroupable'];
5357
columnDefaultSortable?: InfiniteTableProps<T>['columnDefaultSortable'];
5458
columnPinning: InfiniteTablePropColumnPinning;
5559
columnSizing: InfiniteTablePropColumnSizing;
@@ -107,6 +111,7 @@ export const useComputedColumns = <T extends unknown>({
107111
editable,
108112
columnDefaultEditable,
109113
columnDefaultFilterable,
114+
columnDefaultGroupable,
110115
columnDefaultSortable,
111116
scrollbarWidth,
112117
columnTypes,
@@ -117,6 +122,7 @@ export const useComputedColumns = <T extends unknown>({
117122
columnVisibility,
118123
columnVisibilityAssumeVisible,
119124
columnSizing,
125+
groupBy,
120126
}: UseComputedColumnsParam<T>): UseComputedVisibleColumnsResult<T> => {
121127
// const columnsRenderId = useRerenderOnKeyChange(columns);
122128
const columnsRenderId = 1;
@@ -171,6 +177,7 @@ export const useComputedColumns = <T extends unknown>({
171177
columnPinning,
172178
columnDefaultEditable,
173179
columnDefaultFilterable,
180+
columnDefaultGroupable,
174181
columnDefaultSortable,
175182
editable,
176183

@@ -179,6 +186,8 @@ export const useComputedColumns = <T extends unknown>({
179186

180187
columnVisibility,
181188
columnVisibilityAssumeVisible: columnVisibilityAssumeVisible ?? true,
189+
190+
groupBy,
182191
});
183192
}, [
184193
columns,
@@ -214,7 +223,9 @@ export const useComputedColumns = <T extends unknown>({
214223
columnPinning,
215224
columnDefaultEditable,
216225
columnDefaultFilterable,
226+
columnDefaultGroupable,
217227
editable,
228+
groupBy,
218229

219230
columnsRenderId,
220231
]);

0 commit comments

Comments
 (0)