1"use client"
2
3import type { ReactNode } from "react"
4import {
5 ArrowClockwise,
6 ArrowDown,
7 ArrowLeft,
8 ArrowRight,
9 ArrowUp,
10 MagnifyingGlassMinus,
11 MagnifyingGlassPlus,
12 Trash,
13 type Icon,
14} from "@phosphor-icons/react"
15
16import { Button } from "@/components/ui/button"
17import { useBoothStore, type StickerInstance, type StripText } from "@/stores/booth"
18import { STICKERS } from "./presets"
19
20/* DESIGN-SYSTEM §11: kanvas tidak terbaca pembaca layar, jadi tiap stiker dan
21 teks punya barisnya sendiri dengan tombol pindah, ukuran, putar, dan hapus.
22 Seluruh penyuntingan bisa selesai tanpa penunjuk; Transformer hanya jalur
23 tambahan. */
24
25const STEP = 8
26const WIDTH_STEP = 12
27const FONT_STEP = 4
28const ROTATE_STEP = 15
29
30function IconAction({
31 label,
32 icon: ActionIcon,
33 onClick,
34}: {
35 label: string
36 icon: Icon
37 onClick: () => void
38}) {
39 return (
40 <Button variant="ghost" size="icon" aria-label={label} onClick={onClick}>
41 <ActionIcon weight="bold" className="size-5" aria-hidden />
42 </Button>
43 )
44}
45
46function RowShell({
47 children,
48 readout,
49}: {
50 children: ReactNode
51 readout: string
52}) {
53 return (
54 <li className="flex flex-wrap items-center gap-x-3 gap-y-1 rounded-card border-2 border-ink bg-paper px-3 py-2">
55 <span className="flex min-w-0 flex-1 flex-col">
56 {children}
57 <span className="font-mono text-xs text-muted">{readout}</span>
58 </span>
59 </li>
60 )
61}
62
63function StickerRow({
64 sticker,
65 label,
66}: {
67 sticker: StickerInstance
68 label: string
69}) {
70 const updateSticker = useBoothStore((state) => state.updateSticker)
71 const removeSticker = useBoothStore((state) => state.removeSticker)
72
73 return (
74 <RowShell
75 readout={`x ${Math.round(sticker.x)} · y ${Math.round(sticker.y)} · ${Math.round(
76 sticker.width
77 )} px · ${Math.round(sticker.rotation)}°`}
78 >
79 <span className="font-bold">Stiker {label}</span>
80 <span className="mt-1 flex flex-wrap gap-1">
81 <IconAction
82 label={`Pindah kiri: ${label}`}
83 icon={ArrowLeft}
84 onClick={() => updateSticker(sticker.id, { x: sticker.x - STEP })}
85 />
86 <IconAction
87 label={`Pindah kanan: ${label}`}
88 icon={ArrowRight}
89 onClick={() => updateSticker(sticker.id, { x: sticker.x + STEP })}
90 />
91 <IconAction
92 label={`Pindah atas: ${label}`}
93 icon={ArrowUp}
94 onClick={() => updateSticker(sticker.id, { y: sticker.y - STEP })}
95 />
96 <IconAction
97 label={`Pindah bawah: ${label}`}
98 icon={ArrowDown}
99 onClick={() => updateSticker(sticker.id, { y: sticker.y + STEP })}
100 />
101 <IconAction
102 label={`Perbesar: ${label}`}
103 icon={MagnifyingGlassPlus}
104 onClick={() =>
105 updateSticker(sticker.id, { width: sticker.width + WIDTH_STEP })
106 }
107 />
108 <IconAction
109 label={`Perkecil: ${label}`}
110 icon={MagnifyingGlassMinus}
111 onClick={() =>
112 updateSticker(sticker.id, { width: sticker.width - WIDTH_STEP })
113 }
114 />
115 <IconAction
116 label={`Putar: ${label}`}
117 icon={ArrowClockwise}
118 onClick={() =>
119 updateSticker(sticker.id, { rotation: sticker.rotation + ROTATE_STEP })
120 }
121 />
122 <IconAction
123 label={`Hapus: ${label}`}
124 icon={Trash}
125 onClick={() => removeSticker(sticker.id)}
126 />
127 </span>
128 </RowShell>
129 )
130}
131
132function TextRow({ text }: { text: StripText }) {
133 const updateText = useBoothStore((state) => state.updateText)
134 const removeText = useBoothStore((state) => state.removeText)
135 const label = `Teks ${text.value}`
136
137 return (
138 <RowShell readout={`ukuran ${Math.round(text.fontSize)} px`}>
139 <span className="font-bold">{label}</span>
140 <span className="mt-1 flex flex-wrap gap-1">
141 <IconAction
142 label={`Perbesar: ${label}`}
143 icon={MagnifyingGlassPlus}
144 onClick={() =>
145 updateText(text.id, { fontSize: text.fontSize + FONT_STEP })
146 }
147 />
148 <IconAction
149 label={`Perkecil: ${label}`}
150 icon={MagnifyingGlassMinus}
151 onClick={() =>
152 updateText(text.id, { fontSize: text.fontSize - FONT_STEP })
153 }
154 />
155 <IconAction
156 label={`Hapus: ${label}`}
157 icon={Trash}
158 onClick={() => removeText(text.id)}
159 />
160 </span>
161 </RowShell>
162 )
163}
164
165export function DecorList() {
166 const stickers = useBoothStore((state) => state.stickers)
167 const texts = useBoothStore((state) => state.texts)
168
169 if (stickers.length === 0 && texts.length === 0) {
170 return null
171 }
172
173 return (
174 <section
175 aria-label="Isi strip"
176 className="mx-auto flex w-full max-w-[480px] flex-col gap-3"
177 >
178 <h3 className="font-mono text-xs font-bold tracking-[0.06em] uppercase">
179 Isi strip
180 </h3>
181 <ul className="flex flex-col gap-2">
182 {stickers.map((sticker) => (
183 <StickerRow
184 key={sticker.id}
185 sticker={sticker}
186 label={
187 STICKERS.find((asset) => asset.id === sticker.assetId)?.label ??
188 sticker.assetId
189 }
190 />
191 ))}
192 {texts.map((text) => (
193 <TextRow key={text.id} text={text} />
194 ))}
195 </ul>
196 </section>
197 )
198}
199