feat(design-preview): перетаскивание строк в «Ссылках приложений» через dnd-kit
quality / changes (push) Successful in 9s
quality / commitlint (push) Skipped
quality / docker-check (push) Skipped
quality / web (push) Successful in 1m32s
quality / api (push) Successful in 42s
CD / quality (push) Successful in 2m25s
CD / publish (push) Successful in 2m52s
quality / changes (push) Successful in 9s
quality / commitlint (push) Skipped
quality / docker-check (push) Skipped
quality / web (push) Successful in 1m32s
quality / api (push) Successful in 42s
CD / quality (push) Successful in 2m25s
CD / publish (push) Successful in 2m52s
PreviewSectionGrid получил onReorder: ручка DataGridTableDndRowHandle и DataGridTableDndRows из ReUI. Порядок меняется перетаскиванием, меню строки осталось запасным путём. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4f5026980a
commit
41f5669812
2 files changed
+50
-9
No files matched your search
@@ -4,7 +4,6 @@ import {
|
|||||||
CloudIcon,
|
CloudIcon,
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
FingerprintIcon,
|
FingerprintIcon,
|
||||||
GripVerticalIcon,
|
|
||||||
KeyRoundIcon,
|
KeyRoundIcon,
|
||||||
LayoutDashboardIcon,
|
LayoutDashboardIcon,
|
||||||
LayoutGridIcon,
|
LayoutGridIcon,
|
||||||
@@ -141,6 +140,15 @@ export function AppLinksPage() {
|
|||||||
[addChange],
|
[addChange],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** Перетаскивание: строка встаёт на место той, на которую её бросили. */
|
||||||
|
const dropOn = useCallback(
|
||||||
|
(activeId: string, overId: string) => {
|
||||||
|
const to = rowsRef.current.findIndex((r) => r.id === overId)
|
||||||
|
if (to >= 0) moveTo(activeId as AppId, () => to)
|
||||||
|
},
|
||||||
|
[moveTo],
|
||||||
|
)
|
||||||
|
|
||||||
const resetOrder = useCallback(() => {
|
const resetOrder = useCallback(() => {
|
||||||
const before = rowsRef.current
|
const before = rowsRef.current
|
||||||
if (before.every((r, i) => r.id === INITIAL[i]?.id)) return
|
if (before.every((r, i) => r.id === INITIAL[i]?.id)) return
|
||||||
@@ -158,10 +166,9 @@ export function AppLinksPage() {
|
|||||||
{
|
{
|
||||||
id: 'order',
|
id: 'order',
|
||||||
title: '№',
|
title: '№',
|
||||||
size: 76,
|
size: 52,
|
||||||
cell: (r) => (
|
cell: (r) => (
|
||||||
<span className="text-muted-foreground flex items-center gap-1 text-xs tabular-nums">
|
<span className="text-muted-foreground text-sm tabular-nums">
|
||||||
<GripVerticalIcon className="size-4" aria-hidden />
|
|
||||||
{rows.findIndex((x) => x.id === r.id) + 1}
|
{rows.findIndex((x) => x.id === r.id) + 1}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -267,7 +274,7 @@ export function AppLinksPage() {
|
|||||||
<TabsContent value="list" className="min-w-0">
|
<TabsContent value="list" className="min-w-0">
|
||||||
<PreviewSectionGrid
|
<PreviewSectionGrid
|
||||||
title="Ссылки сервисов"
|
title="Ссылки сервисов"
|
||||||
description="Порядок строк = порядок в меню · изменения применяются после сохранения"
|
description="Перетащите строку за ручку: порядок строк = порядок в меню · изменения применяются после сохранения"
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
getRowId={(r) => r.id}
|
getRowId={(r) => r.id}
|
||||||
@@ -296,6 +303,7 @@ export function AppLinksPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
onReorder={dropOn}
|
||||||
onRowClick={setEditing}
|
onRowClick={setEditing}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import { DataGrid, dataGridFeatures, type DataGridFeatures } from '@/components/
|
|||||||
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
|
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
|
||||||
import { DataGridPagination } from '@/components/reui/data-grid/data-grid-pagination'
|
import { DataGridPagination } from '@/components/reui/data-grid/data-grid-pagination'
|
||||||
import { DataGridScrollArea } from '@/components/reui/data-grid/data-grid-scroll-area'
|
import { DataGridScrollArea } from '@/components/reui/data-grid/data-grid-scroll-area'
|
||||||
|
import {
|
||||||
|
DataGridTableDndRowHandle,
|
||||||
|
DataGridTableDndRows,
|
||||||
|
} from '@/components/reui/data-grid/data-grid-table-dnd-rows'
|
||||||
|
import type { DragEndEvent } from '@dnd-kit/core'
|
||||||
import {
|
import {
|
||||||
DataGridTable,
|
DataGridTable,
|
||||||
DataGridTableRowSelect,
|
DataGridTableRowSelect,
|
||||||
@@ -40,6 +45,7 @@ export type GridColumn<T> = {
|
|||||||
export type GridView<T> = { label: string; match: (row: T) => boolean }
|
export type GridView<T> = { label: string; match: (row: T) => boolean }
|
||||||
|
|
||||||
const SELECT_COLUMN_SIZE = 44
|
const SELECT_COLUMN_SIZE = 44
|
||||||
|
const DRAG_COLUMN_SIZE = 44
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Секция-таблица макета: Frame + Filters-подобная панель + DataGrid на DataGridScrollArea.
|
* Секция-таблица макета: Frame + Filters-подобная панель + DataGrid на DataGridScrollArea.
|
||||||
@@ -59,6 +65,7 @@ export function PreviewSectionGrid<T extends object>({
|
|||||||
selectable = false,
|
selectable = false,
|
||||||
bulkActions,
|
bulkActions,
|
||||||
toolbar,
|
toolbar,
|
||||||
|
onReorder,
|
||||||
pageSize = 8,
|
pageSize = 8,
|
||||||
emptyMessage = 'Нет записей по текущим фильтрам.',
|
emptyMessage = 'Нет записей по текущим фильтрам.',
|
||||||
className,
|
className,
|
||||||
@@ -75,6 +82,8 @@ export function PreviewSectionGrid<T extends object>({
|
|||||||
selectable?: boolean
|
selectable?: boolean
|
||||||
bulkActions?: (selectedIds: string[], clear: () => void) => ReactNode
|
bulkActions?: (selectedIds: string[], clear: () => void) => ReactNode
|
||||||
toolbar?: ReactNode
|
toolbar?: ReactNode
|
||||||
|
/** Включает перетаскивание строк (dnd-kit). Передаёт id строки и id строки, на место которой её бросили. */
|
||||||
|
onReorder?: (activeId: string, overId: string) => void
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
emptyMessage?: string
|
emptyMessage?: string
|
||||||
className?: string
|
className?: string
|
||||||
@@ -113,6 +122,16 @@ export function PreviewSectionGrid<T extends object>({
|
|||||||
|
|
||||||
const defs = useMemo<ColumnDef<DataGridFeatures, T>[]>(() => {
|
const defs = useMemo<ColumnDef<DataGridFeatures, T>[]>(() => {
|
||||||
const list: ColumnDef<DataGridFeatures, T>[] = []
|
const list: ColumnDef<DataGridFeatures, T>[] = []
|
||||||
|
if (onReorder) {
|
||||||
|
list.push({
|
||||||
|
id: 'drag',
|
||||||
|
header: () => <span className="sr-only">Порядок</span>,
|
||||||
|
cell: () => <DataGridTableDndRowHandle />,
|
||||||
|
enableSorting: false,
|
||||||
|
enableResizing: false,
|
||||||
|
size: DRAG_COLUMN_SIZE,
|
||||||
|
})
|
||||||
|
}
|
||||||
if (selectable) {
|
if (selectable) {
|
||||||
list.push({
|
list.push({
|
||||||
id: 'select',
|
id: 'select',
|
||||||
@@ -140,7 +159,7 @@ export function PreviewSectionGrid<T extends object>({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}, [columns, selectable])
|
}, [columns, selectable, onReorder])
|
||||||
|
|
||||||
const table = useTable({
|
const table = useTable({
|
||||||
features: dataGridFeatures,
|
features: dataGridFeatures,
|
||||||
@@ -154,7 +173,17 @@ export function PreviewSectionGrid<T extends object>({
|
|||||||
onPaginationChange: setPagination,
|
onPaginationChange: setPagination,
|
||||||
})
|
})
|
||||||
|
|
||||||
const minWidth = columns.reduce((sum, c) => sum + c.size, selectable ? SELECT_COLUMN_SIZE : 0)
|
const dataIds = table.getRowModel().rows.map((r) => r.id)
|
||||||
|
const handleDragEnd = (event: DragEndEvent) => {
|
||||||
|
const { active, over } = event
|
||||||
|
if (!onReorder || !active || !over || active.id === over.id) return
|
||||||
|
onReorder(String(active.id), String(over.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
const minWidth = columns.reduce(
|
||||||
|
(sum, c) => sum + c.size,
|
||||||
|
(selectable ? SELECT_COLUMN_SIZE : 0) + (onReorder ? DRAG_COLUMN_SIZE : 0),
|
||||||
|
)
|
||||||
const selectedIds = table.getSelectedRowModel().rows.map((r) => r.id)
|
const selectedIds = table.getSelectedRowModel().rows.map((r) => r.id)
|
||||||
const paged = data.length > pageSize
|
const paged = data.length > pageSize
|
||||||
|
|
||||||
@@ -165,7 +194,7 @@ export function PreviewSectionGrid<T extends object>({
|
|||||||
recordCount={data.length}
|
recordCount={data.length}
|
||||||
onRowClick={onRowClick}
|
onRowClick={onRowClick}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
tableLayout={{ headerSticky: false, dense: true, width: 'auto' }}
|
tableLayout={{ headerSticky: false, dense: true, width: 'auto', rowsDraggable: Boolean(onReorder) }}
|
||||||
>
|
>
|
||||||
<Frame spacing="sm" className={cn('w-full min-w-0', className)}>
|
<Frame spacing="sm" className={cn('w-full min-w-0', className)}>
|
||||||
<FrameHeader>
|
<FrameHeader>
|
||||||
@@ -226,7 +255,11 @@ export function PreviewSectionGrid<T extends object>({
|
|||||||
) : null}
|
) : null}
|
||||||
<DataGridScrollArea>
|
<DataGridScrollArea>
|
||||||
<div className="pb-3" style={{ minWidth }}>
|
<div className="pb-3" style={{ minWidth }}>
|
||||||
<DataGridTable />
|
{onReorder ? (
|
||||||
|
<DataGridTableDndRows dataIds={dataIds} handleDragEnd={handleDragEnd} />
|
||||||
|
) : (
|
||||||
|
<DataGridTable />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</DataGridScrollArea>
|
</DataGridScrollArea>
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|||||||
Reference in new issue
Block a user