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).
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user