diff --git a/website/src/components/demo/AppControls.tsx b/website/src/components/demo/AppControls.tsx index 5e6799d..0f21679 100644 --- a/website/src/components/demo/AppControls.tsx +++ b/website/src/components/demo/AppControls.tsx @@ -23,6 +23,7 @@ import { SelectTrigger, SelectValue, } from "../ui/select"; +import { ControlInfo } from "./control-docs"; export const TIMEZONES = [ "America/New_York", @@ -167,14 +168,21 @@ const toInputValue = (zdt: Temporal.ZonedDateTime) => function SingleCheckbox({ children, + info, ...props -}: ComponentProps & { children: React.ReactNode }) { +}: ComponentProps & { + children: React.ReactNode; + info?: React.ReactNode; +}) { return ( - - - {children} - +
+ + + {children} + + {info} +
); } @@ -280,7 +288,10 @@ export function AppControls(props: AppControlsProps) { {selectionMode === "range" && ( - rangeMode +
+ rangeMode + +
- locale +
+ locale + +
- min +
+ min + +
- max +
+ max + +
setDisabled(checked)} + info={} > disabled @@ -426,6 +454,7 @@ export function AppControls(props: AppControlsProps) { setReadOnly(checked)} + info={} > readOnly @@ -439,7 +468,10 @@ export function AppControls(props: AppControlsProps) { /> } > - isDateDisabled +
+ isDateDisabled + +
None (default) Weekends Past dates @@ -458,9 +490,12 @@ export function AppControls(props: AppControlsProps) {
- - numberOfMonths - +
+ + numberOfMonths + + +
- outsideDays +
+ outsideDays + +
value={outsideDays} onValueChange={(v) => setOutsideDays(v as OutsideDays)} @@ -499,10 +537,14 @@ export function AppControls(props: AppControlsProps) { onCheckedChange={(checked) => setFixedWeeks(checked)} /> fixedWeeks +
- outOfRangeBehavior +
+ outOfRangeBehavior + +
@@ -526,7 +568,10 @@ export function AppControls(props: AppControlsProps) {
- weekCount +
+ weekCount + +
setAutoFocus(checked)} /> Grid.autoFocus +
@@ -626,7 +678,10 @@ export function AppControls(props: AppControlsProps) { )} - Grid.orientation +
+ Grid.orientation + +
diff --git a/website/src/components/demo/control-docs.tsx b/website/src/components/demo/control-docs.tsx new file mode 100644 index 0000000..4f522b8 --- /dev/null +++ b/website/src/components/demo/control-docs.tsx @@ -0,0 +1,142 @@ +import { createContext, useContext, useMemo } from "react"; +import InlineDescription from "#/components/InlineDescription"; +import { Code } from "#/components/ui/code"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "#/components/ui/tooltip"; +import type { ApiData } from "#/lib/api-data"; + +/** + * Maps each demo control to the library symbol + property whose TSDoc documents + * it. The tooltip text is pulled live from the generated API data + * (`api-data/symbols.gen.json`), so it always mirrors the source TSDoc. + * + * `rangeMode`/`preventRangeReversal` are only documented on the stable context + * value (they live on a selection-mode union, not the base provider props), so + * they reference `CalendarStableContextValue`. + */ +const CONTROL_DOC_MAP = { + rangeMode: { symbol: "CalendarStableContextValue", prop: "rangeMode" }, + preventRangeReversal: { + symbol: "CalendarStableContextValue", + prop: "preventRangeReversal", + }, + timeZone: { symbol: "CalendarProviderProps", prop: "timeZone" }, + locale: { symbol: "CalendarProviderProps", prop: "locale" }, + weekStartDay: { symbol: "CalendarProviderProps", prop: "weekStartDay" }, + min: { symbol: "CalendarProviderProps", prop: "min" }, + max: { symbol: "CalendarProviderProps", prop: "max" }, + disabled: { symbol: "CalendarProviderProps", prop: "disabled" }, + readOnly: { symbol: "CalendarProviderProps", prop: "readOnly" }, + isDateDisabled: { symbol: "CalendarProviderProps", prop: "isDateDisabled" }, + numberOfMonths: { symbol: "MonthViewRootProps", prop: "numberOfMonths" }, + outsideDays: { symbol: "MonthViewRootProps", prop: "outsideDays" }, + fixedWeeks: { symbol: "MonthViewRootProps", prop: "fixedWeeks" }, + monthOutOfRangeBehavior: { + symbol: "MonthViewRootProps", + prop: "outOfRangeBehavior", + }, + weekCount: { symbol: "WeeksViewRootProps", prop: "weekCount" }, + scrollBy: { symbol: "WeeksViewRootProps", prop: "scrollBy" }, + weeksOutOfRangeBehavior: { + symbol: "WeeksViewRootProps", + prop: "outOfRangeBehavior", + }, + autoFocus: { symbol: "GridProps", prop: "autoFocus" }, + orientation: { symbol: "GridProps", prop: "orientation" }, +} as const satisfies Record; + +export type ControlKey = keyof typeof CONTROL_DOC_MAP; + +interface ResolvedDoc { + description: string; + defaultValue?: string; +} + +const ApiDataContext = createContext(null); + +/** Provides the generated API data to descendant {@link ControlInfo} icons. */ +export function ControlDocsProvider({ + apiData, + children, +}: { + apiData: ApiData; + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} + +function useControlDoc(control: ControlKey): ResolvedDoc | null { + const apiData = useContext(ApiDataContext); + return useMemo(() => { + if (!apiData) return null; + const ref = CONTROL_DOC_MAP[control]; + const symbol = apiData.symbols.find((s) => s.name === ref.symbol); + const prop = symbol?.properties?.find((p) => p.name === ref.prop); + if (!prop?.description.trim()) return null; + return { description: prop.description, defaultValue: prop.defaultValue }; + }, [apiData, control]); +} + +const infoIcon = ( + +); + +/** + * Info icon rendered next to a demo control label. On hover/focus it reveals the + * library's TSDoc for the mapped prop. Renders nothing when the prop has no + * extracted documentation, so undocumented/demo-only controls stay icon-free. + */ +export function ControlInfo({ control }: { control: ControlKey }) { + const doc = useControlDoc(control); + if (!doc) return null; + + const { prop } = CONTROL_DOC_MAP[control]; + + return ( + + + {infoIcon} + + } + /> + +
+ + {doc.defaultValue && ( +
+ Default: {doc.defaultValue} +
+ )} +
+
+
+ ); +} diff --git a/website/src/routes/demo.tsx b/website/src/routes/demo.tsx index bcbf473..8bb2ef8 100644 --- a/website/src/routes/demo.tsx +++ b/website/src/routes/demo.tsx @@ -13,15 +13,31 @@ import { TIMEZONES, formatTzLabel, } from "#/components/demo/AppControls"; +import { ControlDocsProvider } from "#/components/demo/control-docs"; +import { TooltipProvider } from "#/components/ui/tooltip"; import { StyledMonthView } from "#/examples/styled-month-view"; import { StyledWeeksView } from "#/examples/styled-weeks-view"; +import type { ApiData } from "#/lib/api-data"; -export const Route = createFileRoute("/demo")({ component: DemoPage }); +export const Route = createFileRoute("/demo")({ + loader: async () => { + // Same generated API data the docs site uses; powers the control tooltips. + const apiData = (await import("../../api-data/symbols.gen.json")) + .default as ApiData; + return { apiData }; + }, + component: DemoPage, +}); function DemoPage() { + const { apiData } = Route.useLoaderData(); return (
- + + + + +
); }