+
)
diff --git a/packages/components/table/src/main/body/Body.tsx b/packages/components/table/src/main/body/Body.tsx
index 7c4d48fd1..f2cd7d319 100644
--- a/packages/components/table/src/main/body/Body.tsx
+++ b/packages/components/table/src/main/body/Body.tsx
@@ -49,7 +49,7 @@ export default defineComponent({
} else {
data.forEach((item, rowIndex) => {
const { expanded, level, record, rowKey } = item
- const rowProps = { key: rowKey, expanded, level, record, rowIndex, rowKey }
+ const rowProps = { key: rowKey, expanded, level, record, rowData: item, rowIndex, rowKey }
children.push(
)
})
}
diff --git a/packages/components/table/src/main/body/BodyCell.tsx b/packages/components/table/src/main/body/BodyCell.tsx
index a77c4c0ce..0df42924e 100644
--- a/packages/components/table/src/main/body/BodyCell.tsx
+++ b/packages/components/table/src/main/body/BodyCell.tsx
@@ -33,18 +33,28 @@ type BodyColumn = TableColumnMergedExtra & {
export default defineComponent({
props: tableBodyCellProps,
setup(props) {
- const { slots, activeSortable, fixedColumnKeys, columnOffsets, isSticky, expandable, selectable, bodyColTag } =
- inject(TABLE_TOKEN)!
+ const {
+ mergedPrefixCls,
+ slots,
+ activeSortable,
+ fixedColumnKeys,
+ columnOffsets,
+ isSticky,
+ expandable,
+ selectable,
+ bodyColTag,
+ } = inject(TABLE_TOKEN)!
const dataValue = useDataValue(props)
const cellProps = computed(() => {
const { key, fixed, align, ellipsis } = props.column as BodyColumn
- const prefixCls = 'ix-table'
+ const prefixCls = mergedPrefixCls.value
let classes: Record
= {
[`${prefixCls}-cell-sorted`]: activeSortable.key === key && !!activeSortable.orderBy,
[`${prefixCls}-align-${align}`]: !!align,
[`${prefixCls}-ellipsis`]: !!ellipsis,
}
+
let style: StyleValue | undefined
if (fixed) {
const { lastStartKey, firstEndKey } = fixedColumnKeys.value
@@ -75,22 +85,23 @@ export default defineComponent({
return () => {
const { type, additional } = props.column as BodyColumn
- let children: VNodeTypes | null
+ let children: VNodeTypes | null = null
let title: string | undefined
- if (type === 'expandable') {
- children = props.disabled ? null : renderExpandableChildren(props, slots, expandable)
- } else if (type === 'selectable') {
+
+ if (type === 'selectable') {
children = renderSelectableChildren(props, selectable, handleClick)
} else {
const { ellipsis } = props.column
const text = dataValue.value
- children = renderChildren(props, slots, text)
- title = getColTitle(ellipsis, children, text)
+ children = text ? renderChildren(props, slots, text) : null
+ title = children ? getColTitle(ellipsis, children, text) : undefined
}
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const BodyColTag = bodyColTag.value as any
return (
+ {type === 'expandable' ? renderExpandableChildren(props, slots, expandable, mergedPrefixCls.value) : null}
{children}
)
@@ -102,6 +113,10 @@ function useDataValue(props: TableBodyCellProps) {
return computed(() => {
const { column, record } = props
const dataKeys = convertArray(column.dataKey)
+ if (dataKeys.length <= 0) {
+ return undefined
+ }
+
let value = record
for (let index = 0; index < dataKeys.length; index++) {
if (!value) {
@@ -130,18 +145,31 @@ function renderExpandableChildren(
props: TableBodyCellProps,
slots: Slots,
expandable: ComputedRef,
+ prefixCls: string,
) {
- const { icon, customIcon } = expandable.value!
- const record = props.record
- const expanded = props.expanded!
+ const { icon, customIcon, indent } = expandable.value!
+ const { record, expanded, level, disabled } = props
const onExpand = props.handleExpend!
- if (isFunction(customIcon)) {
- return customIcon({ expanded, record, onExpand })
+ const style = {
+ marginLeft: indent ? convertCssPixel(level * indent) : undefined,
}
- if (isString(customIcon) && slots[customIcon]) {
- return slots[customIcon]!({ expanded, record, onExpand })
+
+ let iconNode: VNodeTypes | null
+ if (disabled) {
+ iconNode = null
+ } else if (isFunction(customIcon)) {
+ iconNode = customIcon({ expanded: !!expanded, record, onExpand })
+ } else if (isString(customIcon) && slots[customIcon]) {
+ iconNode = slots[customIcon]!({ expanded, record, onExpand })
+ } else {
+ iconNode =
}
- return
+
+ return (
+
+ )
}
function renderSelectableChildren(
diff --git a/packages/components/table/src/main/body/BodyRow.tsx b/packages/components/table/src/main/body/BodyRow.tsx
index 49be9b289..029e3cc2b 100644
--- a/packages/components/table/src/main/body/BodyRow.tsx
+++ b/packages/components/table/src/main/body/BodyRow.tsx
@@ -17,6 +17,7 @@ import { computed, defineComponent, inject } from 'vue'
import { isFunction, isString } from 'lodash-es'
+import { FlattedData } from '../../composables/useDataSource'
import { TABLE_TOKEN } from '../../token'
import { tableBodyRowProps } from '../../types'
import BodyCell from './BodyCell'
@@ -27,10 +28,12 @@ export default defineComponent({
setup(props) {
const {
props: tableProps,
+ mergedPrefixCls,
slots,
flattedColumns,
expandable,
handleExpandChange,
+ checkExpandDisabled,
selectable,
selectedRowKeys,
indeterminateRowKeys,
@@ -39,10 +42,10 @@ export default defineComponent({
bodyRowTag,
} = inject(TABLE_TOKEN)!
- const { expendDisabled, handleExpend, selectDisabled, handleSelect, clickEvents } = useEvents(
+ const { expandDisabled, handleExpend, selectDisabled, handleSelect, clickEvents } = useEvents(
props,
- tableProps,
expandable,
+ checkExpandDisabled,
handleExpandChange,
selectable,
handleSelectChange,
@@ -52,12 +55,12 @@ export default defineComponent({
const isSelected = computed(() => selectedRowKeys.value.includes(props.rowKey))
const isIndeterminate = computed(() => indeterminateRowKeys.value.includes(props.rowKey))
- const classes = useClasses(props, tableProps, isSelected)
+ const classes = useClasses(props, tableProps, isSelected, mergedPrefixCls)
return () => {
const children = renderChildren(
props,
flattedColumns,
- expendDisabled,
+ expandDisabled.value,
handleExpend,
isSelected,
isIndeterminate,
@@ -81,10 +84,15 @@ export default defineComponent({
},
})
-function useClasses(props: TableBodyRowProps, tableProps: TableProps, isSelected: ComputedRef) {
+function useClasses(
+ props: TableBodyRowProps,
+ tableProps: TableProps,
+ isSelected: ComputedRef,
+ mergedPrefixCls: ComputedRef,
+) {
const rowClassName = computed(() => tableProps.rowClassName?.(props.record, props.rowIndex))
return computed(() => {
- const prefixCls = 'ix-table-row'
+ const prefixCls = `${mergedPrefixCls.value}`
const { level, expanded } = props
const computeRowClassName = rowClassName.value
return {
@@ -98,14 +106,14 @@ function useClasses(props: TableBodyRowProps, tableProps: TableProps, isSelected
function useEvents(
props: TableBodyRowProps,
- tableProps: TableProps,
expandable: ComputedRef,
+ checkExpandDisabled: (data: FlattedData) => boolean,
handleExpandChange: (key: Key, record: unknown) => void,
selectable: ComputedRef,
handleSelectChange: (key: Key, record: unknown) => void,
currentPageRowKeys: ComputedRef<{ enabledRowKeys: Key[]; disabledRowKeys: Key[] }>,
) {
- const expendDisabled = useExpandDisabled(props, tableProps, expandable)
+ const expandDisabled = computed(() => checkExpandDisabled(props.rowData))
const expendTrigger = computed(() => expandable.value?.trigger)
const handleExpend = () => {
const { rowKey, record } = props
@@ -120,7 +128,7 @@ function useEvents(
}
const handleClick = () => {
- if (expendTrigger.value === 'click' && !expendDisabled.value) {
+ if (expendTrigger.value === 'click' && !expandDisabled.value) {
handleExpend()
}
if (selectTrigger.value === 'click' && !selectDisabled.value) {
@@ -129,7 +137,7 @@ function useEvents(
}
const handleDblclick = () => {
- if (expendTrigger.value === 'dblclick' && !expendDisabled.value) {
+ if (expendTrigger.value === 'dblclick' && !expandDisabled.value) {
handleExpend()
}
if (selectTrigger.value === 'dblclick' && !selectDisabled.value) {
@@ -144,32 +152,13 @@ function useEvents(
return { onClick, onDblclick }
})
- return { expendDisabled, handleExpend, selectDisabled, handleSelect, clickEvents }
-}
-
-function useExpandDisabled(
- props: TableBodyRowProps,
- tableProps: TableProps,
- expandable: ComputedRef,
-) {
- return computed(() => {
- const column = expandable.value
- if (!column) {
- return true
- }
- const { disabled, customExpand } = column
- const { record, rowIndex } = props
- if (disabled?.(record, rowIndex)) {
- return true
- }
- return !(customExpand || record[tableProps.childrenKey]?.length > 0)
- })
+ return { expandDisabled, handleExpend, selectDisabled, handleSelect, clickEvents }
}
function renderChildren(
props: TableBodyRowProps,
flattedColumns: ComputedRef,
- expendDisabled: ComputedRef,
+ expandDisabled: boolean,
handleExpend: () => void,
isSelected: ComputedRef,
isIndeterminate: ComputedRef,
@@ -177,7 +166,7 @@ function renderChildren(
handleSelect: () => void,
) {
const children: VNodeTypes[] = []
- const { rowIndex, record } = props
+ const { rowIndex, record, level } = props
flattedColumns.value.forEach((column, colIndex) => {
const { type, colSpan: getColSpan, rowSpan: getRowSpan, key } = column
const colSpan = getColSpan?.(record, rowIndex)
@@ -193,11 +182,12 @@ function renderChildren(
colIndex,
record,
column,
+ level,
key,
}
if (type === 'expandable') {
colProps.expanded = props.expanded
- colProps.disabled = expendDisabled.value
+ colProps.disabled = expandDisabled
colProps.handleExpend = handleExpend
} else if (type === 'selectable') {
colProps.selected = isSelected.value
diff --git a/packages/components/table/src/main/body/MeasureRow.tsx b/packages/components/table/src/main/body/MeasureRow.tsx
index 4bc849dc7..375df413e 100644
--- a/packages/components/table/src/main/body/MeasureRow.tsx
+++ b/packages/components/table/src/main/body/MeasureRow.tsx
@@ -12,7 +12,7 @@ import MeasureCell from './MeasureCell'
export default defineComponent({
setup() {
- const { flattedColumns } = inject(TABLE_TOKEN)!
+ const { mergedPrefixCls, flattedColumns } = inject(TABLE_TOKEN)!
const { changeColumnWidth } = inject(tableBodyToken)!
return () => {
const children = flattedColumns.value.map(column => {
@@ -21,7 +21,7 @@ export default defineComponent({
return
})
return (
-
+
{children}
)
diff --git a/packages/components/table/src/main/head-trigger/HeadCellFilterableTrigger.tsx b/packages/components/table/src/main/head-trigger/HeadCellFilterableTrigger.tsx
new file mode 100644
index 000000000..11bdcc284
--- /dev/null
+++ b/packages/components/table/src/main/head-trigger/HeadCellFilterableTrigger.tsx
@@ -0,0 +1,56 @@
+/**
+ * @license
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
+ */
+
+import type { DropdownProps } from '@idux/components/dropdown'
+
+import { computed, defineComponent, inject } from 'vue'
+
+import { isArray } from 'lodash-es'
+
+import { IxDropdown } from '@idux/components/dropdown'
+import { IxIcon } from '@idux/components/icon'
+
+import FilterDropdown from '../../other/FilterDropdown'
+import { TABLE_TOKEN } from '../../token'
+import { tableHeadCellFilterableTriggerProps } from '../../types'
+
+export default defineComponent({
+ props: tableHeadCellFilterableTriggerProps,
+ setup(props) {
+ const { mergedPrefixCls } = inject(TABLE_TOKEN)!
+ const classes = computed(() => {
+ const prefixCls = `${mergedPrefixCls.value}-filterable-trigger`
+ const { filterable } = props
+
+ return {
+ [prefixCls]: true,
+ [`${prefixCls}-active`]: isArray(filterable.filterBy) && filterable.filterBy.length > 0,
+ }
+ })
+
+ const renderTrigger = () => (
+ evt.stopPropagation()}>
+
+
+ )
+ const renderDropDown = () =>
+
+ return () => {
+ const { filterable } = props
+ if (!filterable || filterable.filters.length <= 0) {
+ return null
+ }
+
+ const dropdownProps: DropdownProps = {
+ trigger: 'click',
+ placement: 'bottomStart',
+ }
+
+ return
+ }
+ },
+})
diff --git a/packages/components/table/src/main/head/HeadCellSortable.tsx b/packages/components/table/src/main/head-trigger/HeadCellSortableTrigger.tsx
similarity index 60%
rename from packages/components/table/src/main/head/HeadCellSortable.tsx
rename to packages/components/table/src/main/head-trigger/HeadCellSortableTrigger.tsx
index 10ca23e74..3afe2fea0 100644
--- a/packages/components/table/src/main/head/HeadCellSortable.tsx
+++ b/packages/components/table/src/main/head-trigger/HeadCellSortableTrigger.tsx
@@ -5,11 +5,13 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/
+import type { TableColumnSortOrder } from '@idux/components/table'
+import type { ComputedRef } from 'vue'
+
import { defineComponent, inject } from 'vue'
import { TableLocale } from '@idux/components/i18n'
import { IxIcon } from '@idux/components/icon'
-import { TableColumnSortOrder } from '@idux/components/table'
import { IxTooltip } from '@idux/components/tooltip'
import { TABLE_TOKEN } from '../../token'
@@ -17,20 +19,15 @@ import { TABLE_TOKEN } from '../../token'
export default defineComponent({
// eslint-disable-next-line vue/require-prop-types
props: ['activeOrderBy', 'sortable'],
- setup(props, { slots }) {
- const { locale } = inject(TABLE_TOKEN)!
+ setup(props) {
+ const { locale, mergedPrefixCls } = inject(TABLE_TOKEN)!
return () => {
const { activeOrderBy, sortable } = props
const { orders, nextTooltip } = sortable
const title = nextTooltip ? getNextTooltipTitle(locale.value, orders!, activeOrderBy) : undefined
- const sortableNode = (
-
- {slots.default!()}
- {renderSortTrigger(orders!, activeOrderBy)}
-
- )
- return title ? {sortableNode} : sortableNode
+ const sortableTriggerNode = renderSortTrigger(mergedPrefixCls, orders!, activeOrderBy)
+ return title ? {sortableTriggerNode} : sortableTriggerNode
}
},
})
@@ -48,15 +45,24 @@ function getNextTooltipTitle(
return nextOrderBy === 'ascend' ? sortAsc : sortDesc
}
-function renderSortTrigger(orders: TableColumnSortOrder[], activeOrderBy?: TableColumnSortOrder) {
+function renderSortTrigger(
+ mergedPrefixCls: ComputedRef,
+ orders: TableColumnSortOrder[],
+ activeOrderBy?: TableColumnSortOrder,
+) {
+ const prefixCls = mergedPrefixCls.value
+
const upNode = orders!.includes('ascend') ? (
-
+
) : undefined
const downNode = orders!.includes('descend') ? (
-
+
) : undefined
return (
-
+
{upNode}
{downNode}
diff --git a/packages/components/table/src/main/head/HeadCell.tsx b/packages/components/table/src/main/head/HeadCell.tsx
index 69d6d5e50..8cda415f5 100644
--- a/packages/components/table/src/main/head/HeadCell.tsx
+++ b/packages/components/table/src/main/head/HeadCell.tsx
@@ -6,7 +6,7 @@
*/
import type { TableColumnMergedExtra } from '../../composables/useColumns'
-import type { TableColumnTitleFn } from '../../types'
+import type { TableColumnFilterable, TableColumnSortable, TableColumnTitleFn } from '../../types'
import type { Slots, StyleValue, VNodeTypes } from 'vue'
import { computed, defineComponent, inject } from 'vue'
@@ -18,8 +18,9 @@ import { convertCssPixel } from '@idux/cdk/utils'
import { TABLE_TOKEN } from '../../token'
import { tableHeadCellProps } from '../../types'
import { getColTitle } from '../../utils'
+import HeadCellFilterableTrigger from '../head-trigger/HeadCellFilterableTrigger'
+import HeadCellSortableTrigger from '../head-trigger/HeadCellSortableTrigger'
import HeadCellSelectable from './HeadCellSelectable'
-import HeadCellSortable from './HeadCellSortable'
type HeadColumn = TableColumnMergedExtra & {
type: 'selectable' | 'expandable' | 'scroll-bar' | undefined
@@ -29,11 +30,18 @@ type HeadColumn = TableColumnMergedExtra & {
export default defineComponent({
props: tableHeadCellProps,
setup(props) {
- const { slots, fixedColumnKeys, columnOffsetsWithScrollBar, isSticky, activeSortable, handleSort, headColTag } =
- inject(TABLE_TOKEN)!
- const key = computed(() => props.column.key)
- const isSorted = computed(() => activeSortable.key === key.value && !!activeSortable.orderBy)
- const activeSortOrderBy = computed(() => (isSorted.value ? activeSortable.orderBy : undefined))
+ const {
+ mergedPrefixCls,
+ slots,
+ fixedColumnKeys,
+ columnOffsetsWithScrollBar,
+ isSticky,
+ handleSort,
+ headColTag,
+ activeSortable,
+ filterables,
+ } = inject(TABLE_TOKEN)!
+
const onClick = () => {
const { key, sortable } = props.column
if (sortable) {
@@ -45,11 +53,10 @@ export default defineComponent({
const { type, align, hasChildren, fixed, key, colStart, colEnd, sortable, titleColSpan, titleRowSpan } =
props.column as HeadColumn
- const prefixCls = 'ix-table'
+ const prefixCls = mergedPrefixCls.value
let classes: Record = {
[`${prefixCls}-cell-${type}`]: !!type,
[`${prefixCls}-cell-sortable`]: !!sortable,
- [`${prefixCls}-cell-sorted`]: isSorted.value,
[`${prefixCls}-align-${align}`]: !hasChildren && !!align,
[`${prefixCls}-align-center`]: hasChildren,
}
@@ -82,34 +89,46 @@ export default defineComponent({
}
})
+ const sortable = computed(() => props.column.sortable)
+ const activeSortOrderBy = computed(() =>
+ activeSortable.key === props.column.key && !!activeSortable.orderBy ? activeSortable.orderBy : undefined,
+ )
+ const filterable = computed(() => filterables.value.find(f => f.key === props.column.key))
+
return () => {
const { type, additional } = props.column as HeadColumn
+ const prefixCls = mergedPrefixCls.value
+
let _title: string | undefined
let children: VNodeTypes | undefined
- if (type === 'expandable' || type === 'scroll-bar') {
+ let iconTriggers: (VNodeTypes | null)[] | undefined
+ if (type === 'scroll-bar') {
children = undefined
} else if (type === 'selectable') {
children =
} else {
- const { title, customTitle, ellipsis, sortable } = props.column as HeadColumn
+ const { title, customTitle, ellipsis } = props.column as HeadColumn
children = renderChildren(title, customTitle, slots)
_title = getColTitle(ellipsis, children!, title)
- if (ellipsis || sortable) {
- children = {children}
- if (sortable) {
- children = (
-
- {children}
-
- )
- }
+
+ iconTriggers = [
+ renderSortableTrigger(sortable.value, activeSortOrderBy.value),
+ renderFilterableTrigger(filterable.value),
+ ]
+
+ if (ellipsis || iconTriggers.some(trigger => !!trigger)) {
+ children = {children}
}
}
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const HeadColTag = headColTag.value as any
return (
- {children}
+
+ {children}
+ {iconTriggers}
+
)
}
@@ -125,3 +144,22 @@ function renderChildren(title: string | undefined, customTitle: string | TableCo
}
return children
}
+
+function renderSortableTrigger(
+ sortable: TableColumnSortable | undefined,
+ activeSortOrderBy: 'descend' | 'ascend' | undefined,
+): VNodeTypes | null {
+ if (!sortable) {
+ return null
+ }
+
+ return
+}
+
+function renderFilterableTrigger(filterable: TableColumnFilterable | undefined): VNodeTypes | null {
+ if (!filterable) {
+ return null
+ }
+
+ return
+}
diff --git a/packages/components/table/src/main/head/HeadCellSelectable.tsx b/packages/components/table/src/main/head/HeadCellSelectable.tsx
index 791a755b3..d77699d34 100644
--- a/packages/components/table/src/main/head/HeadCellSelectable.tsx
+++ b/packages/components/table/src/main/head/HeadCellSelectable.tsx
@@ -5,10 +5,11 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/
-import type { VNodeTypes } from 'vue'
+import type { DropdownProps } from '@idux/components/dropdown'
import { computed, defineComponent, inject } from 'vue'
+import { useState } from '@idux/cdk/utils'
import { IxCheckbox } from '@idux/components/checkbox'
import { IxDropdown } from '@idux/components/dropdown'
import { IxIcon } from '@idux/components/icon'
@@ -19,6 +20,7 @@ import { TABLE_TOKEN } from '../../token'
export default defineComponent({
setup() {
const {
+ mergedPrefixCls,
paginatedMap,
selectable,
currentPageRowKeys,
@@ -32,31 +34,61 @@ export default defineComponent({
const dataCount = paginatedMap.value.size
return dataCount === 0 || dataCount === currentPageRowKeys.value.disabledRowKeys.length
})
+ const [visible, setVisible] = useState(false)
+ const prefixCls = computed(() => `${mergedPrefixCls.value}-selectable`)
+ const triggerClasses = computed(() => {
+ return {
+ [`${prefixCls.value}-trigger`]: true,
+ [`${prefixCls.value}-trigger--open`]: visible.value,
+ }
+ })
+
+ const renderOverlay = () => {
+ const options = mergedSelectableOptions.value
+ if (!options) {
+ return null
+ }
+
+ return (
+
+ {options.map(option => (
+
+ ))}
+
+ )
+ }
+ const renderTrigger = () =>
+ const renderDropDown = () => {
+ if (!mergedSelectableOptions.value) {
+ return null
+ }
+
+ const dropdownProps: DropdownProps = {
+ 'onUpdate:visible': setVisible,
+ trigger: 'click',
+ }
+
+ return
+ }
return () => {
const { multiple } = selectable.value!
+
if (!multiple) {
return undefined
}
- const children: VNodeTypes[] = []
- const checked = currentPageAllSelected.value
- const indeterminate = currentPageSomeSelected.value
- const checkboxProps = { checked, indeterminate, disabled: disabled.value, onChange: handleHeadSelectChange }
- children.push()
- const options = mergedSelectableOptions.value
- if (options) {
- const trigger =
- const content = (
-
- {options.map(option => (
-
- ))}
-
- )
- const slots = { default: () => trigger, overlay: () => content }
- children.push()
- }
- return children
+
+ return (
+
+
+ {renderDropDown()}
+
+ )
}
},
})
diff --git a/packages/components/table/src/other/FilterDropdown.tsx b/packages/components/table/src/other/FilterDropdown.tsx
new file mode 100644
index 000000000..ac7d1c789
--- /dev/null
+++ b/packages/components/table/src/other/FilterDropdown.tsx
@@ -0,0 +1,41 @@
+/**
+ * @license
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
+ */
+
+import { defineComponent, inject } from 'vue'
+
+import { IxCheckbox, IxCheckboxGroup } from '@idux/components/checkbox'
+
+import { TABLE_TOKEN } from '../token'
+import { tableFilterPanelProps } from '../types'
+
+export default defineComponent({
+ props: tableFilterPanelProps,
+ setup(props) {
+ const { mergedPrefixCls } = inject(TABLE_TOKEN)!
+
+ return () => {
+ const prefixCls = `${mergedPrefixCls.value}-filter-dropdown`
+ const {
+ filterable: { filters, filterBy, onChange },
+ } = props
+
+ return (
+
+
+ {filters.map(filter => (
+
+
+ {filter.text}
+
+
+ ))}
+
+
+ )
+ }
+ },
+})
diff --git a/packages/components/table/src/other/Footer.tsx b/packages/components/table/src/other/Footer.tsx
index 4699dca7a..31fda2782 100644
--- a/packages/components/table/src/other/Footer.tsx
+++ b/packages/components/table/src/other/Footer.tsx
@@ -7,9 +7,10 @@
import type { Slots, VNode } from 'vue'
-export function renderFooter(slots: Slots): VNode | null {
+export function renderFooter(slots: Slots, prefixCls: string): VNode | null {
if (!slots.footer) {
return null
}
- return
+
+ return
}
diff --git a/packages/components/table/src/other/Pagination.tsx b/packages/components/table/src/other/Pagination.tsx
index 15595bb99..745d87f0b 100644
--- a/packages/components/table/src/other/Pagination.tsx
+++ b/packages/components/table/src/other/Pagination.tsx
@@ -16,6 +16,7 @@ import { IxPagination } from '@idux/components/pagination'
export function renderPagination(
mergedPagination: TablePagination | null,
filteredData: MergedData[],
+ prefixCls: string,
): [VNode | null, VNode | null] {
let top: VNode | null = null
let bottom: VNode | null = null
@@ -23,7 +24,7 @@ export function renderPagination(
if (mergedPagination !== null) {
const { position } = mergedPagination
const [vertical, horizontal] = kebabCase(position).split('-')
- const className = `ix-table-pagination ix-table-pagination-${horizontal}`
+ const className = `${prefixCls}-pagination ${prefixCls}-pagination-${horizontal}`
const node =
diff --git a/packages/components/table/src/token.ts b/packages/components/table/src/token.ts
index f0f2b9b34..0726adee2 100644
--- a/packages/components/table/src/token.ts
+++ b/packages/components/table/src/token.ts
@@ -8,6 +8,7 @@
import type { ColumnsContext } from './composables/useColumns'
import type { DataSourceContext } from './composables/useDataSource'
import type { ExpandableContext } from './composables/useExpandable'
+import type { FilterableContext } from './composables/useFilterable'
import type { GetRowKey } from './composables/useGetRowKey'
import type { PaginationContext } from './composables/usePagination'
import type { ScrollContext } from './composables/useScroll'
@@ -28,9 +29,11 @@ export interface TableContext
ScrollContext,
SelectableContext,
SortableContext,
+ FilterableContext,
StickyContext,
TagsContext {
props: TableProps
+ mergedPrefixCls: ComputedRef
slots: Slots
config: TableConfig
locale: ComputedRef
@@ -43,7 +46,7 @@ export const TABLE_TOKEN: InjectionKey = Symbol('TABLE_TOKEN')
export interface TableBodyContext {
mainTableWidth: Ref
- changeColumnWidth: (key: Key, width: number) => void
+ changeColumnWidth: (key: Key, width: number | false) => void
}
export const tableBodyToken: InjectionKey = Symbol('tableBodyToken')
diff --git a/packages/components/table/src/types.ts b/packages/components/table/src/types.ts
index 248148efb..f74098a7b 100644
--- a/packages/components/table/src/types.ts
+++ b/packages/components/table/src/types.ts
@@ -9,6 +9,7 @@
import type { TableColumnMerged, TableColumnMergedExtra } from './composables/useColumns'
import type { BreakpointKey } from '@idux/cdk/breakpoint'
+import type { VirtualScrollToFn } from '@idux/cdk/scroll'
import type { IxInnerPropTypes, IxPublicPropTypes } from '@idux/cdk/utils'
import type { EmptyProps } from '@idux/components/empty'
import type { HeaderProps } from '@idux/components/header'
@@ -16,13 +17,16 @@ import type { PaginationProps } from '@idux/components/pagination'
import type { SpinProps } from '@idux/components/spin'
import type { DefineComponent, HTMLAttributes, VNodeTypes } from 'vue'
-import { VirtualScrollToFn } from '@idux/cdk/scroll'
import { IxPropTypes } from '@idux/cdk/utils'
+import { FlattedData } from './composables/useDataSource'
+
export const tableProps = {
expandedRowKeys: IxPropTypes.array(),
selectedRowKeys: IxPropTypes.array(),
+ expandedAllStatus: IxPropTypes.bool,
+
borderless: IxPropTypes.bool,
childrenKey: IxPropTypes.string.def('children'),
columns: IxPropTypes.array>().def(() => []),
@@ -61,7 +65,10 @@ export type TableComponent = DefineComponent<
>
export type TableInstance = InstanceType>
-export type TableColumn = TableColumnBase | TableColumnExpandable | TableColumnSelectable
+export type TableColumn =
+ | TableColumnBase
+ | TableColumnExpandable
+ | TableColumnSelectable
export interface TableColumnCommon {
additional?: {
@@ -78,12 +85,13 @@ export interface TableColumnCommon {
width?: string | number
}
-export interface TableColumnBase extends TableColumnCommon {
+export interface TableColumnBase extends TableColumnCommon {
dataKey?: Key | Key[]
editable?: boolean
ellipsis?: boolean
key?: Key
sortable?: TableColumnSortable
+ filterable?: TableColumnFilterable
title?: string
children?: TableColumn[]
customRender?: string | TableColumnRenderFn
@@ -98,12 +106,12 @@ export interface TableColumnRenderOption {
export type TableColumnRenderFn = (options: TableColumnRenderOption) => VNodeTypes
export type TableColumnTitleFn = (options: { title?: string }) => VNodeTypes
-export interface TableColumnExpandable extends TableColumnCommon {
+export interface TableColumnExpandable extends TableColumnBase {
type: 'expandable'
customExpand?: string | TableColumnExpandableExpandFn
customIcon?: string | TableColumnExpandableIconFn
- disabled?: (record: T, rowIndex: number) => boolean
+ disabled?: (record: T) => boolean
// remove ?
icon?: [string, string]
indent?: number
@@ -181,6 +189,18 @@ export interface TableColumnSortable {
onChange?: (currOrderBy?: TableColumnSortOrder) => void
}
+export interface TableColumnFilter {
+ text: string
+ value: V
+}
+
+export interface TableColumnFilterable {
+ filters: TableColumnFilter[]
+ filterBy?: V[]
+ filter: (currFilterBy: V[], record: T) => boolean
+ onChange?: (currFilterBy: V[]) => void
+}
+
export interface TableSticky {
offsetHead?: number
offsetFoot?: number
@@ -208,6 +228,7 @@ export const tableBodyRowProps = {
rowIndex: IxPropTypes.number.isRequired,
level: IxPropTypes.number.isRequired,
record: IxPropTypes.any.isRequired,
+ rowData: IxPropTypes.object().isRequired,
rowKey: IxPropTypes.oneOfType([String, Number]).isRequired,
}
@@ -216,6 +237,7 @@ export type TableBodyRowProps = IxInnerPropTypes
export const tableBodyCellProps = {
column: IxPropTypes.object().isRequired,
colIndex: IxPropTypes.number.isRequired,
+ level: IxPropTypes.number.isRequired,
record: IxPropTypes.any.isRequired,
rowIndex: IxPropTypes.number.isRequired,
disabled: IxPropTypes.bool,
@@ -234,3 +256,12 @@ export const tableMeasureCellProps = {
}
export type TableMeasureCellProps = IxInnerPropTypes
+
+export const tableFilterPanelProps = {
+ filterable: IxPropTypes.object().isRequired,
+}
+
+export type TableFilterPanelProps = IxInnerPropTypes
+
+export const tableHeadCellFilterableTriggerProps = tableFilterPanelProps
+export type TableHeadCellFilterableTriggerProps = TableFilterPanelProps
diff --git a/packages/components/table/style/filterDropdown.less b/packages/components/table/style/filterDropdown.less
new file mode 100644
index 000000000..119728681
--- /dev/null
+++ b/packages/components/table/style/filterDropdown.less
@@ -0,0 +1,24 @@
+.@{table-prefix}-filter-dropdown {
+ display: inline-flex;
+ padding: @table-filter-dropdown-padding;
+ background-color: @table-filter-dropdown-background-color;
+ box-shadow: @table-filter-dropdown-box-shadow;
+
+ &-group {
+ display: inline-flex;
+ flex-direction: column;
+ }
+
+ &-item {
+ min-width: @table-filter-dropdown-width;
+ height: @table-filter-dropdown-item-height;
+ padding: @table-filter-dropdown-item-padding;
+ display: inline-flex;
+ align-items: center;
+ justify-content: flex-start;
+
+ &:hover {
+ background-color: @table-filter-dropdown-item-hover-background-color;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/components/table/style/headerIcon.less b/packages/components/table/style/headerIcon.less
new file mode 100644
index 000000000..7b93ae3a4
--- /dev/null
+++ b/packages/components/table/style/headerIcon.less
@@ -0,0 +1,20 @@
+.header-icon() {
+ min-width: @table-head-icon-size;
+ min-height: @table-head-icon-size;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 1px;
+ font-size: @table-head-icon-font-size;
+ color: @table-head-icon-color;
+ transition: color, background @table-head-icon-transition-duration;
+ cursor: pointer;
+
+ &:hover {
+ background-color: @table-head-icon-hover-backgroud-color;
+ }
+
+ &-active {
+ color: @color-primary;
+ }
+}
\ No newline at end of file
diff --git a/packages/components/table/style/index.less b/packages/components/table/style/index.less
index 7ae30e83b..360cb41de 100644
--- a/packages/components/table/style/index.less
+++ b/packages/components/table/style/index.less
@@ -6,11 +6,14 @@
@import './fixed.less';
@import './radius.less';
@import './size.less';
+@import './headerIcon.less';
+@import './filterDropdown.less';
.@{table-prefix} {
.reset-component();
.clearfix();
+ color: @table-color;
max-width: 100%;
&-container {
@@ -49,6 +52,7 @@
color: @table-head-color;
background: @table-head-background;
font-weight: @table-head-font-weight;
+ line-height: @table-head-line-height;
overflow-wrap: break-word;
border-bottom: @table-border-width @table-border-style @table-border-color;
@@ -78,6 +82,7 @@
td {
position: relative;
border-bottom: @table-border-width @table-border-style @table-border-color;
+ line-height: @table-body-line-height;
overflow-wrap: break-word;
transition: background 0.3s;
@@ -120,13 +125,19 @@
&-cell-title {
position: relative;
z-index: 1;
- flex: 1;
}
&-cell-scroll-bar {
box-shadow: 0 1px 0 1px @table-head-background;
}
+ & &-head-cell-wrapper,
+ & &-head-icon-triggers {
+ display: inline-flex;
+ align-items: center;
+ justify-content: flex-start;
+ }
+
&-pagination {
display: flex;
flex-wrap: wrap;
@@ -149,6 +160,14 @@
}
}
+ & &-expandable-icon {
+ width: @table-expandable-icon-size;
+ height: @table-expandable-icon-size;
+ font-size: @table-expandable-icon-size;
+ margin-right: @table-icon-margin;
+ color: @table-expandable-icon-color;
+ }
+
// --------- Selectable ---------
& &-col-selectable {
@@ -160,10 +179,19 @@
}
& &-cell-selectable {
- display: flex;
- align-items: center;
- & > .@{idux-prefix}-dropdown-trigger {
- margin-left: 4px;
+ .@{table-prefix}-selectable {
+ display: flex;
+ align-items: center;
+
+ &-trigger {
+ margin-left: @table-icon-margin;
+ color: @table-head-icon-color;
+ cursor: pointer;
+
+ &--open {
+ transform: rotateX(180deg);
+ }
+ }
}
}
@@ -189,32 +217,21 @@
}
}
- & th&-cell-sorted,
- & td&-cell-sorted {
- background: @table-body-hover-background;
- }
+ &-sortable-trigger {
+ .header-icon();
- &-sortable {
- display: flex;
- flex: auto;
- align-items: center;
- justify-content: space-between;
+ flex-direction: column;
+ font-size: 8px;
+ margin-left: @table-icon-margin;
- &-trigger {
- display: inline-flex;
- flex-direction: column;
- align-items: center;
- color: @table-head-icon-color;
- font-size: 10px;
- transition: color 0.3s;
+ .@{idux-prefix}-icon + .@{idux-prefix}-icon {
+ margin-top: -0.2em;
+ }
+ }
- &-active {
- color: @color-primary;
- }
+ &-filterable-trigger {
+ .header-icon();
- .@{idux-prefix}-icon + .@{idux-prefix}-icon {
- margin-top: -0.2em;
- }
- }
+ margin-left: @table-icon-margin;
}
}
diff --git a/packages/components/table/style/size.less b/packages/components/table/style/size.less
index ee867ec1d..5e62a467b 100644
--- a/packages/components/table/style/size.less
+++ b/packages/components/table/style/size.less
@@ -1,8 +1,12 @@
-.table-size(@size, @padding-vertical, @padding-horizontal, @font-size) {
+.table-size(@size, @head-height, @padding-vertical, @padding-horizontal, @font-size) {
.@{table-prefix}-@{size} {
font-size: @font-size;
- th,
+ th {
+ padding: 0 @padding-horizontal;
+ height: @head-height;
+ }
+
td {
padding: @padding-vertical @padding-horizontal;
}
@@ -11,6 +15,6 @@
}
}
-.table-size(~'lg', @table-padding-vertical-lg, @table-padding-horizontal-lg, @table-font-size-lg);
-.table-size(~'md', @table-padding-vertical-md, @table-padding-horizontal-md, @table-font-size-md);
-.table-size(~'sm', @table-padding-vertical-sm, @table-padding-horizontal-sm, @table-font-size-sm);
+.table-size(~'lg', @table-head-height-lg, @table-padding-vertical-lg, @table-padding-horizontal-lg, @table-font-size-lg);
+.table-size(~'md', @table-head-height-md, @table-padding-vertical-md, @table-padding-horizontal-md, @table-font-size-md);
+.table-size(~'sm', @table-head-height-sm, @table-padding-vertical-sm, @table-padding-horizontal-sm, @table-font-size-sm);
diff --git a/packages/components/table/style/themes/default.less b/packages/components/table/style/themes/default.less
index 329463be6..00f3aac53 100644
--- a/packages/components/table/style/themes/default.less
+++ b/packages/components/table/style/themes/default.less
@@ -1,3 +1,4 @@
@import '../../../style/themes/default.less';
+@import '../../../form/style/themes/default.variable.less';
@import '../index.less';
@import './default.variable.less';
diff --git a/packages/components/table/style/themes/default.variable.less b/packages/components/table/style/themes/default.variable.less
index f79d035ac..e78da9e28 100644
--- a/packages/components/table/style/themes/default.variable.less
+++ b/packages/components/table/style/themes/default.variable.less
@@ -1,3 +1,12 @@
+@table-color: @text-color;
+
+@table-head-line-height: @line-height-base;
+@table-body-line-height: @line-height-base;
+
+@table-head-height-lg: @height-xxl;
+@table-head-height-md: @height-xl;
+@table-head-height-sm: @height-md;
+
@table-padding-vertical-lg: @spacing-lg;
@table-padding-horizontal-lg: @spacing-lg;
@table-padding-vertical-md: @spacing-md;
@@ -5,7 +14,7 @@
@table-padding-vertical-sm: @spacing-sm;
@table-padding-horizontal-sm: @spacing-sm;
-@table-font-size-lg: @font-size-md;
+@table-font-size-lg: @font-size-lg;
@table-font-size-md: @font-size-md;
@table-font-size-sm: @font-size-sm;
@@ -17,10 +26,29 @@
@table-border-radius: @border-radius-sm;
@table-head-background: @background-color-light;
@table-head-color: @color-black;
+@table-head-split-height: 16px;
@table-head-split-color: rgba(0, 0, 0, 0.06);
+
@table-head-icon-color: @color-black;
+@table-head-icon-size: 16px;
+@table-head-icon-font-size: @font-size-xs;
+@table-head-icon-hover-backgroud-color: @color-graphite-l40;
@table-head-font-weight: @font-weight-lg;
+@table-head-icon-transition-duration: @transition-duration-base;
@table-body-hover-background: @background-color-light;
@table-pagination-margin: @spacing-lg 0;
+
+@table-icon-margin: @spacing-xs;
+
+@table-expandable-icon-size: @font-size-md;
+@table-expandable-icon-color: @color-black;
+
+@table-filter-dropdown-width: 120px;
+@table-filter-dropdown-padding: @spacing-xs 0;
+@table-filter-dropdown-box-shadow: @shadow-bottom-md;
+@table-filter-dropdown-background-color: @form-background-color;
+@table-filter-dropdown-item-height: @height-md;
+@table-filter-dropdown-item-padding: 0 @spacing-sm;
+@table-filter-dropdown-item-hover-background-color: @background-color-light;
\ No newline at end of file
diff --git a/scripts/gulp/icons/assets/filter-filled.svg b/scripts/gulp/icons/assets/filter-filled.svg
new file mode 100644
index 000000000..a8ecb9e13
--- /dev/null
+++ b/scripts/gulp/icons/assets/filter-filled.svg
@@ -0,0 +1 @@
+
\ No newline at end of file