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.