"use client"; import { useState } from "react"; import type { RefObject } from "react"; import { ChoiceGroup, choiceOptions } from "@/components/form/choice-group"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { SheetFooter } from "@/components/ui/sheet"; import { FRAME_TYPE_LABELS } from "@/lib/hive/labels"; import { partDisplayName } from "@/lib/hive/names"; import { FRAME_TYPES, MAX_TEXT_LENGTH } from "@/lib/hive/types"; import type { FrameType, HiveCatalog, HiveState } from "@/lib/hive/types"; import type { HiveOutlineHandle } from "./hive-outline"; import { HiveSheetShell } from "./hive-sheet-shell"; import { useSheetSubmit } from "./use-sheet-submit"; import type { SubmitOps } from "./use-sheet-submit"; const FRAME_OPTIONS = choiceOptions(FRAME_TYPE_LABELS, FRAME_TYPES); export interface FrameSheetProps { open: boolean; onOpenChange: (open: boolean) => void; state: HiveState; catalog: HiveCatalog; boxId: string; slot: number; /** Sends the ops (source CARD); rejects on failure. */ submit: SubmitOps; /** On close, focus returns to this frame's item in the outline. */ outlineRef: RefObject; } interface FrameFormProps { initialType: FrameType; initialName: string; boxId: string; slot: number; submit: SubmitOps; /** Saved, or the hive changed meanwhile (the conflict sheet takes over). */ onClose: () => void; } function FrameForm({ initialType, initialName, boxId, slot, submit, onClose }: FrameFormProps) { const [type, setType] = useState(initialType); const [name, setName] = useState(initialName); const { send, pending, error } = useSheetSubmit(submit); const trimmed = name.trim(); const changed = type !== initialType || trimmed !== initialName.trim(); async function save() { const outcome = await send([{ op: "setFrame", boxId, slot, type, name: trimmed === "" ? null : trimmed }]); if (outcome !== "failed") onClose(); } return (
{ // Portal forms bubble their submit through the React tree (e.g. into an enclosing form). event.preventDefault(); event.stopPropagation(); if (changed && !pending) void save(); }} >
Art
setName(event.target.value)} aria-describedby={`frame-${boxId}-${slot}-count`} className="h-11" />

{name.length} / {MAX_TEXT_LENGTH}

{error ? (

{error}

) : null}
); } /** Edit one frame of a box: its type and an optional name (spec 9.4). One `setFrame` per save. */ export function FrameSheet({ open, onOpenChange, state, catalog, boxId, slot, submit, outlineRef }: FrameSheetProps) { const box = state.stack.find((candidate) => candidate.id === boxId); const frame = box?.frames?.find((candidate) => candidate.slot === slot); return ( { outlineRef.current?.focusFrame(boxId, slot); return false; }} > {frame ? ( onOpenChange(false)} /> ) : (

Dieses Rähmchen gibt es nicht mehr.

)}
); }