buster/apps/web/src/components/ui/buttons/Button.tsx

222 lines
6.4 KiB
TypeScript
Raw Normal View History

2025-02-23 04:50:10 +08:00
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
Biome linter test (#336) * update files for use with biom * fix prettier formats * minor biome fixes * fix additional files * update linting rules * fix additional linting errors * fix linting errors * update lib * run check in the lib direcotyr * update some linting errors * fix problems * move addtional files to stricter linting * fix imports and linting errors * update some biome settings * fix query parser * quick wins * update files * fix addtional things * fix context files * update additional biome files * fix additional files * biome fixes * fixin files * fix broken * fix additional files and problems * fix biome buster stuff * fix helpers and * stricter linting * fixed * fix missing types * fix linting * remove missing imports * update more biome test stuff * update fixes * update imports * more updates * fix some linting errors * fix broken children as prop errors * lint ci * update lint check * Update package.json * Update next.config.mjs * fix some broken tests * update some tests * chore(versions): bump api to v0.1.16; bump web to v0.1.16; bump cli to v0.1.16 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * Update web-unit-tests.yml * fix some tests * chore(versions): bump api to v0.1.17; bump web to v0.1.17; bump cli to v0.1.17 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * update ignore files * chore(versions): bump api to v0.1.18; bump web to v0.1.18; bump cli to v0.1.18 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * Update web-lint.yml * chore(versions): bump api to v0.1.19; bump web to v0.1.19; bump cli to v0.1.19 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * update boime package settings * chore(versions): bump api to v0.1.20; bump web to v0.1.20; bump cli to v0.1.20 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * save to dropdown bug fixes * update console logs * update chart animation * chore(versions): bump api to v0.1.21; bump web to v0.1.21; bump cli to v0.1.21 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * update build * chore(versions): bump api to v0.1.22; bump web to v0.1.22; bump cli to v0.1.22 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * additinal updates for biome 2 * update imports * chore(versions): bump api to v0.1.23; bump web to v0.1.23; bump cli to v0.1.23 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * final updates * chore(versions): bump api to v0.1.24; bump web to v0.1.24; bump cli to v0.1.24 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * Update dashboard-updates.test.ts * chore(versions): bump api to v0.1.25; bump web to v0.1.25; bump cli to v0.1.25 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * crazy fix * chore(versions): bump api to v0.1.26; bump web to v0.1.26; bump cli to v0.1.26 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * more attempts for stable tests * chore(versions): bump api to v0.1.27; bump web to v0.1.27; bump cli to v0.1.27 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * update tests * split the test off * chore(versions): bump api to v0.1.28; bump web to v0.1.28; bump cli to v0.1.28 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * Update pie-styling-updates.spec.ts * Create web-e2e-tests-optimized.yml * chore(versions): bump api to v0.1.29; bump web to v0.1.29; bump cli to v0.1.29 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * update tests * chore(versions): bump api to v0.1.30; bump web to v0.1.30; bump cli to v0.1.30 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * Update web-e2e-tests-optimized.yml * chore(versions): bump api to v0.1.31; bump web to v0.1.31; bump cli to v0.1.31 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * Update web-e2e-tests-optimized.yml * chore(versions): bump api to v0.1.32; bump web to v0.1.32; bump cli to v0.1.32 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * Update web-e2e-tests-optimized.yml * chore(versions): bump api to v0.1.33; bump web to v0.1.33; bump cli to v0.1.33 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * Update web-e2e-tests-optimized.yml * chore(versions): bump api to v0.1.34; bump web to v0.1.34; bump cli to v0.1.34 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * Update web-e2e-tests-optimized.yml * Update web-e2e-tests-optimized.yml * Revert "Update web-e2e-tests-optimized.yml" This reverts commit dfc9263a2621f1c00dee30e28dbb01a9f8f914ef. * Update web-e2e-tests-optimized.yml * chore(versions): bump api to v0.1.35; bump web to v0.1.35; bump cli to v0.1.35 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] * Update web-e2e-tests-optimized.yml * chore(versions): bump api to v0.1.36; bump web to v0.1.36; bump cli to v0.1.36 [skip ci] * chore: update tag_info.json with potential release versions [skip ci] --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-31 01:16:48 +08:00
import * as React from 'react';
2025-02-23 05:01:55 +08:00
import { cn } from '@/lib/classMerge';
2025-02-23 05:58:58 +08:00
import { CircleSpinnerLoader } from '../loaders/CircleSpinnerLoader';
2025-02-23 05:01:55 +08:00
2025-02-25 04:52:33 +08:00
export const buttonTypeClasses = {
default:
2025-02-26 12:28:50 +08:00
'bg-background border hover:bg-item-hover disabled:bg-disabled disabled:text-gray-light active:bg-item-active data-[selected=true]:bg-item-select',
2025-03-04 05:25:51 +08:00
black: 'bg-black text-white hover:bg-foreground-hover disabled:bg-black/60',
outlined:
'bg-transparent border border-border text-gray-dark hover:bg-item-hover disabled:bg-transparent disabled:text-gray-light active:bg-item-active data-[selected=true]:bg-item-select',
2025-02-25 04:52:33 +08:00
primary:
2025-03-21 02:03:00 +08:00
'bg-primary disabled:bg-disabled text-white hover:bg-primary-light disabled:text-gray-light active:bg-primary-dark data-[selected=true]:bg-primary-dark',
2025-02-25 04:52:33 +08:00
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',
2025-02-25 05:15:21 +08:00
link: 'bg-transparent text-gray-dark shadow-none hover:text-foreground disabled:bg-transparent disabled:text-gray-light',
danger:
'bg-danger-background text-danger-foreground hover:bg-danger-background-hover active:bg-danger-background-hover data-[selected=true]:bg-danger-background-hover',
warning:
'bg-warning-background text-warning-foreground hover:bg-warning-background-hover active:bg-warning-background-hover data-[selected=true]:bg-warning-background-hover',
success:
'bg-success-background text-success-foreground hover:bg-success-background-hover active:bg-success-background-hover data-[selected=true]:bg-success-background-hover'
2025-02-25 04:52:33 +08:00
};
const roundingVariants = {
default: 'rounded',
full: 'rounded-full',
large: 'rounded-lg',
small: 'rounded-sm',
none: 'rounded-none'
};
const sizeVariants = {
2025-03-02 12:17:45 +08:00
default: 'h-6 min-h-6 max-h-6',
tall: 'h-7 min-h-7 max-h-7',
small: 'h-5 min-h-5 max-h-5'
2025-02-25 04:52:33 +08:00
};
2025-02-25 02:18:44 +08:00
export const buttonVariants = cva(
2025-04-09 08:39:27 +08:00
'inline-flex whitespace-nowrap items-center overflow-hidden text-base justify-center gap-1.5 shadow rounded transition-all duration-300 focus-visible:outline-none disabled:pointer-events-none disabled:cursor-not-allowed data-[loading=true]:cursor-progress',
2025-02-23 05:01:55 +08:00
{
variants: {
2025-02-25 10:11:26 +08:00
variant: buttonTypeClasses,
2025-02-25 04:52:33 +08:00
size: sizeVariants,
2025-02-23 07:07:58 +08:00
iconButton: {
true: '',
false: 'px-2.5'
2025-02-25 01:55:35 +08:00
},
2025-02-25 05:10:09 +08:00
rounding: roundingVariants,
block: {
true: 'w-full',
false: ''
}
2025-02-23 05:01:55 +08:00
},
2025-02-23 07:07:58 +08:00
compoundVariants: [
{
iconButton: true,
size: 'default',
2025-03-02 12:17:45 +08:00
className: 'w-6 min-w-6 max-w-6'
2025-02-23 07:07:58 +08:00
},
{
iconButton: true,
size: 'tall',
2025-03-02 12:17:45 +08:00
className: 'w-7 min-w-7 max-w-7'
2025-02-23 07:07:58 +08:00
},
{
iconButton: true,
size: 'small',
2025-03-02 12:17:45 +08:00
className: 'w-5 min-w-5 max-w-5'
2025-02-23 07:07:58 +08:00
}
],
2025-02-23 05:01:55 +08:00
defaultVariants: {
2025-02-25 10:11:26 +08:00
variant: 'default',
2025-02-23 07:07:58 +08:00
size: 'default',
2025-02-25 05:10:09 +08:00
iconButton: false,
block: false
2025-02-23 05:01:55 +08:00
}
}
);
2025-02-25 02:18:44 +08:00
export const buttonIconVariants = cva('', {
2025-02-23 07:19:43 +08:00
variants: {
2025-02-25 10:11:26 +08:00
variant: {
2025-03-20 01:06:52 +08:00
default: 'text-icon-color',
2025-03-27 02:07:02 +08:00
black: 'text-white!',
outlined: 'text-icon-color',
2025-03-20 01:06:52 +08:00
primary: 'text-white',
ghost: 'text-icon-color',
link: 'text-icon-color',
danger: 'text-danger-foreground',
warning: 'text-warning-foreground',
success: 'text-success-foreground'
2025-02-23 07:19:43 +08:00
},
size: {
2025-03-21 00:23:51 +08:00
default: 'text-icon-size!',
tall: 'text-icon-size-lg!',
small: 'text-icon-size-sm!'
2025-03-20 01:06:52 +08:00
},
disabled: {
true: 'text-gray-light',
false: ''
2025-02-23 07:19:43 +08:00
}
2025-03-27 02:07:02 +08:00
},
compoundVariants: [
{
variant: 'black',
disabled: true,
className: 'text-white'
},
{
variant: 'outlined',
size: 'tall',
className: 'text-icon-size!'
2025-03-27 02:07:02 +08:00
}
]
2025-02-23 07:19:43 +08:00
});
2025-02-25 01:29:44 +08:00
const loadingSizeVariants = {
default: 'w-[var(--text-icon-size)] h-[var(--text-icon-size)]',
tall: 'w-[var(--text-icon-size-lg)] h-[var(--text-icon-size-lg)]',
small: 'w-[var(--text-icon-size-sm)] h-[var(--text-icon-size-sm)]'
};
2025-02-23 05:01:55 +08:00
export interface ButtonProps
2025-02-23 05:58:58 +08:00
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'prefix'>,
2025-02-23 05:01:55 +08:00
VariantProps<typeof buttonVariants> {
asChild?: boolean;
2025-02-23 05:58:58 +08:00
prefix?: React.ReactNode;
suffix?: React.ReactNode;
loading?: boolean;
selected?: boolean;
2025-02-23 07:09:47 +08:00
prefixClassName?: string;
suffixClassName?: string;
2025-02-25 05:10:09 +08:00
block?: boolean;
2025-02-23 05:01:55 +08:00
}
2025-02-25 02:18:44 +08:00
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
2025-02-23 05:58:58 +08:00
(
{
2025-02-23 06:20:50 +08:00
className = '',
2025-02-25 10:11:26 +08:00
variant = 'default',
2025-02-23 06:20:50 +08:00
size = 'default',
2025-02-23 05:58:58 +08:00
asChild = false,
prefix,
suffix,
children,
loading = false,
selected = false,
2025-02-23 06:20:50 +08:00
disabled = false,
2025-02-23 07:09:47 +08:00
prefixClassName,
suffixClassName,
2025-02-25 01:55:35 +08:00
rounding = 'default',
2025-02-25 05:10:09 +08:00
block = false,
2025-02-25 05:17:07 +08:00
onClick,
2025-02-23 05:58:58 +08:00
...props
},
ref
) => {
2025-02-23 05:01:55 +08:00
const Comp = asChild ? Slot : 'button';
2025-02-23 07:07:58 +08:00
const hasChildren = !!children;
const iconButton = !hasChildren && (!prefix || !suffix);
2025-02-25 01:55:35 +08:00
2025-02-23 05:01:55 +08:00
return (
2025-02-23 05:58:58 +08:00
<Comp
2025-02-25 05:17:07 +08:00
className={cn(
2025-02-26 05:59:21 +08:00
'cursor-pointer',
buttonVariants({ variant, size, iconButton, rounding, block, className })
2025-02-25 05:17:07 +08:00
)}
onClick={onClick}
2025-02-23 05:58:58 +08:00
ref={ref}
2025-02-23 06:20:50 +08:00
disabled={disabled}
2025-02-23 05:58:58 +08:00
data-loading={loading}
data-selected={selected}
{...props}>
2025-02-23 07:07:58 +08:00
{loading ? (
2025-02-25 10:11:26 +08:00
<LoadingIcon variant={variant} size={size} />
2025-02-23 07:07:58 +08:00
) : (
prefix && (
2025-03-20 01:06:52 +08:00
<span className={cn(buttonIconVariants({ disabled, variant, size }), prefixClassName)}>
2025-02-23 07:09:47 +08:00
{prefix}
</span>
2025-02-23 07:07:58 +08:00
)
)}
2025-02-25 03:55:35 +08:00
{hasChildren && <span className="">{children}</span>}
2025-02-23 07:07:58 +08:00
{suffix && (
2025-03-20 01:06:52 +08:00
<span className={cn(buttonIconVariants({ disabled, variant, size }), suffixClassName)}>
2025-02-25 02:18:44 +08:00
{suffix}
</span>
2025-02-23 07:07:58 +08:00
)}
2025-02-23 05:58:58 +08:00
</Comp>
2025-02-23 05:01:55 +08:00
);
}
);
2025-02-25 02:18:44 +08:00
Button.displayName = 'Button';
2025-02-23 05:01:55 +08:00
2025-02-25 04:52:33 +08:00
export const LoadingIcon: React.FC<{
2025-02-25 10:11:26 +08:00
variant: ButtonProps['variant'];
2025-02-25 01:29:44 +08:00
size: ButtonProps['size'];
2025-02-25 10:11:26 +08:00
}> = ({ variant = 'default', size = 'default' }) => {
2025-02-23 06:05:27 +08:00
return (
<div
className={cn(
2025-02-25 01:29:44 +08:00
'flex items-center justify-center text-black dark:text-white',
2025-02-25 10:11:26 +08:00
variant === 'black' && 'dark',
2025-02-25 01:29:44 +08:00
loadingSizeVariants[size || 'default']
2025-02-23 06:05:27 +08:00
)}>
<CircleSpinnerLoader
2025-02-25 01:29:44 +08:00
size={size === 'tall' ? 12.5 : 9.5}
2025-02-23 06:27:32 +08:00
fill={
2025-02-25 10:11:26 +08:00
variant === 'black'
2025-02-23 06:27:32 +08:00
? 'var(--color-white)'
2025-02-25 10:11:26 +08:00
: variant === 'primary'
2025-02-23 06:27:32 +08:00
? 'var(--color-primary)'
: 'var(--color-primary)'
}
2025-02-23 06:05:27 +08:00
/>
</div>
);
};