'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 (
}
>
{link.label}
)
}
return (
}
>
{link.label}
)
}
/**
* 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 (
)
}