GodUIGodUI
113Follow on X
Standard · hover, focus, or tap an action
Choose an action
Compact · 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>);}