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 - Fullscreen loading overlay with optional text
- AppLoader - Bouncing dots loader with custom colors
- SuspenseSpinner - Rotating spinner for React Suspense fallbacks
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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| isLoading | boolean | ✓ | - | Controls the visibility of spinner |
| children | ReactNode | ✓ | - | Content to render behind overlay |
| loadingText | string | - | - | 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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| backgroundColor | string | - | '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 (
<Card>
<CardHeader>
<Heading size='md'>
Data Overview
{isRefreshing && <AppLoader backgroundColor='#62A446' />}
</Heading>
</CardHeader>
<CardBody>
<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 (
<Routes>
<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
- Lazy Loading: Use SuspenseSpinner for code splitting
- Debouncing: Avoid showing loaders for very short operations
- Memory Management: Clean up loading states properly
- 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
- Loading Text: Provide descriptive loading messages
- Progressive Loading: Show content as it becomes available
- Error Handling: Always handle loading state cleanup
- Accessibility: Ensure screen readers announce loading states
Technical Implementation
- State Management: Use consistent loading state patterns
- Memory Leaks: Clean up loading states in useEffect cleanup
- Performance: Avoid unnecessary re-renders during loading
- 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.