From 3542739786c16c6e43db7cafe266dcc8f3a7378f Mon Sep 17 00:00:00 2001 From: fireshaper Date: Sat, 31 Oct 2026 22:45:34 -0400 Subject: [PATCH] Design pass: contrast, shared form controls, dialogs, mobile layouts A - accessibility - on-accent-warm text token (For Sale badge, flags) and darker Sunset/Ocean colors to meet WCAG AA contrast - 44px touch targets for Herd and Shows filters on phones - status badge on non-active Herd grid cards; drop redundant View Herd B - consistency - FormControls (Field, FieldGroup, Input, Select, Textarea, FileInput, Segmented) used by every form, with labels wired to their inputs - ConfirmDialog + useConfirm replace window.confirm and hand-built modals - ErrorPanel with Try Again for failed lists; LoadError built on it C - layout - ActionMenu: rabbit header is Edit, Status, More - Herd folds Status/Sort/view behind a Filters button on phones - Dashboard status counts as a compact second row - Shows list as stacked cards below xl - SVG timeline icons instead of emoji Co-Authored-By: Claude Opus 5.5 --- memory-bank/architecture.md | 14 ++ mykindlebox/client/src/auth.jsx | 33 ++-- .../client/src/components/ActionMenu.jsx | 77 ++++++++ .../client/src/components/AddToEventModal.jsx | 36 ++-- mykindlebox/client/src/components/Badge.jsx | 2 +- .../client/src/components/BreedSelect.jsx | 9 +- .../client/src/components/ConfirmDialog.jsx | 56 ++++++ .../client/src/components/ErrorPanel.jsx | 26 +++ .../client/src/components/FormControls.jsx | 136 ++++++++++++++ .../client/src/components/LitterForm.jsx | 86 +++------ .../client/src/components/LoadError.jsx | 19 +- .../client/src/components/PedigreeScan.jsx | 2 +- .../client/src/components/PedigreeTree.jsx | 2 +- .../client/src/components/RabbitCard.jsx | 10 +- .../src/components/RabbitDetailsForm.jsx | 176 ++++++------------ .../client/src/components/RabbitTimeline.jsx | 153 +++++++++------ .../client/src/components/UsersCard.jsx | 66 +++---- mykindlebox/client/src/hooks/useConfirm.jsx | 35 ++++ mykindlebox/client/src/index.css | 14 +- mykindlebox/client/src/lib/theme.js | 4 +- mykindlebox/client/src/pages/AddRabbit.jsx | 25 ++- mykindlebox/client/src/pages/Dashboard.jsx | 77 +++++--- mykindlebox/client/src/pages/EditRabbit.jsx | 12 +- mykindlebox/client/src/pages/Herd.jsx | 119 +++++++----- mykindlebox/client/src/pages/LitterDetail.jsx | 14 +- mykindlebox/client/src/pages/Litters.jsx | 5 +- mykindlebox/client/src/pages/RabbitDetail.jsx | 51 +++-- mykindlebox/client/src/pages/Settings.jsx | 100 ++++------ .../client/src/pages/ShowEventDetail.jsx | 88 +++++---- mykindlebox/client/src/pages/ShowEvents.jsx | 31 ++- mykindlebox/client/src/pages/Shows.jsx | 91 ++++++++- mykindlebox/client/tailwind.config.js | 1 + 32 files changed, 979 insertions(+), 591 deletions(-) create mode 100644 mykindlebox/client/src/components/ActionMenu.jsx create mode 100644 mykindlebox/client/src/components/ConfirmDialog.jsx create mode 100644 mykindlebox/client/src/components/ErrorPanel.jsx create mode 100644 mykindlebox/client/src/components/FormControls.jsx create mode 100644 mykindlebox/client/src/hooks/useConfirm.jsx diff --git a/memory-bank/architecture.md b/memory-bank/architecture.md index 3bb01fa..656eff5 100644 --- a/memory-bank/architecture.md +++ b/memory-bank/architecture.md @@ -126,4 +126,18 @@ docker-compose.yml — orchestrates both services for self-hosted deployment - **Settings** is split into a shared **Rabbitry** card (name, kit prefix, default breed) and a per-user **Your Preferences** card (weight display, theme), each saving only its own keys. Export is labelled **Back Up** and states that Import can't read it (Import = CSV/JSON herd replacement). - **Genetics is hidden from the sidebar and mobile More menu** until the calculator is built; the `/genetics` placeholder route still exists. Grandparents are corrected by editing the parent rabbit (Edit Rabbit only re-links sire/dam), by user decision. - Herd's sex tab for `sex='unknown'` is labelled **Unknown** (was "Other", which collided with the `other` status). +- **Design pass A (2026-10-31)** — accessibility fixes: + - Text on `accent-warm` fills uses the **`on-accent-warm`** token (dark ink in every theme; Midnight maps it to `surface`), never `text-surface` — white on the warm oranges fails contrast. Sunset's accent/hover and secondary text and Ocean's secondary text were darkened to reach WCAG AA (≥4.5:1). + - Filter controls on Herd and Shows are ≥44px tall on phones (`min-h-[44px] md:min-h-[32px]`, matching `Button size="sm"`). + - Herd grid cards badge any non-active status over the photo; the Dashboard's redundant "View Herd" quick action was removed. +- **Design pass B (2026-10-31)** — shared UI primitives; use these instead of hand-styling: + - **`components/FormControls.jsx`**: `Field` (label + hint/error, wires `htmlFor`/`aria-describedby`/`aria-invalid` to its control via context), `FieldGroup` (same label style for non-input controls such as `RabbitAutocomplete` or `Segmented`), `Input` / `Select` / `Textarea` / `FileInput` (`size="md"` forms: 44px touch, 40px desktop; `size="sm"` filter bars: 44px touch, 32px desktop), and `Segmented` (button-group choice: Sex, Weight Display). `BreedSelect` renders an `Input`, so it picks up a surrounding `Field`'s id. Every form in the app — including the login/setup screens — uses these; the only raw inputs left are search boxes with icons and the pedigree-tree slot editors. + - **`components/ConfirmDialog.jsx`** (native `` + `showModal()`: focus trap, Escape, top layer; focus starts on Cancel) replaces every `window.confirm` and the hand-built Settings import / remove-user modals. `hooks/useConfirm.jsx` is the promise-based `await confirm({...})` form; render `` directly when the dialog must show progress (`busy`/`busyLabel`). + - **`components/ErrorPanel.jsx`** is the failed-to-load state for lists/sections (title, error message, Try Again → React Query `refetch`); `LoadError` (whole-page record) is built on it. Show Event Detail now uses `LoadError`. +- **Design pass C (2026-10-31)** — layout: + - **`components/ActionMenu.jsx`**: a "More ▾" button + menu for secondary page actions (`items` = `{label, to}` / `{label, onClick}`). Rabbit Detail's header is Edit · Status · More (Record Breeding / Record Litter for bucks & does, Print Pedigree); Delete stays at the page bottom. + - Herd on phones shows the sex tabs + search; Status, Sort and grid/list fold behind a **Filters** button (badged with how many differ from the defaults). Desktop shows everything inline. + - Dashboard: Herd / Bucks / Does / Kits are the large tiles; For Sale / On Hold / Sold / Archived are a compact second row (same `--stat-N` theme vars). + - Shows: stacked `ShowListCard`s below `xl`; the 9-column table only at `xl`+ where it fits beside the sidebar. + - Rabbit timeline markers are inline SVG stroke icons (no emoji anywhere in the client). - **Offline handling is one app-wide banner, not per-query state**: `Layout.jsx`'s `OfflineBanner` listens to the `online`/`offline` `window` events directly — it isn't wired to React Query's error state. This was a deliberate scope choice: every page already has its own `isError` fallback (so nothing crashes when a fetch fails offline), and threading a distinct "offline" message through every individual query would duplicate that existing handling for a cosmetic improvement only. If a future feature needs to distinguish "offline" from "other network error" *within* a specific page's content (not just a banner), that's new logic, not an extension of this banner. diff --git a/mykindlebox/client/src/auth.jsx b/mykindlebox/client/src/auth.jsx index afe386c..db41e52 100644 --- a/mykindlebox/client/src/auth.jsx +++ b/mykindlebox/client/src/auth.jsx @@ -8,6 +8,8 @@ import { setUnauthorizedHandler, } from './api' import { AuthContext } from './authContext' +import Button from './components/Button' +import { Field, Input } from './components/FormControls' const MIN_PASSWORD_LENGTH = 8 // mirrors the server's MIN_PASSWORD_LENGTH @@ -29,28 +31,19 @@ function AuthShell({ title, subtitle, onSubmit, error, children }) { ) } -function Field({ label, hint, ...inputProps }) { +function AuthField({ label, hint, ...inputProps }) { return ( -
- - - {hint &&

{hint}

} -
+ + + ) } function SubmitButton({ disabled, children }) { return ( - + ) } @@ -76,7 +69,7 @@ function LoginScreen({ onSuccess }) { return ( - setUsername(e.target.value)} /> - - setUsername(e.target.value)} /> - setPassword(e.target.value)} /> - { + if (!open) return + function onPointerDown(e) { + if (ref.current && !ref.current.contains(e.target)) setOpen(false) + } + function onKeyDown(e) { + if (e.key === 'Escape') setOpen(false) + } + document.addEventListener('pointerdown', onPointerDown) + document.addEventListener('keydown', onKeyDown) + return () => { + document.removeEventListener('pointerdown', onPointerDown) + document.removeEventListener('keydown', onKeyDown) + } + }, [open]) + + return ( +
+ + {open && ( +
+ {items.map((item) => + item.to ? ( + + {item.label} + + ) : ( + + ) + )} +
+ )} +
+ ) +} + +export default ActionMenu diff --git a/mykindlebox/client/src/components/AddToEventModal.jsx b/mykindlebox/client/src/components/AddToEventModal.jsx index 48f72dd..264a75e 100644 --- a/mykindlebox/client/src/components/AddToEventModal.jsx +++ b/mykindlebox/client/src/components/AddToEventModal.jsx @@ -2,6 +2,7 @@ import { useState } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { getShowEvents, createShowEvent, addShowToEvent } from '../api' import Button from './Button' +import { Field, Input, Select, Textarea } from './FormControls' // Modal launched from a row's "Add to Event" button in the Shows browser. // Lets the user drop the ARBA show into an existing event or spin up a new one @@ -102,44 +103,29 @@ function AddToEventModal({ show, onClose }) { )} {effectiveMode === 'existing' ? ( -
- - setSelectedId(e.target.value)}> {events?.map((event) => ( ))} - -
+ +
) : (
-
- - + setNewName(e.target.value)} placeholder="e.g. Spring Circuit Weekend" - className="mt-1 w-full rounded-md border border-border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent" /> -
-
- -