{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "p-switch-8",
  "description": "Weekly availability editor with grouped time range controls",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@coss/button",
    "@coss/checkbox",
    "@coss/checkbox-group",
    "@coss/combobox",
    "@coss/group",
    "@coss/label",
    "@coss/popover",
    "@coss/switch",
    "@coss/tooltip"
  ],
  "files": [
    {
      "path": "registry/default/particles/p-switch-8.tsx",
      "content": "\"use client\";\n\nimport {\n  ArrowRightIcon,\n  CopyIcon,\n  PlusIcon,\n  SearchIcon,\n  XIcon,\n} from \"lucide-react\";\nimport { useState } from \"react\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport { Checkbox } from \"@/registry/default/ui/checkbox\";\nimport { CheckboxGroup } from \"@/registry/default/ui/checkbox-group\";\nimport {\n  Combobox,\n  ComboboxEmpty,\n  ComboboxInput,\n  ComboboxItem,\n  ComboboxList,\n  ComboboxPopup,\n  ComboboxTrigger,\n  ComboboxValue,\n} from \"@/registry/default/ui/combobox\";\nimport { Group, GroupSeparator, GroupText } from \"@/registry/default/ui/group\";\nimport { Label } from \"@/registry/default/ui/label\";\nimport {\n  Popover,\n  PopoverPopup,\n  PopoverTrigger,\n} from \"@/registry/default/ui/popover\";\nimport { Switch } from \"@/registry/default/ui/switch\";\nimport {\n  Tooltip,\n  TooltipPopup,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/registry/default/ui/tooltip\";\n\nconst days = [\n  \"Monday\",\n  \"Tuesday\",\n  \"Wednesday\",\n  \"Thursday\",\n  \"Friday\",\n  \"Saturday\",\n  \"Sunday\",\n] as const;\n\ntype Day = (typeof days)[number];\n\ntype TimeRange = {\n  id: number;\n  start: string;\n  end: string;\n};\n\nconst timeOptions = Array.from({ length: 96 }, (_, i) => {\n  const hours = Math.floor(i / 4);\n  const minutes = (i % 4) * 15;\n  const period = hours < 12 ? \"AM\" : \"PM\";\n  const displayHours = hours % 12 === 0 ? 12 : hours % 12;\n  return `${displayHours}:${minutes.toString().padStart(2, \"0\")} ${period}`;\n});\n\nconst timeIndex = (time: string) => timeOptions.indexOf(time);\n\nlet rangeId = 0;\nconst createRange = (start: string, end: string): TimeRange => ({\n  end,\n  id: ++rangeId,\n  start,\n});\n\nconst defaultAvailability: Record<Day, TimeRange[]> = {\n  Friday: [createRange(\"9:00 AM\", \"5:00 PM\")],\n  Monday: [createRange(\"9:00 AM\", \"5:00 PM\")],\n  Saturday: [],\n  Sunday: [],\n  Thursday: [createRange(\"9:00 AM\", \"5:00 PM\")],\n  Tuesday: [\n    createRange(\"9:00 AM\", \"1:00 PM\"),\n    createRange(\"3:00 PM\", \"5:00 PM\"),\n  ],\n  Wednesday: [createRange(\"9:00 AM\", \"5:00 PM\")],\n};\n\nfunction TimeCombobox({\n  ariaLabel,\n  items,\n  onChange,\n  value,\n}: {\n  ariaLabel: string;\n  items: string[];\n  onChange: (time: string) => void;\n  value: string;\n}) {\n  return (\n    <Combobox\n      autoHighlight\n      items={items}\n      onValueChange={(time) => {\n        if (typeof time === \"string\") {\n          onChange(time);\n        }\n      }}\n      value={value}\n    >\n      <ComboboxTrigger\n        aria-label={ariaLabel}\n        render={\n          <Button\n            className=\"w-24 font-normal tabular-nums\"\n            size=\"sm\"\n            variant=\"outline\"\n          />\n        }\n      >\n        <ComboboxValue />\n      </ComboboxTrigger>\n      <ComboboxPopup aria-label={ariaLabel} className=\"min-w-44\">\n        <div className=\"border-b p-2\">\n          <ComboboxInput\n            className=\"rounded-md before:rounded-[calc(var(--radius-md)-1px)]\"\n            placeholder=\"Search time\"\n            showTrigger={false}\n            size=\"sm\"\n            startAddon={<SearchIcon />}\n          />\n        </div>\n        <ComboboxEmpty>No times found.</ComboboxEmpty>\n        <ComboboxList>\n          {(time: string) => (\n            <ComboboxItem key={time} value={time}>\n              <span className=\"tabular-nums\">{time}</span>\n            </ComboboxItem>\n          )}\n        </ComboboxList>\n      </ComboboxPopup>\n    </Combobox>\n  );\n}\n\nfunction CopyTimesPopover({\n  day,\n  disabled,\n  onCopy,\n}: {\n  day: Day;\n  disabled: boolean;\n  onCopy: (targets: Day[]) => void;\n}) {\n  const [open, setOpen] = useState(false);\n  const [selectedDays, setSelectedDays] = useState<string[]>([]);\n\n  return (\n    <Popover\n      onOpenChange={(nextOpen) => {\n        setOpen(nextOpen);\n        if (nextOpen) {\n          setSelectedDays([]);\n        }\n      }}\n      open={open}\n    >\n      <Tooltip disableHoverablePopup>\n        <PopoverTrigger\n          render={\n            <TooltipTrigger\n              render={\n                <Button\n                  aria-label={`Copy ${day} times to other days`}\n                  disabled={disabled}\n                  size=\"icon-sm\"\n                  variant=\"ghost\"\n                />\n              }\n            />\n          }\n        >\n          <CopyIcon aria-hidden=\"true\" />\n        </PopoverTrigger>\n        <TooltipPopup>Copy to other days</TooltipPopup>\n      </Tooltip>\n      <PopoverPopup align=\"end\" className=\"w-44\">\n        <div className=\"flex flex-col gap-3\">\n          <div className=\"font-medium text-foreground text-sm\">\n            Copy times to\n          </div>\n          <CheckboxGroup\n            aria-label={`Copy ${day} times to`}\n            onValueChange={setSelectedDays}\n            value={selectedDays}\n          >\n            {days\n              .filter((target) => target !== day)\n              .map((target) => (\n                <Label key={target}>\n                  <Checkbox value={target} />\n                  {target}\n                </Label>\n              ))}\n          </CheckboxGroup>\n          <Button\n            disabled={selectedDays.length === 0}\n            onClick={() => {\n              onCopy(selectedDays as Day[]);\n              setOpen(false);\n            }}\n            size=\"sm\"\n          >\n            Apply\n          </Button>\n        </div>\n      </PopoverPopup>\n    </Popover>\n  );\n}\n\nexport default function Particle() {\n  const [availability, setAvailability] =\n    useState<Record<Day, TimeRange[]>>(defaultAvailability);\n\n  const setDayRanges = (day: Day, ranges: TimeRange[]) => {\n    setAvailability((prev) => ({ ...prev, [day]: ranges }));\n  };\n\n  const toggleDay = (day: Day, enabled: boolean) => {\n    setDayRanges(day, enabled ? [createRange(\"9:00 AM\", \"5:00 PM\")] : []);\n  };\n\n  const addRange = (day: Day) => {\n    const ranges = availability[day];\n    const lastRange = ranges[ranges.length - 1];\n    if (!lastRange) {\n      setDayRanges(day, [createRange(\"9:00 AM\", \"5:00 PM\")]);\n      return;\n    }\n    const startIndex = Math.min(\n      timeIndex(lastRange.end) + 4,\n      timeOptions.length - 2,\n    );\n    const endIndex = Math.min(startIndex + 4, timeOptions.length - 1);\n    setDayRanges(day, [\n      ...ranges,\n      createRange(timeOptions[startIndex] ?? \"\", timeOptions[endIndex] ?? \"\"),\n    ]);\n  };\n\n  const removeRange = (day: Day, id: number) => {\n    setDayRanges(\n      day,\n      availability[day].filter((range) => range.id !== id),\n    );\n  };\n\n  const updateStart = (day: Day, id: number, start: string) => {\n    setDayRanges(\n      day,\n      availability[day].map((range) => {\n        if (range.id !== id) return range;\n        const end =\n          timeIndex(start) >= timeIndex(range.end)\n            ? (timeOptions[\n                Math.min(timeIndex(start) + 4, timeOptions.length - 1)\n              ] ?? range.end)\n            : range.end;\n        return { ...range, end, start };\n      }),\n    );\n  };\n\n  const updateEnd = (day: Day, id: number, end: string) => {\n    setDayRanges(\n      day,\n      availability[day].map((range) =>\n        range.id === id ? { ...range, end } : range,\n      ),\n    );\n  };\n\n  const copyTo = (source: Day, targets: Day[]) => {\n    setAvailability((prev) => {\n      const next = { ...prev };\n      for (const target of targets) {\n        next[target] = prev[source].map((range) =>\n          createRange(range.start, range.end),\n        );\n      }\n      return next;\n    });\n  };\n\n  return (\n    <TooltipProvider delay={0}>\n      <div className=\"divide-y\">\n        {days.map((day) => {\n          const ranges = availability[day];\n          const lastRange = ranges[ranges.length - 1];\n          const addDisabled = lastRange\n            ? timeIndex(lastRange.end) >= timeOptions.length - 2\n            : false;\n\n          return (\n            <div\n              className=\"flex flex-col gap-4 py-3 first:pt-0 last:pb-0 md:flex-row md:flex-wrap md:items-start\"\n              key={day}\n            >\n              <Label className=\"flex h-8 w-30 shrink-0 items-center gap-2.5 sm:h-7\">\n                <Switch\n                  checked={ranges.length > 0}\n                  onCheckedChange={(checked) => toggleDay(day, checked)}\n                />\n                {day}\n              </Label>\n              <div className=\"flex w-full min-w-0 items-start gap-4 md:flex-1\">\n                <div className=\"flex min-w-0 flex-col gap-2\">\n                  {ranges.length === 0 ? (\n                    <p className=\"flex h-8 items-center text-muted-foreground sm:h-7 sm:text-sm\">\n                      Unavailable\n                    </p>\n                  ) : (\n                    ranges.map((range) => (\n                      <div className=\"flex items-center gap-2\" key={range.id}>\n                        <Group aria-label={`${day} time range`}>\n                          <TimeCombobox\n                            ariaLabel={`${day} start time`}\n                            items={timeOptions}\n                            onChange={(start) =>\n                              updateStart(day, range.id, start)\n                            }\n                            value={range.start}\n                          />\n                          <GroupSeparator />\n                          <GroupText aria-hidden=\"true\" className=\"px-2\">\n                            <ArrowRightIcon className=\"size-3.5\" />\n                          </GroupText>\n                          <GroupSeparator />\n                          <TimeCombobox\n                            ariaLabel={`${day} end time`}\n                            items={timeOptions.slice(\n                              timeIndex(range.start) + 1,\n                            )}\n                            onChange={(end) => updateEnd(day, range.id, end)}\n                            value={range.end}\n                          />\n                        </Group>\n                        <Tooltip disableHoverablePopup>\n                          <TooltipTrigger\n                            render={\n                              <Button\n                                aria-label={`Delete ${range.start} to ${range.end} on ${day}`}\n                                onClick={() => removeRange(day, range.id)}\n                                size=\"icon-sm\"\n                                variant=\"ghost\"\n                              />\n                            }\n                          >\n                            <XIcon aria-hidden=\"true\" />\n                          </TooltipTrigger>\n                          <TooltipPopup>Delete range</TooltipPopup>\n                        </Tooltip>\n                      </div>\n                    ))\n                  )}\n                </div>\n                <div className=\"ml-auto flex shrink-0 gap-1\">\n                  <Tooltip disableHoverablePopup>\n                    <TooltipTrigger\n                      render={\n                        <Button\n                          aria-label={`Add time range to ${day}`}\n                          disabled={addDisabled}\n                          onClick={() => addRange(day)}\n                          size=\"icon-sm\"\n                          variant=\"ghost\"\n                        />\n                      }\n                    >\n                      <PlusIcon aria-hidden=\"true\" />\n                    </TooltipTrigger>\n                    <TooltipPopup>Add range</TooltipPopup>\n                  </Tooltip>\n                  <CopyTimesPopover\n                    day={day}\n                    disabled={ranges.length === 0}\n                    onCopy={(targets) => copyTo(day, targets)}\n                  />\n                </div>\n              </div>\n            </div>\n          );\n        })}\n      </div>\n    </TooltipProvider>\n  );\n}\n",
      "type": "registry:block"
    }
  ],
  "meta": {
    "className": "**:data-[slot=preview]:w-full sm:**:data-[slot=preview]:max-w-4xl",
    "colSpan": 2
  },
  "categories": [
    "switch",
    "combobox",
    "group",
    "popover",
    "time"
  ],
  "type": "registry:block"
}