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 = { export const Disabled: Story = {
args: { args: {
variant: 'filled', variant: 'filled',
@@ -50,7 +106,7 @@ export const Grid: Story = {
<div style={{ display: 'flex', gap: '15px' }}> <div style={{ display: 'flex', gap: '15px' }}>
<Button {...props} variant='filled' color='ut-black' /> <Button {...props} variant='filled' color='ut-black' />
<Button {...props} variant='outline' 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> </div>
<hr /> <hr />
@@ -58,7 +114,7 @@ export const Grid: Story = {
<div style={{ display: 'flex', gap: '15px' }}> <div style={{ display: 'flex', gap: '15px' }}>
<Button {...props} variant='filled' color='ut-black' disabled /> <Button {...props} variant='filled' color='ut-black' disabled />
<Button {...props} variant='outline' 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>
</div> </div>
), ),
@@ -82,12 +138,12 @@ export const PrettyColors: Story = {
<Button {...props} variant='outline' color={color}> <Button {...props} variant='outline' color={color}>
Button Button
</Button> </Button>
<Button {...props} variant='single' color={color}> <Button {...props} variant='minimal' color={color}>
Button Button
</Button> </Button>
<Button {...props} variant='filled' color={color} /> <Button {...props} variant='filled' color={color} />
<Button {...props} variant='outline' color={color} /> <Button {...props} variant='outline' color={color} />
<Button {...props} variant='single' color={color} /> <Button {...props} variant='minimal' color={color} />
</div> </div>
))} ))}
</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 = { export const Disabled: Story = {
args: { args: {
variant: 'filled', variant: 'filled',
@@ -60,7 +116,7 @@ export const Grid: Story = {
<div style={{ display: 'flex', gap: '15px' }}> <div style={{ display: 'flex', gap: '15px' }}>
<FileUpload {...props} variant='filled' color='ut-black' /> <FileUpload {...props} variant='filled' color='ut-black' />
<FileUpload {...props} variant='outline' 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> </div>
<hr /> <hr />
@@ -68,7 +124,7 @@ export const Grid: Story = {
<div style={{ display: 'flex', gap: '15px' }}> <div style={{ display: 'flex', gap: '15px' }}>
<FileUpload {...props} variant='filled' color='ut-black' disabled /> <FileUpload {...props} variant='filled' color='ut-black' disabled />
<FileUpload {...props} variant='outline' 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>
</div> </div>
), ),
@@ -92,7 +148,7 @@ export const PrettyColors: Story = {
<FileUpload {...props} variant='outline' color={color}> <FileUpload {...props} variant='outline' color={color}>
Button Button
</FileUpload> </FileUpload>
<FileUpload {...props} variant='single' color={color}> <FileUpload {...props} variant='minimal' color={color}>
Button Button
</FileUpload> </FileUpload>
</div> </div>

View File

@@ -56,10 +56,10 @@ export const AreYouSure: StoryObj<PromptDialogProps> = {
</Text> </Text>
), ),
children: [ children: [
<Button key='yes' variant='single' color='ut-burntorange'> <Button key='yes' variant='minimal' color='ut-burntorange'>
Yes Yes
</Button>, </Button>,
<Button key='no' variant='single' color='ut-black'> <Button key='no' variant='minimal' color='ut-black'>
No No
</Button>, </Button>,
], ],
@@ -76,7 +76,7 @@ export const YouHave10ActiveSchedules: StoryObj<PromptDialogProps> = {
</Text> </Text>
), ),
children: [ children: [
<Button key='yes' variant='single' color='ut-black'> <Button key='yes' variant='minimal' color='ut-black'>
I understand I understand
</Button>, </Button>,
], ],
@@ -94,10 +94,10 @@ export const WelcomeToUTRPV2: StoryObj<PromptDialogProps> = {
</Text> </Text>
), ),
children: [ children: [
<Button key='migrate' variant='single' color='ut-black'> <Button key='migrate' variant='minimal' color='ut-black'>
Don&apos;t Migrate Don&apos;t Migrate
</Button>, </Button>,
<Button key='start-fresh' variant='single' color='ut-burntorange'> <Button key='start-fresh' variant='minimal' color='ut-burntorange'>
Migrate Migrate
</Button>, </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'> <div className='bottom-0 right-0 mt-2.5 w-full flex justify-end'>
<Button <Button
variant='filled' variant='filled'
size='mini'
color='ut-burntorange' color='ut-burntorange'
className='h-fit p-0 btn'
onClick={handleAddSchedule} onClick={handleAddSchedule}
> icon={Plus}
<Plus className='h-6 w-6' /> />
</Button>
</div> </div>
</ScheduleDropdown> </ScheduleDropdown>
</div> </div>

View File

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

View File

@@ -57,7 +57,7 @@ export default function CalendarHeader({ onSidebarToggle }: CalendarHeaderProps)
return ( 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'> <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 <Button
variant='single' variant='minimal'
icon={Sidebar} icon={Sidebar}
color='ut-gray' color='ut-gray'
onClick={onSidebarToggle} onClick={onSidebarToggle}
@@ -83,7 +83,7 @@ export default function CalendarHeader({ onSidebarToggle }: CalendarHeaderProps)
{/* <Button variant='single' icon={UndoIcon} color='ut-black' /> {/* <Button variant='single' icon={UndoIcon} color='ut-black' />
<Button variant='single' icon={RedoIcon} 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>
</div> </div>
); );

View File

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

View File

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

View File

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

View File

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

View File

@@ -73,7 +73,7 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
// eslint-disable-next-line react/no-unstable-nested-components // eslint-disable-next-line react/no-unstable-nested-components
buttons: close => ( buttons: close => (
<> <>
<Button variant='single' color='ut-black' onClick={close}> <Button variant='minimal' color='ut-black' onClick={close}>
Cancel Cancel
</Button> </Button>
<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'> <Text variant='h1' className='flex-1 whitespace-nowrap text-theme-black'>
({department} {courseNumber}) ({department} {courseNumber})
</Text> </Text>
<Button color='ut-burntorange' variant='single' icon={Copy} onClick={handleCopy}> <Button color='ut-burntorange' variant='minimal' icon={Copy} onClick={handleCopy}>
{formattedUniqueId} {formattedUniqueId}
</Button> </Button>
<button className='bg-transparent p-0 text-ut-black btn' onClick={onClose}> <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 UTRP SETTINGS & CREDITS PAGE
</Text> </Text>
<div className='hidden flex-row items-center justify-end gap-6 screenshot:hidden lg:flex'> <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' /> <IconoirGitFork className='h-6 w-6 text-ut-gray' />
<Text variant='small' className='text-ut-gray font-normal'> <Text variant='small' className='text-ut-gray font-normal'>
v{manifest.version} - {process.env.NODE_ENV} 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'> <Text variant='h1' className='text-theme-black'>
Changelog Changelog
</Text> </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' /> <X className='h-6 w-6' />
</Button> </Button>
</div> </div>

View File

@@ -26,7 +26,7 @@ export default defineConfig({
], ],
shortcuts: { shortcuts: {
focusable: 'outline-none ring-blue-500/50 dark:ring-blue-400/60 ring-0 focus-visible:ring-4', 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', 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: 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', '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',