import { useState } from "react"; import { cn } from "@/lib/utils"; import type { LineSlideElement } from "@/components/slide-editor/state/state"; import { FloatingToolbar, type FloatingToolbarBox, } from "@/components/slide-editor/toolbar/FloatingToolbar"; import { ComponentActionsMenu, ComponentUngroupButton, type ComponentActionsMenuActions, } from "@/components/slide-editor/selection/ComponentActionsMenu"; import { OpacitySwatchIcon } from "@/components/slide-editor/toolbar/OpacitySwatchIcon"; import { withHash } from "@/components/slide-editor/utils/color"; import { ColorField, Divider, Panel, ShadowPanel, SliderField, ToolbarButton, } from "@/components/slide-editor/shapes/ShapeToolbar"; type LinePanel = | "style" | "color" | "shadow" | "opacity" | null; type LineStyleKey = "solid" | "dashed" | "dotted"; const LINE_STYLE_OPTIONS: Array<{ key: LineStyleKey; label: string; dash: number[]; }> = [ { key: "solid", label: "Solid", dash: [] }, { key: "dashed", label: "Dashed", dash: [10, 6] }, { key: "dotted", label: "Dotted", dash: [2, 4] }, ]; const DEFAULT_LINE_SHADOW = { color: "#000000", blur: 8, opacity: 0.2, offset_x: 0.04, offset_y: 0.04, }; export function LineToolbar({ anchorBox, element, index, scale, componentActions, onChange, }: { anchorBox?: FloatingToolbarBox | null; element: LineSlideElement; index: number; scale: number; componentActions?: ComponentActionsMenuActions | null; onChange: (index: number, element: LineSlideElement) => void; }) { const [openPanel, setOpenPanel] = useState(null); const position = element.position ?? { x: 0, y: 0 }; const size = element.size ?? { width: 1, height: 1 }; const stroke = element.stroke; const shadow = element.shadow ?? DEFAULT_LINE_SHADOW; const shadowEnabled = element.shadow != null; const currentStyle = lineStyleFromDash(stroke.dash); const update = (changes: Partial) => { onChange(index, { ...element, ...changes }); }; const setStroke = (changes: Partial) => { update({ stroke: { ...stroke, ...changes } }); }; const togglePanel = (panel: Exclude) => { setOpenPanel((current) => (current === panel ? null : panel)); }; const toggleShadowPanel = () => { if (!shadowEnabled) update({ shadow }); togglePanel("shadow"); }; return (
togglePanel("style")} > {openPanel === "style" ? (
{LINE_STYLE_OPTIONS.map((option) => ( ))}
`${Math.round(value)} pt`} onCommit={(width) => setStroke({ width })} />
) : null}
togglePanel("color")} > {openPanel === "color" ? ( setStroke({ color })} /> ) : null}
{openPanel === "shadow" ? ( update({ shadow: { ...shadow, ...changes } })} /> ) : null}
togglePanel("opacity")} > {openPanel === "opacity" ? ( `${Math.round(value * 100)}%`} onCommit={(opacity) => update({ opacity })} /> ) : null}
{componentActions ? ( <> {componentActions.canUngroup ? : null} ) : null}
); } function lineStyleFromDash(dash: unknown): (typeof LINE_STYLE_OPTIONS)[number] { const dashArray = Array.isArray(dash) ? dash.filter((item): item is number => typeof item === "number") : []; return ( LINE_STYLE_OPTIONS.find( (option) => option.dash.length === dashArray.length && option.dash.every((value, index) => value === dashArray[index]), ) ?? LINE_STYLE_OPTIONS[0] ); } function LineWidthIcon() { return ( ); } function LinePreview({ dash }: { dash: number[] }) { return ( ); }