Add move row up/down functionality to board editor.

Rows can now be reordered using up/down buttons. Buttons are disabled
at boundaries (up disabled on first row, down disabled on last row).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mike Cao 2026-01-18 20:17:21 -08:00
parent d9f698ca42
commit ff6575ff54

View file

@ -4,7 +4,7 @@ import { Fragment, type ReactElement } from 'react';
import { Group, Panel, Separator } from 'react-resizable-panels'; import { Group, Panel, Separator } from 'react-resizable-panels';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { useBoard } from '@/components/hooks'; import { useBoard } from '@/components/hooks';
import { Minus, Plus } from '@/components/icons'; import { ChevronDown, Minus, Plus } from '@/components/icons';
import type { BoardColumn as BoardColumnType } from '@/lib/types'; import type { BoardColumn as BoardColumnType } from '@/lib/types';
const CATALOG = { const CATALOG = {
@ -14,6 +14,12 @@ const CATALOG = {
}, },
}; };
const MIN_HEIGHT = 300;
const MAX_HEIGHT = 600;
const MIN_WIDTH = 300;
const MARGIN = 10;
const MAX_COLUMNS = 4;
export function BoardBody() { export function BoardBody() {
const { board, updateBoard, saveBoard, isPending } = useBoard(); const { board, updateBoard, saveBoard, isPending } = useBoard();
@ -31,7 +37,6 @@ export function BoardBody() {
}; };
const handleRemoveRow = (id: string) => { const handleRemoveRow = (id: string) => {
console.log('Removing row', id);
updateBoard({ updateBoard({
parameters: produce(board.parameters, draft => { parameters: produce(board.parameters, draft => {
if (!draft.rows) { if (!draft.rows) {
@ -43,44 +48,90 @@ export function BoardBody() {
}); });
}; };
const handleMoveRowUp = (id: string) => {
updateBoard({
parameters: produce(board.parameters, draft => {
if (!draft.rows) return;
const index = draft.rows.findIndex(row => row.id === id);
if (index > 0) {
const temp = draft.rows[index - 1];
draft.rows[index - 1] = draft.rows[index];
draft.rows[index] = temp;
}
}),
});
};
const handleMoveRowDown = (id: string) => {
updateBoard({
parameters: produce(board.parameters, draft => {
if (!draft.rows) return;
const index = draft.rows.findIndex(row => row.id === id);
if (index < draft.rows.length - 1) {
const temp = draft.rows[index + 1];
draft.rows[index + 1] = draft.rows[index];
draft.rows[index] = temp;
}
}),
});
};
const rows = board?.parameters?.rows ?? []; const rows = board?.parameters?.rows ?? [];
const minHeight = 300 * (rows.length || 1); const rowCount = (rows.length || 1) + 1;
const minHeight = (MAX_HEIGHT + MARGIN) * rowCount;
return ( return (
<> <Group orientation="vertical" style={{ minHeight }}>
<Group orientation="vertical" style={{ minHeight }}> {rows.map((row, index) => (
{rows.map((row, index) => ( <Fragment key={row.id}>
<Fragment key={row.id}> <Panel minSize={MIN_HEIGHT}>
<Panel minSize={200}> <BoardRow
<BoardRow {...row} rowId={row.id} onRemove={handleRemoveRow} /> {...row}
</Panel> rowId={row.id}
{index < rows.length - 1 && <Separator />} rowIndex={index}
</Fragment> rowCount={rows.length}
))} onRemove={handleRemoveRow}
</Group> onMoveUp={handleMoveRowUp}
<Row> onMoveDown={handleMoveRowDown}
<TooltipTrigger delay={0}> />
<Button variant="outline" onPress={handleAddRow}> </Panel>
<Icon> {index < rows.length - 1 && <Separator />}
<Plus /> </Fragment>
</Icon> ))}
</Button> <Panel>
<Tooltip placement="bottom">Add row</Tooltip> <Row padding="3">
</TooltipTrigger> <TooltipTrigger delay={0}>
</Row> <Button variant="outline" onPress={handleAddRow}>
</> <Icon>
<Plus />
</Icon>
</Button>
<Tooltip placement="bottom">Add row</Tooltip>
</TooltipTrigger>
</Row>
</Panel>
</Group>
); );
} }
function BoardRow({ function BoardRow({
rowId, rowId,
rowIndex,
rowCount,
columns, columns,
onRemove, onRemove,
onMoveUp,
onMoveDown,
}: { }: {
rowId: string; rowId: string;
rowIndex: number;
rowCount: number;
columns: BoardColumnType[]; columns: BoardColumnType[];
onAddComponent?: () => void;
onRemove?: (id: string) => void; onRemove?: (id: string) => void;
onMoveUp?: (id: string) => void;
onMoveDown?: (id: string) => void;
}) { }) {
const { board, updateBoard } = useBoard(); const { board, updateBoard } = useBoard();
@ -102,27 +153,54 @@ function BoardRow({
<Group style={{ height: '100%' }}> <Group style={{ height: '100%' }}>
{columns?.map((column, index) => ( {columns?.map((column, index) => (
<Fragment key={column.id}> <Fragment key={column.id}>
<Panel minSize={300}> <Panel minSize={MIN_HEIGHT}>
<BoardColumn {...column} /> <BoardColumn {...column} />
</Panel> </Panel>
{index < columns.length - 1 && <Separator />} {index < columns.length - 1 && <Separator />}
</Fragment> </Fragment>
))} ))}
<Box alignSelf="center" padding="3"> <Column alignSelf="center" padding="3" gap="1">
<Button variant="outline" onPress={handleAddColumn}> <TooltipTrigger delay={0}>
<Icon> <Button variant="outline" onPress={() => onMoveUp?.(rowId)} isDisabled={rowIndex === 0}>
<Plus /> <Icon rotate={180}>
</Icon> <ChevronDown />
</Button> </Icon>
</Button>
<Tooltip placement="top">Move row up</Tooltip>
</TooltipTrigger>
<TooltipTrigger delay={0}>
<Button
variant="outline"
onPress={handleAddColumn}
isDisabled={columns.length >= MAX_COLUMNS}
>
<Icon>
<Plus />
</Icon>
</Button>
<Tooltip placement="left">Add column</Tooltip>
</TooltipTrigger>
<TooltipTrigger delay={0}> <TooltipTrigger delay={0}>
<Button variant="outline" onPress={() => onRemove?.(rowId)}> <Button variant="outline" onPress={() => onRemove?.(rowId)}>
<Icon> <Icon>
<Minus /> <Minus />
</Icon> </Icon>
</Button> </Button>
<Tooltip placement="bottom">Remove row</Tooltip> <Tooltip placement="left">Remove row</Tooltip>
</TooltipTrigger> </TooltipTrigger>
</Box> <TooltipTrigger delay={0}>
<Button
variant="outline"
onPress={() => onMoveDown?.(rowId)}
isDisabled={rowIndex === rowCount - 1}
>
<Icon>
<ChevronDown />
</Icon>
</Button>
<Tooltip placement="bottom">Move row down</Tooltip>
</TooltipTrigger>
</Column>
</Group> </Group>
); );
} }