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

- 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:
Bryan1029384756
2026-04-14 09:02:14 -05:00
parent 9ef839938e
commit b7a4cf4ce8
376 changed files with 52619 additions and 167641 deletions

108
packages/ui/src/Button.tsx Normal file
View 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';