Commit 8739c05c by zuiho Committed by GitHub

feat(web): 支持渠道列表手动调整列宽 (#5948)

* 支持渠道表格列宽拖拽

* fix(web): address column resize review feedback

* feat(web): auto-fit resized table columns

* fix(web): reduce column resize persistence work

---------

Signed-off-by: zuiho <2324465096@qq.com>
parent 97bbb7c8
...@@ -34,9 +34,12 @@ export function DataTableColgroup<TData>({ ...@@ -34,9 +34,12 @@ export function DataTableColgroup<TData>({
return ( return (
<colgroup> <colgroup>
{columns.map((column) => { {columns.map((column) => {
const width = isContentSizedColumn(column.id) const width = getColumnWidth(
? undefined table,
: getColumnWidth(column.getSize(), totalSize) column.id,
column.getSize(),
totalSize
)
return <col key={column.id} style={{ width }} /> return <col key={column.id} style={{ width }} />
})} })}
...@@ -44,7 +47,20 @@ export function DataTableColgroup<TData>({ ...@@ -44,7 +47,20 @@ export function DataTableColgroup<TData>({
) )
} }
function getColumnWidth(columnSize: number, totalSize: number) { function getColumnWidth<TData>(
table: TanstackTable<TData>,
columnId: string,
columnSize: number,
totalSize: number
) {
if (isContentSizedColumn(columnId)) {
return undefined
}
if (table.options.enableColumnResizing === true) {
return `${columnSize}px`
}
if (totalSize <= 0) { if (totalSize <= 0) {
return undefined return undefined
} }
......
...@@ -21,8 +21,11 @@ import { ...@@ -21,8 +21,11 @@ import {
type Header, type Header,
type Table as TanstackTable, type Table as TanstackTable,
} from '@tanstack/react-table' } from '@tanstack/react-table'
import type { KeyboardEvent, MouseEvent } from 'react'
import { useTranslation } from 'react-i18next'
import { TableHead, TableHeader, TableRow } from '@/components/ui/table' import { TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { cn } from '@/lib/utils'
import { DataTableColumnHeader } from './column-header' import { DataTableColumnHeader } from './column-header'
import { isContentSizedColumn } from './content-sized-columns' import { isContentSizedColumn } from './content-sized-columns'
...@@ -43,6 +46,8 @@ export function DataTableHeader<TData>({ ...@@ -43,6 +46,8 @@ export function DataTableHeader<TData>({
rowClassName, rowClassName,
getColumnClassName, getColumnClassName,
}: DataTableHeaderProps<TData>) { }: DataTableHeaderProps<TData>) {
const { t } = useTranslation()
return ( return (
<TableHeader className={className}> <TableHeader className={className}>
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
...@@ -51,10 +56,36 @@ export function DataTableHeader<TData>({ ...@@ -51,10 +56,36 @@ export function DataTableHeader<TData>({
<TableHead <TableHead
key={header.id} key={header.id}
colSpan={header.colSpan} colSpan={header.colSpan}
className={getColumnClassName?.(header.column.id, 'header')} data-column-id={header.column.id}
className={cn(
'relative',
getColumnClassName?.(header.column.id, 'header')
)}
style={getHeaderSizeStyle(header, applyHeaderSize)} style={getHeaderSizeStyle(header, applyHeaderSize)}
> >
{renderHeaderContent(header)} {renderHeaderContent(header)}
{shouldRenderColumnResizer(table, header) && (
<div
role='separator'
aria-orientation='vertical'
aria-label={t('Resize column')}
data-column-resizer
tabIndex={0}
onDoubleClick={(event) =>
handleColumnAutoSize(event, table, header)
}
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
onKeyDown={(event) =>
handleColumnResizeKeyDown(event, table, header)
}
className={cn(
'absolute top-0 right-0 h-full w-2 cursor-col-resize touch-none select-none',
'after:bg-border hover:after:bg-primary after:absolute after:top-2 after:right-0 after:h-[calc(100%-1rem)] after:w-px after:transition-colors',
header.column.getIsResizing() && 'after:bg-primary'
)}
/>
)}
</TableHead> </TableHead>
))} ))}
</TableRow> </TableRow>
...@@ -63,6 +94,161 @@ export function DataTableHeader<TData>({ ...@@ -63,6 +94,161 @@ export function DataTableHeader<TData>({
) )
} }
function handleColumnResizeKeyDown<TData>(
event: KeyboardEvent<HTMLDivElement>,
table: TanstackTable<TData>,
header: Header<TData, unknown>
) {
const step = event.shiftKey ? 50 : 10
if (event.key === 'ArrowLeft') {
event.preventDefault()
resizeColumnByKeyboard(table, header, -step)
return
}
if (event.key === 'ArrowRight') {
event.preventDefault()
resizeColumnByKeyboard(table, header, step)
return
}
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
autoSizeColumn(event.currentTarget, table, header)
}
}
function resizeColumnByKeyboard<TData>(
table: TanstackTable<TData>,
header: Header<TData, unknown>,
delta: number
) {
table.setColumnSizing((previous) => ({
...previous,
[header.column.id]: getClampedColumnSize(
header,
header.column.getSize() + delta
),
}))
}
function handleColumnAutoSize<TData>(
event: MouseEvent<HTMLDivElement>,
table: TanstackTable<TData>,
header: Header<TData, unknown>
) {
event.preventDefault()
autoSizeColumn(event.currentTarget, table, header)
}
function autoSizeColumn<TData>(
resizerElement: HTMLElement,
table: TanstackTable<TData>,
header: Header<TData, unknown>
) {
const measuredSize = measureColumnContentWidth(
resizerElement,
header.column.id
)
if (measuredSize === undefined) {
return
}
table.setColumnSizing((previous) => ({
...previous,
[header.column.id]: getClampedColumnSize(header, measuredSize),
}))
}
function getClampedColumnSize<TData>(
header: Header<TData, unknown>,
nextSize: number
) {
const { minSize, maxSize } = header.column.columnDef
if (typeof minSize === 'number' && nextSize < minSize) {
return minSize
}
if (typeof maxSize === 'number' && nextSize > maxSize) {
return maxSize
}
return nextSize
}
function measureColumnContentWidth(
resizerElement: HTMLElement,
columnId: string
) {
const tableElement = resizerElement.closest('table')
if (!tableElement) {
return undefined
}
const cells = tableElement.querySelectorAll<HTMLElement>(
getColumnElementSelector(columnId)
)
if (cells.length === 0) {
return undefined
}
const measuredWidth = [...cells].reduce(
(maxWidth, cell) => Math.max(maxWidth, measureElementWidth(cell)),
0
)
return measuredWidth > 0 ? Math.ceil(measuredWidth) : undefined
}
function measureElementWidth(element: HTMLElement) {
const clone = element.cloneNode(true) as HTMLElement
clone.querySelectorAll('[data-column-resizer]').forEach((resizer) => {
resizer.remove()
})
clone.style.position = 'absolute'
clone.style.visibility = 'hidden'
clone.style.pointerEvents = 'none'
clone.style.left = '-10000px'
clone.style.top = '0'
clone.style.width = 'max-content'
clone.style.minWidth = '0'
clone.style.maxWidth = 'none'
clone.style.height = 'auto'
clone.style.whiteSpace = 'nowrap'
document.body.append(clone)
const width = clone.scrollWidth
clone.remove()
return width
}
function getColumnElementSelector(columnId: string) {
const escapedColumnId =
typeof CSS !== 'undefined' && typeof CSS.escape === 'function'
? CSS.escape(columnId)
: columnId.replaceAll('\\', '\\\\').replaceAll('"', '\\"')
return `[data-column-id="${escapedColumnId}"]`
}
function shouldRenderColumnResizer<TData>(
table: TanstackTable<TData>,
header: Header<TData, unknown>
) {
return (
table.options.enableColumnResizing === true &&
!header.isPlaceholder &&
header.column.getCanResize() &&
!isContentSizedColumn(header.column.id)
)
}
function getHeaderSizeStyle<TData>( function getHeaderSizeStyle<TData>(
header: Header<TData, unknown>, header: Header<TData, unknown>,
applyHeaderSize: boolean | undefined applyHeaderSize: boolean | undefined
......
...@@ -65,6 +65,7 @@ function DataTableRowInner<TData>({ ...@@ -65,6 +65,7 @@ function DataTableRowInner<TData>({
return ( return (
<TableCell <TableCell
key={cell.id} key={cell.id}
data-column-id={cell.column.id}
className={cn( className={cn(
'max-w-full min-w-0', 'max-w-full min-w-0',
renderedCell.isPrimitive && 'overflow-hidden', renderedCell.isPrimitive && 'overflow-hidden',
......
...@@ -19,6 +19,7 @@ For commercial licensing, please contact support@quantumnous.com ...@@ -19,6 +19,7 @@ For commercial licensing, please contact support@quantumnous.com
import { import {
type ColumnDef, type ColumnDef,
type ColumnFiltersState, type ColumnFiltersState,
type ColumnSizingState,
type ExpandedState, type ExpandedState,
type OnChangeFn, type OnChangeFn,
type PaginationState, type PaginationState,
...@@ -48,6 +49,7 @@ type DataTableFeatureOptions<TData> = Pick< ...@@ -48,6 +49,7 @@ type DataTableFeatureOptions<TData> = Pick<
| 'manualFiltering' | 'manualFiltering'
| 'manualPagination' | 'manualPagination'
| 'manualSorting' | 'manualSorting'
| 'enableColumnResizing'
> >
type DataTableStateOptions = { type DataTableStateOptions = {
...@@ -58,6 +60,10 @@ type DataTableStateOptions = { ...@@ -58,6 +60,10 @@ type DataTableStateOptions = {
columnVisibilityStorageKey?: string | false columnVisibilityStorageKey?: string | false
columnVisibility?: VisibilityState columnVisibility?: VisibilityState
onColumnVisibilityChange?: OnChangeFn<VisibilityState> onColumnVisibilityChange?: OnChangeFn<VisibilityState>
initialColumnSizing?: ColumnSizingState
columnSizingStorageKey?: string | false
columnSizing?: ColumnSizingState
onColumnSizingChange?: OnChangeFn<ColumnSizingState>
initialRowSelection?: RowSelectionState initialRowSelection?: RowSelectionState
rowSelection?: RowSelectionState rowSelection?: RowSelectionState
onRowSelectionChange?: OnChangeFn<RowSelectionState> onRowSelectionChange?: OnChangeFn<RowSelectionState>
...@@ -91,6 +97,21 @@ type UseDataTableOptions<TData> = DataTableFeatureOptions<TData> & ...@@ -91,6 +97,21 @@ type UseDataTableOptions<TData> = DataTableFeatureOptions<TData> &
ensurePageInRange?: (pageCount: number) => void ensurePageInRange?: (pageCount: number) => void
} }
type ColumnSizingBounds = Record<
string,
{
minSize?: number
maxSize?: number
}
>
type ColumnWithSizing<TData> = ColumnDef<TData, unknown> & {
accessorKey?: string | number
columns?: ColumnDef<TData, unknown>[]
}
const COLUMN_SIZING_PERSIST_DELAY_MS = 250
function resolveUpdater<TValue>( function resolveUpdater<TValue>(
updater: Updater<TValue>, updater: Updater<TValue>,
previous: TValue previous: TValue
...@@ -149,6 +170,110 @@ function readColumnVisibility(storageKey: string | undefined): VisibilityState { ...@@ -149,6 +170,110 @@ function readColumnVisibility(storageKey: string | undefined): VisibilityState {
} }
} }
function getColumnId<TData>(column: ColumnDef<TData, unknown>) {
const columnWithSizing = column as ColumnWithSizing<TData>
if (typeof columnWithSizing.id === 'string') {
return columnWithSizing.id
}
if (typeof columnWithSizing.accessorKey === 'string') {
return columnWithSizing.accessorKey.replaceAll('.', '_')
}
if (typeof columnWithSizing.accessorKey === 'number') {
return String(columnWithSizing.accessorKey)
}
return undefined
}
function buildColumnSizingBounds<TData>(
columns: ColumnDef<TData, unknown>[]
): ColumnSizingBounds {
return columns.reduce<ColumnSizingBounds>((bounds, column) => {
const columnWithSizing = column as ColumnWithSizing<TData>
const columnId = getColumnId(column)
if (columnId) {
const minSize =
typeof columnWithSizing.minSize === 'number' &&
Number.isFinite(columnWithSizing.minSize)
? columnWithSizing.minSize
: undefined
const maxSize =
typeof columnWithSizing.maxSize === 'number' &&
Number.isFinite(columnWithSizing.maxSize)
? columnWithSizing.maxSize
: undefined
if (minSize !== undefined || maxSize !== undefined) {
bounds[columnId] = { minSize, maxSize }
}
}
if (Array.isArray(columnWithSizing.columns)) {
Object.assign(bounds, buildColumnSizingBounds(columnWithSizing.columns))
}
return bounds
}, {})
}
function getBoundedColumnSize(
columnId: string,
value: unknown,
bounds: ColumnSizingBounds
) {
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
return undefined
}
const columnBounds = bounds[columnId]
let size = value
if (columnBounds?.minSize !== undefined && size < columnBounds.minSize) {
size = columnBounds.minSize
}
if (columnBounds?.maxSize !== undefined && size > columnBounds.maxSize) {
size = columnBounds.maxSize
}
return size > 0 ? size : undefined
}
function readColumnSizing(
storageKey: string | undefined,
bounds: ColumnSizingBounds
): ColumnSizingState {
if (!storageKey || typeof window === 'undefined') return {}
try {
const raw = window.localStorage.getItem(storageKey)
if (!raw) return {}
const parsed = JSON.parse(raw) as unknown
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
return {}
}
return Object.entries(parsed).reduce<ColumnSizingState>(
(sizing, [key, value]) => {
const boundedSize = getBoundedColumnSize(key, value, bounds)
if (boundedSize !== undefined) {
sizing[key] = boundedSize
}
return sizing
},
{}
)
} catch {
return {}
}
}
export function useDataTable<TData>(options: UseDataTableOptions<TData>) { export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
const { const {
data, data,
...@@ -161,6 +286,7 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) { ...@@ -161,6 +286,7 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
manualSorting, manualSorting,
initialSorting = [], initialSorting = [],
initialColumnVisibility = {}, initialColumnVisibility = {},
initialColumnSizing = {},
initialRowSelection = {}, initialRowSelection = {},
initialExpanded = {}, initialExpanded = {},
initialPagination = { pageIndex: 0, pageSize: 20 }, initialPagination = { pageIndex: 0, pageSize: 20 },
...@@ -175,6 +301,10 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) { ...@@ -175,6 +301,10 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
typeof options.columnVisibilityStorageKey === 'string' typeof options.columnVisibilityStorageKey === 'string'
? options.columnVisibilityStorageKey ? options.columnVisibilityStorageKey
: undefined : undefined
const columnSizingStorageKey =
typeof options.columnSizingStorageKey === 'string'
? options.columnSizingStorageKey
: undefined
const resolvedInitialColumnVisibility = React.useMemo( const resolvedInitialColumnVisibility = React.useMemo(
() => ({ () => ({
...initialColumnVisibility, ...initialColumnVisibility,
...@@ -182,6 +312,17 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) { ...@@ -182,6 +312,17 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
}), }),
[columnVisibilityStorageKey, initialColumnVisibility] [columnVisibilityStorageKey, initialColumnVisibility]
) )
const columnSizingBounds = React.useMemo(
() => buildColumnSizingBounds(columns),
[columns]
)
const resolvedInitialColumnSizing = React.useMemo(
() => ({
...initialColumnSizing,
...readColumnSizing(columnSizingStorageKey, columnSizingBounds),
}),
[columnSizingBounds, columnSizingStorageKey, initialColumnSizing]
)
const [sorting, onSortingChange] = useControllableTableState( const [sorting, onSortingChange] = useControllableTableState(
options.sorting, options.sorting,
...@@ -194,10 +335,20 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) { ...@@ -194,10 +335,20 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
resolvedInitialColumnVisibility, resolvedInitialColumnVisibility,
options.onColumnVisibilityChange options.onColumnVisibilityChange
) )
const [columnSizing, onColumnSizingChange] = useControllableTableState(
options.columnSizing,
resolvedInitialColumnSizing,
options.onColumnSizingChange
)
const hydratedColumnVisibilityStorageKeyRef = React.useRef( const hydratedColumnVisibilityStorageKeyRef = React.useRef(
columnVisibilityStorageKey columnVisibilityStorageKey
) )
const hydratedColumnSizingStorageKeyRef = React.useRef(columnSizingStorageKey)
const skipNextColumnVisibilityPersistRef = React.useRef(false) const skipNextColumnVisibilityPersistRef = React.useRef(false)
const skipNextColumnSizingPersistRef = React.useRef(false)
const columnSizingPersistTimerRef = React.useRef<number | undefined>(
undefined
)
const [rowSelection, onRowSelectionChange] = useControllableTableState( const [rowSelection, onRowSelectionChange] = useControllableTableState(
options.rowSelection, options.rowSelection,
initialRowSelection, initialRowSelection,
...@@ -228,6 +379,7 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) { ...@@ -228,6 +379,7 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
state: { state: {
sorting, sorting,
columnVisibility, columnVisibility,
columnSizing,
rowSelection, rowSelection,
expanded, expanded,
columnFilters: options.columnFilters, columnFilters: options.columnFilters,
...@@ -242,8 +394,11 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) { ...@@ -242,8 +394,11 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
manualFiltering, manualFiltering,
manualPagination, manualPagination,
manualSorting, manualSorting,
enableColumnResizing: options.enableColumnResizing,
columnResizeMode: 'onChange',
onSortingChange, onSortingChange,
onColumnVisibilityChange, onColumnVisibilityChange,
onColumnSizingChange,
onRowSelectionChange, onRowSelectionChange,
onExpandedChange, onExpandedChange,
onColumnFiltersChange: options.onColumnFiltersChange, onColumnFiltersChange: options.onColumnFiltersChange,
...@@ -291,6 +446,24 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) { ...@@ -291,6 +446,24 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
]) ])
React.useEffect(() => { React.useEffect(() => {
if (
options.columnSizing !== undefined ||
columnSizingStorageKey === hydratedColumnSizingStorageKeyRef.current
) {
return
}
hydratedColumnSizingStorageKeyRef.current = columnSizingStorageKey
skipNextColumnSizingPersistRef.current = true
onColumnSizingChange(() => resolvedInitialColumnSizing)
}, [
columnSizingStorageKey,
onColumnSizingChange,
options.columnSizing,
resolvedInitialColumnSizing,
])
React.useEffect(() => {
if (!columnVisibilityStorageKey || typeof window === 'undefined') return if (!columnVisibilityStorageKey || typeof window === 'undefined') return
if (skipNextColumnVisibilityPersistRef.current) { if (skipNextColumnVisibilityPersistRef.current) {
...@@ -308,6 +481,39 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) { ...@@ -308,6 +481,39 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
} }
}, [columnVisibility, columnVisibilityStorageKey]) }, [columnVisibility, columnVisibilityStorageKey])
React.useEffect(() => {
if (!columnSizingStorageKey || typeof window === 'undefined') return
if (skipNextColumnSizingPersistRef.current) {
skipNextColumnSizingPersistRef.current = false
return
}
if (columnSizingPersistTimerRef.current !== undefined) {
window.clearTimeout(columnSizingPersistTimerRef.current)
}
columnSizingPersistTimerRef.current = window.setTimeout(() => {
try {
window.localStorage.setItem(
columnSizingStorageKey,
JSON.stringify(columnSizing)
)
} catch {
// Storage can be unavailable in private mode; table controls still work.
} finally {
columnSizingPersistTimerRef.current = undefined
}
}, COLUMN_SIZING_PERSIST_DELAY_MS)
return () => {
if (columnSizingPersistTimerRef.current !== undefined) {
window.clearTimeout(columnSizingPersistTimerRef.current)
columnSizingPersistTimerRef.current = undefined
}
}
}, [columnSizing, columnSizingStorageKey])
return { return {
table, table,
} }
......
...@@ -560,6 +560,7 @@ export function useChannelsColumns( ...@@ -560,6 +560,7 @@ export function useChannelsColumns(
}, },
enableSorting: false, enableSorting: false,
enableHiding: false, enableHiding: false,
enableResizing: false,
size: 40, size: 40,
} satisfies ColumnDef<Channel>, } satisfies ColumnDef<Channel>,
] ]
...@@ -624,13 +625,13 @@ export function useChannelsColumns( ...@@ -624,13 +625,13 @@ export function useChannelsColumns(
const hasParamOverride = Boolean(channel.param_override?.trim()) const hasParamOverride = Boolean(channel.param_override?.trim())
return ( return (
<div className='flex items-center gap-2'> <div className='flex max-w-full min-w-0 items-center gap-2'>
<div className='flex flex-col gap-1'> <div className='flex max-w-full min-w-0 flex-col gap-1'>
<div className='flex items-center gap-1.5'> <div className='flex max-w-full min-w-0 items-center gap-1.5'>
<TruncatedText <TruncatedText
text={sensitiveVisible ? name : SENSITIVE_MASK} text={sensitiveVisible ? name : SENSITIVE_MASK}
className='font-medium' className='font-medium'
maxWidth='max-w-[180px]' maxWidth='max-w-full'
/> />
{isPassThrough && ( {isPassThrough && (
<TooltipProvider delay={100}> <TooltipProvider delay={100}>
...@@ -684,6 +685,7 @@ export function useChannelsColumns( ...@@ -684,6 +685,7 @@ export function useChannelsColumns(
</div> </div>
) )
}, },
size: 260,
minSize: 200, minSize: 200,
}, },
......
...@@ -67,6 +67,7 @@ import { DataTableBulkActions } from './data-table-bulk-actions' ...@@ -67,6 +67,7 @@ import { DataTableBulkActions } from './data-table-bulk-actions'
const route = getRouteApi('/_authenticated/channels/') const route = getRouteApi('/_authenticated/channels/')
const CHANNELS_COLUMN_VISIBILITY_STORAGE_KEY = 'channels:column-visibility' const CHANNELS_COLUMN_VISIBILITY_STORAGE_KEY = 'channels:column-visibility'
const CHANNELS_COLUMN_SIZING_STORAGE_KEY = 'channels:column-sizing'
const CHANNELS_VIEW_MODE_STORAGE_KEY = 'channels:view-mode' const CHANNELS_VIEW_MODE_STORAGE_KEY = 'channels:view-mode'
const CHANNELS_STATUS_FILTER_STORAGE_KEY = 'channel-status-filter' const CHANNELS_STATUS_FILTER_STORAGE_KEY = 'channel-status-filter'
...@@ -316,6 +317,9 @@ export function ChannelsTable() { ...@@ -316,6 +317,9 @@ export function ChannelsTable() {
tag: false, tag: false,
}, },
columnVisibilityStorageKey: CHANNELS_COLUMN_VISIBILITY_STORAGE_KEY, columnVisibilityStorageKey: CHANNELS_COLUMN_VISIBILITY_STORAGE_KEY,
columnSizingStorageKey: isMobile
? false
: CHANNELS_COLUMN_SIZING_STORAGE_KEY,
columnFilters, columnFilters,
pagination, pagination,
globalFilter, globalFilter,
...@@ -331,6 +335,7 @@ export function ChannelsTable() { ...@@ -331,6 +335,7 @@ export function ChannelsTable() {
manualSorting: true, manualSorting: true,
manualFiltering: true, manualFiltering: true,
withExpandedRowModel: true, withExpandedRowModel: true,
enableColumnResizing: !isMobile,
ensurePageInRange, ensurePageInRange,
}) })
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment