@rainbow-oh/yee-c 0.12.1 → 0.13.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/cjs/Form/style/index.less +4 -0
- package/dist/cjs/Input/style/index.less +1 -1
- package/dist/cjs/InputNumber/style/index.less +1 -1
- package/dist/cjs/Search/style/index.less +1 -1
- package/dist/cjs/Selector/style/index.less +1 -1
- package/dist/cjs/Table/demo/controlled-filter.d.ts +3 -0
- package/dist/cjs/Table/demo/controlled-filter.js +105 -0
- package/dist/cjs/Table/hooks/useFilter.d.ts +5 -1
- package/dist/cjs/Table/hooks/useFilter.js +80 -42
- package/dist/cjs/Table/index.d.ts +1 -1
- package/dist/cjs/Table/interface.d.ts +14 -4
- package/dist/cjs/Table/table.js +22 -25
- package/dist/cjs/Table/theader/cell.js +0 -3
- package/dist/cjs/Table/theader/filter.js +22 -5
- package/dist/cjs/TextArea/style/index.less +1 -1
- package/dist/cjs/VerificationCode/style/index.less +1 -1
- package/dist/cjs/style/color.blue.css +6 -7
- package/dist/cjs/style/color.crimson.css +8 -9
- package/dist/cjs/style/color.css +14 -11
- package/dist/cjs/style/color.dark.css +8 -9
- package/dist/cjs/style/color.gemini.css +8 -2
- package/dist/cjs/style/color.green.css +8 -9
- package/dist/cjs/style/color.jam.css +8 -9
- package/dist/cjs/style/color.navy.css +8 -9
- package/dist/cjs/style/color.peach.css +8 -9
- package/dist/cjs/style/color.pine.css +8 -9
- package/dist/cjs/style/color.pitaya.css +8 -9
- package/dist/cjs/style/color.pumpkin.css +8 -9
- package/dist/cjs/style/color.ruby.css +8 -9
- package/dist/cjs/style/themes.css +95 -100
- package/dist/cjs/style/variables.css +15 -12
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/Form/style/index.less +4 -0
- package/dist/esm/Input/style/index.less +1 -1
- package/dist/esm/InputNumber/style/index.less +1 -1
- package/dist/esm/Search/style/index.less +1 -1
- package/dist/esm/Selector/style/index.less +1 -1
- package/dist/esm/Table/demo/controlled-filter.d.ts +3 -0
- package/dist/esm/Table/demo/controlled-filter.js +122 -0
- package/dist/esm/Table/hooks/useFilter.d.ts +5 -1
- package/dist/esm/Table/hooks/useFilter.js +112 -69
- package/dist/esm/Table/index.d.ts +1 -1
- package/dist/esm/Table/interface.d.ts +14 -4
- package/dist/esm/Table/table.js +34 -21
- package/dist/esm/Table/theader/cell.js +1 -3
- package/dist/esm/Table/theader/filter.js +22 -1
- package/dist/esm/TextArea/style/index.less +1 -1
- package/dist/esm/VerificationCode/style/index.less +1 -1
- package/dist/esm/style/color.blue.css +6 -7
- package/dist/esm/style/color.crimson.css +8 -9
- package/dist/esm/style/color.css +14 -11
- package/dist/esm/style/color.dark.css +8 -9
- package/dist/esm/style/color.gemini.css +8 -2
- package/dist/esm/style/color.green.css +8 -9
- package/dist/esm/style/color.jam.css +8 -9
- package/dist/esm/style/color.navy.css +8 -9
- package/dist/esm/style/color.peach.css +8 -9
- package/dist/esm/style/color.pine.css +8 -9
- package/dist/esm/style/color.pitaya.css +8 -9
- package/dist/esm/style/color.pumpkin.css +8 -9
- package/dist/esm/style/color.ruby.css +8 -9
- package/dist/esm/style/themes.css +95 -100
- package/dist/esm/style/variables.css +15 -12
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/umd/yee-c.min.css +6 -8
- package/dist/umd/yee-c.min.js +1 -1
- package/package.json +1 -1
- package/src/Form/style/index.less +4 -0
- package/src/Input/style/index.less +1 -1
- package/src/InputNumber/style/index.less +1 -1
- package/src/Search/style/index.less +1 -1
- package/src/Selector/style/index.less +1 -1
- package/src/Table/demo/controlled-filter.tsx +107 -0
- package/src/Table/hooks/useFilter.ts +107 -49
- package/src/Table/index.en-US.md +22 -1
- package/src/Table/index.ts +1 -0
- package/src/Table/index.zh-CN.md +22 -1
- package/src/Table/interface.ts +14 -2
- package/src/Table/table-filter.test.tsx +343 -0
- package/src/Table/table.tsx +29 -27
- package/src/Table/theader/cell.tsx +0 -2
- package/src/Table/theader/filter.tsx +26 -1
- package/src/TextArea/style/index.less +1 -1
- package/src/VerificationCode/style/index.less +1 -1
- package/src/style/color.blue.css +6 -7
- package/src/style/color.crimson.css +8 -9
- package/src/style/color.css +14 -11
- package/src/style/color.dark.css +8 -9
- package/src/style/color.gemini.css +8 -2
- package/src/style/color.green.css +8 -9
- package/src/style/color.jam.css +8 -9
- package/src/style/color.navy.css +8 -9
- package/src/style/color.peach.css +8 -9
- package/src/style/color.pine.css +8 -9
- package/src/style/color.pitaya.css +8 -9
- package/src/style/color.pumpkin.css +8 -9
- package/src/style/color.ruby.css +8 -9
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
&&-error {
|
|
100
100
|
--yee-input-border-color: var(--yee-color-error);
|
|
101
101
|
--yee-input-hover-border-color: var(--yee-color-error-hover);
|
|
102
|
+
--yee-input-focus-border-color: var(--yee-color-error-hover);
|
|
102
103
|
--yee-input-focus-bs-color: var(--yee-color-error-border);
|
|
103
104
|
--yee-checkbox-border-color: var(--yee-color-error);
|
|
104
105
|
}
|
|
@@ -106,6 +107,7 @@
|
|
|
106
107
|
&&-warning {
|
|
107
108
|
--yee-input-border-color: var(--yee-color-warning);
|
|
108
109
|
--yee-input-hover-border-color: var(--yee-color-warning-hover);
|
|
110
|
+
--yee-input-focus-border-color: var(--yee-color-warning-hover);
|
|
109
111
|
--yee-input-focus-bs-color: var(--yee-color-warning-border);
|
|
110
112
|
--yee-checkbox-border-color: var(--yee-color-warning);
|
|
111
113
|
}
|
|
@@ -113,6 +115,7 @@
|
|
|
113
115
|
&&-success {
|
|
114
116
|
--yee-input-border-color: var(--yee-color-success);
|
|
115
117
|
--yee-input-hover-border-color: var(--yee-color-success-hover);
|
|
118
|
+
--yee-input-focus-border-color: var(--yee-color-success-hover);
|
|
116
119
|
--yee-input-focus-bs-color: var(--yee-color-success-border);
|
|
117
120
|
--yee-checkbox-border-color: var(--yee-color-success);
|
|
118
121
|
}
|
|
@@ -120,6 +123,7 @@
|
|
|
120
123
|
&&-info {
|
|
121
124
|
--yee-input-border-color: var(--yee-color-info);
|
|
122
125
|
--yee-input-hover-border-color: var(--yee-color-info-hover);
|
|
126
|
+
--yee-input-focus-border-color: var(--yee-color-info-hover);
|
|
123
127
|
--yee-input-focus-bs-color: var(--yee-color-info-border);
|
|
124
128
|
--yee-checkbox-border-color: var(--yee-color-info);
|
|
125
129
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Button, message, Space, Table } from '@rainbow-oh/yee-c';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import type { ColumnProps } from '@rainbow-oh/yee-c';
|
|
5
|
+
|
|
6
|
+
interface RecordType {
|
|
7
|
+
key: string;
|
|
8
|
+
name: string;
|
|
9
|
+
age: number;
|
|
10
|
+
gender: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const allData: RecordType[] = [
|
|
14
|
+
{ key: '1', name: 'John Brown', age: 32, gender: 'male' },
|
|
15
|
+
{ key: '2', name: 'Jim Green', age: 42, gender: 'male' },
|
|
16
|
+
{ key: '3', name: 'Joe Black', age: 32, gender: 'male' },
|
|
17
|
+
{ key: '4', name: 'Jon Snow', age: 42, gender: 'female' },
|
|
18
|
+
{ key: '5', name: 'Arya Stark', age: 16, gender: 'female' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export default () => {
|
|
22
|
+
const { messageApi, messageHolder } = message.useMessage();
|
|
23
|
+
|
|
24
|
+
// Server-side search: keep the keyword controlled, refetch in onChange.
|
|
25
|
+
const [nameKeyword, setNameKeyword] = useState<string>('');
|
|
26
|
+
|
|
27
|
+
// Controlled local filter: the tree selection lives in the parent state.
|
|
28
|
+
const [genders, setGenders] = useState<string[]>([]);
|
|
29
|
+
|
|
30
|
+
const columns: ColumnProps[] = [
|
|
31
|
+
{
|
|
32
|
+
title: 'Name',
|
|
33
|
+
dataIndex: 'name',
|
|
34
|
+
key: 'name',
|
|
35
|
+
filter: {
|
|
36
|
+
searchable: true,
|
|
37
|
+
// No onFilter: no local matching, the search hits the "server".
|
|
38
|
+
filteredValue: nameKeyword,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
title: 'Age',
|
|
43
|
+
dataIndex: 'age',
|
|
44
|
+
key: 'age',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
title: 'Gender',
|
|
48
|
+
dataIndex: 'gender',
|
|
49
|
+
key: 'gender',
|
|
50
|
+
filter: {
|
|
51
|
+
filterMode: 'tree',
|
|
52
|
+
items: [
|
|
53
|
+
{ label: 'Male', key: 'male' },
|
|
54
|
+
{ label: 'Female', key: 'female' },
|
|
55
|
+
],
|
|
56
|
+
filteredValue: genders,
|
|
57
|
+
onFilter: (value, record) => value === record.gender,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const handleChange: React.ComponentProps<typeof Table>['onChange'] = (
|
|
63
|
+
info,
|
|
64
|
+
) => {
|
|
65
|
+
if (info.action !== 'filter') return;
|
|
66
|
+
|
|
67
|
+
const filters = (info.filters ?? {}) as {
|
|
68
|
+
name?: string;
|
|
69
|
+
gender?: string[];
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// The committing column's key is always present in the payload — with its
|
|
73
|
+
// empty value on clear — so both setting and clearing are mirrored here.
|
|
74
|
+
const name = filters.name ?? '';
|
|
75
|
+
if (name !== nameKeyword) {
|
|
76
|
+
// Pretend to refetch with ?name=<keyword>.
|
|
77
|
+
messageApi.info(`Fetch: /api/users?name=${name}`);
|
|
78
|
+
setNameKeyword(name);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setGenders(filters.gender ?? []);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const data = allData.filter((item) => {
|
|
85
|
+
const matchName = nameKeyword
|
|
86
|
+
? item.name.toLowerCase().includes(nameKeyword.toLowerCase())
|
|
87
|
+
: true;
|
|
88
|
+
const matchGender = genders.length ? genders.includes(item.gender) : true;
|
|
89
|
+
return matchName && matchGender;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<>
|
|
94
|
+
{messageHolder}
|
|
95
|
+
<Space block style={{ marginBottom: 16 }}>
|
|
96
|
+
<Button onClick={() => setNameKeyword('')}>Reset name search</Button>
|
|
97
|
+
<Button onClick={() => setGenders([])}>Reset gender filter</Button>
|
|
98
|
+
</Space>
|
|
99
|
+
<Table
|
|
100
|
+
columns={columns}
|
|
101
|
+
dataSource={data}
|
|
102
|
+
onChange={handleChange}
|
|
103
|
+
pagination={false}
|
|
104
|
+
/>
|
|
105
|
+
</>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import type { ColumnProps, FilterValue } from '../interface';
|
|
3
3
|
|
|
4
4
|
type FilterInput = FilterValue | FilterValue[];
|
|
@@ -16,9 +16,81 @@ type FilterRecord = {
|
|
|
16
16
|
const isEmptyFilterValue = (value: FilterInput) =>
|
|
17
17
|
value === '' || (Array.isArray(value) && value.length === 0);
|
|
18
18
|
|
|
19
|
+
export const isControlledFilter = (column?: ColumnProps) =>
|
|
20
|
+
column?.filter?.filteredValue !== undefined;
|
|
21
|
+
|
|
19
22
|
const normalizeValue = (value: unknown) =>
|
|
20
23
|
value === null || value === undefined ? '' : String(value).toLowerCase();
|
|
21
24
|
|
|
25
|
+
// Controlled columns (`filteredValue` defined) always take their value from
|
|
26
|
+
// the prop; internal records for them are ignored entirely — a commit on a
|
|
27
|
+
// controlled column must not leak into the merged state while the parent has
|
|
28
|
+
// not (yet) applied the new prop value.
|
|
29
|
+
const mergeRecords = (
|
|
30
|
+
internalRecords: Array<FilterRecord>,
|
|
31
|
+
columnMap: Map<string, ColumnProps>,
|
|
32
|
+
): Array<FilterRecord> => {
|
|
33
|
+
const controlled: Array<FilterRecord> = [];
|
|
34
|
+
columnMap.forEach((column, dataIndex) => {
|
|
35
|
+
if (!isControlledFilter(column)) return;
|
|
36
|
+
const value = column.filter?.filteredValue as FilterInput;
|
|
37
|
+
if (!isEmptyFilterValue(value)) {
|
|
38
|
+
controlled.push({ dataIndex, value });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
const merged = internalRecords.filter(
|
|
42
|
+
(record) => !isControlledFilter(columnMap.get(record.dataIndex)),
|
|
43
|
+
);
|
|
44
|
+
if (!controlled.length && merged.length === internalRecords.length) {
|
|
45
|
+
return internalRecords;
|
|
46
|
+
}
|
|
47
|
+
return [...merged, ...controlled];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const applyFilters = (
|
|
51
|
+
data: Array<Record<string, unknown>>,
|
|
52
|
+
records: Array<FilterRecord>,
|
|
53
|
+
columnMap: Map<string, ColumnProps>,
|
|
54
|
+
) => {
|
|
55
|
+
const handled = records.filter(
|
|
56
|
+
({ dataIndex, value }) =>
|
|
57
|
+
Boolean(dataIndex) &&
|
|
58
|
+
columnMap.has(dataIndex) &&
|
|
59
|
+
!isEmptyFilterValue(value),
|
|
60
|
+
);
|
|
61
|
+
if (!handled.length) {
|
|
62
|
+
return data;
|
|
63
|
+
}
|
|
64
|
+
return data.filter((item) => {
|
|
65
|
+
return handled.every(({ dataIndex, value }) => {
|
|
66
|
+
const column = columnMap.get(dataIndex);
|
|
67
|
+
|
|
68
|
+
// Controlled column without `onFilter` is server-side: the data the
|
|
69
|
+
// consumer renders is already filtered upstream, pass it through.
|
|
70
|
+
if (isControlledFilter(column) && !column?.filter?.onFilter) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const customFilter = column?.filter?.onFilter;
|
|
75
|
+
if (customFilter) {
|
|
76
|
+
return Array.isArray(value)
|
|
77
|
+
? value.some((filterValue) => customFilter(filterValue, item))
|
|
78
|
+
: customFilter(value, item);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const targetValue = normalizeValue(item[dataIndex]);
|
|
82
|
+
|
|
83
|
+
if (Array.isArray(value)) {
|
|
84
|
+
return value.some(
|
|
85
|
+
(filterValue) => targetValue === normalizeValue(filterValue),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return targetValue.includes(normalizeValue(value));
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
22
94
|
export default function useFilter({
|
|
23
95
|
data,
|
|
24
96
|
columns,
|
|
@@ -26,7 +98,9 @@ export default function useFilter({
|
|
|
26
98
|
data: Array<Record<string, unknown>>;
|
|
27
99
|
columns: ColumnProps[];
|
|
28
100
|
}) {
|
|
29
|
-
const [
|
|
101
|
+
const [internalRecords, setInternalRecords] = useState<Array<FilterRecord>>(
|
|
102
|
+
[],
|
|
103
|
+
);
|
|
30
104
|
const columnMap = useMemo(
|
|
31
105
|
() =>
|
|
32
106
|
new Map(
|
|
@@ -38,64 +112,48 @@ export default function useFilter({
|
|
|
38
112
|
);
|
|
39
113
|
|
|
40
114
|
useEffect(() => {
|
|
41
|
-
|
|
115
|
+
setInternalRecords((current) => {
|
|
42
116
|
const next = current.filter(({ dataIndex }) => columnMap.has(dataIndex));
|
|
43
117
|
return next.length === current.length ? current : next;
|
|
44
118
|
});
|
|
45
119
|
}, [columnMap]);
|
|
46
120
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
121
|
+
const activeRecords = useMemo(
|
|
122
|
+
() => mergeRecords(internalRecords, columnMap),
|
|
123
|
+
[internalRecords, columnMap],
|
|
124
|
+
);
|
|
52
125
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
126
|
+
const filtered = useMemo(
|
|
127
|
+
() => applyFilters(data, activeRecords, columnMap),
|
|
128
|
+
[columnMap, data, activeRecords],
|
|
129
|
+
);
|
|
57
130
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
})
|
|
62
|
-
|
|
131
|
+
// Mirrors useSorter.onSort: computes the next state synchronously and
|
|
132
|
+
// returns it, so callers can emit onChange with the result in the same tick.
|
|
133
|
+
const onFilter = useCallback(
|
|
134
|
+
({ value, dataIndex }: FilterOptions) => {
|
|
135
|
+
const nextInternal = isEmptyFilterValue(value)
|
|
136
|
+
? internalRecords.filter((item) => item.dataIndex !== dataIndex)
|
|
137
|
+
: internalRecords.some((item) => item.dataIndex === dataIndex)
|
|
138
|
+
? internalRecords.map((item) =>
|
|
139
|
+
item.dataIndex === dataIndex ? { ...item, value } : item,
|
|
140
|
+
)
|
|
141
|
+
: [...internalRecords, { dataIndex, value }];
|
|
63
142
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
(
|
|
67
|
-
|
|
68
|
-
columnMap
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return data.filter((item) => {
|
|
75
|
-
return handled.every(({ dataIndex, value }) => {
|
|
76
|
-
const customFilter = columnMap.get(dataIndex)?.filter?.onFilter;
|
|
77
|
-
if (customFilter) {
|
|
78
|
-
return Array.isArray(value)
|
|
79
|
-
? value.some((filterValue) => customFilter(filterValue, item))
|
|
80
|
-
: customFilter(value, item);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const targetValue = normalizeValue(item[dataIndex]);
|
|
84
|
-
|
|
85
|
-
if (Array.isArray(value)) {
|
|
86
|
-
return value.some(
|
|
87
|
-
(filterValue) => targetValue === normalizeValue(filterValue),
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return targetValue.includes(normalizeValue(value));
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
}, [columnMap, data, filterRecords]);
|
|
143
|
+
setInternalRecords(nextInternal);
|
|
144
|
+
|
|
145
|
+
const nextActive = mergeRecords(nextInternal, columnMap);
|
|
146
|
+
return {
|
|
147
|
+
data: applyFilters(data, nextActive, columnMap),
|
|
148
|
+
filterRecords: nextActive,
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
[internalRecords, columnMap, data],
|
|
152
|
+
);
|
|
95
153
|
|
|
96
154
|
return {
|
|
97
155
|
data: filtered,
|
|
98
|
-
filterRecords,
|
|
156
|
+
filterRecords: activeRecords,
|
|
99
157
|
onFilter,
|
|
100
158
|
};
|
|
101
159
|
}
|
package/src/Table/index.en-US.md
CHANGED
|
@@ -22,6 +22,7 @@ A table displays rows of data.
|
|
|
22
22
|
<code src="./demo/expand.tsx" title="Expandable" description="Expandable rows"></code>
|
|
23
23
|
<code src="./demo/pagination.tsx" title="Pagination" description="Table with pagination"></code>
|
|
24
24
|
<code src="./demo/column-filter.tsx" title="column filter" description="Table column filter"></code>
|
|
25
|
+
<code src="./demo/controlled-filter.tsx" title="controlled filter" description="Controlled filtering with filteredValue: server-side search and programmatic reset"></code>
|
|
25
26
|
<code src="./demo/grouping.tsx" title="Grouping" description="Multi-level header"></code>
|
|
26
27
|
<code src="./demo/fixed.tsx" title="Fixed Columns" description="Fix columns on both sides and scroll horizontally to see the effect"></code>
|
|
27
28
|
<code src="./demo/resizable.tsx" title="Resizable Columns" description="Drag the right edge of a header cell to resize it; arrow keys work too"></code>
|
|
@@ -77,7 +78,7 @@ A table displays rows of data.
|
|
|
77
78
|
| styles | `Partial<Record<ColumnSemanticType, React.CSSProperties>>` | Column semantic styles | - |
|
|
78
79
|
| align | `'left' \| 'right' \| 'center'` | Alignment | - |
|
|
79
80
|
| fixed | `'left' \| 'right' \| true` | Fixed column | - |
|
|
80
|
-
| filter | `object` | Filter configuration | - |
|
|
81
|
+
| filter | `object` | Filter configuration, see [ColumnFilter](#columnfilter) below | - |
|
|
81
82
|
| sorter | `boolean \| object` | Sorter configuration | - |
|
|
82
83
|
| dataIndex | `string` | Data index | - |
|
|
83
84
|
| key | `string \| number` | Unique key | - |
|
|
@@ -92,6 +93,26 @@ A table displays rows of data.
|
|
|
92
93
|
| onHeaderCell | `(column: ColumnProps) => object` | Header cell props | - |
|
|
93
94
|
| render | `(record: object, rowIndex: number) => React.ReactNode` | Custom render | - |
|
|
94
95
|
|
|
96
|
+
### ColumnFilter
|
|
97
|
+
|
|
98
|
+
Filter configuration of `ColumnProps.filter`.
|
|
99
|
+
|
|
100
|
+
| Property | Type | Description | Default |
|
|
101
|
+
| --- | --- | --- | --- |
|
|
102
|
+
| filterMode | `'tree' \| 'menu'` | Filter menu mode (effective when `items` is set) | `menu` |
|
|
103
|
+
| items | `Array<Record<string, any>>` | Filter menu items; renders a checkable tree panel when set | - |
|
|
104
|
+
| searchable | `boolean` | Whether searchable; renders a search input when `items` is unset | `true` |
|
|
105
|
+
| filterOnClose | `boolean` | Whether to apply the filter when the panel closes | `true` |
|
|
106
|
+
| icon | `(filtered: boolean) => React.ReactNode` | Custom filter icon | - |
|
|
107
|
+
| render | `() => React.ReactNode` | Custom filter panel content | - |
|
|
108
|
+
| onFilter | `(value: FilterValue, record: object) => boolean` | Local filter function; falls back to default string-includes matching when unset | - |
|
|
109
|
+
| filtered | `boolean` | Force the filter icon highlight (takes precedence over `filteredValue`) | - |
|
|
110
|
+
| filteredValue | `FilterValue \| FilterValue[]` | (Controlled) Filter value. When set, the column's filter state is fully controlled: panel echo, icon highlight and the `filters` in `onChange` all derive from it; reset with `''` / `[]` | - |
|
|
111
|
+
|
|
112
|
+
> **Controlled + server-side filtering**: with `filteredValue` set and no `onFilter`, no local matching happens at all — whether the data is filtered is decided by refetching in your `onChange` handler (see the controlled filter demo below).
|
|
113
|
+
>
|
|
114
|
+
> When `action === 'filter'`, the key of the column that was just committed is always present in `filters` (with `''` / `[]` on clear), so parents can mirror filter state from the payload alone.
|
|
115
|
+
|
|
95
116
|
### RowSelectionType
|
|
96
117
|
|
|
97
118
|
| Property | Type | Description | Default |
|
package/src/Table/index.ts
CHANGED
package/src/Table/index.zh-CN.md
CHANGED
|
@@ -29,6 +29,7 @@ toc: 'content'
|
|
|
29
29
|
<code src="./demo/summary.tsx" title="统计" description="表格的统计行"></code>
|
|
30
30
|
<code src="./demo/filter.tsx" title="筛选" description="表格的筛选功能"></code>
|
|
31
31
|
<code src="./demo/column-filter.tsx" title="列筛选" description="表格的列筛选功能"></code>
|
|
32
|
+
<code src="./demo/controlled-filter.tsx" title="受控筛选" description="filteredValue 受控筛选,支持服务端搜索与程序化重置"></code>
|
|
32
33
|
<code src="./demo/components-override.tsx" title="自定义表格元素" description="可被外部(如 dnd-kit)接管的 tbody / 行渲染"></code>
|
|
33
34
|
|
|
34
35
|
## API
|
|
@@ -82,7 +83,7 @@ toc: 'content'
|
|
|
82
83
|
| styles | `Partial<Record<ColumnSemanticType, React.CSSProperties>>` | 列结构化样式 | - |
|
|
83
84
|
| align | `'left' \| 'right' \| 'center'` | 对齐方式 | - |
|
|
84
85
|
| fixed | `'left' \| 'right' \| true` | 固定列 | - |
|
|
85
|
-
| filter | `object` |
|
|
86
|
+
| filter | `object` | 过滤配置,见下方 [ColumnFilter](#columnfilter) | - |
|
|
86
87
|
| sorter | `boolean \| object` | 排序配置 | - |
|
|
87
88
|
| dataIndex | `string` | 行数据索引 | - |
|
|
88
89
|
| key | `string \| number` | 唯一key | - |
|
|
@@ -97,6 +98,26 @@ toc: 'content'
|
|
|
97
98
|
| onHeaderCell | `(column: ColumnProps) => object` | 设置表头单元格属性 | - |
|
|
98
99
|
| render | `(record: Record<string, any>, rowIndex: number) => React.ReactNode` | 自定义渲染函数 | - |
|
|
99
100
|
|
|
101
|
+
### ColumnFilter
|
|
102
|
+
|
|
103
|
+
`ColumnProps.filter` 的过滤配置。
|
|
104
|
+
|
|
105
|
+
| 属性名 | 类型 | 描述 | 默认值 |
|
|
106
|
+
| ------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------ |
|
|
107
|
+
| filterMode | `'tree' \| 'menu'` | 筛选菜单模式(配置 `items` 时生效) | `menu` |
|
|
108
|
+
| items | `Array<Record<string, any>>` | 筛选菜单项,配置后渲染树形多选面板 | - |
|
|
109
|
+
| searchable | `boolean` | 是否可搜索;未配置 `items` 时渲染搜索输入框 | `true` |
|
|
110
|
+
| filterOnClose | `boolean` | 关闭筛选面板时是否自动应用筛选 | `true` |
|
|
111
|
+
| icon | `(filtered: boolean) => React.ReactNode` | 自定义筛选图标 | - |
|
|
112
|
+
| render | `() => React.ReactNode` | 自定义筛选面板内容 | - |
|
|
113
|
+
| onFilter | `(value: FilterValue, record: object) => boolean` | 本地过滤函数;不配置时使用默认的字符串包含匹配 | - |
|
|
114
|
+
| filtered | `boolean` | 强制筛选图标高亮(优先级高于由 `filteredValue` 派生的高亮) | - |
|
|
115
|
+
| filteredValue | `FilterValue \| FilterValue[]` | (受控)筛选值。配置后该列筛选状态完全受控:面板回显、图标高亮、`onChange` 的 `filters` 均以其为准;重置传 `''` / `[]` | - |
|
|
116
|
+
|
|
117
|
+
> **受控 + 服务端过滤**:列配置 `filteredValue` 且不配置 `onFilter` 时,不做任何本地过滤,数据是否过滤由你在 `onChange` 里重新请求决定,适合后台搜索场景(见下方「受控筛选」示例)。
|
|
118
|
+
>
|
|
119
|
+
> `action === 'filter'` 时,本次操作的列的 key 必在 `filters` 中(清空时值为 `''` / `[]`),父组件可直接据此镜像筛选状态。
|
|
120
|
+
|
|
100
121
|
### RowSelectionType
|
|
101
122
|
|
|
102
123
|
| 属性名 | 类型 | 描述 | 默认值 |
|
package/src/Table/interface.ts
CHANGED
|
@@ -68,9 +68,13 @@ export interface HeadCellProps extends Omit<WrapedColumnProps, 'title'> {
|
|
|
68
68
|
tableActionBarShowOnHover?: boolean;
|
|
69
69
|
sorters?: { [prop: string]: number };
|
|
70
70
|
showSorterTooltip?: boolean;
|
|
71
|
-
internalFilters?: { [dataIndex: string]: boolean }; // Whether this column is filtered
|
|
72
71
|
onSort?: (dataIndex: string, sorter: any) => void;
|
|
73
|
-
onInternalFilter?: (
|
|
72
|
+
onInternalFilter?: (options: {
|
|
73
|
+
dataIndex: string;
|
|
74
|
+
value: FilterValue | FilterValue[];
|
|
75
|
+
type?: 'filter' | 'search';
|
|
76
|
+
column: WrapedColumnProps;
|
|
77
|
+
}) => void;
|
|
74
78
|
/**
|
|
75
79
|
* `resizeKey` of the column currently being dragged, if any
|
|
76
80
|
*/
|
|
@@ -215,6 +219,14 @@ export interface ColumnProps {
|
|
|
215
219
|
* Whether searchable, when items is undefined, it acts as a search component
|
|
216
220
|
*/
|
|
217
221
|
searchable?: boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Controlled filter state. When defined, this column's filter state is
|
|
224
|
+
* controlled: the dropdown echo, icon highlight and the `filters` in
|
|
225
|
+
* onChange all derive from it. With no `onFilter`, local matching is
|
|
226
|
+
* skipped entirely (server mode — filter in your onChange handler).
|
|
227
|
+
* Reset with `[]` / `''`.
|
|
228
|
+
*/
|
|
229
|
+
filteredValue?: FilterValue | FilterValue[];
|
|
218
230
|
/**
|
|
219
231
|
* Filter icon
|
|
220
232
|
*/
|