-
Notifications
You must be signed in to change notification settings - Fork 19
Components Foundational UI
Foundation components that power every interaction in EduLite. Built with TypeScript for reliability, styled with Tailwind for beauty, and designed for students worldwide.
The Button and Input components are the most fundamental UI elements in EduLite. Every form, every action, every user interaction relies on these components working perfectly - even on 2G networks, old devices, and during connectivity issues.
The Button component provides a consistent, accessible, and flexible way to handle user actions throughout EduLite. From submitting assignments to navigating between pages, buttons are how students interact with our platform.
interface ButtonProps {
// Required
children: ReactNode; // Button content (text, icons, etc.)
// Optional
onClick?: MouseEventHandler; // Click handler function
type?: 'primary' | 'secondary' | 'danger'; // Visual style
size?: 'sm' | 'md' | 'lg'; // Button size
width?: 'auto' | 'full' | 'half' | 'one-third' | 'two-thirds' | 'one-fourth' | 'three-fourths';
disabled?: boolean; // Disable interactions
className?: string; // Additional CSS classes
// Plus all native HTML button attributes
}import Button from '@/components/common/Button';
// Simple primary button
<Button onClick={handleSave}>
Save Assignment
</Button>
// Secondary action
<Button type="secondary" onClick={handleCancel}>
Cancel
</Button>
// Danger action with confirmation
<Button type="danger" onClick={handleDelete}>
Delete Course
</Button>// Small button for inline actions
<Button size="sm" onClick={handleQuickSave}>
Quick Save
</Button>
// Medium (default) for standard actions
<Button size="md" onClick={handleSubmit}>
Submit Assignment
</Button>
// Large for primary CTAs
<Button size="lg" onClick={handleStartCourse}>
Start Learning
</Button>// Full width for mobile forms
<Button width="full" onClick={handleLogin}>
Sign In
</Button>
// Half width for side-by-side buttons
<div className="flex gap-4">
<Button width="half" type="secondary">Cancel</Button>
<Button width="half">Continue</Button>
</div>import { Save, Send, Download } from 'lucide-react';
// Icon with text
<Button onClick={handleSave}>
<Save className="w-4 h-4 mr-2" />
Save Draft
</Button>
// Icon only (with aria-label for accessibility)
<Button size="sm" aria-label="Download assignment">
<Download className="w-4 h-4" />
</Button>// Disabled during loading
<Button disabled={isLoading} onClick={handleSubmit}>
{isLoading ? 'Submitting...' : 'Submit'}
</Button>
// Conditionally disabled
<Button
disabled={!formIsValid}
onClick={handleSubmit}
>
Submit Assignment
</Button>The Button component uses Tailwind CSS classes organized by concern:
// Base styles - applied to all buttons
const baseStyles =
"cursor-pointer inline-flex items-center justify-center font-medium rounded " +
"focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 " +
"transition-all ease-in-out duration-150 " +
"disabled:opacity-50 disabled:cursor-not-allowed " +
"active:scale-[.98]";
// Type variants - color schemes
const typeStyles = {
primary: "bg-blue-600 hover:bg-blue-700 text-white focus-visible:ring-blue-500",
secondary: "bg-white text-blue-700 border border-blue-600 hover:bg-blue-50",
danger: "bg-red-600 hover:bg-red-700 text-white focus-visible:ring-red-500"
};
// Size variants - padding and text size
const sizeStyles = {
sm: "px-3 py-1.5 text-sm",
md: "px-4 py-2 text-base",
lg: "px-6 py-3 text-lg"
};- Keyboard Navigation: Full keyboard support with visible focus states
- Screen Reader Support: Proper ARIA attributes
- Disabled State: Both visual and ARIA disabled states
- Focus Management: Clear focus indicators for keyboard users
- Color Contrast: WCAG AA compliant color combinations
We migrated Button from PropTypes to TypeScript for better type safety:
Before (PropTypes):
Button.propTypes = {
children: PropTypes.node.isRequired,
onClick: PropTypes.func,
type: PropTypes.oneOf(['primary', 'secondary', 'danger']),
size: PropTypes.oneOf(['sm', 'md', 'lg']),
disabled: PropTypes.bool
};After (TypeScript):
interface ButtonProps {
children: ReactNode;
onClick?: MouseEventHandler<HTMLButtonElement>;
type?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
}Benefits:
- Compile-time type checking
- Better IDE autocomplete
- Clearer component contracts
- No runtime overhead
The Input component provides a consistent, accessible text input field with built-in error handling, labels, and responsive design. It's the foundation for all forms in EduLite - from user registration to assignment submissions.
interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
// Required
name: string; // Input name attribute
value: string; // Current value
onChange: (e: ChangeEvent<HTMLInputElement>) => void; // Change handler
// Optional
type?: string; // Input type (text, email, password, etc.)
placeholder?: string; // Placeholder text
label?: string; // Label text above input
error?: string; // Error message to display
disabled?: boolean; // Disable input
required?: boolean; // Mark as required field
compact?: boolean; // Use compact spacing
className?: string; // Additional CSS classes
// Plus all native HTML input attributes
}import Input from '@/components/common/Input';
const [email, setEmail] = useState('');
<Input
name="email"
type="email"
label="Email Address"
placeholder="student@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setPassword(value);
// Validate password
if (value.length < 8) {
setError('Password must be at least 8 characters');
} else {
setError('');
}
};
<Input
name="password"
type="password"
label="Password"
value={password}
onChange={handlePasswordChange}
error={error}
required
/>const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
});
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
<form onSubmit={handleSubmit}>
<Input
name="username"
label="Username"
value={formData.username}
onChange={handleChange}
required
/>
<Input
name="email"
type="email"
label="Email"
value={formData.email}
onChange={handleChange}
required
/>
<Input
name="password"
type="password"
label="Password"
value={formData.password}
onChange={handleChange}
error={errors.password}
required
/>
<Button type="submit">Create Account</Button>
</form>// Use compact mode for dense forms
<div className="space-y-2">
<Input
name="firstName"
label="First Name"
value={firstName}
onChange={handleChange}
compact
/>
<Input
name="lastName"
label="Last Name"
value={lastName}
onChange={handleChange}
compact
/>
</div>const inputRef = useRef<HTMLInputElement>(null);
// Focus input on mount
useEffect(() => {
inputRef.current?.focus();
}, []);
<Input
ref={inputRef}
name="search"
placeholder="Search courses..."
value={searchTerm}
onChange={handleSearch}
/>The Input component features a modern glass-morphism design with dark mode support:
/* Key design features */
- Glass-morphism background: bg-white/80 dark:bg-gray-800/40
- Backdrop blur: backdrop-blur-xl
- Soft borders: border-gray-200/50 dark:border-gray-700/30
- Rounded corners: rounded-2xl
- Smooth transitions: transition-all duration-300
- Focus state scaling: focus:scale-[1.02]
- Error state styling: border-red-500/50 bg-red-50/50- Subtle border and shadow
- Clear placeholder text
- Smooth hover effects
- Blue ring indicator
- Slight scale increase (1.02x)
- Enhanced shadow
- Background color shift
- Red border color
- Red-tinted background
- Error message with pulse animation
- Red label color
- Reduced opacity (60%)
- Cursor not-allowed
- Muted background color
- No hover effects
-
Label Association: Proper
htmlForandidattributes - Required Indicators: Visual asterisk (*) for required fields
- Error Announcements: Error messages linked to inputs for screen readers
- Keyboard Navigation: Full keyboard support
- Focus Management: Clear focus indicators
- ARIA Attributes: Proper ARIA labels and descriptions
const LoginForm = () => {
const [formData, setFormData] = useState({
username: '',
password: ''
});
const [errors, setErrors] = useState({});
const [loading, setLoading] = useState(false);
return (
<form onSubmit={handleSubmit} className="space-y-4">
<Input
name="username"
label="Username"
value={formData.username}
onChange={handleChange}
error={errors.username}
disabled={loading}
required
/>
<Input
name="password"
type="password"
label="Password"
value={formData.password}
onChange={handleChange}
error={errors.password}
disabled={loading}
required
/>
<Button
width="full"
disabled={loading}
>
{loading ? 'Signing in...' : 'Sign In'}
</Button>
</form>
);
};const SearchBar = () => {
const [search, setSearch] = useState('');
const debouncedSearch = useDebounce(search, 500);
useEffect(() => {
if (debouncedSearch) {
performSearch(debouncedSearch);
}
}, [debouncedSearch]);
return (
<Input
name="search"
type="search"
placeholder="Search courses, students, or assignments..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="max-w-md"
/>
);
};The Input component's TypeScript implementation provides:
-
Type-safe event handling:
onChange: (e: ChangeEvent<HTMLInputElement>) => void
-
Extends native attributes:
interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange'>
-
ForwardRef typing:
const Input = React.forwardRef<HTMLInputElement, InputProps>
-
Autocomplete for all HTML input attributes: The component accepts all standard input attributes like
autoComplete,autoFocus,pattern, etc.
Both components are optimized for performance:
- React.forwardRef for proper ref forwarding
- Memoization-ready - components are pure
- Minimal re-renders - proper event handler patterns
- CSS-based animations - no JavaScript animations
- Tailwind purging - only used classes in final bundle
Planned improvements for these components:
- Loading spinner built into Button component
- Input masking for phone numbers, dates
- Textarea variant of Input for long text
- Button groups for related actions
- Input addons for prefixes/suffixes (like currency symbols)
- Floating labels animation pattern
These components are the foundation of EduLite's UI. Every improvement here benefits every student using our platform. Contributions welcome!