Files
backnotprop__plannotator/packages/ui/components/ModeToggle.tsx
Michael Ramos 94f8d45daa guide-viewer: readable on phones and tablets, desktop untouched (#1329)
* 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
2026-08-16 13:30:58 -07:00

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>
);
}