feat(ui): update button variants following figma (#482)

* feat(ui): update button variants following figma

* feat(ui): separate size prop to allow for regular and small sized button variants

* update type to no longer include minimal-small

* update uno css config to use new spacing system

* add variants and sizes to file upload; update button and file upload stories

* add mini button variant and update small button

* specify width on icon-only regular variant

* update plus buttons to be mini sizes

* remove redundant classnames

---------

Co-authored-by: doprz <52579214+doprz@users.noreply.github.com>
This commit is contained in:
Preston Cook
2025-01-07 14:59:15 -06:00
committed by GitHub
parent 0d73b13b28
commit 0aa469af81
15 changed files with 172 additions and 45 deletions

View File

@@ -35,6 +35,62 @@ export const Default: Story = {
},
};
export const Small: Story = {
// @ts-ignore
args: {
children: '',
},
render: props => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
<div style={{ display: 'flex', gap: '15px' }}>
<Button {...props} variant='filled' color='ut-black' size='small'>
Button
</Button>
<Button {...props} variant='outline' color='ut-black' size='small'>
Button
</Button>
<Button {...props} variant='minimal' color='ut-black' size='small'>
Button
</Button>
</div>
<hr />
<div style={{ display: 'flex', gap: '15px' }}>
<Button {...props} icon={ImageSquare} variant='filled' color='ut-black' size='small' />
<Button {...props} icon={ImageSquare} variant='outline' color='ut-black' size='small' />
<Button {...props} icon={ImageSquare} variant='minimal' color='ut-black' size='small' />
</div>
</div>
),
};
export const Mini: Story = {
// @ts-ignore
args: {
children: '',
},
render: props => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
<div style={{ display: 'flex', gap: '15px' }}>
<Button {...props} variant='filled' color='ut-black' size='mini'>
Button
</Button>
<Button {...props} variant='outline' color='ut-black' size='mini'>
Button
</Button>
<Button {...props} variant='minimal' color='ut-black' size='mini'>
Button
</Button>
</div>
<hr />
<div style={{ display: 'flex', gap: '15px' }}>
<Button {...props} icon={ImageSquare} variant='filled' color='ut-black' size='mini' />
<Button {...props} icon={ImageSquare} variant='outline' color='ut-black' size='mini' />
<Button {...props} icon={ImageSquare} variant='minimal' color='ut-black' size='mini' />
</div>
</div>
),
};
export const Disabled: Story = {
args: {
variant: 'filled',
@@ -50,7 +106,7 @@ export const Grid: Story = {
<div style={{ display: 'flex', gap: '15px' }}>
<Button {...props} variant='filled' color='ut-black' />
<Button {...props} variant='outline' color='ut-black' />
<Button {...props} variant='single' color='ut-black' />
<Button {...props} variant='minimal' color='ut-black' />
</div>
<hr />
@@ -58,7 +114,7 @@ export const Grid: Story = {
<div style={{ display: 'flex', gap: '15px' }}>
<Button {...props} variant='filled' color='ut-black' disabled />
<Button {...props} variant='outline' color='ut-black' disabled />
<Button {...props} variant='single' color='ut-black' disabled />
<Button {...props} variant='minimal' color='ut-black' disabled />
</div>
</div>
),
@@ -82,12 +138,12 @@ export const PrettyColors: Story = {
<Button {...props} variant='outline' color={color}>
Button
</Button>
<Button {...props} variant='single' color={color}>
<Button {...props} variant='minimal' color={color}>
Button
</Button>
<Button {...props} variant='filled' color={color} />
<Button {...props} variant='outline' color={color} />
<Button {...props} variant='single' color={color} />
<Button {...props} variant='minimal' color={color} />
</div>
))}
</div>

View File

@@ -45,6 +45,62 @@ export const Default: Story = {
},
};
export const Small: Story = {
// @ts-ignore
args: {
children: '',
},
render: props => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
<div style={{ display: 'flex', gap: '15px' }}>
<FileUpload {...props} variant='filled' color='ut-black' size='small'>
Upload File
</FileUpload>
<FileUpload {...props} variant='outline' color='ut-black' size='small'>
Upload File
</FileUpload>
<FileUpload {...props} variant='minimal' color='ut-black' size='small'>
Upload File
</FileUpload>
</div>
<hr />
<div style={{ display: 'flex', gap: '15px' }}>
<FileUpload {...props} icon={ImageSquare} variant='filled' color='ut-black' size='small' />
<FileUpload {...props} icon={ImageSquare} variant='outline' color='ut-black' size='small' />
<FileUpload {...props} icon={ImageSquare} variant='minimal' color='ut-black' size='small' />
</div>
</div>
),
};
export const Mini: Story = {
// @ts-ignore
args: {
children: '',
},
render: props => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
<div style={{ display: 'flex', gap: '15px' }}>
<FileUpload {...props} variant='filled' color='ut-black' size='mini'>
Button
</FileUpload>
<FileUpload {...props} variant='outline' color='ut-black' size='mini'>
Button
</FileUpload>
<FileUpload {...props} variant='minimal' color='ut-black' size='mini'>
Button
</FileUpload>
</div>
<hr />
<div style={{ display: 'flex', gap: '15px' }}>
<FileUpload {...props} icon={ImageSquare} variant='filled' color='ut-black' size='mini' />
<FileUpload {...props} icon={ImageSquare} variant='outline' color='ut-black' size='mini' />
<FileUpload {...props} icon={ImageSquare} variant='minimal' color='ut-black' size='mini' />
</div>
</div>
),
};
export const Disabled: Story = {
args: {
variant: 'filled',
@@ -60,7 +116,7 @@ export const Grid: Story = {
<div style={{ display: 'flex', gap: '15px' }}>
<FileUpload {...props} variant='filled' color='ut-black' />
<FileUpload {...props} variant='outline' color='ut-black' />
<FileUpload {...props} variant='single' color='ut-black' />
<FileUpload {...props} variant='minimal' color='ut-black' />
</div>
<hr />
@@ -68,7 +124,7 @@ export const Grid: Story = {
<div style={{ display: 'flex', gap: '15px' }}>
<FileUpload {...props} variant='filled' color='ut-black' disabled />
<FileUpload {...props} variant='outline' color='ut-black' disabled />
<FileUpload {...props} variant='single' color='ut-black' disabled />
<FileUpload {...props} variant='minimal' color='ut-black' disabled />
</div>
</div>
),
@@ -92,7 +148,7 @@ export const PrettyColors: Story = {
<FileUpload {...props} variant='outline' color={color}>
Button
</FileUpload>
<FileUpload {...props} variant='single' color={color}>
<FileUpload {...props} variant='minimal' color={color}>
Button
</FileUpload>
</div>

View File

@@ -56,10 +56,10 @@ export const AreYouSure: StoryObj<PromptDialogProps> = {
</Text>
),
children: [
<Button key='yes' variant='single' color='ut-burntorange'>
<Button key='yes' variant='minimal' color='ut-burntorange'>
Yes
</Button>,
<Button key='no' variant='single' color='ut-black'>
<Button key='no' variant='minimal' color='ut-black'>
No
</Button>,
],
@@ -76,7 +76,7 @@ export const YouHave10ActiveSchedules: StoryObj<PromptDialogProps> = {
</Text>
),
children: [
<Button key='yes' variant='single' color='ut-black'>
<Button key='yes' variant='minimal' color='ut-black'>
I understand
</Button>,
],
@@ -94,10 +94,10 @@ export const WelcomeToUTRPV2: StoryObj<PromptDialogProps> = {
</Text>
),
children: [
<Button key='migrate' variant='single' color='ut-black'>
<Button key='migrate' variant='minimal' color='ut-black'>
Don&apos;t Migrate
</Button>,
<Button key='start-fresh' variant='single' color='ut-burntorange'>
<Button key='start-fresh' variant='minimal' color='ut-burntorange'>
Migrate
</Button>,
],

View File

@@ -132,12 +132,11 @@ export default function PopupMain(): JSX.Element {
<div className='bottom-0 right-0 mt-2.5 w-full flex justify-end'>
<Button
variant='filled'
size='mini'
color='ut-burntorange'
className='h-fit p-0 btn'
onClick={handleAddSchedule}
>
<Plus className='h-6 w-6' />
</Button>
icon={Plus}
/>
</div>
</ScheduleDropdown>
</div>

View File

@@ -60,12 +60,12 @@ export default function CalendarBottomBar({ courseCells, setCourse }: CalendarBo
</div>
<div className='flex items-center screenshot:hidden'>
{displayCourses && <Divider orientation='vertical' size='1rem' className='mx-1.25' />}
<Button variant='single' color='ut-black' icon={CalendarDots} onClick={saveAsCal}>
<Button variant='minimal' color='ut-black' icon={CalendarDots} onClick={saveAsCal}>
Save as .CAL
</Button>
<Divider orientation='vertical' size='1rem' className='mx-1.25' />
<Button
variant='single'
variant='minimal'
color='ut-black'
icon={ImageSquare}
onClick={() => requestAnimationFrame(() => saveCalAsPng())}

View File

@@ -57,7 +57,7 @@ export default function CalendarHeader({ onSidebarToggle }: CalendarHeaderProps)
return (
<div className='flex items-center gap-5 overflow-x-auto overflow-y-hidden border-b border-ut-offwhite px-7 py-4 md:overflow-x-hidden'>
<Button
variant='single'
variant='minimal'
icon={Sidebar}
color='ut-gray'
onClick={onSidebarToggle}
@@ -83,7 +83,7 @@ export default function CalendarHeader({ onSidebarToggle }: CalendarHeaderProps)
{/* <Button variant='single' icon={UndoIcon} color='ut-black' />
<Button variant='single' icon={RedoIcon} color='ut-black' /> */}
<Button variant='single' icon={GearSix} color='theme-black' onClick={handleOpenOptions} />
<Button variant='minimal' icon={GearSix} color='theme-black' onClick={handleOpenOptions} />
</div>
</div>
);

View File

@@ -31,9 +31,7 @@ export function CalendarSchedules() {
<Text variant='h3' className='text-nowrap'>
MY SCHEDULES
</Text>
<Button variant='single' color='theme-black' className='h-fit p-0 btn' onClick={handleAddSchedule}>
<Plus className='h-6 w-6' />
</Button>
<Button size='mini' variant='minimal' color='theme-black' onClick={handleAddSchedule} icon={Plus} />
</div>
<div className='flex flex-col space-y-2.5'>
<List

View File

@@ -8,7 +8,8 @@ import React from 'react';
interface Props {
className?: string;
style?: React.CSSProperties;
variant: 'filled' | 'outline' | 'single';
variant?: 'filled' | 'outline' | 'minimal';
size?: 'regular' | 'small' | 'mini';
onClick?: () => void;
icon?: Icon;
iconProps?: IconProps;
@@ -24,7 +25,8 @@ interface Props {
export function Button({
className,
style,
variant,
variant = 'filled',
size = 'regular',
onClick,
icon,
iconProps,
@@ -52,11 +54,15 @@ export function Button({
{
'text-white! bg-opacity-100 hover:enabled:shadow-md active:enabled:shadow-sm shadow-black/20':
variant === 'filled',
'bg-opacity-0 border-current hover:enabled:bg-opacity-8 border': variant === 'outline',
'bg-opacity-0 border-none hover:enabled:bg-opacity-8': variant === 'single', // settings is the only "single"
'px-2 py-1.25': isIconOnly && variant !== 'outline',
'px-1.75 py-1.25': isIconOnly && variant === 'outline',
'px-3.75': variant === 'outline' && !isIconOnly,
'bg-opacity-0 border-current hover:enabled:bg-opacity-8 border stroke-width-[1px]':
variant === 'outline',
'bg-opacity-0 border-none hover:enabled:bg-opacity-8': variant === 'minimal',
'h-10 gap-spacing-3 px-spacing-5': size === 'regular' && !isIconOnly,
'h-10 w-10 p-spacing-2': size === 'regular' && isIconOnly,
'h-[35px] gap-spacing-3 px-spacing-3': size === 'small' && !isIconOnly,
'h-[35px] w-[35px] p-spacing-2': size === 'small' && isIconOnly,
'h-6 p-spacing-2': size === 'mini' && !isIconOnly,
'h-6 w-6 p-0': size === 'mini' && isIconOnly,
},
className
)}
@@ -66,7 +72,10 @@ export function Button({
>
{Icon && <Icon {...iconProps} className={clsx('h-6 w-6', iconProps?.className)} />}
{!isIconOnly && (
<Text variant='h4' className='inline-flex translate-y-0.08 items-center gap-2'>
<Text
variant={size === 'regular' ? 'h4' : 'small'}
className='inline-flex translate-y-0.08 items-center gap-2'
>
{children}
</Text>
)}

View File

@@ -8,7 +8,8 @@ import React from 'react';
interface Props {
className?: string;
style?: React.CSSProperties;
variant: 'filled' | 'outline' | 'single';
variant?: 'filled' | 'outline' | 'minimal';
size?: 'regular' | 'small' | 'mini';
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
icon?: Icon;
iconProps?: IconProps;
@@ -25,7 +26,8 @@ interface Props {
export default function FileUpload({
className,
style,
variant,
variant = 'filled',
size = 'regular',
onChange,
icon,
iconProps,
@@ -53,11 +55,15 @@ export default function FileUpload({
{
'text-white! bg-opacity-100 hover:enabled:shadow-md active:enabled:shadow-sm shadow-black/20':
variant === 'filled',
'bg-opacity-0 border-current hover:enabled:bg-opacity-8 border': variant === 'outline',
'bg-opacity-0 border-none hover:enabled:bg-opacity-8': variant === 'single', // settings is the only "single"
'px-2 py-1.25': isIconOnly && variant !== 'outline',
'px-1.75 py-1.25': isIconOnly && variant === 'outline',
'px-3.75': variant === 'outline' && !isIconOnly,
'bg-opacity-0 border-current hover:enabled:bg-opacity-8 border stroke-width-[1px]':
variant === 'outline',
'bg-opacity-0 border-none hover:enabled:bg-opacity-8': variant === 'minimal',
'h-10 gap-spacing-3 px-spacing-5': size === 'regular' && !isIconOnly,
'h-10 w-10 p-spacing-2': size === 'regular' && isIconOnly,
'h-[35px] gap-spacing-3 px-spacing-3': size === 'small' && !isIconOnly,
'h-[35px] w-[35px] p-spacing-2': size === 'small' && isIconOnly,
'h-6 p-spacing-2': size === 'mini' && !isIconOnly,
'h-6 w-6 p-0': size === 'mini' && isIconOnly,
},
className
)}
@@ -65,7 +71,10 @@ export default function FileUpload({
>
{Icon && <Icon {...iconProps} className={clsx('h-6 w-6', iconProps?.className)} />}
{!isIconOnly && (
<Text variant='h4' className='inline-flex translate-y-0.08 items-center gap-2'>
<Text
variant={size === 'regular' ? 'h4' : 'small'}
className='inline-flex translate-y-0.08 items-center gap-2'
>
{children}
</Text>
)}

View File

@@ -53,7 +53,7 @@ function MigrationButtons({ close }: { close: () => void }): JSX.Element {
</Text>
)}
<Button
variant='single'
variant='minimal'
color='ut-black'
onClick={() => {
close();

View File

@@ -73,7 +73,7 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
// eslint-disable-next-line react/no-unstable-nested-components
buttons: close => (
<>
<Button variant='single' color='ut-black' onClick={close}>
<Button variant='minimal' color='ut-black' onClick={close}>
Cancel
</Button>
<Button

View File

@@ -107,7 +107,7 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
<Text variant='h1' className='flex-1 whitespace-nowrap text-theme-black'>
({department} {courseNumber})
</Text>
<Button color='ut-burntorange' variant='single' icon={Copy} onClick={handleCopy}>
<Button color='ut-burntorange' variant='minimal' icon={Copy} onClick={handleCopy}>
{formattedUniqueId}
</Button>
<button className='bg-transparent p-0 text-ut-black btn' onClick={onClose}>

View File

@@ -260,7 +260,7 @@ export default function Settings(): JSX.Element {
UTRP SETTINGS & CREDITS PAGE
</Text>
<div className='hidden flex-row items-center justify-end gap-6 screenshot:hidden lg:flex'>
<Button variant='single' color='theme-black' onClick={handleChangelogOnClick}>
<Button variant='minimal' color='theme-black' onClick={handleChangelogOnClick}>
<IconoirGitFork className='h-6 w-6 text-ut-gray' />
<Text variant='small' className='text-ut-gray font-normal'>
v{manifest.version} - {process.env.NODE_ENV}

View File

@@ -21,7 +21,7 @@ export default function useChangelog(): () => void {
<Text variant='h1' className='text-theme-black'>
Changelog
</Text>
<Button variant='single' onClick={close} color='theme-black' className='p-1 text-gray-700'>
<Button variant='minimal' onClick={close} color='theme-black' className='p-1 text-gray-700'>
<X className='h-6 w-6' />
</Button>
</div>

View File

@@ -26,7 +26,7 @@ export default defineConfig({
],
shortcuts: {
focusable: 'outline-none ring-blue-500/50 dark:ring-blue-400/60 ring-0 focus-visible:ring-4',
btn: 'h-10 w-auto flex cursor-pointer justify-center items-center gap-2 rounded-1 px-4 py-0 text-4.5 btn-transition disabled:(cursor-not-allowed opacity-50) active:enabled:scale-96 focusable',
btn: 'h-10 w-auto flex cursor-pointer justify-center items-center gap-spacing-3 rounded-1 px-spacing-5 py-0 text-4.5 btn-transition disabled:(cursor-not-allowed opacity-50) active:enabled:scale-96 focusable',
link: 'text-ut-burntorange link:text-ut-burntorange underline underline-offset-2 hover:text-ut-orange focus-visible:text-ut-orange focusable btn-transition ease-out-expo',
linkanimate:
'relative cursor-pointer transition duration-100 ease-out after:(absolute left-0.4 right-0.4 h-2px scale-x-95 bg-ut-orange opacity-0 transition duration-250 ease-out-expo content-empty -bottom-0.75 -translate-y-0.5) active:scale-95 hover:text-ut-orange focus-visible:text-ut-orange hover:after:(opacity-100) !hover:after:translate-y-0 !hover:after:scale-x-100',