({
label,
description,
value,
options,
settingId,
onChange
}: {
label: string
description?: string
value: string | null
options: string[]
settingId?: string
onChange: (next: string | null) => void
})
| 4475 | } |
| 4476 | |
| 4477 | function FontRow({ |
| 4478 | label, |
| 4479 | description, |
| 4480 | value, |
| 4481 | options, |
| 4482 | settingId, |
| 4483 | onChange |
| 4484 | }: { |
| 4485 | label: string |
| 4486 | description?: string |
| 4487 | value: string | null |
| 4488 | options: string[] |
| 4489 | settingId?: string |
| 4490 | onChange: (next: string | null) => void |
| 4491 | }): JSX.Element { |
| 4492 | const [open, setOpen] = useState(false) |
| 4493 | const [query, setQuery] = useState('') |
| 4494 | const [activeIdx, setActiveIdx] = useState(0) |
| 4495 | const buttonRef = useRef<HTMLButtonElement | null>(null) |
| 4496 | const searchRef = useRef<HTMLInputElement | null>(null) |
| 4497 | const listRef = useRef<HTMLDivElement | null>(null) |
| 4498 | const [rect, setRect] = useState<{ left: number; top: number; width: number } | null>( |
| 4499 | null |
| 4500 | ) |
| 4501 | |
| 4502 | // Reset the search box whenever the popover opens. |
| 4503 | useEffect(() => { |
| 4504 | if (open) { |
| 4505 | setQuery('') |
| 4506 | setActiveIdx(0) |
| 4507 | setTimeout(() => searchRef.current?.focus(), 0) |
| 4508 | } |
| 4509 | }, [open]) |
| 4510 | |
| 4511 | // Close on outside click. |
| 4512 | useEffect(() => { |
| 4513 | if (!open) return |
| 4514 | const onDown = (e: MouseEvent): void => { |
| 4515 | const target = e.target as Node |
| 4516 | if (buttonRef.current?.contains(target)) return |
| 4517 | const portalRoot = document.getElementById('zen-font-portal') |
| 4518 | if (portalRoot?.contains(target)) return |
| 4519 | setOpen(false) |
| 4520 | } |
| 4521 | const onKey = (e: KeyboardEvent): void => { |
| 4522 | if (e.key === 'Escape') setOpen(false) |
| 4523 | } |
| 4524 | window.addEventListener('mousedown', onDown) |
| 4525 | window.addEventListener('keydown', onKey) |
| 4526 | return () => { |
| 4527 | window.removeEventListener('mousedown', onDown) |
| 4528 | window.removeEventListener('keydown', onKey) |
| 4529 | } |
| 4530 | }, [open]) |
| 4531 | |
| 4532 | // Position the popover below the button; reposition on scroll/resize. |
| 4533 | useLayoutEffect(() => { |
| 4534 | if (!open) return |
nothing calls this directly
no test coverage detected