mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
Merge remote-tracking branch 'origin/release/v1.42.0' into v142/ui-audit
This commit is contained in:
commit
492c2a4131
41 files changed
+1268
-151
No files matched your search
@@ -4,10 +4,12 @@ import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { Textarea } from "../textarea";
|
||||
|
||||
describe("<Textarea>", () => {
|
||||
it("pins text-base on mobile and shrinks to text-sm on sm+ (iOS zoom defence)", () => {
|
||||
it("pins text-base on touch screens and shrinks to text-sm only under a fine pointer (iOS zoom defence)", () => {
|
||||
const html = renderToStaticMarkup(<Textarea id="note" />);
|
||||
expect(html).toContain("text-base");
|
||||
expect(html).toContain("sm:text-sm");
|
||||
expect(html).toContain("sm:pointer-fine:text-sm");
|
||||
// A width breakpoint alone handed 14 px to a phone held sideways.
|
||||
expect(html).not.toMatch(/(^|\s)sm:text-sm(\s|")/);
|
||||
});
|
||||
|
||||
it("floors at min-h-11 on mobile and min-h-9 on sm+ (WCAG 2.5.5 tap target)", () => {
|
||||
@@ -62,7 +64,7 @@ describe("<Textarea>", () => {
|
||||
expect(html).toContain("font-mono");
|
||||
// iOS zoom defence still present after merge
|
||||
expect(html).toContain("text-base");
|
||||
expect(html).toContain("sm:text-sm");
|
||||
expect(html).toContain("sm:pointer-fine:text-sm");
|
||||
});
|
||||
|
||||
it("forwards rows / placeholder / maxLength to the underlying element", () => {
|
||||
|
||||
@@ -60,11 +60,13 @@ const FIELD_HEIGHT_CLASSES = "min-h-11 h-11 sm:min-h-10 sm:h-10";
|
||||
// `input-focus` tone, with no ring. The earlier `ring-2 ring-offset-2`
|
||||
// painted a hard 4 px ring OUTSIDE the box — wider than the field's div and
|
||||
// visibly mismatched next to the app's other inputs, and it shifted the
|
||||
// highlight box on focus. `text-base md:text-sm` mirrors `<Input>` too: a
|
||||
// sub-16 px input triggers iOS Safari's focus auto-zoom, which reads as a
|
||||
// layout jump; 16 px below `md:` suppresses it.
|
||||
// highlight box on focus. `text-base md:pointer-fine:text-sm` mirrors
|
||||
// `<Input>` too: a sub-16 px input triggers iOS Safari's focus auto-zoom,
|
||||
// which reads as a layout jump; 16 px on every touch screen suppresses it
|
||||
// (`pointer-fine`, globals.css — a width breakpoint alone missed a phone
|
||||
// held sideways and every iPad).
|
||||
const FIELD_BASE_CLASSES =
|
||||
"border-input bg-background text-foreground placeholder:text-muted-foreground relative flex w-full min-w-0 items-center rounded-md border ps-3 pe-2 text-base md:text-sm shadow-xs transition-[color,box-shadow] focus-within:border-input-focus focus-within:outline-none";
|
||||
"border-input bg-background text-foreground placeholder:text-muted-foreground relative flex w-full min-w-0 items-center rounded-md border ps-3 pe-2 text-base md:pointer-fine:text-sm shadow-xs transition-[color,box-shadow] focus-within:border-input-focus focus-within:outline-none";
|
||||
|
||||
export interface DateFieldProps {
|
||||
id?: string;
|
||||
|
||||
@@ -76,7 +76,7 @@ function Input({
|
||||
className={cn(
|
||||
// v1.4.34 IW-G — floor at 44 px on mobile to clear WCAG 2.5.5;
|
||||
// shrink to 40 px on sm+ where pointer precision is higher.
|
||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-11 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 sm:h-10 md:text-sm",
|
||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-11 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 sm:h-10 md:pointer-fine:text-sm",
|
||||
"focus-visible:border-input-focus aria-invalid:focus-visible:ring-[3px]",
|
||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
className,
|
||||
|
||||
@@ -56,8 +56,11 @@ import { cn } from "@/lib/utils";
|
||||
// theme's `--muted-foreground` — same colour the shadcn `<SelectTrigger>`
|
||||
// chevron uses.
|
||||
|
||||
// Type size: 16 px on a touch screen, 14 px only under a mouse or trackpad
|
||||
// (`pointer-fine`, globals.css). iOS Safari zooms the page into a focused
|
||||
// `<select>` whose text is smaller than 16 px, the same as into an input.
|
||||
const NATIVE_SELECT_CLASS =
|
||||
"border-input bg-background text-foreground placeholder:text-muted-foreground focus-visible:border-input-focus flex h-11 sm:h-10 w-full appearance-none rounded-md border ps-3 pe-9 py-1 text-sm shadow-xs transition-[color,box-shadow] focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 bg-[image:var(--native-select-chevron)] bg-[length:1rem] bg-[position:right_0.75rem_center] bg-no-repeat";
|
||||
"border-input bg-background text-foreground placeholder:text-muted-foreground focus-visible:border-input-focus flex h-11 sm:h-10 w-full appearance-none rounded-md border ps-3 pe-9 py-1 text-base pointer-fine:text-sm shadow-xs transition-[color,box-shadow] focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 bg-[image:var(--native-select-chevron)] bg-[length:1rem] bg-[position:right_0.75rem_center] bg-no-repeat";
|
||||
|
||||
export type NativeSelectProps = React.SelectHTMLAttributes<HTMLSelectElement>;
|
||||
|
||||
|
||||
@@ -250,8 +250,14 @@ export function ResponsiveSheet({
|
||||
className={cn(
|
||||
// Vertical-only scroll port — see the note on the Dialog
|
||||
// branch's body below for why `overflow-y-auto` alone does not
|
||||
// say that.
|
||||
"flex min-h-0 flex-1 flex-col gap-4 overflow-x-clip overflow-y-auto p-4",
|
||||
// say that. `overscroll-contain`: a flick that reaches the end
|
||||
// of the form stops there instead of chaining into the page
|
||||
// (or the browser's pull-to-refresh) behind the sheet.
|
||||
"flex min-h-0 flex-1 flex-col gap-4 overflow-x-clip overflow-y-auto overscroll-contain p-4",
|
||||
// Without a footer the body is the sheet's bottom edge, so it
|
||||
// keeps its last row above the home indicator of an
|
||||
// installed app; the footer does that when there is one.
|
||||
!footer && "pb-[calc(env(safe-area-inset-bottom,0px)+1rem)]",
|
||||
bodyClassName,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -61,6 +61,12 @@ function SheetContent({
|
||||
<SheetOverlay />
|
||||
<SheetPrimitive.Content
|
||||
data-slot="sheet-content"
|
||||
// Read by the safe-area and on-screen-keyboard rules in
|
||||
// `globals.css`: a bottom sheet stands on the keyboard, the
|
||||
// full-height sides keep clear of the status bar and the notch.
|
||||
// Its own attribute rather than `data-slot`, which consumers
|
||||
// replace with theirs (`responsive-sheet-content`, `day-panel`).
|
||||
data-sheet-side={side}
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||
side === "right" &&
|
||||
|
||||
@@ -12,10 +12,9 @@ import { Toaster as Sonner, type ToasterProps } from "sonner";
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
return (
|
||||
<Sonner
|
||||
// `theme="system"` lets Sonner match the resolved theme on the
|
||||
// root element (set pre-paint by the inline theme script in
|
||||
// `app/layout.tsx`) instead of pinning dark — on light-mode the
|
||||
// hardcoded dark toaster contrasted badly against the page.
|
||||
// A fallback only: Sonner's `"system"` follows the operating
|
||||
// system's scheme, not the app's theme, so the app's toaster passes
|
||||
// its resolved theme (`AppToaster` in `providers.tsx`).
|
||||
theme="system"
|
||||
className="toaster group"
|
||||
icons={{
|
||||
|
||||
@@ -14,10 +14,13 @@ import { cn } from "@/lib/utils";
|
||||
*
|
||||
* What it bakes in:
|
||||
*
|
||||
* - `text-base sm:text-sm` — iOS Safari zooms the viewport on
|
||||
* focus when the font-size renders below 16 px, which yanks the
|
||||
* keyboard up and leaves the user lost. 16 px on mobile holds
|
||||
* the viewport; 14 px on `sm`+ keeps the compact desktop look.
|
||||
* - `text-base sm:pointer-fine:text-sm` — iOS Safari zooms the
|
||||
* viewport on focus when the font-size renders below 16 px, which
|
||||
* yanks the keyboard up and leaves the user lost. 16 px on a touch
|
||||
* screen holds the viewport at any width (a phone held sideways is
|
||||
* past `sm`, an iPad past `md`); 14 px only where a mouse or
|
||||
* trackpad drives (`pointer-fine`, globals.css) keeps the compact
|
||||
* desktop look.
|
||||
*
|
||||
* - `min-h-11 sm:min-h-9` — WCAG 2.5.5 / Apple HIG floor of 44 px
|
||||
* on touch screens, shrinking to 36 px once we know we have a
|
||||
@@ -65,7 +68,7 @@ function Textarea({
|
||||
className={cn(
|
||||
// iOS zoom defence + WCAG 2.5.5 tap-target floor; see the
|
||||
// primitive's docblock for the full reasoning.
|
||||
"border-input bg-background text-foreground placeholder:text-muted-foreground dark:bg-input/30 min-h-11 w-full rounded-md border px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-9 sm:text-sm",
|
||||
"border-input bg-background text-foreground placeholder:text-muted-foreground dark:bg-input/30 min-h-11 w-full rounded-md border px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-9 sm:pointer-fine:text-sm",
|
||||
"focus-visible:border-input-focus aria-invalid:focus-visible:ring-[3px]",
|
||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
className,
|
||||
|
||||
@@ -45,10 +45,10 @@ const FIELD_HEIGHT_CLASSES = "min-h-11 h-11 sm:min-h-10 sm:h-10";
|
||||
// Focus vocabulary and font-size parity with `<Input>` / `<DateField>`: the
|
||||
// border turns the neutral `input-focus` tone with no ring (not the old hard
|
||||
// `ring-2 ring-offset-2`, which painted 4 px outside the div and jumped on
|
||||
// focus) and `text-base md:text-sm`
|
||||
// focus) and `text-base md:pointer-fine:text-sm`
|
||||
// to keep iOS Safari from auto-zooming a sub-16 px input.
|
||||
const FIELD_BASE_CLASSES =
|
||||
"border-input bg-background text-foreground placeholder:text-muted-foreground relative flex w-full min-w-0 items-center rounded-md border ps-3 pe-2 text-base md:text-sm shadow-xs transition-[color,box-shadow] focus-within:border-input-focus focus-within:outline-none";
|
||||
"border-input bg-background text-foreground placeholder:text-muted-foreground relative flex w-full min-w-0 items-center rounded-md border ps-3 pe-2 text-base md:pointer-fine:text-sm shadow-xs transition-[color,box-shadow] focus-within:border-input-focus focus-within:outline-none";
|
||||
|
||||
const TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
||||
|
||||
|
||||
Reference in new issue
Block a user