diff --git a/packages/components/config/src/defaultConfig.ts b/packages/components/config/src/defaultConfig.ts index e4caf21bb..633c9130a 100644 --- a/packages/components/config/src/defaultConfig.ts +++ b/packages/components/config/src/defaultConfig.ts @@ -254,6 +254,7 @@ const table: TableConfig = { columnBase: { align: 'start', sortable: { nextTooltip: false, orders: ['ascend', 'descend'] }, + filterable: { multiple: true, footer: true }, }, columnExpandable: { icon: ['plus-square', 'minus-square'] }, } diff --git a/packages/components/config/src/types.ts b/packages/components/config/src/types.ts index ddebefd6f..e63aca17e 100644 --- a/packages/components/config/src/types.ts +++ b/packages/components/config/src/types.ts @@ -261,6 +261,7 @@ export interface TableConfig { export interface TableColumnBaseConfig { align: TableColumnAlign sortable: { nextTooltip: boolean; orders: TableColumnSortOrder[] } + filterable: { multiple: boolean; footer: true } } export interface TableColumnExpandableConfig { diff --git a/packages/components/dropdown/style/index.less b/packages/components/dropdown/style/index.less index fad08c334..f5d57c3bb 100644 --- a/packages/components/dropdown/style/index.less +++ b/packages/components/dropdown/style/index.less @@ -2,7 +2,11 @@ @import '../../style/motion/fade.less'; .@{dropdown-prefix} { - .reset-component(); + z-index: @dropdown-overlay-zindex; + min-width: @dropdown-overlay-min-width; + background-color: @dropdown-bg-color; + border-radius: @dropdown-overlay-border-radius; + box-shadow: @dropdown-overlay-box-shadow; &-trigger { cursor: pointer; @@ -12,3 +16,44 @@ color: @dropdown-background-color; } } + +.@{menu-prefix} { + + &&-dropdown { + &.@{menu-prefix}-vertical, + &.@{menu-prefix}-inline { + .@{menu-prefix}-item { + + &-selected { + + &::after { + transform: scaleY(0); + opacity: 0; + } + } + } + } + + &.@{menu-prefix}-horizontal { + .@{menu-prefix}-item, + .@{menu-prefix}-sub { + + &:hover, + &-expanded, + &-selected { + border-color: transparent; + } + } + } + } +} + +.@{menu-prefix}-dropdown { + .@{menu-prefix}-item, + .@{menu-prefix}-sub-title, + .@{menu-prefix}-item-group .@{menu-prefix}-item-group-title { + height: @dropdown-menu-item-height; + line-height: @dropdown-menu-item-height; + margin: @dropdown-menu-item-margin; + } +} diff --git a/packages/components/dropdown/style/themes/default.less b/packages/components/dropdown/style/themes/default.less index 7bfe1181a..711fb59f1 100644 --- a/packages/components/dropdown/style/themes/default.less +++ b/packages/components/dropdown/style/themes/default.less @@ -1,3 +1,2 @@ -@import '../../../style/themes/default.less'; @import './default.variable.less'; @import '../index.less'; diff --git a/packages/components/dropdown/style/themes/default.variable.less b/packages/components/dropdown/style/themes/default.variable.less index a224b1c7a..efdd0b3d2 100644 --- a/packages/components/dropdown/style/themes/default.variable.less +++ b/packages/components/dropdown/style/themes/default.variable.less @@ -1,3 +1,12 @@ +@import '../../../style/themes/default.less'; +@import '../../../menu/style/themes/default.variable.less'; + @dropdown-menu-item-height: @height-md; @dropdown-menu-item-margin: 0; @dropdown-background-color: @background-color-component; + +@dropdown-overlay-zindex: @menu-overlay-zindex; +@dropdown-overlay-min-width: @menu-overlay-min-width; +@dropdown-bg-color: @menu-bg-color; +@dropdown-overlay-border-radius: @menu-overlay-border-radius; +@dropdown-overlay-box-shadow: @menu-overlay-box-shadow; diff --git a/packages/components/menu/style/dropdown.less b/packages/components/menu/style/dropdown.less deleted file mode 100644 index cb8d289b9..000000000 --- a/packages/components/menu/style/dropdown.less +++ /dev/null @@ -1,46 +0,0 @@ -.@{menu-prefix} { - - &&-dropdown { - z-index: @menu-overlay-zindex; - min-width: @menu-overlay-min-width; - background-color: @menu-bg-color; - border-radius: @menu-overlay-border-radius; - box-shadow: @menu-overlay-box-shadow; - - &.@{menu-prefix}-vertical, - &.@{menu-prefix}-inline { - .@{menu-prefix}-item { - - &-selected { - - &::after { - transform: scaleY(0); - opacity: 0; - } - } - } - } - - &.@{menu-prefix}-horizontal { - .@{menu-prefix}-item, - .@{menu-prefix}-sub { - - &:hover, - &-expanded, - &-selected { - border-color: transparent; - } - } - } - } -} - -.@{menu-prefix}-dropdown { - .@{menu-prefix}-item, - .@{menu-prefix}-sub-title, - .@{menu-prefix}-item-group .@{menu-prefix}-item-group-title { - height: @dropdown-menu-item-height; - line-height: @dropdown-menu-item-height; - margin: @dropdown-menu-item-margin; - } -} diff --git a/packages/components/menu/style/index.less b/packages/components/menu/style/index.less index 8f92d28b8..c9d7f0415 100644 --- a/packages/components/menu/style/index.less +++ b/packages/components/menu/style/index.less @@ -5,7 +5,6 @@ @import './menu.less'; @import './dark.less'; @import './collapsed.less'; -@import './dropdown.less'; .@{menu-prefix} { .reset-component(); diff --git a/packages/components/menu/style/themes/default.less b/packages/components/menu/style/themes/default.less index 41d2a79e4..c1c89d158 100644 --- a/packages/components/menu/style/themes/default.less +++ b/packages/components/menu/style/themes/default.less @@ -1,4 +1,2 @@ -@import '../../../style/themes/default.less'; -@import '../../../dropdown/style/themes/default.variable.less'; @import '../index.less'; @import './default.variable.less'; diff --git a/packages/components/menu/style/themes/default.variable.less b/packages/components/menu/style/themes/default.variable.less index c0cf0361c..6595d383f 100644 --- a/packages/components/menu/style/themes/default.variable.less +++ b/packages/components/menu/style/themes/default.variable.less @@ -1,3 +1,5 @@ +@import '../../../style/themes/default.less'; + @menu-text-color: @text-color; @menu-bg-color: @background-color-component; @menu-highlight-color: @color-primary; diff --git a/packages/components/table/demo/Filterable.md b/packages/components/table/demo/Filterable.md index 2e560ea21..195a37683 100644 --- a/packages/components/table/demo/Filterable.md +++ b/packages/components/table/demo/Filterable.md @@ -2,7 +2,7 @@ title: zh: 可筛选 en: Filterable -order: 52 +order: 45 --- ## zh diff --git a/packages/components/table/demo/Filterable.vue b/packages/components/table/demo/Filterable.vue index c697c4d6e..2764cb687 100644 --- a/packages/components/table/demo/Filterable.vue +++ b/packages/components/table/demo/Filterable.vue @@ -30,18 +30,14 @@ const columns: TableColumn[] = [ sorter: (curr, next) => curr.age - next.age, }, filterable: { - filters: [ - { - text: 'over 19', - value: (age: number) => age > 19, - }, - { - text: 'below 21', - value: (age: number) => age < 21, - }, + menus: [ + { key: 'over', label: 'Over 19' }, + { key: 'under', label: 'Under 21' }, ], - filter(currentFilterBy, record) { - return currentFilterBy.every(filterBy => filterBy(record.age)) + multiple: false, + filter: (currentFilterBy, record) => { + const isOver = currentFilterBy.includes('over') + return isOver ? record.age > 19 : record.age < 21 }, }, }, @@ -53,18 +49,12 @@ const columns: TableColumn[] = [ title: 'Address', dataKey: 'address', filterable: { - filters: [ - { - text: 'Sidney', - value: 'Sidney', - }, - { - text: 'New York', - value: 'New York', - }, + menus: [ + { key: 'Sidney', label: 'Sidney' }, + { key: 'New York', label: 'New York' }, ], - filter(currentFilterBy, record) { - return currentFilterBy.every(filterBy => record.address.includes(filterBy)) + filter: (currentFilterBy, record) => { + return currentFilterBy.some(filterBy => record.address.includes(filterBy as string)) }, }, }, diff --git a/packages/components/table/demo/FilterableFilterBy.md b/packages/components/table/demo/FilterableFilterBy.md index 34236443a..86fb7c58e 100644 --- a/packages/components/table/demo/FilterableFilterBy.md +++ b/packages/components/table/demo/FilterableFilterBy.md @@ -2,13 +2,13 @@ title: zh: 受控的筛选 en: Controlled filterable -order: 53 +order: 46 --- ## zh -如果设置了 `filterable` 的 `filterby`,表格列的筛选将为受控状态。 +如果设置了 `filterable` 的 `filterBy`,表格列的筛选将为受控状态。 ## en -If `filterby` of `filterable` is set, the filtering of the column will be in a controlled state. +If `filterBy` of `filterable` is set, the filtering of the column will be in a controlled state. diff --git a/packages/components/table/demo/FilterableFilterBy.vue b/packages/components/table/demo/FilterableFilterBy.vue index 8ec0e5aae..bda5eed67 100644 --- a/packages/components/table/demo/FilterableFilterBy.vue +++ b/packages/components/table/demo/FilterableFilterBy.vue @@ -24,50 +24,38 @@ interface Data { address: string } -type AgeFilter = (age: number) => boolean - -const ageFilterable: TableColumnFilterable = reactive({ - filters: [ - { - text: 'over 19', - value: (age: number) => age > 19, - }, - { - text: 'below 21', - value: (age: number) => age < 21, - }, +const ageFilterable: TableColumnFilterable = reactive({ + menus: [ + { key: 'over', label: 'Over 19' }, + { key: 'under', label: 'Under 21' }, ], - filter(currentFilterBy, record) { - return currentFilterBy.every(filterBy => filterBy(record.age)) + multiple: false, + filter: (currentFilterBy, record) => { + const isOver = currentFilterBy.includes('over') + return isOver ? record.age > 19 : record.age < 21 }, filterBy: [], - onChange(currentFilterBy) { + onChange: currentFilterBy => { ageFilterable.filterBy = currentFilterBy }, }) -const addressFilterable: TableColumnFilterable = reactive({ - filters: [ - { - text: 'Sidney', - value: 'Sidney', - }, - { - text: 'New York', - value: 'New York', - }, +const addressFilterable: TableColumnFilterable = reactive({ + menus: [ + { key: 'Sidney', label: 'Sidney' }, + { key: 'New York', label: 'New York' }, ], - filter(currentFilterBy, record) { - return currentFilterBy.every(filterBy => record.address.includes(filterBy)) + filter: (currentFilterBy, record) => { + return currentFilterBy.some(filterBy => record.address.includes(filterBy as string)) }, filterBy: [], - onChange(currentFilterBy) { + onChange: currentFilterBy => { addressFilterable.filterBy = currentFilterBy }, }) const filterByAge19 = () => { - ageFilterable.filterBy = [ageFilterable.filters[0].value] + ageFilterable.filterBy = ['over'] addressFilterable.filterBy = [] } @@ -94,7 +82,7 @@ const columns: TableColumn[] = [ sorter: (curr, next) => curr.age - next.age, }, filterable: ageFilterable, - } as TableColumn, + }, { title: 'Grade', dataKey: 'grade', @@ -103,7 +91,7 @@ const columns: TableColumn[] = [ title: 'Address', dataKey: 'address', filterable: addressFilterable, - } as TableColumn, + }, ] const data: Data[] = [ diff --git a/packages/components/table/demo/Tree.vue b/packages/components/table/demo/Tree.vue index a0cd16a4a..690d759c1 100644 --- a/packages/components/table/demo/Tree.vue +++ b/packages/components/table/demo/Tree.vue @@ -62,24 +62,14 @@ const columns: TableColumn[] = [ return h(IxTag, { color }, { default: () => tag.toUpperCase() }) }), }, - filterable: { - filters: [ - { - text: 'attack', - value: 'attack', - }, - { - text: 'damage', - value: 'damage', - }, - { - text: 'infiltrate', - value: 'infiltrate', - }, + menus: [ + { key: 'attack', label: 'Attack' }, + { key: 'damage', label: 'Damage' }, + { key: 'infiltrate', label: 'Infiltrate' }, ], - filter(tags, record) { - return tags.every(tag => record.tags.includes(tag)) + filter: (tags, record) => { + return tags.some(tag => record.tags.includes(tag as string)) }, }, }, diff --git a/packages/components/table/docs/Index.zh.md b/packages/components/table/docs/Index.zh.md index 294c4c424..fc41d2bbe 100644 --- a/packages/components/table/docs/Index.zh.md +++ b/packages/components/table/docs/Index.zh.md @@ -108,7 +108,7 @@ export interface TableColumnExpandableSlots { // 自定义展开内容 expand?: string | ((data: { record: T; rowIndex: number }) => VNodeChild) // 自定义展开图标 - icon?: string | ((data: { expanded: boolean; record: T; onExpand: () => void }) => VNodeChild) + icon?: string | ((data: { expanded: boolean; record: T }) => VNodeChild) } ``` diff --git a/packages/components/table/index.ts b/packages/components/table/index.ts index 4a7a14093..9a137633b 100644 --- a/packages/components/table/index.ts +++ b/packages/components/table/index.ts @@ -8,12 +8,13 @@ import type { TableComponent } from './src/types' import Table from './src/Table' -import { TableColumn } from './src/tableColumn' +import { TableColumn } from './src/column' +import { TABLE_TOKEN } from './src/token' const IxTable = Table as unknown as TableComponent const IxTableColumn = TableColumn -export { IxTable, IxTableColumn } +export { IxTable, IxTableColumn, TABLE_TOKEN } export type { TableInstance, diff --git a/packages/components/table/src/Table.tsx b/packages/components/table/src/Table.tsx index 18fb1c4c7..772dd050c 100644 --- a/packages/components/table/src/Table.tsx +++ b/packages/components/table/src/Table.tsx @@ -19,7 +19,7 @@ import { IxSpin } from '@idux/components/spin' import { useColumns } from './composables/useColumns' import { useDataSource } from './composables/useDataSource' import { useExpandable } from './composables/useExpandable' -import { useFilterables } from './composables/useFilterable' +import { useFilterable } from './composables/useFilterable' import { useGetRowKey } from './composables/useGetRowKey' import { usePagination } from './composables/usePagination' import { useScroll } from './composables/useScroll' @@ -49,7 +49,7 @@ export default defineComponent({ const scrollContext = useScroll(props, stickyContext) const columnsContext = useColumns(props, slots, config, scrollContext.scrollBarSizeOnFixedHolder) const sortableContext = useSortable(columnsContext.flattedColumns) - const filterableContext = useFilterables(columnsContext.flattedColumns) + const filterableContext = useFilterable(columnsContext.flattedColumns) const expandableContext = useExpandable(props, columnsContext.flattedColumns) const tableLayout = useTableLayout(props, columnsContext, scrollContext, stickyContext.isSticky) const { mergedPagination } = usePagination(props, config) @@ -58,7 +58,7 @@ export default defineComponent({ props, getRowKey, sortableContext.activeSortable, - filterableContext.activeFilterables, + filterableContext.activeFilters, expandableContext.expandedRowKeys, mergedPagination, ) diff --git a/packages/components/table/src/tableColumn.ts b/packages/components/table/src/column.ts similarity index 100% rename from packages/components/table/src/tableColumn.ts rename to packages/components/table/src/column.ts diff --git a/packages/components/table/src/composables/useColumns.ts b/packages/components/table/src/composables/useColumns.ts index 77aec69c9..d40b7d9b4 100644 --- a/packages/components/table/src/composables/useColumns.ts +++ b/packages/components/table/src/composables/useColumns.ts @@ -13,7 +13,7 @@ import { type BreakpointKey, useSharedBreakpoints } from '@idux/cdk/breakpoint' import { type VKey, convertArray, flattenNode } from '@idux/cdk/utils' import { type TableColumnBaseConfig, type TableColumnExpandableConfig, type TableConfig } from '@idux/components/config' -import { tableColumnKey } from '../tableColumn' +import { tableColumnKey } from '../column' import { type TableColumn, type TableColumnAlign, @@ -113,7 +113,7 @@ export interface TableColumnMergedExpandable extends TableColumnMergedBaseExtra, key: VKey icon: [string, string] titleColSpan: number - slots: TableColumnExpandableSlots + slots?: TableColumnExpandableSlots } export interface TableColumnMergedSelectable extends TableColumnMergedBaseExtra, TableColumnSelectable { align: TableColumnAlign @@ -139,7 +139,7 @@ function mergeColumns( ): TableColumnMerged[] { return columns .filter(column => !column.responsive || column.responsive.some(key => breakpoints[key])) - .map(column => covertColumn(column, breakpoints, baseConfig, expandableConfig)) + .map((column, index) => covertColumn(column, breakpoints, baseConfig, expandableConfig, index)) } function convertColumns( @@ -150,7 +150,7 @@ function convertColumns( ) { const mergedColumns: Array = [] - flattenNode(nodes, { key: tableColumnKey }).forEach(node => { + flattenNode(nodes, { key: tableColumnKey }).forEach((node, index) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const { props, children } = node as VNode & { children: any } const { key = Symbol(), editable, ellipsis, slots, ...newColumn } = props || {} @@ -162,7 +162,7 @@ function convertColumns( if (defaultSlot) { newColumn.children = convertColumns(defaultSlot(), breakpoints, baseConfig, expandableConfig) } - mergedColumns.push(covertColumn(newColumn as TableColumnMerged, breakpoints, baseConfig, expandableConfig)) + mergedColumns.push(covertColumn(newColumn as TableColumnMerged, breakpoints, baseConfig, expandableConfig, index)) }) return mergedColumns @@ -173,6 +173,7 @@ function covertColumn( breakpoints: Record, baseConfig: TableColumnBaseConfig, expandableConfig: TableColumnExpandableConfig, + index: number, ): TableColumnMerged { const { align = baseConfig.align } = column @@ -187,12 +188,15 @@ function covertColumn( return { ...column, key, align, multiple } } } else { - const { key, dataKey, sortable, children } = column - const _key = key ?? (convertArray(dataKey).join('-') || Symbol()) + const { key, dataKey, sortable, filterable, children } = column + const _key = key ?? (convertArray(dataKey).join('-') || `'IDUX_TABLE_KEY_${index}`) const newColumn = { ...column, key: _key, align } if (sortable) { newColumn.sortable = { ...baseConfig.sortable, ...sortable } } + if (filterable) { + newColumn.filterable = { ...baseConfig.filterable, ...filterable } + } if (children?.length) { newColumn.children = mergeColumns(children, breakpoints, baseConfig, expandableConfig) } diff --git a/packages/components/table/src/composables/useDataSource.ts b/packages/components/table/src/composables/useDataSource.ts index 5f348f331..5350c0eef 100644 --- a/packages/components/table/src/composables/useDataSource.ts +++ b/packages/components/table/src/composables/useDataSource.ts @@ -10,7 +10,7 @@ import { type ComputedRef, type Ref, computed } from 'vue' import { type VKey } from '@idux/cdk/utils' import { type TablePagination, type TableProps } from '../types' -import { type Filterable } from './useFilterable' +import { type ActiveFilter } from './useFilterable' import { type GetRowKey } from './useGetRowKey' import { type ActiveSortable } from './useSortable' @@ -18,7 +18,7 @@ export function useDataSource( props: TableProps, getRowKey: ComputedRef, activeSortable: ActiveSortable, - activeFilterables: ComputedRef, + activeFilters: ComputedRef, expandedRowKeys: Ref, mergedPagination: ComputedRef, ): DataSourceContext { @@ -34,11 +34,11 @@ export function useDataSource( return map }) - const filteredData = computed(() => filterData(mergedData.value, activeFilterables.value)) + const filteredData = computed(() => filterData(mergedData.value, activeFilters.value, expandedRowKeys.value)) const sortedData = computed(() => { const { sorter, orderBy } = activeSortable if (sorter && orderBy) { - return sortData(filteredData.value, sorter, orderBy) + return sortData(filteredData.value, sorter, orderBy, expandedRowKeys.value) } return filteredData.value @@ -69,7 +69,7 @@ export function useDataSource( if (expandedKeys.length > 0) { return flatData(paginatedData.value, expandedKeys, 0) } - return paginatedData.value.map((item, idx) => ({ ...item, expanded: false, level: 0, rowIndex: idx })) + return paginatedData.value }) return { filteredData, flattedData, mergedMap, paginatedMap } @@ -90,8 +90,8 @@ export interface MergedData { } export interface FlattedData extends MergedData { - expanded: boolean - level: number + expanded?: boolean + level?: number } function covertMergeData(record: unknown, getRowKey: GetRowKey, childrenKey: string, parentKey?: VKey) { @@ -119,38 +119,45 @@ function sortData( mergedData: MergedData[], sorter: (curr: unknown, next: unknown) => number, orderBy: 'ascend' | 'descend', + expandedRowKeys: VKey[], ) { const tempData = mergedData.slice() const orderFlag = orderBy === 'ascend' ? 1 : -1 tempData.forEach(item => { - if (item.children?.length && item.children.length > 0) { - item.children = sortData(item.children, sorter, orderBy) + if (expandedRowKeys.includes(item.rowKey) && item.children && item.children.length > 0) { + item.children = sortData(item.children, sorter, orderBy, expandedRowKeys) } }) - return tempData.sort((curr, next) => orderFlag * sorter(curr.record, next.record)) } -function filterData(mergedData: MergedData[], activeFilterables: Filterable[]): MergedData[] { +function filterData(mergedData: MergedData[], activeFilters: ActiveFilter[], expandedRowKeys: VKey[]): MergedData[] { + if (activeFilters.length === 0) { + return mergedData + } + return mergedData .map(item => { - const newItem = { ...item } - - const itemValid = activeFilterables.every(filterable => filterable.filter(filterable.filterBy ?? [], item.record)) - - if (item.children?.length && item.children.length > 0) { - newItem.children = filterData(item.children, activeFilterables) + const valid = activeFilters.every(({ filter, filterBy }) => filter(filterBy, item.record)) + + const { rowKey, children } = item + const isExpanded = expandedRowKeys.includes(rowKey) + let newItem = item + if (isExpanded && children && children.length) { + const newChildren = filterData(children, activeFilters, expandedRowKeys) + if (newChildren.length !== children.length) { + newItem = { ...item, children: newChildren } + } } - - return (newItem.children && newItem.children.length > 0) || itemValid ? newItem : null + return valid || (isExpanded && newItem.children) ? newItem : null }) - .filter(item => !!item) as MergedData[] + .filter(item => item !== null) as MergedData[] } // TODO: performance optimization // when virtual scrolling is enabled, this do not need to traverse all nodes -function flatData(mergedData: MergedData[], expandedRowKeys: VKey[], level = 0) { +function flatData(mergedData: MergedData[], expandedRowKeys: VKey[], level: number) { return mergedData.reduce((result, item) => { const { children, parentKey, record, rowKey } = item const expanded = expandedRowKeys.includes(rowKey) diff --git a/packages/components/table/src/composables/useExpandable.ts b/packages/components/table/src/composables/useExpandable.ts index 91b7ca61e..b33cbeadb 100644 --- a/packages/components/table/src/composables/useExpandable.ts +++ b/packages/components/table/src/composables/useExpandable.ts @@ -40,14 +40,17 @@ export function useExpandable(props: TableProps, flattedColumns: ComputedRef 0)) + return !(customExpand || slots?.expand || (data.children && data.children.length > 0)) } return { expandable, expandedRowKeys, setExpandedRowKeys, checkExpandDisabled, handleExpandChange } diff --git a/packages/components/table/src/composables/useFilterable.ts b/packages/components/table/src/composables/useFilterable.ts index 2313a4976..a6dd5cf94 100644 --- a/packages/components/table/src/composables/useFilterable.ts +++ b/packages/components/table/src/composables/useFilterable.ts @@ -5,50 +5,58 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import { type ComputedRef, computed, reactive } from 'vue' +import { type ComputedRef, computed, reactive, watch } from 'vue' -import { type VKey, callEmit } from '@idux/cdk/utils' +import { type VKey } from '@idux/cdk/utils' -import { type TableColumnFilterable } from '../types' import { type TableColumnMerged } from './useColumns' -export function useFilterables(flattedColumns: ComputedRef): FilterableContext { - const tempFilterByMap = reactive(new Map()) +export interface ActiveFilter { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + filter: (currFilterBy: VKey[], record: any) => boolean + filterBy: VKey[] +} - const filterables = computed(() => - flattedColumns.value - .filter(column => !!column.filterable) - .map(column => { - const { filterable, key } = column +export interface FilterableContext { + activeFilters: ComputedRef + filterByMap: Record + setFilterBy: (key: VKey, filterBy: VKey[]) => void +} - const onChange = (value: unknown[]) => { - tempFilterByMap.set(key, value) - callEmit(filterable!.onChange, value) - } +export function useFilterable(flattedColumns: ComputedRef): FilterableContext { + const filterByMap = reactive>({}) + const setFilterBy = (key: VKey, filterBy: VKey[]) => { + filterByMap[key] = filterBy + } - return { - key: column.key, - filters: filterable!.filters, - filterBy: filterable!.filterBy ?? tempFilterByMap.get(key), - filter: filterable!.filter, - onChange, + const filterableColumns = computed(() => flattedColumns.value.filter(column => !!column.filterable)) + watch( + filterableColumns, + columns => { + columns.forEach(column => { + if (filterByMap[column.key] === undefined) { + filterByMap[column.key] = column.filterable?.filterBy || [] } - }), + }) + }, + { immediate: true }, ) - const activeFilterables = computed(() => filterables.value.filter(f => f.filterBy && f.filterBy.length > 0)) + const activeFilters = computed( + () => + filterableColumns.value + .map(column => { + const filterable = column.filterable! + const filter = filterable.filter + const filterBy = filterable.filterBy || filterByMap[column.key] + return { filter, filterBy } + }) + .filter(item => item.filter && item.filterBy.length > 0) as ActiveFilter[], + ) return { - filterables, - activeFilterables, + activeFilters, + filterByMap, + setFilterBy, } } - -export interface Filterable extends TableColumnFilterable { - key: VKey -} - -export interface FilterableContext { - filterables: ComputedRef[]> - activeFilterables: ComputedRef[]> -} diff --git a/packages/components/table/src/main/MainTable.tsx b/packages/components/table/src/main/MainTable.tsx index 2b51fcf90..31bd79c70 100644 --- a/packages/components/table/src/main/MainTable.tsx +++ b/packages/components/table/src/main/MainTable.tsx @@ -153,7 +153,7 @@ export default defineComponent({ const prefixCls = mergedPrefixCls.value if (scrollVertical.value || isSticky.value) { - const tableHead = props.headless ? null : ( + const tableHead = !props.headless && ( @@ -221,7 +221,7 @@ export default defineComponent({
- {props.headless ? null : } + {!props.headless && } diff --git a/packages/components/table/src/main/body/Body.tsx b/packages/components/table/src/main/body/Body.tsx index 4b9d96103..4d21cc705 100644 --- a/packages/components/table/src/main/body/Body.tsx +++ b/packages/components/table/src/main/body/Body.tsx @@ -7,9 +7,7 @@ import { type VNodeTypes, computed, defineComponent, inject } from 'vue' -import { isString } from 'lodash-es' - -import { IxEmpty } from '@idux/components/empty' +import { ɵEmpty } from '@idux/components/_private/empty' import { TABLE_TOKEN } from '../../token' import BodyRow from './BodyRow' @@ -29,16 +27,11 @@ export default defineComponent({ } = inject(TABLE_TOKEN)! const showMeasure = computed(() => scrollHorizontal.value || scrollVertical.value || isSticky.value) - const emptyProps = computed(() => { - const { empty } = props - return isString(empty) ? { description: empty } : empty - }) return () => { const children: VNodeTypes[] = [] if (tableSlots.alert) { - const alertNode = tableSlots.alert() - children.push({alertNode}) + children.push({tableSlots.alert()}) } const data = flattedData.value if (data.length > 0) { @@ -52,13 +45,13 @@ export default defineComponent({ }) } } else { - children.push({slots.empty?.() ?? }) + children.push({slots.empty ? slots.empty() : <ɵEmpty empty={props.empty} />}) } // eslint-disable-next-line @typescript-eslint/no-explicit-any const BodyTag = bodyTag.value as any return ( - {showMeasure.value ? : null} + {showMeasure.value && } {children} ) diff --git a/packages/components/table/src/main/body/BodyCell.tsx b/packages/components/table/src/main/body/BodyCell.tsx index f7890cb97..5728140bf 100644 --- a/packages/components/table/src/main/body/BodyCell.tsx +++ b/packages/components/table/src/main/body/BodyCell.tsx @@ -164,7 +164,7 @@ function renderExpandableChildren( * @deprecated customIcon */ const { icon, customIcon, slots: columnSlots, indent } = expandable.value! - const { record, expanded, level, disabled } = props + const { record, expanded, level = 0, disabled } = props const onExpand = props.handleExpend! const style = { marginLeft: indent ? convertCssPixel(level * indent) : undefined, @@ -173,19 +173,20 @@ function renderExpandableChildren( Logger.warn('components/table', '`customIcon` is deprecated, please use `icon` in `slots` instead') } const iconRender = customIcon ?? columnSlots?.icon + let iconNode: VNodeTypes | null if (disabled) { iconNode = null } else if (isFunction(iconRender)) { - iconNode = iconRender({ expanded: !!expanded, record, onExpand }) + iconNode = iconRender({ expanded: !!expanded, record }) } else if (isString(iconRender) && slots[iconRender]) { - iconNode = slots[iconRender]!({ expanded, record, onExpand }) + iconNode = slots[iconRender]!({ expanded, record }) } else { - iconNode = + iconNode = } return ( - ) diff --git a/packages/components/table/src/main/body/BodyRow.tsx b/packages/components/table/src/main/body/BodyRow.tsx index fa7ca2eea..2bca9e697 100644 --- a/packages/components/table/src/main/body/BodyRow.tsx +++ b/packages/components/table/src/main/body/BodyRow.tsx @@ -95,7 +95,7 @@ function useClasses( const { level, expanded } = props const computeRowClassName = rowClassName.value return { - [`${prefixCls}-level-${level}`]: level > 0, + [`${prefixCls}-level-${level}`]: !!level, [`${prefixCls}-selected`]: isSelected.value, [`${prefixCls}-expanded`]: expanded, [computeRowClassName as string]: !!computeRowClassName, diff --git a/packages/components/table/src/main/head-trigger/HeadCellFilterableTrigger.tsx b/packages/components/table/src/main/head-trigger/HeadCellFilterableTrigger.tsx deleted file mode 100644 index 11bdcc284..000000000 --- a/packages/components/table/src/main/head-trigger/HeadCellFilterableTrigger.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @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/HeadCell.tsx b/packages/components/table/src/main/head/HeadCell.tsx index 7116e5a1a..028e249c1 100644 --- a/packages/components/table/src/main/head/HeadCell.tsx +++ b/packages/components/table/src/main/head/HeadCell.tsx @@ -5,19 +5,19 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import { type CSSProperties, type Slots, type VNodeTypes, computed, defineComponent, inject } from 'vue' +import { ComputedRef, type Slots, type VNodeTypes, computed, defineComponent, inject } from 'vue' import { isFunction, isString } from 'lodash-es' -import { Logger, convertCssPixel } from '@idux/cdk/utils' +import { Logger, type VKey, callEmit, convertCssPixel } from '@idux/cdk/utils' import { type TableColumnMergedExtra } from '../../composables/useColumns' import { TABLE_TOKEN } from '../../token' import { type TableColumnFilterable, type TableColumnSortable, tableHeadCellProps } from '../../types' import { getColTitle } from '../../utils' -import HeadCellFilterableTrigger from '../head-trigger/HeadCellFilterableTrigger' -import HeadCellSortableTrigger from '../head-trigger/HeadCellSortableTrigger' -import HeadCellSelectable from './HeadCellSelectable' +import FilterableTrigger from './triggers/FilterableTrigger' +import SelectableTrigger from './triggers/SelectableTrigger' +import SortableTrigger from './triggers/SortableTrigger' type HeadColumn = TableColumnMergedExtra & { type: 'selectable' | 'expandable' | 'scroll-bar' | undefined @@ -36,28 +36,20 @@ export default defineComponent({ handleSort, headColTag, activeSortable, - filterables, + filterByMap, + setFilterBy, } = inject(TABLE_TOKEN)! - const onClick = () => { - const { key, sortable } = props.column - if (sortable) { - handleSort(key, sortable) - } - } - - const cellProps = computed(() => { - const { type, align, hasChildren, fixed, key, colStart, colEnd, sortable, titleColSpan, titleRowSpan } = - props.column as HeadColumn - + const classes = computed(() => { + const { type, align, hasChildren, fixed, key, sortable, filterable } = props.column as HeadColumn const prefixCls = mergedPrefixCls.value let classes: Record = { [`${prefixCls}-cell-${type}`]: !!type, [`${prefixCls}-cell-sortable`]: !!sortable, + [`${prefixCls}-cell-filterable`]: !!filterable, [`${prefixCls}-align-${align}`]: !hasChildren && !!align, [`${prefixCls}-align-center`]: hasChildren, } - let style: CSSProperties | undefined if (fixed) { const { lastStartKey, firstEndKey } = fixedColumnKeys.value classes = { @@ -68,58 +60,75 @@ export default defineComponent({ [`${prefixCls}-fix-end-first`]: firstEndKey === key, [`${prefixCls}-fix-sticky`]: isSticky.value, } - const { starts, ends } = columnOffsetsWithScrollBar.value - const offsets = fixed === 'start' ? starts : ends - const offsetIndex = fixed === 'start' ? colStart : colEnd - const fixedOffset = convertCssPixel(offsets[offsetIndex]) - style = { - position: 'sticky', - left: fixed === 'start' ? fixedOffset : undefined, - right: fixed === 'end' ? fixedOffset : undefined, - } } + return classes + }) + + const style = computed(() => { + const { fixed, colStart, colEnd } = props.column as HeadColumn + if (!fixed) { + return + } + const { starts, ends } = columnOffsetsWithScrollBar.value + const offsets = fixed === 'start' ? starts : ends + const offsetIndex = fixed === 'start' ? colStart : colEnd + const fixedOffset = convertCssPixel(offsets[offsetIndex]) return { - class: classes, - style, - colSpan: titleColSpan === 1 ? undefined : titleColSpan, - rowSpan: titleRowSpan === 1 ? undefined : titleRowSpan, + position: 'sticky', + left: fixed === 'start' ? fixedOffset : undefined, + right: fixed === 'end' ? fixedOffset : undefined, } }) - 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)) + const activeFilterBy = computed(() => filterByMap[props.column.key]) + const onUpdateFilterBy = (filterBy: VKey[]) => { + const { key, filterable } = props.column + setFilterBy(key, filterBy) + callEmit(filterable?.onChange, filterBy) + } + + const onClick = () => { + const { key, sortable } = props.column + if (sortable) { + handleSort(key, sortable) + } + } return () => { - const { type, additional } = props.column as HeadColumn + const { type, additional, titleColSpan, titleRowSpan } = props.column as HeadColumn const prefixCls = mergedPrefixCls.value let _title: string | undefined let children: VNodeTypes | undefined - let iconTriggers: (VNodeTypes | null)[] | undefined + if (type === 'scroll-bar') { children = undefined } else if (type === 'selectable') { - children = + children = } else { /** * @deprecated customTitle */ - const { title, customTitle, slots: columnSlots, ellipsis } = props.column as HeadColumn + const { title, customTitle, slots: columnSlots, ellipsis, sortable, filterable } = props.column as HeadColumn if (__DEV__ && customTitle) { Logger.warn('components/table', '`customTitle` is deprecated, please use `title` in `slots` instead') } children = renderChildren(title, customTitle ?? columnSlots?.title, slots) _title = getColTitle(ellipsis, children!, title) - iconTriggers = [ - renderSortableTrigger(sortable.value, activeSortOrderBy.value), - renderFilterableTrigger(filterable.value), - ] + const iconTriggers = renderTrigger(sortable, activeSortOrderBy, filterable, activeFilterBy, onUpdateFilterBy) - if (ellipsis || iconTriggers.some(trigger => !!trigger)) { + if (iconTriggers.length > 0) { + children = ( + + {children} + {iconTriggers} + + ) + } else if (ellipsis) { children = {children} } } @@ -127,11 +136,16 @@ export default defineComponent({ // eslint-disable-next-line @typescript-eslint/no-explicit-any const HeadColTag = headColTag.value as any return ( - - - {children} - {iconTriggers} - + + {children} ) } @@ -152,21 +166,23 @@ function renderChildren( return children } -function renderSortableTrigger( +function renderTrigger( 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 + activeSortOrderBy: ComputedRef<'descend' | 'ascend' | undefined>, + filterable: TableColumnFilterable | undefined, + activeFilterBy: ComputedRef, + onUpdateFilterBy: (filterBy: VKey[]) => void, +) { + const children = [] + sortable && children.push() + if (filterable) { + children.push( + , + ) } - - return + return children } diff --git a/packages/components/table/src/main/head/triggers/FilterableTrigger.tsx b/packages/components/table/src/main/head/triggers/FilterableTrigger.tsx new file mode 100644 index 000000000..9d0a2a606 --- /dev/null +++ b/packages/components/table/src/main/head/triggers/FilterableTrigger.tsx @@ -0,0 +1,145 @@ +/** + * @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 { ComputedRef, VNodeTypes, computed, defineComponent, inject, normalizeClass, watch } from 'vue' + +import { VKey, useState } from '@idux/cdk/utils' +import { IxButton } from '@idux/components/button' +import { IxCheckbox } from '@idux/components/checkbox' +import { type DropdownProps, IxDropdown } from '@idux/components/dropdown' +import { TableLocale } from '@idux/components/i18n' +import { IxIcon } from '@idux/components/icon' +import { IxMenu, MenuData, type MenuItemProps, type MenuProps } from '@idux/components/menu' +import { IxRadio } from '@idux/components/radio' + +import { TABLE_TOKEN } from '../../../token' +import { tableFilterableTriggerProps } from '../../../types' + +export default defineComponent({ + props: tableFilterableTriggerProps, + setup(props) { + const { config, locale, mergedPrefixCls } = inject(TABLE_TOKEN)! + + const multiple = computed(() => props.filterable.multiple ?? config.columnBase.filterable.multiple) + const footer = computed(() => props.filterable.footer ?? config.columnBase.filterable.footer) + + const labelRender = (item: MenuItemProps & { selected: boolean }) => { + const prefixCls = `${mergedPrefixCls.value}-filterable-menu-label` + const Node = multiple.value ? IxCheckbox : IxRadio + return ( + + {} + {item.label} + + ) + } + + const mergedMenus = computed(() => { + return props.filterable.menus.map(item => { + const { type } = item + if ((!type || type === 'item') && (!item.slots || !item.slots.label)) { + return { ...item, slots: { label: labelRender, ...item.slots } } + } + return item + }) + }) + + // eslint-disable-next-line vue/no-setup-props-destructure + const initSelectedKeys = props.activeFilterBy + const [selectedKeys, setSelectedKeys] = useState(() => initSelectedKeys) + watch( + () => props.activeFilterBy, + keys => setSelectedKeys(keys), + ) + + const [visible, setVisible] = useState(false) + const onUpdateVisible = (value: boolean) => { + setVisible(value) + if (!value) { + props.onUpdateFilterBy(selectedKeys.value) + } + } + + const handleConfirm = () => { + setVisible(false) + props.onUpdateFilterBy(selectedKeys.value) + } + + const handleReset = () => { + setSelectedKeys(initSelectedKeys) + } + + const classes = computed(() => { + const prefixCls = `${mergedPrefixCls.value}-filterable-trigger` + return normalizeClass({ + [prefixCls]: true, + [`${prefixCls}-active`]: props.activeFilterBy.length > 0, + }) + }) + + const renderOverlay = () => { + const children: VNodeTypes[] = [renderMenu(mergedMenus, multiple, selectedKeys, setSelectedKeys)] + if (footer.value) { + children.push(renderFooter(locale, mergedPrefixCls, handleConfirm, handleReset)) + } + return children + } + + const renderTrigger = () => ( + evt.stopPropagation()}> + + + ) + + return () => { + const dropdownProps: DropdownProps = { + visible: visible.value, + hideOnClick: !footer.value, + placement: 'bottomEnd', + trigger: 'click', + 'onUpdate:visible': onUpdateVisible, + } + + return + } + }, +}) + +const renderMenu = ( + mergedMenus: ComputedRef, + multiple: ComputedRef, + selectedKeys: ComputedRef, + setSelectedKeys: (value: VKey[]) => void, +) => { + const menuProps: MenuProps = { + dataSource: mergedMenus.value, + multiple: multiple.value, + selectable: true, + selectedKeys: selectedKeys.value, + 'onUpdate:selectedKeys': setSelectedKeys, + } + return +} + +const renderFooter = ( + locale: ComputedRef, + mergedPrefixCls: ComputedRef, + handleConfirm: () => void, + handleReset: () => void, +) => { + const { filterConfirm, filterReset } = locale.value + return ( +
+ + {filterConfirm} + + + {filterReset} + +
+ ) +} diff --git a/packages/components/table/src/main/head/HeadCellSelectable.tsx b/packages/components/table/src/main/head/triggers/SelectableTrigger.tsx similarity index 97% rename from packages/components/table/src/main/head/HeadCellSelectable.tsx rename to packages/components/table/src/main/head/triggers/SelectableTrigger.tsx index 0f8026040..0346fd9a7 100644 --- a/packages/components/table/src/main/head/HeadCellSelectable.tsx +++ b/packages/components/table/src/main/head/triggers/SelectableTrigger.tsx @@ -13,7 +13,7 @@ import { type DropdownProps, IxDropdown } from '@idux/components/dropdown' import { IxIcon } from '@idux/components/icon' import { IxMenu } from '@idux/components/menu' -import { TABLE_TOKEN } from '../../token' +import { TABLE_TOKEN } from '../../../token' export default defineComponent({ setup() { diff --git a/packages/components/table/src/main/head-trigger/HeadCellSortableTrigger.tsx b/packages/components/table/src/main/head/triggers/SortableTrigger.tsx similarity index 76% rename from packages/components/table/src/main/head-trigger/HeadCellSortableTrigger.tsx rename to packages/components/table/src/main/head/triggers/SortableTrigger.tsx index 3afe2fea0..72a989d75 100644 --- a/packages/components/table/src/main/head-trigger/HeadCellSortableTrigger.tsx +++ b/packages/components/table/src/main/head/triggers/SortableTrigger.tsx @@ -14,7 +14,7 @@ import { TableLocale } from '@idux/components/i18n' import { IxIcon } from '@idux/components/icon' import { IxTooltip } from '@idux/components/tooltip' -import { TABLE_TOKEN } from '../../token' +import { TABLE_TOKEN } from '../../../token' export default defineComponent({ // eslint-disable-next-line vue/require-prop-types @@ -25,7 +25,7 @@ export default defineComponent({ return () => { const { activeOrderBy, sortable } = props const { orders, nextTooltip } = sortable - const title = nextTooltip ? getNextTooltipTitle(locale.value, orders!, activeOrderBy) : undefined + const title = nextTooltip && getNextTooltipTitle(locale.value, orders!, activeOrderBy) const sortableTriggerNode = renderSortTrigger(mergedPrefixCls, orders!, activeOrderBy) return title ? {sortableTriggerNode} : sortableTriggerNode } @@ -51,16 +51,13 @@ function renderSortTrigger( activeOrderBy?: TableColumnSortOrder, ) { const prefixCls = mergedPrefixCls.value - - const upNode = orders!.includes('ascend') ? ( - - ) : undefined - const downNode = orders!.includes('descend') ? ( - - ) : undefined + const activeClassName = `${prefixCls}-sortable-trigger-active` + const upNode = orders!.includes('ascend') && ( + + ) + const downNode = orders!.includes('descend') && ( + + ) return ( {upNode} diff --git a/packages/components/table/src/types.ts b/packages/components/table/src/types.ts index 8f074b0eb..2cee11b81 100644 --- a/packages/components/table/src/types.ts +++ b/packages/components/table/src/types.ts @@ -93,7 +93,7 @@ export interface TableColumnBase extends TableColumnCommon ellipsis?: boolean key?: VKey sortable?: TableColumnSortable - filterable?: TableColumnFilterable + filterable?: TableColumnFilterable title?: string children?: TableColumn[] slots?: TableColumnBaseSlots @@ -103,7 +103,7 @@ export interface TableColumnExpandableSlots { cell?: string | ((data: { value: V; record: T; rowIndex: number }) => VNodeChild) title?: string | ((data: { title?: string }) => VNodeChild) expand?: string | ((data: { record: T; rowIndex: number }) => VNodeChild) - icon?: string | ((data: { expanded: boolean; record: T; onExpand: () => void }) => VNodeChild) + icon?: string | ((data: { expanded: boolean; record: T }) => VNodeChild) } export interface TableColumnExpandable extends TableColumnBase { @@ -180,11 +180,13 @@ export interface TableColumnFilter { value: V } -export interface TableColumnFilterable { - filters: TableColumnFilter[] - filterBy?: V[] - filter: (currFilterBy: V[], record: T) => boolean - onChange?: (currFilterBy: V[]) => void +export interface TableColumnFilterable { + filter?: (currFilterBy: VKey[], record: T) => boolean + filterBy?: VKey[] + footer?: boolean + menus: MenuData[] + multiple?: boolean + onChange?: (currFilterBy: VKey[]) => void } export interface TableSticky { @@ -208,9 +210,9 @@ export const tableHeadCellProps = { export type TableHeadCellProps = IxInnerPropTypes export const tableBodyRowProps = { - expanded: IxPropTypes.bool.isRequired, + expanded: IxPropTypes.bool, rowIndex: IxPropTypes.number.isRequired, - level: IxPropTypes.number.isRequired, + level: IxPropTypes.number, record: IxPropTypes.any.isRequired, rowData: IxPropTypes.object().isRequired, rowKey: IxPropTypes.oneOfType([String, Number, Symbol]).isRequired, @@ -221,7 +223,7 @@ export type TableBodyRowProps = IxInnerPropTypes export const tableBodyCellProps = { column: IxPropTypes.object().isRequired, colIndex: IxPropTypes.number.isRequired, - level: IxPropTypes.number.isRequired, + level: IxPropTypes.number, record: IxPropTypes.any.isRequired, rowIndex: IxPropTypes.number.isRequired, disabled: IxPropTypes.bool, @@ -241,11 +243,18 @@ export const tableMeasureCellProps = { export type TableMeasureCellProps = IxInnerPropTypes -export const tableFilterPanelProps = { +export const tableFilterableTriggerProps = { + activeFilterBy: IxPropTypes.array().isRequired, filterable: IxPropTypes.object().isRequired, + onUpdateFilterBy: IxPropTypes.func<(filterBy: VKey[]) => void>().isRequired, } -export type TableFilterPanelProps = IxInnerPropTypes +export type TableFilterableTriggerProps = IxInnerPropTypes + +export const tableSortableTriggerProps = { + activeFilterBy: IxPropTypes.array().isRequired, + filterable: IxPropTypes.object().isRequired, + onUpdateFilterBy: IxPropTypes.func<(filterBy: VKey[]) => void>().isRequired, +} -export const tableHeadCellFilterableTriggerProps = tableFilterPanelProps -export type TableHeadCellFilterableTriggerProps = TableFilterPanelProps +export type TableSortableTriggerProps = IxInnerPropTypes diff --git a/packages/components/table/style/filterDropdown.less b/packages/components/table/style/filterDropdown.less deleted file mode 100644 index 119728681..000000000 --- a/packages/components/table/style/filterDropdown.less +++ /dev/null @@ -1,24 +0,0 @@ -.@{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 deleted file mode 100644 index 7b93ae3a4..000000000 --- a/packages/components/table/style/headerIcon.less +++ /dev/null @@ -1,20 +0,0 @@ -.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 862e54bf0..406623957 100644 --- a/packages/components/table/style/index.less +++ b/packages/components/table/style/index.less @@ -6,8 +6,6 @@ @import './fixed.less'; @import './radius.less'; @import './size.less'; -@import './headerIcon.less'; -@import './filterDropdown.less'; .@{table-prefix} { .reset-component(); @@ -131,13 +129,6 @@ 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; @@ -160,7 +151,13 @@ } } - & &-expandable-icon { + // --------- Expandable --------- + + & &-col-expandable { + width: 40px; + } + + & &-expandable-trigger { width: @table-expandable-icon-size; height: @table-expandable-icon-size; font-size: @table-expandable-icon-size; @@ -214,20 +211,63 @@ } &-sortable-trigger { - .header-icon(); - + display: inline-flex; flex-direction: column; - font-size: 8px; - margin-left: @table-icon-margin; + font-size: 10px; .@{idux-prefix}-icon + .@{idux-prefix}-icon { margin-top: -0.2em; } } + // --------- Filterable --------- + &-filterable-trigger { - .header-icon(); + font-size: 12px; + + &-footer { + border-top: @table-border-width @table-border-style @table-border-color; + padding: 8px 16px; + text-align: right; + + .@{button-prefix} + .@{button-prefix} { + margin-left: 8px; + } + } + } + + &-filterable-menu-label { + display: inline-flex; + &-content { + margin-left: 8px; + } + } + + &-sortable-trigger, + &-filterable-trigger { margin-left: @table-icon-margin; + padding: 8px 4px; + border-radius: 1px; + color: @table-head-icon-color; + transition: color @transition-duration-base, background @transition-duration-base; + cursor: pointer; + + &:hover { + background-color: @table-head-icon-hover-backgroud-color; + } + + &-active { + color: @color-primary; + } + } + + &-cell-triggers { + display: flex; + align-items: center; + + .@{table-prefix}-cell-title { + flex: auto; + } } } diff --git a/packages/components/table/style/themes/default.variable.less b/packages/components/table/style/themes/default.variable.less index e78da9e28..033ce5c69 100644 --- a/packages/components/table/style/themes/default.variable.less +++ b/packages/components/table/style/themes/default.variable.less @@ -30,11 +30,8 @@ @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; @@ -44,11 +41,3 @@ @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