Files
2023-04-04 21:44:14 +02:00

228 lines
6.4 KiB
TypeScript

import { tv } from 'tailwind-variants';
import { twMerge } from 'tailwind-merge';
import { forwardRef } from 'react';
import { Link } from '@remix-run/react';
import type { LinkProps } from '@remix-run/react';
const button = tv({
base: 'font-sans focus:outline-none focus-visible:ring-2 inline-flex items-center',
variants: {
size: {
md: 'px-6 py-4 text-lg font-medium',
base: 'px-4 py-3 text-lg font-medium',
sm: 'px-5 py-[0.6875rem] text-base font-medium',
xs: 'px-4 py-1.5 text-base',
'2xs': 'py-1 px-4 text-base',
},
color: {
default: 'bg-grey-900 text-white hover:bg-grey-800 shadow-md focus-visible:ring-grey-500 antialiased',
grey: 'bg-grey-200 text-grey-900 focus-visible:ring-grey-300 ',
red: 'bg-red-light text-error-700 focus-visible:ring-error-200',
transparent: 'bg-transparent border font-thin border-solid border-grey-200 text-grey-900',
},
disabled: {
true: 'bg-grey-100 text-grey-800/40 pointer-events-none shadow-none',
},
roundness: {
default: 'rounded-[0.875rem]',
lg: 'rounded-3xl',
},
//Use in compound variants
startIcon: {
true: '',
false: '',
},
//Use in compound variants
endIcon: {
true: '',
false: '',
},
},
compoundVariants: [
{
size: ['sm', 'xs', '2xs'],
roundness: 'default',
className: 'rounded-xl',
},
{ size: 'md', startIcon: true, className: 'pl-4' },
{ size: 'md', endIcon: true, className: 'pr-4' },
{ size: 'base', startIcon: true, className: 'pl-3' },
{ size: 'base', endIcon: true, className: 'pr-3' },
{ size: 'sm', startIcon: true, className: 'pl-4' },
{ size: 'sm', endIcon: true, className: 'pr-4' },
{ size: 'xs', startIcon: true, className: 'pl-3' },
{ size: 'xs', endIcon: true, className: 'pr-3' },
{ size: '2xs', startIcon: true, className: 'pl-3' },
{ size: '2xs', endIcon: true, className: 'pr-3' },
],
});
const iconContainerBase = tv({
base: 'w-6 h-6 inline-flex items-center justify-center relative top-[1px]',
});
const iconStartContainer = tv({
extend: iconContainerBase,
variants: {
size: {
md: 'mr-2',
base: 'mr-2',
sm: 'mr-1',
xs: 'mr-1 w-5 h-5',
'2xs': 'mr-1 w-4 h-4',
},
},
compoundVariants: [
{
size: 'sm',
roundness: 'lg',
className: 'w-5 h-5',
},
],
});
const iconEndContainer = tv({
extend: iconContainerBase,
variants: {
size: {
md: 'ml-2',
base: 'ml-2',
sm: 'ml-1',
xs: 'ml-1 w-5 h-5',
'2xs': 'ml-1 w-4 h-4',
},
roundness: {
default: '',
lg: '',
},
},
compoundVariants: [
{
size: 'sm',
roundness: 'lg',
className: 'w-5 h-5 -top-[1px]',
},
],
});
type BaseButtonProps = {
/**
* How large should the button be?
*/
size?: 'md' | 'base' | 'sm' | 'xs' | '2xs';
/**
* What color to use?
*/
color?: 'default' | 'grey' | 'red' | 'transparent';
/**
* Is button disabled?
*/
disabled?: boolean;
/**
* How rounded the button should be
*/
roundness?: 'default' | 'lg';
/**
* An icon before the button's label.
*/
startIcon?: React.ReactElement;
/**
* An icon after the button's label.
*/
endIcon?: React.ReactElement;
};
type ButtonProps = BaseButtonProps & React.ComponentPropsWithRef<'button'>;
type AnchorProps = BaseButtonProps & React.ComponentPropsWithRef<'a'>;
type LinkButtonProps = BaseButtonProps & LinkProps;
export const LinkButton = forwardRef<HTMLAnchorElement, LinkButtonProps>(function LinkButton(
{
children,
className,
color = 'default',
size = 'base',
roundness = 'default',
disabled,
startIcon,
endIcon,
...other
},
ref,
) {
return (
<Link
className={twMerge(
button({ color, size, disabled, roundness, endIcon: Boolean(endIcon), startIcon: Boolean(startIcon) }),
className,
)}
{...other}
ref={ref}
>
{startIcon ? <span className={iconStartContainer({ size, roundness })}>{startIcon}</span> : null}
{children}
{endIcon ? <span className={iconEndContainer({ size, roundness })}>{endIcon}</span> : null}
</Link>
);
});
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
{
children,
className,
color = 'default',
size = 'base',
roundness = 'default',
disabled,
startIcon,
endIcon,
...other
},
forwardedRef,
) {
return (
<button
className={twMerge(
button({ color, size, disabled, roundness, startIcon: Boolean(startIcon), endIcon: Boolean(endIcon) }),
className,
)}
disabled={disabled}
{...other}
ref={forwardedRef}
>
{startIcon ? <span className={iconStartContainer({ size, roundness })}>{startIcon}</span> : null}
{children}
{endIcon ? <span className={iconEndContainer({ size, roundness })}>{endIcon}</span> : null}
</button>
);
});
export const AnchorButton = forwardRef<HTMLAnchorElement, AnchorProps>(function Button(
{
children,
className,
color = 'default',
size = 'base',
roundness = 'default',
disabled,
startIcon,
endIcon,
...other
},
forwardedRef,
) {
return (
<a
className={twMerge(
button({ color, size, disabled, roundness, startIcon: Boolean(startIcon), endIcon: Boolean(endIcon) }),
className,
)}
{...other}
ref={forwardedRef}
>
{startIcon ? <span className={iconStartContainer({ size })}>{startIcon}</span> : null}
{children}
{endIcon ? <span className={iconEndContainer({ size, roundness })}>{endIcon}</span> : null}
</a>
);
});