genesis+worker: design system Genesis ทั้งชุด + patch pdf.worker แก้ fake-space ไทย + ปิด review design/responsive/quality รอบ 2026-09-24 — (1) Genesis: ฟอนต์ General Sans self-host + DM Sans + Sarabun, AppNav 56px backdrop-blur แทน SessionBar, DotGrid หน้า login, palette/รัศมี/เงา/focus ตาม docs/DESIGN.md ครบทุกหน้า (2) postinstall scripts/patch-pdf-worker.mjs ถอด if (scaledDim) รอบ prevTransform ให้อัปเดตทุก glyph — กัน addFakeSpaces แทรก space กลางคำไทยใน PDF ที่ justify ด้วย letter-spacing + test กัน regression (tests/scripts/ + fixture thai_letterspacing.pdf, invariant #6) (3) ฟอร์มอัปโหลด + ช่องกลุ่ม typeId ทางเลือก — normDocumentGroup ที่ utils/external-file.ts เป็น single source ร่วมกับ N8 (4) token fixes: focus ring ปุ่ม pin + แถว bookmark ตามหน้า, ปุ่ม action ใน popup/composer/dialog เป็น rounded-md ทั้งคู่, selection preview เป็น tint ของ primary แทน blue-500 ดิบ, default สี shape/highlight รวมที่ utils/annotation-display, loading animate-pulse + จดข้อยกเว้น (เงา pin, font weights ของ viewer) ลง DESIGN.md (5) responsive: Export PDF max-lg:order-first ให้เห็นบน iPad, แถวบน header แถวเดียวเลื่อนได้บนจอแคบ, re-fit ความกว้างตอน toggle sidebar เฉพาะเมื่อยังไม่ซูมเอง, pin popover flip ขึ้นบนเมื่อใกล้ท้ายหน้า, toast hint ตอนสลับ tool กลางเอกสาร, ป้าย "ชนิด · ผู้ทำ" ของรายการผู้อื่นสำหรับจอสัมผัส (6) คุณภาพ: @layer ครอบ @media กันกฎ touch รั่ยไป desktop (Tailwind v3 ยก @layer ออกจาก media ทิ้ง wrapper — ยืนยันด้วย build จริง + วัดบนจอ), กฎ M5 ครอบ contenteditable กัน Safari zoom ตอนแก้ไข TEXTBOX บน iPad, COMMENT ใช้ box มาตราฐานเดียวตาม pin px จริง (render/scroll target/move-clamp/bookmark), ตัดหัวการ์ดซ้ำกับ h2 หน้ารายการ — พร้อมไฟล์ untracked ที่ postinstall/โค้ดอ้างถึงครบชุด (app/fonts/, AppNav, DotGrid, tests/) กัน fresh clone fail ที่ npm install (คำขอผู้ใช้ 2026-09-24)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-09-24 11:46:02 +07:00
parent e254b935ce
commit c5c6b65bd2
42 changed files with 1429 additions and 745 deletions

View file

@ -14,7 +14,7 @@ import { colorName } from "@/utils/annotation-display";
* PDFViewer/AnnotationSidebar (ประกาศแยกที่นี่เพราะ PDFViewer import ไฟล์นี้
* อยู่แล้ว ห้ามวนกลับ) */
const FOCUS_RING_CLS =
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1";
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary";
/** สี preset ให้เลือก (ทุก tool ที่ปรับสีได้: draw/shape/highlight/textbox/stamp)
* สีอื่นนอกรายการเลือกได้ผ่านปุ่มสีรุ้ง (input type="color") ท้ายแถว */
@ -38,7 +38,7 @@ export function PanelRow({ label, children }: { label?: string; children: ReactN
return (
<div className="panel-row flex items-start gap-2 px-1 py-1">
{label ? (
<span className="w-11 shrink-0 pt-1 text-[11px] font-medium text-slate-500">{label}</span>
<span className="w-11 shrink-0 pt-1 text-[11px] font-medium text-ink-soft">{label}</span>
) : null}
{children}
</div>
@ -57,7 +57,7 @@ export function ColorSwatches({ value, onChange }: { value: string; onChange: (c
type="button"
aria-label={`เลือกสี ${colorName(c)}`}
title={colorName(c)}
className={`aspect-square max-w-8 w-full justify-self-center rounded-full border border-slate-300 transition ${FOCUS_RING_CLS} ${
className={`aspect-square max-w-8 w-full justify-self-center rounded-full border border-line transition ${FOCUS_RING_CLS} ${
value === c ? "ring-2 ring-primary ring-offset-1" : "hover:scale-110"
}`}
style={{ backgroundColor: c }}
@ -66,7 +66,7 @@ export function ColorSwatches({ value, onChange }: { value: string; onChange: (c
))}
<label
title="เลือกสีอื่น"
className={`relative aspect-square max-w-8 w-full cursor-pointer justify-self-center rounded-full border border-slate-300 transition has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-primary has-[:focus-visible]:ring-offset-1 ${
className={`relative aspect-square max-w-8 w-full cursor-pointer justify-self-center rounded-full border border-line transition has-[:focus-visible]:outline has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-primary ${
INK_COLORS.includes(value) ? "hover:scale-110" : "ring-2 ring-primary ring-offset-1"
}`}
style={{
@ -179,7 +179,7 @@ export function NumberCombo({
}
}}
>
<div className="flex items-stretch overflow-hidden rounded-md border border-slate-300 bg-white">
<div className="flex items-stretch overflow-hidden rounded-md border border-line bg-white">
<input
type="number"
value={draft}
@ -196,16 +196,16 @@ export function NumberCombo({
if (e.key !== "Escape") e.stopPropagation(); // พิมพ์ในช่องต้องไม่ชนคีย์ลัด F3 (Esc ปล่อยไหลไปปิดเมนู)
if (e.key === "Enter") commitDraft(); // ยืนยันครั้งเดียวตรงนี้ ไม่ต้องรอ blur
}}
className="min-w-0 flex-1 border-0 px-1.5 py-1.5 text-[11px] text-slate-700 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-slate-400 [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
className="min-w-0 flex-1 border-0 px-1.5 py-1.5 text-[11px] text-ink focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
/>
<div className="flex flex-col border-l border-slate-300">
<div className="flex flex-col border-l border-line">
{/* combo-step: ปุ่ม ▲▼ ซ้อนแนวตั้ง 2 ชั้น — ต้องหลุดจาก rule touch 36px
ของ dropdown (globals.css) ไม่งั้นคอลัมน์สูง 72px ดึงทั้งแถวบวมตาม */}
<button
type="button"
aria-label="เพิ่มค่า"
onMouseDown={(e) => e.preventDefault()} // ไม่แย่ง focus จาก input (dropdown ไม่กระพริบ)
className={`combo-step flex flex-1 items-center justify-center rounded-sm px-1.5 text-slate-500 hover:bg-slate-100 ${FOCUS_RING_CLS}`}
className={`combo-step flex flex-1 items-center justify-center rounded-sm px-1.5 text-ink-soft hover:bg-ink/[0.05] ${FOCUS_RING_CLS}`}
onClick={() => stepValue(1)}
>
<ChevronUp className="h-3 w-3" />
@ -214,7 +214,7 @@ export function NumberCombo({
type="button"
aria-label="ลดค่า"
onMouseDown={(e) => e.preventDefault()}
className={`combo-step flex flex-1 items-center justify-center rounded-sm border-t border-slate-300 px-1.5 text-slate-500 hover:bg-slate-100 ${FOCUS_RING_CLS}`}
className={`combo-step flex flex-1 items-center justify-center rounded-sm border-t border-line px-1.5 text-ink-soft hover:bg-ink/[0.05] ${FOCUS_RING_CLS}`}
onClick={() => stepValue(-1)}
>
<ChevronDown className="h-3 w-3" />
@ -227,7 +227,7 @@ export function NumberCombo({
// L2 (UX audit 2026-09-16): เปิด "ขึ้นบน" (bottom-full) เสมอ — แถวท้ายเมนู
// tool (ความทึบ/ความหนา) เปิดลงจะล้นเขต scroll ของเมนู ต้องเลื่อนเมนูจึงเห็น
// ครบ / ทุกจุดใช้งานมีแถวอื่นอยู่ข้างบนเสมอ จึงไม่หลุดขอบบนของเมนูเช่นกัน
className="absolute bottom-full right-0 z-[60] mb-1 w-full overflow-hidden rounded-md border border-slate-200 bg-white py-0.5 shadow-lg"
className="absolute bottom-full right-0 z-[60] mb-1 w-full overflow-hidden rounded-md border border-line bg-white py-0.5 shadow-lg"
>
{options.map((opt) => (
<button
@ -238,8 +238,8 @@ export function NumberCombo({
onMouseDown={(e) => e.preventDefault()} // ไม่แย่ง focus จาก input ก่อนคลิก
className={`flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs font-medium transition ${FOCUS_RING_CLS} ${
value === opt
? "bg-slate-100 text-slate-900"
: "text-slate-600 hover:bg-slate-100"
? "bg-primary/10 text-primary"
: "text-ink-soft hover:bg-ink/[0.05]"
}`}
onClick={() => {
onChange(opt);