{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"date-range-picker","type":"registry:component","title":"Date Range Picker","description":"Composable date range picker with root, trigger, popover content, calendar, header, grid, summary, footer, clear and preset parts, plus a shared state hook. Compact inline and dropdown compositions, direct month and year selection, and full keyboard navigation.","author":"Saurabh <saurabh10102@gmail.com>","dependencies":["clsx","lucide-react","motion","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/motion/date-range-picker.tsx","type":"registry:component","target":"@components/motion/date-range-picker.tsx","content":"\"use client\";\n// beui.dev/components/motion/date-range-picker\n\nimport {\n  ArrowRight,\n  CalendarDays,\n  ChevronDown,\n  ChevronLeft,\n  ChevronRight,\n} from \"lucide-react\";\nimport {\n  AnimatePresence,\n  type HTMLMotionProps,\n  motion,\n  useIsPresent,\n} from \"motion/react\";\nimport {\n  type ComponentPropsWithRef,\n  type ComponentPropsWithoutRef,\n  type KeyboardEvent,\n  type ReactNode,\n  useCallback,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { EASE_OUT, SPRING_LAYOUT, SPRING_PRESS } from \"@/lib/ease\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  MorphPopover,\n  MorphPopoverContent,\n  type MorphPopoverContentProps,\n  MorphPopoverTrigger,\n} from \"@/components/motion/popover-morph\";\nimport {\n  DateRangePickerContext,\n  useDateRangePickerContext,\n} from \"./date-range-picker/context\";\nimport { parse } from \"./date-range-picker/date-utils\";\nimport { useDateRangePickerController } from \"./date-range-picker/use-date-range-picker\";\nimport type {\n  DateRange,\n  DateRangePickerProps,\n} from \"./date-range-picker/types\";\nexport type {\n  DateRange,\n  DateRangePreset,\n  DateRangePickerProps,\n} from \"./date-range-picker/types\";\n\nconst monthVariants = {\n  enter: ({ direction, reduce }: { direction: number; reduce: boolean }) => ({\n    opacity: 0,\n    x: reduce ? 0 : direction * 12,\n  }),\n  exit: ({ direction, reduce }: { direction: number; reduce: boolean }) => ({\n    opacity: 0,\n    x: reduce ? 0 : direction * -8,\n    transition: { duration: 0.12, ease: EASE_OUT },\n  }),\n};\n\nconst iconClass =\n  \"inline-flex size-8 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-30\";\n\n/** State shared by built-in parts and custom date range controls. */\nexport function useDateRangePicker() {\n  const ctx = useDateRangePickerContext(\"useDateRangePicker\");\n  return {\n    value: ctx.value,\n    setValue: ctx.change,\n    open: ctx.open,\n    setOpen: ctx.setOpen,\n    month: ctx.month,\n    view: ctx.view,\n    label: ctx.label,\n    locale: ctx.locale,\n    disabled: ctx.disabled,\n    selectDate: ctx.select,\n    clear: ctx.clear,\n    goToMonth: ctx.goToMonth,\n    previous: () => !ctx.previousDisabled && ctx.page(-1),\n    next: () => !ctx.nextDisabled && ctx.page(1),\n    previousDisabled: ctx.previousDisabled,\n    nextDisabled: ctx.nextDisabled,\n    chooseMonth: () => ctx.openChoices(\"months\"),\n    chooseYear: () => ctx.openChoices(\"years\"),\n  };\n}\n\n/** Range state and popover root. Without children, renders the inline calendar. */\nexport function DateRangePicker({\n  children,\n  className,\n  ...props\n}: DateRangePickerProps) {\n  const ctx = useDateRangePickerController(props);\n  return (\n    <DateRangePickerContext.Provider value={ctx}>\n      <MorphPopover\n        open={ctx.open}\n        onOpenChange={ctx.setOpen}\n        className={cn(\"max-w-full\", children !== undefined && className)}\n      >\n        {children === undefined ? (\n          <DateRangePickerCalendar className={className} />\n        ) : (\n          children\n        )}\n      </MorphPopover>\n    </DateRangePickerContext.Provider>\n  );\n}\n\nexport interface DateRangePickerCalendarProps\n  extends ComponentPropsWithoutRef<\"section\"> {\n  showSummary?: boolean;\n}\n\n/** Calendar surface; arrange or omit its summary, header, grid and footer. */\nexport function DateRangePickerCalendar({\n  children,\n  className,\n  showSummary,\n  ...props\n}: DateRangePickerCalendarProps) {\n  const ctx = useDateRangePickerContext(\"DateRangePickerCalendar\");\n  return (\n    <section\n      {...props}\n      aria-label={props[\"aria-label\"] ?? ctx.label}\n      className={cn(\n        \"w-80 max-w-full rounded-2xl border border-border bg-background p-3.5\",\n        className,\n      )}\n    >\n      <h3 id={ctx.heading} aria-live=\"polite\" className=\"sr-only\">\n        {ctx.monthFormat.format(parse(ctx.month))}\n      </h3>\n      {children === undefined ? (\n        <>\n          {(showSummary ?? ctx.showSummary) && <DateRangePickerSummary />}\n          <DateRangePickerHeader />\n          <DateRangePickerGrid />\n          <DateRangePickerFooter />\n        </>\n      ) : (\n        children\n      )}\n      <p id={ctx.help} className=\"sr-only\">\n        Use arrow keys to navigate dates, Home and End for the week, Page Up and\n        Page Down for months, Shift with Page Up or Page Down for years. Enter\n        or Space selects a date. Use the month and year buttons to jump\n        directly. In the month or year choices, use arrow keys to move and\n        Escape to return.\n      </p>\n      <span role=\"status\" className=\"sr-only\">\n        {ctx.announcement}\n      </span>\n    </section>\n  );\n}\n\nexport type DateRangePickerSummaryProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  \"children\"\n>;\nexport function DateRangePickerSummary({\n  className,\n  ...props\n}: DateRangePickerSummaryProps) {\n  const { value, shortFormat } = useDateRangePickerContext(\n    \"DateRangePickerSummary\",\n  );\n  return (\n    <div\n      {...props}\n      className={cn(\n        \"mb-2.5 grid grid-cols-[1fr_auto_1fr] items-center gap-2 rounded-lg border border-border bg-muted/30 px-3 py-2\",\n        className,\n      )}\n    >\n      <div className=\"min-w-0\">\n        <p className=\"mb-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground\">\n          Start date\n        </p>\n        <p className=\"text-[13px] font-medium tabular-nums\">\n          {value ? shortFormat.format(parse(value.from)) : \"Select date\"}\n        </p>\n      </div>\n      <ArrowRight\n        size={14}\n        className=\"text-muted-foreground/60\"\n        aria-hidden=\"true\"\n      />\n      <div className=\"min-w-0\">\n        <p className=\"mb-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground\">\n          End date\n        </p>\n        <p\n          className={cn(\n            \"text-[13px] font-medium tabular-nums\",\n            !value?.to && \"text-muted-foreground\",\n          )}\n        >\n          {value?.to ? shortFormat.format(parse(value.to)) : \"Select date\"}\n        </p>\n      </div>\n    </div>\n  );\n}\n\nexport type DateRangePickerHeaderProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  \"children\"\n>;\nexport function DateRangePickerHeader({\n  className,\n  ...props\n}: DateRangePickerHeaderProps) {\n  const {\n    monthTrigger,\n    yearTrigger,\n    view,\n    choicesId,\n    disabled,\n    openChoices,\n    monthNameFormat,\n    month,\n    yearPage,\n    yearPageEnd,\n    year,\n    previousDisabled,\n    nextDisabled,\n    page,\n  } = useDateRangePickerContext(\"DateRangePickerHeader\");\n  return (\n    <div\n      {...props}\n      className={cn(\"mb-2 flex items-center justify-between gap-1\", className)}\n    >\n      <div className=\"flex min-w-0 items-center gap-0.5\">\n        <button\n          ref={monthTrigger}\n          type=\"button\"\n          aria-label=\"Choose month\"\n          aria-expanded={view === \"months\"}\n          aria-controls={view === \"months\" ? choicesId : undefined}\n          disabled={disabled}\n          onClick={() => openChoices(\"months\")}\n          className={cn(\n            \"inline-flex items-center gap-1.5 rounded-lg px-2 py-1.5 text-[13px] font-semibold transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40\",\n            view === \"years\" && \"hidden\",\n          )}\n        >\n          {monthNameFormat.format(parse(month))}\n          <ChevronDown\n            size={12}\n            aria-hidden=\"true\"\n            className={cn(\n              \"text-muted-foreground\",\n              view === \"months\" && \"rotate-180\",\n            )}\n          />\n        </button>\n        <button\n          ref={yearTrigger}\n          type=\"button\"\n          aria-label=\"Choose year\"\n          aria-expanded={view === \"years\"}\n          aria-controls={view === \"years\" ? choicesId : undefined}\n          disabled={disabled}\n          onClick={() => openChoices(\"years\")}\n          className=\"inline-flex items-center gap-1.5 rounded-lg px-2 py-1.5 text-[13px] font-medium tabular-nums text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40\"\n        >\n          {view === \"years\" ? `${yearPage}–${yearPageEnd}` : year}\n          <ChevronDown\n            size={12}\n            aria-hidden=\"true\"\n            className={cn(view === \"years\" && \"rotate-180\")}\n          />\n        </button>\n      </div>\n      <div className=\"flex shrink-0 gap-0.5\">\n        <button\n          type=\"button\"\n          aria-label={\n            view === \"years\"\n              ? \"Previous years\"\n              : view === \"months\"\n                ? \"Previous year\"\n                : \"Previous month\"\n          }\n          disabled={previousDisabled}\n          className={iconClass}\n          onClick={() => page(-1)}\n        >\n          <ChevronLeft size={16} aria-hidden=\"true\" />\n        </button>\n        <button\n          type=\"button\"\n          aria-label={\n            view === \"years\"\n              ? \"Next years\"\n              : view === \"months\"\n                ? \"Next year\"\n                : \"Next month\"\n          }\n          disabled={nextDisabled}\n          hidden={view === \"years\" && nextDisabled}\n          className={cn(\n            iconClass,\n            view === \"years\" && nextDisabled && \"hidden\",\n          )}\n          onClick={() => page(1)}\n        >\n          <ChevronRight size={16} aria-hidden=\"true\" />\n        </button>\n      </div>\n    </div>\n  );\n}\n\nexport type DateRangePickerGridProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  \"children\"\n>;\nexport function DateRangePickerGrid({\n  className,\n  onPointerLeave,\n  ...props\n}: DateRangePickerGridProps) {\n  const {\n    setPreview,\n    view,\n    month,\n    yearPage,\n    yearPageEnd,\n    year,\n    direction,\n    reduce,\n    choicesId,\n    choices,\n    focusChoices,\n    closeChoices,\n    goToMonth,\n    grid,\n    heading,\n    help,\n    active,\n    locale,\n    weeks,\n    shownRange,\n    value,\n    canHover,\n    unavailable,\n    dateFormat,\n    today,\n    select,\n    setCursor,\n    onKey,\n  } = useDateRangePickerContext(\"DateRangePickerGrid\");\n  return (\n    <div\n      {...props}\n      className={cn(\"overflow-hidden\", className)}\n      onPointerLeave={(event) => {\n        onPointerLeave?.(event);\n        setPreview(null);\n      }}\n    >\n      <AnimatePresence\n        initial={false}\n        mode=\"popLayout\"\n        custom={{ direction, reduce }}\n      >\n        <motion.div\n          key={\n            view === \"days\"\n              ? month\n              : `${view}-${view === \"years\" ? yearPage : year}`\n          }\n          custom={{ direction, reduce }}\n          variants={monthVariants}\n          initial=\"enter\"\n          animate={{ opacity: 1, x: 0 }}\n          exit=\"exit\"\n          transition={{ duration: reduce ? 0.1 : 0.18, ease: EASE_OUT }}\n        >\n          <CalendarPresence>\n            {view !== \"days\" ? (\n              <CalendarChoices\n                id={choicesId}\n                label={\n                  view === \"months\"\n                    ? `Choose a month in ${year}`\n                    : `Choose a year from ${yearPage} to ${yearPageEnd}`\n                }\n                choices={choices}\n                selected={view === \"months\" ? month : String(year)}\n                focusOnMount={focusChoices}\n                reduce={reduce}\n                onClose={closeChoices}\n                onSelect={(id) =>\n                  goToMonth(\n                    view === \"months\"\n                      ? id\n                      : `${id.padStart(4, \"0\")}-${month.slice(5, 7)}-01`,\n                  )\n                }\n              />\n            ) : (\n              <table\n                ref={(node) => {\n                  if (node) {\n                    grid.current = node;\n                    return () => {\n                      if (grid.current === node) grid.current = null;\n                    };\n                  }\n                }}\n                // biome-ignore lint/a11y/noNoninteractiveElementToInteractiveRole: WAI calendar grids retain native table structure with an interactive grid role.\n                role=\"grid\"\n                aria-labelledby={heading}\n                aria-describedby={help}\n                tabIndex={active === null ? 0 : undefined}\n                className=\"w-full table-fixed select-none border-separate border-spacing-x-0 border-spacing-y-1\"\n              >\n                <thead>\n                  <tr>\n                    {Array.from({ length: 7 }, (_, index) => {\n                      const day = new Intl.DateTimeFormat(locale, {\n                        weekday: \"long\",\n                        timeZone: \"UTC\",\n                      }).format(Date.UTC(2026, 0, 4 + index));\n                      return (\n                        <th\n                          key={day}\n                          scope=\"col\"\n                          abbr={day}\n                          className=\"h-6 pb-1.5 text-center text-[11px] font-medium text-muted-foreground\"\n                        >\n                          {new Intl.DateTimeFormat(locale, {\n                            weekday: \"short\",\n                            timeZone: \"UTC\",\n                          }).format(Date.UTC(2026, 0, 4 + index))}\n                        </th>\n                      );\n                    })}\n                  </tr>\n                </thead>\n                <tbody>\n                  {weeks.map(({ start, dates: week }) => (\n                    <tr key={start}>\n                      {week.map((date, dayIndex) => {\n                        const inRange = Boolean(\n                          date &&\n                            shownRange &&\n                            date >= shownRange.from &&\n                            date <= (shownRange.to ?? shownRange.from),\n                        );\n                        const endpoint = Boolean(\n                          date &&\n                            value &&\n                            (date === value.from || date === value.to),\n                        );\n                        return (\n                          // biome-ignore lint/a11y/useFocusableInteractive: Calendar cells delegate focus to the contained day button.\n                          <td\n                            // biome-ignore lint/a11y/noNoninteractiveElementToInteractiveRole: Gridcell selection semantics belong on the native table cell.\n                            role=\"gridcell\"\n                            key={date ?? `empty-${dayIndex}`}\n                            aria-selected={\n                              date\n                                ? Boolean(\n                                    value &&\n                                      date >= value.from &&\n                                      date <= (value.to ?? value.from),\n                                  )\n                                : undefined\n                            }\n                            onPointerEnter={(event) => {\n                              if (\n                                canHover &&\n                                event.pointerType !== \"touch\" &&\n                                date &&\n                                !unavailable(date) &&\n                                value &&\n                                !value.to\n                              )\n                                setPreview(date);\n                            }}\n                            className={cn(\n                              \"relative h-8 p-0 text-center\",\n                              inRange &&\n                                (dayIndex === 0 || date === shownRange?.from) &&\n                                \"rounded-l-full\",\n                              inRange &&\n                                (dayIndex === 6 ||\n                                  date === shownRange?.to ||\n                                  !shownRange?.to) &&\n                                \"rounded-r-full\",\n                            )}\n                          >\n                            {date && (\n                              <span\n                                aria-hidden=\"true\"\n                                // Range scrubbing follows immediately: independent cell\n                                // fades leave stale highlights behind a moving pointer.\n                                className={cn(\n                                  \"pointer-events-none absolute inset-y-0 rounded-[inherit] bg-foreground/[0.08]\",\n                                  inRange ? \"opacity-100\" : \"opacity-0\",\n                                )}\n                                style={{\n                                  left:\n                                    endpoint &&\n                                    (date === shownRange?.from ||\n                                      !week[dayIndex - 1])\n                                      ? \"50%\"\n                                      : 0,\n                                  right:\n                                    endpoint &&\n                                    (date ===\n                                      (shownRange?.to ?? shownRange?.from) ||\n                                      !week[dayIndex + 1])\n                                      ? \"50%\"\n                                      : 0,\n                                }}\n                              />\n                            )}\n                            {date && (\n                              <motion.button\n                                type=\"button\"\n                                disabled={unavailable(date)}\n                                aria-label={dateFormat.format(parse(date))}\n                                aria-current={\n                                  date === today ? \"date\" : undefined\n                                }\n                                tabIndex={date === active ? 0 : -1}\n                                whileTap={reduce ? undefined : { scale: 0.9 }}\n                                transition={SPRING_PRESS}\n                                onClick={() => select(date)}\n                                onFocus={() => {\n                                  setCursor(date);\n                                  if (value && !value.to) setPreview(date);\n                                }}\n                                onKeyDown={(event) => onKey(event, date)}\n                                className={cn(\n                                  \"relative mx-auto flex h-8 w-full max-w-8 items-center justify-center rounded-full text-xs tabular-nums focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-30\",\n                                  endpoint\n                                    ? \"bg-foreground font-medium text-background\"\n                                    : canHover &&\n                                        !unavailable(date) &&\n                                        !inRange &&\n                                        \"hover:bg-muted\",\n                                  date === today &&\n                                    !endpoint &&\n                                    \"font-semibold after:absolute after:bottom-1 after:size-0.75 after:rounded-full after:bg-current\",\n                                )}\n                              >\n                                {new Date(parse(date)).getUTCDate()}\n                              </motion.button>\n                            )}\n                          </td>\n                        );\n                      })}\n                    </tr>\n                  ))}\n                </tbody>\n              </table>\n            )}\n          </CalendarPresence>\n        </motion.div>\n      </AnimatePresence>\n    </div>\n  );\n}\n\nexport type DateRangePickerSelectionProps = ComponentPropsWithoutRef<\"p\">;\nexport function DateRangePickerSelection({\n  children,\n  className,\n  ...props\n}: DateRangePickerSelectionProps) {\n  const { value, dayCount } = useDateRangePickerContext(\n    \"DateRangePickerSelection\",\n  );\n  return (\n    <p {...props} className={cn(\"text-xs text-muted-foreground\", className)}>\n      {children ??\n        (dayCount\n          ? `${dayCount} ${dayCount === 1 ? \"day\" : \"days\"} selected`\n          : value\n            ? \"Choose an end date\"\n            : \"Choose a start date\")}\n    </p>\n  );\n}\n\nexport type DateRangePickerClearProps = ComponentPropsWithRef<\"button\">;\nexport function DateRangePickerClear({\n  children = \"Clear\",\n  className,\n  onClick,\n  disabled,\n  ...props\n}: DateRangePickerClearProps) {\n  const ctx = useDateRangePickerContext(\"DateRangePickerClear\");\n  return (\n    <button\n      {...props}\n      type=\"button\"\n      aria-label={props[\"aria-label\"] ?? \"Clear date range\"}\n      disabled={disabled || ctx.disabled || !ctx.value}\n      className={cn(\n        \"rounded-md px-1.5 py-1 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-30\",\n        className,\n      )}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) ctx.clear();\n      }}\n    >\n      {children}\n    </button>\n  );\n}\n\nexport type DateRangePickerFooterProps = ComponentPropsWithoutRef<\"div\">;\nexport function DateRangePickerFooter({\n  children,\n  className,\n  ...props\n}: DateRangePickerFooterProps) {\n  return (\n    <div\n      {...props}\n      className={cn(\"mt-2.5 border-t border-border pt-2.5\", className)}\n    >\n      {children === undefined ? (\n        <>\n          <div className=\"flex items-center justify-between gap-2\">\n            <DateRangePickerSelection />\n            <DateRangePickerClear />\n          </div>\n          <DateRangePickerPresets />\n        </>\n      ) : (\n        children\n      )}\n    </div>\n  );\n}\n\nexport type DateRangePickerPresetsProps = ComponentPropsWithoutRef<\"div\">;\nexport function DateRangePickerPresets({\n  children,\n  className,\n  ...props\n}: DateRangePickerPresetsProps) {\n  const ctx = useDateRangePickerContext(\"DateRangePickerPresets\");\n  if (children === undefined && ctx.presets.length === 0) return null;\n  return (\n    <div\n      {...props}\n      className={cn(\"mt-2.5 flex flex-wrap justify-center gap-1.5\", className)}\n    >\n      {children === undefined\n        ? ctx.presets.map((preset) => (\n            <DateRangePickerPreset\n              key={preset.label}\n              value={preset.value}\n              label={preset.label}\n            >\n              {preset.label}\n            </DateRangePickerPreset>\n          ))\n        : children}\n    </div>\n  );\n}\n\nexport interface DateRangePickerPresetProps\n  extends Omit<ComponentPropsWithRef<\"button\">, \"value\"> {\n  value: DateRange;\n  /** Accessible preset name and selection announcement. */\n  label: string;\n}\nexport function DateRangePickerPreset({\n  value,\n  label,\n  children,\n  className,\n  disabled,\n  onClick,\n  ...props\n}: DateRangePickerPresetProps) {\n  const ctx = useDateRangePickerContext(\"DateRangePickerPreset\");\n  const selected = ctx.value?.from === value.from && ctx.value?.to === value.to;\n  return (\n    <button\n      {...props}\n      type=\"button\"\n      aria-label={label}\n      aria-pressed={selected}\n      disabled={disabled || !ctx.rangeAvailable(value)}\n      className={cn(\n        \"rounded-lg px-3 py-1.5 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40\",\n        selected\n          ? \"bg-foreground text-background\"\n          : \"bg-muted/60 text-muted-foreground hover:bg-muted hover:text-foreground\",\n        className,\n      )}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) ctx.selectPreset(value, label);\n      }}\n    >\n      {children ?? label}\n    </button>\n  );\n}\n\nexport interface DateRangePickerTriggerProps\n  extends HTMLMotionProps<\"button\"> {\n  placeholder?: string;\n}\nexport function DateRangePickerTrigger({\n  children,\n  placeholder = \"Select dates\",\n  className,\n  disabled,\n  ref,\n  ...props\n}: DateRangePickerTriggerProps) {\n  const ctx = useDateRangePickerContext(\"DateRangePickerTrigger\");\n  const mergedRef = useCallback(\n    (node: HTMLButtonElement | null) => {\n      ctx.trigger.current = node;\n      if (typeof ref === \"function\") ref(node);\n      else if (ref) ref.current = node;\n    },\n    [ctx.trigger, ref],\n  );\n  const format = new Intl.DateTimeFormat(ctx.locale, {\n    month: \"short\",\n    day: \"numeric\",\n    timeZone: \"UTC\",\n  });\n  const sameYear =\n    ctx.value?.to && ctx.value.from.slice(0, 4) === ctx.value.to.slice(0, 4);\n  const summary = ctx.value\n    ? `${(sameYear ? format : ctx.shortFormat).format(parse(ctx.value.from))} – ${ctx.value.to ? ctx.shortFormat.format(parse(ctx.value.to)) : \"Select end\"}`\n    : placeholder;\n  return (\n    <MorphPopoverTrigger>\n      <motion.button\n        {...props}\n        ref={mergedRef}\n        layout={!ctx.reduce}\n        transition={{ layout: SPRING_LAYOUT }}\n        style={{ borderRadius: 12, ...props.style }}\n        type=\"button\"\n        disabled={disabled || ctx.disabled}\n        aria-label={props[\"aria-label\"] ?? `${ctx.label}: ${summary}`}\n        className={cn(\n          \"inline-flex h-9 w-fit max-w-full items-center gap-2 rounded-xl border border-border bg-background px-3 text-[13px] font-medium text-foreground transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40\",\n          className,\n        )}\n      >\n        {children != null ? (\n          <motion.span\n            layout={ctx.reduce ? false : \"position\"}\n            transition={{ layout: SPRING_LAYOUT }}\n            className=\"inline-flex min-w-0 items-center gap-2\"\n          >\n            {children}\n          </motion.span>\n        ) : (\n          <>\n            <motion.span\n              layout={ctx.reduce ? false : \"position\"}\n              transition={{ layout: SPRING_LAYOUT }}\n              className=\"shrink-0 text-muted-foreground\"\n              aria-hidden=\"true\"\n            >\n              <CalendarDays size={15} />\n            </motion.span>\n            <motion.span\n              layout={ctx.reduce ? false : \"position\"}\n              transition={{ layout: SPRING_LAYOUT }}\n              className=\"min-w-0 truncate tabular-nums\"\n            >\n              {summary}\n            </motion.span>\n            <motion.span\n              layout={ctx.reduce ? false : \"position\"}\n              transition={{ layout: SPRING_LAYOUT }}\n              className=\"shrink-0 text-muted-foreground\"\n              aria-hidden=\"true\"\n            >\n              <ChevronDown size={13} className={cn(ctx.open && \"rotate-180\")} />\n            </motion.span>\n          </>\n        )}\n      </motion.button>\n    </MorphPopoverTrigger>\n  );\n}\n\nexport interface DateRangePickerContentProps extends MorphPopoverContentProps {\n  /** Move focus to the active calendar date when opened. Default true. */\n  autoFocus?: boolean;\n}\n\nfunction focusCalendarDate(content: HTMLDivElement | null) {\n  const grid = Array.from(\n    content?.querySelectorAll<HTMLElement>('[role=\"grid\"]') ?? [],\n  ).find((element) => !element.closest(\"[inert]\"));\n  const target =\n    grid?.querySelector<HTMLElement>('button[tabindex=\"0\"]') ?? grid;\n  target?.focus({ preventScroll: true });\n}\n\nexport function DateRangePickerContent({\n  children,\n  className,\n  autoFocus = true,\n  onOpenAutoFocus,\n  ...props\n}: DateRangePickerContentProps) {\n  const ctx = useDateRangePickerContext(\"DateRangePickerContent\");\n  useLayoutEffect(() => {\n    // A rapid reopen can revive the exiting surface before it unmounts.\n    // That surface already ran its positioned callback, so focus it here too.\n    if (ctx.open && autoFocus) focusCalendarDate(ctx.content.current);\n  }, [ctx.open, ctx.content, autoFocus]);\n  return (\n    <MorphPopoverContent\n      align=\"start\"\n      radius={16}\n      shadow={false}\n      {...props}\n      className={cn(\"w-80 max-w-[calc(100vw-1.5rem)]\", className)}\n      onOpenAutoFocus={(content) => {\n        ctx.content.current = content;\n        if (autoFocus) focusCalendarDate(content);\n        onOpenAutoFocus?.(content);\n      }}\n    >\n      {children}\n    </MorphPopoverContent>\n  );\n}\n\nexport interface DateRangePickerDropdownProps\n  extends Omit<DateRangePickerProps, \"children\"> {\n  placeholder?: string;\n  triggerClassName?: string;\n  calendarClassName?: string;\n}\n/** Convenience composition of the root, trigger, content and calendar. */\nexport function DateRangePickerDropdown({\n  placeholder,\n  triggerClassName,\n  calendarClassName,\n  autoFocus = true,\n  showSummary = false,\n  ...props\n}: DateRangePickerDropdownProps) {\n  return (\n    <DateRangePicker {...props} autoFocus={autoFocus} showSummary={showSummary}>\n      <DateRangePickerTrigger\n        placeholder={placeholder}\n        className={triggerClassName}\n      />\n      <DateRangePickerContent autoFocus={autoFocus}>\n        <DateRangePickerCalendar\n          className={cn(\"w-full rounded-none border-0\", calendarClassName)}\n        />\n      </DateRangePickerContent>\n    </DateRangePicker>\n  );\n}\n\n// The departing month's grid must leave the tab order as soon as exit begins.\nfunction CalendarPresence({ children }: { children: ReactNode }) {\n  const present = useIsPresent();\n  return <div inert={!present}>{children}</div>;\n}\n\nfunction CalendarChoices({\n  id,\n  label,\n  choices,\n  selected,\n  focusOnMount,\n  reduce,\n  onClose,\n  onSelect,\n}: {\n  id: string;\n  label: string;\n  choices: { id: string; label: string; disabled: boolean }[];\n  selected: string;\n  focusOnMount: boolean;\n  reduce: boolean;\n  onClose: () => void;\n  onSelect: (id: string) => void;\n}) {\n  const enabled = choices.filter((choice) => !choice.disabled);\n  const [cursor, setCursor] = useState<string | null>(selected);\n  const active =\n    enabled.find((choice) => choice.id === cursor)?.id ??\n    enabled.find((choice) => choice.id === selected)?.id ??\n    enabled[0]?.id;\n  if (cursor !== null && cursor !== active) setCursor(active ?? null);\n  const root = useRef<HTMLFieldSetElement>(null);\n  useLayoutEffect(() => {\n    if (focusOnMount)\n      root.current\n        ?.querySelector<HTMLButtonElement>('button[tabindex=\"0\"]')\n        ?.focus({ preventScroll: true });\n  }, [focusOnMount]);\n  const onKey = (event: KeyboardEvent<HTMLButtonElement>) => {\n    if (event.altKey || event.ctrlKey || event.metaKey) return;\n    const buttons = Array.from(\n      root.current?.querySelectorAll<HTMLButtonElement>(\"button\") ?? [],\n    );\n    const index = buttons.indexOf(event.currentTarget);\n    const offset =\n      event.key === \"ArrowLeft\"\n        ? -1\n        : event.key === \"ArrowRight\"\n          ? 1\n          : event.key === \"ArrowUp\"\n            ? -3\n            : event.key === \"ArrowDown\"\n              ? 3\n              : null;\n    let target =\n      event.key === \"Home\"\n        ? 0\n        : event.key === \"End\"\n          ? buttons.length - 1\n          : offset === null\n            ? null\n            : index + offset;\n    if (event.key === \"Escape\") {\n      event.preventDefault();\n      event.stopPropagation();\n      onClose();\n      return;\n    }\n    if (target === null) return;\n    event.preventDefault();\n    const step =\n      offset === null ? (event.key === \"Home\" ? 1 : -1) : Math.sign(offset);\n    while (target >= 0 && target < buttons.length && buttons[target].disabled)\n      target += step;\n    buttons[target]?.focus({ preventScroll: true });\n  };\n  return (\n    <fieldset\n      ref={root}\n      id={id}\n      aria-label={label}\n      className=\"grid min-h-[15.25rem] grid-cols-3 content-start gap-2 border-0 p-0 pt-1\"\n    >\n      {choices.map((choice) => (\n        <motion.button\n          key={choice.id}\n          type=\"button\"\n          disabled={choice.disabled}\n          aria-pressed={choice.id === selected}\n          tabIndex={choice.id === active ? 0 : -1}\n          onFocus={() => setCursor(choice.id)}\n          onKeyDown={onKey}\n          onClick={() => onSelect(choice.id)}\n          whileTap={reduce ? undefined : { scale: 0.96 }}\n          transition={SPRING_PRESS}\n          className={cn(\n            \"flex h-13 items-center justify-center rounded-xl px-1 text-[13px] tabular-nums transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-25\",\n            choice.id === selected\n              ? \"bg-foreground font-medium text-background\"\n              : \"bg-muted/40 hover:bg-muted\",\n          )}\n        >\n          {choice.label}\n        </motion.button>\n      ))}\n    </fieldset>\n  );\n}\n"},{"path":"components/motion/date-range-picker/context.ts","type":"registry:component","target":"@components/motion/date-range-picker/context.ts","content":"\"use client\";\n\nimport { createContext, useContext } from \"react\";\nimport type { useDateRangePickerController } from \"./use-date-range-picker\";\n\ntype DateRangePickerContextValue = ReturnType<\n  typeof useDateRangePickerController\n>;\nexport const DateRangePickerContext =\n  createContext<DateRangePickerContextValue | null>(null);\nexport function useDateRangePickerContext(part: string) {\n  const context = useContext(DateRangePickerContext);\n  if (!context) throw new Error(`${part} must be used within DateRangePicker`);\n  return context;\n}\n"},{"path":"components/motion/date-range-picker/date-utils.ts","type":"registry:component","target":"@components/motion/date-range-picker/date-utils.ts","content":"import type { DateRange } from \"./types\";\n\nexport const DAY = 86_400_000;\nexport function parse(date: string) {\n  const time = Date.parse(`${date}T00:00:00Z`);\n  if (\n    !/^\\d{4}-\\d{2}-\\d{2}$/.test(date) ||\n    !Number.isFinite(time) ||\n    new Date(time).toISOString().slice(0, 10) !== date\n  )\n    throw new Error(`Invalid calendar date: ${date}. Use YYYY-MM-DD.`);\n  return time;\n}\nexport function iso(time: number) {\n  return new Date(time).toISOString().slice(0, 10);\n}\nexport function monthOf(date: string) {\n  return `${date.slice(0, 7)}-01`;\n}\nexport function shiftMonth(date: string, offset: number) {\n  const day = new Date(parse(date));\n  const month = new Date(parse(monthOf(date)));\n  month.setUTCMonth(month.getUTCMonth() + offset);\n  const end = new Date(month);\n  end.setUTCMonth(end.getUTCMonth() + 1);\n  end.setUTCDate(0);\n  month.setUTCDate(Math.min(day.getUTCDate(), end.getUTCDate()));\n  return iso(month.getTime());\n}\nexport function ordered(a: string, b: string): Required<DateRange> {\n  return a <= b ? { from: a, to: b } : { from: b, to: a };\n}\n"},{"path":"components/motion/date-range-picker/types.ts","type":"registry:component","target":"@components/motion/date-range-picker/types.ts","content":"import type { ReactNode } from \"react\";\n\n/** Calendar dates, independent of a browser's timezone: YYYY-MM-DD. */\nexport interface DateRange {\n  from: string;\n  to?: string;\n}\nexport interface DateRangePreset {\n  label: string;\n  value: DateRange;\n}\nexport interface DateRangePickerProps {\n  value?: DateRange | null;\n  defaultValue?: DateRange | null;\n  onValueChange?: (range: DateRange | null) => void;\n  defaultMonth?: string;\n  min?: string;\n  max?: string;\n  isDateDisabled?: (date: string) => boolean;\n  presets?: DateRangePreset[];\n  locale?: string;\n  label?: string;\n  disabled?: boolean;\n  /** Move focus to the active date on mount, for use inside a popover. */\n  autoFocus?: boolean;\n  /** Show the start/end summary above the calendar. Default true. */\n  showSummary?: boolean;\n  className?: string;\n  /** Compose custom controls; omitted children render the inline calendar. */\n  children?: ReactNode;\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  /** Close an open popover after a complete range or preset. Default false. */\n  closeOnSelect?: boolean;\n}\n"},{"path":"components/motion/date-range-picker/use-date-range-picker.ts","type":"registry:component","target":"@components/motion/date-range-picker/use-date-range-picker.ts","content":"\"use client\";\n\nimport { useReducedMotion } from \"motion/react\";\nimport {\n  type KeyboardEvent,\n  useId,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { useHoverCapable } from \"@/lib/hooks/use-hover-capable\";\nimport { DAY, parse, iso, monthOf, shiftMonth, ordered } from \"./date-utils\";\nimport type { DateRange, DateRangePickerProps } from \"./types\";\n\nexport function useDateRangePickerController({\n  value: controlledValue,\n  defaultValue = null,\n  onValueChange,\n  defaultMonth,\n  min,\n  max,\n  isDateDisabled,\n  presets = [],\n  locale = \"en-US\",\n  label = \"Choose a date range\",\n  disabled = false,\n  autoFocus = false,\n  showSummary = true,\n  open: controlledOpen,\n  defaultOpen = false,\n  onOpenChange,\n  closeOnSelect = false,\n}: DateRangePickerProps) {\n  const [today] = useState(() => {\n    const now = new Date();\n    return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, \"0\")}-${String(now.getDate()).padStart(2, \"0\")}`;\n  });\n  const [internalValue, setInternalValue] = useState(defaultValue);\n  const value = controlledValue === undefined ? internalValue : controlledValue;\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\n  const open = controlledOpen === undefined ? internalOpen : controlledOpen;\n  const trigger = useRef<HTMLButtonElement>(null);\n  const content = useRef<HTMLDivElement>(null);\n  const setOpen = (next: boolean) => {\n    if (controlledOpen === undefined) setInternalOpen(next);\n    onOpenChange?.(next);\n    if (!next && content.current?.contains(document.activeElement))\n      trigger.current?.focus({ preventScroll: true });\n  };\n  useLayoutEffect(() => {\n    if (!open && content.current?.contains(document.activeElement))\n      trigger.current?.focus({ preventScroll: true });\n  }, [open]);\n  if (min) parse(min);\n  if (max) parse(max);\n  if (min && max && min > max)\n    throw new Error(\"DateRangePicker min must not exceed max.\");\n  if (defaultMonth) parse(defaultMonth);\n  if (value) {\n    parse(value.from);\n    if (value.to) {\n      parse(value.to);\n      if (value.to < value.from)\n        throw new Error(\"DateRangePicker range must be ordered.\");\n    }\n  }\n  const initial =\n    defaultMonth ??\n    value?.from ??\n    (min && today < min ? min : max && today > max ? max : today);\n  const [requestedMonth, setMonth] = useState(() => monthOf(initial));\n  const month =\n    min && requestedMonth < monthOf(min)\n      ? monthOf(min)\n      : max && requestedMonth > monthOf(max)\n        ? monthOf(max)\n        : requestedMonth;\n  if (requestedMonth !== month) setMonth(month);\n  const [cursor, setCursor] = useState<string | null>(\n    () => value?.from ?? initial,\n  );\n  const [preview, setPreview] = useState<string | null>(null);\n  const [direction, setDirection] = useState(1);\n  const [announcement, setAnnouncement] = useState(\"\");\n  const [view, setView] = useState<\"days\" | \"months\" | \"years\">(\"days\");\n  const [requestedYearPage, setYearPage] = useState(\n    () => Math.floor(Number(initial.slice(0, 4)) / 12) * 12,\n  );\n  const [focusChoices, setFocusChoices] = useState(false);\n  const monthTrigger = useRef<HTMLButtonElement>(null);\n  const yearTrigger = useRef<HTMLButtonElement>(null);\n  const returnToTrigger = useRef<\"months\" | \"years\" | null>(null);\n  const grid = useRef<HTMLTableElement>(null);\n  const pendingFocus = useRef(autoFocus);\n  const heading = useId();\n  const help = useId();\n  const choicesId = useId();\n  const reduce = useReducedMotion() ?? false;\n  const canHover = useHoverCapable();\n  const monthFormat = new Intl.DateTimeFormat(locale, {\n    month: \"long\",\n    year: \"numeric\",\n    timeZone: \"UTC\",\n  });\n  const monthNameFormat = new Intl.DateTimeFormat(locale, {\n    month: \"long\",\n    timeZone: \"UTC\",\n  });\n  const dateFormat = new Intl.DateTimeFormat(locale, {\n    weekday: \"long\",\n    month: \"long\",\n    day: \"numeric\",\n    year: \"numeric\",\n    timeZone: \"UTC\",\n  });\n  const shortFormat = new Intl.DateTimeFormat(locale, {\n    month: \"short\",\n    day: \"numeric\",\n    year: \"numeric\",\n    timeZone: \"UTC\",\n  });\n  const unavailable = (date: string) =>\n    disabled ||\n    Boolean(min && date < min) ||\n    Boolean(max && date > max) ||\n    Boolean(isDateDisabled?.(date));\n  const rangeAvailable = (range: DateRange) => {\n    const first = parse(range.from);\n    const last = parse(range.to ?? range.from);\n    if (\n      last < first ||\n      unavailable(range.from) ||\n      unavailable(range.to ?? range.from)\n    )\n      return false;\n    // Only scan interiors when the consumer supplies unavailable dates.\n    if (isDateDisabled)\n      for (let time = first + DAY; time < last; time += DAY)\n        if (unavailable(iso(time))) return false;\n    return true;\n  };\n  const lastDay = new Date(parse(month));\n  lastDay.setUTCMonth(lastDay.getUTCMonth() + 1);\n  lastDay.setUTCDate(0);\n  const daysInMonth = lastDay.getUTCDate();\n  const dates = Array.from({ length: daysInMonth }, (_, index) =>\n    iso(parse(month) + index * DAY),\n  );\n  const active =\n    cursor && dates.includes(cursor) && !unavailable(cursor)\n      ? cursor\n      : (dates.find((date) => !unavailable(date)) ?? null);\n  if (cursor !== null && cursor !== active) setCursor(active);\n  const shownRange =\n    value &&\n    !value.to &&\n    preview &&\n    rangeAvailable(ordered(value.from, preview))\n      ? ordered(value.from, preview)\n      : value;\n  const latest = useRef({ month, active });\n  useLayoutEffect(() => {\n    latest.current = { month, active };\n  });\n  useLayoutEffect(() => {\n    if (returnToTrigger.current) {\n      const target = returnToTrigger.current;\n      returnToTrigger.current = null;\n      (target === \"months\" ? monthTrigger : yearTrigger).current?.focus({\n        preventScroll: true,\n      });\n    }\n    if (!pendingFocus.current) return;\n    pendingFocus.current = false;\n    const button = grid.current?.querySelector<HTMLButtonElement>(\n      'button[tabindex=\"0\"]',\n    );\n    (button ?? grid.current)?.focus({ preventScroll: true });\n  });\n\n  const change = (next: DateRange | null) => {\n    if (disabled || (next && !rangeAvailable(next))) return;\n    if (controlledValue === undefined) setInternalValue(next);\n    onValueChange?.(next);\n    setPreview(null);\n    if (next?.to && open && closeOnSelect) setOpen(false);\n  };\n  const select = (date: string) => {\n    if (unavailable(date)) return;\n    setCursor(date);\n    if (!value || value.to) {\n      change({ from: date });\n      setAnnouncement(\n        `Start date ${dateFormat.format(parse(date))}. Choose an end date.`,\n      );\n    } else {\n      const next = ordered(value.from, date);\n      if (!rangeAvailable(next)) {\n        setAnnouncement(\n          \"This range includes unavailable dates. Choose another end date.\",\n        );\n        return;\n      }\n      change(next);\n      setAnnouncement(\n        `${shortFormat.format(parse(next.from))} to ${shortFormat.format(parse(next.to))} selected.`,\n      );\n    }\n  };\n  const navigate = (\n    offset: number,\n    focus = false,\n    target = latest.current.active ?? latest.current.month,\n  ) => {\n    const next = shiftMonth(target, offset);\n    if (\n      (min && monthOf(next) < monthOf(min)) ||\n      (max && monthOf(next) > monthOf(max))\n    )\n      return;\n    latest.current = { month: monthOf(next), active: next };\n    setDirection(offset > 0 ? 1 : -1);\n    setMonth(monthOf(next));\n    setCursor(next);\n    setPreview(null);\n    pendingFocus.current = focus;\n  };\n  const onKey = (event: KeyboardEvent<HTMLButtonElement>, date: string) => {\n    if (event.altKey || event.ctrlKey || event.metaKey) return;\n    if (event.key === \"PageUp\" || event.key === \"PageDown\") {\n      event.preventDefault();\n      navigate(\n        (event.key === \"PageUp\" ? -1 : 1) * (event.shiftKey ? 12 : 1),\n        true,\n        date,\n      );\n      return;\n    }\n    const base = latest.current.active ?? date;\n    const weekday = new Date(parse(base)).getUTCDay();\n    const offset =\n      event.key === \"ArrowLeft\"\n        ? -1\n        : event.key === \"ArrowRight\"\n          ? 1\n          : event.key === \"ArrowUp\"\n            ? -7\n            : event.key === \"ArrowDown\"\n              ? 7\n              : event.key === \"Home\"\n                ? -weekday\n                : event.key === \"End\"\n                  ? 6 - weekday\n                  : null;\n    if (offset === null) return;\n    event.preventDefault();\n    let next = iso(parse(base) + offset * DAY);\n    // Arrow navigation skips unavailable dates; Home/End stay within their week.\n    if (event.key !== \"Home\" && event.key !== \"End\") {\n      const step = offset < 0 ? -1 : 1;\n      for (let attempts = 0; unavailable(next) && attempts < 366; attempts++) {\n        if ((min && next < min) || (max && next > max)) return;\n        next = iso(parse(next) + step * DAY);\n      }\n    }\n    if (unavailable(next)) return;\n    const nextMonth = monthOf(next);\n    setDirection(nextMonth >= latest.current.month ? 1 : -1);\n    // Own event updates are visible to a second key in the same batch.\n    latest.current = { month: nextMonth, active: next };\n    pendingFocus.current = true;\n    setMonth(nextMonth);\n    setCursor(next);\n    setPreview(value && !value.to ? next : null);\n  };\n  const leading = new Date(parse(month)).getUTCDay();\n  const cells: (string | null)[] = [\n    ...Array<string | null>(leading).fill(null),\n    ...dates,\n  ];\n  // Six rows keep the surface stable while changing months or browsing years.\n  while (cells.length < 42) cells.push(null);\n  const weeks = Array.from({ length: cells.length / 7 }, (_, index) => ({\n    start: iso(parse(month) + (index * 7 - leading) * DAY),\n    dates: cells.slice(index * 7, index * 7 + 7),\n  }));\n  const year = Number(month.slice(0, 4));\n  const monthIndex = Number(month.slice(5, 7)) - 1;\n  const minYear = min ? Number(min.slice(0, 4)) : 0;\n  const maxYear = max ? Number(max.slice(0, 4)) : 9999;\n  const firstYearPage = minYear;\n  // Backfill the final page so it still has twelve years when the bounds allow it.\n  const lastYearPage = Math.max(minYear, maxYear - 11);\n  const yearPage = Math.min(\n    lastYearPage,\n    Math.max(firstYearPage, requestedYearPage),\n  );\n  if (requestedYearPage !== yearPage) setYearPage(yearPage);\n  const yearPageEnd = Math.min(yearPage + 11, maxYear);\n  const previousDisabled =\n    disabled ||\n    (view === \"years\"\n      ? yearPage <= firstYearPage\n      : view === \"months\"\n        ? year <= minYear\n        : (year === 0 && monthIndex === 0) ||\n          Boolean(min && monthOf(shiftMonth(month, -1)) < monthOf(min)));\n  const nextDisabled =\n    disabled ||\n    (view === \"years\"\n      ? yearPage >= lastYearPage\n      : view === \"months\"\n        ? year >= maxYear\n        : (year === 9999 && monthIndex === 11) ||\n          Boolean(max && monthOf(shiftMonth(month, 1)) > monthOf(max)));\n  const closeChoices = () => {\n    returnToTrigger.current = view === \"years\" ? \"years\" : \"months\";\n    setView(\"days\");\n  };\n  const openChoices = (next: \"months\" | \"years\") => {\n    if (disabled) return;\n    if (view === next) {\n      closeChoices();\n      return;\n    }\n    setPreview(null);\n    setFocusChoices(true);\n    if (next === \"years\")\n      setYearPage(\n        Math.min(\n          lastYearPage,\n          Math.max(\n            firstYearPage,\n            minYear + Math.floor((year - minYear) / 12) * 12,\n          ),\n        ),\n      );\n    setView(next);\n  };\n  const goToMonth = (next: string) => {\n    if (disabled) return;\n    const clamped =\n      min && next < monthOf(min)\n        ? monthOf(min)\n        : max && next > monthOf(max)\n          ? monthOf(max)\n          : next;\n    setDirection(clamped >= month ? 1 : -1);\n    setMonth(clamped);\n    setCursor(clamped);\n    setPreview(null);\n    closeChoices();\n  };\n  const page = (step: -1 | 1) => {\n    setFocusChoices(false);\n    if (view === \"years\") {\n      setDirection(step);\n      const next = Math.min(\n        lastYearPage,\n        Math.max(firstYearPage, yearPage + step * 12),\n      );\n      if (next === firstYearPage || next === lastYearPage)\n        returnToTrigger.current = \"years\";\n      setYearPage((current) =>\n        Math.min(lastYearPage, Math.max(firstYearPage, current + step * 12)),\n      );\n    } else if (view === \"months\") {\n      const next = shiftMonth(month, step * 12);\n      const clamped =\n        min && next < monthOf(min)\n          ? monthOf(min)\n          : max && next > monthOf(max)\n            ? monthOf(max)\n            : monthOf(next);\n      setDirection(step);\n      setMonth(clamped);\n      setCursor(clamped);\n      if (\n        Number(clamped.slice(0, 4)) === minYear ||\n        Number(clamped.slice(0, 4)) === maxYear\n      )\n        returnToTrigger.current = \"months\";\n    } else {\n      const next = monthOf(shiftMonth(latest.current.active ?? month, step));\n      if (next === (min && monthOf(min)) || next === (max && monthOf(max)))\n        returnToTrigger.current = \"months\";\n      navigate(step);\n    }\n  };\n  const choices =\n    view === \"months\"\n      ? Array.from({ length: 12 }, (_, index) => {\n          const date = `${String(year).padStart(4, \"0\")}-${String(index + 1).padStart(2, \"0\")}-01`;\n          return {\n            id: date,\n            label: monthNameFormat.format(parse(date)),\n            disabled:\n              disabled ||\n              Boolean(min && date < monthOf(min)) ||\n              Boolean(max && date > monthOf(max)),\n          };\n        })\n      : Array.from(\n          { length: Math.max(0, yearPageEnd - yearPage + 1) },\n          (_, index) => {\n            const choiceYear = yearPage + index;\n            return {\n              id: String(choiceYear),\n              label: String(choiceYear),\n              disabled:\n                disabled || choiceYear < minYear || choiceYear > maxYear,\n            };\n          },\n        );\n  const dayCount = value?.to\n    ? Math.round((parse(value.to) - parse(value.from)) / DAY) + 1\n    : null;\n\n  const clear = () => {\n    if (disabled) return;\n    if (view === \"days\") pendingFocus.current = true;\n    else returnToTrigger.current = view;\n    change(null);\n    setAnnouncement(\"Date range cleared.\");\n  };\n  const selectPreset = (range: DateRange, name: string) => {\n    if (!rangeAvailable(range)) return;\n    if (view !== \"days\") closeChoices();\n    change(range);\n    setDirection(range.from >= month ? 1 : -1);\n    setMonth(monthOf(range.from));\n    setCursor(range.from);\n    setAnnouncement(`${name} selected.`);\n  };\n  const [wasOpen, setWasOpen] = useState(open);\n  if (wasOpen !== open) {\n    setWasOpen(open);\n    if (open) {\n      // A reopened popover starts a fresh calendar session, retaining its range.\n      setMonth(monthOf(initial));\n      setCursor(value?.from ?? initial);\n      setPreview(null);\n      setView(\"days\");\n      setFocusChoices(false);\n      setAnnouncement(\"\");\n    }\n  }\n  return {\n    value,\n    change,\n    open,\n    setOpen,\n    trigger,\n    content,\n    label,\n    locale,\n    disabled,\n    autoFocus,\n    showSummary,\n    presets,\n    today,\n    month,\n    view,\n    heading,\n    help,\n    choicesId,\n    monthTrigger,\n    yearTrigger,\n    monthFormat,\n    monthNameFormat,\n    shortFormat,\n    dateFormat,\n    year,\n    yearPage,\n    yearPageEnd,\n    previousDisabled,\n    nextDisabled,\n    openChoices,\n    page,\n    direction,\n    reduce,\n    choices,\n    focusChoices,\n    closeChoices,\n    goToMonth,\n    grid,\n    active,\n    weeks,\n    shownRange,\n    canHover,\n    unavailable,\n    setPreview,\n    select,\n    setCursor,\n    onKey,\n    dayCount,\n    clear,\n    rangeAvailable,\n    selectPreset,\n    announcement,\n  };\n}\n"},{"path":"components/motion/popover-morph.tsx","type":"registry:component","target":"@components/motion/popover-morph.tsx","content":"\"use client\";\n\nimport {\n  AnimatePresence,\n  motion,\n  animate,\n  useMotionValue,\n  usePresence,\n  useReducedMotion,\n} from \"motion/react\";\nimport {\n  cloneElement,\n  createContext,\n  isValidElement,\n  type ReactElement,\n  type ReactNode,\n  type Ref,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { usePopoverPortalPosition } from \"@/components/motion/popover-position\";\nimport { EASE_OUT, SPRING_PANEL } from \"@/lib/ease\";\nimport { cn } from \"@/lib/utils\";\n\ntype Side = \"top\" | \"bottom\";\ntype Align = \"start\" | \"end\";\n\ntype MorphContextValue = {\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  toggle: () => void;\n  triggerId: string;\n  contentId: string;\n  /** The element the panel measures against — see `registerTrigger`. */\n  triggerRef: React.MutableRefObject<HTMLElement | null>;\n  registerTrigger: (node: HTMLElement | null) => void;\n  contentRef: React.MutableRefObject<HTMLDivElement | null>;\n};\n\nconst MorphContext = createContext<MorphContextValue | null>(null);\n\nfunction useMorphContext(component: string) {\n  const ctx = useContext(MorphContext);\n  if (!ctx) throw new Error(`${component} must be used within <MorphPopover>`);\n  return ctx;\n}\n\nexport interface MorphPopoverProps {\n  children: ReactNode;\n  /** Controlled open state. */\n  open?: boolean;\n  /** Uncontrolled initial open state. */\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  className?: string;\n}\n\n/**\n * A popover whose panel morphs open from the trigger corner: it's laid out at\n * full size but clipped to the corner nearest the trigger, then unclips as one\n * piece. Closes on outside pointer / Escape. Controlled or uncontrolled.\n */\nexport function MorphPopover({\n  children,\n  open: controlledOpen,\n  defaultOpen = false,\n  onOpenChange,\n  className,\n}: MorphPopoverProps) {\n  const baseId = useId();\n  const [root, setRoot] = useState<HTMLDivElement | null>(null);\n  const [trigger, setTrigger] = useState<HTMLElement | null>(null);\n  const contentRef = useRef<HTMLDivElement | null>(null);\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\n  const controlled = controlledOpen !== undefined;\n  const open = controlled ? controlledOpen : internalOpen;\n\n  const setOpen = useCallback(\n    (next: boolean) => {\n      if (!controlled) setInternalOpen(next);\n      onOpenChange?.(next);\n    },\n    [controlled, onOpenChange],\n  );\n  const toggle = useCallback(() => setOpen(!open), [setOpen, open]);\n\n  // A trigger normally registers itself through MorphPopoverTrigger. It can't\n  // when something else already clones the element — a Tooltip wrapping the\n  // button, say — and an unregistered trigger leaves the panel with nothing to\n  // measure against, so it renders permanently invisible. The root boxes the\n  // trigger exactly (the content portals out of it), so it stands in until a\n  // real trigger registers, and stands in again if that one unmounts. Both are\n  // state, so a trigger arriving while the panel is open re-anchors it.\n  const anchorRef = useMemo<React.MutableRefObject<HTMLElement | null>>(\n    () => ({ current: trigger ?? root }),\n    [root, trigger],\n  );\n\n  // The panel is a `role=\"dialog\"` and goes inert the moment it closes, so\n  // focus cannot be left sitting inside it: a dismissal hands it back to the\n  // trigger, the way the ARIA dialog pattern asks. A pointer dismissal takes\n  // the focus onward itself when it lands on something focusable — this only\n  // catches the case where it would otherwise be stranded. When no trigger has\n  // registered, the root anchor stands in only if it can actually hold focus;\n  // there is nowhere better than where the keyboard already is, so leave it.\n  const close = useCallback(() => {\n    setOpen(false);\n    const focused = document.activeElement;\n    const inPanel =\n      focused instanceof HTMLElement && contentRef.current?.contains(focused);\n    if (!inPanel) return;\n    const restore = trigger ?? (root && root.tabIndex >= 0 ? root : null);\n    restore?.focus();\n  }, [root, setOpen, trigger]);\n\n  useEffect(() => {\n    if (!open) return;\n    const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && close();\n    const onPointer = (e: PointerEvent) => {\n      const target = e.target as Node;\n      if (\n        root &&\n        !root.contains(target) &&\n        !contentRef.current?.contains(target)\n      )\n        close();\n    };\n    window.addEventListener(\"keydown\", onKey);\n    window.addEventListener(\"pointerdown\", onPointer);\n    return () => {\n      window.removeEventListener(\"keydown\", onKey);\n      window.removeEventListener(\"pointerdown\", onPointer);\n    };\n  }, [open, root, close]);\n\n  const ctx = useMemo<MorphContextValue>(\n    () => ({\n      open,\n      setOpen,\n      toggle,\n      triggerId: `${baseId}-trigger`,\n      contentId: `${baseId}-content`,\n      triggerRef: anchorRef,\n      registerTrigger: setTrigger,\n      contentRef,\n    }),\n    [open, setOpen, toggle, baseId, anchorRef],\n  );\n\n  return (\n    <MorphContext.Provider value={ctx}>\n      <div ref={setRoot} className={cn(\"relative inline-flex\", className)}>\n        {children}\n      </div>\n    </MorphContext.Provider>\n  );\n}\n\nexport interface MorphPopoverTriggerProps {\n  children: ReactElement;\n}\n\nfunction mergeRefs<T>(...refs: Array<Ref<T> | undefined>) {\n  return (node: T | null) => {\n    for (const ref of refs) {\n      if (typeof ref === \"function\") ref(node);\n      else if (ref && typeof ref === \"object\")\n        (ref as React.MutableRefObject<T | null>).current = node;\n    }\n  };\n}\n\n/** Wraps a single element, toggling the popover on click. */\nexport function MorphPopoverTrigger({ children }: MorphPopoverTriggerProps) {\n  const ctx = useMorphContext(\"MorphPopoverTrigger\");\n  const child = children as ReactElement<Record<string, unknown>>;\n  const childOnClick = child?.props?.onClick as\n    | ((e: unknown) => void)\n    | undefined;\n  const childRef = (child?.props as { ref?: Ref<HTMLElement> } | undefined)\n    ?.ref;\n  // Register once per actual ref change, not once per open-state render.\n  const mergedRef = useMemo(\n    () => mergeRefs(childRef, ctx.registerTrigger),\n    [childRef, ctx.registerTrigger],\n  );\n  if (!isValidElement(children)) return children;\n\n  return cloneElement(child, {\n    id: ctx.triggerId,\n    ref: mergedRef,\n    onClick: (e: unknown) => {\n      childOnClick?.(e);\n      ctx.toggle();\n    },\n    \"aria-haspopup\": \"dialog\",\n    \"aria-expanded\": ctx.open,\n    \"aria-controls\": ctx.open ? ctx.contentId : undefined,\n  });\n}\n\nconst originFor = (side: Side, align: Align) =>\n  `${side === \"bottom\" ? \"top\" : \"bottom\"} ${align === \"end\" ? \"right\" : \"left\"}`;\n\n// A clip that hides everything but the corner nearest the trigger, so the\n// panel appears to grow out of it. inset(top right bottom left).\nfunction clipAt(side: Side, align: Align, radius: number, inset: number) {\n  const top = side === \"bottom\" ? \"0%\" : `${inset}%`;\n  const bottom = side === \"bottom\" ? `${inset}%` : \"0%\";\n  const right = align === \"end\" ? \"0%\" : `${inset}%`;\n  const left = align === \"end\" ? `${inset}%` : \"0%\";\n  return `inset(${top} ${right} ${bottom} ${left} round ${radius}px)`;\n}\n\n// Preserve the original spring character on the wrapper, but tween the complex\n// clip-path so it cannot snap when the spring resolves its final distance.\nconst MORPH_CLIP_TRANSITION = { duration: 0.32, ease: EASE_OUT } as const;\n\nexport interface MorphPopoverContentProps {\n  children: ReactNode;\n  side?: Side;\n  align?: Align;\n  /** Gap between trigger and panel, in px. Default 8. */\n  sideOffset?: number;\n  /** Panel corner radius, in px. Default 16. */\n  radius?: number;\n  /** Draw the surface shadow. Default true. */\n  shadow?: boolean;\n  /** Runs after the portalled surface is positioned and visible. */\n  onOpenAutoFocus?: (content: HTMLDivElement) => void;\n  className?: string;\n}\n\nexport function MorphPopoverContent(props: MorphPopoverContentProps) {\n  const ctx = useMorphContext(\"MorphPopoverContent\");\n  const [portalReady, setPortalReady] = useState(false);\n  useEffect(() => setPortalReady(true), []);\n  if (!portalReady) return null;\n  return createPortal(\n    <AnimatePresence>\n      {ctx.open && <MorphPopoverSurface {...props} />}\n    </AnimatePresence>,\n    document.body,\n  );\n}\n\n// Measurement belongs to the mounted portal session: reopening must not start\n// an entrance at the previous session's coordinates before measuring this one.\nfunction MorphPopoverSurface({\n  children,\n  side = \"bottom\",\n  align = \"end\",\n  sideOffset = 8,\n  radius = 16,\n  shadow = true,\n  onOpenAutoFocus,\n  className,\n}: MorphPopoverContentProps) {\n  const ctx = useMorphContext(\"MorphPopoverContent\");\n  const reduce = useReducedMotion() ?? false;\n  const [isPresent, safeToRemove] = usePresence();\n  const layout = usePopoverPortalPosition(\n    ctx.triggerRef,\n    ctx.contentRef,\n    isPresent,\n  );\n\n  const preferredLeft = layout\n    ? align === \"end\"\n      ? layout.trigger.left + layout.trigger.width - layout.content.width\n      : layout.trigger.left\n    : 0;\n  // Keep larger compositions, such as calendars, inside the viewport. Prefer\n  // the requested side, then use the roomier side when it cannot fit there.\n  const gutter = 12;\n  const below = layout\n    ? Math.max(\n        0,\n        window.innerHeight -\n          layout.trigger.top -\n          layout.trigger.height -\n          sideOffset -\n          gutter,\n      )\n    : 0;\n  const above = layout\n    ? Math.max(0, layout.trigger.top - sideOffset - gutter)\n    : 0;\n  const requestedSpace = side === \"bottom\" ? below : above;\n  const otherSpace = side === \"bottom\" ? above : below;\n  const resolvedSide =\n    layout &&\n    layout.content.height > requestedSpace &&\n    otherSpace > requestedSpace\n      ? side === \"bottom\"\n        ? \"top\"\n        : \"bottom\"\n      : side;\n  const availableHeight = resolvedSide === \"bottom\" ? below : above;\n  const left = layout\n    ? Math.max(\n        gutter,\n        Math.min(\n          preferredLeft,\n          window.innerWidth - layout.content.width - gutter,\n        ),\n      )\n    : 0;\n  const top = layout\n    ? resolvedSide === \"bottom\"\n      ? layout.trigger.top + layout.trigger.height + sideOffset\n      : Math.max(\n          gutter,\n          layout.trigger.top - layout.content.height - sideOffset,\n        )\n    : 0;\n\n  // Both directions travel between the exact same hidden/show states. Exit\n  // targets \"hidden\" directly instead of introducing separate choreography.\n  const wrap = reduce\n    ? undefined\n    : {\n        hidden: { scale: 0.96, transition: SPRING_PANEL },\n        show: { scale: 1, transition: SPRING_PANEL },\n      };\n  const clip = reduce\n    ? undefined\n    : {\n        hidden: {\n          clipPath: clipAt(resolvedSide, align, radius, 92),\n          transition: MORPH_CLIP_TRANSITION,\n        },\n        show: {\n          clipPath: clipAt(resolvedSide, align, radius, 0),\n          transition: MORPH_CLIP_TRANSITION,\n        },\n      };\n  // Animate the value directly so opacity stays in the inline style throughout\n  // the entrance. A native opacity animation can expose the initial inline 0\n  // for a frame when it finishes, before Motion writes the final value.\n  const opacity = useMotionValue(0);\n  const ready = layout !== null;\n  const focusedOnOpen = useRef(false);\n  useEffect(() => {\n    if (\n      !ready ||\n      !isPresent ||\n      focusedOnOpen.current ||\n      !ctx.contentRef.current\n    )\n      return;\n    focusedOnOpen.current = true;\n    onOpenAutoFocus?.(ctx.contentRef.current);\n  }, [ready, isPresent, ctx.contentRef, onOpenAutoFocus]);\n  useEffect(() => {\n    if (!ready) {\n      if (!isPresent) safeToRemove?.();\n      return;\n    }\n    const animation = animate(opacity, isPresent ? 1 : 0, {\n      ...(reduce ? { duration: 0.12 } : SPRING_PANEL),\n      onComplete: () => {\n        if (!isPresent) safeToRemove?.();\n      },\n    });\n    return () => animation.stop();\n  }, [opacity, ready, isPresent, reduce, safeToRemove]);\n\n  return (\n    <motion.div\n      data-morph-popover-portal=\"\"\n      inert={!isPresent}\n      // Wrapper carries the shadow as a drop-shadow filter, which hugs the\n      // clipped shape below (box-shadow would just get clipped away).\n      variants={wrap}\n      initial=\"hidden\"\n      animate={layout ? \"show\" : \"hidden\"}\n      exit=\"hidden\"\n      style={{\n        left,\n        top,\n        opacity,\n        pointerEvents: isPresent ? \"auto\" : \"none\",\n        visibility: layout ? \"visible\" : \"hidden\",\n        transformOrigin: originFor(resolvedSide, align),\n      }}\n      className={cn(\n        \"fixed z-[9999]\",\n        shadow && \"[filter:drop-shadow(0_10px_18px_rgba(0,0,0,0.14))]\",\n      )}\n    >\n      <motion.div\n        ref={ctx.contentRef}\n        id={ctx.contentId}\n        role=\"dialog\"\n        aria-labelledby={ctx.triggerId}\n        variants={clip}\n        style={{\n          borderRadius: radius,\n          maxHeight: layout ? availableHeight : undefined,\n          overflowY: \"auto\",\n        }}\n        className={cn(\n          \"overflow-hidden border border-border bg-background\",\n          className,\n        )}\n      >\n        {children}\n      </motion.div>\n    </motion.div>\n  );\n}\n"},{"path":"lib/ease.ts","type":"registry:lib","target":"@lib/ease.ts","content":"// Shared motion tokens. Easing curves mirror the CSS custom properties in\n// globals.css; springs are the canonical physics used across components.\n// Strong custom variants — defaults like `ease-in`/`ease-out` feel weak.\n\nexport const EASE_OUT = [0.16, 1, 0.3, 1] as const;\nexport const EASE_IN_OUT = [0.77, 0, 0.175, 1] as const;\nexport const EASE_DRAWER = [0.32, 0.72, 0, 1] as const;\n\n/** CSS string form of EASE_OUT for inline style transitions. */\nexport const EASE_OUT_CSS = \"cubic-bezier(0.16, 1, 0.3, 1)\";\n\n/** Press feedback on buttons and other tappable surfaces. */\nexport const SPRING_PRESS = {\n  type: \"spring\",\n  stiffness: 500,\n  damping: 30,\n  mass: 0.6,\n} as const;\n\n/** Content swaps — label/icon slots trading places inside a control. */\nexport const SPRING_SWAP = {\n  type: \"spring\",\n  stiffness: 460,\n  damping: 30,\n  mass: 0.55,\n} as const;\n\n/** Overlay panel entrances — modals and sheets summoned by pointer. */\nexport const SPRING_PANEL = {\n  type: \"spring\",\n  stiffness: 420,\n  damping: 40,\n  mass: 0.5,\n} as const;\n\n/** Shared-layout glides — pills, indicators and panels morphing between positions. */\nexport const SPRING_LAYOUT = {\n  type: \"spring\",\n  stiffness: 360,\n  damping: 32,\n  mass: 0.6,\n} as const;\n\n/** Cursor-follow physics for decorative mouse tracking (magnetic, tilt, dock). */\nexport const SPRING_MOUSE = {\n  stiffness: 200,\n  damping: 15,\n  mass: 0.3,\n} as const;\n\n/** Dragged handles and fills (sliders) — critically damped `useSpring` config,\n * so the value follows the pointer butterily and never rebounds off an end. */\nexport const SPRING_GLIDE = {\n  stiffness: 700,\n  damping: 50,\n  mass: 0.5,\n} as const;\n"},{"path":"lib/utils.ts","type":"registry:lib","target":"@lib/utils.ts","content":"import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"},{"path":"lib/hooks/use-hover-capable.ts","type":"registry:hook","target":"@lib/hooks/use-hover-capable.ts","content":"\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\n/**\n * Returns true only on devices that have a true hover (mouse / trackpad).\n * Touch devices fire phantom `:hover` on tap that sticks until tap-elsewhere\n * — gate hover-only effects (scale lifts, magnetic pulls) behind this.\n */\nexport function useHoverCapable() {\n  const [canHover, setCanHover] = useState(false);\n\n  useEffect(() => {\n    if (typeof window === \"undefined\" || !window.matchMedia) return;\n    const mq = window.matchMedia(\"(hover: hover) and (pointer: fine)\");\n    const update = () => setCanHover(mq.matches);\n    update();\n    mq.addEventListener?.(\"change\", update);\n    return () => mq.removeEventListener?.(\"change\", update);\n  }, []);\n\n  return canHover;\n}\n"},{"path":"components/motion/popover-position.ts","type":"registry:component","target":"@components/motion/popover-position.ts","content":"\"use client\";\n\nimport {\n  type MutableRefObject,\n  useCallback,\n  useLayoutEffect,\n  useState,\n} from \"react\";\n\nexport type PortalLayout = {\n  trigger: {\n    left: number;\n    top: number;\n    width: number;\n    height: number;\n  };\n  content: {\n    width: number;\n    height: number;\n  };\n};\n\nfunction sameLayout(a: PortalLayout | null, b: PortalLayout) {\n  return (\n    a?.trigger.left === b.trigger.left &&\n    a.trigger.top === b.trigger.top &&\n    a.trigger.width === b.trigger.width &&\n    a.trigger.height === b.trigger.height &&\n    a.content.width === b.content.width &&\n    a.content.height === b.content.height\n  );\n}\n\n/** Measures a trigger and portalled panel in viewport coordinates. */\nexport function usePopoverPortalPosition<\n  TriggerElement extends HTMLElement,\n  ContentElement extends HTMLElement,\n>(\n  triggerRef: MutableRefObject<TriggerElement | null>,\n  contentRef: MutableRefObject<ContentElement | null>,\n  active: boolean,\n) {\n  const [layout, setLayout] = useState<PortalLayout | null>(null);\n\n  const update = useCallback(() => {\n    const trigger = triggerRef.current;\n    const content = contentRef.current;\n    if (!trigger || !content) return;\n\n    const rect = trigger.getBoundingClientRect();\n    const next: PortalLayout = {\n      trigger: {\n        left: rect.left,\n        top: rect.top,\n        width: rect.width,\n        height: rect.height,\n      },\n      content: {\n        width: content.offsetWidth,\n        height: content.offsetHeight,\n      },\n    };\n    setLayout((current) => (sameLayout(current, next) ? current : next));\n  }, [contentRef, triggerRef]);\n\n  useLayoutEffect(() => {\n    update();\n    if (!active) return;\n\n    const trigger = triggerRef.current;\n    const content = contentRef.current;\n    const observer = new ResizeObserver(update);\n    if (trigger) observer.observe(trigger);\n    if (content) observer.observe(content);\n\n    window.addEventListener(\"scroll\", update, true);\n    window.addEventListener(\"resize\", update);\n    return () => {\n      observer.disconnect();\n      window.removeEventListener(\"scroll\", update, true);\n      window.removeEventListener(\"resize\", update);\n    };\n  }, [active, contentRef, triggerRef, update]);\n\n  return layout;\n}\n"}]}