SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
2.7 KB · 66 lines tsx
Raw Blame History
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