diff --git a/.cursor/rules/ls-foundry-core.mdc b/.cursor/rules/ls-foundry-core.mdc index b4efc2d..a8fc972 100644 --- a/.cursor/rules/ls-foundry-core.mdc +++ b/.cursor/rules/ls-foundry-core.mdc @@ -42,7 +42,7 @@ Also update the pin table below, `docs/stickpak/phase-1.md` version table + inst | Package | Version | |---------|---------| | `@jeffgo10/shared-types` | 0.2.0 | -| `@jeffgo10/react-canvas-designer` | 0.2.9 | +| `@jeffgo10/react-canvas-designer` | 0.2.15 | | `@jeffgo10/canvas-upscaler` | 0.2.0 | Always bump `shared-types` first when changing layout/DPI APIs; keep dependent package pins in sync. diff --git a/apps/docs/src/components/StickPakCanvasSection.test.tsx b/apps/docs/src/components/StickPakCanvasSection.test.tsx index c6acdf6..8ef8d9e 100644 --- a/apps/docs/src/components/StickPakCanvasSection.test.tsx +++ b/apps/docs/src/components/StickPakCanvasSection.test.tsx @@ -9,11 +9,11 @@ const exportLayout = jest.fn(async () => ({ assets: [], })); -let mockSelectedId: string | null = "sticker-1"; +let mockSelectedIds: string[] = ["sticker-1"]; jest.mock("next/dynamic", () => () => - forwardRef void }>( - function MockCanvasDesigner({ onSelectedIdChange }, ref) { + forwardRef void }>( + function MockCanvasDesigner({ onSelectedIdsChange }, ref) { useImperativeHandle(ref, () => ({ exportLayout, exportLayoutState: jest.fn(), @@ -27,8 +27,8 @@ jest.mock("next/dynamic", () => () => })); useEffect(() => { - onSelectedIdChange?.(mockSelectedId); - }, [onSelectedIdChange]); + onSelectedIdsChange?.(mockSelectedIds); + }, [onSelectedIdsChange]); return
CanvasDesigner
; }, @@ -41,7 +41,7 @@ import StickPakCanvasSection from "./StickPakCanvasSection"; describe("StickPakCanvasSection", () => { beforeEach(() => { - mockSelectedId = "sticker-1"; + mockSelectedIds = ["sticker-1"]; duplicateSelectedHorizontally.mockReset().mockReturnValue(2); duplicateSelectedVertically.mockReset().mockReturnValue(1); arrangeAll.mockReset().mockResolvedValue(true); @@ -79,7 +79,7 @@ describe("StickPakCanvasSection", () => { }); it("disables duplicate buttons when nothing is selected", () => { - mockSelectedId = null; + mockSelectedIds = []; render(); expect( diff --git a/apps/docs/src/components/StickPakCanvasSection.tsx b/apps/docs/src/components/StickPakCanvasSection.tsx index e23f598..2dce294 100644 --- a/apps/docs/src/components/StickPakCanvasSection.tsx +++ b/apps/docs/src/components/StickPakCanvasSection.tsx @@ -29,12 +29,12 @@ function StickPakCanvasSection() { const [autoArrangeOnAdd, setAutoArrangeOnAdd] = useState(false); const [showSelectionDimensions, setShowSelectionDimensions] = useState(true); const [dimensionUnit, setDimensionUnit] = useState("mm"); - const [selectedId, setSelectedId] = useState(null); + const [selectedIds, setSelectedIds] = useState([]); const [arrangeMessage, setArrangeMessage] = useState(""); const [duplicateMessage, setDuplicateMessage] = useState(""); const [isExporting, setIsExporting] = useState(false); const [isArranging, setIsArranging] = useState(false); - const hasSelection = selectedId !== null; + const hasSelection = selectedIds.length > 0; const handleExport = async () => { if (!designerRef.current) return; @@ -166,7 +166,7 @@ function StickPakCanvasSection() { autoArrangeOnAdd={autoArrangeOnAdd} showSelectionDimensions={showSelectionDimensions} dimensionUnit={dimensionUnit} - onSelectedIdChange={setSelectedId} + onSelectedIdsChange={setSelectedIds} onAutoArrange={({ allPlaced }) => { setDuplicateMessage(""); setArrangeMessage( @@ -180,8 +180,10 @@ function StickPakCanvasSection() {

{hasSelection - ? "Selected sticker — duplicate to fill a row or column inside the printable margin." - : "Click a sticker to select it, then duplicate horizontally or vertically."} + ? selectedIds.length > 1 + ? `${selectedIds.length} stickers selected — duplicate fills the whole selection together. Use the transform box to move, resize, or rotate.` + : "Selected sticker — duplicate to fill a row or column inside the printable margin." + : "Click a sticker to select it. Shift-click to multi-select, or drag on empty canvas to marquee-select."}