'use client' import { useState, useRef, useEffect, useCallback } from 'react' import { createPortal } from 'react-dom' import { cn } from '@/lib/utils' import { Check, ChevronDown } from 'lucide-react' export interface ContextPickerItem { id: string label: string /** Muted right-side note on the row, e.g. "stängt" for a closed fiscal year. */ annotation?: string disabled?: boolean } interface ContextPickerProps { items: ContextPickerItem[] /** Selected item id. */ value: string | null onChange: (id: string) => void /** Chip text, e.g. "Räkenskapsår 2026" or "Alla källor · 24 300 kr". */ triggerLabel: string disabled?: boolean ariaLabel?: string className?: string } /** * The page context picker (UI-migration convention 8): a chip-dropdown that * scopes the page to a fiscal year, account or source. One per page, far * right in the toolbar. A chip that looks like a picker must be a picker; * this is that picker. */ export function ContextPicker({ items, value, onChange, triggerLabel, disabled = false, ariaLabel, className, }: ContextPickerProps) { const [open, setOpen] = useState(false) const triggerRef = useRef(null) const listRef = useRef(null) const [pos, setPos] = useState({ top: 0, left: 0 }) const updatePosition = useCallback(() => { if (!triggerRef.current || !listRef.current) return const t = triggerRef.current.getBoundingClientRect() const l = listRef.current.getBoundingClientRect() const margin = 8 // Right-aligned under the chip (the picker lives far right in the // toolbar), clamped to the viewport. const left = Math.max(margin, Math.min(t.right - l.width, window.innerWidth - l.width - margin)) const top = Math.min(t.bottom + 4, window.innerHeight - l.height - margin) setPos({ top, left }) }, []) useEffect(() => { if (!open) return const raf = requestAnimationFrame(() => updatePosition()) return () => cancelAnimationFrame(raf) }, [open, updatePosition]) useEffect(() => { if (!open) return function handleClick(e: MouseEvent) { const target = e.target as HTMLElement if (!target.isConnected) return if ( (!triggerRef.current || !triggerRef.current.contains(target)) && (!listRef.current || !listRef.current.contains(target)) ) { setOpen(false) } } function handleKey(e: KeyboardEvent) { if (e.key === 'Escape') setOpen(false) } document.addEventListener('mousedown', handleClick) document.addEventListener('keydown', handleKey) return () => { document.removeEventListener('mousedown', handleClick) document.removeEventListener('keydown', handleKey) } }, [open]) return ( <> {open && createPortal(
{items.map((item) => ( ))}
, document.body, )} ) }