Files
eigen-hris/src/components/ui/Button.tsx
T
saefulrahman 16cbe65c62 Initialize Eigen HRIS project
- Add .gitignore, .yarnrc.yml, and initial project metadata (AGENTS.md, DESIGN.md, package.json, Yarn lock, Tailwind, PostCSS, Next.js config, TypeScript config)
- Add CSV data files (users, employees, contracts, attendances)
- Implement API routes for auth, employees, contracts, attendances, analytics, and file upload
- Add core pages (dashboard, employees, attendances, profile, login, main layout)
- Add UI components (avatar, avatar picker, badge, button, card, input, modal, select, sidebar, navigation shell, top header)
- Add attendance features (table, clock widget, unified permit modal with file upload, upload API)
- Add employee management (contract timeline with file upload, employee form modal, employee table)
- Add dashboard widgets (leaderboard, early bird, latecomer, night owl, day status feed, date filter bar)
- Add utilities (avatar generator, analytics calculations, CSV DB layer, auth context)
- Add type definitions for auth, employee, contract, attendance, dashboard

All changes are synchronized with documentation (AGENTS.md, DESIGN.md, walkthrough).
2026-08-27 17:41:59 +07:00

82 lines
2.4 KiB
TypeScript

import React from 'react';
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' | 'pastel';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
}
export const Button: React.FC<ButtonProps> = ({
children,
className,
variant = 'primary',
size = 'md',
isLoading = false,
disabled,
...props
}) => {
const baseStyles =
'inline-flex items-center justify-center font-semibold rounded-xl transition-all duration-150 active:scale-[0.98] disabled:opacity-50 disabled:pointer-events-none focus:outline-none focus:ring-2 focus:ring-offset-2';
const sizeStyles = {
sm: 'px-3 py-1.5 text-xs gap-1.5',
md: 'px-4 py-2 text-sm gap-2',
lg: 'px-6 py-2.5 text-base gap-2.5',
};
const variantStyles = {
primary:
'bg-brand-primary text-white hover:bg-brand-hover shadow-sm hover:shadow-brand-glow focus:ring-brand-primary',
secondary:
'bg-brand-secondary text-white hover:bg-brand-hover focus:ring-brand-secondary',
pastel:
'bg-brand-pastel/30 text-brand-dark hover:bg-brand-pastel/60 border border-brand-pastel focus:ring-brand-pastel',
outline:
'border border-slate-200 bg-white text-slate-700 hover:bg-slate-50 hover:border-slate-300 focus:ring-slate-300',
ghost:
'bg-transparent text-slate-600 hover:bg-slate-100 hover:text-slate-900 focus:ring-slate-200',
danger:
'bg-red-600 text-white hover:bg-red-700 shadow-sm focus:ring-red-500',
};
return (
<button
className={twMerge(
clsx(
baseStyles,
sizeStyles[size],
variantStyles[variant],
className
)
)}
disabled={disabled || isLoading}
{...props}
>
{isLoading && (
<svg
className="animate-spin -ml-1 mr-2 h-4 w-4 text-current"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
)}
{children}
</button>
);
};