TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1"use client";2import * as React from "react";3import { MoreHorizontal } from "lucide-react";4import { Button } from "@/components/ui/button";5import { ActionSheet, type ActionSheetItem } from "@/components/ui/sheet";6import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";7import { useIsMobile } from "@/lib/client/hooks";8import { cn } from "@/lib/utils";910/**11 * "⋯" actions for a list row / card. Phone: 44 px button + ActionSheet (also opened by the row's long-press through12 * the controlled `open` prop). Desktop: hover-revealed icon button + dropdown.13 */14export function RowMenu({ items, title, open, onOpenChange, className, alwaysVisible }: { items: (ActionSheetItem | "separator")[]; title?: string; open?: boolean; onOpenChange?: (o: boolean) => void; className?: string; alwaysVisible?: boolean }) {15 const isMobile = useIsMobile();16 const [inner, setInner] = React.useState(false);17 const isOpen = open ?? inner;18 const setOpen = onOpenChange ?? setInner;19 const label = title ? `Actions for ${title}` : "Actions";2021 if (isMobile) {22 return (23 <>24 <Button25 variant="ghost"26 size="icon"27 className={cn("tap shrink-0 text-fg-subtle", className)}28 aria-label={label}29 onClick={(e) => {30 e.preventDefault();31 e.stopPropagation();32 setOpen(true);33 }}34 >35 <MoreHorizontal />36 </Button>37 <ActionSheet open={isOpen} onOpenChange={setOpen} title={title} items={items} />38 </>39 );40 }4142 return (43 <DropdownMenu open={isOpen} onOpenChange={setOpen}>44 <DropdownMenuTrigger asChild>45 <Button variant="ghost" size="icon-sm" className={cn("shrink-0 text-fg-subtle", !alwaysVisible && "hover-reveal", className)} aria-label={label} onClick={(e) => e.stopPropagation()}>46 <MoreHorizontal />47 </Button>48 </DropdownMenuTrigger>49 <DropdownMenuContent align="end" className="w-56" onClick={(e) => e.stopPropagation()}>50 {items.map((it, i) =>51 it === "separator" ? (52 <DropdownMenuSeparator key={`sep-${i}`} />53 ) : (54 <DropdownMenuItem key={it.key} disabled={it.disabled} destructive={it.destructive} onSelect={it.onSelect}>55 {it.icon}56 <span className="min-w-0 flex-1 truncate">{it.label}</span>57 {it.hint ? <span className="text-[11px] text-fg-subtle">{it.hint}</span> : null}58 {it.selected ? <span className="text-accent">✓</span> : null}59 </DropdownMenuItem>60 ),61 )}62 </DropdownMenuContent>63 </DropdownMenu>64 );65}66