// 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,
};