{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-time-picker",
  "version": "2.1.1",
  "category": "form",
  "meta": {
    "preview": "https://ui.manifest.build/previews/date-time-picker.png",
    "version": "2.1.1",
    "changelog": {
      "1.0.0": "Initial release with calendar and time slot selection",
      "1.0.1": "Added aria-labels to navigation buttons for better screen reader accessibility",
      "1.0.3": "Added comprehensive JSDoc documentation",
      "1.0.4": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
      "2.0.0": "BREAKING: Removed onSelect action. Intermediate date/time selection is now internal.",
      "2.0.1": "Removed default content data - component only renders explicitly provided data",
      "2.0.2": "Fixed missing popover dependency for shadcn CLI installation",
      "2.0.3": "Replaced hardcoded UTC offsets with IANA timezone identifiers for correct DST handling",
      "2.1.0": "Added weekStartsOn appearance option to configure first day of the week (Sunday, Monday, or Saturday)",
      "2.1.1": "Show demo data when rendered without props"
    }
  },
  "changelog": {
    "1.0.0": "Initial release with calendar and time slot selection",
    "1.0.1": "Added aria-labels to navigation buttons for better screen reader accessibility",
    "1.0.3": "Added comprehensive JSDoc documentation",
    "1.0.4": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
    "2.0.0": "BREAKING: Removed onSelect action. Intermediate date/time selection is now internal.",
    "2.0.1": "Removed default content data - component only renders explicitly provided data",
    "2.0.2": "Fixed missing popover dependency for shadcn CLI installation",
    "2.0.3": "Replaced hardcoded UTC offsets with IANA timezone identifiers for correct DST handling",
    "2.1.0": "Added weekStartsOn appearance option to configure first day of the week (Sunday, Monday, or Saturday)",
    "2.1.1": "Show demo data when rendered without props"
  },
  "title": "Date & Time Picker",
  "author": "MNFST, Inc",
  "description": "A Calendly-style date and time picker with calendar, available time slots, and timezone display.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "popover"
  ],
  "files": [
    {
      "path": "registry/form/date-time-picker.tsx",
      "content": "'use client'\n\nimport { useState, useEffect, useRef } from 'react'\nimport { Button } from '@/components/ui/button'\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger\n} from '@/components/ui/popover'\nimport { ArrowLeft, ChevronLeft, ChevronRight, Globe, Search } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { demoDateTimePickerData } from './demo/form'\n\n/** Timezone configuration using IANA timezone identifiers for correct DST handling */\nconst timezones = [\n  { id: 'pacific', name: 'Pacific Time - US & Canada', iana: 'America/Los_Angeles' },\n  { id: 'mountain', name: 'Mountain Time - US & Canada', iana: 'America/Denver' },\n  { id: 'central', name: 'Central Time - US & Canada', iana: 'America/Chicago' },\n  { id: 'eastern', name: 'Eastern Time - US & Canada', iana: 'America/New_York' },\n  { id: 'alaska', name: 'Alaska Time', iana: 'America/Anchorage' },\n  { id: 'arizona', name: 'Arizona, Yukon Time', iana: 'America/Phoenix' },\n  { id: 'newfoundland', name: 'Newfoundland Time', iana: 'America/St_Johns' },\n  { id: 'atlantic', name: 'Atlantic Time - Canada', iana: 'America/Halifax' },\n  { id: 'london', name: 'London, Dublin, Edinburgh', iana: 'Europe/London' },\n  { id: 'paris', name: 'Paris, Berlin, Amsterdam', iana: 'Europe/Paris' },\n  { id: 'athens', name: 'Athens, Helsinki, Istanbul', iana: 'Europe/Athens' },\n  { id: 'moscow', name: 'Moscow, St. Petersburg', iana: 'Europe/Moscow' },\n  { id: 'dubai', name: 'Dubai, Abu Dhabi', iana: 'Asia/Dubai' },\n  { id: 'karachi', name: 'Karachi, Islamabad', iana: 'Asia/Karachi' },\n  { id: 'dhaka', name: 'Dhaka, Almaty', iana: 'Asia/Dhaka' },\n  { id: 'bangkok', name: 'Bangkok, Hanoi, Jakarta', iana: 'Asia/Bangkok' },\n  { id: 'singapore', name: 'Singapore, Hong Kong, Perth', iana: 'Asia/Singapore' },\n  { id: 'tokyo', name: 'Tokyo, Seoul, Osaka', iana: 'Asia/Tokyo' },\n  { id: 'sydney', name: 'Sydney, Melbourne, Brisbane', iana: 'Australia/Sydney' },\n  { id: 'auckland', name: 'Auckland, Wellington', iana: 'Pacific/Auckland' }\n]\n\nconst getTimeForTimezone = (iana: string) => {\n  try {\n    return new Intl.DateTimeFormat('en-US', {\n      timeZone: iana,\n      hour: 'numeric',\n      minute: '2-digit',\n      hour12: true,\n    }).format(new Date()).toLowerCase()\n  } catch {\n    return ''\n  }\n}\n\n/**\n * ═══════════════════════════════════════════════════════════════════════════\n * DateTimePickerProps\n * ═══════════════════════════════════════════════════════════════════════════\n *\n * Props for the DateTimePicker component with calendar view, available time\n * slots, and timezone selection.\n */\nexport interface DateTimePickerProps {\n  data?: {\n    /** Title displayed at the top of the picker. */\n    title?: string\n    /** Array of dates that can be selected. */\n    availableDates?: Date[]\n    /** Array of time slot strings (e.g., '11:30am'). */\n    availableTimeSlots?: string[]\n    /** Default timezone name to display. */\n    timezone?: string\n  }\n  actions?: {\n    /** Called when the user clicks the Next button. */\n    onNext?: (date: Date, time: string) => void\n  }\n  appearance?: {\n    /**\n     * Whether to display the title.\n     * @default true\n     */\n    showTitle?: boolean\n    /**\n     * Whether to show timezone selector.\n     * @default true\n     */\n    showTimezone?: boolean\n    /**\n     * First day of the week.\n     * - `'sunday'` — US, Canada, Japan (default)\n     * - `'monday'` — ISO 8601, Europe, most of the world\n     * - `'saturday'` — Middle East\n     * @default 'sunday'\n     */\n    weekStartsOn?: 'sunday' | 'monday' | 'saturday'\n  }\n  control?: {\n    /** Controlled selected date value. */\n    selectedDate?: Date | null\n    /** Controlled selected time value. */\n    selectedTime?: string | null\n  }\n}\n\nconst ALL_DAYS = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT']\n\nconst WEEK_START_OFFSETS: Record<'sunday' | 'monday' | 'saturday', number> = {\n  sunday: 0,\n  monday: 1,\n  saturday: 6,\n}\n\nconst getOrderedDays = (weekStartsOn: 'sunday' | 'monday' | 'saturday') => {\n  const offset = WEEK_START_OFFSETS[weekStartsOn]\n  return [...ALL_DAYS.slice(offset), ...ALL_DAYS.slice(0, offset)]\n}\nconst MONTHS = [\n  'January', 'February', 'March', 'April', 'May', 'June',\n  'July', 'August', 'September', 'October', 'November', 'December'\n]\n\n\n\nconst formatDateHeader = (date: Date) => {\n  const dayName = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()]\n  const monthName = MONTHS[date.getMonth()]\n  return `${dayName}, ${monthName} ${date.getDate()}`\n}\n\nconst isSameDay = (date1: Date, date2: Date) => {\n  return (\n    date1.getFullYear() === date2.getFullYear() &&\n    date1.getMonth() === date2.getMonth() &&\n    date1.getDate() === date2.getDate()\n  )\n}\n\n/**\n * A Calendly-style date and time picker with calendar view, available time slots,\n * and timezone selection. Supports both desktop and mobile layouts.\n *\n * Features:\n * - Monthly calendar navigation with available date highlighting\n * - Time slot selection with animated transitions\n * - Searchable timezone dropdown with current time display\n * - Responsive design with mobile-first time view\n * - Today indicator and selected date highlighting\n * - Controlled and uncontrolled modes\n *\n * @component\n * @example\n * ```tsx\n * <DateTimePicker\n *   data={{\n *     title: \"Schedule a Meeting\",\n *     availableDates: [new Date('2024-01-15'), new Date('2024-01-16')],\n *     availableTimeSlots: ['9:00am', '10:00am', '2:00pm'],\n *     timezone: \"Pacific Time - US & Canada\"\n *   }}\n *   actions={{\n *     onSelect: (date, time) => console.log(\"Selected:\", date, time),\n *     onNext: (date, time) => console.log(\"Proceeding with:\", date, time)\n *   }}\n *   appearance={{ showTitle: true, showTimezone: true }}\n * />\n * ```\n */\nexport function DateTimePicker({ data, actions, appearance, control }: DateTimePickerProps) {\n  const resolved: NonNullable<DateTimePickerProps['data']> = data ?? demoDateTimePickerData\n  const title = resolved.title\n  const availableDates = resolved.availableDates ?? []\n  const availableTimeSlots = resolved.availableTimeSlots ?? []\n  const timezone = resolved.timezone\n  const { onNext } = actions ?? {}\n  const { showTitle = true, showTimezone = true, weekStartsOn = 'sunday' } = appearance ?? {}\n  const orderedDays = getOrderedDays(weekStartsOn)\n  const weekStartOffset = WEEK_START_OFFSETS[weekStartsOn]\n  const {\n    selectedDate: controlledDate,\n    selectedTime: controlledTime\n  } = control ?? {}\n\n  const [currentMonth, setCurrentMonth] = useState(() => {\n    const now = new Date()\n    return new Date(now.getFullYear(), now.getMonth(), 1)\n  })\n  const [selectedDate, setSelectedDate] = useState<Date | null>(controlledDate ?? null)\n  const [selectedTime, setSelectedTime] = useState<string | null>(controlledTime ?? null)\n  const [selectedTimezone, setSelectedTimezone] = useState(timezones.find(tz => tz.name === timezone) || timezones[3])\n  const [timezoneSearch, setTimezoneSearch] = useState('')\n  const [timezoneDropdownOpen, setTimezoneDropdownOpen] = useState(false)\n  const timezoneSearchRef = useRef<HTMLInputElement>(null)\n  // Mobile view mode: 'calendar' or 'time'\n  const [mobileView, setMobileView] = useState<'calendar' | 'time'>('calendar')\n\n  const filteredTimezones = timezones.filter(tz =>\n    tz.name.toLowerCase().includes(timezoneSearch.toLowerCase())\n  )\n\n  useEffect(() => {\n    if (timezoneDropdownOpen && timezoneSearchRef.current) {\n      timezoneSearchRef.current.focus()\n    }\n  }, [timezoneDropdownOpen])\n\n  const handleTimezoneSelect = (tz: typeof timezones[0]) => {\n    setSelectedTimezone(tz)\n    setTimezoneDropdownOpen(false)\n    setTimezoneSearch('')\n  }\n\n  const year = currentMonth.getFullYear()\n  const month = currentMonth.getMonth()\n\n  // Calculate calendar grid\n  const firstDayOfMonth = new Date(year, month, 1).getDay()\n  const daysInMonth = new Date(year, month + 1, 0).getDate()\n  const daysInPrevMonth = new Date(year, month, 0).getDate()\n\n  const calendarDays: { day: number; isCurrentMonth: boolean; date: Date }[] = []\n\n  // Previous month days (adjusted for week start)\n  const leadingDays = (firstDayOfMonth - weekStartOffset + 7) % 7\n  for (let i = leadingDays - 1; i >= 0; i--) {\n    const day = daysInPrevMonth - i\n    calendarDays.push({\n      day,\n      isCurrentMonth: false,\n      date: new Date(year, month - 1, day)\n    })\n  }\n\n  // Current month days\n  for (let day = 1; day <= daysInMonth; day++) {\n    calendarDays.push({\n      day,\n      isCurrentMonth: true,\n      date: new Date(year, month, day)\n    })\n  }\n\n  // Next month days to fill the grid (6 rows max)\n  const totalCells = Math.ceil(calendarDays.length / 7) * 7\n  const remainingDays = totalCells - calendarDays.length\n  for (let day = 1; day <= remainingDays; day++) {\n    calendarDays.push({\n      day,\n      isCurrentMonth: false,\n      date: new Date(year, month + 1, day)\n    })\n  }\n\n  const isDateAvailable = (date: Date) => {\n    return availableDates.some(d => isSameDay(d, date))\n  }\n\n  const handlePrevMonth = () => {\n    setCurrentMonth(new Date(year, month - 1, 1))\n  }\n\n  const handleNextMonth = () => {\n    setCurrentMonth(new Date(year, month + 1, 1))\n  }\n\n  const handleDateSelect = (date: Date) => {\n    if (!isDateAvailable(date)) return\n    setSelectedDate(date)\n    setSelectedTime(null)\n    // On mobile, switch to time view when date is selected\n    setMobileView('time')\n  }\n\n  const handleBackToCalendar = () => {\n    setMobileView('calendar')\n  }\n\n  const handleTimeSelect = (time: string) => {\n    setSelectedTime(time)\n  }\n\n  const handleNext = () => {\n    if (selectedDate && selectedTime) {\n      onNext?.(selectedDate, selectedTime)\n    }\n  }\n\n  const now = new Date()\n\n  return (\n    <div className=\"w-full bg-card rounded-xl p-6\">\n      {showTitle && title && (\n        <h2 className=\"text-xl font-semibold text-foreground mb-6\">{title}</h2>\n      )}\n\n      <div className=\"flex justify-center\">\n        {/* Calendar Section - Hidden on mobile when viewing time slots */}\n        <div className={cn(\n          \"w-[304px] flex-shrink-0\",\n          mobileView === 'time' ? 'hidden md:block' : 'block'\n        )}>\n          {/* Month Navigation */}\n          <div className=\"flex items-center justify-center gap-4 mb-4\">\n            <button\n              onClick={handlePrevMonth}\n              aria-label=\"Previous month\"\n              className=\"p-1 hover:bg-muted rounded transition-colors\"\n            >\n              <ChevronLeft className=\"h-5 w-5 text-muted-foreground\" />\n            </button>\n            <span className=\"text-base font-medium text-foreground min-w-[140px] text-center\">\n              {MONTHS[month]} {year}\n            </span>\n            <button\n              onClick={handleNextMonth}\n              aria-label=\"Next month\"\n              className=\"p-1 hover:bg-muted rounded transition-colors\"\n            >\n              <ChevronRight className=\"h-5 w-5 text-muted-foreground\" />\n            </button>\n          </div>\n\n          {/* Day Headers */}\n          <div className=\"grid grid-cols-7 mb-2\">\n            {orderedDays.map(day => (\n              <div\n                key={day}\n                className=\"text-center text-xs font-medium text-muted-foreground py-2\"\n              >\n                {day}\n              </div>\n            ))}\n          </div>\n\n          {/* Calendar Grid */}\n          <div className=\"grid grid-cols-7 gap-y-1\">\n            {calendarDays.map((item, index) => {\n              const isAvailable = item.isCurrentMonth && isDateAvailable(item.date)\n              const isSelected = selectedDate && isSameDay(item.date, selectedDate)\n              const isToday = isSameDay(item.date, now)\n\n              return (\n                <button\n                  key={index}\n                  onClick={() => item.isCurrentMonth && handleDateSelect(item.date)}\n                  disabled={!item.isCurrentMonth || !isAvailable}\n                  className={cn(\n                    'relative h-10 w-10 rounded-full text-sm transition-all duration-200 flex items-center justify-center mx-auto',\n                    !item.isCurrentMonth && 'text-muted-foreground/30',\n                    item.isCurrentMonth && !isAvailable && 'text-muted-foreground cursor-default',\n                    item.isCurrentMonth && isAvailable && !isSelected && 'text-primary font-medium hover:bg-primary/10 cursor-pointer',\n                    isSelected && 'bg-primary text-primary-foreground font-medium',\n                    isAvailable && !isSelected && 'bg-primary/10'\n                  )}\n                >\n                  {item.day}\n                  {isToday && !isSelected && (\n                    <span className=\"absolute bottom-1 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-foreground\" />\n                  )}\n                </button>\n              )\n            })}\n          </div>\n\n          {/* Timezone */}\n          {showTimezone && (\n            <div className=\"mt-6\">\n              <p className=\"text-sm font-medium text-foreground mb-2\">Time zone</p>\n              <Popover open={timezoneDropdownOpen} onOpenChange={setTimezoneDropdownOpen}>\n                <PopoverTrigger asChild>\n                  <button\n                    aria-label=\"Select timezone\"\n                    className=\"flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors\"\n                  >\n                    <Globe className=\"h-4 w-4\" />\n                    <span>{selectedTimezone.name} ({getTimeForTimezone(selectedTimezone.iana)})</span>\n                    <ChevronRight className={cn(\"h-3 w-3 transition-transform\", timezoneDropdownOpen ? \"rotate-90\" : \"rotate-0\")} />\n                  </button>\n                </PopoverTrigger>\n                <PopoverContent className=\"w-[320px] p-0\" align=\"start\">\n                  <div className=\"p-2 border-b\">\n                    <div className=\"relative\">\n                      <Search className=\"absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground\" />\n                      <input\n                        ref={timezoneSearchRef}\n                        type=\"text\"\n                        placeholder=\"Search timezone...\"\n                        value={timezoneSearch}\n                        onChange={(e) => setTimezoneSearch(e.target.value)}\n                        className=\"w-full pl-8 pr-3 py-2 text-sm border rounded-md focus:outline-none focus:border-primary bg-background\"\n                      />\n                    </div>\n                  </div>\n                  <div className=\"max-h-[280px] overflow-y-auto\">\n                    {filteredTimezones.map((tz) => (\n                      <button\n                        key={tz.id}\n                        onClick={() => handleTimezoneSelect(tz)}\n                        className={cn(\n                          \"w-full px-3 py-2.5 text-left text-sm hover:bg-muted transition-colors flex items-center justify-between\",\n                          selectedTimezone.id === tz.id && \"bg-muted\"\n                        )}\n                      >\n                        <span className=\"text-foreground\">{tz.name}</span>\n                        <span className=\"text-muted-foreground text-xs\">{getTimeForTimezone(tz.iana)}</span>\n                      </button>\n                    ))}\n                    {filteredTimezones.length === 0 && (\n                      <div className=\"px-3 py-6 text-center text-sm text-muted-foreground\">\n                        No timezone found\n                      </div>\n                    )}\n                  </div>\n                </PopoverContent>\n              </Popover>\n            </div>\n          )}\n        </div>\n\n        {/* Time Slots Section - Visible on mobile when viewing times, animated on desktop */}\n        <div\n          className={cn(\n            'overflow-hidden transition-all duration-300 ease-out',\n            // Mobile: show/hide based on mobileView, full width\n            mobileView === 'time' ? 'block w-full md:w-[200px]' : 'hidden md:block',\n            // Desktop: animate width based on selectedDate\n            selectedDate ? 'md:w-[200px] md:opacity-100 md:ml-8' : 'md:w-0 md:opacity-0 md:ml-0'\n          )}\n        >\n          <div className=\"w-full md:w-[200px]\">\n            {/* Back button - Mobile only */}\n            <button\n              onClick={handleBackToCalendar}\n              className=\"flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground mb-4 md:hidden\"\n            >\n              <ArrowLeft className=\"h-4 w-4\" />\n              <span>Back to calendar</span>\n            </button>\n\n            <p className=\"text-base font-medium text-foreground mb-4 whitespace-nowrap\">\n              {selectedDate ? formatDateHeader(selectedDate) : ''}\n            </p>\n\n            <div className=\"space-y-2 max-h-[320px] overflow-y-auto\">\n              {availableTimeSlots.map((time) => {\n                const isTimeSelected = selectedTime === time\n\n                return (\n                  <div key={time} className=\"grid grid-cols-2 gap-2\">\n                    <button\n                      onClick={() => handleTimeSelect(time)}\n                      className={cn(\n                        'h-[52px] rounded-lg border text-sm font-semibold transition-all duration-200',\n                        isTimeSelected\n                          ? 'bg-muted-foreground text-background border-muted-foreground'\n                          : 'col-span-2 border-primary text-primary hover:bg-primary/5'\n                      )}\n                    >\n                      {time}\n                    </button>\n                    {isTimeSelected && (\n                      <Button\n                        onClick={handleNext}\n                        className=\"h-[52px] animate-in fade-in slide-in-from-left-2 duration-200\"\n                      >\n                        Next\n                      </Button>\n                    )}\n                  </div>\n                )\n              })}\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "components/ui/date-time-picker.tsx"
    },
    {
      "path": "registry/form/demo/form.ts",
      "content": "// Demo data for Form category components\n// This file contains sample data used for component previews and documentation\n\n/**\n * Generate available dates dynamically so the DateTimePicker preview\n * always has clickable dates regardless of when it is viewed.\n */\nfunction generateAvailableDates(): Date[] {\n  const dates: Date[] = [];\n  const now = new Date();\n  // Walk through current and next month, only keeping weekdays (Mon-Fri)\n  for (let m = 0; m <= 1; m++) {\n    for (let d = 1; d <= 28; d += 2) {\n      const date = new Date(now.getFullYear(), now.getMonth() + m, d);\n      const day = date.getDay();\n      if (day !== 0 && day !== 6) {\n        dates.push(date);\n      }\n    }\n  }\n  return dates;\n}\n\nexport const demoContactFormData = {\n  title: 'Get in Touch',\n  subtitle: \"We'd love to hear from you. Fill out the form below.\",\n  submitLabel: 'Send Message',\n}\n\nexport const demoIssueReportFormData = {\n  title: 'Report an Issue',\n  teams: ['Engineering', 'Product', 'Design', 'Marketing', 'Operations'],\n  locations: ['New York - HQ', 'San Francisco', 'London', 'Remote'],\n  categories: {\n    Software: ['Business App', 'Email', 'VPN', 'Browser', 'OS'],\n    Hardware: ['Computer', 'Monitor', 'Keyboard', 'Mouse', 'Printer'],\n    Network: ['Wi-Fi', 'Ethernet', 'VPN Access'],\n    Access: ['Account', 'Permissions', 'Password Reset'],\n  } as Record<string, string[]>,\n  impacts: [\n    { value: 'critical', label: 'Critical - Work stopped' },\n    { value: 'high', label: 'High - Major feature broken' },\n    { value: 'medium', label: 'Medium - Workaround available' },\n    { value: 'low', label: 'Low - Minor inconvenience' },\n  ],\n  urgencies: [\n    { value: 'immediate', label: 'Immediate' },\n    { value: 'today', label: 'Today' },\n    { value: 'this-week', label: 'This week' },\n    { value: 'no-rush', label: 'No rush' },\n  ],\n  frequencies: [\n    { value: 'constant', label: 'Constant' },\n    { value: 'frequent', label: 'Frequent' },\n    { value: 'occasional', label: 'Occasional' },\n    { value: 'once', label: 'Happened once' },\n  ],\n  attemptedActions: [\n    'Restarted the application',\n    'Cleared browser cache',\n    'Restarted computer',\n    'Checked internet connection',\n    'Contacted a colleague',\n  ],\n}\n\nexport const demoDateTimePickerData = {\n  title: 'Select a Date & Time',\n  availableDates: generateAvailableDates(),\n  availableTimeSlots: [\n    '9:00am',\n    '10:00am',\n    '11:30am',\n    '1:00pm',\n    '2:30pm',\n    '4:00pm',\n  ],\n  timezone: 'Eastern Time - US & Canada',\n};\n",
      "type": "registry:lib",
      "target": "components/ui/demo/form.ts"
    }
  ],
  "categories": [
    "form"
  ],
  "type": "registry:block"
}