67 lines
1.5 KiB
TypeScript
67 lines
1.5 KiB
TypeScript
import { cn } from '@crux-kit/utils';
|
|
import { toPublicHref } from '#i18n/content-href';
|
|
import { buttonVariants } from '#shared/ui/Button';
|
|
import type { NavigationLink } from '../types';
|
|
|
|
export function safeHref(link: NavigationLink): string | null {
|
|
const href = link.href || link.externalUrl;
|
|
if (!href) {
|
|
return null;
|
|
}
|
|
|
|
if (href.startsWith('/')) {
|
|
return toPublicHref(href);
|
|
}
|
|
|
|
if (href.startsWith('#')) {
|
|
return href;
|
|
}
|
|
|
|
try {
|
|
const url = new URL(href);
|
|
return ['http:', 'https:', 'mailto:', 'tel:'].includes(url.protocol) ? href : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function PageLink({ link, className = '' }: { link: NavigationLink; className?: string }) {
|
|
const href = safeHref(link);
|
|
const classes = cn(buttonVariants({ variant: link.variant }), className);
|
|
|
|
if (!href) {
|
|
return <span className={cn(classes, 'cursor-not-allowed opacity-45')}>{link.label}</span>;
|
|
}
|
|
|
|
return (
|
|
<a
|
|
href={href}
|
|
className={classes}
|
|
target={link.openInNewTab ? '_blank' : undefined}
|
|
rel={link.openInNewTab ? 'noreferrer noopener' : undefined}
|
|
>
|
|
{link.label}
|
|
</a>
|
|
);
|
|
}
|
|
|
|
export function LinkList({
|
|
links,
|
|
className = ''
|
|
}: {
|
|
links: NavigationLink[];
|
|
className?: string;
|
|
}) {
|
|
if (links.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className={cn('flex flex-wrap items-center gap-3', className)}>
|
|
{links.map((link, index) => (
|
|
<PageLink key={`${link.label}-${index}`} link={link} />
|
|
))}
|
|
</div>
|
|
);
|
|
}
|