Seth commited on
Commit ·
9faed2a
1
Parent(s): b52b265
update
Browse files
frontend/src/components/workspace/SearchableCountryPicker.jsx
CHANGED
|
@@ -13,7 +13,7 @@ export function SearchableCountryPicker({ value, onChange, className = '' }) {
|
|
| 13 |
const triggerRef = useRef(null);
|
| 14 |
const menuRef = useRef(null);
|
| 15 |
const inputRef = useRef(null);
|
| 16 |
-
const [pos, setPos] = useState({ top: 0, left: 0, width:
|
| 17 |
|
| 18 |
const matched = matchCountry(value || '', countries);
|
| 19 |
const flag = matched ? flagEmojiFromCode(matched.code) : '🏳️';
|
|
@@ -28,19 +28,37 @@ export function SearchableCountryPicker({ value, onChange, className = '' }) {
|
|
| 28 |
);
|
| 29 |
}, [countries, query]);
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
useLayoutEffect(() => {
|
| 32 |
if (!open || !triggerRef.current) return;
|
| 33 |
-
|
| 34 |
-
setPos({ top: r.bottom + 4, left: r.left, width: Math.max(r.width, 220) });
|
| 35 |
}, [open]);
|
| 36 |
|
| 37 |
useEffect(() => {
|
| 38 |
if (!open) return;
|
| 39 |
-
const reposition = () =>
|
| 40 |
-
if (!triggerRef.current) return;
|
| 41 |
-
const r = triggerRef.current.getBoundingClientRect();
|
| 42 |
-
setPos({ top: r.bottom + 4, left: r.left, width: Math.max(r.width, 220) });
|
| 43 |
-
};
|
| 44 |
window.addEventListener('scroll', reposition, true);
|
| 45 |
window.addEventListener('resize', reposition);
|
| 46 |
return () => {
|
|
@@ -102,18 +120,15 @@ export function SearchableCountryPicker({ value, onChange, className = '' }) {
|
|
| 102 |
ref={menuRef}
|
| 103 |
role="listbox"
|
| 104 |
data-country-picker="true"
|
| 105 |
-
className="fixed z-[10050] rounded-md border border-slate-200 bg-white shadow-
|
| 106 |
style={{
|
| 107 |
top: pos.top,
|
| 108 |
left: pos.left,
|
| 109 |
-
|
| 110 |
-
maxHeight: 'min(70vh, 20rem)',
|
| 111 |
-
display: 'flex',
|
| 112 |
-
flexDirection: 'column',
|
| 113 |
}}
|
| 114 |
onClick={(e) => e.stopPropagation()}
|
| 115 |
>
|
| 116 |
-
<div className="border-b border-slate-100 p-2">
|
| 117 |
<input
|
| 118 |
ref={inputRef}
|
| 119 |
type="search"
|
|
@@ -126,7 +141,7 @@ export function SearchableCountryPicker({ value, onChange, className = '' }) {
|
|
| 126 |
className="w-full rounded-md border border-slate-200 px-2 py-1.5 text-sm outline-none focus:border-violet-300 focus:ring-1 focus:ring-violet-200"
|
| 127 |
/>
|
| 128 |
</div>
|
| 129 |
-
<div className="overflow-y-auto p-1">
|
| 130 |
<button
|
| 131 |
type="button"
|
| 132 |
role="option"
|
|
|
|
| 13 |
const triggerRef = useRef(null);
|
| 14 |
const menuRef = useRef(null);
|
| 15 |
const inputRef = useRef(null);
|
| 16 |
+
const [pos, setPos] = useState({ top: 0, left: 0, width: 320 });
|
| 17 |
|
| 18 |
const matched = matchCountry(value || '', countries);
|
| 19 |
const flag = matched ? flagEmojiFromCode(matched.code) : '🏳️';
|
|
|
|
| 28 |
);
|
| 29 |
}, [countries, query]);
|
| 30 |
|
| 31 |
+
const computeMenuPosition = () => {
|
| 32 |
+
const trigger = triggerRef.current;
|
| 33 |
+
if (!trigger) return;
|
| 34 |
+
const r = trigger.getBoundingClientRect();
|
| 35 |
+
const PAD = 12;
|
| 36 |
+
const MENU_W = 320;
|
| 37 |
+
const vw = window.innerWidth;
|
| 38 |
+
const vh = window.innerHeight;
|
| 39 |
+
// Last-column friendly: if trigger sits in the right part of the viewport,
|
| 40 |
+
// anchor the panel by its right edge to the trigger's right edge so the menu opens leftward.
|
| 41 |
+
const inRightZone = r.right > vw * 0.55;
|
| 42 |
+
let left = inRightZone ? r.right - MENU_W : r.left;
|
| 43 |
+
left = Math.max(PAD, Math.min(left, vw - MENU_W - PAD));
|
| 44 |
+
|
| 45 |
+
const estPanelH = Math.min(vh * 0.72, 420);
|
| 46 |
+
let top = r.bottom + 6;
|
| 47 |
+
if (top + estPanelH > vh - PAD) {
|
| 48 |
+
top = Math.max(PAD, r.top - estPanelH - 6);
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
setPos({ top, left, width: MENU_W });
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
useLayoutEffect(() => {
|
| 55 |
if (!open || !triggerRef.current) return;
|
| 56 |
+
computeMenuPosition();
|
|
|
|
| 57 |
}, [open]);
|
| 58 |
|
| 59 |
useEffect(() => {
|
| 60 |
if (!open) return;
|
| 61 |
+
const reposition = () => computeMenuPosition();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
window.addEventListener('scroll', reposition, true);
|
| 63 |
window.addEventListener('resize', reposition);
|
| 64 |
return () => {
|
|
|
|
| 120 |
ref={menuRef}
|
| 121 |
role="listbox"
|
| 122 |
data-country-picker="true"
|
| 123 |
+
className="fixed z-[10050] flex max-h-[min(70vh,24rem)] flex-col rounded-md border border-slate-200 bg-white shadow-xl ring-1 ring-black/5"
|
| 124 |
style={{
|
| 125 |
top: pos.top,
|
| 126 |
left: pos.left,
|
| 127 |
+
width: pos.width,
|
|
|
|
|
|
|
|
|
|
| 128 |
}}
|
| 129 |
onClick={(e) => e.stopPropagation()}
|
| 130 |
>
|
| 131 |
+
<div className="shrink-0 border-b border-slate-100 p-2">
|
| 132 |
<input
|
| 133 |
ref={inputRef}
|
| 134 |
type="search"
|
|
|
|
| 141 |
className="w-full rounded-md border border-slate-200 px-2 py-1.5 text-sm outline-none focus:border-violet-300 focus:ring-1 focus:ring-violet-200"
|
| 142 |
/>
|
| 143 |
</div>
|
| 144 |
+
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-1">
|
| 145 |
<button
|
| 146 |
type="button"
|
| 147 |
role="option"
|
frontend/src/pages/Deals.jsx
CHANGED
|
@@ -277,7 +277,7 @@ function DealRow({ deal, tableEditRowId, setTableEditRowId, patchDeal, updateSta
|
|
| 277 |
</div>
|
| 278 |
)}
|
| 279 |
</td>
|
| 280 |
-
<td className="px-3 py-2 align-top tabular-nums max-w-[
|
| 281 |
{tableEditRowId === deal.id ? (
|
| 282 |
<EditableCell
|
| 283 |
type="number"
|
|
@@ -291,6 +291,8 @@ function DealRow({ deal, tableEditRowId, setTableEditRowId, patchDeal, updateSta
|
|
| 291 |
if (!Number.isFinite(n)) return;
|
| 292 |
patchDeal(deal.id, { deal_value: n });
|
| 293 |
}}
|
|
|
|
|
|
|
| 294 |
/>
|
| 295 |
) : (
|
| 296 |
<div className="min-h-[2rem] py-1 text-sm text-slate-700 tabular-nums">
|
|
|
|
| 277 |
</div>
|
| 278 |
)}
|
| 279 |
</td>
|
| 280 |
+
<td className="px-3 py-2 align-top tabular-nums min-w-[10rem] w-40 max-w-[16rem]">
|
| 281 |
{tableEditRowId === deal.id ? (
|
| 282 |
<EditableCell
|
| 283 |
type="number"
|
|
|
|
| 291 |
if (!Number.isFinite(n)) return;
|
| 292 |
patchDeal(deal.id, { deal_value: n });
|
| 293 |
}}
|
| 294 |
+
className="w-full"
|
| 295 |
+
inputClassName="min-w-[9rem] w-full max-w-full tabular-nums text-right text-base py-1"
|
| 296 |
/>
|
| 297 |
) : (
|
| 298 |
<div className="min-h-[2rem] py-1 text-sm text-slate-700 tabular-nums">
|