Skip to content

Commit

Permalink
feat(next/antd): add SelectTable component (#2708)
Browse files Browse the repository at this point in the history
  • Loading branch information
ifblooms authored Dec 31, 2021
1 parent 98a544a commit 8222421
Show file tree
Hide file tree
Showing 17 changed files with 3,075 additions and 0 deletions.
613 changes: 613 additions & 0 deletions packages/antd/docs/components/SelectTable.md

Large diffs are not rendered by default.

609 changes: 609 additions & 0 deletions packages/antd/docs/components/SelectTable.zh-CN.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/antd/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ export * from './upload'
export * from './submit'
export * from './reset'
export * from './editable'
export * from './select-table'
209 changes: 209 additions & 0 deletions packages/antd/src/select-table/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import React, { useEffect, useState, useMemo } from 'react'
import { observer, useFieldSchema, useField, Schema } from '@formily/react'
import cls from 'classnames'
import { isArr, isBool, isFn } from '@formily/shared'
import { Input, Table } from 'antd'
import { TableProps, ColumnProps } from 'antd/lib/table'
import { SearchProps } from 'antd/lib/input'
import { useFilterOptions } from './useFilterOptions'
import { usePrefixCls } from '../__builtins__'

const { Search } = Input

type IFilterOption = boolean | ((option: any, keyword: string) => boolean)

type IFilterSort = (optionA: any, optionB: any) => number

export interface ISelectTableColumnProps extends ColumnProps<any> {
key: React.ReactText
}

export interface ISelectTableProps extends TableProps<any> {
mode?: 'multiple' | 'single'
dataSource?: any[]
optionAsValue?: boolean
showSearch?: boolean
searchProps?: SearchProps
optionFilterProp?: string
primaryKey?: string | ((record: any) => string)
filterOption?: IFilterOption
filterSort?: IFilterSort
onSearch?: (keyword: string) => void
onChange?: (value: any) => void
value?: any
}

type ComposedSelectTable = React.FC<ISelectTableProps> & {
Column?: React.FC<ISelectTableColumnProps>
}

const isColumnComponent = (schema: Schema) => {
return schema['x-component']?.indexOf('Column') > -1
}

const useColumns = () => {
const schema = useFieldSchema()
const columns: ISelectTableColumnProps[] = []
const validSchema = (
schema.type === 'array' && schema?.items ? schema.items : schema
) as Schema

validSchema?.mapProperties((schema, name) => {
if (isColumnComponent(schema)) {
const props = schema?.['x-component-props']
columns.push({
...props,
title: props?.title || schema?.title,
dataIndex: props?.dataIndex || name,
})
}
})
return columns
}

const addPrimaryKey = (dataSource, rowKey, primaryKey) =>
dataSource.map((item) => ({
...item,
[primaryKey]: rowKey(item),
}))

export const SelectTable: ComposedSelectTable = observer((props) => {
const {
mode,
dataSource: propsDataSource,
optionAsValue,
showSearch,
filterOption,
optionFilterProp,
filterSort,
onSearch,
searchProps,
className,
value,
onChange,
rowSelection,
primaryKey: rowKey,
...otherTableProps
} = props
const prefixCls = usePrefixCls('formily-select-table', props)
const [selected, setSelected] = useState<any[]>()
const [searchValue, setSearchValue] = useState<string>()
const field = useField() as any
const loading = isBool(props.loading) ? props.loading : field.loading
const disabled = field.disabled
const readPretty = field.readPretty
const primaryKey = isFn(rowKey) ? '__formily_key__' : rowKey
let dataSource = isArr(propsDataSource) ? propsDataSource : field.dataSource
dataSource = isFn(rowKey)
? addPrimaryKey(dataSource, rowKey, primaryKey)
: dataSource
const columns = useColumns()

// Filter dataSource By Search
const filteredDataSource = useFilterOptions(
dataSource,
optionFilterProp || primaryKey,
searchValue,
filterOption
)

// Order dataSource By filterSort
const orderedFilteredDataSource = useMemo(() => {
if (!filterSort) {
return filteredDataSource
}
return [...filteredDataSource].sort((a, b) => filterSort(a, b))
}, [filteredDataSource, filterSort])

// readPretty Value
const readPrettyDataSource = useMemo(
() =>
orderedFilteredDataSource.filter((item) =>
selected?.includes(item?.[primaryKey])
),
[orderedFilteredDataSource, selected, primaryKey]
)

const onInnerSearch = (searchText) => {
const formatted = (searchText || '').trim()
setSearchValue(searchText)
onSearch?.(formatted)
}

const onInnerChange = (selectedRowKeys: any[], records: any[]) => {
let outputValue = optionAsValue
? records.map((item) => {
const validItem = { ...item }
delete validItem['__formily_key__']
return validItem
})
: selectedRowKeys
outputValue = mode === 'single' ? outputValue[0] : outputValue
onChange?.(outputValue)
}

// Antd rowSelection type
const modeAsType: any = { multiple: 'checkbox', single: 'radio' }?.[mode]

useEffect(() => {
let inputValue = mode === 'single' ? [value] : isArr(value) ? value : []
inputValue = optionAsValue
? inputValue.map((record: any) =>
isFn(rowKey) ? rowKey(record) : record?.[primaryKey]
)
: inputValue
setSelected(inputValue)
}, [value, mode, primaryKey, rowKey])

return (
<div className={prefixCls}>
{showSearch && !readPretty ? (
<Search
{...searchProps}
className={cls(`${prefixCls}-search`, searchProps?.className)}
onSearch={onInnerSearch}
disabled={disabled}
loading={loading} // antd
/>
) : null}
<Table
{...otherTableProps}
className={cls(`${prefixCls}-table`, className)}
dataSource={
readPretty ? readPrettyDataSource : orderedFilteredDataSource
}
rowSelection={
readPretty
? undefined
: {
...rowSelection,
getCheckboxProps: (record) => ({
...(rowSelection?.getCheckboxProps(record) as any),
disabled,
}), // antd
selectedRowKeys: selected,
onChange: onInnerChange,
type: modeAsType,
}
}
columns={props.columns || columns}
rowKey={primaryKey}
loading={loading}
>
{''}
</Table>
</div>
)
})

const TableColumn: React.FC<ISelectTableColumnProps> = () => <></>

SelectTable.Column = TableColumn

SelectTable.defaultProps = {
showSearch: false,
primaryKey: 'key',
mode: 'multiple',
}

export default SelectTable
10 changes: 10 additions & 0 deletions packages/antd/src/select-table/style.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@root-entry-name: 'default';
@import (reference) '~antd/es/style/themes/index.less';

@select-table-prefix-cls: ~'@{ant-prefix}-formily-select-table';

.@{select-table-prefix-cls} {
.@{select-table-prefix-cls}-search {
margin-bottom: 8px;
}
}
2 changes: 2 additions & 0 deletions packages/antd/src/select-table/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import 'antd/lib/radio/style/index'
import './style.less'
52 changes: 52 additions & 0 deletions packages/antd/src/select-table/useFilterOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from 'react'
import { isFn, isArr } from '@formily/shared'

type IFilterOption = boolean | ((option: any, keyword: string) => boolean)

function includes(test: React.ReactNode, search: string) {
return toArray(test).join('').toUpperCase().includes(search)
}

function toArray<T>(value: T | T[]): T[] {
if (isArr(value)) {
return value
}
return value !== undefined ? [value] : []
}

const useFilterOptions = (
options: any[],
searchKey?: string,
searchValue?: string,
filterOption?: IFilterOption
) =>
React.useMemo(() => {
if (!searchValue || filterOption === false) {
return options
}
const upperSearch = searchValue.toUpperCase()
const filterFunc = isFn(filterOption)
? filterOption
: (_: string, option: any) => includes(option[searchKey], upperSearch)

const doFilter = (arr: any[]) => {
const filterArr: any[] = []
arr.forEach((item) => {
if (item?.children?.length) {
const filterChildren = doFilter(item.children)
if (filterChildren.length) {
filterArr.push({ ...item, children: filterChildren })
} else if (filterFunc(searchValue, item)) {
filterArr.push({ ...item, children: [] })
}
} else if (filterFunc(searchValue, item)) {
filterArr.push(item)
}
})
return filterArr
}

return doFilter(options)
}, [options, searchKey, searchValue, filterOption])

export { useFilterOptions }
1 change: 1 addition & 0 deletions packages/antd/src/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ import './form-layout/style.less'
import './form/style.less'
import './preview-text/style.less'
import './radio/style.less'
import './select-table/style.less'
Loading

0 comments on commit 8222421

Please sign in to comment.