This commit is contained in:
Nate Kelley 2025-02-24 13:52:33 -07:00
parent 49337658ea
commit e41180ff20
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
3 changed files with 246 additions and 26 deletions

View File

@ -4,36 +4,42 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/classMerge'; import { cn } from '@/lib/classMerge';
import { CircleSpinnerLoader } from '../loaders/CircleSpinnerLoader'; import { CircleSpinnerLoader } from '../loaders/CircleSpinnerLoader';
export const buttonTypeClasses = {
default:
'bg-white border hover:bg-item-hover disabled:bg-disabled disabled:text-gray-light active:bg-item-active data-[selected=true]:bg-item-select',
black: 'bg-black text-white hover:bg-foreground-hover disabled:bg-foreground-hover',
primary:
'bg-primary text-white hover:bg-primary-light active:bg-primary-dark data-[selected=true]:bg-primary-dark',
ghost:
'bg-transparent text-gray-dark shadow-none hover:bg-item-hover hover:text-foreground disabled:bg-transparent disabled:text-gray-light active:bg-item-active data-[selected=true]:bg-item-select',
link: 'bg-transparent text-gray-dark shadow-none hover:text-foreground disabled:bg-transparent disabled:text-gray-light'
};
const roundingVariants = {
default: 'rounded',
full: 'rounded-full',
large: 'rounded-lg',
small: 'rounded-sm',
none: 'rounded-none'
};
const sizeVariants = {
default: 'h-6',
tall: 'h-7',
small: 'h-[18px]'
};
export const buttonVariants = cva( export const buttonVariants = cva(
'inline-flex items-center overflow-hidden text-base justify-center gap-[5px] shadow-btn rounded transition-all duration-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none cursor-pointer disabled:cursor-not-allowed data-[loading=true]:cursor-progress', 'inline-flex items-center overflow-hidden text-base justify-center gap-[5px] shadow-btn rounded transition-all duration-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none cursor-pointer disabled:cursor-not-allowed data-[loading=true]:cursor-progress',
{ {
variants: { variants: {
buttonType: { buttonType: buttonTypeClasses,
default: size: sizeVariants,
'bg-white border hover:bg-item-hover disabled:bg-disabled disabled:text-gray-light active:bg-item-active data-[selected=true]:bg-item-select',
black: 'bg-black text-white hover:bg-foreground-hover disabled:bg-foreground-hover',
primary:
'bg-primary text-white hover:bg-primary-light active:bg-primary-dark data-[selected=true]:bg-primary-dark',
ghost:
'bg-transparent text-gray-dark shadow-none hover:bg-item-hover hover:text-foreground disabled:bg-transparent disabled:text-gray-light active:bg-item-active data-[selected=true]:bg-item-select',
link: 'bg-transparent text-gray-dark shadow-none hover:text-foreground disabled:bg-transparent disabled:text-gray-light'
},
size: {
default: 'h-6',
tall: 'h-7',
small: 'h-[18px]'
},
iconButton: { iconButton: {
true: '', true: '',
false: 'px-2.5' false: 'px-2.5'
}, },
rounding: { rounding: roundingVariants
default: 'rounded',
full: 'rounded-full',
large: 'rounded-lg',
small: 'rounded-sm',
none: 'rounded-none'
}
}, },
compoundVariants: [ compoundVariants: [
{ {
@ -149,7 +155,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
Button.displayName = 'Button'; Button.displayName = 'Button';
const LoadingIcon: React.FC<{ export const LoadingIcon: React.FC<{
buttonType: ButtonProps['buttonType']; buttonType: ButtonProps['buttonType'];
size: ButtonProps['size']; size: ButtonProps['size'];
}> = ({ buttonType = 'default', size = 'default' }) => { }> = ({ buttonType = 'default', size = 'default' }) => {

View File

@ -0,0 +1,100 @@
import type { Meta, StoryObj } from '@storybook/react';
import { ButtonDropdown } from './ButtonDropdown';
import { PaintRoller } from '../icons/NucleoIconOutlined';
const meta: Meta<typeof ButtonDropdown> = {
title: 'Base/ButtonDropdown',
component: ButtonDropdown,
parameters: {
layout: 'centered'
},
argTypes: {
buttonType: {
control: 'select',
options: ['default', 'black', 'primary', 'ghost', 'link']
},
size: {
control: 'select',
options: ['default', 'tall', 'small']
},
disabled: {
control: 'boolean',
defaultValue: false
},
loading: {
control: 'boolean',
defaultValue: false
},
rounding: {
control: 'select',
options: ['default', 'full', 'large', 'small', 'none']
}
},
tags: ['autodocs']
};
export default meta;
type Story = StoryObj<typeof ButtonDropdown>;
export const Default: Story = {
args: {
buttonText: 'Button Text',
buttonType: 'default'
}
};
export const Primary: Story = {
args: {
buttonText: 'Primary Button',
buttonType: 'primary'
}
};
export const Black: Story = {
args: {
buttonText: 'Black Button',
buttonType: 'black'
}
};
export const Ghost: Story = {
args: {
buttonText: 'Ghost Button',
buttonType: 'ghost'
}
};
export const Link: Story = {
args: {
buttonText: 'Link Button',
buttonType: 'link'
}
};
export const Tall: Story = {
args: {
buttonText: 'Tall Button',
size: 'tall'
}
};
export const Small: Story = {
args: {
buttonText: 'Small Button',
size: 'small'
}
};
export const Disabled: Story = {
args: {
buttonText: 'Disabled Button',
disabled: true
}
};
export const WithIcon: Story = {
args: {
buttonText: 'With Icon',
icon: <PaintRoller />
}
};

View File

@ -1,5 +1,119 @@
import React from 'react'; import React from 'react';
import { buttonVariants, buttonIconVariants } from './Button'; import {
import { ChevronDown, ChevronUp } from '../icons/NucleoIconOutlined'; buttonVariants,
buttonIconVariants,
ButtonProps,
buttonTypeClasses,
LoadingIcon
} from './Button';
import { ChevronDown } from '../icons/NucleoIconOutlined';
import { cn } from '@/lib/classMerge'; import { cn } from '@/lib/classMerge';
import { DropdownMenuProps } from '@radix-ui/react-dropdown-menu'; import { cva } from 'class-variance-authority';
import { type DropdownProps, Dropdown } from '../dropdown/Dropdown';
interface ButtonDropdownProps {
icon?: React.ReactNode;
buttonText?: string;
disabled?: boolean;
rounding?: ButtonProps['rounding'];
size?: ButtonProps['size'];
buttonType?: ButtonProps['buttonType'];
loading?: boolean;
className?: string;
iconClassName?: string;
dropdownProps: Omit<DropdownProps, 'children'>;
}
const dropdownButtonVariants = cva('flex items-center pr-0', {
variants: {
rounding: {
default: 'rounded',
full: 'rounded-full',
large: 'rounded-lg',
small: 'rounded-sm',
none: 'rounded-none'
}
}
});
const primaryButtonVariants = cva('', {
variants: {
buttonType: buttonTypeClasses
}
});
const splitButtonVariants = cva('flex w-full items-center justify-center h-full px-[5px]', {
variants: {
buttonType: buttonTypeClasses
}
});
export const ButtonDropdown = React.forwardRef<HTMLDivElement, ButtonDropdownProps>(
({ dropdownProps, ...buttonProps }, ref) => {
return (
<Dropdown {...dropdownProps}>
<ButtonSplit ref={ref} {...buttonProps} />
</Dropdown>
);
}
);
export const ButtonSplit = React.forwardRef<
HTMLDivElement,
Omit<ButtonDropdownProps, 'dropdownProps'>
>(
(
{
className,
buttonType = 'default',
size = 'default',
icon,
buttonText,
disabled = false,
rounding = 'default',
loading = false,
iconClassName
},
ref
) => {
return (
<div
ref={ref}
className={cn(
buttonVariants({ buttonType, size, rounding: 'none' }),
dropdownButtonVariants({ rounding }),
'gap-0 !bg-transparent p-0',
disabled && 'cursor-not-allowed opacity-60',
className
)}>
<div
className={cn(
primaryButtonVariants({ buttonType }),
'flex h-full items-center gap-0.5 border-none pr-2 pl-2.5'
)}>
{loading ? (
<LoadingIcon buttonType={buttonType} size={size} />
) : (
icon && (
<span
className={cn(buttonIconVariants({ buttonType, size }), 'text-sm', iconClassName)}>
{icon}
</span>
)
)}
{buttonText && <span className="">{buttonText}</span>}
</div>
<div className="bg-border mr-0 h-full w-[0.5px]" />
<div className="flex h-full items-center justify-center text-sm">
<div
className={cn(splitButtonVariants({ buttonType }), 'border-none')}
aria-label="Open dropdown menu">
<ChevronDown />
</div>
</div>
</div>
);
}
);
ButtonSplit.displayName = 'ButtonSplit';