feat(ui): add Button, Modal, Spinner, Toast, and Tooltip components with styles
All checks were successful
Build and Release / build-and-release (push) Successful in 13m12s
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.
This commit is contained in:
108
packages/ui/src/Button.tsx
Normal file
108
packages/ui/src/Button.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
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';
|
||||
Reference in New Issue
Block a user