From 4259783a31854ddce1d86ab87e408db9f6365ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=A4=E6=9C=88?= Date: Mon, 27 May 2024 16:46:02 +0800 Subject: [PATCH] Refactor table api and type (#223) --- .changeset/real-carpets-occur.md | 5 + docs/docs/en/components/Table.mdx | 31 ++---- docs/docs/zh/components/Table.mdx | 31 ++---- .../src/Table/DataTable/BaseTable.tsx | 3 +- .../components/src/Table/DataTable/Table.tsx | 71 ++++--------- .../src/Table/DataTable/TableHead.tsx | 68 ++++++++----- .../DataTable/features/InitFilterFeature.ts | 12 +-- .../components/src/Table/DataTable/index.ts | 1 + .../src/Table/DataTable/interfaces.ts | 74 ++++++++++---- .../src/Table/DataTable/utils/index.tsx | 20 ++-- packages/components/src/Table/Table.story.tsx | 99 ++++++++++++------- 11 files changed, 218 insertions(+), 197 deletions(-) create mode 100644 .changeset/real-carpets-occur.md diff --git a/.changeset/real-carpets-occur.md b/.changeset/real-carpets-occur.md new file mode 100644 index 00000000..0176806a --- /dev/null +++ b/.changeset/real-carpets-occur.md @@ -0,0 +1,5 @@ +--- +'@kubed/components': patch +--- + +Refactor table api and type diff --git a/docs/docs/en/components/Table.mdx b/docs/docs/en/components/Table.mdx index 1ab43231..a34537c4 100644 --- a/docs/docs/en/components/Table.mdx +++ b/docs/docs/en/components/Table.mdx @@ -151,10 +151,12 @@ Customize column configuration by setting `column.meta` ```ts interface ColumnMeta { description?: Record; // icon ? tooltip props - filterOptions?: { key: string; label: React.ReactNode }[]; // filter options + // filterOptions?: { key: string; label: React.ReactNode }[]; // filter options selectType?: 'single' | 'multiple'; // multiple selection type sortable?: boolean; // whether it can be sorted searchKey?: string; // custom search key + th?: Partial; // baseTable th props, priority is higher than getProps.th + td?: Partial; //baseTable td props, priority is higher than getProps.td } ``` @@ -401,16 +403,6 @@ export const App = () => { footer: (props) => props.column.id, meta: { sortable: true, - filterOptions: [ - { - key: '0', - label: '0', - }, - { - key: '1', - label: '1', - }, - ], }, }, { @@ -427,16 +419,6 @@ export const App = () => { footer: (props) => props.column.id, meta: { searchKey: 'status1', - filterOptions: [ - { - key: 'status-0', - label: 'status-0', - }, - { - key: 'status-1', - label: 'status-1', - }, - ], }, }, { @@ -611,7 +593,12 @@ export const App = () => { }, }); - return ; + return ( + <> +
+
{JSON.stringify(state, null, 2)}
+ + ); }; ``` diff --git a/docs/docs/zh/components/Table.mdx b/docs/docs/zh/components/Table.mdx index 954e01c8..f311a3fd 100644 --- a/docs/docs/zh/components/Table.mdx +++ b/docs/docs/zh/components/Table.mdx @@ -151,10 +151,12 @@ interface StateHandler { ```ts interface ColumnMeta { description?: Record; // icon ? tooltip props - filterOptions?: { key: string; label: React.ReactNode }[]; // 过滤选项 + // filterOptions?: { key: string; label: React.ReactNode }[]; // 过滤选项 selectType?: 'single' | 'multiple'; // 多选类型 sortable?: boolean; // 是否可排序 searchKey?: string; // 自定义搜索 key + th?: Partial; // baseTable th props,优先级高于 getProps.th + td?: Partial; //baseTable td props,优先级高于 getProps.td } ``` @@ -400,16 +402,6 @@ export const App = () => { footer: (props) => props.column.id, meta: { sortable: true, - filterOptions: [ - { - key: '0', - label: '0', - }, - { - key: '1', - label: '1', - }, - ], }, }, { @@ -426,16 +418,6 @@ export const App = () => { footer: (props) => props.column.id, meta: { searchKey: 'status1', - filterOptions: [ - { - key: 'status-0', - label: 'status-0', - }, - { - key: 'status-1', - label: 'status-1', - }, - ], }, }, { @@ -610,7 +592,12 @@ export const App = () => { }, }); - return
; + return ( + <> +
+
{JSON.stringify(state, null, 2)}
+ + ); }; ``` diff --git a/packages/components/src/Table/DataTable/BaseTable.tsx b/packages/components/src/Table/DataTable/BaseTable.tsx index cdef284e..803183bb 100644 --- a/packages/components/src/Table/DataTable/BaseTable.tsx +++ b/packages/components/src/Table/DataTable/BaseTable.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import styled from 'styled-components'; import { LoadingOverlay } from '../../LoadingOverlay/LoadingOverlay'; import * as BaseTable from '../BaseTable'; -import TableHead from './TableHead'; +import { TableHead } from './TableHead'; import { getDefaultTrProps } from './utils'; interface BaseDataTableProps { @@ -57,6 +57,7 @@ export function BaseDataTable({ table }: BaseDataTableProps) { {flexRender(cell.column.columnDef.cell, cell.getContext())} diff --git a/packages/components/src/Table/DataTable/Table.tsx b/packages/components/src/Table/DataTable/Table.tsx index aec89220..8d81f49c 100644 --- a/packages/components/src/Table/DataTable/Table.tsx +++ b/packages/components/src/Table/DataTable/Table.tsx @@ -1,66 +1,35 @@ -import * as React from 'react'; +import type { Header, Row, RowData, Table, TableState } from '@tanstack/react-table'; import cx from 'classnames'; -import type { - Table, - RowData, - Header, - TableState, - Row, - TableOptions as TableOptionsBase, - TableOptionsResolved, -} from '@tanstack/react-table'; -import { Pagination } from './Pagination'; +import * as React from 'react'; import * as BaseTable from '../BaseTable'; -import { Toolbar } from './Toolbar'; import { BaseDataTable } from './BaseTable'; - -export interface TableEnableConfig { - enableToolbar?: boolean; - enablePagination?: boolean; - enableVisible?: boolean; - enableFilters?: boolean; - enableParamsToUrl?: boolean; - enableStateToStorage?: boolean; - enableSelection?: boolean; - enableSort?: boolean; - enableMultiSelection?: boolean; - enableInitParamsByUrl?: boolean; -} - -export interface StorageStateOptions { - storage2State?: (storageKey: string) => Partial; - state2Storage?: (storageKey: string, state: Partial) => void; -} -export interface StateHandler { - handlerName?: string; - stateKeys: (keyof TableState)[] | '*'; - callback?: (state: Partial) => void; -} - -export interface FeaturesHandlersOPtions { - _featuresHandlers?: StateHandler[]; - _featuresInitState?: Partial; -} - -export interface DefaultOptionsResolved { - getDefaultOptionsResolved?: (options: TableOptionsResolved) => TableOptionsResolved; -} +import { Pagination } from './Pagination'; +import { Toolbar } from './Toolbar'; +import { + DataTableRootProps, + DefaultOptionsResolved, + FeaturesHandlersOptions, + StateHandler, + StorageStateOptions, +} from './interfaces'; declare module '@tanstack/react-table' { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface TableOptionsResolved extends StorageStateOptions, - FeaturesHandlersOPtions { + FeaturesHandlersOptions { loading?: boolean; } // eslint-disable-next-line @typescript-eslint/no-unused-vars interface ColumnMeta { description?: Record; - filterOptions?: { key: string; label: React.ReactNode }[]; + // filterOptions?: { key: string; label: React.ReactNode }[]; selectType?: 'single' | 'multiple'; sortable?: boolean; searchKey?: string; + th?: Omit; + td?: Omit; } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -74,6 +43,9 @@ declare module '@tanstack/react-table' { tableName: string; refetch?: () => void; storageStateKeys?: (keyof TableState)[] | '*'; + _defaultConfig?: { + selectColumnId?: string; + }; manual?: boolean; enable?: { pagination?: boolean; @@ -112,13 +84,6 @@ declare module '@tanstack/react-table' { } } -export interface TableInstance extends Table {} -export interface TableOptions extends TableOptionsBase {} -export interface DataTableRootProps { - className?: string; - table: TableInstance; -} - export function DataTable({ className, table }: DataTableRootProps) { const { options: { meta: { enable: { pagination, toolbar } = {} } = {} } = {} } = table; return ( diff --git a/packages/components/src/Table/DataTable/TableHead.tsx b/packages/components/src/Table/DataTable/TableHead.tsx index ae7bdfde..c63de758 100644 --- a/packages/components/src/Table/DataTable/TableHead.tsx +++ b/packages/components/src/Table/DataTable/TableHead.tsx @@ -7,10 +7,8 @@ import { BaseTable, Dropdown, Menu, MenuItem, MenuLabel, Popover } from '../../i import { getDefaultThProps } from './utils'; import { useLocales } from '../../ConfigProvider/LocaleProvider/LocaleContext'; -const TWrapper = styled.div` - display: inline-flex; - align-items: center; - gap: 4px; +const TWrapper = styled.div<{ center?: boolean }>` + ${({ center }) => center && 'display: flex; align-items: center; justify-content: center;'} .sort-indicator { color: #79879c; @@ -41,18 +39,21 @@ const DropdownWrapper = styled.span` justify-content: center; `; -export interface TableHeadProps { +interface TableHeadProps { header: Header; table: Table; } -function TableHead({ header, table }: PropsWithChildren>) { +export type { TableHeadProps }; + +export function TableHead({ header, table }: PropsWithChildren>) { const { description, // filterOptions = [], searchKey: _searchKey, selectType, sortable: _sortable, + th = {}, } = header.column.columnDef.meta ?? {}; const { locales } = useLocales(); @@ -70,6 +71,17 @@ function TableHead({ header, table }: PropsWithChildren>) { const searchKey = manualFiltering ? _searchKey ?? id : id; + const isCheckbox = selectType + ? selectType === 'multiple' + : table.options.enableMultiRowSelection && + id === table.options.meta._defaultConfig?.selectColumnId; + + const isRadio = selectType + ? selectType === 'single' + : table.options.enableRowSelection && + !table.options.enableMultiRowSelection && + id === table.options.meta._defaultConfig?.selectColumnId; + const suggestions = getFiltersProps?.(table)?.suggestions ?? []; const filterOptions = suggestions.find((suggestion) => suggestion.key === searchKey)?.options ?? []; @@ -106,7 +118,20 @@ function TableHead({ header, table }: PropsWithChildren>) { }; const handleFilter = (value: any) => { - header.column.setFilterValue(value); + if (searchKey === id) { + header.column.setFilterValue(value); + } else { + table.setColumnFilters((rows) => { + const index = rows.findIndex((row) => row.id === searchKey); + return [ + ...rows.slice(index, 1), + { + id: searchKey, + value, + }, + ]; + }); + } }; const renderDropdown = () => { @@ -162,7 +187,7 @@ function TableHead({ header, table }: PropsWithChildren>) { ); } - if (header.column.id === '_selector' && selectType === 'single') return null; + if (isRadio) return null; return flexRender(header.column.columnDef.header, header.getContext()); }; @@ -171,23 +196,18 @@ function TableHead({ header, table }: PropsWithChildren>) { colSpan={header.colSpan} {...(enableTh && getDefaultThProps(table))} {...(getThProps && getThProps(table, header))} + {...th} > -
- {header.isPlaceholder ? null : ( - <> - - {renderDropdown()} - {description && ( - )}> - - - )} - - - )} -
+ {header.isPlaceholder ? null : ( + + {renderDropdown()} + {description && ( + )}> + + + )} + + )} ); } - -export default TableHead; diff --git a/packages/components/src/Table/DataTable/features/InitFilterFeature.ts b/packages/components/src/Table/DataTable/features/InitFilterFeature.ts index a8ca4233..8411a153 100644 --- a/packages/components/src/Table/DataTable/features/InitFilterFeature.ts +++ b/packages/components/src/Table/DataTable/features/InitFilterFeature.ts @@ -1,12 +1,6 @@ -import { - ColumnFiltersState, - TableFeature, - TableOptionsResolved, - Updater, - functionalUpdate, - getFilteredRowModel, - shouldAutoRemoveFilter, -} from '@tanstack/react-table'; +import { TableOptionsResolved, getFilteredRowModel } from '@tanstack/react-table'; + +import { TableFeature } from '../interfaces'; const getFilterOptions = (options: TableOptionsResolved) => { const { diff --git a/packages/components/src/Table/DataTable/index.ts b/packages/components/src/Table/DataTable/index.ts index bcd6a65f..9cbffe6b 100644 --- a/packages/components/src/Table/DataTable/index.ts +++ b/packages/components/src/Table/DataTable/index.ts @@ -5,3 +5,4 @@ export * from './hooks'; export * from './features/index'; export * from './TableHead'; export * from './utils'; +export * from './interfaces'; diff --git a/packages/components/src/Table/DataTable/interfaces.ts b/packages/components/src/Table/DataTable/interfaces.ts index 782bc08c..3bb47b20 100644 --- a/packages/components/src/Table/DataTable/interfaces.ts +++ b/packages/components/src/Table/DataTable/interfaces.ts @@ -1,25 +1,61 @@ -export interface BaseColumn { - title: string | React.ReactNode; - description?: { title?: string; content?: React.ReactNode }; - id?: string; - searchable?: boolean; - sortable?: boolean; - filterOptions?: { label: string | React.ReactNode; key: any }[]; - canHide?: boolean; - width?: number | string; - minWidth?: number; - maxWidth?: number; +import type { + TableOptionsResolved as _TableOptionsResolved, + ColumnMeta as _ColumnMeta, + TableOptions as _TableOptions, + TableFeature as _TableFeature, + TableMeta as _TableMeta, + RowData, + ColumnDef as _ColumnDef, + Table as _Table, + TableState, +} from '@tanstack/react-table'; + +export type TableOptionsResolved = _TableOptionsResolved; + +export type ColumnMeta = _ColumnMeta; + +export type TableOptions = _TableOptions; + +export type TableFeature = _TableFeature; + +export type TableMeta = _TableMeta; + +export type ColumnDef = _ColumnDef; + +export interface TableEnableConfig { + enableToolbar?: boolean; + enablePagination?: boolean; + enableVisible?: boolean; + enableFilters?: boolean; + enableParamsToUrl?: boolean; + enableStateToStorage?: boolean; + enableSelection?: boolean; + enableSort?: boolean; + enableMultiSelection?: boolean; + enableInitParamsByUrl?: boolean; } -interface RequiredFieldColumn, TValue = unknown> { - field: string; - render?: (value: TValue, record: T) => React.ReactNode; +export interface StorageStateOptions { + storage2State?: (storageKey: string) => Partial; + state2Storage?: (storageKey: string, state: Partial) => void; +} +export interface StateHandler { + handlerName?: string; + stateKeys: (keyof TableState)[] | '*'; + callback?: (state: Partial) => void; } -interface RequiredRenderColumn> { - field: undefined; - render?: (record: T) => React.ReactNode; +export interface FeaturesHandlersOptions { + _featuresHandlers?: StateHandler[]; + _featuresInitState?: Partial; } -export type ColumnV7 = unknown, TValue = unknown> = BaseColumn & - (RequiredFieldColumn | RequiredRenderColumn); +export interface DefaultOptionsResolved { + getDefaultOptionsResolved?: (options: TableOptionsResolved) => TableOptionsResolved; +} + +export type Table = _Table; +export interface DataTableRootProps { + className?: string; + table: Table; +} diff --git a/packages/components/src/Table/DataTable/utils/index.tsx b/packages/components/src/Table/DataTable/utils/index.tsx index 303861ef..d26222e5 100644 --- a/packages/components/src/Table/DataTable/utils/index.tsx +++ b/packages/components/src/Table/DataTable/utils/index.tsx @@ -1,19 +1,12 @@ import { Eye, EyeClosed } from '@kubed/icons'; import { pick } from 'lodash'; -import { - Row, - RowData, - Table, - TableMeta, - TableOptions, - getCoreRowModel, -} from '@tanstack/react-table'; +import { Row, RowData, getCoreRowModel } from '@tanstack/react-table'; import * as React from 'react'; import { Menu, MenuItem, MenuLabel } from '../../../index'; import type { ToolbarProps } from '../../BaseTable'; import { Suggestions } from '../../BaseTable'; -import { TableEnableConfig } from '../Table'; +import { TableEnableConfig, Table, TableMeta, TableOptions } from '../interfaces'; import { InitFilterFeature, InitPaginationFeature, @@ -67,6 +60,7 @@ export function getDefaultToolbarProps( const { options: { meta: { + refetch, enable: { filters: enableFilters } = {}, getProps: { filters: getFiltersProps } = {}, } = {}, @@ -136,6 +130,7 @@ export function getDefaultToolbarProps( settingMenu, settingMenuText, loading: table.options.loading, + refetch: props.refetch ?? refetch, }; } @@ -151,11 +146,13 @@ export function getDefaultTrProps(table: Table, row: Row) { }; } -export interface DefaultTdPropsConfig extends TableEnableConfig { +interface DefaultTdPropsConfig extends TableEnableConfig { tableName: string; manual: boolean; } +export const TABLE_DEFAULT_SELECT_COLUMN_ID = '_selector'; + function _getDefaultTableOptions( tableName: string, manual: boolean = true, @@ -189,6 +186,9 @@ function _getDefaultTableOptions( meta: { tableName, manual, + _defaultConfig: { + selectColumnId: TABLE_DEFAULT_SELECT_COLUMN_ID, + }, autoResetPageIndex: false, storageStateKeys: ['columnVisibility'], registerHandlers: manual diff --git a/packages/components/src/Table/Table.story.tsx b/packages/components/src/Table/Table.story.tsx index 97046897..8eff5946 100644 --- a/packages/components/src/Table/Table.story.tsx +++ b/packages/components/src/Table/Table.story.tsx @@ -4,7 +4,6 @@ import { getCoreRowModel, getPaginationRowModel, RowSelectionState, - TableState, Updater, useReactTable, } from '@tanstack/react-table'; @@ -17,6 +16,7 @@ import { Dropdown } from '../Dropdown/Dropdown'; import { Menu, MenuItem } from '../Menu/Menu'; import { Select } from '../Select/Select'; import { StateHandler, Status2StorageFeature, getDefaultTableOptions, useTable } from './DataTable'; +import { Center } from '../Center/Center'; const { Table, TableBody, TableCell, TableHead, TableRow, Pagination, Toolbar } = BaseTable; export default { @@ -428,16 +428,16 @@ const defaultColumns: ColumnDef[] = [ footer: (props) => props.column.id, meta: { sortable: true, - filterOptions: [ - { - key: '0', - label: '0', - }, - { - key: '1', - label: '1', - }, - ], + // filterOptions: [ + // { + // key: '0', + // label: '0', + // }, + // { + // key: '1', + // label: '1', + // }, + // ], }, }, { @@ -454,17 +454,18 @@ const defaultColumns: ColumnDef[] = [ footer: (props) => props.column.id, meta: { searchKey: 'status1', - filterOptions: [ - { - key: 'status-0', - label: 'status-0', - }, - { - key: 'status-1', - label: 'status-1', - }, - ], }, + // filterOptions: [ + // { + // key: 'status-0', + // label: 'status-0', + // }, + // { + // key: 'status-1', + // label: 'status-1', + // }, + // ], + // }, }, { accessorKey: 'progress', @@ -477,6 +478,11 @@ const defaultColumns: ColumnDef[] = [ }, { id: 'actions', + meta: { + th: { + width: '100px', + }, + }, cell: () => ( { /> ), cell: ({ row }) => ( - +
+ +
), }, ...defaultColumns, @@ -932,20 +940,37 @@ export const DataTableSimple = () => { const [loading] = React.useState(false); const [columns] = React.useState(() => [ { - id: 'selection', - header: ({ table }) => ( + id: '_selector', + meta: { + th: { + width: '36px', + align: 'center', + }, + td: { + align: 'center', + }, + }, + header: ({ table }: { table }) => ( { + const indeterminate = table.getIsSomeRowsSelected(); + if (indeterminate) { + e.target.checked = true; + } + table.getToggleAllRowsSelectedHandler()(e); + }} //or getToggleAllPageRowsSelectedHandler /> ), - cell: ({ row }) => ( - + cell: ({ row }: { row }) => ( +
+ +
), }, ...defaultColumns, @@ -971,8 +996,8 @@ export const DataTableSimple = () => { tableName: 'table1', manual: false, enableSelection: true, - enableMultiSelection: false, - enableFilters: false, + enableMultiSelection: true, + enableFilters: true, }) )[0];