sidebar จอกลางเป็นคอลัมน์ + bookmark row hover แยกแถว + PDF fit จอเล็ก
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 39s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 13s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s

- overlay sidebar ลด breakpoint จาก xl (1280) เป็น lg (1024): จอ 1024-1280
  เปิดแผงข้างแล้ว content ขยับไปข้าง ๆ ไม่ลอยทับบัง PDF
- overlay บนจอ < lg กว้างเป็นสัดส่วนของจอ (75vw, cap 288px) กันบังเกือบเต็มมือถือ
- ลบการปิด sidebar อัตโนมัติตอน mount บนจอเล็ก (ต้องกดปิดเองเท่านั้น)
- PDF fit ความกว้างอัตโนมัติครั้งเดียวตอนโหลดเมื่อพื้นที่แคบกว่าหน้ากระดาษ
- รายการ bookmark: ย้ายพื้นหลัง hover จาก li (ครอบ <ul> ลูกทั้งก้อน ชี้ลูกแล้ว
  :hover ของแม่ทำงานด้วย ไฮไลต์ทั้ง subtree) ลง div แถวรายการ ให้ไฮไลต์เฉพาะแถว

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 2026-09-11 18:05:38 +07:00
parent b588343d50
commit 1c2f4c834a
2 changed files with 30 additions and 20 deletions

View file

@ -1234,11 +1234,6 @@ export default function PDFViewer({
setUser(getLocalUser()); setUser(getLocalUser());
}, []); }, []);
// UX review 2026-09-09: จอแคบกว่า xl (< 1280px) sidebar เป็น overlay — ปิดอัตโนมัติตอน mount
useEffect(() => {
if (!window.matchMedia("(min-width: 1280px)").matches) setSidebarOpen(false);
}, []);
// โหลดเอกสาร: dynamic import ภายใน useEffect เท่านั้น (invariant #6) // โหลดเอกสาร: dynamic import ภายใน useEffect เท่านั้น (invariant #6)
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
@ -1374,6 +1369,21 @@ export default function PDFViewer({
} }
}, [pageSizes, annotations]); }, [pageSizes, annotations]);
// จอเล็ก: หน้ากระดาษ (scale 1) กว้างกว่าพื้นที่แสดงผล → ย่อให้พอดีความกว้าง
// ครั้งเดียวตอนโหลดเท่านั้น (ผู้ใช้ซูมเองหลังจากนี้แล้วไม่ถูกแทนที่) —
// อิงความกว้างหน้าแรกเหมือน basePageWidth และตัด padding px-4 ซ้าย-ขวาของ main ออก
const mainRef = useRef<HTMLElement>(null);
const fitWidthDoneRef = useRef(false);
useEffect(() => {
if (pageSizes.length === 0 || fitWidthDoneRef.current) return;
fitWidthDoneRef.current = true;
const avail = (mainRef.current?.clientWidth ?? 0) - 32;
const pageW = pageSizes[0].width;
if (avail > 0 && pageW > avail) {
setScale(clamp(Math.floor((avail / pageW) * 100) / 100, 0.5, 1));
}
}, [pageSizes]);
useEffect(() => { useEffect(() => {
setDraft(""); setDraft("");
}, [composer]); }, [composer]);
@ -2352,22 +2362,23 @@ export default function PDFViewer({
{TEXT_SELECTION_TOOLS.has(tool) && ( {TEXT_SELECTION_TOOLS.has(tool) && (
<style>{`.textLayer span::selection, .textLayer br::selection { background: transparent; }`}</style> <style>{`.textLayer span::selection, .textLayer br::selection { background: transparent; }`}</style>
)} )}
{/* UX review 2026-09-09: backdrop กันคลิกทะลุใต้ sidebar overlay (เฉพาะ < xl) {/* UX review 2026-09-09: backdrop กันคลิกทะลุใต้ sidebar overlay (เฉพาะ < lg)
z-[45] อยู่เหนือ header sticky z-40 แต่ใต้ aside z-50 / composer z-50 */} z-[45] อยู่เหนือ header sticky z-40 แต่ใต้ aside z-50 / composer z-50 */}
{sidebarOpen && ( {sidebarOpen && (
<div <div
className="fixed inset-0 z-[45] bg-slate-900/30 xl:hidden" className="fixed inset-0 z-[45] bg-slate-900/30 lg:hidden"
aria-hidden aria-hidden
onClick={() => setSidebarOpen(false)} onClick={() => setSidebarOpen(false)}
/> />
)} )}
{/* xl+ = sticky แยกคอลัมน์เหมือนเดิม / < xl = overlay เลื่อนเข้า-ออก {/* lg+ = sticky แยกคอลัมน์ (เปิดแล้ว content ขยับไปข้าง ๆ ไม่บังกัน)
(ปิดบน desktop ใช้ xl:hidden ออกจาก flow → main เต็มจอ) */} / < lg = overlay เลื่อนเข้า-ออก กว้างเป็นสัดส่วนของจอกว้างกว่า 288px
(ปิดบน desktop ใช้ lg:hidden ออกจาก flow → main เต็มจอ) */}
<aside <aside
className={`sticky top-0 h-screen w-72 shrink-0 border-r border-slate-200 bg-white max-xl:fixed max-xl:left-0 max-xl:z-50 max-xl:transition-transform max-xl:duration-200 ${ className={`sticky top-0 h-screen w-72 shrink-0 border-r border-slate-200 bg-white max-lg:fixed max-lg:left-0 max-lg:z-50 max-lg:w-[75vw] max-lg:max-w-72 max-lg:transition-transform max-lg:duration-200 ${
sidebarOpen sidebarOpen
? "max-xl:translate-x-0" ? "max-lg:translate-x-0"
: "max-xl:pointer-events-none max-xl:-translate-x-full xl:hidden" : "max-lg:pointer-events-none max-lg:-translate-x-full lg:hidden"
}`} }`}
> >
<AnnotationSidebar <AnnotationSidebar
@ -3040,6 +3051,7 @@ export default function PDFViewer({
</header> </header>
<main <main
ref={mainRef}
className="space-y-4 px-4 py-4" className="space-y-4 px-4 py-4"
style={{ style={{
cursor: cursor:

View file

@ -327,15 +327,13 @@ export default function AnnotationSidebar({
3, 3,
) )
: []; : [];
// พื้นหลัง hover อยู่ที่ div แถวรายการ ไม่ใช่ li — li ครอบ <ul> ลูกทั้งก้อน
// ทำให้ :hover ของแม่ทำงานด้วยเมื่อชี้รายการลูก (ไฮไลต์ทั้ง subtree เป็นก้อนเดียว)
const pad = depth === 1 ? "px-1" : `pr-1 ${depth === 2 ? "pl-5" : "pl-9"}`;
return ( return (
<li <li key={b.id}>
key={b.id}
className={`group rounded hover:bg-slate-100 ${
depth === 1 ? "px-1 py-0.5" : `py-0.5 pr-1 ${depth === 2 ? "pl-5" : "pl-9"}`
}`}
>
{eb ? ( {eb ? (
<div className="py-1"> <div className={`rounded py-1 ${pad}`}>
<input <input
autoFocus autoFocus
value={eb.title} value={eb.title}
@ -394,7 +392,7 @@ export default function AnnotationSidebar({
</div> </div>
</div> </div>
) : ( ) : (
<div className="flex items-center gap-1.5 py-1"> <div className={`group flex items-center gap-1.5 rounded py-1 hover:bg-slate-100 ${pad}`}>
{hasChildren ? ( {hasChildren ? (
<button <button
type="button" type="button"