Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
764a4e5b4e |
@@ -98,6 +98,7 @@ export function AccessApiKeysGrid({
|
|||||||
{
|
{
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
|
size: 88,
|
||||||
header: () => null,
|
header: () => null,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const k = row.original
|
const k = row.original
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { kitDataGridTableClassNames, kitDataGridTableLayout } from './frame-data-grid'
|
import {
|
||||||
|
applyKitActionColumn,
|
||||||
|
KIT_ACTION_COLUMN_SIZE,
|
||||||
|
kitColumnPinning,
|
||||||
|
kitDataGridTableClassNames,
|
||||||
|
kitDataGridTableLayout,
|
||||||
|
} from './frame-data-grid'
|
||||||
|
|
||||||
describe('kitDataGridTableLayout', () => {
|
describe('kitDataGridTableLayout', () => {
|
||||||
it('filtering-2 defaults: dense, headerBackground false, width fixed', () => {
|
it('filtering-2 defaults: dense, headerBackground false, width fixed', () => {
|
||||||
@@ -30,3 +36,63 @@ describe('kitDataGridTableLayout', () => {
|
|||||||
expect(layout.headerBackground).toBe(false)
|
expect(layout.headerBackground).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('applyKitActionColumn', () => {
|
||||||
|
it('locks filtering-2 size on id=actions', () => {
|
||||||
|
const [name, actions] = applyKitActionColumn([
|
||||||
|
{ id: 'name', header: 'Name' },
|
||||||
|
{ id: 'actions', header: () => null, cell: () => 'x' },
|
||||||
|
])
|
||||||
|
expect(name?.id).toBe('name')
|
||||||
|
expect(actions?.size).toBe(KIT_ACTION_COLUMN_SIZE)
|
||||||
|
expect(actions?.minSize).toBe(KIT_ACTION_COLUMN_SIZE)
|
||||||
|
expect(actions?.maxSize).toBe(KIT_ACTION_COLUMN_SIZE)
|
||||||
|
expect(actions?.enableSorting).toBe(false)
|
||||||
|
expect(actions?.enableResizing).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps explicit size (data-grid-base-7 text button)', () => {
|
||||||
|
const [actions] = applyKitActionColumn([
|
||||||
|
{ id: 'actions', size: 104, cell: () => 'x' },
|
||||||
|
])
|
||||||
|
expect(actions?.size).toBe(104)
|
||||||
|
expect(actions?.minSize).toBe(104)
|
||||||
|
expect(actions?.maxSize).toBe(104)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not rewrite a non-actions last column', () => {
|
||||||
|
const [col] = applyKitActionColumn([{ id: 'name', header: 'Name' }])
|
||||||
|
expect(col?.size).toBeUndefined()
|
||||||
|
expect(col?.enableResizing).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('applies DNA when pinLastColumn even without id=actions', () => {
|
||||||
|
const [col] = applyKitActionColumn([{ id: 'other', cell: () => 'x' }], {
|
||||||
|
pinLastColumn: true,
|
||||||
|
})
|
||||||
|
expect(col?.size).toBe(KIT_ACTION_COLUMN_SIZE)
|
||||||
|
expect(col?.maxSize).toBe(KIT_ACTION_COLUMN_SIZE)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('kitColumnPinning', () => {
|
||||||
|
it('does not end-pin without horizontalScroll', () => {
|
||||||
|
const result = kitColumnPinning({
|
||||||
|
pinLastColumn: true,
|
||||||
|
lastColId: 'actions',
|
||||||
|
})
|
||||||
|
expect(result.enablePinning).toBe(false)
|
||||||
|
expect(result.columnPinning.end).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('end-pins only with horizontalScroll', () => {
|
||||||
|
const result = kitColumnPinning({
|
||||||
|
pinLastColumn: true,
|
||||||
|
horizontalScroll: true,
|
||||||
|
lastColId: 'actions',
|
||||||
|
})
|
||||||
|
expect(result.enablePinning).toBe(true)
|
||||||
|
expect(result.columnPinning.end).toEqual(['actions'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,81 @@ export const kitDataGridTableClassNames = {
|
|||||||
edgeCell: 'first:ps-3 last:pe-3',
|
edgeCell: 'first:ps-3 last:pe-3',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compact action column size from data-grid-filtering-2.
|
||||||
|
* Preview: https://reui.io/preview/base/data-grid-filtering-2
|
||||||
|
*/
|
||||||
|
export const KIT_ACTION_COLUMN_SIZE = 56
|
||||||
|
|
||||||
|
const ACTION_CELL_ALIGN = 'flex items-center justify-end'
|
||||||
|
|
||||||
|
function lastColumnId<T extends object>(columns: DataGridColumnDef<T>[]): string {
|
||||||
|
const last = columns[columns.length - 1]
|
||||||
|
if (!last) return ''
|
||||||
|
if (last.id) return last.id
|
||||||
|
if ('accessorKey' in last && typeof last.accessorKey === 'string') return last.accessorKey
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapActionCell<T extends object>(
|
||||||
|
cell: DataGridColumnDef<T>['cell'],
|
||||||
|
): DataGridColumnDef<T>['cell'] {
|
||||||
|
if (typeof cell !== 'function') {
|
||||||
|
return () => <div className={ACTION_CELL_ALIGN}>{cell as ReactNode}</div>
|
||||||
|
}
|
||||||
|
return (ctx) => <div className={ACTION_CELL_ALIGN}>{cell(ctx)}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* filtering-2 action column DNA: locked width, no sort/resize, inner justify-end
|
||||||
|
* (flex on the cell wrapper, never on `td` — that breaks rowBorder alignment).
|
||||||
|
* Preview: https://reui.io/preview/base/data-grid-filtering-2
|
||||||
|
*/
|
||||||
|
export function applyKitActionColumn<T extends object>(
|
||||||
|
columns: DataGridColumnDef<T>[],
|
||||||
|
opts: { pinLastColumn?: boolean } = {},
|
||||||
|
): DataGridColumnDef<T>[] {
|
||||||
|
if (columns.length === 0) return columns
|
||||||
|
const last = columns[columns.length - 1]
|
||||||
|
const lastId = lastColumnId(columns)
|
||||||
|
if (lastId !== 'actions' && !opts.pinLastColumn) return columns
|
||||||
|
|
||||||
|
const size = last.size ?? KIT_ACTION_COLUMN_SIZE
|
||||||
|
return [
|
||||||
|
...columns.slice(0, -1),
|
||||||
|
{
|
||||||
|
...last,
|
||||||
|
size,
|
||||||
|
minSize: last.minSize ?? size,
|
||||||
|
maxSize: last.maxSize ?? size,
|
||||||
|
enableSorting: false,
|
||||||
|
enableResizing: false,
|
||||||
|
cell: last.cell ? wrapActionCell(last.cell) : last.cell,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** End-pin only when the grid actually scrolls horizontally (not for action columns). */
|
||||||
|
export function kitColumnPinning(opts: {
|
||||||
|
pinLastColumn?: boolean
|
||||||
|
horizontalScroll?: boolean
|
||||||
|
lastColId: string
|
||||||
|
pinLeftColumnIds?: string[]
|
||||||
|
}): {
|
||||||
|
enablePinning: boolean
|
||||||
|
columnPinning: { start: string[]; end: string[] }
|
||||||
|
} {
|
||||||
|
const pinLeft = opts.pinLeftColumnIds ?? []
|
||||||
|
const pinEnd = Boolean(opts.pinLastColumn && opts.horizontalScroll && opts.lastColId)
|
||||||
|
return {
|
||||||
|
enablePinning: pinEnd || pinLeft.length > 0,
|
||||||
|
columnPinning: {
|
||||||
|
start: pinLeft,
|
||||||
|
end: pinEnd ? [opts.lastColId] : [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function loadStoredColumnVisibility(key: string): ColumnVisibilityState | undefined {
|
function loadStoredColumnVisibility(key: string): ColumnVisibilityState | undefined {
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(key)
|
const raw = localStorage.getItem(key)
|
||||||
@@ -281,6 +356,7 @@ export function FrameDataGrid<TData extends object>({
|
|||||||
expandedContent,
|
expandedContent,
|
||||||
getRowCanExpand,
|
getRowCanExpand,
|
||||||
pinLeftColumnIds,
|
pinLeftColumnIds,
|
||||||
|
horizontalScroll = false,
|
||||||
tableWidth = 'fixed',
|
tableWidth = 'fixed',
|
||||||
columnPinControls = false,
|
columnPinControls = false,
|
||||||
}: FrameDataGridProps<TData>) {
|
}: FrameDataGridProps<TData>) {
|
||||||
@@ -345,21 +421,23 @@ export function FrameDataGrid<TData extends object>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tableColumns: DataGridColumnDef<TData>[] = applyColumnPinControls(
|
const tableColumns: DataGridColumnDef<TData>[] = applyColumnPinControls(
|
||||||
[
|
applyKitActionColumn(
|
||||||
...(expandedContent ? [expandColumn] : []),
|
[
|
||||||
...(enableRowSelection ? [selectColumn] : []),
|
...(expandedContent ? [expandColumn] : []),
|
||||||
...columns,
|
...(enableRowSelection ? [selectColumn] : []),
|
||||||
],
|
...columns,
|
||||||
|
],
|
||||||
|
{ pinLastColumn },
|
||||||
|
),
|
||||||
columnPinControls,
|
columnPinControls,
|
||||||
)
|
)
|
||||||
|
|
||||||
const lastColId = pinLastColumn ? (tableColumns[tableColumns.length - 1]?.id ?? '') : ''
|
const { enablePinning, columnPinning } = kitColumnPinning({
|
||||||
const pinLeft = pinLeftColumnIds ?? []
|
pinLastColumn,
|
||||||
const enablePinning = pinLastColumn || pinLeft.length > 0
|
horizontalScroll,
|
||||||
const columnPinning = {
|
lastColId: lastColumnId(tableColumns),
|
||||||
start: pinLeft,
|
pinLeftColumnIds,
|
||||||
end: pinLastColumn && lastColId ? [lastColId] : [],
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const table = useTable({
|
const table = useTable({
|
||||||
features: dataGridFeatures,
|
features: dataGridFeatures,
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import { FILTERS_LABELS_RU, FILTERS_OPERATOR_LABELS_RU } from '@/lib/filters-i18
|
|||||||
import { applyFiltersToData, createEmptyFilterQuery } from './filter-utils'
|
import { applyFiltersToData, createEmptyFilterQuery } from './filter-utils'
|
||||||
import {
|
import {
|
||||||
FrameDataGrid,
|
FrameDataGrid,
|
||||||
|
applyKitActionColumn,
|
||||||
|
kitColumnPinning,
|
||||||
kitDataGridTableClassNames,
|
kitDataGridTableClassNames,
|
||||||
kitDataGridTableLayout,
|
kitDataGridTableLayout,
|
||||||
type DataGridColumnDef,
|
type DataGridColumnDef,
|
||||||
@@ -278,6 +280,7 @@ function ResourcePageFiltered<T extends object>({
|
|||||||
onRowClick,
|
onRowClick,
|
||||||
virtualization = false,
|
virtualization = false,
|
||||||
height = 480,
|
height = 480,
|
||||||
|
horizontalScroll = false,
|
||||||
}: ResourcePageProps<T>) {
|
}: ResourcePageProps<T>) {
|
||||||
const headerActions = primaryAction ?? actions
|
const headerActions = primaryAction ?? actions
|
||||||
const [internalTab, setInternalTab] = useState(tabs?.[0]?.id ?? 'all')
|
const [internalTab, setInternalTab] = useState(tabs?.[0]?.id ?? 'all')
|
||||||
@@ -328,12 +331,15 @@ function ResourcePageFiltered<T extends object>({
|
|||||||
|
|
||||||
const selectedCount = selectedIds.length
|
const selectedCount = selectedIds.length
|
||||||
|
|
||||||
const lastColId = pinLastColumn ? (columns[columns.length - 1]?.id ?? '') : ''
|
const tableColumns = useMemo(
|
||||||
const enablePinning = Boolean(pinLastColumn && lastColId)
|
() => applyKitActionColumn(columns, { pinLastColumn }),
|
||||||
const columnPinning = {
|
[columns, pinLastColumn],
|
||||||
start: [] as string[],
|
)
|
||||||
end: enablePinning ? [lastColId] : [],
|
const { enablePinning, columnPinning } = kitColumnPinning({
|
||||||
}
|
pinLastColumn,
|
||||||
|
horizontalScroll,
|
||||||
|
lastColId: tableColumns[tableColumns.length - 1]?.id ?? '',
|
||||||
|
})
|
||||||
|
|
||||||
const clearSelection = useCallback(() => {
|
const clearSelection = useCallback(() => {
|
||||||
setRowSelection({})
|
setRowSelection({})
|
||||||
@@ -342,7 +348,7 @@ function ResourcePageFiltered<T extends object>({
|
|||||||
const table = useTable({
|
const table = useTable({
|
||||||
features: dataGridFeatures,
|
features: dataGridFeatures,
|
||||||
data: filteredData,
|
data: filteredData,
|
||||||
columns,
|
columns: tableColumns,
|
||||||
getRowId: (row, index) => getRowId(row, index),
|
getRowId: (row, index) => getRowId(row, index),
|
||||||
state: {
|
state: {
|
||||||
sorting,
|
sorting,
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export function ScheduleModulesGrid({
|
|||||||
{
|
{
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
|
size: 104,
|
||||||
header: () => null,
|
header: () => null,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
|
|||||||
Reference in New Issue
Block a user