Compare commits

...
2 Commits
Author SHA1 Message Date
Denozordec 764a4e5b4e feat(web): enhance action column functionality in data grid components
quality / commitlint (push) Skipped
quality / changes (push) Successful in 6s
quality / go (push) Skipped
quality / bird2 (push) Skipped
quality / docker-check (push) Skipped
quality / openapi (push) Successful in 23s
quality / web (push) Successful in 1m4s
CD / quality (push) Successful in 1m36s
CD / publish (push) Successful in 2m55s
- Introduced `KIT_ACTION_COLUMN_SIZE` for consistent action column sizing across data grids.
- Updated `applyKitActionColumn` to enforce action column properties such as fixed size, no sorting, and no resizing.
- Enhanced `kitColumnPinning` logic to conditionally enable pinning based on horizontal scrolling.
- Added tests for `applyKitActionColumn` and `kitColumnPinning` to ensure expected behavior.
- Adjusted `AccessApiKeysGrid`, `ScheduleModulesGrid`, and `ResourcePageFiltered` components to utilize new action column features.
2026-08-23 02:58:07 +07:00
Denozordec 2d29a892f9 feat(web): integrate shadcn and enhance data grid styles
quality / changes (push) Successful in 8s
quality / commitlint (push) Skipped
quality / docker-check (push) Skipped
quality / openapi (push) Successful in 31s
quality / web (push) Successful in 1m7s
quality / go (push) Successful in 1m10s
quality / bird2 (push) Successful in 17s
CD / quality (push) Successful in 3m20s
CD / publish (push) Successful in 4m5s
- Added `shadcn` dependency to the project for improved styling.
- Updated `kitDataGridTableClassNames` to include a new base class for pinned cells.
- Enhanced global styles by importing `shadcn/tailwind.css`.
- Added tests to verify new class names in the data grid component.
2026-08-23 02:33:22 +07:00
8 changed files with 1505 additions and 21 deletions
+1
View File
@@ -40,6 +40,7 @@
"react-dom": "^19.2.0",
"react-hook-form": "^7.60.0",
"recharts": "3.8.0",
"shadcn": "^4.19.0",
"sonner": "^1.7.0",
"zod": "^3.25.0"
},
@@ -98,6 +98,7 @@ export function AccessApiKeysGrid({
{
id: 'actions',
enableSorting: false,
size: 88,
header: () => null,
cell: ({ row }) => {
const k = row.original
@@ -1,6 +1,12 @@
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', () => {
it('filtering-2 defaults: dense, headerBackground false, width fixed', () => {
@@ -15,6 +21,9 @@ describe('kitDataGridTableLayout', () => {
it('kit tableClassNames совпадает с filtering-2 edgeCell', () => {
expect(kitDataGridTableClassNames.edgeCell).toBe('first:ps-3 last:pe-3')
expect(kitDataGridTableClassNames.base).toBe(
'[&_[data-pinned]]:bg-(--frame-panel-bg)',
)
})
it('именованные opts: auto + pin', () => {
@@ -27,3 +36,63 @@ describe('kitDataGridTableLayout', () => {
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'])
})
})
@@ -81,11 +81,87 @@ export function kitDataGridTableLayout(
}
}
/** filtering-2 edge inset — единственный tableClassNames в kit, не в domain. */
/** filtering-2 edge inset + Frame-surface pinned cells (not page --background). */
export const kitDataGridTableClassNames = {
base: '[&_[data-pinned]]:bg-(--frame-panel-bg)',
edgeCell: 'first:ps-3 last:pe-3',
} 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 {
try {
const raw = localStorage.getItem(key)
@@ -280,6 +356,7 @@ export function FrameDataGrid<TData extends object>({
expandedContent,
getRowCanExpand,
pinLeftColumnIds,
horizontalScroll = false,
tableWidth = 'fixed',
columnPinControls = false,
}: FrameDataGridProps<TData>) {
@@ -344,21 +421,23 @@ export function FrameDataGrid<TData extends object>({
}
const tableColumns: DataGridColumnDef<TData>[] = applyColumnPinControls(
[
...(expandedContent ? [expandColumn] : []),
...(enableRowSelection ? [selectColumn] : []),
...columns,
],
applyKitActionColumn(
[
...(expandedContent ? [expandColumn] : []),
...(enableRowSelection ? [selectColumn] : []),
...columns,
],
{ pinLastColumn },
),
columnPinControls,
)
const lastColId = pinLastColumn ? (tableColumns[tableColumns.length - 1]?.id ?? '') : ''
const pinLeft = pinLeftColumnIds ?? []
const enablePinning = pinLastColumn || pinLeft.length > 0
const columnPinning = {
start: pinLeft,
end: pinLastColumn && lastColId ? [lastColId] : [],
}
const { enablePinning, columnPinning } = kitColumnPinning({
pinLastColumn,
horizontalScroll,
lastColId: lastColumnId(tableColumns),
pinLeftColumnIds,
})
const table = useTable({
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 {
FrameDataGrid,
applyKitActionColumn,
kitColumnPinning,
kitDataGridTableClassNames,
kitDataGridTableLayout,
type DataGridColumnDef,
@@ -278,6 +280,7 @@ function ResourcePageFiltered<T extends object>({
onRowClick,
virtualization = false,
height = 480,
horizontalScroll = false,
}: ResourcePageProps<T>) {
const headerActions = primaryAction ?? actions
const [internalTab, setInternalTab] = useState(tabs?.[0]?.id ?? 'all')
@@ -328,12 +331,15 @@ function ResourcePageFiltered<T extends object>({
const selectedCount = selectedIds.length
const lastColId = pinLastColumn ? (columns[columns.length - 1]?.id ?? '') : ''
const enablePinning = Boolean(pinLastColumn && lastColId)
const columnPinning = {
start: [] as string[],
end: enablePinning ? [lastColId] : [],
}
const tableColumns = useMemo(
() => applyKitActionColumn(columns, { pinLastColumn }),
[columns, pinLastColumn],
)
const { enablePinning, columnPinning } = kitColumnPinning({
pinLastColumn,
horizontalScroll,
lastColId: tableColumns[tableColumns.length - 1]?.id ?? '',
})
const clearSelection = useCallback(() => {
setRowSelection({})
@@ -342,7 +348,7 @@ function ResourcePageFiltered<T extends object>({
const table = useTable({
features: dataGridFeatures,
data: filteredData,
columns,
columns: tableColumns,
getRowId: (row, index) => getRowId(row, index),
state: {
sorting,
@@ -96,6 +96,7 @@ export function ScheduleModulesGrid({
{
id: 'actions',
enableSorting: false,
size: 104,
header: () => null,
cell: ({ row }) => (
<div className="text-right">
+1
View File
@@ -1,5 +1,6 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@source "../";
@source "../../../apps/web/src";
+1326
View File
File diff suppressed because it is too large Load Diff