Compare commits
2 Commits
0022d807d0
...
521ecce68f
| Author | SHA1 | Date | |
|---|---|---|---|
| 521ecce68f | |||
| 5d2cad255c |
@@ -3,7 +3,7 @@
|
|||||||
import { useMemo, useRef, useState } from "react";
|
import { useMemo, useRef, useState } from "react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Check, Package, Loader2, GripVertical, Trash2, Search, Sparkles } from "lucide-react";
|
import { Check, Package, Loader2, GripVertical, MoreVertical, Trash2, Search, Sparkles } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -14,7 +14,16 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSub,
|
||||||
|
DropdownMenuSubContent,
|
||||||
|
DropdownMenuSubTrigger,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -615,12 +624,12 @@ type ItemRowProps = {
|
|||||||
function ItemRow({ item, readOnly, categoryOptions, categoryLabel, tShopping, onToggle, onChangeCategory, onDeleteRequest, dragHandle }: ItemRowProps) {
|
function ItemRow({ item, readOnly, categoryOptions, categoryLabel, tShopping, onToggle, onChangeCategory, onDeleteRequest, dragHandle }: ItemRowProps) {
|
||||||
const [newCategory, setNewCategory] = useState("");
|
const [newCategory, setNewCategory] = useState("");
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex items-center gap-2 px-4 py-3 hover:bg-muted/30 transition-colors">
|
<div className="w-full flex items-center gap-3 px-4 py-3 hover:bg-muted/30 transition-colors">
|
||||||
{dragHandle}
|
{dragHandle}
|
||||||
<button
|
<button
|
||||||
onClick={() => onToggle(item)}
|
onClick={() => onToggle(item)}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
className="flex flex-1 min-w-0 items-center gap-3 text-left disabled:cursor-default"
|
className="flex flex-1 items-center gap-3 text-left disabled:cursor-default"
|
||||||
>
|
>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"h-5 w-5 rounded border-2 flex items-center justify-center shrink-0 transition-colors",
|
"h-5 w-5 rounded border-2 flex items-center justify-center shrink-0 transition-colors",
|
||||||
@@ -628,7 +637,7 @@ function ItemRow({ item, readOnly, categoryOptions, categoryLabel, tShopping, on
|
|||||||
)}>
|
)}>
|
||||||
{item.checked && <Check className="h-3 w-3 text-primary-foreground" />}
|
{item.checked && <Check className="h-3 w-3 text-primary-foreground" />}
|
||||||
</div>
|
</div>
|
||||||
<span className={cn("flex-1 min-w-0 truncate text-sm", item.checked && "line-through text-muted-foreground")}>
|
<span className={cn("flex-1 text-sm", item.checked && "line-through text-muted-foreground")}>
|
||||||
{item.rawName}
|
{item.rawName}
|
||||||
</span>
|
</span>
|
||||||
{item.inPantry && (
|
{item.inPantry && (
|
||||||
@@ -643,63 +652,55 @@ function ItemRow({ item, readOnly, categoryOptions, categoryLabel, tShopping, on
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
{!readOnly && (
|
{!readOnly && (
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
<DropdownMenu>
|
||||||
<Select
|
<DropdownMenuTrigger className="shrink-0 rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground">
|
||||||
value={item.aisle ?? OTHER_KEY}
|
<MoreVertical className="h-4 w-4" />
|
||||||
onValueChange={(v) => onChangeCategory(item, v === OTHER_KEY ? null : v)}
|
</DropdownMenuTrigger>
|
||||||
>
|
<DropdownMenuContent align="end">
|
||||||
<SelectTrigger className="h-7 max-w-28 border-none bg-transparent px-1.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-3">
|
<DropdownMenuSub>
|
||||||
<SelectValue>
|
<DropdownMenuSubTrigger>{tShopping("changeCategory")}</DropdownMenuSubTrigger>
|
||||||
{(v: string) => <span className="truncate">{v === OTHER_KEY ? tShopping("aisleOther") : categoryLabel(v)}</span>}
|
<DropdownMenuSubContent className="w-56">
|
||||||
</SelectValue>
|
{categoryOptions.map((category) => (
|
||||||
</SelectTrigger>
|
<DropdownMenuItem key={category} onClick={() => onChangeCategory(item, category)}>
|
||||||
<SelectContent className="w-56">
|
{categoryLabel(category)}
|
||||||
{categoryOptions.map((category) => (
|
</DropdownMenuItem>
|
||||||
<SelectItem key={category} value={category}>{categoryLabel(category)}</SelectItem>
|
))}
|
||||||
))}
|
<DropdownMenuItem onClick={() => onChangeCategory(item, null)}>
|
||||||
<SelectItem value={OTHER_KEY}>{tShopping("aisleOther")}</SelectItem>
|
{tShopping("aisleOther")}
|
||||||
<div className="flex items-center gap-1 p-1.5" onClick={(e) => e.stopPropagation()}>
|
</DropdownMenuItem>
|
||||||
<Input
|
<DropdownMenuSeparator />
|
||||||
value={newCategory}
|
<div className="flex items-center gap-1 p-1.5" onClick={(e) => e.stopPropagation()}>
|
||||||
onChange={(e) => setNewCategory(e.target.value)}
|
<Input
|
||||||
onKeyDown={(e) => {
|
value={newCategory}
|
||||||
if (e.key === "Enter" && newCategory.trim()) {
|
onChange={(e) => setNewCategory(e.target.value)}
|
||||||
onChangeCategory(item, newCategory.trim());
|
onKeyDown={(e) => {
|
||||||
setNewCategory("");
|
if (e.key === "Enter" && newCategory.trim()) {
|
||||||
}
|
onChangeCategory(item, newCategory.trim());
|
||||||
}}
|
setNewCategory("");
|
||||||
placeholder={tShopping("newCategoryPlaceholder")}
|
}
|
||||||
className="h-7 text-xs"
|
}}
|
||||||
/>
|
placeholder={tShopping("newCategoryPlaceholder")}
|
||||||
<Button
|
className="h-7 text-xs"
|
||||||
size="sm"
|
/>
|
||||||
variant="ghost"
|
<Button
|
||||||
className="h-7 shrink-0 px-2 text-xs"
|
size="sm"
|
||||||
disabled={!newCategory.trim()}
|
variant="ghost"
|
||||||
onClick={() => { onChangeCategory(item, newCategory.trim()); setNewCategory(""); }}
|
className="h-7 shrink-0 px-2 text-xs"
|
||||||
>
|
disabled={!newCategory.trim()}
|
||||||
{tShopping("addCategory")}
|
onClick={() => { onChangeCategory(item, newCategory.trim()); setNewCategory(""); }}
|
||||||
</Button>
|
>
|
||||||
</div>
|
{tShopping("addCategory")}
|
||||||
</SelectContent>
|
</Button>
|
||||||
</Select>
|
</div>
|
||||||
<TooltipProvider>
|
</DropdownMenuSubContent>
|
||||||
<Tooltip>
|
</DropdownMenuSub>
|
||||||
<TooltipTrigger render={
|
<DropdownMenuSeparator />
|
||||||
<Button
|
<DropdownMenuItem variant="destructive" onClick={() => onDeleteRequest(item)}>
|
||||||
variant="ghost"
|
<Trash2 className="h-4 w-4" />
|
||||||
size="icon"
|
{tShopping("deleteItem")}
|
||||||
className="h-7 w-7 text-muted-foreground hover:text-destructive"
|
</DropdownMenuItem>
|
||||||
aria-label={tShopping("deleteItem")}
|
</DropdownMenuContent>
|
||||||
onClick={() => onDeleteRequest(item)}
|
</DropdownMenu>
|
||||||
>
|
|
||||||
<Trash2 className="h-3.5 w-3.5" />
|
|
||||||
</Button>
|
|
||||||
} />
|
|
||||||
<TooltipContent>{tShopping("deleteItem")}</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,15 +3,10 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { MoreVertical, Pencil, Trash2 } from "lucide-react";
|
import { Pencil, Trash2 } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button, buttonVariants } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -96,30 +91,45 @@ export function ShoppingListActionsMenu({ listId, name, onRenamed, onDeleted, re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rows on the index page are wrapped in a Link — stop the click from bubbling to it
|
||||||
|
// and prevent the anchor's default navigation.
|
||||||
|
function stopRowNavigation(e: React.MouseEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownMenu>
|
<TooltipProvider>
|
||||||
<DropdownMenuTrigger
|
<div className={cn("flex items-center gap-1", className)}>
|
||||||
aria-label={t("actionsMenuLabel")}
|
<Tooltip>
|
||||||
className={cn(buttonVariants({ variant: "ghost", size: "icon" }), className)}
|
<TooltipTrigger render={
|
||||||
onClick={(e: React.MouseEvent) => {
|
<Button
|
||||||
// Rows on the index page are wrapped in a Link — stop the click
|
variant="ghost"
|
||||||
// from bubbling to it and prevent the anchor's default navigation.
|
size="icon"
|
||||||
e.preventDefault();
|
aria-label={t("rename")}
|
||||||
e.stopPropagation();
|
onClick={(e: React.MouseEvent) => { stopRowNavigation(e); openRename(); }}
|
||||||
}}
|
>
|
||||||
>
|
<Pencil className="h-4 w-4" />
|
||||||
<MoreVertical className="h-4 w-4" />
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
} />
|
||||||
<DropdownMenuContent align="end">
|
<TooltipContent>{t("rename")}</TooltipContent>
|
||||||
<DropdownMenuItem onClick={openRename}>
|
</Tooltip>
|
||||||
<Pencil className="h-4 w-4 mr-2" /> {t("rename")}
|
<Tooltip>
|
||||||
</DropdownMenuItem>
|
<TooltipTrigger render={
|
||||||
<DropdownMenuItem variant="destructive" onClick={() => setDeleteOpen(true)}>
|
<Button
|
||||||
<Trash2 className="h-4 w-4 mr-2" /> {tCommon("delete")}
|
variant="ghost"
|
||||||
</DropdownMenuItem>
|
size="icon"
|
||||||
</DropdownMenuContent>
|
aria-label={tCommon("delete")}
|
||||||
</DropdownMenu>
|
onClick={(e: React.MouseEvent) => { stopRowNavigation(e); setDeleteOpen(true); }}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
} />
|
||||||
|
<TooltipContent>{tCommon("delete")}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</TooltipProvider>
|
||||||
|
|
||||||
<Dialog open={renameOpen} onOpenChange={setRenameOpen}>
|
<Dialog open={renameOpen} onOpenChange={setRenameOpen}>
|
||||||
<DialogContent className="max-w-md">
|
<DialogContent className="max-w-md">
|
||||||
|
|||||||
Reference in New Issue
Block a user