132 lines
3.2 KiB
TypeScript
132 lines
3.2 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { forwardRef } from 'react';
|
|
import type { ReactNode, MouseEventHandler } from 'react';
|
|
import ArrowRight from 'lucide-react/dist/esm/icons/arrow-right';
|
|
import ArrowUpRight from 'lucide-react/dist/esm/icons/arrow-up-right';
|
|
|
|
export type CTAVariant = 'primary' | 'secondary' | 'ghost' | 'link';
|
|
export type CTASize = 'sm' | 'md' | 'lg';
|
|
export type CTAArrow = 'up-right' | 'right' | 'none';
|
|
|
|
type BaseProps = {
|
|
variant?: CTAVariant;
|
|
size?: CTASize;
|
|
arrow?: CTAArrow;
|
|
full?: boolean;
|
|
children: ReactNode;
|
|
className?: string;
|
|
ariaLabel?: string;
|
|
};
|
|
|
|
type AnchorProps = BaseProps & {
|
|
href: string;
|
|
external?: boolean;
|
|
download?: boolean | string;
|
|
type?: never;
|
|
onClick?: MouseEventHandler<HTMLAnchorElement>;
|
|
disabled?: never;
|
|
};
|
|
|
|
type ButtonProps = BaseProps & {
|
|
href?: undefined;
|
|
type?: 'button' | 'submit' | 'reset';
|
|
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
disabled?: boolean;
|
|
};
|
|
|
|
export type CTAButtonProps = AnchorProps | ButtonProps;
|
|
|
|
const arrowIcon = (arrow: CTAArrow, size: CTASize) => {
|
|
if (arrow === 'none') return null;
|
|
const px = size === 'sm' ? 12 : size === 'lg' ? 15 : 13;
|
|
return (
|
|
<span className="cta-arrow" aria-hidden="true">
|
|
{arrow === 'up-right' ? <ArrowUpRight size={px} /> : <ArrowRight size={px} />}
|
|
</span>
|
|
);
|
|
};
|
|
|
|
const classFor = (variant: CTAVariant, size: CTASize, full: boolean, extra?: string) =>
|
|
[
|
|
'cta-btn',
|
|
`cta-${variant}`,
|
|
`cta-${size}`,
|
|
full ? 'cta-full' : '',
|
|
extra ?? '',
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ');
|
|
|
|
export const CTAButton = forwardRef<HTMLAnchorElement | HTMLButtonElement, CTAButtonProps>(
|
|
function CTAButton(props, ref) {
|
|
const {
|
|
variant = 'primary',
|
|
size = 'md',
|
|
arrow = 'up-right',
|
|
full = false,
|
|
children,
|
|
className,
|
|
ariaLabel,
|
|
} = props;
|
|
|
|
const cls = classFor(variant, size, full, className);
|
|
const inner = (
|
|
<>
|
|
<span className="cta-label">{children}</span>
|
|
{arrowIcon(arrow, size)}
|
|
</>
|
|
);
|
|
|
|
if ('href' in props && props.href) {
|
|
const isExternal = props.external || /^https?:\/\//.test(props.href);
|
|
if (isExternal) {
|
|
return (
|
|
<a
|
|
ref={ref as React.Ref<HTMLAnchorElement>}
|
|
href={props.href}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className={cls}
|
|
data-arrow={arrow}
|
|
aria-label={ariaLabel}
|
|
onClick={props.onClick}
|
|
>
|
|
{inner}
|
|
</a>
|
|
);
|
|
}
|
|
return (
|
|
<Link
|
|
ref={ref as React.Ref<HTMLAnchorElement>}
|
|
href={props.href}
|
|
className={cls}
|
|
data-arrow={arrow}
|
|
aria-label={ariaLabel}
|
|
onClick={props.onClick}
|
|
>
|
|
{inner}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
const btnProps = props as ButtonProps;
|
|
return (
|
|
<button
|
|
ref={ref as React.Ref<HTMLButtonElement>}
|
|
type={btnProps.type ?? 'button'}
|
|
onClick={btnProps.onClick}
|
|
disabled={btnProps.disabled}
|
|
className={cls}
|
|
data-arrow={arrow}
|
|
aria-label={ariaLabel}
|
|
>
|
|
{inner}
|
|
</button>
|
|
);
|
|
},
|
|
);
|
|
|
|
CTAButton.displayName = 'CTAButton';
|