Scroll long customer search results, and keep the dialog usable on short screens
054a6f9fix(orders)merged 3d ago1 review thread
Illustrative: based on a real change, edited for clarity and privacy.
src/domains/orders/components/customer-picker-dialog.tsx+13 −4
| @@ export function CustomerSearchDialog({ onCloseAction, onSelectAction }: { | |||
| 30 | 30 | const [isSearching, startTransition] = useTransition() | |
| 31 | 31 | const inputRef = useRef<HTMLInputElement>(null) | |
| 32 | 32 | const queryRef = useRef('') | |
| 33 | added | const rowRefs = useRef<(HTMLButtonElement | null)[]>([]) | |
| 33 | 34 | ||
| 34 | 35 | useEffect(() => { | |
| 35 | 36 | inputRef.current?.focus() | |
| @@ export function CustomerSearchDialog({ onCloseAction, onSelectAction }: { | |||
| 63 | 64 | ||
| 64 | 65 | const focused = Math.min(focusIndex, Math.max(results.length - 1, 0)) | |
| 65 | 66 | ||
| 67 | added | // Arrow keys can move the highlight past the visible rows; keep it in view. | |
| 68 | added | useEffect(() => { | |
| 69 | added | rowRefs.current[focused]?.scrollIntoView({ block: 'nearest' }) | |
| 70 | added | }, [focused, results]) | |
| 71 | added | ||
| 66 | 72 | function handleKeyDown(e: React.KeyboardEvent) { | |
| 67 | 73 | if (e.key === 'Escape') { | |
| 68 | 74 | onCloseAction() | |
| @@ export function CustomerSearchDialog({ onCloseAction, onSelectAction }: { | |||
| 87 | 93 | role="dialog" | |
| 88 | 94 | aria-modal="true" | |
| 89 | 95 | aria-label="Select customer" | |
| 90 | removed | className="w-full max-w-[560px] overflow-hidden rounded-xl bg-card shadow-lg" | |
| 1 line 96, review thread 1 | added | className="flex max-h-full w-full max-w-[560px] flex-col overflow-y-auto overscroll-contain rounded-xl bg-card shadow-lg" | |
| 91 | 97 | onClick={(e) => e.stopPropagation()} | |
| 92 | 98 | onKeyDown={handleKeyDown} | |
| 93 | 99 | > | |
| 94 | removed | <div className="flex items-center justify-between px-6 pb-4 pt-5"> | |
| 100 | added | <div className="flex shrink-0 items-center justify-between px-6 pb-4 pt-5"> | |
| 95 | 101 | <h2 className="text-base font-semibold text-primary">Select customer</h2> | |
| 96 | 102 | <button type="button" onClick={onCloseAction} aria-label="Close"> | |
| 97 | 103 | <X size={16} strokeWidth={2} /> | |
| 98 | 104 | </button> | |
| 99 | 105 | </div> | |
| 100 | removed | <div className="px-6 pb-4"> | |
| 106 | added | <div className="shrink-0 px-6 pb-4"> | |
| 101 | 107 | <div className="relative"> | |
| 102 | 108 | <Search size={15} className="absolute left-3.5 top-1/2 -translate-y-1/2" /> | |
| 103 | 109 | <input | |
| @@ export function CustomerSearchDialog({ onCloseAction, onSelectAction }: { | |||
| 124 | 130 | )} | |
| 125 | 131 | {showResults && ( | |
| 126 | 132 | <> | |
| 127 | removed | <ul className="border-t"> | |
| 133 | added | <ul className="min-h-24 flex-1 overflow-y-auto overscroll-contain border-t"> | |
| 128 | 134 | {results.map((customer, i) => ( | |
| 129 | 135 | <li key={customer.ref} className={i > 0 ? 'border-t' : ''}> | |
| 130 | 136 | <button | |
| 137 | added | ref={(el) => { | |
| 138 | added | rowRefs.current[i] = el | |
| 139 | added | }} | |
| 131 | 140 | type="button" | |
| 132 | 141 | onMouseEnter={() => setFocusIndex(i)} | |
| 133 | 142 | onClick={() => onSelectAction({ name: customer.name, ref: customer.ref })} | |
reviewer
Tried this on my phone with the keyboard up: only the results scroll, and the “create a new customer” link disappears off the bottom. Can the dialog itself scroll?
author
Fixed: the dialog now scrolls when its content is taller than the space available, and the results list keeps a
min-h-24floor plus its own scroll. The no-matches message and the create link stay reachable.AI reviewP1
Height cap hardcodes spacing
The height calculation uses a literal
1.5remoffset. The repo’s rules say spacing comes from design tokens, not inline values.author
The calc is gone. The overlay already pads the viewport, so the dialog uses
max-h-fullagainst the overlay’s padded box.