{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-picker-compact",
  "title": "Picker-compact date",
  "description": "Styled date: the Picker-compact set. 5 decorative date variations (DenseDatePicker, MinimalDatePicker, NarrowDatePicker, InlineCompactDatePicker, BareDatePicker) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2",
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/date-picker-compact.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Calendar, ChevronLeft, ChevronRight } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion, type Transition } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Compact date picker family: 5 self-contained dense pickers. The difference is how\n   space is used: dense (a compressed grid), minimal (no frame), narrow (a narrow\n   column), inline compact (a trigger that sits on a line of text), bare (with all\n   the decoration stripped). NO radix, NO date library, NO portal, NO Intl. Each\n   export is a complete picker: a relative wrapper + a trigger (role=\"combobox\" +\n   aria-haspopup=\"grid\", aria-expanded) + a month-grid calendar panel (role=\"grid\")\n   absolutely positioned BELOW the trigger. Picking a day updates the value and\n   closes the panel. It closes on an outside click or Escape (focus returns to the\n   trigger).\n\n   CRITICAL: the visible month derives from a FIXED value (the month of the selected\n   date if there is one, otherwise FIXED_MONTH = 2026-01) - NEVER today. Reading the\n   real clock breaks prerender/hydration determinism. Density means a SMALL\n   appearance, not a small target: day cells that fall under 24px carry an invisible\n   hit-area extension (after:absolute after:-inset-1). Color comes ONLY from tokens,\n   via alpha color-mix.\n\n   State trap: the selection state is held OUTSIDE THE POPOVER TREE - the panel\n   unmounts on close, and the selection lives on. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst triggerHeight: Record<StyledSize, string> = {\n  sm: \"h-7 text-xs\",\n  md: \"h-8 text-xs\",\n  lg: \"h-9 text-sm\",\n  xl: \"h-10 text-sm\",\n}\n\n/* SSR-guvenli sabit gorunen ay. today ASLA kullanilmaz. */\nconst FIXED_MONTH = new Date(2026, 0, 1)\n\nconst MONTH_NAMES = [\n  \"January\",\n  \"February\",\n  \"March\",\n  \"April\",\n  \"May\",\n  \"June\",\n  \"July\",\n  \"August\",\n  \"September\",\n  \"October\",\n  \"November\",\n  \"December\",\n]\n\nconst MONTH_SHORT = [\n  \"Jan\",\n  \"Feb\",\n  \"Mar\",\n  \"Apr\",\n  \"May\",\n  \"Jun\",\n  \"Jul\",\n  \"Aug\",\n  \"Sep\",\n  \"Oct\",\n  \"Nov\",\n  \"Dec\",\n]\n\nconst WEEKDAY_LABELS = [\"S\", \"M\", \"T\", \"W\", \"T\", \"F\", \"S\"]\nconst WEEKDAY_NAMES = [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"]\n\nfunction pad2(n: number): string {\n  return n < 10 ? `0${n}` : `${n}`\n}\n\nfunction formatDate(d: Date): string {\n  return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`\n}\n\nfunction formatShort(d: Date): string {\n  return `${MONTH_SHORT[d.getMonth()]} ${d.getDate()}`\n}\n\nfunction formatLong(d: Date): string {\n  return `${MONTH_NAMES[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`\n}\n\nfunction startOfMonth(d: Date): Date {\n  return new Date(d.getFullYear(), d.getMonth(), 1)\n}\n\nfunction daysInMonth(year: number, month: number): number {\n  return new Date(year, month + 1, 0).getDate()\n}\n\nfunction sameDay(a: Date | undefined, b: Date | undefined): boolean {\n  return (\n    !!a &&\n    !!b &&\n    a.getFullYear() === b.getFullYear() &&\n    a.getMonth() === b.getMonth() &&\n    a.getDate() === b.getDate()\n  )\n}\n\nfunction useDismiss(\n  open: boolean,\n  onClose: (refocus: boolean) => void,\n  ref: React.RefObject<HTMLDivElement | null>\n) {\n  React.useEffect(() => {\n    if (!open) return\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") onClose(true)\n    }\n    const onPointer = (e: PointerEvent) => {\n      const node = ref.current\n      if (node && e.target instanceof Node && !node.contains(e.target)) {\n        onClose(false)\n      }\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, onClose, ref])\n}\n\nconst springTransition: Transition = { type: \"spring\" as const, stiffness: 380, damping: 28 }\n\nconst panelMotion = {\n  initial: { opacity: 0, scale: 0.97, y: -4 },\n  animate: { opacity: 1, scale: 1, y: 0 },\n  exit: { opacity: 0, scale: 0.97, y: -4 },\n}\n\nconst triggerBase =\n  \"inline-flex select-none items-center gap-1.5 whitespace-nowrap rounded-md font-medium outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:size-3.5 [&_svg]:shrink-0 [&_i]:text-xs [&_i]:leading-none\"\n\nconst navBtn =\n  \"inline-flex size-6 items-center justify-center rounded text-muted-foreground outline-none transition-colors hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)] hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-3.5 [&_svg]:shrink-0 [&_i]:text-xs [&_i]:leading-none\"\n\n/* Yogun gun hucresi: gorsel kutu 24px, hit-area after:-inset-1 ile buyutulur. */\nconst dayBase =\n  \"relative inline-flex size-6 items-center justify-center rounded text-xs text-foreground outline-none transition-colors after:absolute after:-inset-1 after:content-[''] hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)] focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\nconst daySelected =\n  \"bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--color-primary)_90%,transparent)]\"\n\ninterface CompactStyle {\n  wrapper: string\n  trigger: string\n  panel: string\n  showIcon: boolean\n  showWeekdays: boolean\n  label: (d: Date) => string\n}\n\nconst shells: Record<string, CompactStyle> = {\n  dense: {\n    wrapper: \"w-44\",\n    trigger:\n      \"w-full justify-between border border-field-border bg-transparent px-2 text-foreground hover:bg-[color-mix(in_oklab,var(--color-foreground)_4%,transparent)]\",\n    panel: \"w-52 border border-border bg-popover p-2 shadow-lg\",\n    showIcon: true,\n    showWeekdays: true,\n    label: formatDate,\n  },\n  minimal: {\n    wrapper: \"w-36\",\n    trigger:\n      \"w-full justify-between border border-transparent bg-transparent px-1.5 text-foreground hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)]\",\n    panel: \"w-52 border border-border bg-popover p-2 shadow-md\",\n    showIcon: true,\n    showWeekdays: true,\n    label: formatShort,\n  },\n  narrow: {\n    wrapper: \"w-28\",\n    trigger:\n      \"w-full justify-center border border-field-border bg-transparent px-1 text-foreground hover:bg-[color-mix(in_oklab,var(--color-foreground)_4%,transparent)]\",\n    panel: \"w-52 border border-border bg-popover p-2 shadow-lg\",\n    showIcon: false,\n    showWeekdays: true,\n    label: formatShort,\n  },\n  inline: {\n    wrapper: \"w-auto align-baseline\",\n    trigger:\n      \"h-auto rounded-sm border-0 border-b border-dashed border-field-border bg-transparent px-0.5 py-0 text-foreground hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)]\",\n    panel: \"w-52 border border-border bg-popover p-2 shadow-lg\",\n    showIcon: false,\n    showWeekdays: true,\n    label: formatShort,\n  },\n  bare: {\n    wrapper: \"w-32\",\n    trigger:\n      \"w-full justify-start border-0 bg-transparent px-0 text-muted-foreground hover:text-foreground\",\n    panel: \"w-48 bg-popover p-1.5 shadow-md ring-1 ring-border\",\n    showIcon: false,\n    showWeekdays: false,\n    label: formatShort,\n  },\n}\n\nexport interface DatePickerProps {\n  className?: string\n  size?: StyledSize\n  placeholder?: string\n  value?: Date\n  defaultValue?: Date\n  onChange?: (d: Date) => void\n}\n\n/* Shared body: the selection state lives OUTSIDE the popover. */\nfunction CompactPicker({\n  className,\n  size = \"md\",\n  placeholder = \"Pick a date\",\n  value,\n  defaultValue,\n  onChange,\n  shell,\n}: DatePickerProps & { shell: CompactStyle }) {\n  const isControlled = value !== undefined\n  const [internal, setInternal] = React.useState<Date | undefined>(defaultValue)\n  const selected = isControlled ? value : internal\n\n  const [open, setOpen] = React.useState(false)\n  const [month, setMonth] = React.useState(() => startOfMonth(selected ?? FIXED_MONTH))\n\n  const reduce = useReducedMotion()\n  const wrapperRef = React.useRef<HTMLDivElement>(null)\n  const triggerRef = React.useRef<HTMLButtonElement>(null)\n  const panelId = React.useId()\n  const labelId = React.useId()\n\n  const close = React.useCallback((refocus: boolean) => {\n    setOpen(false)\n    if (refocus) triggerRef.current?.focus()\n  }, [])\n\n  useDismiss(open, close, wrapperRef)\n\n  const pick = (day: Date) => {\n    if (!isControlled) setInternal(day)\n    onChange?.(day)\n    setMonth(startOfMonth(day))\n    setOpen(false)\n    triggerRef.current?.focus()\n  }\n\n  const year = month.getFullYear()\n  const m = month.getMonth()\n  const leading = new Date(year, m, 1).getDay()\n  const total = daysInMonth(year, m)\n  const cells: (Date | null)[] = []\n  for (let i = 0; i < leading; i++) cells.push(null)\n  for (let d = 1; d <= total; d++) cells.push(new Date(year, m, d))\n\n  const fade = { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 } }\n  const mp = reduce ? fade : panelMotion\n\n  return (\n    <div\n      ref={wrapperRef}\n      data-slot=\"styled-date-picker\"\n      className={cn(\"relative inline-flex flex-col\", shell.wrapper, className)}\n    >\n      <button\n        ref={triggerRef}\n        type=\"button\"\n        role=\"combobox\"\n        aria-haspopup=\"grid\"\n        aria-expanded={open}\n        aria-controls={open ? panelId : undefined}\n        className={cn(triggerBase, triggerHeight[size], shell.trigger)}\n        onClick={() => setOpen(!open)}\n      >\n        <span className={cn(!selected && \"text-muted-foreground\")}>\n          {selected ? shell.label(selected) : placeholder}\n        </span>\n        {shell.showIcon ? <Calendar className=\"text-muted-foreground\" /> : null}\n      </button>\n\n      <AnimatePresence>\n        {open ? (\n          <motion.div\n            id={panelId}\n            data-slot=\"styled-date-picker-popover\"\n            className={cn(\n              \"absolute left-0 top-full z-50 mt-1.5 origin-top rounded-lg text-popover-foreground outline-none\",\n              shell.panel\n            )}\n            initial={mp.initial}\n            animate={mp.animate}\n            exit={mp.exit}\n            transition={reduce ? ({ duration: 0.12 } as Transition) : springTransition}\n          >\n            <div data-slot=\"styled-date-picker-calendar\" className=\"flex flex-col gap-1\">\n              <div className=\"flex items-center justify-between\">\n                <button\n                  type=\"button\"\n                  aria-label=\"Previous month\"\n                  className={navBtn}\n                  onClick={() => setMonth(new Date(year, m - 1, 1))}\n                >\n                  <ChevronLeft />\n                </button>\n                <div id={labelId} className=\"text-xs font-semibold text-foreground\">\n                  {MONTH_SHORT[m]} {year}\n                </div>\n                <button\n                  type=\"button\"\n                  aria-label=\"Next month\"\n                  className={navBtn}\n                  onClick={() => setMonth(new Date(year, m + 1, 1))}\n                >\n                  <ChevronRight />\n                </button>\n              </div>\n\n              {shell.showWeekdays ? (\n                <div className=\"grid grid-cols-7\">\n                  {WEEKDAY_LABELS.map((w, i) => (\n                    <abbr\n                      key={WEEKDAY_NAMES[i]}\n                      title={WEEKDAY_NAMES[i]}\n                      className=\"flex size-6 items-center justify-center text-[0.625rem] font-medium text-muted-foreground no-underline\"\n                    >\n                      {w}\n                    </abbr>\n                  ))}\n                </div>\n              ) : null}\n\n              <div role=\"grid\" aria-labelledby={labelId} className=\"grid grid-cols-7\">\n                {cells.map((day, i) =>\n                  day === null ? (\n                    <span key={`empty-${i}`} className=\"size-6\" aria-hidden=\"true\" />\n                  ) : (\n                    <button\n                      key={formatDate(day)}\n                      type=\"button\"\n                      role=\"gridcell\"\n                      aria-label={formatLong(day)}\n                      aria-selected={sameDay(day, selected)}\n                      className={cn(dayBase, sameDay(day, selected) && daySelected)}\n                      onClick={() => pick(day)}\n                    >\n                      {day.getDate()}\n                    </button>\n                  )\n                )}\n              </div>\n            </div>\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n\n/* DenseDatePicker: sikistirilmis izgara, tam tarih etiketi. */\nexport function DenseDatePicker(props: DatePickerProps) {\n  return <CompactPicker {...props} shell={shells.dense} />\n}\n\n/* MinimalDatePicker: no frame, only a hover background. */\nexport function MinimalDatePicker(props: DatePickerProps) {\n  return <CompactPicker {...props} shell={shells.minimal} />\n}\n\n/* NarrowDatePicker: dar sutunlara sigan ortalanmis trigger. */\nexport function NarrowDatePicker(props: DatePickerProps) {\n  return <CompactPicker {...props} shell={shells.narrow} />\n}\n\n/* InlineCompactDatePicker: metin satirina oturan, noktali altcizgili trigger. */\nexport function InlineCompactDatePicker(props: DatePickerProps) {\n  return <CompactPicker {...props} shell={shells.inline} />\n}\n\n/* BareDatePicker: with the decoration stripped; the panel is frameless too, only a\n   ring + a shadow. */\nexport function BareDatePicker(props: DatePickerProps) {\n  return <CompactPicker {...props} shell={shells.bare} />\n}\n",
      "type": "registry:component",
      "target": "components/ui/date-picker-compact.tsx"
    }
  ],
  "categories": [
    "styled",
    "date"
  ],
  "type": "registry:component"
}