{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "bottom-menu", "title": "Bottom Menu", "description": "A floating bottom navigation menu with smooth interactions.", "dependencies": [ "motion", "react-use-measure", "@hugeicons/core-free-icons", "@hugeicons/react", "clsx", "tailwind-merge" ], "files": [ { "path": "registry/default/example/bottom-menu.tsx", "content": "\"use client\";\r\n\r\nimport {\r\n Notification03Icon,\r\n Search01Icon,\r\n Sun03Icon,\r\n Moon02Icon,\r\n ComputerIcon,\r\n UserEdit01Icon,\r\n PlusSignIcon,\r\n Mic01Icon,\r\n Camera01Icon,\r\n PencilEdit02Icon,\r\n FilterHorizontalIcon,\r\n AutoConversationsIcon,\r\n} from \"@hugeicons/core-free-icons\";\r\nimport { HugeiconsIcon } from \"@hugeicons/react\";\r\nimport React, { useMemo, useState, useRef, useEffect } from \"react\";\r\nimport { AnimatePresence, motion } from \"motion/react\";\r\nimport useMeasure from \"react-use-measure\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\n// Change Here\r\nconst MAIN_NAV = [\r\n { icon: PlusSignIcon, name: \"home\" },\r\n { icon: Search01Icon, name: \"search\" },\r\n { icon: Notification03Icon, name: \"notifications\" },\r\n { icon: UserEdit01Icon, name: \"profile\" },\r\n { icon: Sun03Icon, name: \"theme\" },\r\n];\r\n\r\nconst HOME_ITEMS = [\r\n { icon: PencilEdit02Icon, text: \"Note\" },\r\n { icon: Mic01Icon, text: \"Voice\" },\r\n { icon: Camera01Icon, text: \"Screenshot\" },\r\n];\r\n\r\nconst SEARCH_OPTIONS = [\r\n { icon: FilterHorizontalIcon, text: \"Filter\" },\r\n { icon: AutoConversationsIcon, text: \"Trending\" },\r\n];\r\n\r\nconst NOTIFICATION_TYPES = [\"Messages\", \"System Alerts\"];\r\n\r\nconst PROFILE_LINKS = [\"My Account\", \"Settings\", \"Subscription / Billing\"];\r\n\r\nconst THEME_OPTIONS = [\r\n { key: \"light\", icon: Sun03Icon, text: \"Light\" },\r\n { key: \"dark\", icon: Moon02Icon, text: \"Dark\" },\r\n { key: \"system\", icon: ComputerIcon, text: \"System\" },\r\n];\r\n\r\nconst BottomMenu = () => {\r\n const containerRef = useRef(null);\r\n const [elementRef] = useMeasure();\r\n const [hiddenRef, hiddenBounds] = useMeasure();\r\n const [view, setView] = useState<\r\n \"default\" | \"home\" | \"search\" | \"notifications\" | \"profile\" | \"theme\"\r\n >(\"default\");\r\n\r\n // Track selected theme\r\n const [theme, setTheme] = useState<\"light\" | \"dark\" | \"system\">(\"light\");\r\n useEffect(() => {\r\n const handleClickOutside = (event: MouseEvent) => {\r\n if (\r\n containerRef.current &&\r\n !containerRef.current.contains(event.target as Node)\r\n ) {\r\n setView(\"default\");\r\n }\r\n };\r\n\r\n document.addEventListener(\"mousedown\", handleClickOutside);\r\n return () => {\r\n document.removeEventListener(\"mousedown\", handleClickOutside);\r\n };\r\n }, []);\r\n\r\n const sharedHover =\r\n \"group transition-all duration-75 px-3 py-2 text-[15px] text-muted-foreground w-full text-left rounded-[12px] hover:bg-muted/80 hover:text-foreground\";\r\n\r\n const content = useMemo(() => {\r\n switch (view) {\r\n case \"default\":\r\n return null;\r\n\r\n case \"home\":\r\n return (\r\n
\r\n {HOME_ITEMS.map(({ icon: Icon, text }) => (\r\n \r\n \r\n {text}\r\n \r\n ))}\r\n
\r\n );\r\n\r\n case \"search\":\r\n return (\r\n
\r\n
\r\n \r\n \r\n
\r\n
\r\n {SEARCH_OPTIONS.map(({ icon: Icon, text }) => (\r\n \r\n \r\n {text}\r\n \r\n ))}\r\n
\r\n
\r\n );\r\n\r\n case \"notifications\":\r\n return (\r\n
\r\n {NOTIFICATION_TYPES.map((t) => (\r\n \r\n ))}\r\n
\r\n );\r\n\r\n case \"profile\":\r\n return (\r\n
\r\n {PROFILE_LINKS.map((t) => (\r\n \r\n ))}\r\n
\r\n \r\n
\r\n );\r\n\r\n case \"theme\":\r\n return (\r\n
\r\n {THEME_OPTIONS.map(({ key, icon: Icon, text }) => (\r\n setTheme(key as \"light\" | \"dark\" | \"system\")}\r\n className={`flex items-center justify-center gap-2 rounded-[12px] px-3 py-2 transition-all duration-100 ${\r\n theme === key\r\n ? \"bg-accent text-foreground\"\r\n : \"text-muted-foreground hover:bg-muted\"\r\n }`}\r\n >\r\n \r\n {text}\r\n \r\n ))}\r\n
\r\n );\r\n\r\n default:\r\n return null;\r\n }\r\n }, [view, theme]);\r\n\r\n return (\r\n \r\n {/* Hidden for measurement */}\r\n \r\n
\r\n {content}\r\n
\r\n
\r\n\r\n {/* Animated submenu */}\r\n \r\n {view !== \"default\" && (\r\n \r\n \r\n \r\n \r\n {content}\r\n \r\n \r\n \r\n \r\n )}\r\n \r\n\r\n {/* Toolbar */}\r\n
\r\n {MAIN_NAV.map(({ icon: Icon, name }) => (\r\n setView(view === name ? \"default\" : (name as any))}\r\n >\r\n \r\n \r\n ))}\r\n
\r\n \r\n );\r\n};\r\n\r\nexport default BottomMenu;\r\n", "type": "registry:component" } ], "type": "registry:component" }