All checks were successful
Build and Release / build-and-release (push) Successful in 13m12s
- Implemented Button component with various props for customization. - Created Modal component with header, content, and footer subcomponents. - Added Spinner component for loading indicators. - Developed Toast component for displaying notifications. - Introduced Tooltip component for contextual hints with keyboard shortcuts. - Added corresponding CSS modules for styling each component. - Updated index file to export new components. - Configured TypeScript settings for the UI package.
109 lines
2.4 KiB
TypeScript
109 lines
2.4 KiB
TypeScript
import clsx from 'clsx';
|
|
import React from 'react';
|
|
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
import styles from './Button.module.css';
|
|
|
|
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
variant?: 'primary' | 'secondary' | 'danger' | 'dangerSecondary' | 'ghost' | 'link';
|
|
size?: 'sm' | 'md' | 'lg';
|
|
loading?: boolean;
|
|
icon?: ReactNode;
|
|
fitContainer?: boolean;
|
|
fitContent?: boolean;
|
|
square?: boolean;
|
|
children?: ReactNode;
|
|
}
|
|
|
|
const variantClassMap: Record<string, string> = {
|
|
primary: 'primary',
|
|
secondary: 'secondary',
|
|
danger: 'dangerPrimary',
|
|
dangerSecondary: 'dangerSecondary',
|
|
ghost: 'ghost',
|
|
link: 'link',
|
|
};
|
|
|
|
const sizeClassMap: Record<string, string | undefined> = {
|
|
sm: 'compact',
|
|
md: undefined,
|
|
lg: undefined,
|
|
};
|
|
|
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
(
|
|
{
|
|
variant = 'primary',
|
|
size = 'md',
|
|
loading = false,
|
|
icon,
|
|
fitContainer = false,
|
|
fitContent = false,
|
|
square = false,
|
|
children,
|
|
className,
|
|
disabled,
|
|
type = 'button',
|
|
onClick,
|
|
...props
|
|
},
|
|
ref,
|
|
) => {
|
|
const variantClass = variantClassMap[variant] ?? 'primary';
|
|
const sizeClass = sizeClassMap[size];
|
|
|
|
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
if (loading) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
onClick?.(event);
|
|
};
|
|
|
|
return (
|
|
<button
|
|
ref={ref}
|
|
className={clsx(
|
|
styles.button,
|
|
styles[variantClass],
|
|
{
|
|
[styles.compact]: sizeClass === 'compact',
|
|
[styles.square]: square,
|
|
[styles.fitContainer]: fitContainer,
|
|
[styles.fitContent]: fitContent,
|
|
},
|
|
className,
|
|
)}
|
|
disabled={disabled || loading}
|
|
type={type}
|
|
onClick={handleClick}
|
|
tabIndex={disabled ? -1 : 0}
|
|
{...props}
|
|
>
|
|
<div className={styles.grid}>
|
|
<div className={clsx(styles.iconWrapper, { [styles.hidden]: loading })}>
|
|
{square ? (
|
|
icon
|
|
) : (
|
|
<>
|
|
{icon}
|
|
{children}
|
|
</>
|
|
)}
|
|
</div>
|
|
<div className={clsx(styles.spinnerWrapper, { [styles.hidden]: !loading })}>
|
|
<span className={styles.spinner}>
|
|
<span className={styles.spinnerInner}>
|
|
<span className={styles.spinnerItem} />
|
|
<span className={styles.spinnerItem} />
|
|
<span className={styles.spinnerItem} />
|
|
</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
);
|
|
},
|
|
);
|
|
|
|
Button.displayName = 'Button';
|