Skip to content

Instantly share code, notes, and snippets.

@deyceg
Last active August 9, 2022 10:25
Show Gist options
  • Save deyceg/1dba85f398c26e8f1826af50412d32a1 to your computer and use it in GitHub Desktop.
Save deyceg/1dba85f398c26e8f1826af50412d32a1 to your computer and use it in GitHub Desktop.
React + Tailwindcss
type TailwindButtonProps = {
/**
* Text to render on the button
*/
text?: string;
/**
* Click handler
*/
onClick: React.MouseEventHandler<HTMLButtonElement>;
/**
* Control the state of the button e.g. for use in a \<form\> element
*/
isActive?: boolean;
/**
* Render an icon inside the button
*/
Icon?: FunctionComponent<React.ComponentProps<'svg'>>;
/**
* Position of the icon relative to the text
*/
iconPosition?: 'left' | 'right';
/**
* Control the visibility of the icon:
*
* static -> always visible
* active -> visible when isActive is truthy
*/
iconEffect?: 'static' | 'active';
/**
* Switch between fixed and percentage based widths
*/
width?: 'fixed' | 'percent';
/**
* Constrain the button width
*/
size?: 'sm' | 'md' | 'lg';
/**
* Control the button variant
*/
type?: 'solid' | 'ghost';
/**
* Control the theme
*/
variant?: 'primary' | 'secondary';
};
export const TailwindButton = ({
text = null,
onClick,
isActive = false,
Icon = null,
iconPosition = 'left',
iconEffect = 'static',
width = 'percent',
size = 'md',
type: variant = 'solid',
variant: theme = 'primary',
}: TailwindButtonProps) => {
console.log(Icon);
// Classnames broadly map to tailwindcss categories.
const layoutClass = classNames('justify-center', {
flex: Icon === null,
'inline-flex items-center': Icon !== null,
});
const sizingClass = classNames(
// percent
{ 'w-1/4': width === 'percent' && size === 'sm' },
{ 'w-1/2': width === 'percent' && size === 'md' },
{ 'w-full': width === 'percent' && size === 'lg' },
// fixed
{ 'w-24': width === 'fixed' && size === 'sm' },
{ 'w-48': width === 'fixed' && size === 'md' },
{ 'w-96': width === 'fixed' && size === 'lg' },
);
const spacingClass = classNames(
{ 'py-2 px-4': size === 'sm' },
{ 'py-3 px-5': size === 'md' },
{ 'py-4 px-6': size === 'lg' },
);
const typographyClass = classNames(
'font-medium',
{ 'text-sm': size === 'sm' },
{ 'text-md': size === 'md' },
{ 'text-lg': size === 'lg' },
);
const borderClass = classNames(
'border rounded-md',
// solid
{ 'border-transparent': variant === 'solid' && theme === 'primary' },
{ 'border-transparent': variant === 'solid' && theme === 'secondary' },
// ghost
{
'border-indigo-600': variant === 'ghost' && theme === 'primary',
},
{ 'border-gray-600': variant === 'ghost' && theme === 'secondary' },
);
const colorClass = classNames(
//solid
{ 'bg-indigo-600 text-white': variant === 'solid' && theme === 'primary' },
{
'bg-gray-600 text-white': variant === 'solid' && theme === 'secondary',
},
// ghost
{ 'bg-white text-indigo-600': variant === 'ghost' && theme === 'primary' },
{ 'bg-white text-gray-600': variant === 'ghost' && theme === 'secondary' },
);
const effectClass = classNames(
'shadow-sm',
//solid
{
'hover:bg-indigo-700': variant === 'solid' && theme === 'primary',
},
{
'hover:bg-gray-700': variant === 'solid' && theme === 'secondary',
},
// ghost
{
'hover:border-indigo-700 hover:text-indigo-700':
variant === 'ghost' && theme === 'primary',
},
{
'hover:border-gray-700 hover:text-gray-700':
variant === 'ghost' && theme === 'secondary',
},
);
const iconClass = classNames(
{ 'mr-2': iconPosition === 'left' },
{ 'ml-2': iconPosition === 'right' },
{ 'h-4 w-4': size === 'sm' },
{ 'h-5 w-5': size === 'md' },
{ 'h-6 w-6': size === 'lg' },
);
console.log(iconClass);
// Icon visibility
const iconElement =
Icon !== null && iconEffect === 'static'
? React.createElement(Icon, { className: iconClass })
: Icon !== null && iconEffect === 'active' && isActive
? React.createElement(Icon, { className: iconClass })
: null;
return (
<button
className={[
layoutClass,
sizingClass,
spacingClass,
typographyClass,
borderClass,
colorClass,
effectClass,
].join(' ')}
onClick={onClick}
>
{iconPosition === 'left' && iconElement}
{text && <span>{text}</span>}
{iconPosition === 'right' && iconElement}
</button>
);
};
export const Page = () => {
return (
<TailwindButton
text="Sign Up"
onClick={noop}
width={'percent'}
size={'sm'}
type={'solid'}
/>
<TailwindButton
text="Sign Up"
onClick={noop}
width={'percent'}
size={'md'}
type={'solid'}
Icon={Spinner}
iconPosition={'right'}
/>
<TailwindButton
text="Sign Up"
onClick={noop}
width={'percent'}
size={'lg'}
type={'solid'}
variant={'secondary'}
Icon={SolidIcon}
/>
<TailwindButton
text="Sign Up"
onClick={noop}
width={'percent'}
size={'lg'}
type={'ghost'}
variant={'primary'}
/>
<TailwindButton
text="Sign Up"
onClick={noop}
width={'percent'}
size={'lg'}
type={'ghost'}
variant={'secondary'}
Icon={Spinner}
/>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment