// AlignUI LinkButton v0.0.0 import * as React from 'react'; import { tv, type VariantProps } from '@/utils/tv'; import { recursiveCloneChildren } from '@/utils/recursive-clone-children'; import { Slot } from '@radix-ui/react-slot'; import { PolymorphicComponentProps } from '@/utils/polymorphic'; const LINK_BUTTON_ROOT_NAME = 'LinkButtonRoot'; const LINK_BUTTON_ICON_NAME = 'LinkButtonIcon'; function addTextSlots(children: React.ReactNode) { return React.Children.map(children, (child) => { if (typeof child === 'string' || typeof child === 'number') { return {child}; } return child; }); } function hasTextSlot(children: React.ReactNode) { return React.Children.toArray(children).some((child) => { if (typeof child === 'string' || typeof child === 'number') { return true; } return ( React.isValidElement(child) && 'data-text' in (child.props as Record) ); }); } export const linkButtonVariants = tv({ slots: { root: [ // base 'group inline-flex items-center justify-center whitespace-nowrap outline-none', 'transition duration-200 ease-out', 'underline decoration-transparent underline-offset-[3px]', // hover 'hover:decoration-current', // focus 'focus:outline-none focus-visible:underline', // disabled 'disabled:text-text-disabled-300 disabled:pointer-events-none disabled:no-underline', ], icon: 'shrink-0', }, variants: { variant: { gray: { root: [ // base 'text-text-sub-600', // focus 'focus-visible:text-text-strong-950', ], }, black: { root: 'text-text-strong-950', }, primary: { root: [ // base 'text-primary-base', // hover 'hover:text-primary-darker', ], }, error: { root: [ // base 'text-error-base', // hover 'hover:text-red-700', ], }, modifiable: {}, }, size: { medium: { root: 'text-label-sm h-5 gap-1', icon: 'size-5', }, small: { root: 'text-label-xs h-4 gap-1', icon: 'size-4', }, }, underline: { true: { root: 'decoration-current', }, }, }, defaultVariants: { variant: 'gray', size: 'medium', }, }); type LinkButtonSharedProps = VariantProps; type LinkButtonProps = VariantProps & React.ButtonHTMLAttributes & { asChild?: boolean; }; const LinkButtonRoot = React.forwardRef( ( { asChild, children, variant, size, underline, className, ...rest }, forwardedRef, ) => { const uniqueId = React.useId(); const Component = asChild ? Slot : 'button'; const { root } = linkButtonVariants({ variant, size, underline }); const sharedProps: LinkButtonSharedProps = { variant, size, }; const extendedChildren = recursiveCloneChildren( children as React.ReactElement[], sharedProps, [LINK_BUTTON_ICON_NAME], uniqueId, asChild, ); const slottedChildren = addTextSlots(extendedChildren); const iconOnly = !hasTextSlot(slottedChildren); return ( {slottedChildren} ); }, ); LinkButtonRoot.displayName = LINK_BUTTON_ROOT_NAME; function LinkButtonIcon({ className, variant, size, as, ...rest }: PolymorphicComponentProps) { const Component = as || 'div'; const { icon } = linkButtonVariants({ variant, size }); return ( ); } LinkButtonIcon.displayName = LINK_BUTTON_ICON_NAME; function LinkButtonText({ className, children, ...rest }: React.HTMLAttributes) { return ( {children} ); } LinkButtonText.displayName = 'LinkButtonText'; export { LinkButtonRoot as Root, LinkButtonIcon as Icon, LinkButtonText as Text, };