126 lines
4.2 KiB
TypeScript
126 lines
4.2 KiB
TypeScript
"use client"
|
|
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
|
|
import { createRoot } from "react-dom/client"
|
|
import { useRef, useEffect, useState } from "react"
|
|
import { Skeleton } from "../../ui/skeleton"
|
|
|
|
interface YearSelectorProps {
|
|
availableYears?: (number | null)[]
|
|
selectedYear: number
|
|
onYearChange: (year: number) => void
|
|
isLoading?: boolean
|
|
className?: string
|
|
}
|
|
|
|
interface YearSelectorProps {
|
|
availableYears?: (number | null)[];
|
|
selectedYear: number;
|
|
onYearChange: (year: number) => void;
|
|
isLoading?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
// React component for the year selector UI
|
|
function YearSelectorUI({
|
|
availableYears = [],
|
|
selectedYear,
|
|
onYearChange,
|
|
isLoading = false,
|
|
className = "w-[120px]"
|
|
}: YearSelectorProps) {
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const [isClient, setIsClient] = useState(false);
|
|
|
|
useEffect(() => {
|
|
// This will ensure that the document is only used in the client-side context
|
|
setIsClient(true);
|
|
}, []);
|
|
|
|
// Conditionally access the document only when running on the client
|
|
const container = isClient ? document.getElementById("root") : null;
|
|
|
|
return (
|
|
<div ref={containerRef} className="mapboxgl-year-selector">
|
|
{isLoading ? (
|
|
<div className="flex items-center justify-center h-8">
|
|
<Skeleton className="h-full w-full rounded-md" />
|
|
</div>
|
|
) : (
|
|
<Select
|
|
value={selectedYear.toString()}
|
|
onValueChange={(value) => onYearChange(Number(value))}
|
|
disabled={isLoading}
|
|
>
|
|
<SelectTrigger className={className}>
|
|
<SelectValue placeholder="Year" />
|
|
</SelectTrigger>
|
|
{/* Ensure that the dropdown content renders correctly only on the client side */}
|
|
<SelectContent
|
|
container={containerRef.current || container || undefined}
|
|
style={{ zIndex: 2000 }}
|
|
className={`${className}`}
|
|
>
|
|
{availableYears
|
|
?.filter((year) => year !== null)
|
|
.map((year) => (
|
|
<SelectItem key={year} value={year!.toString()}>
|
|
{year}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Mapbox GL control class implementation
|
|
export class YearSelectorControl {
|
|
private _map: any;
|
|
private _container!: HTMLElement;
|
|
private _root: any;
|
|
private props: YearSelectorProps;
|
|
|
|
constructor(props: YearSelectorProps) {
|
|
this.props = props;
|
|
}
|
|
|
|
onAdd(map: any) {
|
|
this._map = map;
|
|
this._container = document.createElement('div');
|
|
this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';
|
|
this._container.style.padding = '5px';
|
|
|
|
// Set position to relative to keep dropdown content in context
|
|
this._container.style.position = 'relative';
|
|
// Higher z-index to ensure dropdown appears above map elements
|
|
this._container.style.zIndex = '50';
|
|
|
|
// Create React root for rendering our component
|
|
this._root = createRoot(this._container);
|
|
this._root.render(<YearSelectorUI {...this.props} />);
|
|
|
|
return this._container;
|
|
}
|
|
|
|
onRemove() {
|
|
if (this._container && this._container.parentNode) {
|
|
this._container.parentNode.removeChild(this._container);
|
|
}
|
|
|
|
// Unmount React component properly
|
|
if (this._root) {
|
|
this._root.unmount();
|
|
}
|
|
|
|
this._map = undefined;
|
|
}
|
|
}
|
|
|
|
// Export original React component as default for backward compatibility
|
|
export default function YearSelector(props: YearSelectorProps) {
|
|
// This wrapper allows the component to be used both as a React component
|
|
// and to help create a MapboxGL control
|
|
return <YearSelectorUI {...props} />;
|
|
} |