{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "language-selector",
  "title": "Language Selector",
  "description": "Minimal locale control with scaled blur-sm menu and staggered options - no native select chrome.",
  "dependencies": [
    "framer-motion"
  ],
  "files": [
    {
      "path": "components/navigation/LanguageSelector.tsx",
      "content": "'use client';\n\nimport { AnimatePresence, motion } from 'framer-motion';\nimport { useEffect, useId, useRef, useState } from 'react';\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n//\n// locales         Available locales — code + label (+ optional short)\n// currentLocale   Controlled selected code\n// onChange        Selection callback\n// variant         text | compact\n// placement       Menu opens down | up\n// className       Styles for the outer shell\n//\ntype Locale = {\n  code: string;\n  label: string;\n  short?: string;\n};\n\ntype LanguageSelectorProps = {\n  locales?: Locale[];\n  currentLocale?: string;\n  onChange?: (code: string) => void;\n  variant?: 'text' | 'compact';\n  placement?: 'down' | 'up';\n  className?: string;\n};\n\nconst DEFAULT_LOCALES: Locale[] = [\n  { code: 'en', label: 'English', short: 'EN' },\n  { code: 'es', label: 'Español', short: 'ES' },\n  { code: 'fr', label: 'Français', short: 'FR' },\n  { code: 'de', label: 'Deutsch', short: 'DE' },\n  { code: 'pt', label: 'Português', short: 'PT' },\n];\n\nexport default function LanguageSelector({\n  locales = DEFAULT_LOCALES,\n  currentLocale,\n  onChange,\n  variant = 'text',\n  placement = 'down',\n  className = '',\n}: LanguageSelectorProps) {\n  const list = locales.length ? locales : DEFAULT_LOCALES;\n  const [open, setOpen] = useState(false);\n  const [selected, setSelected] = useState(\n    currentLocale ?? list[0]?.code ?? 'en'\n  );\n  const rootRef = useRef<HTMLDivElement>(null);\n  const menuId = useId();\n\n  useEffect(() => {\n    if (currentLocale) setSelected(currentLocale);\n  }, [currentLocale]);\n\n  useEffect(() => {\n    if (!open) return;\n    function onPointer(e: MouseEvent) {\n      if (!rootRef.current?.contains(e.target as Node)) setOpen(false);\n    }\n    function onKey(e: KeyboardEvent) {\n      if (e.key === 'Escape') setOpen(false);\n    }\n    window.addEventListener('mousedown', onPointer);\n    window.addEventListener('keydown', onKey);\n    return () => {\n      window.removeEventListener('mousedown', onPointer);\n      window.removeEventListener('keydown', onKey);\n    };\n  }, [open]);\n\n  const active = list.find((l) => l.code === selected) ?? list[0];\n  const triggerLabel =\n    variant === 'compact'\n      ? (active?.short ?? active?.code.toUpperCase())\n      : active?.label;\n\n  function choose(code: string) {\n    setSelected(code);\n    onChange?.(code);\n    setOpen(false);\n  }\n\n  return (\n    <div\n      ref={rootRef}\n      className={['relative inline-block', className].filter(Boolean).join(' ')}\n    >\n      <motion.button\n        type='button'\n        aria-haspopup='listbox'\n        aria-expanded={open}\n        aria-controls={menuId}\n        onClick={() => setOpen((v) => !v)}\n        className='group inline-flex items-center gap-2 rounded-full border border-[color:var(--color-border,#e5e7eb)] bg-[var(--color-background,#fff)] px-3 py-1.5 text-xs font-medium tracking-wide text-[color:var(--color-foreground,#374151)] shadow-[0_1px_2px_rgba(0,0,0,0.04)] backdrop-blur-md transition-[border-color,box-shadow] duration-300 hover:border-[color:var(--color-input,#d1d5db)] hover:shadow-[0_4px_16px_rgba(0,0,0,0.06)] focus:outline-hidden focus-visible:ring-2 focus-visible:ring-[color:var(--color-ring,#111827)]'\n        whileTap={{ scale: 0.98 }}\n      >\n        <span className='relative'>\n          {triggerLabel}\n          <span\n            className={[\n              'absolute -bottom-0.5 left-0 h-px w-full origin-left bg-[var(--color-foreground,#111827)] transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]',\n              open ? 'scale-x-100' : 'scale-x-0 group-hover:scale-x-100',\n            ].join(' ')}\n          />\n        </span>\n        <motion.svg\n          viewBox='0 0 12 12'\n          className='h-3 w-3 text-[color:var(--color-muted-foreground,#9ca3af)]'\n          animate={{ rotate: open ? 180 : 0 }}\n          transition={{ duration: 0.25 }}\n          aria-hidden\n        >\n          <path\n            d='M3 4.5L6 7.5L9 4.5'\n            fill='none'\n            stroke='currentColor'\n            strokeWidth='1.4'\n            strokeLinecap='round'\n            strokeLinejoin='round'\n          />\n        </motion.svg>\n      </motion.button>\n\n      <AnimatePresence>\n        {open ? (\n          <motion.ul\n            id={menuId}\n            role='listbox'\n            aria-label='Select language'\n            className={[\n              'absolute z-50 min-w-42 overflow-hidden rounded-[var(--radius-2xl,1rem)] border border-[color:var(--color-border,#e5e7eb)] bg-[var(--color-popover,#fff)] p-1 shadow-[0_16px_40px_rgba(0,0,0,0.1)] backdrop-blur-xl',\n              placement === 'up' ? 'bottom-full mb-2' : 'top-full mt-2',\n              'left-0',\n            ].join(' ')}\n            initial={{ opacity: 0, y: placement === 'up' ? 6 : -6, scale: 0.96, filter: 'blur(4px)' }}\n            animate={{ opacity: 1, y: 0, scale: 1, filter: 'blur(0px)' }}\n            exit={{ opacity: 0, y: placement === 'up' ? 4 : -4, scale: 0.98, filter: 'blur(3px)' }}\n            transition={{ duration: 0.22, ease: [0.22, 1, 0.36, 1] }}\n          >\n            {list.map((locale, i) => {\n              const isActive = locale.code === selected;\n              return (\n                <motion.li\n                  key={locale.code}\n                  role='option'\n                  aria-selected={isActive}\n                  initial={{ opacity: 0, y: 4 }}\n                  animate={{ opacity: 1, y: 0 }}\n                  transition={{ delay: i * 0.03, duration: 0.2 }}\n                >\n                  <button\n                    type='button'\n                    onClick={() => choose(locale.code)}\n                    className={[\n                      'flex w-full items-center justify-between rounded-xl px-3 py-2 text-left text-xs tracking-wide transition-colors',\n                      isActive\n                        ? 'bg-[var(--color-primary,#111827)] text-[color:var(--color-primary-foreground,#fff)]'\n                        : 'text-[color:var(--color-popover-foreground,#374151)] hover:bg-[var(--color-muted,#f3f4f6)]',\n                    ].join(' ')}\n                  >\n                    <span className='font-medium'>{locale.label}</span>\n                    <span\n                      className={[\n                        'font-mono text-[10px] uppercase tracking-wider',\n                        isActive ? 'text-[color:var(--color-primary-foreground,#fff)]/60' : 'text-[color:var(--color-muted-foreground,#9ca3af)]',\n                      ].join(' ')}\n                    >\n                      {locale.short ?? locale.code}\n                    </span>\n                  </button>\n                </motion.li>\n              );\n            })}\n          </motion.ul>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/navigation/LanguageSelector.tsx"
    }
  ],
  "categories": [
    "navigation"
  ],
  "type": "registry:component"
}