'use client' import type React from 'react' import { Button, type Intent } from '@byline/ui/react' import cx from 'clsx' import { LangLink } from '@/ui/byline/components/link/lang-link' import type { Locale } from '@/ui/byline/types/i18n' export interface LinkFieldAttributes { id?: string | null type?: ('reference' | 'custom') | null newTab?: boolean | null label: string customId?: string | null url?: string | null appearance?: ('primary' | 'secondary') | null reference?: { value: string relationTo: string data: { id: string title: string slug: string collectionAlias: string } } } export interface LinkFieldProps extends React.ComponentPropsWithoutRef<'a'> { link: LinkFieldAttributes size?: 'sm' | 'md' | 'lg' lng: Locale className?: string onMouseEnter?: () => void onMouseLeave?: () => void children?: React.ReactNode } export function manageRel(input: string, action: 'add' | 'remove', value: string): string { let result: string let mutableInput = `${input}` if (action === 'add') { // if we somehow got out of sync - clean up if (mutableInput.includes(value)) { const re = new RegExp(value, 'g') mutableInput = mutableInput.replace(re, '').trim() } mutableInput = mutableInput.trim() result = mutableInput.length === 0 ? `${value}` : `${mutableInput} ${value}` } else { const re = new RegExp(value, 'g') result = mutableInput.replace(re, '').trim() } return result } function getHref(args: LinkFieldAttributes): string { let href = '' const publicWebsiteUrl = '/' // getPublicWebsiteUrl() const { type, url } = args if ((type === 'custom' || type === undefined) && url != null) { href = url } else if ( type === 'reference' && args.reference?.relationTo != null && args.reference?.data?.slug != null ) { const collection = args?.reference?.relationTo // biome-ignore lint/correctness/noUnsafeOptionalChaining: To be refactored in the future const { slug, collectionAlias } = args?.reference?.data if (collectionAlias != null) { // The alias might be for the root if (collectionAlias.length === 0) { href = `/${slug}` } else { href = `/${collectionAlias}/${slug}` } } else { href = `/${collection}/${slug}` } } const hrefIsLocal = ['tel:', 'mailto:', '/'].some((prefix) => href.startsWith(prefix)) // Relative custom hrefs are valid but cannot be passed to URL without a base. if (!hrefIsLocal && /^[a-z][a-z\d+.-]*:/i.test(href)) { try { const objectURL = new URL(href) if (objectURL.origin === publicWebsiteUrl) { href = objectURL.href.replace(publicWebsiteUrl, '') } } catch (e) { console.error(`Failed to format url: ${href}`, e) } } return href } function getAdditionalProps( args: LinkFieldAttributes, href: string ): { rel: string | undefined target: string | undefined } { const additionalProps: { rel: string | undefined target: string | undefined } = { rel: undefined, target: undefined, } let rel = '' if (args.newTab === true) rel = manageRel(rel, 'add', 'noopener') additionalProps.rel = rel if (args.newTab === true) { additionalProps.target = '_blank' } if (!href.startsWith('/')) { additionalProps.target = '_blank' } if (additionalProps.rel == null || additionalProps.rel.length === 0) delete additionalProps.rel if (additionalProps.target == null) delete additionalProps.target return additionalProps } /** * DefaultLink * @param param0 * @returns */ export function DefaultLink({ link, lng, className, onMouseEnter, onMouseLeave, ...rest }: LinkFieldProps): React.JSX.Element { const href = getHref(link) const additionalProps = getAdditionalProps(link, href) if (href.startsWith('/')) { return ( {link.label} ) } return ( {link.label} ) } /** * ButtonLink * @param param0 * @returns */ export function ButtonLink({ link, size = 'md', lng, className, onMouseEnter, onMouseLeave, ...rest }: LinkFieldProps): React.JSX.Element { const href = getHref(link) const additionalProps = getAdditionalProps(link, href) if (href.startsWith('/')) { return ( ) } return ( ) } /** * We export LinkField which will attempt to automatically switch * between ButtonLink and DefaultLink based on the appearance of the link. * * For more control including className and style overrides, a block may * choose to use the above ButtonLink or DefaultLink directly. * * @param param0 * @returns */ export function LinkField({ link, lng, size = 'md', className, onMouseEnter, onMouseLeave, ...rest }: LinkFieldProps): React.JSX.Element { if ( link.appearance != null && (link.appearance === 'primary' || link.appearance === 'secondary') ) { return ( ) } return ( ) }