text highlight and og image
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useCopyFeedback, type Position } from "./shared";
|
||||
import { buildHighlightUrl } from "@/lib/text-highlight";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Bookmark, FolderPlus, Link2, AtSign, Check } from "lucide-react";
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
} from "@/components/ui/drawer";
|
||||
|
||||
export function MorePopover({
|
||||
open,
|
||||
position,
|
||||
onClose,
|
||||
onBookmark,
|
||||
onSaveToCollection,
|
||||
onMention,
|
||||
isMobile,
|
||||
selectedText,
|
||||
}: {
|
||||
open: boolean;
|
||||
position: Position;
|
||||
onClose: () => void;
|
||||
onBookmark: () => void;
|
||||
onSaveToCollection: () => void;
|
||||
onMention: () => void;
|
||||
isMobile: boolean;
|
||||
selectedText: string;
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const { copied, copy } = useCopyFeedback();
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || isMobile) return;
|
||||
function handleClick(e: MouseEvent) {
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
document.addEventListener("mousedown", handleClick);
|
||||
}, 10);
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
document.removeEventListener("mousedown", handleClick);
|
||||
};
|
||||
}, [open, onClose, isMobile]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
const actions = [
|
||||
{
|
||||
key: "bookmark",
|
||||
label: "Add Bookmark",
|
||||
description: "Save your place",
|
||||
icon: <Bookmark className="size-5" />,
|
||||
onClick: onBookmark,
|
||||
},
|
||||
{
|
||||
key: "collection",
|
||||
label: "Save to Collection",
|
||||
description: "Organize into a collection",
|
||||
icon: <FolderPlus className="size-5" />,
|
||||
onClick: onSaveToCollection,
|
||||
},
|
||||
{
|
||||
key: "copy-link",
|
||||
label: copied ? "Link Copied!" : "Copy Link",
|
||||
description: "Copy a link to this page",
|
||||
icon: copied ? (
|
||||
<Check className="size-5" />
|
||||
) : (
|
||||
<Link2 className="size-5" />
|
||||
),
|
||||
onClick: () => copy(buildHighlightUrl(selectedText)),
|
||||
},
|
||||
{
|
||||
key: "mention",
|
||||
label: "Mention",
|
||||
description: "Tag someone with this quote",
|
||||
icon: <AtSign className="size-5" />,
|
||||
onClick: onMention,
|
||||
},
|
||||
];
|
||||
|
||||
// Mobile: bottom drawer
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={(o) => !o && onClose()}>
|
||||
<DrawerContent className="bg-black dark:bg-[#1a1a1a] border-white/10">
|
||||
<DrawerHeader className="text-left border-b border-white/10 pb-4">
|
||||
<DrawerTitle className="text-white">More Options</DrawerTitle>
|
||||
</DrawerHeader>
|
||||
<div className="p-4 space-y-1">
|
||||
{actions.map((action) => (
|
||||
<button
|
||||
key={action.key}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
action.onClick();
|
||||
if (action.key !== "copy-link") {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
className="flex items-center gap-3 w-full px-3 py-3 rounded-lg text-left hover:bg-white/10 transition-colors"
|
||||
>
|
||||
<div className="flex items-center justify-center size-10 rounded-lg bg-white/10 text-white/60">
|
||||
{action.icon}
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-white/90">
|
||||
{action.label}
|
||||
</p>
|
||||
<p className="text-xs text-white/50">{action.description}</p>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="p-4 pt-0">
|
||||
<DrawerClose asChild>
|
||||
<button className="w-full py-2.5 rounded-lg border border-white/15 text-sm font-medium text-white/60 hover:bg-white/10 transition-colors">
|
||||
Cancel
|
||||
</button>
|
||||
</DrawerClose>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
// Desktop: floating popover
|
||||
const popoverW = 220;
|
||||
const popoverH = actions.length * 44 + 8;
|
||||
const pad = 8;
|
||||
|
||||
let popLeft = position.x;
|
||||
let popTop = position.y + 8;
|
||||
|
||||
if (popLeft + popoverW > window.innerWidth - pad) {
|
||||
popLeft = window.innerWidth - popoverW - pad;
|
||||
}
|
||||
if (popLeft < pad) {
|
||||
popLeft = pad;
|
||||
}
|
||||
if (popTop + popoverH > window.innerHeight - pad) {
|
||||
popTop = position.y - popoverH - 8;
|
||||
}
|
||||
if (popTop < pad) {
|
||||
popTop = pad;
|
||||
}
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
position: "fixed",
|
||||
zIndex: 90,
|
||||
left: popLeft,
|
||||
top: popTop,
|
||||
};
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
ref={ref}
|
||||
style={style}
|
||||
className="w-55 rounded-lg border border-white/10 bg-black shadow-2xl py-1.5 animate-in fade-in-0 zoom-in-95 dark:bg-[#1a1a1a] dark:border-white/15"
|
||||
>
|
||||
{actions.map((action) => (
|
||||
<button
|
||||
key={action.key}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
action.onClick();
|
||||
if (action.key !== "copy-link") {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-white/90 hover:bg-white/10 transition-colors text-left"
|
||||
>
|
||||
<span className="text-white/50">{action.icon}</span>
|
||||
{action.label}
|
||||
</button>
|
||||
))}
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user