import { useHistory, useLocation } from "@docusaurus/router"; import { useEffect, useMemo, useState } from "react"; import type { DayPickerProps } from "react-day-picker"; import * as locales from "react-day-picker/locale"; const qsProps = [ "animate", "broadcastCalendar", "captionLayout", "defaultMonth", "dir", "disabled", "disableNavigation", "calendar", "firstDayOfWeek", "firstWeekContainsDate", "fixedWeeks", "fromMonth", "hideNavigation", "hideWeekdays", "ISOWeek", "locale", "max", "min", "mode", "navLayout", "numberOfMonths", "numerals", "noonSafe", "pagedNavigation", "required", "reverseMonths", "reverseYears", "selected", "showOutsideDays", "showWeekNumber", "startMonth", "endMonth", "month", "timeZone", "toMonth", "weeksStartOn", "weekStartsOn", ]; export type DayPickerPropsWithCalendar = DayPickerProps & { calendar?: "gregorian" | "persian" | "ethiopic" | "buddhist" | "hebrew"; }; export function useQueryStringSync(basePath: string = "/playground") { const history = useHistory(); const location = useLocation(); const parseQueryString = (search: string): DayPickerPropsWithCalendar => { const params = new URLSearchParams(search); const parsedProps: DayPickerPropsWithCalendar = {}; const typeMap: Record< string, "boolean" | "number" | "string" | "locale" | "date" > = { animate: "boolean", broadcastCalendar: "boolean", calendar: "string", captionLayout: "string", defaultMonth: "date", dir: "string", disabled: "string", disableNavigation: "boolean", endMonth: "date", firstDayOfWeek: "number", firstWeekContainsDate: "number", fixedWeeks: "boolean", fromMonth: "date", hideNavigation: "boolean", hideWeekdays: "boolean", ISOWeek: "boolean", locale: "locale", max: "number", min: "number", mode: "string", month: "date", navLayout: "string", numberOfMonths: "number", numerals: "string", noonSafe: "boolean", pagedNavigation: "boolean", required: "boolean", reverseMonths: "boolean", reverseYears: "boolean", selected: "string", showOutsideDays: "boolean", showWeekNumber: "boolean", startMonth: "date", timeZone: "string", toMonth: "date", weeksStartOn: "number", weekStartsOn: "number", }; qsProps.forEach((key) => { if (!params.has(key)) { return; } const value = params.get(key); try { switch (typeMap[key]) { case "boolean": parsedProps[key as keyof DayPickerPropsWithCalendar] = true; break; case "number": if (value !== null) { parsedProps[key as keyof DayPickerPropsWithCalendar] = Number(value); } break; case "string": parsedProps[key as keyof DayPickerPropsWithCalendar] = value ?? ""; break; case "locale": if (!value) break; parsedProps.locale = Object.values(locales).find( (locale) => locale.code === value, ); break; case "date": { if (!value) break; const timestamp = Number(value); const parsedDate = new Date( Number.isNaN(timestamp) ? value : timestamp, ); if (!Number.isNaN(parsedDate.getTime())) { parsedProps[key as keyof DayPickerPropsWithCalendar] = parsedDate; } break; } default: break; } } catch (error) { console.error(`Error parsing query string key "${key}":`, error); } }); return parsedProps; }; const initialProps: DayPickerProps = parseQueryString(location.search); const [props, setProps] = useState(initialProps); const updateQueryString = useMemo( () => (updatedProps: DayPickerProps) => { const qs: string[] = []; Object.entries(updatedProps) .filter(([key, value]) => !!value && qsProps.includes(key)) .forEach(([key, value]) => { if (key === "locale") { if (!value) return; qs.push(`locale=${value.code}`); } else if (value instanceof Date) { qs.push(`${key}=${value.getTime()}`); } else { qs.push(`${key}${value === true ? "" : `=${value}`}`); } }); const newQueryString = qs.length === 0 ? "" : `?${qs.join("&")}`; if (location.search !== newQueryString) { history.replace(basePath + newQueryString); } }, [history, location.search, basePath], ); useEffect(() => { updateQueryString(props); }, [props, updateQueryString]); return { props, setProps }; }