Skip to content
91 changes: 52 additions & 39 deletions ui/src/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ type ContextualMenuProps = {
col: WaveColumn
listProps: Fluent.IContextualMenuListProps
selectedFilters: Dict<S[]> | null
setFiltersInBulk: (colKey: S, filters: S[]) => void
setFiltersInBulk: (colKey: S, filters: S[]) => void
}

type FooterProps = {
Expand Down Expand Up @@ -291,6 +291,7 @@ const
ContextualMenu = ({ onFilterChange, col, listProps, selectedFilters, setFiltersInBulk }: ContextualMenuProps) => {
const
isFilterChecked = (data: S, key: S) => !!selectedFilters && selectedFilters[data]?.includes(key),
[selectedFiltersCount, setSelectedFiltersCount] = React.useState(0), // Step 1
[menuFilters, setMenuFilters] = React.useState(col.cellType?.tag
? Array.from(listProps.items.reduce((_filters, { key, text, data }) => {
key.split(',').forEach(key => _filters.set(key, { key, text, data, checked: isFilterChecked(data, key) }))
Expand All @@ -301,18 +302,28 @@ const
selectAll = () => {
setMenuFilters(menuFilters.map(i => ({ ...i, checked: true })))
setFiltersInBulk(col.key, menuFilters.map(f => f.key))
setSelectedFiltersCount(menuFilters.length)
},
deselectAll = () => {
setMenuFilters(menuFilters.map(i => ({ ...i, checked: false })))
setFiltersInBulk(col.key, [])
setSelectedFiltersCount(0)
},
getOnFilterChangeHandler = (data: S, key: S) => (_ev?: React.FormEvent<HTMLInputElement | HTMLElement>, checked?: B) => {
setMenuFilters(filters => filters.map(f => f.key === key ? ({ ...f, checked }) : f))
const onChangeFilterMap = menuFilters.map(f => (f.key === key ? { ...f, checked } : f))
setMenuFilters(onChangeFilterMap)
onFilterChange(data, key, checked)
setFiltersInBulk(col.key, onChangeFilterMap.filter(f => f.checked).map(f => f.key))
setSelectedFiltersCount(
onChangeFilterMap.reduce((count, filter) => (filter.checked ? count + 1 : count), 0)
)
}

return (
<div style={{ padding: 10 }}>
<Fluent.Text variant='mediumPlus' styles={{ root: { paddingTop: 10, paddingBottom: 10, fontWeight: 'bold' } }} block>
{`(${selectedFiltersCount} selected)`}
</Fluent.Text>
<Fluent.Text variant='mediumPlus' styles={{ root: { paddingTop: 10, paddingBottom: 10, fontWeight: 'bold' } }} block>Show only</Fluent.Text>
<Fluent.Text variant='small'>
<Fluent.Link onClick={selectAll}>Select All</Fluent.Link> | <Fluent.Link onClick={deselectAll}>Deselect All</Fluent.Link>
Expand All @@ -334,7 +345,7 @@ const
},
DataTable = React.forwardRef(({ model: m, onFilterChange, items, filteredItems, selection, selectedFilters, isMultiple, isSingle, groups, expandedRefs, onSortChange, setFiltersInBulk }: DataTable, ref) => {
const
[colContextMenuList, setColContextMenuList] = React.useState<Fluent.IContextualMenuProps | null>(null),
[colContextMenuList, setColContextMenuList] = React.useState<Fluent.IContextualMenuProps | null>(null),
onRenderMenuList = React.useCallback((col: WaveColumn) => (listProps?: Fluent.IContextualMenuListProps) => {
return listProps ?
<ContextualMenu
Expand Down Expand Up @@ -373,7 +384,8 @@ const
return col
}))
}
}, [onColumnContextMenu, onSortChange]),
}
, [onColumnContextMenu, onSortChange]),
tableToWaveColumn = React.useCallback((c: TableColumn): WaveColumn => {
const
minWidth = c.min_width
Expand All @@ -386,9 +398,15 @@ const
? +c.max_width.substring(0, c.max_width.length - 2)
: +c.max_width
: undefined
let label = c.label
if (c.filterable) {
const dataKey = c.name // Assuming the column name represents the data key
const appliedFilters = selectedFilters?.[dataKey]?.length || 0
label += (appliedFilters > 0 && appliedFilters <= 9) ? ` (${appliedFilters})` : appliedFilters > 9 ? '(9+)': ''
}
return {
key: c.name,
name: c.label,
name: label,
fieldName: c.name,
minWidth,
maxWidth,
Expand All @@ -406,37 +424,38 @@ const
isMultiline: c.cell_overflow === 'wrap',
filters: c.filterable ? c.filters : undefined,
}
}, [onColumnClick]),
}, [onColumnClick, selectedFilters]),
[columns, setColumns] = React.useState(m.columns.map(tableToWaveColumn)),
primaryColumnKey = m.columns.find(c => c.link)?.name || (m.columns[0].link === false ? undefined : m.columns[0].name),
onRenderDetailsHeader = React.useCallback((props?: Fluent.IDetailsHeaderProps) => {
if (!props) return <span />

return (
<Fluent.Sticky stickyPosition={Fluent.StickyPositionType.Header} isScrollSynced>
<Fluent.DetailsHeader
{...props}
isAllCollapsed={groups?.every(group => group.isCollapsed)}
styles={{
...props.styles,
root: {
padding: 0,
height: 48,
lineHeight: '48px',
background: cssVar('$neutralLight'),
borderBottom: 'none',
},
cellSizerEnd: {
marginLeft: -8,
},
cellIsGroupExpander: {
// HACK: fixed size of expand/collapse button in column header
height: 48
}
}}
/>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Fluent.DetailsHeader
{...props}
isAllCollapsed={groups?.every(group => group.isCollapsed)}
styles={{
...props.styles,
root: {
padding: 0,
height: 48,
lineHeight: '48px',
background: cssVar('$neutralLight'),
borderBottom: 'none',
},
cellSizerEnd: {
marginLeft: -8,
},
cellIsGroupExpander: {
// HACK: fixed size of expand/collapse button in column header
height: 48,
},
}}
/>
</div>
</Fluent.Sticky>
)
)
}, [groups]),
onRenderGroupHeader = React.useCallback((props?: Fluent.IDetailsGroupDividerProps) => {
if (!props) return <span />
Expand Down Expand Up @@ -578,9 +597,6 @@ const
onRenderItemColumn={onRenderItemColumn}
onRenderDetailsHeader={onRenderDetailsHeader}
checkboxVisibility={checkboxVisibilityMap[m.checkbox_visibility || 'on-hover']}
// Prevent selection from being cleared when 'items' are updated.
// https://github.com/microsoft/fluentui/blob/4c1cd4bbba73bbca4411db9d01ffb486b1a90303/packages/react/src/components/DetailsList/DetailsList.base.tsx#L1032.
setKey='wave-table-items'
/>
{colContextMenuList && <Fluent.ContextualMenu {...colContextMenuList} />}
</>
Expand Down Expand Up @@ -931,11 +947,6 @@ export const
const selectedItemKeys = selection.getSelection().map(item => item.key as S)
wave.args[m.name] = selectedItemKeys
if (!skipNextEventEmit.current && m.events?.includes('select')) wave.emit(m.name, 'select', selectedItemKeys)
},
onItemsChanged: () => {
// HACK: Skip emitting 'select' event on 'items' update.
skipNextEventEmit.current = true
setTimeout(() => skipNextEventEmit.current = false)
}
}), [m.name, m.events]),
computeHeight = () => {
Expand Down Expand Up @@ -975,17 +986,19 @@ export const
}, [m.pagination, m.events, m.name, filter, search, currentSort, initGroups])

React.useEffect(() => {
skipNextEventEmit.current = true
wave.args[m.name] = []
if (isSingle && m.value) {
skipNextEventEmit.current = true
selection.setKeySelected(m.value, true, false)
skipNextEventEmit.current = false
wave.args[m.name] = [m.value]
}
else if (isMultiple && m.values) {
skipNextEventEmit.current = true
m.values.forEach(v => selection.setKeySelected(v, true, false))
skipNextEventEmit.current = false
wave.args[m.name] = m.values
}
skipNextEventEmit.current = false
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

Expand Down