{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-picker",
  "type": "registry:component",
  "title": "Date picker",
  "description": "Selects a date from a calendar overlay opened by a 1px trigger.",
  "registryDependencies": [
    "https://vlak.dev/r/vlak-base.json",
    "https://vlak.dev/r/inter.json",
    "https://vlak.dev/r/dropdown-menu.json",
    "https://vlak.dev/r/calendar.json",
    "https://vlak.dev/r/vlak-lib.json",
    "https://vlak.dev/r/icons.json"
  ],
  "dependencies": [
    "@stylexjs/stylex"
  ],
  "devDependencies": [
    "@stylexjs/babel-plugin"
  ],
  "docs": "Vlak leaves are StyleX. Compile them with @stylexjs/babel-plugin (Vite: @stylexjs/unplugin, Next: @stylexjs/nextjs-plugin). If you would rather not run a compiler, import @noorddev/vlak-react instead: it ships precompiled with one stylesheet.",
  "files": [
    {
      "path": "vlak/date-picker.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak } from \"./tokens.stylex\";\nimport { rs } from \"./rs\";\nimport { useMergedRefs } from \"./merge-refs\";\nimport { useOverlayPosition } from \"./use-overlay-position\";\nimport { Calendar } from \"./calendar\";\nimport { Icon } from \"./icons\";\nimport { menuStyles } from \"./dropdown-menu\";\n\nconst styles = stylex.create({\n  calMenu: {\n    insetInlineEnd: \"auto\",\n    padding: \"0.75rem\",\n    backgroundColor: vlak.paper,\n  },\n});\n\nexport interface DatePickerProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\" | \"defaultValue\"> {\n  value?: Date;\n  defaultValue?: Date;\n  onValueChange?: (date: Date) => void;\n  /** @deprecated Use `onValueChange`. */\n  onChange?: (date: Date) => void;\n  placeholder?: string;\n  format?: (date: Date) => string;\n  disabled?: boolean;\n  min?: Date;\n  max?: Date;\n  isDateDisabled?: (date: Date) => boolean;\n  locale?: string;\n  /** Accessible name of the calendar dialog. */\n  dialogLabel?: string;\n}\n\nconst defaultDateFormat = (d: Date) =>\n  d.toLocaleDateString(\"en\", { day: \"numeric\", month: \"long\", year: \"numeric\" });\n\n/**\n * Hairline trigger that opens a calendar in a non-modal dialog. Focus\n * moves to the selected (or today's) day; Escape, Tab out, and a click\n * outside close it; focus returns to the trigger.\n */\nexport const DatePicker = React.forwardRef<HTMLDivElement, DatePickerProps>(function DatePicker({\n  value,\n  defaultValue,\n  onValueChange,\n  onChange,\n  placeholder = \"Pick a date\",\n  format = defaultDateFormat,\n  disabled,\n  min,\n  max,\n  isDateDisabled,\n  locale,\n  dialogLabel = \"Choose a date\",\n  className,\n  style,\n  onKeyDown,\n  onBlur,\n  ...props\n}: DatePickerProps, ref: React.ForwardedRef<HTMLDivElement>) {\n  const idBase = React.useId();\n  const dialogId = `${idBase}-dialog`;\n  const rootRef = React.useRef<HTMLDivElement>(null);\n  const setRootRef = useMergedRefs(rootRef, ref);\n  const triggerRef = React.useRef<HTMLButtonElement>(null);\n  const [open, setOpen] = React.useState(false);\n  const panelRef = React.useRef<HTMLDivElement>(null);\n  const placement = useOverlayPosition(open, panelRef, triggerRef);\n  const [inner, setInner] = React.useState(defaultValue);\n  const isControlled = value !== undefined;\n  const current = isControlled ? value : inner;\n\n  React.useEffect(() => {\n    if (!open) return;\n    const onPointerDown = (e: PointerEvent) => {\n      if (!rootRef.current?.contains(e.target as Node)) setOpen(false);\n    };\n    document.addEventListener(\"pointerdown\", onPointerDown);\n    return () => document.removeEventListener(\"pointerdown\", onPointerDown);\n  }, [open]);\n\n  const close = (restoreFocus: boolean) => {\n    setOpen(false);\n    if (restoreFocus) triggerRef.current?.focus();\n  };\n\n  const choose = (d: Date) => {\n    if (!isControlled) setInner(d);\n    onValueChange?.(d);\n    onChange?.(d);\n    close(true);\n  };\n\n  const root = rs([\"rs-select\", className], menuStyles.select);\n  const trigger = rs([\"rs-dropdown\"], menuStyles.dropdown);\n  const menu = rs([\"rs-menu\", \"rs-date-picker-cal-menu\"], menuStyles.menu, menuStyles.menuOverlay, menuStyles.menuCal, styles.calMenu);\n\n  return (\n    <div\n      ref={setRootRef}\n      className={root.className}\n      style={{ ...root.style, ...style }}\n      onKeyDown={(e) => {\n        onKeyDown?.(e);\n        if (e.defaultPrevented) return;\n        if (e.key === \"Escape\" && open) {\n          e.preventDefault();\n          close(true);\n        } else if (e.key === \"ArrowDown\" && !open && e.target === triggerRef.current) {\n          e.preventDefault();\n          setOpen(true);\n        }\n      }}\n      onBlur={(e) => {\n        onBlur?.(e);\n        const to = e.relatedTarget as Node | null;\n        if (open && to && !rootRef.current?.contains(to)) setOpen(false);\n      }}\n      {...props}\n    >\n      <button\n        ref={triggerRef}\n        type=\"button\"\n        className={trigger.className}\n        style={trigger.style}\n        aria-haspopup=\"dialog\"\n        aria-expanded={open}\n        aria-controls={open ? dialogId : undefined}\n        disabled={disabled}\n        onClick={() => (open ? close(true) : setOpen(true))}\n      >\n        <span>{current ? format(current) : placeholder}</span>\n        <Icon name=\"calendar\" size={16} />\n      </button>\n      {open && (\n        <div ref={panelRef} id={dialogId} role=\"dialog\" aria-label={dialogLabel} className={menu.className} style={{ ...menu.style, ...placement }}>\n          <Calendar autoFocus={placement.visibility === \"visible\"} value={current} onValueChange={choose} min={min} max={max} isDateDisabled={isDateDisabled} locale={locale} />\n        </div>\n      )}\n    </div>\n  );\n});\n",
      "type": "registry:component",
      "target": "components/vlak/date-picker.tsx"
    },
    {
      "path": "vlak/use-overlay-position.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\n/** Keep lightweight overlays inside the visual viewport. The native top layer avoids clipping. */\nexport function useOverlayPosition(\n  open: boolean,\n  panel: React.RefObject<HTMLElement | null>,\n  anchor: React.RefObject<HTMLElement | null>,\n  point?: { x: number; y: number } | null,\n  placement: \"bottom\" | \"inline-end\" = \"bottom\",\n  options?: {\n    popover?: \"auto\" | \"manual\";\n    edge?: number;\n    matchAnchorWidth?: boolean;\n    /** Preferred block side. Collision handling may use the opposite side. */\n    side?: \"auto\" | \"top\" | \"bottom\";\n  },\n) {\n  const [position, setPosition] = React.useState<React.CSSProperties>({ visibility: \"hidden\" });\n  React.useLayoutEffect(() => {\n    const element = panel.current;\n    if (!open || !element) return;\n    const native = typeof element.showPopover === \"function\";\n    if (native) {\n      element.setAttribute(\"popover\", options?.popover ?? \"manual\");\n      if (!element.matches(\":popover-open\")) element.showPopover();\n    }\n    const place = () => {\n      const viewport = window.visualViewport;\n      const edge = Math.max(0, options?.edge ?? 8);\n      const leftEdge = (viewport?.offsetLeft ?? 0) + edge;\n      const topEdge = (viewport?.offsetTop ?? 0) + edge;\n      const viewportWidth = viewport?.width ?? window.innerWidth;\n      const viewportHeight = viewport?.height ?? window.innerHeight;\n      const rightEdge = leftEdge + viewportWidth - edge * 2;\n      const bottomEdge = topEdge + viewportHeight - edge * 2;\n      const target = anchor.current?.getBoundingClientRect();\n      const paint = getComputedStyle(element);\n      const borderX = (Number.parseFloat(paint.borderLeftWidth) || 0) + (Number.parseFloat(paint.borderRightWidth) || 0);\n      const borderY = (Number.parseFloat(paint.borderTopWidth) || 0) + (Number.parseFloat(paint.borderBottomWidth) || 0);\n      const matchAnchor = options?.matchAnchorWidth !== false;\n      let panelWidth = element.scrollWidth + borderX;\n      if (!matchAnchor) {\n        // Measure the authored width without the last placement’s viewport clamp.\n        // Restoring it here also keeps resize observation tied to the final layout.\n        const { width, minWidth, maxWidth } = element.style;\n        element.style.width = \"\";\n        element.style.minWidth = \"\";\n        element.style.maxWidth = \"\";\n        panelWidth = element.getBoundingClientRect().width;\n        Object.assign(element.style, { width, minWidth, maxWidth });\n      }\n      const width = Math.min(Math.max(panelWidth, matchAnchor ? target?.width ?? 0 : 0), viewportWidth - edge * 2);\n      const height = Math.min(element.scrollHeight + borderY, viewportHeight - edge * 2);\n      let left = point?.x ?? target?.left ?? leftEdge;\n      let top = point?.y ?? (target ? target.bottom + 6 : topEdge);\n      if (placement === \"inline-end\" && target) {\n        const rtl = getComputedStyle(anchor.current!).direction === \"rtl\";\n        left = rtl ? target.left - width - 4 : target.right + 4;\n        if (left + width > rightEdge) left = target.left - width - 4;\n        if (left < leftEdge) left = target.right + 4;\n        top = target.top;\n        if (top + height > bottomEdge) top = bottomEdge - height;\n      } else if (target && point?.y === undefined) {\n        const below = target.bottom + 6;\n        const above = target.top - height - 6;\n        if (options?.side === \"top\") {\n          top = above >= topEdge || below + height > bottomEdge ? above : below;\n        } else {\n          top = below + height <= bottomEdge || above < topEdge ? below : above;\n        }\n      } else if (top + height > bottomEdge) {\n        top = point?.y !== undefined ? point.y - height : bottomEdge - height;\n      }\n      left = Math.max(leftEdge, Math.min(left, rightEdge - width));\n      top = Math.max(topEdge, Math.min(top, bottomEdge - height));\n      const next: React.CSSProperties = {\n        position: \"fixed\", inset: \"auto\", left, top, margin: 0,\n        minWidth: matchAnchor ? Math.min(target?.width ?? 0, viewportWidth - edge * 2) : 0,\n        maxWidth: viewportWidth - edge * 2, maxHeight: viewportHeight - edge * 2,\n        width, overflow: \"auto\", visibility: \"visible\",\n      };\n      setPosition((previous) => JSON.stringify(previous) === JSON.stringify(next) ? previous : next);\n    };\n    place();\n    const observer = typeof ResizeObserver === \"undefined\" ? undefined : new ResizeObserver(place);\n    observer?.observe(element);\n    if (anchor.current) observer?.observe(anchor.current);\n    window.addEventListener(\"resize\", place);\n    window.addEventListener(\"scroll\", place, true);\n    window.visualViewport?.addEventListener(\"resize\", place);\n    window.visualViewport?.addEventListener(\"scroll\", place);\n    return () => {\n      observer?.disconnect();\n      window.removeEventListener(\"resize\", place);\n      window.removeEventListener(\"scroll\", place, true);\n      window.visualViewport?.removeEventListener(\"resize\", place);\n      window.visualViewport?.removeEventListener(\"scroll\", place);\n    };\n  }, [open, panel, anchor, point?.x, point?.y, placement, options?.popover, options?.edge, options?.matchAnchorWidth, options?.side]);\n  return position;\n}\n",
      "type": "registry:file",
      "target": "components/vlak/use-overlay-position.ts"
    },
    {
      "path": "vlak/styles/date-picker.css",
      "content": "/* ── date-picker: generated from packages/react/src/components/date-picker.tsx ── */\n.rs-menu.rs-date-picker-cal-menu{inset-inline-end:auto;padding:0.75rem;background-color:var(--bg)}\n",
      "type": "registry:file",
      "target": "styles/vlak/date-picker.css"
    }
  ],
  "meta": {
    "vlak": {
      "category": "forms",
      "classes": [
        "rs-date-picker-cal-menu"
      ],
      "snippet": "<button class=\"rs-dropdown\"><span>24 July 2026</span></button>",
      "cssOnly": false,
      "registryDependencies": [
        "dropdown-menu",
        "calendar",
        "vlak-lib",
        "icons"
      ],
      "aliases": [
        "Date picker",
        "DatePicker",
        "Date input",
        "Date field"
      ],
      "example": "import { useState } from \"react\";\nimport { DatePicker } from \"@noorddev/vlak-react\";\n\nconst [date, setDate] = useState<Date>();\n\n<DatePicker value={date} onValueChange={setDate} placeholder=\"Pick a date\" dialogLabel=\"Choose a date\" />",
      "usage": {
        "use": [
          "A date field in a form where the calendar should stay out of the way until asked.",
          "format to render the chosen date your way."
        ],
        "avoid": [
          "Dates the user knows by heart; use Input type=\"date\".",
          "Always-visible calendars; use Calendar."
        ]
      },
      "keyboard": [
        {
          "keys": "Enter, Space, Arrow down",
          "does": "Opens the calendar"
        },
        {
          "keys": "Arrow keys, Home, End, Page up, Page down",
          "does": "Move through the calendar"
        },
        {
          "keys": "Enter, Space",
          "does": "Selects the day and closes"
        },
        {
          "keys": "Escape",
          "does": "Closes and returns focus to the trigger"
        },
        {
          "keys": "Tab",
          "does": "Moves through the calendar controls; leaving the picker closes it"
        }
      ],
      "a11y": [
        "The trigger is a <button> with aria-haspopup=\"dialog\", aria-expanded, and aria-controls; the calendar sits in a non-modal role=\"dialog\" named by dialogLabel.",
        "On open, focus moves to the selected day or today; on close it returns to the trigger.",
        "min, max, isDateDisabled and locale pass through to Calendar. The native top-layer panel follows its trigger and flips or clamps to the viewport.",
        "Controlled with value and onValueChange, or uncontrolled with defaultValue."
      ]
    }
  }
}
