Skip to content

Components Foundational UI

Mathew Storm edited this page Sep 26, 2025 · 1 revision

Core UI Components - Button & Input

Foundation components that power every interaction in EduLite. Built with TypeScript for reliability, styled with Tailwind for beauty, and designed for students worldwide.

Overview

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.

Button Component

Purpose

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.

API Reference

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
}

Usage Examples

Basic Button

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>

Different Sizes

// 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>

Width Variants

// 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>

With Icons

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 States

// Disabled during loading
<Button disabled={isLoading} onClick={handleSubmit}>
  {isLoading ? 'Submitting...' : 'Submit'}
</Button>

// Conditionally disabled
<Button
  disabled={!formIsValid}
  onClick={handleSubmit}
>
  Submit Assignment
</Button>

Styling & Theming

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"
};

Accessibility Features

  • 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

TypeScript Migration Notes

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

Input Component

Purpose

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.

API Reference

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
}

Usage Examples

Basic Input

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)}
/>

With Validation

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
/>

Form Integration

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>

Compact Mode

// 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>

With Ref (for focus management)

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}
/>

Styling & Design

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

State Visualizations

Normal State

  • Subtle border and shadow
  • Clear placeholder text
  • Smooth hover effects

Focus State

  • Blue ring indicator
  • Slight scale increase (1.02x)
  • Enhanced shadow
  • Background color shift

Error State

  • Red border color
  • Red-tinted background
  • Error message with pulse animation
  • Red label color

Disabled State

  • Reduced opacity (60%)
  • Cursor not-allowed
  • Muted background color
  • No hover effects

Accessibility Features

  • Label Association: Proper htmlFor and id attributes
  • 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

Form Patterns

Login Form

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>
  );
};

Search Input

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"
    />
  );
};

TypeScript Benefits

The Input component's TypeScript implementation provides:

  1. Type-safe event handling:

    onChange: (e: ChangeEvent<HTMLInputElement>) => void
  2. Extends native attributes:

    interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange'>
  3. ForwardRef typing:

    const Input = React.forwardRef<HTMLInputElement, InputProps>
  4. Autocomplete for all HTML input attributes: The component accepts all standard input attributes like autoComplete, autoFocus, pattern, etc.


Performance Considerations

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

Future Enhancements

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!

Clone this wiki locally