feat(web): improve ReactionSelector UX with hover visibility

- Add hover-based visibility for reaction selector in memo cards
- Show reaction selector only on card hover or when popover is open
- Add onOpenChange callback to ReactionSelector for state management
- Reorder action buttons for better visual hierarchy
- Simplify conditional rendering of comment link
This commit is contained in:
Claude 2025-10-23 21:20:15 +08:00
parent 16e0049490
commit 16425ed650
2 changed files with 32 additions and 20 deletions

View File

@ -46,6 +46,7 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
const [showEditor, setShowEditor] = useState<boolean>(false);
const [creator, setCreator] = useState(userStore.getUserByName(memo.creator));
const [showNSFWContent, setShowNSFWContent] = useState(props.showNsfwContent);
const [reactionSelectorOpen, setReactionSelectorOpen] = useState<boolean>(false);
const [previewImage, setPreviewImage] = useState<{ open: boolean; urls: string[]; index: number }>({
open: false,
urls: [],
@ -136,7 +137,7 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
) : (
<div
className={cn(
"relative flex flex-col justify-start items-start bg-card w-full px-4 py-3 mb-2 gap-2 text-card-foreground rounded-lg border border-border transition-colors",
"relative group flex flex-col justify-start items-start bg-card w-full px-4 py-3 mb-2 gap-2 text-card-foreground rounded-lg border border-border transition-colors",
className,
)}
>
@ -177,22 +178,16 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
)}
</div>
<div className="flex flex-row justify-end items-center select-none shrink-0 gap-2">
<div className="w-auto flex flex-row justify-between items-center gap-2">
{props.showVisibility && memo.visibility !== Visibility.PRIVATE && (
<Tooltip>
<TooltipTrigger>
<span className="flex justify-center items-center rounded-md p-1 hover:opacity-80">
<VisibilityIcon visibility={memo.visibility} />
</span>
</TooltipTrigger>
<TooltipContent>{t(`memo.visibility.${convertVisibilityToString(memo.visibility).toLowerCase()}` as any)}</TooltipContent>
</Tooltip>
)}
{currentUser && !isArchived && <ReactionSelector className="border-none w-auto h-auto" memo={memo} />}
</div>
{!isInMemoDetailPage && commentAmount > 0 && (
{currentUser && !isArchived && (
<ReactionSelector
className={cn("border-none w-auto h-auto", reactionSelectorOpen && "!block", "hidden group-hover:block")}
memo={memo}
onOpenChange={setReactionSelectorOpen}
/>
)}
{!isInMemoDetailPage && (
<Link
className={cn("flex flex-row justify-start items-center rounded-md p-1 hover:opacity-80", commentAmount === 0 && "invisible")}
className="flex flex-row justify-start items-center rounded-md p-1 hover:opacity-80"
to={`/${memo.name}#comments`}
viewTransition
state={{
@ -203,6 +198,16 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
{commentAmount > 0 && <span className="text-xs text-muted-foreground">{commentAmount}</span>}
</Link>
)}
{props.showVisibility && memo.visibility !== Visibility.PRIVATE && (
<Tooltip>
<TooltipTrigger>
<span className="flex justify-center items-center rounded-md hover:opacity-80">
<VisibilityIcon visibility={memo.visibility} />
</span>
</TooltipTrigger>
<TooltipContent>{t(`memo.visibility.${convertVisibilityToString(memo.visibility).toLowerCase()}` as any)}</TooltipContent>
</Tooltip>
)}
{props.showPinned && memo.pinned && (
<TooltipProvider>
<Tooltip>

View File

@ -12,10 +12,11 @@ import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
interface Props {
memo: Memo;
className?: string;
onOpenChange?: (open: boolean) => void;
}
const ReactionSelector = observer((props: Props) => {
const { memo, className } = props;
const { memo, className, onOpenChange } = props;
const currentUser = useCurrentUser();
const [open, setOpen] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
@ -23,8 +24,14 @@ const ReactionSelector = observer((props: Props) => {
useClickAway(containerRef, () => {
setOpen(false);
onOpenChange?.(false);
});
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
onOpenChange?.(newOpen);
};
const hasReacted = (reactionType: string) => {
return memo.reactions.some((r) => r.reactionType === reactionType && r.creator === currentUser?.name);
};
@ -51,15 +58,15 @@ const ReactionSelector = observer((props: Props) => {
} catch {
// skip error.
}
setOpen(false);
handleOpenChange(false);
};
return (
<Popover open={open} onOpenChange={setOpen}>
<Popover open={open} onOpenChange={handleOpenChange}>
<PopoverTrigger asChild>
<span
className={cn(
"h-7 w-7 flex justify-center items-center rounded-full border cursor-pointer transition-colors hover:opacity-80",
"h-7 w-7 flex justify-center items-center rounded-full border cursor-pointer transition-all hover:opacity-80",
className,
)}
>