From b57ab67c84ef682de28218fac195cc9762876eae Mon Sep 17 00:00:00 2001 From: u8array Date: Thu, 7 May 2026 22:33:30 +0200 Subject: [PATCH 1/6] Refactor pagination control and add delete page functionality Replaces the '-' button with a trash icon to delete the current page. Adds a confirmation dialog before deletion. Also, adds tooltips to print speed and darkness settings for better usability. --- src/components/Canvas/PaginationControl.tsx | 28 +++++++++---------- src/components/Properties/PropertiesPanel.tsx | 22 ++++++++++++--- src/locales/ar.ts | 1 + src/locales/bg.ts | 1 + src/locales/cs.ts | 1 + src/locales/da.ts | 1 + src/locales/de.ts | 1 + src/locales/el.ts | 1 + src/locales/en.ts | 1 + src/locales/es.ts | 1 + src/locales/et.ts | 1 + src/locales/fa.ts | 1 + src/locales/fi.ts | 1 + src/locales/fr.ts | 1 + src/locales/he.ts | 1 + src/locales/hr.ts | 1 + src/locales/hu.ts | 1 + src/locales/it.ts | 1 + src/locales/ja.ts | 1 + src/locales/ko.ts | 1 + src/locales/lt.ts | 1 + src/locales/lv.ts | 1 + src/locales/nl.ts | 1 + src/locales/no.ts | 1 + src/locales/pl.ts | 1 + src/locales/pt.ts | 1 + src/locales/ro.ts | 1 + src/locales/sk.ts | 1 + src/locales/sl.ts | 1 + src/locales/sr.ts | 1 + src/locales/sv.ts | 1 + src/locales/tr.ts | 1 + src/locales/zh-hans.ts | 1 + src/locales/zh-hant.ts | 1 + 34 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/components/Canvas/PaginationControl.tsx b/src/components/Canvas/PaginationControl.tsx index 622bb625..650d3cde 100644 --- a/src/components/Canvas/PaginationControl.tsx +++ b/src/components/Canvas/PaginationControl.tsx @@ -1,18 +1,25 @@ +import { TrashIcon } from "@heroicons/react/16/solid"; import { useLabelStore } from "../../store/labelStore"; +import { useT } from "../../lib/useT"; export function PaginationControl() { + const t = useT(); const pageCount = useLabelStore((s) => s.pages.length); const currentPageIndex = useLabelStore((s) => s.currentPageIndex); const setCurrentPage = useLabelStore((s) => s.setCurrentPage); - const addPage = useLabelStore((s) => s.addPage); const removePage = useLabelStore((s) => s.removePage); - // Hide entirely on single-page documents; "Add page" lives in the File menu. + // Hide entirely on single-page documents; adding pages lives in the File menu. if (pageCount <= 1) return null; const canPrev = currentPageIndex > 0; const canNext = currentPageIndex < pageCount - 1; - const canRemove = pageCount > 1; + + const handleDelete = () => { + if (window.confirm(t.app.deletePageConfirm)) { + removePage(currentPageIndex); + } + }; return (
@@ -39,21 +46,12 @@ export function PaginationControl() {
-
); diff --git a/src/components/Properties/PropertiesPanel.tsx b/src/components/Properties/PropertiesPanel.tsx index 9dd9f4d1..e6ad0df1 100644 --- a/src/components/Properties/PropertiesPanel.tsx +++ b/src/components/Properties/PropertiesPanel.tsx @@ -360,7 +360,15 @@ function LabelConfigPanel({

{t.label.printerSettingsHeading}

- + -

{t.label.printSpeedHint}

- + -

{t.label.darknessHint}

diff --git a/src/locales/ar.ts b/src/locales/ar.ts index 7f1cbc9c..855993ff 100644 --- a/src/locales/ar.ts +++ b/src/locales/ar.ts @@ -93,6 +93,7 @@ const ar = { exportZpl: 'تصدير ZPL', newDesign: 'تصميم جديد', addPage: 'إضافة صفحة', + deletePageConfirm: 'حذف الصفحة الحالية؟ لا يمكن التراجع عن هذا الإجراء.', openDesign: 'فتح تصميم', saveDesign: 'حفظ تصميم', print: 'طباعة كصورة (المتصفح)', diff --git a/src/locales/bg.ts b/src/locales/bg.ts index c61e9c18..05b23ce2 100644 --- a/src/locales/bg.ts +++ b/src/locales/bg.ts @@ -93,6 +93,7 @@ const bg = { exportZpl: 'Export ZPL', newDesign: 'Нов дизайн', addPage: 'Добавяне на страница', + deletePageConfirm: 'Изтриване на текущата страница? Действието е необратимо.', openDesign: 'Отвори дизайн', saveDesign: 'Запази дизайн', print: 'Печат като изображение (браузър)', diff --git a/src/locales/cs.ts b/src/locales/cs.ts index a1dacb1f..489401f7 100644 --- a/src/locales/cs.ts +++ b/src/locales/cs.ts @@ -93,6 +93,7 @@ const cs = { exportZpl: 'Export ZPL', newDesign: 'Nový návrh', addPage: 'Přidat stránku', + deletePageConfirm: 'Smazat aktuální stránku? Tuto akci nelze vrátit zpět.', openDesign: 'Otevřít návrh', saveDesign: 'Uložit návrh', print: 'Tisk jako obrázek (prohlížeč)', diff --git a/src/locales/da.ts b/src/locales/da.ts index 2a171b9e..210f0cd6 100644 --- a/src/locales/da.ts +++ b/src/locales/da.ts @@ -93,6 +93,7 @@ const da = { exportZpl: 'Export ZPL', newDesign: 'Nyt design', addPage: 'Tilføj side', + deletePageConfirm: 'Slet den aktuelle side? Dette kan ikke fortrydes.', openDesign: 'Åbn design', saveDesign: 'Gem design', print: 'Udskriv som billede (browser)', diff --git a/src/locales/de.ts b/src/locales/de.ts index 951f389d..32332bdb 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -93,6 +93,7 @@ const de = { exportZpl: 'Export ZPL', newDesign: 'Neues Design', addPage: 'Seite hinzufügen', + deletePageConfirm: 'Aktuelle Seite löschen? Das kann nicht rückgängig gemacht werden.', openDesign: 'Design öffnen', saveDesign: 'Design speichern', print: 'Als Bild drucken (Browser)', diff --git a/src/locales/el.ts b/src/locales/el.ts index f6505c36..f842f635 100644 --- a/src/locales/el.ts +++ b/src/locales/el.ts @@ -93,6 +93,7 @@ const el = { exportZpl: 'Export ZPL', newDesign: 'Νέο σχέδιο', addPage: 'Προσθήκη σελίδας', + deletePageConfirm: 'Διαγραφή της τρέχουσας σελίδας; Αυτή η ενέργεια δεν αναιρείται.', openDesign: 'Άνοιγμα σχεδίου', saveDesign: 'Αποθήκευση σχεδίου', print: 'Εκτύπωση ως εικόνα (πρόγραμμα περιήγησης)', diff --git a/src/locales/en.ts b/src/locales/en.ts index 5f2d53f2..01a2ddee 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -93,6 +93,7 @@ const en = { exportZpl: 'Export ZPL', newDesign: 'New design', addPage: 'Add page', + deletePageConfirm: 'Delete the current page? This cannot be undone.', openDesign: 'Open design', saveDesign: 'Save design', print: 'Print as Image (browser)', diff --git a/src/locales/es.ts b/src/locales/es.ts index fc240236..0e251d94 100644 --- a/src/locales/es.ts +++ b/src/locales/es.ts @@ -93,6 +93,7 @@ const es = { exportZpl: 'Export ZPL', newDesign: 'Nuevo diseño', addPage: 'Añadir página', + deletePageConfirm: '¿Eliminar la página actual? Esta acción no se puede deshacer.', openDesign: 'Abrir diseño', saveDesign: 'Guardar diseño', print: 'Imprimir como imagen (navegador)', diff --git a/src/locales/et.ts b/src/locales/et.ts index fd118a7d..acbdb3a0 100644 --- a/src/locales/et.ts +++ b/src/locales/et.ts @@ -93,6 +93,7 @@ const et = { exportZpl: 'Export ZPL', newDesign: 'Uus kujundus', addPage: 'Lisa leht', + deletePageConfirm: 'Kustuta praegune leht? Seda toimingut ei saa tagasi võtta.', openDesign: 'Ava kujundus', saveDesign: 'Salvesta kujundus', print: 'Prindi pildina (brauser)', diff --git a/src/locales/fa.ts b/src/locales/fa.ts index 10aa70aa..085ad2f0 100644 --- a/src/locales/fa.ts +++ b/src/locales/fa.ts @@ -93,6 +93,7 @@ const fa = { exportZpl: 'خروجی ZPL', newDesign: 'طرح جدید', addPage: 'افزودن صفحه', + deletePageConfirm: 'صفحه فعلی حذف شود؟ این عمل قابل بازگشت نیست.', openDesign: 'باز کردن طرح', saveDesign: 'ذخیره طرح', print: 'چاپ به عنوان تصویر (مرورگر)', diff --git a/src/locales/fi.ts b/src/locales/fi.ts index d944d4ad..ac63ba10 100644 --- a/src/locales/fi.ts +++ b/src/locales/fi.ts @@ -93,6 +93,7 @@ const fi = { exportZpl: 'Export ZPL', newDesign: 'Uusi rakenne', addPage: 'Lisää sivu', + deletePageConfirm: 'Poistetaanko nykyinen sivu? Tätä ei voi kumota.', openDesign: 'Avaa rakenne', saveDesign: 'Tallenna rakenne', print: 'Tulosta kuvana (selain)', diff --git a/src/locales/fr.ts b/src/locales/fr.ts index 61069440..161fc4c2 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -93,6 +93,7 @@ const fr = { exportZpl: 'Export ZPL', newDesign: 'Nouveau design', addPage: 'Ajouter une page', + deletePageConfirm: 'Supprimer la page actuelle ? Cette action est irréversible.', openDesign: 'Ouvrir le design', saveDesign: 'Enregistrer le design', print: 'Imprimer en image (navigateur)', diff --git a/src/locales/he.ts b/src/locales/he.ts index 470a1a49..5fc80439 100644 --- a/src/locales/he.ts +++ b/src/locales/he.ts @@ -93,6 +93,7 @@ const he = { exportZpl: 'ייצוא ZPL', newDesign: 'עיצוב חדש', addPage: 'הוסף דף', + deletePageConfirm: 'למחוק את הדף הנוכחי? לא ניתן לבטל פעולה זו.', openDesign: 'פתח עיצוב', saveDesign: 'שמור עיצוב', print: 'הדפסה כתמונה (דפדפן)', diff --git a/src/locales/hr.ts b/src/locales/hr.ts index 7a3200e4..8e9aaa74 100644 --- a/src/locales/hr.ts +++ b/src/locales/hr.ts @@ -93,6 +93,7 @@ const hr = { exportZpl: 'Export ZPL', newDesign: 'Novi dizajn', addPage: 'Dodaj stranicu', + deletePageConfirm: 'Izbrisati trenutnu stranicu? Ovu radnju nije moguće poništiti.', openDesign: 'Otvori dizajn', saveDesign: 'Spremi dizajn', print: 'Ispis kao slika (preglednik)', diff --git a/src/locales/hu.ts b/src/locales/hu.ts index aa851d00..681f77d4 100644 --- a/src/locales/hu.ts +++ b/src/locales/hu.ts @@ -93,6 +93,7 @@ const hu = { exportZpl: 'Export ZPL', newDesign: 'Új terv', addPage: 'Oldal hozzáadása', + deletePageConfirm: 'Törli az aktuális oldalt? Ez nem vonható vissza.', openDesign: 'Terv megnyitása', saveDesign: 'Terv mentése', print: 'Nyomtatás képként (böngésző)', diff --git a/src/locales/it.ts b/src/locales/it.ts index b65b58f5..fd700380 100644 --- a/src/locales/it.ts +++ b/src/locales/it.ts @@ -93,6 +93,7 @@ const it = { exportZpl: 'Export ZPL', newDesign: 'Nuovo design', addPage: 'Aggiungi pagina', + deletePageConfirm: 'Eliminare la pagina corrente? Questa azione non può essere annullata.', openDesign: 'Apri design', saveDesign: 'Salva design', print: 'Stampa come immagine (browser)', diff --git a/src/locales/ja.ts b/src/locales/ja.ts index 272ab627..b1bba762 100644 --- a/src/locales/ja.ts +++ b/src/locales/ja.ts @@ -93,6 +93,7 @@ const ja = { exportZpl: 'ZPL エクスポート', newDesign: '新しいデザイン', addPage: 'ページを追加', + deletePageConfirm: '現在のページを削除しますか? この操作は元に戻せません。', openDesign: 'デザインを開く', saveDesign: 'デザインを保存', print: '画像として印刷(ブラウザ)', diff --git a/src/locales/ko.ts b/src/locales/ko.ts index f88fc60c..21b00664 100644 --- a/src/locales/ko.ts +++ b/src/locales/ko.ts @@ -93,6 +93,7 @@ const ko = { exportZpl: 'ZPL 내보내기', newDesign: '새 디자인', addPage: '페이지 추가', + deletePageConfirm: '현재 페이지를 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.', openDesign: '디자인 열기', saveDesign: '디자인 저장', print: '이미지로 인쇄 (브라우저)', diff --git a/src/locales/lt.ts b/src/locales/lt.ts index baad5248..8e712f1d 100644 --- a/src/locales/lt.ts +++ b/src/locales/lt.ts @@ -93,6 +93,7 @@ const lt = { exportZpl: 'Export ZPL', newDesign: 'Naujas dizainas', addPage: 'Pridėti puslapį', + deletePageConfirm: 'Ištrinti dabartinį puslapį? Šio veiksmo negalima atšaukti.', openDesign: 'Atidaryti dizainą', saveDesign: 'Išsaugoti dizainą', print: 'Spausdinti kaip vaizdą (naršyklė)', diff --git a/src/locales/lv.ts b/src/locales/lv.ts index 45b83679..ddffc30f 100644 --- a/src/locales/lv.ts +++ b/src/locales/lv.ts @@ -93,6 +93,7 @@ const lv = { exportZpl: 'Export ZPL', newDesign: 'Jauns dizains', addPage: 'Pievienot lapu', + deletePageConfirm: 'Dzēst pašreizējo lapu? Šo darbību nevar atsaukt.', openDesign: 'Atvērt dizainu', saveDesign: 'Saglabāt dizainu', print: 'Drukāt kā attēlu (pārlūks)', diff --git a/src/locales/nl.ts b/src/locales/nl.ts index bdbc9ee0..e4f769b2 100644 --- a/src/locales/nl.ts +++ b/src/locales/nl.ts @@ -93,6 +93,7 @@ const nl = { exportZpl: 'Export ZPL', newDesign: 'Nieuw ontwerp', addPage: 'Pagina toevoegen', + deletePageConfirm: 'Huidige pagina verwijderen? Dit kan niet ongedaan worden gemaakt.', openDesign: 'Ontwerp openen', saveDesign: 'Ontwerp opslaan', print: 'Afdrukken als afbeelding (browser)', diff --git a/src/locales/no.ts b/src/locales/no.ts index 6a5ae433..9b8a552f 100644 --- a/src/locales/no.ts +++ b/src/locales/no.ts @@ -93,6 +93,7 @@ const no = { exportZpl: 'Export ZPL', newDesign: 'Nytt design', addPage: 'Legg til side', + deletePageConfirm: 'Slette gjeldende side? Dette kan ikke angres.', openDesign: 'Åpne design', saveDesign: 'Lagre design', print: 'Skriv ut som bilde (nettleser)', diff --git a/src/locales/pl.ts b/src/locales/pl.ts index 935a7972..db4095d9 100644 --- a/src/locales/pl.ts +++ b/src/locales/pl.ts @@ -93,6 +93,7 @@ const pl = { exportZpl: 'Export ZPL', newDesign: 'Nowy projekt', addPage: 'Dodaj stronę', + deletePageConfirm: 'Usunąć bieżącą stronę? Tej operacji nie można cofnąć.', openDesign: 'Otwórz projekt', saveDesign: 'Zapisz projekt', print: 'Drukuj jako obraz (przeglądarka)', diff --git a/src/locales/pt.ts b/src/locales/pt.ts index 6c8ca50f..c519a0b0 100644 --- a/src/locales/pt.ts +++ b/src/locales/pt.ts @@ -93,6 +93,7 @@ const pt = { exportZpl: 'Export ZPL', newDesign: 'Novo design', addPage: 'Adicionar página', + deletePageConfirm: 'Eliminar a página atual? Esta ação não pode ser desfeita.', openDesign: 'Abrir design', saveDesign: 'Salvar design', print: 'Imprimir como imagem (navegador)', diff --git a/src/locales/ro.ts b/src/locales/ro.ts index 824624fd..b668fd8f 100644 --- a/src/locales/ro.ts +++ b/src/locales/ro.ts @@ -93,6 +93,7 @@ const ro = { exportZpl: 'Export ZPL', newDesign: 'Design nou', addPage: 'Adaugă pagină', + deletePageConfirm: 'Ștergeți pagina curentă? Această acțiune nu poate fi anulată.', openDesign: 'Deschide design', saveDesign: 'Salvează design', print: 'Tipărire ca imagine (browser)', diff --git a/src/locales/sk.ts b/src/locales/sk.ts index 32a047a0..e4d5132d 100644 --- a/src/locales/sk.ts +++ b/src/locales/sk.ts @@ -93,6 +93,7 @@ const sk = { exportZpl: 'Export ZPL', newDesign: 'Nový návrh', addPage: 'Pridať stránku', + deletePageConfirm: 'Odstrániť aktuálnu stránku? Túto akciu nie je možné vrátiť späť.', openDesign: 'Otvoriť návrh', saveDesign: 'Uložiť návrh', print: 'Tlač ako obrázok (prehliadač)', diff --git a/src/locales/sl.ts b/src/locales/sl.ts index 587dcd9c..a66d8988 100644 --- a/src/locales/sl.ts +++ b/src/locales/sl.ts @@ -93,6 +93,7 @@ const sl = { exportZpl: 'Export ZPL', newDesign: 'Nov dizajn', addPage: 'Dodaj stran', + deletePageConfirm: 'Izbrišem trenutno stran? Tega dejanja ni mogoče razveljaviti.', openDesign: 'Odpri dizajn', saveDesign: 'Shrani dizajn', print: 'Natisni kot sliko (brskalnik)', diff --git a/src/locales/sr.ts b/src/locales/sr.ts index 37a5a23f..3983610b 100644 --- a/src/locales/sr.ts +++ b/src/locales/sr.ts @@ -93,6 +93,7 @@ const sr = { exportZpl: 'Export ZPL', newDesign: 'Нови дизајн', addPage: 'Додај страницу', + deletePageConfirm: 'Обрисати тренутну страницу? Ова радња се не може опозвати.', openDesign: 'Отвори дизајн', saveDesign: 'Сачувај дизајн', print: 'Штампање као слика (прегледач)', diff --git a/src/locales/sv.ts b/src/locales/sv.ts index 2650af14..f63ec8c4 100644 --- a/src/locales/sv.ts +++ b/src/locales/sv.ts @@ -93,6 +93,7 @@ const sv = { exportZpl: 'Export ZPL', newDesign: 'Nytt design', addPage: 'Lägg till sida', + deletePageConfirm: 'Ta bort aktuell sida? Detta kan inte ångras.', openDesign: 'Öppna design', saveDesign: 'Spara design', print: 'Skriv ut som bild (webbläsare)', diff --git a/src/locales/tr.ts b/src/locales/tr.ts index 13d43ff7..3f3f3391 100644 --- a/src/locales/tr.ts +++ b/src/locales/tr.ts @@ -93,6 +93,7 @@ const tr = { exportZpl: 'ZPL Dışa Aktar', newDesign: 'Yeni Tasarım', addPage: 'Sayfa ekle', + deletePageConfirm: 'Geçerli sayfa silinsin mi? Bu işlem geri alınamaz.', openDesign: 'Tasarım Aç', saveDesign: 'Tasarım Kaydet', print: 'Görüntü olarak yazdır (tarayıcı)', diff --git a/src/locales/zh-hans.ts b/src/locales/zh-hans.ts index 37c1586e..28af47c8 100644 --- a/src/locales/zh-hans.ts +++ b/src/locales/zh-hans.ts @@ -93,6 +93,7 @@ const zhHans = { exportZpl: '导出 ZPL', newDesign: '新建设计', addPage: '添加页面', + deletePageConfirm: '删除当前页面?此操作无法撤消。', openDesign: '打开设计', saveDesign: '保存设计', print: '打印为图片(浏览器)', diff --git a/src/locales/zh-hant.ts b/src/locales/zh-hant.ts index 48fae74b..2296f3ca 100644 --- a/src/locales/zh-hant.ts +++ b/src/locales/zh-hant.ts @@ -93,6 +93,7 @@ const zhHant = { exportZpl: '匯出 ZPL', newDesign: '新增設計', addPage: '新增頁面', + deletePageConfirm: '刪除目前頁面?此操作無法復原。', openDesign: '開啟設計', saveDesign: '儲存設計', print: '列印為圖片(瀏覽器)', From d1f1a412c8778cd4d702576831a2f0ed7d4eac03 Mon Sep 17 00:00:00 2001 From: u8array Date: Thu, 7 May 2026 22:33:42 +0200 Subject: [PATCH 2/6] Refactor delete page confirmation dialog Introduce a reusable `ConfirmDialog` component and integrate it into the `PaginationControl` for page deletion. This change replaces the direct, irreversible deletion with a confirmation prompt, improving user experience and preventing accidental data loss. The confirmation dialog provides clearer messaging and separates the "delete" action from the "cancel" action. Locale keys have also been updated to reflect this change. --- src/components/Canvas/PaginationControl.tsx | 24 +++++-- src/components/ui/ConfirmDialog.tsx | 75 +++++++++++++++++++++ src/locales/ar.ts | 4 +- src/locales/bg.ts | 4 +- src/locales/cs.ts | 4 +- src/locales/da.ts | 4 +- src/locales/de.ts | 4 +- src/locales/el.ts | 4 +- src/locales/en.ts | 4 +- src/locales/es.ts | 4 +- src/locales/et.ts | 4 +- src/locales/fa.ts | 4 +- src/locales/fi.ts | 4 +- src/locales/fr.ts | 4 +- src/locales/he.ts | 4 +- src/locales/hr.ts | 4 +- src/locales/hu.ts | 4 +- src/locales/it.ts | 4 +- src/locales/ja.ts | 4 +- src/locales/ko.ts | 4 +- src/locales/lt.ts | 4 +- src/locales/lv.ts | 4 +- src/locales/nl.ts | 4 +- src/locales/no.ts | 4 +- src/locales/pl.ts | 4 +- src/locales/pt.ts | 4 +- src/locales/ro.ts | 4 +- src/locales/sk.ts | 4 +- src/locales/sl.ts | 4 +- src/locales/sr.ts | 4 +- src/locales/sv.ts | 4 +- src/locales/tr.ts | 4 +- src/locales/zh-hans.ts | 4 +- src/locales/zh-hant.ts | 4 +- 34 files changed, 188 insertions(+), 39 deletions(-) create mode 100644 src/components/ui/ConfirmDialog.tsx diff --git a/src/components/Canvas/PaginationControl.tsx b/src/components/Canvas/PaginationControl.tsx index 650d3cde..3d55a0b9 100644 --- a/src/components/Canvas/PaginationControl.tsx +++ b/src/components/Canvas/PaginationControl.tsx @@ -1,9 +1,12 @@ +import { useState } from "react"; import { TrashIcon } from "@heroicons/react/16/solid"; import { useLabelStore } from "../../store/labelStore"; import { useT } from "../../lib/useT"; +import { ConfirmDialog } from "../ui/ConfirmDialog"; export function PaginationControl() { const t = useT(); + const [confirmOpen, setConfirmOpen] = useState(false); const pageCount = useLabelStore((s) => s.pages.length); const currentPageIndex = useLabelStore((s) => s.currentPageIndex); const setCurrentPage = useLabelStore((s) => s.setCurrentPage); @@ -15,12 +18,6 @@ export function PaginationControl() { const canPrev = currentPageIndex > 0; const canNext = currentPageIndex < pageCount - 1; - const handleDelete = () => { - if (window.confirm(t.app.deletePageConfirm)) { - removePage(currentPageIndex); - } - }; - return (
+ {confirmOpen && ( + { + removePage(currentPageIndex); + setConfirmOpen(false); + }} + onCancel={() => setConfirmOpen(false)} + /> + )}
); } diff --git a/src/components/ui/ConfirmDialog.tsx b/src/components/ui/ConfirmDialog.tsx new file mode 100644 index 00000000..6737257e --- /dev/null +++ b/src/components/ui/ConfirmDialog.tsx @@ -0,0 +1,75 @@ +import { useEffect } from 'react'; +import { createPortal } from 'react-dom'; + +interface ConfirmDialogProps { + message: string; + confirmLabel: string; + cancelLabel: string; + /** Renders the confirm button in red. Use for irreversible operations. */ + destructive?: boolean; + onConfirm: () => void; + onCancel: () => void; +} + +/** + * Minimal confirm dialog matching the project's modal aesthetic. + * + * Mount it conditionally (`{open && }`); the parent owns + * visibility state. Backdrop click and Escape both fire `onCancel`. + */ +export function ConfirmDialog({ + message, + confirmLabel, + cancelLabel, + destructive, + onConfirm, + onCancel, +}: ConfirmDialogProps) { + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onCancel(); + }; + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + }, [onCancel]); + + const confirmCls = destructive + ? 'bg-red-500 text-white hover:bg-red-600' + : 'bg-accent text-bg hover:opacity-90'; + + // Portal so the fixed-position backdrop is anchored to the viewport even + // when an ancestor has a CSS transform (which would otherwise contain + // `position: fixed` and miscentre the modal). + return createPortal( +
+
e.stopPropagation()} + > +

{message}

+
+ + +
+
+
, + document.body, + ); +} diff --git a/src/locales/ar.ts b/src/locales/ar.ts index 855993ff..c45fdfcd 100644 --- a/src/locales/ar.ts +++ b/src/locales/ar.ts @@ -93,7 +93,9 @@ const ar = { exportZpl: 'تصدير ZPL', newDesign: 'تصميم جديد', addPage: 'إضافة صفحة', - deletePageConfirm: 'حذف الصفحة الحالية؟ لا يمكن التراجع عن هذا الإجراء.', + cancel: 'إلغاء', + deletePage: 'حذف الصفحة', + deletePageConfirm: 'حذف الصفحة الحالية؟', openDesign: 'فتح تصميم', saveDesign: 'حفظ تصميم', print: 'طباعة كصورة (المتصفح)', diff --git a/src/locales/bg.ts b/src/locales/bg.ts index 05b23ce2..c36b8d9c 100644 --- a/src/locales/bg.ts +++ b/src/locales/bg.ts @@ -93,7 +93,9 @@ const bg = { exportZpl: 'Export ZPL', newDesign: 'Нов дизайн', addPage: 'Добавяне на страница', - deletePageConfirm: 'Изтриване на текущата страница? Действието е необратимо.', + cancel: 'Отказ', + deletePage: 'Изтриване на страница', + deletePageConfirm: 'Изтриване на текущата страница?', openDesign: 'Отвори дизайн', saveDesign: 'Запази дизайн', print: 'Печат като изображение (браузър)', diff --git a/src/locales/cs.ts b/src/locales/cs.ts index 489401f7..d29dd9da 100644 --- a/src/locales/cs.ts +++ b/src/locales/cs.ts @@ -93,7 +93,9 @@ const cs = { exportZpl: 'Export ZPL', newDesign: 'Nový návrh', addPage: 'Přidat stránku', - deletePageConfirm: 'Smazat aktuální stránku? Tuto akci nelze vrátit zpět.', + cancel: 'Zrušit', + deletePage: 'Smazat stránku', + deletePageConfirm: 'Smazat aktuální stránku?', openDesign: 'Otevřít návrh', saveDesign: 'Uložit návrh', print: 'Tisk jako obrázek (prohlížeč)', diff --git a/src/locales/da.ts b/src/locales/da.ts index 210f0cd6..4728231e 100644 --- a/src/locales/da.ts +++ b/src/locales/da.ts @@ -93,7 +93,9 @@ const da = { exportZpl: 'Export ZPL', newDesign: 'Nyt design', addPage: 'Tilføj side', - deletePageConfirm: 'Slet den aktuelle side? Dette kan ikke fortrydes.', + cancel: 'Annuller', + deletePage: 'Slet side', + deletePageConfirm: 'Slet den aktuelle side?', openDesign: 'Åbn design', saveDesign: 'Gem design', print: 'Udskriv som billede (browser)', diff --git a/src/locales/de.ts b/src/locales/de.ts index 32332bdb..8b8e0ba1 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -93,7 +93,9 @@ const de = { exportZpl: 'Export ZPL', newDesign: 'Neues Design', addPage: 'Seite hinzufügen', - deletePageConfirm: 'Aktuelle Seite löschen? Das kann nicht rückgängig gemacht werden.', + cancel: 'Abbrechen', + deletePage: 'Seite löschen', + deletePageConfirm: 'Aktuelle Seite löschen?', openDesign: 'Design öffnen', saveDesign: 'Design speichern', print: 'Als Bild drucken (Browser)', diff --git a/src/locales/el.ts b/src/locales/el.ts index f842f635..f9cf2429 100644 --- a/src/locales/el.ts +++ b/src/locales/el.ts @@ -93,7 +93,9 @@ const el = { exportZpl: 'Export ZPL', newDesign: 'Νέο σχέδιο', addPage: 'Προσθήκη σελίδας', - deletePageConfirm: 'Διαγραφή της τρέχουσας σελίδας; Αυτή η ενέργεια δεν αναιρείται.', + cancel: 'Ακύρωση', + deletePage: 'Διαγραφή σελίδας', + deletePageConfirm: 'Διαγραφή της τρέχουσας σελίδας;', openDesign: 'Άνοιγμα σχεδίου', saveDesign: 'Αποθήκευση σχεδίου', print: 'Εκτύπωση ως εικόνα (πρόγραμμα περιήγησης)', diff --git a/src/locales/en.ts b/src/locales/en.ts index 01a2ddee..09e66a49 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -93,7 +93,9 @@ const en = { exportZpl: 'Export ZPL', newDesign: 'New design', addPage: 'Add page', - deletePageConfirm: 'Delete the current page? This cannot be undone.', + cancel: 'Cancel', + deletePage: 'Delete page', + deletePageConfirm: 'Delete the current page?', openDesign: 'Open design', saveDesign: 'Save design', print: 'Print as Image (browser)', diff --git a/src/locales/es.ts b/src/locales/es.ts index 0e251d94..1eae5205 100644 --- a/src/locales/es.ts +++ b/src/locales/es.ts @@ -93,7 +93,9 @@ const es = { exportZpl: 'Export ZPL', newDesign: 'Nuevo diseño', addPage: 'Añadir página', - deletePageConfirm: '¿Eliminar la página actual? Esta acción no se puede deshacer.', + cancel: 'Cancelar', + deletePage: 'Eliminar página', + deletePageConfirm: '¿Eliminar la página actual?', openDesign: 'Abrir diseño', saveDesign: 'Guardar diseño', print: 'Imprimir como imagen (navegador)', diff --git a/src/locales/et.ts b/src/locales/et.ts index acbdb3a0..f7c4692e 100644 --- a/src/locales/et.ts +++ b/src/locales/et.ts @@ -93,7 +93,9 @@ const et = { exportZpl: 'Export ZPL', newDesign: 'Uus kujundus', addPage: 'Lisa leht', - deletePageConfirm: 'Kustuta praegune leht? Seda toimingut ei saa tagasi võtta.', + cancel: 'Loobu', + deletePage: 'Kustuta leht', + deletePageConfirm: 'Kustuta praegune leht?', openDesign: 'Ava kujundus', saveDesign: 'Salvesta kujundus', print: 'Prindi pildina (brauser)', diff --git a/src/locales/fa.ts b/src/locales/fa.ts index 085ad2f0..f7e4912e 100644 --- a/src/locales/fa.ts +++ b/src/locales/fa.ts @@ -93,7 +93,9 @@ const fa = { exportZpl: 'خروجی ZPL', newDesign: 'طرح جدید', addPage: 'افزودن صفحه', - deletePageConfirm: 'صفحه فعلی حذف شود؟ این عمل قابل بازگشت نیست.', + cancel: 'لغو', + deletePage: 'حذف صفحه', + deletePageConfirm: 'صفحه فعلی حذف شود؟', openDesign: 'باز کردن طرح', saveDesign: 'ذخیره طرح', print: 'چاپ به عنوان تصویر (مرورگر)', diff --git a/src/locales/fi.ts b/src/locales/fi.ts index ac63ba10..55e3b8e3 100644 --- a/src/locales/fi.ts +++ b/src/locales/fi.ts @@ -93,7 +93,9 @@ const fi = { exportZpl: 'Export ZPL', newDesign: 'Uusi rakenne', addPage: 'Lisää sivu', - deletePageConfirm: 'Poistetaanko nykyinen sivu? Tätä ei voi kumota.', + cancel: 'Peruuta', + deletePage: 'Poista sivu', + deletePageConfirm: 'Poistetaanko nykyinen sivu?', openDesign: 'Avaa rakenne', saveDesign: 'Tallenna rakenne', print: 'Tulosta kuvana (selain)', diff --git a/src/locales/fr.ts b/src/locales/fr.ts index 161fc4c2..176f1723 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -93,7 +93,9 @@ const fr = { exportZpl: 'Export ZPL', newDesign: 'Nouveau design', addPage: 'Ajouter une page', - deletePageConfirm: 'Supprimer la page actuelle ? Cette action est irréversible.', + cancel: 'Annuler', + deletePage: 'Supprimer la page', + deletePageConfirm: 'Supprimer la page actuelle ?', openDesign: 'Ouvrir le design', saveDesign: 'Enregistrer le design', print: 'Imprimer en image (navigateur)', diff --git a/src/locales/he.ts b/src/locales/he.ts index 5fc80439..22cbbf1a 100644 --- a/src/locales/he.ts +++ b/src/locales/he.ts @@ -93,7 +93,9 @@ const he = { exportZpl: 'ייצוא ZPL', newDesign: 'עיצוב חדש', addPage: 'הוסף דף', - deletePageConfirm: 'למחוק את הדף הנוכחי? לא ניתן לבטל פעולה זו.', + cancel: 'ביטול', + deletePage: 'מחק דף', + deletePageConfirm: 'למחוק את הדף הנוכחי?', openDesign: 'פתח עיצוב', saveDesign: 'שמור עיצוב', print: 'הדפסה כתמונה (דפדפן)', diff --git a/src/locales/hr.ts b/src/locales/hr.ts index 8e9aaa74..289dba3d 100644 --- a/src/locales/hr.ts +++ b/src/locales/hr.ts @@ -93,7 +93,9 @@ const hr = { exportZpl: 'Export ZPL', newDesign: 'Novi dizajn', addPage: 'Dodaj stranicu', - deletePageConfirm: 'Izbrisati trenutnu stranicu? Ovu radnju nije moguće poništiti.', + cancel: 'Odustani', + deletePage: 'Izbriši stranicu', + deletePageConfirm: 'Izbrisati trenutnu stranicu?', openDesign: 'Otvori dizajn', saveDesign: 'Spremi dizajn', print: 'Ispis kao slika (preglednik)', diff --git a/src/locales/hu.ts b/src/locales/hu.ts index 681f77d4..888f9751 100644 --- a/src/locales/hu.ts +++ b/src/locales/hu.ts @@ -93,7 +93,9 @@ const hu = { exportZpl: 'Export ZPL', newDesign: 'Új terv', addPage: 'Oldal hozzáadása', - deletePageConfirm: 'Törli az aktuális oldalt? Ez nem vonható vissza.', + cancel: 'Mégse', + deletePage: 'Oldal törlése', + deletePageConfirm: 'Törli az aktuális oldalt?', openDesign: 'Terv megnyitása', saveDesign: 'Terv mentése', print: 'Nyomtatás képként (böngésző)', diff --git a/src/locales/it.ts b/src/locales/it.ts index fd700380..225cb483 100644 --- a/src/locales/it.ts +++ b/src/locales/it.ts @@ -93,7 +93,9 @@ const it = { exportZpl: 'Export ZPL', newDesign: 'Nuovo design', addPage: 'Aggiungi pagina', - deletePageConfirm: 'Eliminare la pagina corrente? Questa azione non può essere annullata.', + cancel: 'Annulla', + deletePage: 'Elimina pagina', + deletePageConfirm: 'Eliminare la pagina corrente?', openDesign: 'Apri design', saveDesign: 'Salva design', print: 'Stampa come immagine (browser)', diff --git a/src/locales/ja.ts b/src/locales/ja.ts index b1bba762..84403088 100644 --- a/src/locales/ja.ts +++ b/src/locales/ja.ts @@ -93,7 +93,9 @@ const ja = { exportZpl: 'ZPL エクスポート', newDesign: '新しいデザイン', addPage: 'ページを追加', - deletePageConfirm: '現在のページを削除しますか? この操作は元に戻せません。', + cancel: 'キャンセル', + deletePage: 'ページを削除', + deletePageConfirm: '現在のページを削除しますか?', openDesign: 'デザインを開く', saveDesign: 'デザインを保存', print: '画像として印刷(ブラウザ)', diff --git a/src/locales/ko.ts b/src/locales/ko.ts index 21b00664..96ac9f23 100644 --- a/src/locales/ko.ts +++ b/src/locales/ko.ts @@ -93,7 +93,9 @@ const ko = { exportZpl: 'ZPL 내보내기', newDesign: '새 디자인', addPage: '페이지 추가', - deletePageConfirm: '현재 페이지를 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.', + cancel: '취소', + deletePage: '페이지 삭제', + deletePageConfirm: '현재 페이지를 삭제하시겠습니까?', openDesign: '디자인 열기', saveDesign: '디자인 저장', print: '이미지로 인쇄 (브라우저)', diff --git a/src/locales/lt.ts b/src/locales/lt.ts index 8e712f1d..99d15da9 100644 --- a/src/locales/lt.ts +++ b/src/locales/lt.ts @@ -93,7 +93,9 @@ const lt = { exportZpl: 'Export ZPL', newDesign: 'Naujas dizainas', addPage: 'Pridėti puslapį', - deletePageConfirm: 'Ištrinti dabartinį puslapį? Šio veiksmo negalima atšaukti.', + cancel: 'Atšaukti', + deletePage: 'Ištrinti puslapį', + deletePageConfirm: 'Ištrinti dabartinį puslapį?', openDesign: 'Atidaryti dizainą', saveDesign: 'Išsaugoti dizainą', print: 'Spausdinti kaip vaizdą (naršyklė)', diff --git a/src/locales/lv.ts b/src/locales/lv.ts index ddffc30f..8e12a622 100644 --- a/src/locales/lv.ts +++ b/src/locales/lv.ts @@ -93,7 +93,9 @@ const lv = { exportZpl: 'Export ZPL', newDesign: 'Jauns dizains', addPage: 'Pievienot lapu', - deletePageConfirm: 'Dzēst pašreizējo lapu? Šo darbību nevar atsaukt.', + cancel: 'Atcelt', + deletePage: 'Dzēst lapu', + deletePageConfirm: 'Dzēst pašreizējo lapu?', openDesign: 'Atvērt dizainu', saveDesign: 'Saglabāt dizainu', print: 'Drukāt kā attēlu (pārlūks)', diff --git a/src/locales/nl.ts b/src/locales/nl.ts index e4f769b2..de972811 100644 --- a/src/locales/nl.ts +++ b/src/locales/nl.ts @@ -93,7 +93,9 @@ const nl = { exportZpl: 'Export ZPL', newDesign: 'Nieuw ontwerp', addPage: 'Pagina toevoegen', - deletePageConfirm: 'Huidige pagina verwijderen? Dit kan niet ongedaan worden gemaakt.', + cancel: 'Annuleren', + deletePage: 'Pagina verwijderen', + deletePageConfirm: 'Huidige pagina verwijderen?', openDesign: 'Ontwerp openen', saveDesign: 'Ontwerp opslaan', print: 'Afdrukken als afbeelding (browser)', diff --git a/src/locales/no.ts b/src/locales/no.ts index 9b8a552f..5269c37b 100644 --- a/src/locales/no.ts +++ b/src/locales/no.ts @@ -93,7 +93,9 @@ const no = { exportZpl: 'Export ZPL', newDesign: 'Nytt design', addPage: 'Legg til side', - deletePageConfirm: 'Slette gjeldende side? Dette kan ikke angres.', + cancel: 'Avbryt', + deletePage: 'Slett side', + deletePageConfirm: 'Slette gjeldende side?', openDesign: 'Åpne design', saveDesign: 'Lagre design', print: 'Skriv ut som bilde (nettleser)', diff --git a/src/locales/pl.ts b/src/locales/pl.ts index db4095d9..b49e71ee 100644 --- a/src/locales/pl.ts +++ b/src/locales/pl.ts @@ -93,7 +93,9 @@ const pl = { exportZpl: 'Export ZPL', newDesign: 'Nowy projekt', addPage: 'Dodaj stronę', - deletePageConfirm: 'Usunąć bieżącą stronę? Tej operacji nie można cofnąć.', + cancel: 'Anuluj', + deletePage: 'Usuń stronę', + deletePageConfirm: 'Usunąć bieżącą stronę?', openDesign: 'Otwórz projekt', saveDesign: 'Zapisz projekt', print: 'Drukuj jako obraz (przeglądarka)', diff --git a/src/locales/pt.ts b/src/locales/pt.ts index c519a0b0..9f7580fe 100644 --- a/src/locales/pt.ts +++ b/src/locales/pt.ts @@ -93,7 +93,9 @@ const pt = { exportZpl: 'Export ZPL', newDesign: 'Novo design', addPage: 'Adicionar página', - deletePageConfirm: 'Eliminar a página atual? Esta ação não pode ser desfeita.', + cancel: 'Cancelar', + deletePage: 'Eliminar página', + deletePageConfirm: 'Eliminar a página atual?', openDesign: 'Abrir design', saveDesign: 'Salvar design', print: 'Imprimir como imagem (navegador)', diff --git a/src/locales/ro.ts b/src/locales/ro.ts index b668fd8f..ba7821ae 100644 --- a/src/locales/ro.ts +++ b/src/locales/ro.ts @@ -93,7 +93,9 @@ const ro = { exportZpl: 'Export ZPL', newDesign: 'Design nou', addPage: 'Adaugă pagină', - deletePageConfirm: 'Ștergeți pagina curentă? Această acțiune nu poate fi anulată.', + cancel: 'Anulează', + deletePage: 'Șterge pagina', + deletePageConfirm: 'Ștergeți pagina curentă?', openDesign: 'Deschide design', saveDesign: 'Salvează design', print: 'Tipărire ca imagine (browser)', diff --git a/src/locales/sk.ts b/src/locales/sk.ts index e4d5132d..e35ca2ae 100644 --- a/src/locales/sk.ts +++ b/src/locales/sk.ts @@ -93,7 +93,9 @@ const sk = { exportZpl: 'Export ZPL', newDesign: 'Nový návrh', addPage: 'Pridať stránku', - deletePageConfirm: 'Odstrániť aktuálnu stránku? Túto akciu nie je možné vrátiť späť.', + cancel: 'Zrušiť', + deletePage: 'Odstrániť stránku', + deletePageConfirm: 'Odstrániť aktuálnu stránku?', openDesign: 'Otvoriť návrh', saveDesign: 'Uložiť návrh', print: 'Tlač ako obrázok (prehliadač)', diff --git a/src/locales/sl.ts b/src/locales/sl.ts index a66d8988..8b09cb4c 100644 --- a/src/locales/sl.ts +++ b/src/locales/sl.ts @@ -93,7 +93,9 @@ const sl = { exportZpl: 'Export ZPL', newDesign: 'Nov dizajn', addPage: 'Dodaj stran', - deletePageConfirm: 'Izbrišem trenutno stran? Tega dejanja ni mogoče razveljaviti.', + cancel: 'Prekliči', + deletePage: 'Izbriši stran', + deletePageConfirm: 'Izbrišem trenutno stran?', openDesign: 'Odpri dizajn', saveDesign: 'Shrani dizajn', print: 'Natisni kot sliko (brskalnik)', diff --git a/src/locales/sr.ts b/src/locales/sr.ts index 3983610b..9942f1b2 100644 --- a/src/locales/sr.ts +++ b/src/locales/sr.ts @@ -93,7 +93,9 @@ const sr = { exportZpl: 'Export ZPL', newDesign: 'Нови дизајн', addPage: 'Додај страницу', - deletePageConfirm: 'Обрисати тренутну страницу? Ова радња се не може опозвати.', + cancel: 'Откажи', + deletePage: 'Обриши страницу', + deletePageConfirm: 'Обрисати тренутну страницу?', openDesign: 'Отвори дизајн', saveDesign: 'Сачувај дизајн', print: 'Штампање као слика (прегледач)', diff --git a/src/locales/sv.ts b/src/locales/sv.ts index f63ec8c4..3e783c96 100644 --- a/src/locales/sv.ts +++ b/src/locales/sv.ts @@ -93,7 +93,9 @@ const sv = { exportZpl: 'Export ZPL', newDesign: 'Nytt design', addPage: 'Lägg till sida', - deletePageConfirm: 'Ta bort aktuell sida? Detta kan inte ångras.', + cancel: 'Avbryt', + deletePage: 'Ta bort sida', + deletePageConfirm: 'Ta bort aktuell sida?', openDesign: 'Öppna design', saveDesign: 'Spara design', print: 'Skriv ut som bild (webbläsare)', diff --git a/src/locales/tr.ts b/src/locales/tr.ts index 3f3f3391..9072dbbb 100644 --- a/src/locales/tr.ts +++ b/src/locales/tr.ts @@ -93,7 +93,9 @@ const tr = { exportZpl: 'ZPL Dışa Aktar', newDesign: 'Yeni Tasarım', addPage: 'Sayfa ekle', - deletePageConfirm: 'Geçerli sayfa silinsin mi? Bu işlem geri alınamaz.', + cancel: 'İptal', + deletePage: 'Sayfayı sil', + deletePageConfirm: 'Geçerli sayfa silinsin mi?', openDesign: 'Tasarım Aç', saveDesign: 'Tasarım Kaydet', print: 'Görüntü olarak yazdır (tarayıcı)', diff --git a/src/locales/zh-hans.ts b/src/locales/zh-hans.ts index 28af47c8..b0367986 100644 --- a/src/locales/zh-hans.ts +++ b/src/locales/zh-hans.ts @@ -93,7 +93,9 @@ const zhHans = { exportZpl: '导出 ZPL', newDesign: '新建设计', addPage: '添加页面', - deletePageConfirm: '删除当前页面?此操作无法撤消。', + cancel: '取消', + deletePage: '删除页面', + deletePageConfirm: '删除当前页面?', openDesign: '打开设计', saveDesign: '保存设计', print: '打印为图片(浏览器)', diff --git a/src/locales/zh-hant.ts b/src/locales/zh-hant.ts index 2296f3ca..c14ce5d1 100644 --- a/src/locales/zh-hant.ts +++ b/src/locales/zh-hant.ts @@ -93,7 +93,9 @@ const zhHant = { exportZpl: '匯出 ZPL', newDesign: '新增設計', addPage: '新增頁面', - deletePageConfirm: '刪除目前頁面?此操作無法復原。', + cancel: '取消', + deletePage: '刪除頁面', + deletePageConfirm: '刪除目前頁面?', openDesign: '開啟設計', saveDesign: '儲存設計', print: '列印為圖片(瀏覽器)', From c17884e09112192cc1032a9558a548800281685b Mon Sep 17 00:00:00 2001 From: u8array Date: Thu, 7 May 2026 22:42:42 +0200 Subject: [PATCH 3/6] feat(ui): collapsible palette sections with persisted state Each palette group (Text, 1D Linear, 2D Matrix, Postal & Legacy, Shapes) becomes an independently toggleable section with a chevron header. Open state is persisted per section id in localStorage so the UI is stable across reloads. Adds a generic CollapsibleSection primitive in components/ui/ that the palette consumes; the same component can later back disclosure-style sections elsewhere. --- src/components/Palette/ObjectPalette.tsx | 12 +++--- src/components/ui/CollapsibleSection.tsx | 52 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 src/components/ui/CollapsibleSection.tsx diff --git a/src/components/Palette/ObjectPalette.tsx b/src/components/Palette/ObjectPalette.tsx index 2544852d..90f9af78 100644 --- a/src/components/Palette/ObjectPalette.tsx +++ b/src/components/Palette/ObjectPalette.tsx @@ -6,6 +6,7 @@ import { useT } from '../../lib/useT'; import { useLabelStore } from '../../store/labelStore'; import { mmToDots } from '../../lib/coordinates'; import { DragHandleIcon } from '../ui/DragHandleIcon'; +import { CollapsibleSection } from '../ui/CollapsibleSection'; import type { PaletteDragData } from '../../dnd/types'; interface PaletteEntryProps { @@ -67,14 +68,15 @@ export function ObjectPalette() { ); if (entries.length === 0) return null; return ( -
-

- {t.palette[group.labelKey]} -

+ {entries.map(([type, def]) => ( ))} -
+ ); })}
diff --git a/src/components/ui/CollapsibleSection.tsx b/src/components/ui/CollapsibleSection.tsx new file mode 100644 index 00000000..cb0af3d9 --- /dev/null +++ b/src/components/ui/CollapsibleSection.tsx @@ -0,0 +1,52 @@ +import { useEffect, useState, type ReactNode } from 'react'; +import { ChevronDownIcon } from '@heroicons/react/16/solid'; + +interface CollapsibleSectionProps { + /** Stable identifier, used as the localStorage key for the open state. */ + id: string; + title: ReactNode; + defaultOpen?: boolean; + children: ReactNode; +} + +const LS_PREFIX = 'zpl:section:'; + +/** + * Section with a clickable header that toggles its body. Independent of + * sibling sections — multiple can be open at once. Open state is persisted + * per `id` in localStorage so the UI feels stable across reloads. + */ +export function CollapsibleSection({ + id, + title, + defaultOpen = true, + children, +}: CollapsibleSectionProps) { + const [open, setOpen] = useState(() => { + const saved = localStorage.getItem(LS_PREFIX + id); + return saved === null ? defaultOpen : saved === '1'; + }); + + useEffect(() => { + localStorage.setItem(LS_PREFIX + id, open ? '1' : '0'); + }, [id, open]); + + return ( +
+ + {open &&
{children}
} +
+ ); +} From b5a22bdccde3a75f2a15f2dc1351b362e190b33b Mon Sep 17 00:00:00 2001 From: u8array Date: Thu, 7 May 2026 22:52:29 +0200 Subject: [PATCH 4/6] fix(ui): address gemini review and unify hint icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ConfirmDialog: add role=alertdialog/aria-modal/aria-describedby for screen-reader support, and lock body scroll while open so the modal stays anchored. CollapsibleSection: re-sync open state when id prop changes using React's prev-vs-current setState-during-render pattern (avoids the react-hooks/set-state-in-effect lint rule), and link the toggle button to the content via aria-controls. PropertiesPanel: replace the bare ⓘ glyph with heroicons InformationCircleIcon for consistent rendering across systems. LabelConfigPanel: wrap the printer-settings group in a CollapsibleSection (default-collapsed) so the optional fields read as a real section instead of plain text under a divider. --- src/components/Properties/PropertiesPanel.tsx | 28 ++++++++--------- src/components/ui/CollapsibleSection.tsx | 30 +++++++++++++++---- src/components/ui/ConfirmDialog.tsx | 19 ++++++++++-- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/src/components/Properties/PropertiesPanel.tsx b/src/components/Properties/PropertiesPanel.tsx index e6ad0df1..4388f9d5 100644 --- a/src/components/Properties/PropertiesPanel.tsx +++ b/src/components/Properties/PropertiesPanel.tsx @@ -1,3 +1,4 @@ +import { InformationCircleIcon } from "@heroicons/react/16/solid"; import { useLabelStore, useCurrentObjects } from "../../store/labelStore"; import { ObjectRegistry } from "../../registry"; import { stripZplCommandChars } from "../../registry/zplHelpers"; @@ -12,6 +13,7 @@ import { import type { Unit } from "../../lib/units"; import { useT } from "../../lib/useT"; import { parseIntOrUndef } from "../../lib/inputParse"; +import { CollapsibleSection } from "../ui/CollapsibleSection"; import { inputCls, labelCls } from "./styles"; import type { LabelConfig } from "../../types/ObjectType"; @@ -355,19 +357,18 @@ function LabelConfigPanel({ />
-
- -

{t.label.printerSettingsHeading}

- +
+ ); diff --git a/src/components/ui/CollapsibleSection.tsx b/src/components/ui/CollapsibleSection.tsx index cb0af3d9..001d3d81 100644 --- a/src/components/ui/CollapsibleSection.tsx +++ b/src/components/ui/CollapsibleSection.tsx @@ -11,6 +11,11 @@ interface CollapsibleSectionProps { const LS_PREFIX = 'zpl:section:'; +function readStored(id: string, fallback: boolean): boolean { + const saved = localStorage.getItem(LS_PREFIX + id); + return saved === null ? fallback : saved === '1'; +} + /** * Section with a clickable header that toggles its body. Independent of * sibling sections — multiple can be open at once. Open state is persisted @@ -22,21 +27,32 @@ export function CollapsibleSection({ defaultOpen = true, children, }: CollapsibleSectionProps) { - const [open, setOpen] = useState(() => { - const saved = localStorage.getItem(LS_PREFIX + id); - return saved === null ? defaultOpen : saved === '1'; - }); + const [open, setOpen] = useState(() => readStored(id, defaultOpen)); + + // Re-sync open state when `id` changes so the component can be reused for + // a different section without leaking the previous open state into the new + // id's storage slot. React's blessed pattern for deriving state from + // props: setState during render under a prev-vs-current guard. + // https://react.dev/reference/react/useState#storing-information-from-previous-renders + const [prevId, setPrevId] = useState(id); + if (prevId !== id) { + setPrevId(id); + setOpen(readStored(id, defaultOpen)); + } useEffect(() => { localStorage.setItem(LS_PREFIX + id, open ? '1' : '0'); }, [id, open]); + const contentId = `section-content-${id}`; + return (
- {open &&
{children}
} + {open && ( +
+ {children} +
+ )}
); } diff --git a/src/components/ui/ConfirmDialog.tsx b/src/components/ui/ConfirmDialog.tsx index 6737257e..94e78aae 100644 --- a/src/components/ui/ConfirmDialog.tsx +++ b/src/components/ui/ConfirmDialog.tsx @@ -30,7 +30,14 @@ export function ConfirmDialog({ if (e.key === 'Escape') onCancel(); }; window.addEventListener('keydown', onKey); - return () => window.removeEventListener('keydown', onKey); + // Lock background scroll while the modal is open so the dialog stays + // visually anchored and the user cannot drift past it. + const originalOverflow = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + return () => { + window.removeEventListener('keydown', onKey); + document.body.style.overflow = originalOverflow; + }; }, [onCancel]); const confirmCls = destructive @@ -46,10 +53,18 @@ export function ConfirmDialog({ onClick={onCancel} >
e.stopPropagation()} > -

{message}

+

+ {message} +

-
- +