Bloom Menu
beui/bloom-menuFreeBlock
A button that morphs open into a menu and blooms iris-out from the center, the grid revealing in every direction with radially staggered items.
Install it
npx shadcn@latest add https://beui.dev/r/bloom-menu.jsonSource
1"use client";2// beui.dev/components/blocks/bloom-menu34import {5 Bell,6 FileText,7 FolderClosed,8 LayoutGrid,9 Link,10 Plus,11 Table,12 X,13} from "lucide-react";14import { AnimatePresence, motion, useReducedMotion } from "motion/react";15import { type ComponentType, useEffect, useId, useRef, useState } from "react";16import { EASE_OUT } from "@/lib/ease";17import { cn } from "@/lib/utils";1819type MenuItem = { label: string; icon: ComponentType<{ className?: string }> };2021const ITEMS: MenuItem[] = [22 { label: "Doc", icon: FileText },23 { label: "Board", icon: LayoutGrid },24 { label: "Table", icon: Table },25 { label: "Folder", icon: FolderClosed },26 { label: "Reminder", icon: Bell },27 { label: "Link", icon: Link },28];2930// Folder-open feel: a touch of overshoot as the panel expands, kept subtle.31const SPRING_FOLDER = {32 type: "spring",33 stiffness: 300,34 damping: 32,35 mass: 0.9,36} as const;3738export interface BloomMenuProps {39 items?: MenuItem[];40 onSelect?: (label: string) => void;41 className?: string;42}4344export function BloomMenu({45 items = ITEMS,46 onSelect,47 className,48}: BloomMenuProps) {49 const [open, setOpen] = useState(false);50 const reduce = useReducedMotion();51 const layoutId = useId();52 const ref = useRef<HTMLDivElement>(null);5354 useEffect(() => {55 if (!open) return;56 const onKey = (e: KeyboardEvent) => {57 if (e.key === "Escape") setOpen(false);58 };59 const onPointer = (e: PointerEvent) => {60 if (ref.current && !ref.current.contains(e.target as Node))61 setOpen(false);62 };63 window.addEventListener("keydown", onKey);64 window.addEventListener("pointerdown", onPointer);65 return () => {66 window.removeEventListener("keydown", onKey);67 window.removeEventListener("pointerdown", onPointer);68 };69 }, [open]);7071 const morph = reduce ? { duration: 0.15 } : SPRING_FOLDER;7273 return (74 <div ref={ref} className={cn("relative inline-flex", className)}>75 {/* spacer fixes the anchor to the trigger size */}76 <div className="h-11 w-36" aria-hidden />7778 {/* Centering box sized to the OPEN panel and centered on the trigger.79 place-items-center only centers an item that fits its cell, so the cell80 must be as wide as the panel — otherwise the overflow left-anchors and81 the panel expands rightward. The box is a fixed size per viewport (vw82 doesn't change mid-animation), so its -translate centering never drifts83// … truncated
What it pulls in
npm packages
Files it writes
More from beui
All 104 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| File Upload Attachment Uploadattachment-upload | beui | Blocks | Free | 4 deps · 5 files | |
| Availability Scheduleravailability-scheduler | beui | Blocks | Free | 4 deps · 15 files | |
| Command Palettecommand-palette | beui | Blocks | Free | 4 deps · 3 files | |
| Dynamic Islanddynamic-island | beui | Blocks | Free | 3 deps · 3 files | |
| Expandable Action Barexpandable-action-bar | beui | Blocks | Free | 3 deps · 2 files | |
| Expandable Tabsexpandable-tabs | beui | Blocks | Free | 3 deps · 3 files | |
| Feedback Widgetfeedback-widget | beui | Blocks | Free | 4 deps · 9 files | |
| File Upload Upload Queuefile-upload | beui | Blocks | Free | 4 deps · 3 files |
