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());
}, []);
// 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)
useEffect(() => {
let cancelled = false;
@ -1374,6 +1369,21 @@ export default function PDFViewer({
}
}, [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(() => {
setDraft("");
}, [composer]);
@ -2352,22 +2362,23 @@ export default function PDFViewer({
{TEXT_SELECTION_TOOLS.has(tool) && (
<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 */}
{sidebarOpen && (
<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
onClick={() => setSidebarOpen(false)}
/>
)}
{/* xl+ = sticky แยกคอลัมน์เหมือนเดิม / < xl = overlay เลื่อนเข้า-ออก
(ปิดบน desktop ใช้ xl:hidden ออกจาก flow → main เต็มจอ) */}
{/* lg+ = sticky แยกคอลัมน์ (เปิดแล้ว content ขยับไปข้าง ๆ ไม่บังกัน)
/ < lg = overlay เลื่อนเข้า-ออก กว้างเป็นสัดส่วนของจอกว้างกว่า 288px
(ปิดบน desktop ใช้ lg:hidden ออกจาก flow → main เต็มจอ) */}
<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
? "max-xl:translate-x-0"
: "max-xl:pointer-events-none max-xl:-translate-x-full xl:hidden"
? "max-lg:translate-x-0"
: "max-lg:pointer-events-none max-lg:-translate-x-full lg:hidden"
}`}
>
<AnnotationSidebar
@ -3040,6 +3051,7 @@ export default function PDFViewer({
</header>
<main
ref={mainRef}
className="space-y-4 px-4 py-4"
style={{
cursor:

View file

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