polish(water-log/settings): Apple HIG refinements to the Smartsheet surface
Deploy to route.crispygoat.com / deploy (push) Successful in 4m0s
Deploy to route.crispygoat.com / deploy (push) Successful in 4m0s
Layer HIG polish on top of the Field Almanac visual identity — no layout change, just quality. Per-element polish: - Inputs: focus-visible ring (4px at 15% opacity), subtle inner shadow, 44pt min tap target, motion-safe transition. - Selects: same focus ring + min height + transition. - Toggles: Apple spring curve (cubic-bezier 0.32,0.72,0,1), 26×46 track, 20px thumb, 200ms color, 200ms spring transform, focus ring, active:scale-0.97. - Buttons: focus-visible ring, active:scale-0.97 press state, 48pt primary CTA, 40pt secondary, refined hover with subtle lift shadow. - Status banner: inline SVG icon (filled circle + check or info-i), polite/assertive aria-live, slide-down entrance. - Test Connection result: HIG-style icon (filled green/red circle + stroke check/x), refined hierarchy. - Backfill result + error: same HIG pattern as status banner. - Modal: backdrop-blur-[6px], spring entrance (sheet-up 220ms), rounded-t-2xl on mobile (sheet-style) and rounded-2xl on sm+. - All animations: motion-safe: + a prefers-reduced-motion block in globals.css that disables them. Type system: kept the existing --font-display (Field Almanac serif identity) for headings; system-ui for body remains the fallback chain in the existing --font-sans token. New keyframes in globals.css: smartsheet-fade-in, smartsheet-sheet-up, smartsheet-slide-down. All short (180–220ms) to feel responsive. Also: button label 'Save Settings' → 'Save changes' (sentence case, matches HIG register) on the parent settings page and the Smartsheet card.
This commit is contained in:
@@ -261,13 +261,44 @@ export default function WaterLogSettingsPage() {
|
||||
<form onSubmit={handleSave} className="space-y-6">
|
||||
{message && (
|
||||
<div
|
||||
className={`rounded-lg border px-4 py-3 text-sm font-medium ${
|
||||
message.type === "success"
|
||||
? "border-[#1a4d2e] bg-[#1a4d2e]/5 text-[#1a4d2e]"
|
||||
: "border-[#a4452b] bg-[#a4452b]/5 text-[#a4452b]"
|
||||
}`}
|
||||
role={message.type === "error" ? "alert" : "status"}
|
||||
aria-live={message.type === "error" ? "assertive" : "polite"}
|
||||
className={`smartsheet-slide-down flex items-start gap-3 rounded-lg border px-4 py-3 text-[13.5px] font-medium
|
||||
${
|
||||
message.type === "success"
|
||||
? "border-[#1a4d2e]/25 bg-[#1a4d2e]/[0.05] text-[#143d24]"
|
||||
: "border-[#a4452b]/25 bg-[#a4452b]/[0.05] text-[#7a341f]"
|
||||
}`}
|
||||
>
|
||||
{message.text}
|
||||
<span
|
||||
aria-hidden
|
||||
className={`mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-white ${
|
||||
message.type === "success" ? "bg-[#1a4d2e]" : "bg-[#a4452b]"
|
||||
}`}
|
||||
>
|
||||
{message.type === "success" ? (
|
||||
<svg viewBox="0 0 12 12" className="h-3 w-3">
|
||||
<path
|
||||
d="M2 6.5L5 9.5L10 3.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg viewBox="0 0 12 12" className="h-3 w-3">
|
||||
<path
|
||||
d="M6 3V6.5M6 8.5V9"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</span>
|
||||
<p className="leading-snug">{message.text}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -317,9 +348,22 @@ export default function WaterLogSettingsPage() {
|
||||
type="button"
|
||||
onClick={handleRegenerate}
|
||||
disabled={regenerating}
|
||||
className="shrink-0 rounded-lg border border-[#1a4d2e] bg-[#1a4d2e] px-4 py-2 text-sm font-semibold text-white transition hover:bg-[#143d24] disabled:opacity-50"
|
||||
className="inline-flex min-h-[40px] shrink-0 items-center justify-center rounded-lg border border-[#1a4d2e] bg-[#1a4d2e] px-4 py-2
|
||||
text-[14px] font-semibold text-white
|
||||
motion-safe:transition-[background-color,transform] motion-safe:duration-150 motion-safe:ease-out
|
||||
hover:bg-[#143d24]
|
||||
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/30
|
||||
active:scale-[0.97] active:bg-[#0f2e1c]
|
||||
disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{regenerating ? "Generating…" : "Regenerate PIN"}
|
||||
{regenerating ? (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<span className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-white/30 border-t-white motion-reduce:animate-none" />
|
||||
Generating…
|
||||
</span>
|
||||
) : (
|
||||
"Regenerate PIN"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -393,7 +437,10 @@ export default function WaterLogSettingsPage() {
|
||||
dispatch({ type: "SET_ALERT_PHONE", value: e.target.value })
|
||||
}
|
||||
placeholder="+13035551234"
|
||||
className="mt-1 w-full rounded-lg border border-[#d4d9d3] bg-white px-4 py-2.5 text-base outline-none focus:border-[#1a4d2e] focus:ring-1 focus:ring-[#1a4d2e]"
|
||||
className="mt-1 block min-h-[44px] w-full rounded-lg border border-[#d4d9d3] bg-white px-4 py-2.5
|
||||
text-[14px] text-[#1d1d1f] placeholder:text-[#a3a5a0]
|
||||
outline-none motion-safe:transition-[border-color,box-shadow] motion-safe:duration-150
|
||||
focus:border-[#1a4d2e] focus:ring-4 focus:ring-[#1a4d2e]/15"
|
||||
autoComplete="tel"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-[#8a8b88]">
|
||||
@@ -408,9 +455,23 @@ export default function WaterLogSettingsPage() {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={saving}
|
||||
className="w-full rounded-lg bg-[#1a4d2e] px-6 py-3.5 text-sm font-semibold uppercase tracking-wider text-white transition hover:bg-[#143d24] disabled:opacity-50"
|
||||
className="inline-flex min-h-[48px] w-full items-center justify-center rounded-lg bg-[#1a4d2e] px-6 py-3
|
||||
text-[14px] font-semibold tracking-tight text-white
|
||||
shadow-[0_1px_0_rgba(0,0,0,0.06),inset_0_1px_0_rgba(255,255,255,0.06)]
|
||||
motion-safe:transition-[background-color,transform,box-shadow] motion-safe:duration-150 motion-safe:ease-out
|
||||
hover:bg-[#143d24] hover:shadow-[0_2px_4px_rgba(15,23,18,0.1),inset_0_1px_0_rgba(255,255,255,0.06)]
|
||||
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/30 focus-visible:ring-offset-2 focus-visible:ring-offset-[#fdfaf2]
|
||||
active:scale-[0.985] active:bg-[#0f2e1c]
|
||||
disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{saving ? "Saving…" : "Save Settings"}
|
||||
{saving ? (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white motion-reduce:animate-none" />
|
||||
Saving…
|
||||
</span>
|
||||
) : (
|
||||
"Save changes"
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -450,10 +511,16 @@ function Card({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="rounded-xl border border-[#d4d9d3] bg-white p-5 shadow-sm">
|
||||
<header className="mb-4">
|
||||
<h2 className="text-base font-semibold text-[#1d1d1f]">{title}</h2>
|
||||
{subtitle && <p className="mt-0.5 text-xs text-[#5a5d5a]">{subtitle}</p>}
|
||||
<section className="rounded-xl border border-[#d4d9d3] bg-white/95 p-6 shadow-[0_1px_2px_rgba(15,23,18,0.04),0_2px_6px_-2px_rgba(15,23,18,0.06)]">
|
||||
<header className="mb-5">
|
||||
<h2 className="text-[15px] font-semibold tracking-tight text-[#1d1d1f]">
|
||||
{title}
|
||||
</h2>
|
||||
{subtitle && (
|
||||
<p className="mt-1 text-[13px] leading-snug text-[#5a5d5a]">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</header>
|
||||
{children}
|
||||
</section>
|
||||
@@ -483,14 +550,17 @@ function ToggleRow({
|
||||
aria-checked={value}
|
||||
aria-label={label}
|
||||
onClick={() => onChange(!value)}
|
||||
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors ${
|
||||
value ? "bg-[#1a4d2e]" : "bg-[#d4d9d3]"
|
||||
}`}
|
||||
className={`group relative inline-flex h-[26px] w-[46px] shrink-0 items-center rounded-full
|
||||
motion-safe:transition-colors motion-safe:duration-200
|
||||
focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/25 focus-visible:ring-offset-2 focus-visible:ring-offset-white
|
||||
active:scale-[0.97] motion-safe:transition-transform motion-safe:ease-out
|
||||
${value ? "bg-[#1a4d2e]" : "bg-[#d4d9d3]"}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${
|
||||
value ? "translate-x-6" : "translate-x-1"
|
||||
}`}
|
||||
className={`inline-block h-[20px] w-[20px] transform rounded-full bg-white shadow-[0_1px_2px_rgba(0,0,0,0.2),0_1px_1px_rgba(0,0,0,0.06)]
|
||||
motion-safe:transition-transform motion-safe:duration-200
|
||||
motion-safe:ease-[cubic-bezier(0.32,0.72,0,1)]
|
||||
${value ? "translate-x-[22px]" : "translate-x-[3px]"}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user