From f0ce89a7bb3f08b0aa68f4f9b533228ab8520fad Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Jan 2026 09:51:43 +0000 Subject: [PATCH] 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. --- src/components/ExternalLink.tsx | 65 +++++++++++++++++++ .../ApplicationHandlerDetailRenderer.tsx | 12 +--- .../kinds/ApplicationHandlerRenderer.tsx | 13 +--- 3 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 src/components/ExternalLink.tsx diff --git a/src/components/ExternalLink.tsx b/src/components/ExternalLink.tsx new file mode 100644 index 0000000..16336c2 --- /dev/null +++ b/src/components/ExternalLink.tsx @@ -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 ( + e.stopPropagation()} + > + {showIcon && ( + + )} + {children} + + ); +} diff --git a/src/components/nostr/kinds/ApplicationHandlerDetailRenderer.tsx b/src/components/nostr/kinds/ApplicationHandlerDetailRenderer.tsx index a5fc905..3099727 100644 --- a/src/components/nostr/kinds/ApplicationHandlerDetailRenderer.tsx +++ b/src/components/nostr/kinds/ApplicationHandlerDetailRenderer.tsx @@ -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 && ( - + {website} - - + )} {/* Metadata Grid */} diff --git a/src/components/nostr/kinds/ApplicationHandlerRenderer.tsx b/src/components/nostr/kinds/ApplicationHandlerRenderer.tsx index 987c676..c786dac 100644 --- a/src/components/nostr/kinds/ApplicationHandlerRenderer.tsx +++ b/src/components/nostr/kinds/ApplicationHandlerRenderer.tsx @@ -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) { {/* Website */} - {website && ( - e.stopPropagation()} - > - {website} - - )} + {website && {website}} {/* Supported Kinds */} {displayKinds.length > 0 && (