diff --git a/src/components/people/PersonItem.tsx b/src/components/people/PersonItem.tsx index ac59872..73660a4 100644 --- a/src/components/people/PersonItem.tsx +++ b/src/components/people/PersonItem.tsx @@ -5,12 +5,20 @@ import { PersonMergeDropdown } from "./PersonMergeDropdown"; import PersonBirthdayCell from "./PersonBirthdayCell"; import clsx from "clsx"; import Link from "next/link"; -import { ArrowUpRight, Eye, EyeOff, Info } from "lucide-react"; +import { ArrowUpRight, Eye, EyeOff, GitMerge, Info, MoreHorizontal, Share2 } from "lucide-react"; import { useConfig } from "@/contexts/ConfigContext"; import { useToast } from "../ui/use-toast"; import ShareAssetsTrigger from "../shared/ShareAssetsTrigger"; import { Autocomplete, AutocompleteOption } from "../ui/autocomplete"; import { AlertDialog, IAlertDialogActions } from "../ui/alert-dialog"; +import { Tooltip } from "../ui/tooltip"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "../ui/dropdown-menu"; interface IProps { person: IPerson; @@ -62,7 +70,7 @@ export default function PersonItem({ person, onRemove }: IProps) { "hover:shadow-lg hover:border-border/80", { "opacity-40": formData.isHidden, - "border-blue-500/60 shadow-blue-500/10 shadow-md": formData.name, + "border-border shadow-sm": formData.name, "border-border/40": !formData.name, } )} @@ -82,75 +90,83 @@ export default function PersonItem({ person, onRemove }: IProps) { )} {/* Gradient overlay — always visible at bottom */} -
+
- {/* Asset count — bottom of image */} -
- - {formData.isHidden && Hidden ·} - {person.assetCount} assets + {/* Asset count — bottom-left of image */} +
+ + {formData.isHidden && Hidden ·} + {person.assetCount}
- {/* Action row — slides in from top on hover */} -
- {/* Left: immich + info links */} -
- e.stopPropagation()} - > - - - e.stopPropagation()} - > - - -
+ {/* Three-dot menu — top-right, visible on hover */} +
+ + + + + + + + + View details + + + + + + Open in Immich + + + + + + Share assets + + + +
- {/* Right: hide + share + merge */} -
+ {/* Quick actions — bottom-right, visible on hover */} +
+ - svg]:h-[13px] [&>svg]:w-[13px]", - }} - /> - -
+ + + + } + /> + +
{/* Name + birthday section */} -
+
{!editMode ? ( ) : ( @@ -161,7 +177,7 @@ export default function PersonItem({ person, onRemove }: IProps) { ) } type="text" - className="text-sm font-semibold w-full px-1.5 py-0.5 rounded-md" + className="text-xs font-semibold w-full px-1 py-0.5 rounded-md" defaultValue={formData.name} placeholder="Enter name" autoFocus diff --git a/src/components/people/PersonMergeDropdown.tsx b/src/components/people/PersonMergeDropdown.tsx index a0c5eb6..a5904d6 100644 --- a/src/components/people/PersonMergeDropdown.tsx +++ b/src/components/people/PersonMergeDropdown.tsx @@ -41,6 +41,7 @@ interface PersonMergeDropdownProps { onRemove?: (person: IPerson) => void; onComplete?: (mergedPerson: IPerson) => void; triggerClassName?: string; + triggerIcon?: React.ReactNode; } export function PersonMergeDropdown({ @@ -48,6 +49,7 @@ export function PersonMergeDropdown({ onRemove, onComplete, triggerClassName, + triggerIcon, }: PersonMergeDropdownProps) { const [searchedPeople, setSearchedPeople] = useState(null); const [similarPeople, setSimilarPeople] = useState([]); @@ -243,7 +245,7 @@ export function PersonMergeDropdown({ variant="outline" className={cn("!py-0.5 !px-2 text-xs h-7", triggerClassName)} > - Merge + {triggerIcon ?? "Merge"} diff --git a/src/components/shared/ShareAssetsTrigger.tsx b/src/components/shared/ShareAssetsTrigger.tsx index 366658a..5479230 100644 --- a/src/components/shared/ShareAssetsTrigger.tsx +++ b/src/components/shared/ShareAssetsTrigger.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react' import { Dialog, DialogTitle, DialogContent, DialogHeader, DialogTrigger, DialogDescription } from '../ui/dialog'; import { Button, ButtonProps } from '../ui/button'; +import { DropdownMenuItem } from '../ui/dropdown-menu'; import { ShareLinkFilters } from '@/types/shareLink'; import { generateShareLink } from '@/handlers/api/shareLink.handler'; import { createShareKey, getShareKey } from '@/handlers/api/share-key.handler'; @@ -13,9 +14,11 @@ import { Select, SelectValue, SelectContent, SelectItem, SelectTrigger } from '. interface ShareAssetsTriggerProps { filters: ShareLinkFilters buttonProps?: ButtonProps + asDropdownItem?: boolean + children?: React.ReactNode } -export default function ShareAssetsTrigger({ filters, buttonProps }: ShareAssetsTriggerProps) { +export default function ShareAssetsTrigger({ filters, buttonProps, asDropdownItem, children }: ShareAssetsTriggerProps) { const [generatedLink, setGeneratedLink] = useState(null); const [loading, setLoading] = useState(false); const [errorMessage, setErrorMessage] = useState(null); @@ -91,7 +94,13 @@ export default function ShareAssetsTrigger({ filters, buttonProps }: ShareAssets return ( { if (open) checkShareKey(); }}> - + {asDropdownItem ? ( + e.preventDefault()} className="gap-2"> + {children} + + ) : ( + + )}