mirror of https://github.com/usememos/memos.git
chore(web): unify metadata badge styling and fix event handling
- Remove MetadataBadge component and inline styles consistently - Add pointer/mouse event handlers to prevent drag interference - Fix LocationDisplay mode handling and popover interaction - Clean up RelationList empty state logic
This commit is contained in:
parent
659c63165b
commit
dc398cf6a7
|
|
@ -28,7 +28,7 @@ const AttachmentCard = ({ attachment, mode, onRemove, onClick, className, showTh
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group relative inline-flex items-center gap-1.5 px-2 h-7 rounded-md border border-border bg-background text-secondary-foreground text-xs transition-colors hover:bg-accent",
|
||||
"relative inline-flex items-center gap-1.5 px-2 h-7 rounded-md border border-border bg-background text-secondary-foreground text-xs transition-colors hover:bg-accent",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
|
@ -41,6 +41,12 @@ const AttachmentCard = ({ attachment, mode, onRemove, onClick, className, showTh
|
|||
{onRemove && (
|
||||
<button
|
||||
className="shrink-0 rounded hover:bg-accent transition-colors p-0.5"
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
|
|||
|
|
@ -1,53 +1,60 @@
|
|||
import { LatLng } from "leaflet";
|
||||
import { ExternalLinkIcon, MapPinIcon } from "lucide-react";
|
||||
import { MapPinIcon, XIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Location } from "@/types/proto/api/v1/memo_service";
|
||||
import LeafletMap from "../LeafletMap";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||
import MetadataBadge from "./MetadataBadge";
|
||||
import { BaseMetadataProps } from "./types";
|
||||
|
||||
interface LocationDisplayProps extends BaseMetadataProps {
|
||||
location?: Location;
|
||||
onRemove?: () => void;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified Location component for both editor and view modes
|
||||
*
|
||||
* Editor mode: Shows badge with remove button
|
||||
* View mode: Shows badge with popover map on click
|
||||
*/
|
||||
const LocationDisplay = ({ location, mode, onRemove, onClick, className }: LocationDisplayProps) => {
|
||||
const LocationDisplay = ({ location, mode, onRemove, className }: LocationDisplayProps) => {
|
||||
const [popoverOpen, setPopoverOpen] = useState<boolean>(false);
|
||||
|
||||
if (!location) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const displayText = location.placeholder || `[${location.latitude}, ${location.longitude}]`;
|
||||
const displayText = location.placeholder || `Position: [${location.latitude}, ${location.longitude}]`;
|
||||
|
||||
// Editor mode: Simple badge with remove button
|
||||
if (mode === "edit") {
|
||||
return (
|
||||
<div className="w-full flex flex-row flex-wrap gap-2 mt-2">
|
||||
<MetadataBadge icon={<MapPinIcon className="w-3.5 h-3.5" />} onRemove={onRemove} onClick={onClick} className={className}>
|
||||
{displayText}
|
||||
</MetadataBadge>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// View mode: Badge with popover map
|
||||
return (
|
||||
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="w-full flex flex-row flex-wrap gap-2">
|
||||
<MetadataBadge icon={<MapPinIcon className="w-3.5 h-3.5" />} onClick={() => setPopoverOpen(true)} className={className}>
|
||||
<span>{displayText}</span>
|
||||
<ExternalLinkIcon className="w-2.5 h-2.5 ml-1 opacity-50" />
|
||||
</MetadataBadge>
|
||||
<div
|
||||
className={cn(
|
||||
"w-full max-w-full flex flex-row gap-2",
|
||||
"relative inline-flex items-center gap-1.5 px-2 h-7 rounded-md border border-border bg-background hover:bg-accent text-secondary-foreground text-xs transition-colors",
|
||||
mode === "view" && "cursor-pointer",
|
||||
className,
|
||||
)}
|
||||
onClick={mode === "view" ? () => setPopoverOpen(true) : undefined}
|
||||
>
|
||||
<span className="shrink-0 text-muted-foreground">
|
||||
<MapPinIcon className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
<span className="text-nowrap truncate">{displayText}</span>
|
||||
{onRemove && (
|
||||
<button
|
||||
className="shrink-0 rounded hover:bg-accent transition-colors p-0.5"
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onRemove();
|
||||
}}
|
||||
>
|
||||
<XIcon className="w-3 h-3 text-muted-foreground hover:text-foreground" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start">
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
import { XIcon } from "lucide-react";
|
||||
import { ReactNode } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface MetadataBadgeProps {
|
||||
icon: ReactNode;
|
||||
children: ReactNode;
|
||||
onRemove?: () => void;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
maxWidth?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared badge component for metadata display (Location, Tags, etc.)
|
||||
* Provides consistent styling across editor and view modes
|
||||
*/
|
||||
const MetadataBadge = ({ icon, children, onRemove, onClick, className, maxWidth = "max-w-[160px]" }: MetadataBadgeProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group relative inline-flex items-center gap-1.5 px-2 h-7 rounded-md border border-border bg-background text-secondary-foreground text-xs transition-colors",
|
||||
onClick && "cursor-pointer hover:bg-accent",
|
||||
className,
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<span className="shrink-0 text-muted-foreground">{icon}</span>
|
||||
<span className={cn("truncate", maxWidth)}>{children}</span>
|
||||
{onRemove && (
|
||||
<button
|
||||
className="shrink-0 rounded hover:bg-accent transition-colors p-0.5"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onRemove();
|
||||
}}
|
||||
>
|
||||
<XIcon className="w-3 h-3 text-muted-foreground hover:text-foreground" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetadataBadge;
|
||||
|
|
@ -27,14 +27,30 @@ const RelationCard = ({ memo, mode, onRemove, parentPage, className }: RelationC
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group relative inline-flex items-center gap-1.5 px-2 h-7 rounded-md border border-border bg-background text-secondary-foreground text-xs transition-colors hover:bg-accent cursor-pointer",
|
||||
"relative inline-flex items-center gap-1.5 px-2 h-7 rounded-md border border-border bg-background text-secondary-foreground text-xs transition-colors hover:bg-accent",
|
||||
className,
|
||||
)}
|
||||
onClick={onRemove}
|
||||
>
|
||||
<LinkIcon className="w-3.5 h-3.5 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate max-w-[160px]">{memo.snippet}</span>
|
||||
<XIcon className="w-3 h-3 shrink-0 text-muted-foreground" />
|
||||
{onRemove && (
|
||||
<button
|
||||
className="shrink-0 rounded hover:bg-accent transition-colors p-0.5"
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onRemove();
|
||||
}}
|
||||
>
|
||||
<XIcon className="w-3 h-3 text-muted-foreground hover:text-foreground" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -43,7 +59,7 @@ const RelationCard = ({ memo, mode, onRemove, parentPage, className }: RelationC
|
|||
return (
|
||||
<Link
|
||||
className={cn(
|
||||
"w-full flex flex-row justify-start items-center text-sm leading-5 text-muted-foreground hover:text-foreground hover:bg-accent rounded px-2 py-1 transition-colors",
|
||||
"w-full flex flex-row justify-start items-center text-sm leading-5 text-muted-foreground hover:text-foreground hover:bg-accent rounded px-1 py-1 transition-colors",
|
||||
className,
|
||||
)}
|
||||
to={`/${memo.name}`}
|
||||
|
|
@ -52,7 +68,7 @@ const RelationCard = ({ memo, mode, onRemove, parentPage, className }: RelationC
|
|||
from: parentPage,
|
||||
}}
|
||||
>
|
||||
<span className="text-xs opacity-60 leading-4 border border-border font-mono px-1 rounded-full mr-1">{memoId.slice(0, 6)}</span>
|
||||
<span className="text-[10px] opacity-60 leading-4 border border-border font-mono px-1 rounded-full mr-1">{memoId.slice(0, 6)}</span>
|
||||
<span className="truncate">{memo.snippet}</span>
|
||||
</Link>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -52,13 +52,17 @@ const RelationList = observer(({ relations, currentMemoName, mode, onRelationsCh
|
|||
|
||||
// Fetch full memo details for editor mode
|
||||
useEffect(() => {
|
||||
if (mode === "edit" && referencingRelations.length > 0) {
|
||||
if (mode === "edit") {
|
||||
(async () => {
|
||||
if (referencingRelations.length > 0) {
|
||||
const requests = referencingRelations.map(async (relation) => {
|
||||
return await memoStore.getOrFetchMemoByName(relation.relatedMemo!.name, { skipStore: true });
|
||||
});
|
||||
const list = await Promise.all(requests);
|
||||
setReferencingMemos(list);
|
||||
} else {
|
||||
setReferencingMemos([]);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}, [mode, relations]);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ export { default as AttachmentList } from "./AttachmentList";
|
|||
export { default as RelationList } from "./RelationList";
|
||||
|
||||
// Base components (can be used for other metadata types)
|
||||
export { default as MetadataBadge } from "./MetadataBadge";
|
||||
export { default as MetadataCard } from "./MetadataCard";
|
||||
export { default as AttachmentCard } from "./AttachmentCard";
|
||||
export { default as RelationCard } from "./RelationCard";
|
||||
|
|
|
|||
Loading…
Reference in New Issue