Fix 18 UX issues: confirmations, undo, drawer nav, empty states, and polish
Build and push image / build (push) Successful in 54s
Build and push image / build (push) Successful in 54s
Comprehensive UX audit covering modals, drawers, dashboard, and inventory. Key changes: confirmation steps before destructive actions, undo via toast for consume/gone/checkout, back-navigation across entity drawers, optional ratings, discrete item count field, audit progress bar, and sortable column affordance. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,8 @@ import { getToday, getStoredTimezone } from "../tz.js";
|
||||
import { fmt } from "../format.js";
|
||||
import { Btn, Pill, Icon } from "./primitives/index.js";
|
||||
import { remainingShort } from "../stats.js";
|
||||
import { useExitAnimation } from "../hooks/useExitAnimation.js";
|
||||
import { useFocusTrap } from "../hooks/useFocusTrap.js";
|
||||
|
||||
export function ShopDetail({
|
||||
shop,
|
||||
@@ -50,13 +52,16 @@ export function ShopDetail({
|
||||
const todayStr = getToday(getStoredTimezone());
|
||||
const tz = getStoredTimezone();
|
||||
|
||||
const { closing, triggerClose } = useExitAnimation(220, onClose);
|
||||
const trapRef = useFocusTrap<HTMLDivElement>();
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
if (e.key === "Escape") triggerClose();
|
||||
};
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||
}, [onClose]);
|
||||
}, [triggerClose]);
|
||||
|
||||
const statCards: [string, React.ReactNode][] = [
|
||||
["Purchases", String(allItems.length)],
|
||||
@@ -66,6 +71,9 @@ export function ShopDetail({
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={trapRef}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
@@ -73,16 +81,16 @@ export function ShopDetail({
|
||||
zIndex: 50,
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
animation: "backdrop-in 200ms ease-out",
|
||||
animation: closing ? "backdrop-out 220ms ease-in forwards" : "backdrop-in 200ms ease-out",
|
||||
}}
|
||||
onClick={onClose}
|
||||
onClick={triggerClose}
|
||||
>
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
width: "min(720px, 100vw)",
|
||||
height: "100%",
|
||||
animation: "drawer-in 250ms ease-out",
|
||||
animation: closing ? "drawer-out 220ms ease-in forwards" : "drawer-in 250ms ease-out",
|
||||
background: "var(--bg)",
|
||||
borderLeft: "1px solid var(--line)",
|
||||
overflow: "auto",
|
||||
@@ -112,9 +120,10 @@ export function ShopDetail({
|
||||
icon="bin"
|
||||
disabled={hasItems}
|
||||
onClick={onDelete}
|
||||
title={hasItems ? "Cannot delete — has inventory items" : undefined}
|
||||
style={hasItems ? { opacity: 0.3, cursor: "not-allowed" } : undefined}
|
||||
/>
|
||||
<Btn variant="ghost" icon="close" onClick={onClose} />
|
||||
<Btn variant="ghost" icon="close" onClick={triggerClose} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user