mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-11 16:07:15 +02:00
refactor: create reusable ExternalLink component for consistent styling
Create ExternalLink component following patterns from HighlightRenderer and BookmarkRenderer with: - Two variants: 'muted' (default, text-muted-foreground with underline) and 'default' (text-primary with hover:underline) - Three sizes: xs, sm, base - Configurable icon display - Consistent truncate behavior for long URLs - Stop propagation on click Apply to NIP-89 renderers: - ApplicationHandlerRenderer: uses muted variant (feed view) - ApplicationHandlerDetailRenderer: uses default variant (detail view) This ensures consistent link styling across the entire application and makes it easy to maintain a unified design language.
This commit is contained in:
65
src/components/ExternalLink.tsx
Normal file
65
src/components/ExternalLink.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { ExternalLink as ExternalLinkIcon } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface ExternalLinkProps {
|
||||
href: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
iconClassName?: string;
|
||||
showIcon?: boolean;
|
||||
variant?: "default" | "muted";
|
||||
size?: "xs" | "sm" | "base";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable external link component with consistent styling across the app
|
||||
* Follows patterns from HighlightRenderer and BookmarkRenderer
|
||||
*/
|
||||
export function ExternalLink({
|
||||
href,
|
||||
children,
|
||||
className,
|
||||
iconClassName,
|
||||
showIcon = true,
|
||||
variant = "muted",
|
||||
size = "xs",
|
||||
}: ExternalLinkProps) {
|
||||
const sizeClasses = {
|
||||
xs: "text-xs",
|
||||
sm: "text-sm",
|
||||
base: "text-base",
|
||||
};
|
||||
|
||||
const iconSizeClasses = {
|
||||
xs: "size-3",
|
||||
sm: "size-3",
|
||||
base: "size-4",
|
||||
};
|
||||
|
||||
const variantClasses = {
|
||||
default: "text-primary hover:underline",
|
||||
muted: "text-muted-foreground underline decoration-dotted",
|
||||
};
|
||||
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1",
|
||||
sizeClasses[size],
|
||||
variantClasses[variant],
|
||||
className,
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{showIcon && (
|
||||
<ExternalLinkIcon
|
||||
className={cn("flex-shrink-0", iconSizeClasses[size], iconClassName)}
|
||||
/>
|
||||
)}
|
||||
<span className="truncate">{children}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -11,13 +11,13 @@ import { KindBadge } from "@/components/KindBadge";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useCopy } from "@/hooks/useCopy";
|
||||
import { UserName } from "../UserName";
|
||||
import { ExternalLink } from "@/components/ExternalLink";
|
||||
import {
|
||||
Copy,
|
||||
CopyCheck,
|
||||
Globe,
|
||||
Smartphone,
|
||||
TabletSmartphone,
|
||||
ExternalLink,
|
||||
} from "lucide-react";
|
||||
|
||||
interface ApplicationHandlerDetailRendererProps {
|
||||
@@ -94,15 +94,9 @@ export function ApplicationHandlerDetailRenderer({
|
||||
|
||||
{/* Website */}
|
||||
{website && (
|
||||
<a
|
||||
href={website}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 text-primary hover:underline"
|
||||
>
|
||||
<ExternalLink href={website} variant="default" size="base">
|
||||
{website}
|
||||
<ExternalLink className="size-3" />
|
||||
</a>
|
||||
</ExternalLink>
|
||||
)}
|
||||
|
||||
{/* Metadata Grid */}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@/lib/nip89-helpers";
|
||||
import { KindBadge } from "@/components/KindBadge";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ExternalLink } from "@/components/ExternalLink";
|
||||
import { Globe, Smartphone, TabletSmartphone } from "lucide-react";
|
||||
|
||||
/**
|
||||
@@ -60,17 +61,7 @@ export function ApplicationHandlerRenderer({ event }: BaseEventProps) {
|
||||
</ClickableEventTitle>
|
||||
|
||||
{/* Website */}
|
||||
{website && (
|
||||
<a
|
||||
href={website}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-primary hover:underline"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{website}
|
||||
</a>
|
||||
)}
|
||||
{website && <ExternalLink href={website}>{website}</ExternalLink>}
|
||||
|
||||
{/* Supported Kinds */}
|
||||
{displayKinds.length > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user