mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
94f8d45daa
* fix(guide-viewer): readable on phones and tablets, desktop untouched Every change is behind a breakpoint; 1440px and 1024px renders of the same guide are byte-identical before and after (screenshot MD5s match). - Split diffs below lg (1024px) are forced unified in the portable viewer's diff renderer (matchMedia; the setting is untouched, so a wider window gets split back). A phone has ~350px of pane and a portrait tablet ~430px, so two columns were under 220px each. - Padding scales: page px-3/sm:px-6/lg:px-10, chapter column px-4/md:px-6, diff column px-1.5/md:px-4. Code pane on a 390px phone: 276px → 352px. - Tablets: the chapter column is proportional (minmax(260px,36%)) from md and the fixed 440px only from lg. Pane at 768px: 214px → 426px. - Header actions (Download, theme) sit in a right-aligned row above the title below md instead of floating into it. Viewer rebuilt and published (viewer.dWt7KCum.js), manifest synced. * fix(guide-viewer): touch targets, labels, and no tap delay on coarse pointers Only under `pointer: coarse` (Tailwind's `pointer-coarse:` variant), so mouse layouts are unchanged: - Reviewed checkbox and the collapse chevron get an invisible ::before hit area (visual 15–17px, hit ≥ 44px); the "Reviewed" text button and file chips get taller padding; the theme toggle and hosted Download button grow to a 44px hit box. - `touch-action: manipulation` on controls in the portable viewer and the landing page (no double-tap-to-zoom delay; the page still pinch-zooms). - `aria-label` on the two icon-only buttons (theme toggle, collapse chevron). - Landing page: the GitHub link and the Copy button are 44px tall on touch. Tailwind v4 already gates `hover:` behind `@media (hover: hover)`, so no false hover states on tap. Viewer rebuilt and published, manifest synced. * guide-viewer: manifest for the combined build (labels + mobile), viewer.sFtOnb1i.js published
71 lines
2.4 KiB
TypeScript
71 lines
2.4 KiB
TypeScript
import React, { useState, useRef, useEffect } from 'react';
|
|
import { useTheme } from './ThemeProvider';
|
|
import { THEME_MODES } from './themeModes';
|
|
|
|
export function ModeToggle() {
|
|
const { theme, setTheme } = useTheme();
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (event: PointerEvent) => {
|
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener('pointerdown', handleClickOutside);
|
|
return () => document.removeEventListener('pointerdown', handleClickOutside);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="relative" ref={dropdownRef}>
|
|
<button
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
className="p-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors relative pointer-coarse:before:absolute pointer-coarse:before:-inset-2 pointer-coarse:before:content-['']"
|
|
title="Toggle theme"
|
|
aria-label="Toggle theme"
|
|
>
|
|
{/* Sun */}
|
|
<svg
|
|
className="w-4 h-4 hidden light:block"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
strokeWidth={2}
|
|
>
|
|
<circle cx="12" cy="12" r="4" />
|
|
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" />
|
|
</svg>
|
|
{/* Moon */}
|
|
<svg
|
|
className="w-4 h-4 light:hidden"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
strokeWidth={2}
|
|
>
|
|
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
|
|
</svg>
|
|
</button>
|
|
|
|
{isOpen && (
|
|
<div className="absolute right-0 mt-1 w-32 rounded-lg border border-border bg-popover shadow-xl z-50 overflow-hidden py-1">
|
|
{THEME_MODES.map(({ id, label }) => (
|
|
<button
|
|
key={id}
|
|
onClick={() => { setTheme(id); setIsOpen(false); }}
|
|
className={`w-full px-3 py-1.5 text-left text-xs transition-colors ${
|
|
theme === id
|
|
? 'text-primary bg-primary/10 font-medium'
|
|
: 'text-popover-foreground hover:bg-muted'
|
|
}`}
|
|
>
|
|
{label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|