Skip to main content

Loader Components

The Loader Components provide various loading states and spinner animations for the WorkPayCore Frontend application. These components handle different loading scenarios from fullscreen overlays to inline spinners for better user experience during asynchronous operations.

Overview

This document covers all loader components that provide visual feedback during loading states, from simple spinners to fullscreen overlays with custom text and animations.

Components Overview

Core Loader Components


FullscreenSpinner

A fullscreen loading overlay that displays a spinner with optional loading text. Provides a semi-transparent backdrop to prevent user interaction during loading operations.

Component Location

import FullscreenSpinner from 'components/Loaders/FullscreenSpinner';

Props

PropTypeRequiredDefaultDescription
isLoadingboolean-Controls the visibility of spinner
childrenReactNode-Content to render behind overlay
loadingTextstring--Optional loading text to display

TypeScript Interface

interface FullscreenSpinnerProps {
isLoading: boolean;
children: React.ReactNode;
loadingText?: string;
}

Features

Fullscreen Overlay

  • Fixed positioning covering entire viewport
  • Semi-transparent dark backdrop (rgba(0, 0, 0, 0.5))
  • High z-index (1200) to appear above other content
  • Prevents user interaction during loading

Loading Indicator

  • Large spinner (size='xl') with WorkPay brand color (#62A446)
  • Centered horizontally and vertically
  • Optional loading text with consistent styling

Content Rendering

  • Renders children content behind the overlay
  • Overlay only appears when isLoading is true
  • Non-blocking rendering of underlying content

Usage Examples

Basic Usage

import FullscreenSpinner from 'components/Loaders/FullscreenSpinner';

function DataTable() {
const [isLoading, setIsLoading] = useState(false);

return (
<FullscreenSpinner isLoading={isLoading}>
<TableComponent />
</FullscreenSpinner>
);
}

With Loading Text

import FullscreenSpinner from 'components/Loaders/FullscreenSpinner';

function PayrollProcessing() {
const [isProcessing, setIsProcessing] = useState(false);

const handleProcessPayroll = async () => {
setIsProcessing(true);
try {
await processPayroll();
} finally {
setIsProcessing(false);
}
};

return (
<FullscreenSpinner
isLoading={isProcessing}
loadingText='Processing payroll... Please wait'
>
<PayrollDashboard />
<Button onClick={handleProcessPayroll}>Process Payroll</Button>
</FullscreenSpinner>
);
}

Form Submission

import FullscreenSpinner from 'components/Loaders/FullscreenSpinner';

function EmployeeForm() {
const [isSubmitting, setIsSubmitting] = useState(false);

const handleSubmit = async data => {
setIsSubmitting(true);
try {
await saveEmployee(data);
toast.success('Employee saved successfully');
} catch (error) {
toast.error('Failed to save employee');
} finally {
setIsSubmitting(false);
}
};

return (
<FullscreenSpinner
isLoading={isSubmitting}
loadingText='Saving employee information...'
>
<form onSubmit={handleSubmit}>
<FormFields />
<Button type='submit' disabled={isSubmitting}>
Save Employee
</Button>
</form>
</FullscreenSpinner>
);
}

Styling

  • Background: Semi-transparent overlay (rgba(0, 0, 0, 0.5))
  • Spinner Color: WorkPay brand green (#62A446)
  • Spinner Size: Extra large (xl)
  • Text Color: Dark blue (#253545)
  • Text Size: 16px
  • Text Weight: 400 (normal)
  • Z-Index: 1200

Accessibility

  • Focus Management: Traps focus within overlay when active
  • Screen Reader: Announces loading state through text
  • Keyboard Navigation: Prevents interaction with background content

AppLoader

A bouncing dots loader animation with customizable background color. Provides a subtle loading animation suitable for inline use within buttons or smaller components.

Component Location

import AppLoader from 'components/Loaders/AppLoader';

Props

PropTypeRequiredDefaultDescription
backgroundColorstring-'rgb(34, 34, 34) !important'Background color of dots

TypeScript Interface

interface AppLoaderProps {
backgroundColor?: string;
}

Features

Bouncing Animation

  • Three circular dots bouncing in sequence
  • Smooth ease-in-out animation timing
  • Staggered animation delays for wave effect
  • Infinite loop animation (0.9s duration)

Customizable Styling

  • Customizable dot background color
  • Fixed dot size (8px × 8px)
  • Consistent spacing between dots (2px margin)
  • Fully rounded dots (borderRadius: 9999px)

Performance Optimized

  • CSS keyframes for smooth animation
  • Hardware acceleration with transform properties
  • Minimal DOM footprint (3 span elements)

Usage Examples

Basic Usage

import AppLoader from 'components/Loaders/AppLoader';

function LoadingButton() {
const [isLoading, setIsLoading] = useState(false);

return (
<Button disabled={isLoading}>{isLoading ? <AppLoader /> : 'Submit'}</Button>
);
}

Custom Color

import AppLoader from 'components/Loaders/AppLoader';

function CustomLoader() {
return (
<VStack spacing={4}>
<AppLoader backgroundColor='#62A446' />
<AppLoader backgroundColor='#007bff' />
<AppLoader backgroundColor='#dc3545' />
</VStack>
);
}

Button Integration

import AppLoader from 'components/Loaders/AppLoader';

function AsyncButton({ onClick, isLoading, children }) {
return (
<Button onClick={onClick} disabled={isLoading} minW='120px'>
{isLoading ? <AppLoader backgroundColor='white' /> : children}
</Button>
);
}

// Usage
<AsyncButton onClick={handleSave} isLoading={isSaving}>
Save Changes
</AsyncButton>;

Inline Loading

import AppLoader from 'components/Loaders/AppLoader';

function DataCard() {
const [isRefreshing, setIsRefreshing] = useState(false);

return (
&lt;Card&gt;
&lt;CardHeader&gt;
<Heading size='md'>
Data Overview
{isRefreshing && <AppLoader backgroundColor='#62A446' />}
</Heading>
</CardHeader>
&lt;CardBody&gt;
<DataContent />
</CardBody>
</Card>
);
}

Animation Keyframes

@keyframes bounceDelay {
0%,
80%,
100% {
transform: scale(0);
}
40% {
transform: scale(1);
}
}

Styling

  • Dot Size: 8px × 8px
  • Dot Shape: Fully rounded (borderRadius: 9999px)
  • Dot Spacing: 2px margin between dots
  • Animation Duration: 0.9s
  • Animation Timing: ease-in-out
  • Animation Delays: -0.32s, -0.16s, 0s (staggered)

SuspenseSpinner

A rotating spinner component designed for React Suspense fallbacks and general loading states. Features a custom SVG icon with smooth rotation animation.

Component Location

import SuspenseSpinner from 'components/Loaders/Spinner';

Props

No props required - this is a stateless component.

Features

Custom SVG Animation

  • Custom SVG spinner icon with 8 directional indicators
  • Smooth rotation animation (1s linear infinite)
  • Centered within container using Chakra UI's Center component
  • Responsive sizing (24px × 24px)

Suspense Integration

  • Designed specifically for React Suspense fallbacks
  • Minimal rendering footprint
  • Consistent with application design language

Flexible Positioning

  • Uses Chakra UI Center component for flexible positioning
  • Can be used as inline or block-level loading indicator
  • Maintains aspect ratio across different container sizes

Usage Examples

React Suspense Fallback

import { Suspense } from 'react';
import SuspenseSpinner from 'components/Loaders/Spinner';

function App() {
return (
<Suspense fallback={<SuspenseSpinner />}>
<LazyComponent />
</Suspense>
);
}

Lazy Loading Components

import { lazy, Suspense } from 'react';
import SuspenseSpinner from 'components/Loaders/Spinner';

const LazyDashboard = lazy(() => import('./Dashboard'));
const LazyReports = lazy(() => import('./Reports'));

function Routes() {
return (
&lt;Routes&gt;
<Route
path='/dashboard'
element={
<Suspense fallback={<SuspenseSpinner />}>
<LazyDashboard />
</Suspense>
}
/>
<Route
path='/reports'
element={
<Suspense fallback={<SuspenseSpinner />}>
<LazyReports />
</Suspense>
}
/>
</Routes>
);
}

Loading State

import SuspenseSpinner from 'components/Loaders/Spinner';

function DataDisplay() {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState(null);

useEffect(() => {
fetchData().then(result => {
setData(result);
setIsLoading(false);
});
}, []);

if (isLoading) {
return <SuspenseSpinner />;
}

return <DataComponent data={data} />;
}

Container Usage

import SuspenseSpinner from 'components/Loaders/Spinner';

function LoadingContainer() {
return (
<Box height='200px' width='100%'>
<SuspenseSpinner />
</Box>
);
}

Animation Keyframes

@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

Styling

  • Size: 24px × 24px
  • Animation: 1s linear infinite rotation
  • Stroke Color: Black (#000)
  • Stroke Width: 2px
  • Stroke Caps: Round
  • Stroke Joins: Round

Loading Patterns

Choosing the Right Loader

Use FullscreenSpinner when:

  • Performing critical operations that require user attention
  • Processing large amounts of data
  • Operations that prevent user interaction
  • Long-running background tasks

Use AppLoader when:

  • Loading states within buttons
  • Inline loading indicators
  • Short-duration operations
  • Maintaining visual hierarchy

Use SuspenseSpinner when:

  • React Suspense fallbacks
  • Lazy loading components
  • Simple loading states
  • Minimal visual impact required

Loading State Management

// Global loading state pattern
function useGlobalLoading() {
const [isLoading, setIsLoading] = useState(false);
const [loadingText, setLoadingText] = useState('');

const showLoading = (text = '') => {
setLoadingText(text);
setIsLoading(true);
};

const hideLoading = () => {
setIsLoading(false);
setLoadingText('');
};

return { isLoading, loadingText, showLoading, hideLoading };
}

// Usage
function MyComponent() {
const { isLoading, loadingText, showLoading, hideLoading } =
useGlobalLoading();

const handleOperation = async () => {
showLoading('Processing data...');
try {
await performOperation();
} finally {
hideLoading();
}
};

return (
<FullscreenSpinner isLoading={isLoading} loadingText={loadingText}>
<PageContent />
</FullscreenSpinner>
);
}

Performance Considerations

Optimization Tips

  1. Lazy Loading: Use SuspenseSpinner for code splitting
  2. Debouncing: Avoid showing loaders for very short operations
  3. Memory Management: Clean up loading states properly
  4. Animation Performance: Use CSS animations over JavaScript

Example Implementation

// Debounced loading pattern
function useDebouncedLoading(delay = 300) {
const [isLoading, setIsLoading] = useState(false);
const [showSpinner, setShowSpinner] = useState(false);

useEffect(() => {
let timer;
if (isLoading) {
timer = setTimeout(() => setShowSpinner(true), delay);
} else {
setShowSpinner(false);
}
return () => clearTimeout(timer);
}, [isLoading, delay]);

return { isLoading, showSpinner, setIsLoading };
}

Best Practices

User Experience

  1. Loading Text: Provide descriptive loading messages
  2. Progressive Loading: Show content as it becomes available
  3. Error Handling: Always handle loading state cleanup
  4. Accessibility: Ensure screen readers announce loading states

Technical Implementation

  1. State Management: Use consistent loading state patterns
  2. Memory Leaks: Clean up loading states in useEffect cleanup
  3. Performance: Avoid unnecessary re-renders during loading
  4. Testing: Test loading states thoroughly

Common Patterns

// Async operation with proper cleanup
function useAsyncOperation() {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);

const execute = async operation => {
setIsLoading(true);
setError(null);
try {
const result = await operation();
return result;
} catch (err) {
setError(err);
throw err;
} finally {
setIsLoading(false);
}
};

return { isLoading, error, execute };
}

This comprehensive loader system provides flexible, performant loading states for all scenarios within the WorkPayCore Frontend application.