{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "speed-dial",
  "title": "Speed Dial",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/speed-dial/SpeedDial.vue",
      "content": "<script setup lang=\"ts\">\nimport type { Component, HTMLAttributes } from 'vue'\nimport { computed, ref } from 'vue'\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'\nimport { Fab } from '@/components/ui/fab'\nimport { cn } from '@/lib/utils'\nimport { Plus } from 'lucide-vue-next'\n\nexport interface SpeedDialAction {\n  icon: Component\n  label: string\n  handler?: () => void\n  disabled?: boolean\n  class?: HTMLAttributes['class']\n}\n\ntype Direction = 'up' | 'down' | 'left' | 'right'\ntype Trigger = 'click' | 'hover'\n\ninterface Props {\n  actions: SpeedDialAction[]\n  /** Main FAB icon. */\n  icon?: Component\n  /** Accessible label for the main FAB. */\n  label?: string\n  direction?: Direction\n  trigger?: Trigger\n  /** Close the dial after an action is triggered. */\n  closeOnAction?: boolean\n  variant?: 'default' | 'secondary' | 'destructive' | 'outline'\n  position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'bottom-center' | 'inline'\n  absolute?: boolean\n  disabled?: boolean\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  direction: 'up',\n  trigger: 'click',\n  closeOnAction: true,\n  variant: 'default',\n  position: 'bottom-right',\n  absolute: false,\n})\n\nconst open = ref(false)\nlet hoverTimer: ReturnType<typeof setTimeout> | null = null\n\nfunction onTriggerEnter() {\n  if (props.trigger !== 'hover') return\n  if (hoverTimer) clearTimeout(hoverTimer)\n  open.value = true\n}\n\nfunction onTriggerLeave() {\n  if (props.trigger !== 'hover') return\n  if (hoverTimer) clearTimeout(hoverTimer)\n  hoverTimer = setTimeout(() => (open.value = false), 150)\n}\n\nfunction onContentEnter() {\n  if (props.trigger !== 'hover') return\n  if (hoverTimer) clearTimeout(hoverTimer)\n}\n\nfunction onContentLeave() {\n  if (props.trigger !== 'hover') return\n  if (hoverTimer) clearTimeout(hoverTimer)\n  hoverTimer = setTimeout(() => (open.value = false), 150)\n}\n\nfunction runAction(action: SpeedDialAction) {\n  if (action.disabled) return\n  action.handler?.()\n  if (props.closeOnAction) open.value = false\n}\n\nconst side = computed(() => {\n  switch (props.direction) {\n    case 'up':\n      return 'top'\n    case 'down':\n      return 'bottom'\n    case 'left':\n      return 'left'\n    case 'right':\n      return 'right'\n  }\n})\n\nconst listClass = computed(() => {\n  const base = 'flex items-center gap-3'\n  return props.direction === 'up' || props.direction === 'down' ? `${base} flex-col` : `${base} flex-row`\n})\n</script>\n\n<template>\n  <Popover v-model:open=\"open\">\n    <PopoverTrigger as-child>\n      <div\n        data-uipkge\n        data-slot=\"speed-dial\"\n        :class=\"cn(props.class)\"\n        @mouseenter=\"onTriggerEnter\"\n        @mouseleave=\"onTriggerLeave\"\n      >\n        <Fab\n          :variant=\"variant\"\n          :position=\"position\"\n          :absolute=\"absolute\"\n          :disabled=\"disabled\"\n          :aria-label=\"label || 'Quick actions'\"\n          :class=\"cn('transition-transform duration-200', open && 'rotate-45')\"\n          @click=\"trigger === 'hover' ? $event.stopPropagation() : undefined\"\n        >\n          <component :is=\"icon\" v-if=\"icon\" />\n          <Plus v-else />\n        </Fab>\n      </div>\n    </PopoverTrigger>\n\n    <PopoverContent\n      :side=\"side\"\n      align=\"center\"\n      :side-offset=\"12\"\n      :class=\"cn('w-auto border-0 bg-transparent p-0 shadow-none', trigger === 'hover' && 'pointer-events-auto')\"\n      @mouseenter=\"onContentEnter\"\n      @mouseleave=\"onContentLeave\"\n    >\n      <div :class=\"listClass\">\n        <button\n          v-for=\"(action, i) in actions\"\n          :key=\"i\"\n          type=\"button\"\n          data-slot=\"speed-dial-action\"\n          :disabled=\"action.disabled\"\n          :aria-label=\"action.label\"\n          :style=\"{ animationDelay: `${i * 40}ms` }\"\n          class=\"group/speed-dial-item bg-background text-foreground hover:bg-accent hover:text-accent-foreground motion-safe:animate-in motion-safe:fade-in-0 motion-safe:zoom-in-95 focus-visible:ring-ring/50 inline-flex size-12 items-center justify-center rounded-full border shadow-md transition-colors outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5\"\n          :class=\"action.class\"\n          @click=\"runAction(action)\"\n        >\n          <component :is=\"action.icon\" />\n          <span class=\"sr-only\">{{ action.label }}</span>\n        </button>\n      </div>\n    </PopoverContent>\n  </Popover>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/speed-dial/SpeedDial.vue"
    },
    {
      "path": "packages/registry-vue/components/speed-dial/index.ts",
      "content": "export { default as SpeedDial } from './SpeedDial.vue'\nexport type { SpeedDialAction } from './SpeedDial.vue'\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/speed-dial/index.ts"
    }
  ],
  "dependencies": [
    "lucide-vue-next",
    "reka-ui"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/fab.json",
    "https://uipkge.dev/r/vue/popover.json"
  ],
  "description": "Expanding floating action button that reveals a list of secondary action buttons. Supports click or hover triggers, four expansion directions (up/down/left/right), staggered entrance animation, a configurable main FAB icon, and close-on-action. Built on the fab and popover primitives.",
  "categories": [
    "control",
    "navigation"
  ]
}