{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "poll-widget",
  "type": "registry:ui",
  "description": "Poll widget with inline, popover, and dialog modes, optional results, and animations",
  "dependencies": [
    "@radix-ui/react-use-controllable-state",
    "class-variance-authority",
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "button",
    "dialog",
    "popover"
  ],
  "files": [
    {
      "path": "registry/default/ui/poll-widget.tsx",
      "content": "\"use client\"\n\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n  type ComponentProps,\n  type HTMLAttributes,\n  type KeyboardEvent,\n  type MouseEvent,\n  type PropsWithChildren,\n  type ReactNode,\n} from \"react\"\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\"\nimport { cva } from \"class-variance-authority\"\nimport { BarChart3Icon, CheckIcon, Vote } from \"lucide-react\"\nimport { AnimatePresence, motion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverDescription,\n  PopoverHeader,\n  PopoverTitle,\n  PopoverTrigger,\n} from \"@/components/ui/popover\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface PollOption {\n  /** Unique identifier for the option */\n  id: string\n  /** Display label for the option */\n  label: string\n  /** Optional description */\n  description?: string\n  /** Optional icon */\n  icon?: ReactNode\n}\n\nexport type PollWidgetMode = \"inline\" | \"popover\" | \"dialog\"\n\nexport type PollWidgetAnimationPhase = \"idle\" | \"voting\" | \"results\" | \"success\"\n\nexport interface PollWidgetRootProps\n  extends Omit<PropsWithChildren, \"children\"> {\n  /** Poll question text */\n  question: string\n  /** Optional description for the poll */\n  description?: string\n  /** Available options to vote on */\n  options: PollOption[]\n  /** Currently selected option(s) - controlled */\n  value?: string | string[]\n  /** Default selected option(s) - uncontrolled */\n  defaultValue?: string | string[]\n  /** Callback when selection changes */\n  onValueChange?: (value: string | string[]) => void\n  /** Whether multiple selections are allowed */\n  multiple?: boolean\n  /** Whether the poll is disabled */\n  disabled?: boolean\n  /** Vote counts per option */\n  votes?: Record<string, number>\n  /** Whether user has submitted their vote */\n  hasVoted?: boolean\n  /** Callback when vote is submitted */\n  onVote?: (selectedIds: string[]) => void\n  /** Render mode */\n  mode?: PollWidgetMode\n  /** Controlled open state (for popover/dialog modes) */\n  open?: boolean\n  /** Callback when open state changes */\n  onOpenChange?: (open: boolean) => void\n  /** Duration to show results before collapsing (ms). Set to 0 to disable auto-collapse. */\n  autoCollapseDelay?: number\n  /** Duration to show success message (ms) */\n  successDuration?: number\n  /** Custom success title */\n  successTitle?: string\n  /** Custom success description */\n  successDescription?: string\n  /** Children components */\n  children: ReactNode\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\ninterface PollWidgetContextValue {\n  question: string\n  description?: string\n  options: PollOption[]\n  selected: string[]\n  multiple: boolean\n  disabled: boolean\n  showResults: boolean\n  votes: Record<string, number>\n  totalVotes: number\n  hasVoted: boolean\n  mode: PollWidgetMode\n  open: boolean\n  setOpen: (open: boolean) => void\n  select: (optionId: string) => void\n  isSelected: (optionId: string) => boolean\n  getPercentage: (optionId: string) => number\n  submitVote: () => void\n  canSubmit: boolean\n  // Animation state\n  animationPhase: PollWidgetAnimationPhase\n  successTitle: string\n  successDescription: string\n}\n\nconst PollWidgetContext = createContext<PollWidgetContextValue | null>(null)\n\n/**\n * Hook to access the PollWidget context\n * @throws Error if used outside of PollWidget.Root\n */\nexport function usePollWidget() {\n  const context = useContext(PollWidgetContext)\n  if (!context) {\n    throw new Error(\"PollWidget components must be used within PollWidget.Root\")\n  }\n  return context\n}\n\ninterface PollWidgetOptionContextValue {\n  optionId: string\n  disabled: boolean\n  isSelected: boolean\n  percentage: number\n}\n\nconst PollWidgetOptionContext =\n  createContext<PollWidgetOptionContextValue | null>(null)\n\nfunction usePollWidgetOptionContext() {\n  const context = useContext(PollWidgetOptionContext)\n  if (!context) {\n    throw new Error(\n      \"PollWidget.Option sub-components must be used within PollWidget.Option\"\n    )\n  }\n  return context\n}\n\n// ============================================================================\n// Variants\n// ============================================================================\n\nconst optionVariants = cva(\n  [\n    \"group relative flex w-full cursor-pointer items-center gap-3 rounded-lg border p-3 text-left\",\n    \"transition-all duration-200 ease-out\",\n    \"focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none\",\n    \"disabled:cursor-not-allowed disabled:opacity-50\",\n  ],\n  {\n    variants: {\n      state: {\n        idle: [\n          \"border-border bg-background hover:border-primary/50 hover:bg-accent/50\",\n        ],\n        selected: [\n          \"border-primary bg-primary/5 shadow-sm\",\n          \"hover:border-primary hover:bg-primary/10\",\n        ],\n        voted: [\"border-border bg-muted/30 cursor-default\"],\n      },\n    },\n    defaultVariants: {\n      state: \"idle\",\n    },\n  }\n)\n\nconst indicatorVariants = cva(\n  [\n    \"flex h-4 w-4 shrink-0 items-center justify-center rounded-full border-2\",\n    \"transition-all duration-200 ease-out\",\n  ],\n  {\n    variants: {\n      state: {\n        idle: \"border-muted-foreground/30 bg-background\",\n        selected: \"border-primary bg-primary text-primary-foreground\",\n        voted: \"border-muted-foreground/30 bg-muted\",\n      },\n      multiple: {\n        true: \"rounded-sm\",\n        false: \"rounded-full\",\n      },\n    },\n    defaultVariants: {\n      state: \"idle\",\n      multiple: false,\n    },\n  }\n)\n\nconst progressVariants = cva(\n  [\n    \"absolute inset-y-0 left-0 rounded-l-lg\",\n    \"transition-all duration-500 ease-out\",\n  ],\n  {\n    variants: {\n      state: {\n        idle: \"bg-transparent\",\n        selected: \"bg-primary/10\",\n        voted: \"bg-muted/50\",\n      },\n    },\n    defaultVariants: {\n      state: \"idle\",\n    },\n  }\n)\n\n// ============================================================================\n// Root Component\n// ============================================================================\n\n/**\n * Root component for PollWidget. Provides context for all child components.\n * Supports three rendering modes: inline, popover, and dialog.\n */\nexport function PollWidgetRoot({\n  question,\n  description,\n  options,\n  value: controlledValue,\n  defaultValue,\n  onValueChange,\n  multiple = false,\n  disabled = false,\n  votes = {},\n  hasVoted = false,\n  onVote,\n  mode = \"inline\",\n  open: controlledOpen,\n  onOpenChange,\n  autoCollapseDelay = 2500,\n  successDuration = 1500,\n  successTitle = \"Thanks for voting!\",\n  successDescription = \"Your vote has been recorded\",\n  children,\n}: PollWidgetRootProps) {\n  const normalizeValue = (val: string | string[] | undefined): string[] => {\n    if (!val) {\n      return []\n    }\n    return Array.isArray(val) ? val : [val]\n  }\n\n  const [selectedArray, setSelectedArray] = useControllableState<string[]>({\n    prop: controlledValue ? normalizeValue(controlledValue) : undefined,\n    defaultProp: normalizeValue(defaultValue),\n    onChange: (arr) => {\n      if (onValueChange) {\n        onValueChange(multiple ? arr : (arr[0] ?? \"\"))\n      }\n    },\n  })\n\n  const [open, setOpen] = useControllableState({\n    prop: controlledOpen,\n    defaultProp: false,\n    onChange: onOpenChange,\n  })\n\n  // Animation state\n  const [animationPhase, setAnimationPhase] =\n    useState<PollWidgetAnimationPhase>(\"idle\")\n\n  const selected = selectedArray ?? []\n\n  const totalVotes = useMemo(\n    () => Object.values(votes).reduce((sum, count) => sum + count, 0),\n    [votes]\n  )\n\n  const select = useCallback(\n    (optionId: string) => {\n      if (disabled || hasVoted || animationPhase !== \"idle\") {\n        return\n      }\n\n      setSelectedArray((prev) => {\n        const current = prev ?? []\n        const isCurrentlySelected = current.includes(optionId)\n\n        if (multiple) {\n          if (isCurrentlySelected) {\n            return current.filter((id) => id !== optionId)\n          }\n          return [...current, optionId]\n        }\n        if (isCurrentlySelected) {\n          return []\n        }\n        return [optionId]\n      })\n    },\n    [disabled, hasVoted, multiple, setSelectedArray, animationPhase]\n  )\n\n  const isSelected = useCallback(\n    (optionId: string) => selected.includes(optionId),\n    [selected]\n  )\n\n  const getPercentage = useCallback(\n    (optionId: string) => {\n      if (totalVotes === 0) {\n        return 0\n      }\n      return Math.round(((votes[optionId] ?? 0) / totalVotes) * 100)\n    },\n    [votes, totalVotes]\n  )\n\n  const submitVote = useCallback(() => {\n    if (selected.length > 0 && onVote && animationPhase === \"idle\") {\n      // Start animation sequence\n      setAnimationPhase(\"voting\")\n\n      // Brief delay before showing results\n      setTimeout(() => {\n        onVote(selected)\n        setAnimationPhase(\"results\")\n\n        // Show success after results\n        setTimeout(() => {\n          setAnimationPhase(\"success\")\n\n          // Auto-collapse after success (for popover/dialog modes)\n          if (autoCollapseDelay > 0 && mode !== \"inline\") {\n            setTimeout(() => {\n              setOpen(false)\n              // Reset animation phase after close\n              setTimeout(() => setAnimationPhase(\"idle\"), 300)\n            }, successDuration)\n          } else if (autoCollapseDelay > 0 && mode === \"inline\") {\n            // For inline mode, just go back to idle after showing success\n            setTimeout(() => setAnimationPhase(\"idle\"), successDuration)\n          }\n        }, autoCollapseDelay)\n      }, 400)\n    }\n  }, [\n    selected,\n    onVote,\n    animationPhase,\n    autoCollapseDelay,\n    successDuration,\n    mode,\n    setOpen,\n  ])\n\n  // Reset animation phase when opening\n  useEffect(() => {\n    if (open && !hasVoted) {\n      setAnimationPhase(\"idle\")\n    }\n  }, [open, hasVoted])\n\n  // Update animation phase when hasVoted changes externally\n  useEffect(() => {\n    if (hasVoted && animationPhase === \"idle\") {\n      setAnimationPhase(\"results\")\n    }\n  }, [hasVoted, animationPhase])\n\n  const canSubmit =\n    selected.length > 0 && !hasVoted && !disabled && animationPhase === \"idle\"\n\n  const contextValue = useMemo<PollWidgetContextValue>(\n    () => ({\n      question,\n      description,\n      options,\n      selected,\n      multiple,\n      disabled,\n      showResults: hasVoted || animationPhase === \"results\",\n      votes,\n      totalVotes,\n      hasVoted,\n      mode,\n      open: open ?? false,\n      setOpen: (value: boolean) => setOpen(value),\n      select,\n      isSelected,\n      getPercentage,\n      submitVote,\n      canSubmit,\n      animationPhase,\n      successTitle,\n      successDescription,\n    }),\n    [\n      question,\n      description,\n      options,\n      selected,\n      multiple,\n      disabled,\n      hasVoted,\n      votes,\n      totalVotes,\n      mode,\n      open,\n      setOpen,\n      select,\n      isSelected,\n      getPercentage,\n      submitVote,\n      canSubmit,\n      animationPhase,\n      successTitle,\n      successDescription,\n    ]\n  )\n\n  // Render based on mode\n  if (mode === \"popover\") {\n    return (\n      <PollWidgetContext.Provider value={contextValue}>\n        <Popover onOpenChange={setOpen} open={open}>\n          {children}\n        </Popover>\n      </PollWidgetContext.Provider>\n    )\n  }\n\n  if (mode === \"dialog\") {\n    return (\n      <PollWidgetContext.Provider value={contextValue}>\n        <Dialog onOpenChange={setOpen} open={open}>\n          {children}\n        </Dialog>\n      </PollWidgetContext.Provider>\n    )\n  }\n\n  // Inline mode - just render children with context\n  return (\n    <PollWidgetContext.Provider value={contextValue}>\n      <div data-mode={mode} data-slot=\"poll-widget\">\n        {children}\n      </div>\n    </PollWidgetContext.Provider>\n  )\n}\n\n// ============================================================================\n// Trigger Component\n// ============================================================================\n\nexport type PollWidgetTriggerProps = ComponentProps<typeof Button> & {\n  /** Custom label for the trigger button */\n  label?: ReactNode\n}\n\n/**\n * Button that opens the poll popover/dialog.\n * Only rendered in popover or dialog modes.\n */\nexport function PollWidgetTrigger({\n  className,\n  label,\n  children,\n  ...props\n}: PollWidgetTriggerProps) {\n  const { mode, hasVoted, totalVotes } = usePollWidget()\n\n  if (mode === \"inline\") {\n    return null\n  }\n\n  const content = children ?? (\n    <>\n      <Vote className=\"size-3.5\" />\n      {label ?? \"Poll\"}\n      {totalVotes > 0 && (\n        <span\n          className=\"bg-primary text-primary-foreground inline-flex min-w-4 items-center justify-center rounded-full px-1 text-[0.625rem]\"\n          data-slot=\"poll-widget-vote-count\"\n        >\n          {totalVotes}\n        </span>\n      )}\n    </>\n  )\n\n  const button = (\n    <Button\n      className={cn(\"gap-1.5\", hasVoted && \"text-foreground\", className)}\n      data-slot=\"poll-widget-trigger\"\n      size=\"sm\"\n      type=\"button\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {content}\n    </Button>\n  )\n\n  if (mode === \"dialog\") {\n    return <DialogTrigger asChild>{button}</DialogTrigger>\n  }\n\n  return <PopoverTrigger asChild>{button}</PopoverTrigger>\n}\n\n// ============================================================================\n// Content Component\n// ============================================================================\n\nexport type PollWidgetContentProps = HTMLAttributes<HTMLDivElement>\n\n/**\n * Container for the poll content. Adapts to the current mode.\n * Includes animation transitions between voting states.\n */\nexport function PollWidgetContent({\n  className,\n  children,\n  ...props\n}: PollWidgetContentProps) {\n  const { mode, animationPhase } = usePollWidget()\n\n  const innerContent = (\n    <AnimatePresence mode=\"popLayout\">\n      {animationPhase === \"success\" ? (\n        <motion.div\n          animate={{ y: 0, opacity: 1, filter: \"blur(0px)\" }}\n          className=\"flex h-full min-h-[120px] flex-col items-center justify-center\"\n          exit={{ y: 32, opacity: 0, filter: \"blur(4px)\" }}\n          initial={{ y: -32, opacity: 0, filter: \"blur(4px)\" }}\n          key=\"success\"\n          transition={{\n            type: \"spring\",\n            duration: 0.4,\n            bounce: 0,\n          }}\n        >\n          <PollWidgetSuccess />\n        </motion.div>\n      ) : (\n        <motion.div\n          className=\"flex flex-col gap-3\"\n          exit={{ y: 8, opacity: 0, filter: \"blur(4px)\" }}\n          initial={false}\n          key=\"poll-content\"\n          transition={{\n            type: \"spring\",\n            duration: 0.4,\n            bounce: 0,\n          }}\n        >\n          {children}\n        </motion.div>\n      )}\n    </AnimatePresence>\n  )\n\n  if (mode === \"popover\") {\n    return (\n      <PopoverContent\n        align=\"start\"\n        className={cn(\"w-80 overflow-hidden p-3\", className)}\n        data-animation-phase={animationPhase}\n        data-slot=\"poll-widget-content\"\n        side=\"top\"\n        sideOffset={8}\n        {...props}\n      >\n        {innerContent}\n      </PopoverContent>\n    )\n  }\n\n  if (mode === \"dialog\") {\n    return (\n      <DialogContent\n        className={cn(\"overflow-hidden sm:max-w-md\", className)}\n        data-animation-phase={animationPhase}\n        data-slot=\"poll-widget-content\"\n        {...props}\n      >\n        {innerContent}\n      </DialogContent>\n    )\n  }\n\n  // Inline mode\n  return (\n    <div\n      className={cn(\"flex flex-col gap-3\", className)}\n      data-animation-phase={animationPhase}\n      data-slot=\"poll-widget-content\"\n      {...props}\n    >\n      {innerContent}\n    </div>\n  )\n}\n\n// ============================================================================\n// Success Component\n// ============================================================================\n\nexport type PollWidgetSuccessProps = HTMLAttributes<HTMLDivElement> & {\n  /** Custom title override */\n  title?: string\n  /** Custom description override */\n  description?: string\n}\n\n/**\n * Success state shown after voting with animated checkmark.\n */\nexport function PollWidgetSuccess({\n  title,\n  description,\n  className,\n  ...props\n}: PollWidgetSuccessProps) {\n  const { successTitle, successDescription, totalVotes, selected, options } =\n    usePollWidget()\n\n  const displayTitle = title ?? successTitle\n  const displayDescription = description ?? successDescription\n\n  // Get voted option labels\n  const votedOptions = selected\n    .map((id) => options.find((o) => o.id === id)?.label)\n    .filter(Boolean)\n\n  return (\n    <div\n      className={cn(\"flex flex-col items-center gap-2 text-center\", className)}\n      data-slot=\"poll-widget-success\"\n      {...props}\n    >\n      <motion.div\n        animate={{ scale: 1, opacity: 1 }}\n        initial={{ scale: 0, opacity: 0 }}\n        transition={{\n          type: \"spring\",\n          duration: 0.5,\n          bounce: 0.4,\n          delay: 0.1,\n        }}\n      >\n        <svg\n          aria-hidden=\"true\"\n          className=\"-mt-1\"\n          fill=\"none\"\n          height=\"40\"\n          viewBox=\"0 0 32 32\"\n          width=\"40\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <title>Success</title>\n          <path\n            className=\"fill-primary/20\"\n            d=\"M27.6 16C27.6 17.5234 27.3 19.0318 26.717 20.4392C26.1341 21.8465 25.2796 23.1253 24.2025 24.2025C23.1253 25.2796 21.8465 26.1341 20.4392 26.717C19.0318 27.3 17.5234 27.6 16 27.6C14.4767 27.6 12.9683 27.3 11.5609 26.717C10.1535 26.1341 8.87475 25.2796 7.79759 24.2025C6.72043 23.1253 5.86598 21.8465 5.28302 20.4392C4.70007 19.0318 4.40002 17.5234 4.40002 16C4.40002 12.9235 5.62216 9.97301 7.79759 7.79759C9.97301 5.62216 12.9235 4.40002 16 4.40002C19.0765 4.40002 22.027 5.62216 24.2025 7.79759C26.3779 9.97301 27.6 12.9235 27.6 16Z\"\n          />\n          <path\n            className=\"stroke-primary\"\n            d=\"M12.1334 16.9667L15.0334 19.8667L19.8667 13.1M27.6 16C27.6 17.5234 27.3 19.0318 26.717 20.4392C26.1341 21.8465 25.2796 23.1253 24.2025 24.2025C23.1253 25.2796 21.8465 26.1341 20.4392 26.717C19.0318 27.3 17.5234 27.6 16 27.6C14.4767 27.6 12.9683 27.3 11.5609 26.717C10.1535 26.1341 8.87475 25.2796 7.79759 24.2025C6.72043 23.1253 5.86598 21.8465 5.28302 20.4392C4.70007 19.0318 4.40002 17.5234 4.40002 16C4.40002 12.9235 5.62216 9.97301 7.79759 7.79759C9.97301 5.62216 12.9235 4.40002 16 4.40002C19.0765 4.40002 22.027 5.62216 24.2025 7.79759C26.3779 9.97301 27.6 12.9235 27.6 16Z\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            strokeWidth=\"2.4\"\n          />\n        </svg>\n      </motion.div>\n      <motion.div\n        animate={{ y: 0, opacity: 1 }}\n        className=\"space-y-1\"\n        initial={{ y: 10, opacity: 0 }}\n        transition={{ delay: 0.2, duration: 0.3 }}\n      >\n        <h3 className=\"text-primary text-sm font-medium\">{displayTitle}</h3>\n        <p className=\"text-muted-foreground mx-auto max-w-xs text-xs text-pretty\">\n          {displayDescription}\n        </p>\n        {votedOptions.length > 0 && (\n          <p className=\"text-muted-foreground text-xs\">\n            You voted for:{\" \"}\n            <span className=\"text-foreground font-medium\">\n              {votedOptions.join(\", \")}\n            </span>\n          </p>\n        )}\n        <p className=\"text-muted-foreground pt-1 text-xs\">\n          {totalVotes.toLocaleString()} total{\" \"}\n          {totalVotes === 1 ? \"vote\" : \"votes\"}\n        </p>\n      </motion.div>\n    </div>\n  )\n}\n\n// ============================================================================\n// Question Component\n// ============================================================================\n\nexport type PollWidgetQuestionProps = HTMLAttributes<HTMLDivElement>\n\n/**\n * Displays the poll question and optional description.\n */\nexport function PollWidgetQuestion({\n  className,\n  children,\n  ...props\n}: PollWidgetQuestionProps) {\n  const { question, description, mode } = usePollWidget()\n\n  if (mode === \"popover\") {\n    return (\n      <PopoverHeader className={className} {...props}>\n        <PopoverTitle>{children ?? question}</PopoverTitle>\n        {description && <PopoverDescription>{description}</PopoverDescription>}\n      </PopoverHeader>\n    )\n  }\n\n  if (mode === \"dialog\") {\n    return (\n      <DialogHeader className={className} {...props}>\n        <DialogTitle>{children ?? question}</DialogTitle>\n        {description && <DialogDescription>{description}</DialogDescription>}\n      </DialogHeader>\n    )\n  }\n\n  // Inline mode\n  return (\n    <div\n      className={cn(\"flex flex-col gap-1\", className)}\n      data-slot=\"poll-widget-question\"\n      {...props}\n    >\n      <h3 className=\"text-base font-semibold tracking-tight\">\n        {children ?? question}\n      </h3>\n      {description && (\n        <p className=\"text-muted-foreground text-sm\">{description}</p>\n      )}\n    </div>\n  )\n}\n\n// ============================================================================\n// Options Component\n// ============================================================================\n\nexport type PollWidgetOptionsProps = HTMLAttributes<HTMLDivElement>\n\n/**\n * Container for poll options with keyboard navigation.\n */\nexport function PollWidgetOptions({\n  className,\n  children,\n  ...props\n}: PollWidgetOptionsProps) {\n  const containerRef = useRef<HTMLDivElement>(null)\n\n  const handleKeyDown = useCallback((event: KeyboardEvent<HTMLDivElement>) => {\n    const container = containerRef.current\n    if (!container) {\n      return\n    }\n\n    const options = Array.from(\n      container.querySelectorAll<HTMLButtonElement>(\n        '[data-slot=\"poll-widget-option\"]:not([disabled])'\n      )\n    )\n    const currentIndex = options.indexOf(\n      document.activeElement as HTMLButtonElement\n    )\n\n    let nextIndex = currentIndex\n\n    switch (event.key) {\n      case \"ArrowDown\":\n      case \"ArrowRight\":\n        event.preventDefault()\n        nextIndex = currentIndex < options.length - 1 ? currentIndex + 1 : 0\n        break\n      case \"ArrowUp\":\n      case \"ArrowLeft\":\n        event.preventDefault()\n        nextIndex = currentIndex > 0 ? currentIndex - 1 : options.length - 1\n        break\n      case \"Home\":\n        event.preventDefault()\n        nextIndex = 0\n        break\n      case \"End\":\n        event.preventDefault()\n        nextIndex = options.length - 1\n        break\n      default:\n        break\n    }\n\n    options[nextIndex]?.focus()\n  }, [])\n\n  return (\n    <div\n      className={cn(\"flex flex-col gap-2\", className)}\n      data-slot=\"poll-widget-options\"\n      onKeyDown={handleKeyDown}\n      ref={containerRef}\n      role=\"listbox\"\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\n// ============================================================================\n// Option Component\n// ============================================================================\n\nexport type PollWidgetOptionProps = Omit<ComponentProps<\"button\">, \"value\"> & {\n  /** Unique identifier for this option */\n  value: string\n  /** Whether this specific option is disabled */\n  disabled?: boolean\n}\n\n/**\n * Individual poll option with indicator, label, and progress bar.\n */\nexport function PollWidgetOption({\n  value,\n  disabled: optionDisabled = false,\n  children,\n  className,\n  onClick,\n  ...props\n}: PollWidgetOptionProps) {\n  const {\n    options,\n    disabled: rootDisabled,\n    hasVoted,\n    showResults,\n    isSelected,\n    select,\n    getPercentage,\n  } = usePollWidget()\n\n  const option = options.find((o) => o.id === value)\n  const disabled = rootDisabled || optionDisabled\n  const selected = isSelected(value)\n  const percentage = getPercentage(value)\n\n  const getState = (): \"idle\" | \"selected\" | \"voted\" => {\n    if (hasVoted) {\n      return \"voted\"\n    }\n    if (selected) {\n      return \"selected\"\n    }\n    return \"idle\"\n  }\n  const state = getState()\n\n  const handleClick = useCallback(\n    (event: MouseEvent<HTMLButtonElement>) => {\n      onClick?.(event)\n      if (!(event.defaultPrevented || disabled)) {\n        select(value)\n      }\n    },\n    [onClick, disabled, select, value]\n  )\n\n  const optionContextValue = useMemo(\n    () => ({\n      optionId: value,\n      disabled,\n      isSelected: selected,\n      percentage,\n    }),\n    [value, disabled, selected, percentage]\n  )\n\n  return (\n    <PollWidgetOptionContext.Provider value={optionContextValue}>\n      <button\n        aria-disabled={disabled || hasVoted}\n        aria-selected={selected}\n        className={cn(optionVariants({ state }), className)}\n        data-disabled={disabled ? true : undefined}\n        data-percentage={percentage}\n        data-selected={selected ? true : undefined}\n        data-slot=\"poll-widget-option\"\n        data-state={state}\n        data-value={value}\n        disabled={disabled || hasVoted}\n        onClick={handleClick}\n        role=\"option\"\n        type=\"button\"\n        {...props}\n      >\n        {/* Animated progress bar background */}\n        {showResults && (\n          <motion.span\n            animate={{ width: `${percentage}%` }}\n            aria-hidden=\"true\"\n            className={cn(\n              progressVariants({ state: selected ? \"selected\" : \"voted\" })\n            )}\n            initial={{ width: 0 }}\n            transition={{\n              type: \"spring\",\n              duration: 0.8,\n              bounce: 0.1,\n              delay: 0.1,\n            }}\n          />\n        )}\n\n        {/* Content */}\n        <span className=\"relative z-10 flex w-full items-center gap-3\">\n          {children ?? (\n            <>\n              <PollWidgetIndicator />\n              {option?.icon && (\n                <span className=\"text-muted-foreground shrink-0\">\n                  {option.icon}\n                </span>\n              )}\n              <PollWidgetLabel>\n                {option?.label ?? value}\n                {option?.description && (\n                  <span className=\"text-muted-foreground block text-xs font-normal\">\n                    {option.description}\n                  </span>\n                )}\n              </PollWidgetLabel>\n              {showResults && <PollWidgetPercentage />}\n            </>\n          )}\n        </span>\n      </button>\n    </PollWidgetOptionContext.Provider>\n  )\n}\n\n// ============================================================================\n// Indicator Component\n// ============================================================================\n\nexport type PollWidgetIndicatorProps = HTMLAttributes<HTMLSpanElement>\n\n/**\n * Selection indicator (checkbox/radio visual) for an option.\n */\nexport function PollWidgetIndicator({\n  className,\n  children,\n  ...props\n}: PollWidgetIndicatorProps) {\n  const { multiple, hasVoted } = usePollWidget()\n  const { isSelected } = usePollWidgetOptionContext()\n\n  const getState = (): \"idle\" | \"selected\" | \"voted\" => {\n    if (hasVoted) {\n      return \"voted\"\n    }\n    if (isSelected) {\n      return \"selected\"\n    }\n    return \"idle\"\n  }\n  const state = getState()\n\n  return (\n    <span\n      aria-hidden=\"true\"\n      className={cn(indicatorVariants({ state, multiple }), className)}\n      data-slot=\"poll-widget-indicator\"\n      data-state={state}\n      {...props}\n    >\n      {isSelected && (\n        <CheckIcon\n          className={cn(\n            \"h-2.5 w-2.5 transition-transform duration-200\",\n            isSelected ? \"scale-100\" : \"scale-0\"\n          )}\n          strokeWidth={3}\n        />\n      )}\n      {children}\n    </span>\n  )\n}\n\n// ============================================================================\n// Label Component\n// ============================================================================\n\nexport type PollWidgetLabelProps = HTMLAttributes<HTMLSpanElement>\n\n/**\n * Label text for a poll option.\n */\nexport function PollWidgetLabel({ className, ...props }: PollWidgetLabelProps) {\n  return (\n    <span\n      className={cn(\"flex-1 text-sm font-medium\", className)}\n      data-slot=\"poll-widget-label\"\n      {...props}\n    />\n  )\n}\n\n// ============================================================================\n// Percentage Component\n// ============================================================================\n\nexport type PollWidgetPercentageProps = HTMLAttributes<HTMLSpanElement>\n\n/**\n * Displays the vote percentage for an option (only visible after voting).\n */\nexport function PollWidgetPercentage({\n  children,\n  className,\n  ...props\n}: PollWidgetPercentageProps) {\n  const { showResults } = usePollWidget()\n  const { percentage } = usePollWidgetOptionContext()\n\n  if (!showResults) {\n    return null\n  }\n\n  return (\n    <span\n      className={cn(\n        \"text-muted-foreground min-w-[3ch] text-right text-xs font-medium tabular-nums\",\n        className\n      )}\n      data-slot=\"poll-widget-percentage\"\n      {...props}\n    >\n      {children ?? `${percentage}%`}\n    </span>\n  )\n}\n\n// ============================================================================\n// Results Component\n// ============================================================================\n\nexport type PollWidgetResultsProps = HTMLAttributes<HTMLDivElement>\n\n/**\n * Displays voting results summary (total votes, user vote status).\n */\nexport function PollWidgetResults({\n  children,\n  className,\n  ...props\n}: PollWidgetResultsProps) {\n  const { totalVotes, hasVoted } = usePollWidget()\n\n  return (\n    <div\n      className={cn(\n        \"text-muted-foreground flex items-center justify-between text-xs\",\n        className\n      )}\n      data-slot=\"poll-widget-results\"\n      {...props}\n    >\n      {children ?? (\n        <>\n          <span className=\"flex items-center gap-1.5\">\n            <BarChart3Icon className=\"size-3\" />\n            {totalVotes.toLocaleString()} {totalVotes === 1 ? \"vote\" : \"votes\"}\n          </span>\n          {hasVoted && (\n            <span className=\"text-primary flex items-center gap-1.5\">\n              <CheckIcon className=\"size-3\" />\n              <span>You voted</span>\n            </span>\n          )}\n        </>\n      )}\n    </div>\n  )\n}\n\n// ============================================================================\n// Submit Component\n// ============================================================================\n\nexport type PollWidgetSubmitProps = ComponentProps<typeof Button> & {\n  /** Text to show while submitting */\n  loadingText?: string\n}\n\n/**\n * Submit button for the poll. Disabled when no option is selected or already voted.\n * Shows loading state during vote submission animation.\n */\nexport function PollWidgetSubmit({\n  className,\n  children,\n  onClick,\n  loadingText = \"Submitting...\",\n  ...props\n}: PollWidgetSubmitProps) {\n  const { canSubmit, submitVote, hasVoted, mode, animationPhase } =\n    usePollWidget()\n\n  const isSubmitting = animationPhase === \"voting\"\n\n  const handleClick: NonNullable<PollWidgetSubmitProps[\"onClick\"]> = (\n    event\n  ) => {\n    onClick?.(event)\n    if (!event.defaultPrevented) {\n      submitVote()\n    }\n  }\n\n  if (\n    hasVoted ||\n    animationPhase === \"results\" ||\n    animationPhase === \"success\"\n  ) {\n    return null\n  }\n\n  const buttonContent = (\n    <AnimatePresence initial={false} mode=\"popLayout\">\n      <motion.span\n        animate={{ opacity: 1, y: 0 }}\n        className=\"flex w-full items-center justify-center gap-2\"\n        exit={{ opacity: 0, y: 20 }}\n        initial={{ opacity: 0, y: -20 }}\n        key={isSubmitting ? \"loading\" : \"submit\"}\n        transition={{\n          type: \"spring\",\n          duration: 0.3,\n          bounce: 0,\n        }}\n      >\n        {isSubmitting ? (\n          <>\n            <motion.div\n              animate={{ rotate: 360 }}\n              className=\"size-3.5 rounded-full border-2 border-current border-t-transparent\"\n              transition={{\n                duration: 1,\n                repeat: Number.POSITIVE_INFINITY,\n                ease: \"linear\",\n              }}\n            />\n            {loadingText}\n          </>\n        ) : (\n          (children ?? \"Submit Vote\")\n        )}\n      </motion.span>\n    </AnimatePresence>\n  )\n\n  // For dialog mode, wrap in DialogFooter\n  if (mode === \"dialog\") {\n    return (\n      <DialogFooter>\n        <DialogClose asChild>\n          <Button type=\"button\" variant=\"outline\">\n            Cancel\n          </Button>\n        </DialogClose>\n        <Button\n          className={cn(\"min-w-[120px]\", className)}\n          data-slot=\"poll-widget-submit\"\n          disabled={!canSubmit || isSubmitting}\n          onClick={handleClick}\n          type=\"button\"\n          {...props}\n        >\n          {buttonContent}\n        </Button>\n      </DialogFooter>\n    )\n  }\n\n  return (\n    <Button\n      className={cn(\"w-full overflow-hidden\", className)}\n      data-slot=\"poll-widget-submit\"\n      disabled={!canSubmit || isSubmitting}\n      onClick={handleClick}\n      type=\"button\"\n      {...props}\n    >\n      {buttonContent}\n    </Button>\n  )\n}\n\n// ============================================================================\n// Dialog Component (for dialog mode wrapper)\n// ============================================================================\n\nexport type PollWidgetDialogProps = ComponentProps<typeof DialogContent>\n\n/**\n * Dialog wrapper component. Use when mode=\"dialog\" to wrap PollWidget.Content.\n * This is an alternative to PollWidget.Content for more control over dialog rendering.\n */\nexport function PollWidgetDialog({\n  className,\n  children,\n  ...props\n}: PollWidgetDialogProps) {\n  return (\n    <DialogContent\n      className={cn(\"sm:max-w-md\", className)}\n      data-slot=\"poll-widget-dialog\"\n      {...props}\n    >\n      {children}\n    </DialogContent>\n  )\n}\n\n// ============================================================================\n// Compound Export\n// ============================================================================\n\nexport const PollWidget = Object.assign(PollWidgetRoot, {\n  Trigger: PollWidgetTrigger,\n  Content: PollWidgetContent,\n  Question: PollWidgetQuestion,\n  Options: PollWidgetOptions,\n  Option: PollWidgetOption,\n  Indicator: PollWidgetIndicator,\n  Label: PollWidgetLabel,\n  Percentage: PollWidgetPercentage,\n  Results: PollWidgetResults,\n  Submit: PollWidgetSubmit,\n  Success: PollWidgetSuccess,\n  Dialog: PollWidgetDialog,\n})\n",
      "type": "registry:ui"
    }
  ]
}