{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"mobile-menu","type":"registry:ui","title":"MobileMenu","description":"MobileMenu from react-fancy","package":"react-fancy","dependencies":["lucide-react"],"registryDependencies":["portal"],"files":[{"path":"components\/fancy\/mobile-menu\/MobileMenu.context.ts","content":"import { createContext, useContext } from \"react\";\r\nimport type { MobileMenuContextValue } from \".\/MobileMenu.types\";\r\n\r\nexport const MobileMenuContext = createContext<MobileMenuContextValue | null>(null);\r\n\r\nexport function useMobileMenu(): MobileMenuContextValue {\r\n  const ctx = useContext(MobileMenuContext);\r\n  if (!ctx) {\r\n    throw new Error(\"useMobileMenu must be used within a <MobileMenu> component\");\r\n  }\r\n  return ctx;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/mobile-menu\/MobileMenu.context.ts"},{"path":"components\/fancy\/mobile-menu\/MobileMenu.tsx","content":"import { MobileMenuFlyout } from \".\/MobileMenuFlyout\";\r\nimport { MobileMenuBottomBar } from \".\/MobileMenuBottomBar\";\r\nimport { MobileMenuItem } from \".\/MobileMenuItem\";\r\n\r\nexport const MobileMenu = {\r\n  Flyout: MobileMenuFlyout,\r\n  BottomBar: MobileMenuBottomBar,\r\n  Item: MobileMenuItem,\r\n};\r\n","type":"registry:ui","target":"components\/fancy\/mobile-menu\/MobileMenu.tsx"},{"path":"components\/fancy\/mobile-menu\/MobileMenu.types.ts","content":"import type { ReactNode } from \"react\";\r\n\r\nexport type MobileMenuVariant = \"flyout\" | \"bottom-bar\";\r\nexport type MobileMenuSide = \"left\" | \"right\";\r\n\r\nexport interface MobileMenuProps {\r\n  children: ReactNode;\r\n  variant?: MobileMenuVariant;\r\n  className?: string;\r\n}\r\n\r\nexport interface MobileMenuFlyoutProps {\r\n  children: ReactNode;\r\n  open: boolean;\r\n  onClose: () => void;\r\n  side?: MobileMenuSide;\r\n  title?: string;\r\n  className?: string;\r\n}\r\n\r\nexport interface MobileMenuBottomBarProps {\r\n  children: ReactNode;\r\n  className?: string;\r\n}\r\n\r\nexport interface MobileMenuItemProps {\r\n  children: ReactNode;\r\n  href?: string;\r\n  icon?: ReactNode;\r\n  active?: boolean;\r\n  disabled?: boolean;\r\n  badge?: ReactNode;\r\n  onClick?: () => void;\r\n  className?: string;\r\n}\r\n\r\nexport interface MobileMenuContextValue {\r\n  variant: MobileMenuVariant;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/mobile-menu\/MobileMenu.types.ts"},{"path":"components\/fancy\/mobile-menu\/MobileMenuBottomBar.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport { MobileMenuContext } from \".\/MobileMenu.context\";\r\nimport type { MobileMenuBottomBarProps } from \".\/MobileMenu.types\";\r\n\r\nexport function MobileMenuBottomBar({ children, className }: MobileMenuBottomBarProps) {\r\n  return (\r\n    <MobileMenuContext.Provider value={{ variant: \"bottom-bar\" }}>\r\n      <nav\r\n        data-react-fancy-mobile-menu-bottom-bar=\"\"\r\n        className={cn(\r\n          \"fixed inset-x-0 bottom-0 z-40 flex items-stretch border-t border-zinc-200 bg-white\/95 backdrop-blur-sm dark:border-zinc-700 dark:bg-zinc-900\/95\",\r\n          \"pb-[env(safe-area-inset-bottom)]\",\r\n          className,\r\n        )}\r\n      >\r\n        {children}\r\n      <\/nav>\r\n    <\/MobileMenuContext.Provider>\r\n  );\r\n}\r\n\r\nMobileMenuBottomBar.displayName = \"MobileMenuBottomBar\";\r\n","type":"registry:ui","target":"components\/fancy\/mobile-menu\/MobileMenuBottomBar.tsx"},{"path":"components\/fancy\/mobile-menu\/MobileMenuFlyout.tsx","content":"import { useEffect } from \"react\";\r\nimport { X } from \"lucide-react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { useEscapeKey } from \"..\/..\/hooks\";\r\nimport { useAnimation } from \"..\/..\/hooks\";\r\nimport { Portal } from \"..\/Portal\";\r\nimport { MobileMenuContext } from \".\/MobileMenu.context\";\r\nimport type { MobileMenuFlyoutProps } from \".\/MobileMenu.types\";\r\n\r\nexport function MobileMenuFlyout({\r\n  children,\r\n  open,\r\n  onClose,\r\n  side = \"left\",\r\n  title,\r\n  className,\r\n}: MobileMenuFlyoutProps) {\r\n  const isLeft = side === \"left\";\r\n\r\n  const { mounted, className: animClass, ref } = useAnimation({\r\n    open,\r\n    enterClass: isLeft ? \"fancy-slide-in-left\" : \"fancy-slide-in-right\",\r\n    exitClass: isLeft ? \"fancy-slide-out-left\" : \"fancy-slide-out-right\",\r\n  });\r\n\r\n  useEscapeKey(onClose, open);\r\n\r\n  \/\/ Lock body scroll\r\n  useEffect(() => {\r\n    if (open) {\r\n      const prev = document.body.style.overflow;\r\n      document.body.style.overflow = \"hidden\";\r\n      return () => {\r\n        document.body.style.overflow = prev;\r\n      };\r\n    }\r\n  }, [open]);\r\n\r\n  if (!mounted) return null;\r\n\r\n  return (\r\n    <Portal>\r\n      <MobileMenuContext.Provider value={{ variant: \"flyout\" }}>\r\n        <div data-react-fancy-mobile-menu-flyout=\"\" className=\"fixed inset-0 z-50\">\r\n          {\/* Backdrop *\/}\r\n          <div\r\n            className={cn(\r\n              \"absolute inset-0 bg-black\/40 transition-opacity\",\r\n              open ? \"opacity-100\" : \"opacity-0\",\r\n            )}\r\n            onClick={onClose}\r\n          \/>\r\n\r\n          {\/* Panel *\/}\r\n          <div\r\n            ref={ref as React.RefObject<HTMLDivElement>}\r\n            className={cn(\r\n              \"absolute top-0 flex h-full w-72 max-w-[80vw] flex-col bg-white shadow-xl dark:bg-zinc-900\",\r\n              isLeft ? \"left-0\" : \"right-0\",\r\n              animClass,\r\n              className,\r\n            )}\r\n          >\r\n            {\/* Header *\/}\r\n            <div className=\"flex items-center justify-between border-b border-zinc-200 px-4 py-3 dark:border-zinc-700\">\r\n              {title && (\r\n                <span className=\"text-sm font-semibold text-zinc-900 dark:text-zinc-100\">\r\n                  {title}\r\n                <\/span>\r\n              )}\r\n              {!title && <span \/>}\r\n              <button\r\n                type=\"button\"\r\n                onClick={onClose}\r\n                className=\"flex h-8 w-8 items-center justify-center rounded-md text-zinc-400 transition-colors hover:bg-zinc-100 hover:text-zinc-600 dark:hover:bg-zinc-800 dark:hover:text-zinc-300\"\r\n              >\r\n                <X className=\"h-4 w-4\" \/>\r\n              <\/button>\r\n            <\/div>\r\n\r\n            {\/* Content *\/}\r\n            <div className=\"flex-1 overflow-y-auto p-2\">\r\n              {children}\r\n            <\/div>\r\n          <\/div>\r\n        <\/div>\r\n      <\/MobileMenuContext.Provider>\r\n    <\/Portal>\r\n  );\r\n}\r\n\r\nMobileMenuFlyout.displayName = \"MobileMenuFlyout\";\r\n","type":"registry:ui","target":"components\/fancy\/mobile-menu\/MobileMenuFlyout.tsx"},{"path":"components\/fancy\/mobile-menu\/MobileMenuItem.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport { useMobileMenu } from \".\/MobileMenu.context\";\r\nimport type { MobileMenuItemProps } from \".\/MobileMenu.types\";\r\n\r\nexport function MobileMenuItem({\r\n  children,\r\n  href,\r\n  icon,\r\n  active = false,\r\n  disabled = false,\r\n  badge,\r\n  onClick,\r\n  className,\r\n}: MobileMenuItemProps) {\r\n  const { variant } = useMobileMenu();\r\n  const isBottomBar = variant === \"bottom-bar\";\r\n\r\n  const Tag = href ? \"a\" : \"button\";\r\n  const tagProps = href ? { href } : { type: \"button\" as const, onClick };\r\n\r\n  if (isBottomBar) {\r\n    return (\r\n      <Tag\r\n        {...tagProps}\r\n        data-react-fancy-mobile-menu-item=\"\"\r\n        data-active={active || undefined}\r\n        aria-current={active ? \"page\" : undefined}\r\n        aria-disabled={disabled || undefined}\r\n        className={cn(\r\n          \"flex flex-1 flex-col items-center justify-center gap-0.5 py-1.5 text-xs font-medium transition-colors\",\r\n          \"outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-inset\",\r\n          active\r\n            ? \"text-blue-600 dark:text-blue-400\"\r\n            : \"text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100\",\r\n          disabled && \"pointer-events-none opacity-50\",\r\n          className,\r\n        )}\r\n      >\r\n        {icon && <span className=\"flex h-5 w-5 items-center justify-center\">{icon}<\/span>}\r\n        <span className=\"truncate\">{children}<\/span>\r\n        {badge && <span className=\"absolute right-1 top-0.5\">{badge}<\/span>}\r\n      <\/Tag>\r\n    );\r\n  }\r\n\r\n  \/\/ Flyout variant \u2014 same as a vertical menu item\r\n  return (\r\n    <Tag\r\n      {...tagProps}\r\n      data-react-fancy-mobile-menu-item=\"\"\r\n      data-active={active || undefined}\r\n      aria-current={active ? \"page\" : undefined}\r\n      aria-disabled={disabled || undefined}\r\n      className={cn(\r\n        \"flex w-full items-center gap-3 rounded-md px-3 py-2.5 text-sm font-medium transition-colors\",\r\n        \"outline-none focus-visible:ring-2 focus-visible:ring-blue-500\",\r\n        active\r\n          ? \"bg-zinc-100 text-zinc-900 dark:bg-zinc-800 dark:text-zinc-100\"\r\n          : \"text-zinc-600 hover:bg-zinc-50 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-800\/50 dark:hover:text-zinc-100\",\r\n        disabled && \"pointer-events-none opacity-50\",\r\n        className,\r\n      )}\r\n    >\r\n      {icon && <span className=\"flex h-5 w-5 shrink-0 items-center justify-center\">{icon}<\/span>}\r\n      <span className=\"flex-1 truncate text-left\">{children}<\/span>\r\n      {badge && <span className=\"shrink-0\">{badge}<\/span>}\r\n    <\/Tag>\r\n  );\r\n}\r\n\r\nMobileMenuItem.displayName = \"MobileMenuItem\";\r\n","type":"registry:ui","target":"components\/fancy\/mobile-menu\/MobileMenuItem.tsx"},{"path":"components\/fancy\/mobile-menu\/index.ts","content":"export { MobileMenu } from \".\/MobileMenu\";\r\nexport { useMobileMenu } from \".\/MobileMenu.context\";\r\nexport type {\r\n  MobileMenuProps,\r\n  MobileMenuFlyoutProps,\r\n  MobileMenuBottomBarProps,\r\n  MobileMenuItemProps,\r\n  MobileMenuVariant,\r\n  MobileMenuSide,\r\n  MobileMenuContextValue,\r\n} from \".\/MobileMenu.types\";\r\n","type":"registry:ui","target":"components\/fancy\/mobile-menu\/index.ts"}]}