Add skeleton loading components for progressive UX
Implement standardized skeleton loading placeholders to reduce perceived loading time and prevent layout shift during data fetches. These components match actual content dimensions exactly, improving perceived responsiveness. New skeleton components in src/components/skeletons/: - SkeletonTable: Table/grid loading with customizable rows and cells - SkeletonTableRow: Individual animated skeleton row - SkeletonChart: Chart/graph loading with bars matching dimensions - SkeletonStat: Stat card loading with label and value - SkeletonFormField: Form input loading placeholder - PageLoadingSkeleton: Convenience wrapper for page-level loading states Implementation details: - All skeletons use global 'skeleton-pulse' animation (2s cycle) - Dimensions match real content to prevent layout shift on arrival - Marked with aria-hidden and role=presentation for accessibility - Theme-aware colors using Fluent UI tokens - Respects prefers-reduced-motion setting Updates: - ChartStateWrapper: Uses SkeletonChart instead of spinner - PageFeedback: Added PageLoadingSkeleton component - App.tsx: Injects skeleton styles at startup - Web-Design.md: Added § 8a with loading UX guidance and usage examples All components tested (22 tests, 100% passing) and linted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -235,12 +235,86 @@ Use Fluent UI React components as the building blocks. The following mapping sho
|
||||
| Success messages | `MessageBar` (success) | "IP 1.2.3.4 has been banned in jail sshd." |
|
||||
| Error messages | `MessageBar` (error) | "Failed to connect to fail2ban server." |
|
||||
| Warning messages | `MessageBar` (warning) | "Blocklist import encountered 12 invalid entries." |
|
||||
| Loading states | `Shimmer` | Apply to `DetailsList` rows and stat cards while data loads. |
|
||||
| Loading states | `SkeletonTable` / `SkeletonChart` (preferred) or `Shimmer` (legacy) | Show skeleton placeholders matching layout. Use `PageLoadingSkeleton` for page-level loading. |
|
||||
| Empty states | Custom illustration + text | "No bans recorded in the last 24 hours." Centre on the content area. |
|
||||
| Tooltips | `Tooltip` / `TooltipHost` | Full IP info on hover, full regex on truncated text, icon-only button labels. |
|
||||
|
||||
---
|
||||
|
||||
## 8a. Loading States & Skeleton Components
|
||||
|
||||
Progressive loading states improve perceived responsiveness and reduce cognitive load during data fetches.
|
||||
|
||||
### When to Use Skeleton Placeholders
|
||||
|
||||
Use skeleton loading states instead of spinners for regions with a fixed, known layout:
|
||||
- **Tables** — Show skeleton rows that match the actual column layout and row height
|
||||
- **Charts** — Show skeleton bars matching the chart dimensions
|
||||
- **Stat cards** — Show skeleton badges matching actual stat dimensions
|
||||
- **Forms** — Show skeleton fields matching input dimensions
|
||||
|
||||
Use full-page spinners only when:
|
||||
- Loading time is expected to be under 1 second
|
||||
- Content layout is unknown or highly variable
|
||||
- Entire page structure is being loaded
|
||||
|
||||
### Skeleton Components
|
||||
|
||||
BanGUI provides pre-built skeleton components in `src/components/skeletons/`:
|
||||
|
||||
| Component | Usage | Notes |
|
||||
|---|---|---|
|
||||
| `<SkeletonTable>` | Table/grid loading | Pass `rowCount` and `cellCount` to match real layout. |
|
||||
| `<SkeletonTableRow>` | Individual table rows | Renders a single animated skeleton row. |
|
||||
| `<SkeletonChart>` | Chart/graph loading | Pass `barCount` and `height` to match container dimensions. |
|
||||
| `<SkeletonStat>` | Stat badge loading | Shows label + value stacked. Pass `showLabel={false}` to hide label. |
|
||||
| `<SkeletonFormField>` | Form input loading | Shows label + input placeholder. Pass `inputHeight` for custom sizing. |
|
||||
| `<PageLoadingSkeleton>` | Page-level loading | Convenience wrapper. Pass `type="table"` or `type="chart"`. |
|
||||
|
||||
### Skeleton Implementation Details
|
||||
|
||||
- **Dimensions:** Skeletons must exactly match real content dimensions (height, width, spacing) to prevent layout shift when content arrives.
|
||||
- **Animation:** All skeletons use a subtle 2-second pulse animation (`skeleton-pulse` keyframe). The animation respects `prefers-reduced-motion`.
|
||||
- **Accessibility:** Skeleton elements are marked `aria-hidden="true"` and `role="presentation"` — they are not part of the accessible tree.
|
||||
- **Theming:** Skeleton colour uses `colorNeutralBackground1Hover` token for automatic light/dark mode support.
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```tsx
|
||||
// Loading table data
|
||||
if (loading) {
|
||||
return <SkeletonTable rowCount={10} cellCount={6} />;
|
||||
}
|
||||
|
||||
// Loading chart
|
||||
if (chartLoading) {
|
||||
return <PageLoadingSkeleton type="chart" itemCount={12} chartHeight={220} />;
|
||||
}
|
||||
|
||||
// Loading multiple stats
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)" }}>
|
||||
{[1, 2, 3].map(i => <SkeletonStat key={i} />)}
|
||||
</div>
|
||||
|
||||
// Loading form
|
||||
<div style={{ gap: "16px", display: "flex", flexDirection: "column" }}>
|
||||
<SkeletonFormField />
|
||||
<SkeletonFormField />
|
||||
<SkeletonFormField showLabel={false} />
|
||||
</div>
|
||||
```
|
||||
|
||||
### Avoiding Layout Shift
|
||||
|
||||
**Critical:** Skeleton components must reserve the exact space that real content will occupy. Test by:
|
||||
1. Load skeleton while data is fetching
|
||||
2. Observe the skeleton render to full height/width
|
||||
3. Observe real content arriving — no movement or repaint of surrounding elements
|
||||
|
||||
If content shifts when it arrives, adjust skeleton dimensions or parent container constraints.
|
||||
|
||||
---
|
||||
|
||||
## 9. Tables & Data Grids
|
||||
|
||||
Tables are the primary UI element in BanGUI. They must be treated with extreme care.
|
||||
@@ -387,7 +461,7 @@ export const sideNavCollapsedWidth = 48;
|
||||
| Reference theme tokens for all colours | Hard-code hex values like `#ff0000` in components |
|
||||
| Follow the 4 px spacing grid | Use arbitrary pixel values (13 px, 7 px, 19 px) |
|
||||
| Provide a tooltip for every icon-only button | Leave icons unlabelled and inaccessible |
|
||||
| Use `Shimmer` for loading states | Show a blank screen or a standalone spinner with no context |
|
||||
| Use skeleton placeholders for loading states | Show a blank screen or a standalone spinner with no context |
|
||||
| Design for both light and dark themes | Default to white backgrounds assuming light mode only |
|
||||
| Use `DetailsList` for all tabular data | Use raw HTML `<table>` elements or a third-party data grid |
|
||||
| Use semantic colour slots (`errorText`, `bodyBackground`) | Use descriptive palette slots (`red`, `neutralLight`) directly |
|
||||
|
||||
Reference in New Issue
Block a user