{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-speed-dial",
  "title": "Speed Dial",
  "description": "A floating action button that reveals a set of quick actions on click.",
  "dependencies": [
    "motion",
    "class-variance-authority"
  ],
  "registryDependencies": [
    "@azemmur/components-button",
    "@azemmur/hooks-use-key-actions"
  ],
  "files": [
    {
      "path": "registry/components/azemmur/speed-dial/index.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\nimport {\n  SpeedDialItem,\n  type SpeedDialItemData,\n} from '@/components/azemmur/components/azemmur/speed-dial/speed-dial-item';\nimport { SpeedDialMenu } from '@/components/azemmur/components/azemmur/speed-dial/speed-dial-menu';\nimport { SpeedDialRoot } from '@/components/azemmur/components/azemmur/speed-dial/speed-dial-root';\nimport { SpeedDialTrigger } from '@/components/azemmur/components/azemmur/speed-dial/speed-dial-trigger';\n\ntype SpeedDialComponent = typeof SpeedDialRoot & {\n  Trigger: typeof SpeedDialTrigger;\n  Menu: typeof SpeedDialMenu;\n  Item: typeof SpeedDialItem;\n};\n\nconst SpeedDial = SpeedDialRoot as SpeedDialComponent;\n\nSpeedDial.Trigger = SpeedDialTrigger;\nSpeedDial.Menu = SpeedDialMenu;\nSpeedDial.Item = SpeedDialItem;\n\nexport { SpeedDial, type SpeedDialItemData };\n",
      "type": "registry:ui",
      "target": "components/azemmur/speed-dial/index.tsx"
    },
    {
      "path": "registry/components/azemmur/speed-dial/speed-dial-root.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\n\nimport { cn } from '@/lib/utils';\nimport { useState, useRef, useMemo, useCallback } from 'react';\nimport { SpeedDialContext } from '@/components/azemmur/components/azemmur/speed-dial/speed-dial-context';\nimport {\n  SPEED_DIAL_NAVIGATION_KEYS,\n  speedDialVariants,\n  type SpeedDialVariantProps,\n} from '@/components/azemmur/components/azemmur/speed-dial/speed-dial-variants';\nimport { useKeyActions } from '@/hooks/use-key-actions';\n\ninterface SpeedDialRootProps extends SpeedDialVariantProps {\n  children: React.ReactNode;\n  className?: string;\n  defaultOpen?: boolean;\n}\n\nfunction SpeedDialRoot({\n  children,\n  className,\n  defaultOpen = false,\n  orientation = 'vertical',\n  expansion = 'reverse',\n  size = 'md',\n  intent = 'primary',\n  styling = 'solid',\n  shape = 'pill',\n  elevation = 'raised',\n}: SpeedDialRootProps) {\n  const [open, setOpen] = useState(defaultOpen);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const navigationKeys = SPEED_DIAL_NAVIGATION_KEYS[orientation ?? 'vertical'];\n  const focusableSelector =\n    'a[href], button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n  const handleBlur = useCallback((e: React.FocusEvent<HTMLDivElement>) => {\n    if (!e.currentTarget.contains(e.relatedTarget as Node)) {\n      setOpen(false);\n    }\n  }, []);\n\n  const handleKeyDown = useKeyActions(\n    [\n      {\n        keys: ['Escape'],\n        action: () => setOpen(false),\n      },\n      {\n        keys: [navigationKeys.next],\n        action: (e) => {\n          const focusable =\n            containerRef.current?.querySelectorAll<HTMLElement>(\n              focusableSelector,\n            );\n          if (!focusable) return;\n          const arr = Array.from(focusable);\n          const index = arr.findIndex((el) => el === e.target);\n          const next = (index + 1) % arr.length;\n          arr[next]?.focus();\n        },\n      },\n      {\n        keys: [navigationKeys.prev],\n        action: (e) => {\n          const focusable =\n            containerRef.current?.querySelectorAll<HTMLElement>(\n              focusableSelector,\n            );\n          if (!focusable) return;\n          const arr = Array.from(focusable);\n          const index = arr.findIndex((el) => el === e.target);\n          const prev = (index - 1 + arr.length) % arr.length;\n          arr[prev]?.focus();\n        },\n      },\n    ],\n    containerRef,\n  );\n\n  const contextValue = useMemo(\n    () => ({\n      open,\n      setOpen,\n      orientation,\n      expansion,\n      size,\n      intent,\n      styling,\n      shape,\n      elevation,\n    }),\n    [open, orientation, expansion, size, intent, styling, shape, elevation],\n  );\n\n  return (\n    <SpeedDialContext.Provider value={contextValue}>\n      <div\n        ref={containerRef}\n        className={cn(\n          speedDialVariants({\n            orientation,\n            expansion,\n            size,\n            intent,\n            styling,\n            shape,\n            elevation,\n          }),\n          className,\n        )}\n        role=\"navigation\"\n        aria-label=\"Speed dial menu\"\n        onBlur={handleBlur}\n        onKeyDown={handleKeyDown}\n        tabIndex={-1}\n      >\n        {children}\n      </div>\n    </SpeedDialContext.Provider>\n  );\n}\n\nSpeedDialRoot.displayName = 'SpeedDial';\n\nexport { SpeedDialRoot, type SpeedDialRootProps };\n",
      "type": "registry:ui",
      "target": "components/azemmur/speed-dial/speed-dial-root.tsx"
    },
    {
      "path": "registry/components/azemmur/speed-dial/speed-dial-context.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\n\nimport { createContext, useContext } from 'react';\nimport type { SpeedDialVariantProps } from '@/components/azemmur/components/azemmur/speed-dial/speed-dial-variants';\n\ntype SpeedDialContextValue = SpeedDialVariantProps & {\n  open: boolean;\n  setOpen: React.Dispatch<React.SetStateAction<boolean>>;\n};\n\nconst SpeedDialContext = createContext<SpeedDialContextValue | null>(null);\n\nfunction useSpeedDial() {\n  const context = useContext(SpeedDialContext);\n  if (!context) {\n    throw new Error('useSpeedDial must be used within a SpeedDial component');\n  }\n  return context;\n}\n\nexport { SpeedDialContext, useSpeedDial, type SpeedDialContextValue };\n",
      "type": "registry:hook",
      "target": "components/azemmur/speed-dial/speed-dial-context.tsx"
    },
    {
      "path": "registry/components/azemmur/speed-dial/speed-dial-trigger.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\n\nimport { cn } from '@/lib/utils';\nimport { Button } from '@/components/azemmur/components/azemmur/button';\nimport { useSpeedDial } from '@/components/azemmur/components/azemmur/speed-dial/speed-dial-context';\nimport { SPEED_DIAL_BUTTON_SIZE } from '@/components/azemmur/components/azemmur/speed-dial/speed-dial-variants';\n\ninterface SpeedDialTriggerProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nfunction SpeedDialTrigger({ children, className }: SpeedDialTriggerProps) {\n  const { open, setOpen, size, intent, styling, shape, elevation } =\n    useSpeedDial();\n\n  const sizeKey = size ?? 'md';\n\n  return (\n    <Button\n      onClick={() => setOpen((prev) => !prev)}\n      aria-expanded={open}\n      aria-controls=\"speed-dial-menu\"\n      aria-label={open ? 'Close speed dial menu' : 'Open speed dial menu'}\n      size={SPEED_DIAL_BUTTON_SIZE[sizeKey]}\n      intent={intent}\n      styling={styling}\n      shape={shape}\n      elevation={elevation}\n      className={cn(\n        'focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/60',\n        className,\n      )}\n    >\n      {children}\n    </Button>\n  );\n}\n\nexport { SpeedDialTrigger, type SpeedDialTriggerProps };\n",
      "type": "registry:ui",
      "target": "components/azemmur/speed-dial/speed-dial-trigger.tsx"
    },
    {
      "path": "registry/components/azemmur/speed-dial/speed-dial-menu.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\n\nimport { cn } from '@/lib/utils';\nimport { AnimatePresence, motion } from 'motion/react';\nimport { Children } from 'react';\nimport { useSpeedDial } from '@/components/azemmur/components/azemmur/speed-dial/speed-dial-context';\nimport { speedDialMenuVariants } from '@/components/azemmur/components/azemmur/speed-dial/speed-dial-variants';\n\ninterface SpeedDialMenuProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nfunction SpeedDialMenu({ children, className }: SpeedDialMenuProps) {\n  const { open, orientation, expansion, size } = useSpeedDial();\n  const items =\n    expansion === 'reverse'\n      ? [...Children.toArray(children)].reverse()\n      : Children.toArray(children);\n\n  return (\n    <AnimatePresence>\n      {open && (\n        <motion.div\n          id=\"speed-dial-menu\"\n          role=\"menu\"\n          aria-label=\"Speed dial actions\"\n          initial={{ opacity: 0, scale: 0.8 }}\n          animate={{ opacity: 1, scale: 1 }}\n          exit={{ opacity: 0, scale: 0.8 }}\n          transition={{ duration: 0.15, ease: 'easeOut' }}\n          className={cn(\n            speedDialMenuVariants({ orientation, expansion, size }),\n            className,\n          )}\n        >\n          {items}\n        </motion.div>\n      )}\n    </AnimatePresence>\n  );\n}\n\nexport { SpeedDialMenu, type SpeedDialMenuProps };\n",
      "type": "registry:ui",
      "target": "components/azemmur/speed-dial/speed-dial-menu.tsx"
    },
    {
      "path": "registry/components/azemmur/speed-dial/speed-dial-item.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\n\nimport { cn } from '@/lib/utils';\nimport { motion } from 'motion/react';\nimport { type ComponentProps } from 'react';\nimport { Button } from '@/components/azemmur/components/azemmur/button';\nimport { useSpeedDial } from '@/components/azemmur/components/azemmur/speed-dial/speed-dial-context';\nimport {\n  SPEED_DIAL_BUTTON_SIZE,\n  SPEED_DIAL_ITEM_OFFSET,\n} from '@/components/azemmur/components/azemmur/speed-dial/speed-dial-variants';\n\ntype SpeedDialItemData = {\n  title: string;\n  icon: React.ReactNode;\n  href: string;\n  obfuscated?: boolean;\n};\n\ninterface SpeedDialItemProps extends ComponentProps<'a'> {\n  item: SpeedDialItemData;\n  index?: number;\n}\n\nfunction SpeedDialItem({\n  item,\n  index = 0,\n  className,\n  ...props\n}: SpeedDialItemProps) {\n  const {\n    size,\n    intent,\n    styling,\n    shape,\n    elevation,\n    setOpen,\n    orientation,\n    expansion,\n  } = useSpeedDial();\n\n  const sizeKey = size ?? 'md';\n  const orientationKey = orientation ?? 'vertical';\n  const expansionKey = expansion ?? 'reverse';\n  const href = item.obfuscated ? undefined : item.href;\n\n  const handleClick = item.obfuscated\n    ? () => {\n        window.open(atob(item.href), '_blank', 'noopener,noreferrer');\n        setOpen(false);\n      }\n    : () => setOpen(false);\n\n  const initialOffset = SPEED_DIAL_ITEM_OFFSET[orientationKey][expansionKey];\n  const animateOffset = { x: 0, y: 0 };\n\n  return (\n    <motion.div\n      initial={{ opacity: 0, ...initialOffset, scale: 0.8 }}\n      animate={{ opacity: 1, ...animateOffset, scale: 1 }}\n      exit={{ opacity: 0, ...initialOffset, scale: 0.8 }}\n      transition={{\n        delay: index * 0.05,\n        duration: 0.15,\n        ease: 'easeOut',\n      }}\n    >\n      <Button\n        asChild\n        size={SPEED_DIAL_BUTTON_SIZE[sizeKey]}\n        intent={intent}\n        styling={styling}\n        shape={shape}\n        elevation={elevation}\n        className=\"p-2\"\n      >\n        <a\n          href={href}\n          role=\"menuitem\"\n          aria-label={item.title}\n          title={item.title}\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          onClick={handleClick}\n          className={cn(\n            'flex items-center justify-center focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/60',\n            className,\n          )}\n          {...props}\n        >\n          {item.icon}\n        </a>\n      </Button>\n    </motion.div>\n  );\n}\n\nexport { SpeedDialItem, type SpeedDialItemProps, type SpeedDialItemData };\n",
      "type": "registry:ui",
      "target": "components/azemmur/speed-dial/speed-dial-item.tsx"
    },
    {
      "path": "registry/components/azemmur/speed-dial/speed-dial-variants.ts",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nconst speedDialVariants = cva(['relative inline-flex items-center'], {\n  variants: {\n    orientation: {\n      vertical: 'flex-col',\n      horizontal: 'flex-row',\n    },\n    expansion: {\n      normal: '',\n      reverse: '',\n    },\n    size: {\n      sm: 'gap-2',\n      md: 'gap-3',\n      lg: 'gap-4',\n    },\n    intent: {\n      primary: '',\n      accent: '',\n      secondary: '',\n      success: '',\n      info: '',\n      warning: '',\n      error: '',\n    },\n    styling: {\n      solid: '',\n      outline: '',\n      ghost: '',\n    },\n    shape: {\n      rounded: '',\n      pill: '',\n      sharp: '',\n    },\n    elevation: {\n      raised: '',\n      floating: '',\n    },\n  },\n  compoundVariants: [\n    {\n      orientation: 'vertical',\n      expansion: 'reverse',\n      className: 'flex-col-reverse',\n    },\n    {\n      orientation: 'horizontal',\n      expansion: 'reverse',\n      className: 'flex-row-reverse',\n    },\n  ],\n  defaultVariants: {\n    orientation: 'vertical',\n    expansion: 'reverse',\n    size: 'md',\n    intent: 'primary',\n    styling: 'solid',\n    shape: 'pill',\n    elevation: 'raised',\n  },\n});\n\nconst speedDialMenuVariants = cva(['flex'], {\n  variants: {\n    orientation: {\n      vertical: 'flex-col',\n      horizontal: 'flex-row',\n    },\n    expansion: {\n      normal: '',\n      reverse: '',\n    },\n    size: {\n      sm: 'gap-2',\n      md: 'gap-3',\n      lg: 'gap-4',\n    },\n  },\n  compoundVariants: [],\n  defaultVariants: {\n    orientation: 'vertical',\n    expansion: 'reverse',\n    size: 'md',\n  },\n});\n\ntype SpeedDialVariantProps = VariantProps<typeof speedDialVariants>;\n\ntype SpeedDialOrientation = NonNullable<SpeedDialVariantProps['orientation']>;\ntype SpeedDialExpansion = NonNullable<SpeedDialVariantProps['expansion']>;\n\nconst SPEED_DIAL_NAVIGATION_KEYS: Record<\n  SpeedDialOrientation,\n  { next: 'ArrowDown' | 'ArrowRight'; prev: 'ArrowUp' | 'ArrowLeft' }\n> = {\n  vertical: {\n    next: 'ArrowDown',\n    prev: 'ArrowUp',\n  },\n  horizontal: {\n    next: 'ArrowRight',\n    prev: 'ArrowLeft',\n  },\n};\n\nconst SPEED_DIAL_ITEM_OFFSET: Record<\n  SpeedDialOrientation,\n  Record<SpeedDialExpansion, { x: number; y: number }>\n> = {\n  vertical: {\n    normal: { x: 0, y: -10 },\n    reverse: { x: 0, y: 10 },\n  },\n  horizontal: {\n    normal: { x: -10, y: 0 },\n    reverse: { x: 10, y: 0 },\n  },\n};\n\nconst SPEED_DIAL_BUTTON_SIZE = {\n  sm: 'icon-sm',\n  md: 'icon-md',\n  lg: 'icon-lg',\n} as const;\n\nexport {\n  speedDialVariants,\n  speedDialMenuVariants,\n  SPEED_DIAL_NAVIGATION_KEYS,\n  SPEED_DIAL_ITEM_OFFSET,\n  SPEED_DIAL_BUTTON_SIZE,\n  type SpeedDialVariantProps,\n  type SpeedDialOrientation,\n  type SpeedDialExpansion,\n};\n",
      "type": "registry:ui",
      "target": "components/azemmur/speed-dial/speed-dial-variants.ts"
    }
  ],
  "type": "registry:ui"
}