{
  "name": "date-slot-picker",
  "title": "DateSlotPicker",
  "description": "Horizontal date strip with slot picker for service scheduling.",
  "type": "component",
  "registryDependencies": [
    "slot-picker",
    "cn"
  ],
  "files": [
    {
      "path": "date-slot-picker.tsx",
      "content": "\"use client\";\n\nimport React, { useState, useMemo, useCallback } from \"react\";\nimport { Tabs } from \"@base-ui/react/tabs\";\nimport type { AvailableSlot, DayAvailability } from \"@cimplify/sdk\";\nimport type { DurationUnit, SchedulingMode } from \"@cimplify/sdk\";\nimport type { CurrencyCode } from \"@cimplify/sdk\";\nimport { useServiceAvailability } from \"@cimplify/sdk/react\";\nimport { SlotPicker } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface DateSlotPickerClassNames {\n  root?: string;\n  dateStrip?: string;\n  dateButton?: string;\n  nav?: string;\n  navButton?: string;\n  slots?: string;\n  loading?: string;\n}\n\nexport interface DateSlotPickerProps {\n  /** Service ID to fetch availability and slots for. */\n  serviceId: string;\n  /** Number of days to show in the date strip. Default: 7. */\n  daysToShow?: number;\n  /** Number of participants. */\n  participantCount?: number;\n  /** Whole units needed — forwarded to availability so short days drop out. */\n  units?: number;\n  /** Currently selected slot. */\n  selectedSlot?: AvailableSlot | null;\n  /** Called when a slot is selected. */\n  onSlotSelect?: (slot: AvailableSlot, date: string) => void;\n  /** Pre-fetched availability data (skips fetch). */\n  availability?: DayAvailability[];\n  /** Show price on slots. Default: true. */\n  showPrice?: boolean;\n  /** Currency the slot prices are denominated in. */\n  currency?: CurrencyCode;\n  /** Forwarded to `<SlotPicker>` to render multi-day stay labels. */\n  schedulingMode?: SchedulingMode;\n  /** Forwarded to `<SlotPicker>` — unit for the stay summary in multi-day mode. */\n  durationUnit?: DurationUnit;\n  /** Forwarded to `<SlotPicker>` — value for the stay summary in multi-day mode. */\n  durationValue?: number;\n  /** Forwarded to `<SlotPicker>` — hide slots already in the past. Default: true. */\n  hideElapsedSlots?: boolean;\n  /** Forwarded to `<SlotPicker>` — minimum lead time in minutes. Default: 0. */\n  minLeadMinutes?: number;\n  /** Forwarded to `<SlotPicker>` — offered when a day has no bookable times. */\n  contactPhone?: string;\n  className?: string;\n  classNames?: DateSlotPickerClassNames;\n}\n\nfunction formatDate(dateStr: string): string {\n  const date = new Date(dateStr + \"T00:00:00\");\n  return date.toLocaleDateString(undefined, { weekday: \"short\", month: \"short\", day: \"numeric\" });\n}\n\nfunction toDateString(date: Date): string {\n  return date.toISOString().split(\"T\")[0];\n}\n\nfunction addDays(date: Date, days: number): Date {\n  const result = new Date(date);\n  result.setDate(result.getDate() + days);\n  return result;\n}\n\nexport function DateSlotPicker({\n  serviceId,\n  daysToShow = 7,\n  participantCount,\n  units,\n  selectedSlot,\n  onSlotSelect,\n  availability: availabilityProp,\n  showPrice = true,\n  currency,\n  schedulingMode,\n  durationUnit,\n  durationValue,\n  hideElapsedSlots = true,\n  minLeadMinutes = 0,\n  contactPhone,\n  className,\n  classNames,\n}: DateSlotPickerProps): React.ReactElement {\n  const [offset, setOffset] = useState(0);\n  const [selectedDate, setSelectedDate] = useState<string>(toDateString(new Date()));\n\n  const dateRange = useMemo(() => {\n    const today = new Date();\n    const start = addDays(today, offset);\n    const dates: string[] = [];\n    for (let i = 0; i < daysToShow; i++) {\n      dates.push(toDateString(addDays(start, i)));\n    }\n    return {\n      dates,\n      startDate: dates[0],\n      endDate: dates[dates.length - 1],\n    };\n  }, [offset, daysToShow]);\n\n  const { days: fetchedDays, isLoading: availabilityLoading } = useServiceAvailability(\n    serviceId,\n    dateRange.startDate,\n    dateRange.endDate,\n    {\n      participantCount,\n      units,\n      enabled: availabilityProp === undefined,\n    },\n  );\n\n  const days = availabilityProp ?? fetchedDays;\n\n  const availabilityMap = useMemo(() => {\n    const map = new Map<string, DayAvailability>();\n    for (const day of days) {\n      map.set(day.date, day);\n    }\n    return map;\n  }, [days]);\n\n  const handlePrev = useCallback(() => {\n    setOffset((prev) => Math.max(0, prev - daysToShow));\n  }, [daysToShow]);\n\n  const handleNext = useCallback(() => {\n    setOffset((prev) => prev + daysToShow);\n  }, [daysToShow]);\n\n  const handleDateChange = useCallback((value: string | number | null) => {\n    if (typeof value === \"string\") {\n      setSelectedDate(value);\n    }\n  }, []);\n\n  const handleSlotSelect = useCallback(\n    (slot: AvailableSlot) => {\n      onSlotSelect?.(slot, selectedDate);\n    },\n    [onSlotSelect, selectedDate],\n  );\n\n  return (\n    <Tabs.Root\n      value={selectedDate}\n      onValueChange={handleDateChange}\n      data-cimplify-date-slot-picker\n      className={cn(\"flex flex-col gap-4\", className, classNames?.root)}\n    >\n      <div\n        data-cimplify-date-nav\n        className={cn(\"flex items-center justify-end gap-2\", classNames?.nav)}\n      >\n        <button\n          type=\"button\"\n          onClick={handlePrev}\n          disabled={offset === 0}\n          aria-label=\"Previous dates\"\n          data-cimplify-date-nav-prev\n          className={cn(\n            \"grid place-items-center w-8 h-8 rounded-md border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-40\",\n            classNames?.navButton,\n          )}\n        >\n          &larr;\n        </button>\n        <button\n          type=\"button\"\n          onClick={handleNext}\n          aria-label=\"Next dates\"\n          data-cimplify-date-nav-next\n          className={cn(\n            \"grid place-items-center w-8 h-8 rounded-md border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\",\n            classNames?.navButton,\n          )}\n        >\n          &rarr;\n        </button>\n      </div>\n\n      <Tabs.List\n        data-cimplify-date-strip\n        className={cn(\"grid grid-cols-7 gap-1 sm:gap-2\", classNames?.dateStrip)}\n      >\n        {dateRange.dates.map((date) => {\n          const dayInfo = availabilityMap.get(date);\n          const hasAvailability = dayInfo?.has_availability !== false;\n          const isSelected = selectedDate === date;\n          return (\n            <Tabs.Tab\n              key={date}\n              value={date}\n              data-cimplify-date-button\n              data-selected={isSelected || undefined}\n              data-available={hasAvailability || undefined}\n              data-fully-booked={(!hasAvailability) || undefined}\n              className={cn(\n                \"flex flex-col items-center justify-center rounded-md border border-border bg-background px-1 py-2 text-center text-xs font-medium text-foreground transition-colors hover:border-foreground/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[selected]:border-foreground data-[selected]:bg-foreground data-[selected]:text-background data-[fully-booked]:cursor-not-allowed data-[fully-booked]:opacity-40\",\n                classNames?.dateButton,\n              )}\n            >\n              {formatDate(date)}\n            </Tabs.Tab>\n          );\n        })}\n      </Tabs.List>\n\n      {availabilityLoading && (\n        <div\n          data-cimplify-date-slot-loading\n          aria-busy=\"true\"\n          className={cn(\"h-32 rounded-md bg-muted/40 animate-pulse\", classNames?.loading)}\n        />\n      )}\n\n      <div data-cimplify-date-slots className={classNames?.slots}>\n        <SlotPicker\n          serviceId={serviceId}\n          date={selectedDate}\n          participantCount={participantCount}\n          selectedSlot={selectedSlot}\n          onSlotSelect={handleSlotSelect}\n          showPrice={showPrice}\n          currency={currency}\n          schedulingMode={schedulingMode}\n          durationUnit={durationUnit}\n          durationValue={durationValue}\n          hideElapsedSlots={hideElapsedSlots}\n          minLeadMinutes={minLeadMinutes}\n          contactPhone={contactPhone}\n        />\n      </div>\n    </Tabs.Root>\n  );\n}\n"
    }
  ]
}
