diff --git a/src/components/block-kitchen.tsx b/src/components/block-kitchen.tsx index 7a679a2..ba4532c 100644 --- a/src/components/block-kitchen.tsx +++ b/src/components/block-kitchen.tsx @@ -188,6 +188,7 @@ export function BlockKitchen(props: BlockKitchenProps) { onUpdate={updateBlock} onDuplicate={duplicateBlock} onDelete={removeBlock} + onReorder={reorderBlock} isPaletteDrag={activePaletteVariant !== null} /> diff --git a/src/components/block-row.tsx b/src/components/block-row.tsx index ce21fac..07209d0 100644 --- a/src/components/block-row.tsx +++ b/src/components/block-row.tsx @@ -1,6 +1,6 @@ import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; -import { AlertTriangle, Copy, Pencil, Trash2 } from 'lucide-react'; +import { AlertTriangle, ArrowDown, ArrowUp, Copy, Pencil, Trash2 } from 'lucide-react'; import { useState } from 'react'; import type { RichTextBlock } from 'slack-web-api-client'; import { cn } from '../lib/cn'; @@ -59,6 +59,9 @@ export function BlockRow({ onUpdate, onDuplicate, onDelete, + index, + total, + onReorder, isPaletteDrag = false }: { builderBlock: BuilderBlock; @@ -73,6 +76,12 @@ export function BlockRow({ onUpdate: (id: string, block: SupportedBlock) => void; onDuplicate: (id: string) => void; onDelete: (id: string) => void; + /** Zero-based index of this row in the surface; powers move up/down. */ + index?: number; + /** Total number of rows on the surface; powers move up/down. */ + total?: number; + /** Move this row to a new index. Wires up the keyboard-accessible move buttons. */ + onReorder?: (id: string, toIndex: number) => void; /** True while a palette item is being dragged (vs. reordering an existing block). */ isPaletteDrag?: boolean; }) { @@ -143,6 +152,15 @@ export function BlockRow({ aria-label="Edit block" {...sortableA11yAttrs} {...listeners} + onKeyDown={(e) => { + // The div trigger is keyboard-activatable via role="button" + tabIndex=0, + // but Space on a non-button element scrolls the page by default. Toggle the + // popover ourselves on Enter/Space and swallow the event so the page stays put. + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onOpenChange?.(!isOpen); + } + }} className={cn( 'block w-full cursor-grab rounded-sm transition-shadow hover:shadow-md focus-visible:ring-1 focus-visible:ring-ring active:cursor-grabbing', hasErrors ? 'ring-1 ring-destructive/60 hover:ring-destructive' : 'hover:ring-1 hover:ring-border' @@ -166,7 +184,10 @@ export function BlockRow({
{hasErrors ? ( @@ -176,7 +197,7 @@ export function BlockRow({ type="button" aria-label={`Show ${errors!.length} validation ${errors!.length === 1 ? 'issue' : 'issues'}`} onClick={() => onOpenChange?.(true)} - className="flex h-6 w-6 items-center justify-center rounded text-destructive hover:bg-destructive/10" + className="flex h-6 w-6 items-center justify-center rounded text-destructive hover:bg-destructive/10 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" > @@ -191,6 +212,38 @@ export function BlockRow({ ) : null} + {onReorder && typeof index === 'number' && typeof total === 'number' ? ( + <> + + + + + Move up + + + + + + Move down + + + ) : null} @@ -216,7 +269,7 @@ export function BlockRow({ type="button" aria-label="Duplicate block" onClick={() => onDuplicate(builderBlock.id)} - className="flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground" + className="flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" > @@ -229,7 +282,7 @@ export function BlockRow({ type="button" aria-label="Delete block" onClick={() => onDelete(builderBlock.id)} - className="flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-destructive/10 hover:text-destructive" + className="flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-destructive/10 hover:text-destructive focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" > diff --git a/src/components/json-drawer.tsx b/src/components/json-drawer.tsx index ffd7e7c..df0d3c6 100644 --- a/src/components/json-drawer.tsx +++ b/src/components/json-drawer.tsx @@ -127,6 +127,7 @@ export function JsonDrawer({