Standard · hover, focus, or tap an action
Choose an actionCompact · selected action: Review
Multi Button
tsx
"use client";import {CompactMultiButton,MultiButton,type MultiButtonItem,} from "@/components/godui/multi-button";import {Archive,Bookmark,Copy,Download,EllipsisVertical,Eye,MessageSquare,Share2,Trash2,} from "lucide-react";import { useEffect, useRef, useState } from "react";const actionDefinitions = [{ id: "review", icon: Eye, label: "Review" },{ id: "download", icon: Download, label: "Download" },{ id: "share", icon: Share2, label: "Share" },{ id: "archive", icon: Archive, label: "Archive" },{ id: "duplicate", icon: Copy, label: "Duplicate" },{ id: "comment", icon: MessageSquare, label: "Comment" },{ id: "save", icon: Bookmark, label: "Save" },{ id: "delete", icon: Trash2, label: "Delete" },] satisfies Omit<MultiButtonItem, "onClick">[];export function ActionRail() {const [selectedId, setSelectedId] = useState("review");const [lastAction, setLastAction] = useState<string | null>(null);const [gooey, setGooey] = useState(false);const [optionCount, setOptionCount] = useState(4);const [isMobile, setIsMobile] = useState(false);const demoRef = useRef<HTMLDivElement>(null);const maxOptions = isMobile ? 4 : 8;const visibleDefinitions = actionDefinitions.slice(0, optionCount);const items = visibleDefinitions.map((item) => ({ ...item, onClick: () => setLastAction(item.label),}));const compactItems = visibleDefinitions.map((item) => ({ ...item, onClick: () => { setSelectedId(item.id); setLastAction(item.label); },}));const changeOptionCount = (nextCount: number) => { const boundedCount = Math.min(nextCount, maxOptions); const nextDefinitions = actionDefinitions.slice(0, boundedCount); setOptionCount(boundedCount); setSelectedId((current) => nextDefinitions.some((item) => item.id === current) ? current : nextDefinitions[0].id, );};useEffect(() => { const view = demoRef.current?.ownerDocument.defaultView ?? window; const mediaQuery = view.matchMedia("(max-width: 639px)"); const syncMobileLimit = () => { const mobile = mediaQuery.matches; setIsMobile(mobile); if (!mobile) return; setOptionCount((current) => Math.min(current, 4)); setSelectedId((current) => actionDefinitions.slice(0, 4).some((item) => item.id === current) ? current : actionDefinitions[0].id, ); }; syncMobileLimit(); mediaQuery.addEventListener("change", syncMobileLimit); view.addEventListener("resize", syncMobileLimit); return () => { mediaQuery.removeEventListener("change", syncMobileLimit); view.removeEventListener("resize", syncMobileLimit); };}, []);return ( <div ref={demoRef} className="flex w-full max-w-xl flex-col items-center gap-6"> <MultiButton items={items} gooey={gooey} highlightColor="var(--primary-foreground)" size="lg" /> <CompactMultiButton items={compactItems} selectedId={selectedId} restIcon={EllipsisVertical} restAriaLabel="Open actions" gooey={gooey} highlightColor="var(--primary)" size="lg" variant="outline" /> <label> Options <input type="range" min={2} max={maxOptions} value={optionCount} onChange={(event) => changeOptionCount(Number(event.target.value))} /> <output>{optionCount}</output> </label> <div className="flex rounded-full bg-muted p-1" aria-label="Motion treatment"> <button type="button" aria-pressed={!gooey} onClick={() => setGooey(false)}> Original </button> <button type="button" aria-pressed={gooey} onClick={() => setGooey(true)}> Gooey </button> </div> <p aria-live="polite">{lastAction ? `Last action: ${lastAction}` : "Choose an action"}</p> </div>);}