feat: add shimmer loading skeletons for various pages and components

- Implemented UiShimmer as a core shimmer wrapper for animated gradient effects.
- Created shimmer presets for list items, stats cards, section headers, and more.
- Developed specific skeletons for billing, invoices, coverage, hubs, reports, payments, shifts, and home pages.
- Enhanced user experience by providing visual placeholders during data loading.
This commit is contained in:
Achintha Isuru
2026-03-10 13:21:30 -04:00
parent 3f112f5eb7
commit 0f0714c55b
36 changed files with 1594 additions and 36 deletions

View File

@@ -14,3 +14,6 @@ export 'src/widgets/ui_loading_page.dart';
export 'src/widgets/ui_snackbar.dart';
export 'src/widgets/ui_notice_banner.dart';
export 'src/widgets/ui_empty_state.dart';
export 'src/widgets/shimmer/ui_shimmer.dart';
export 'src/widgets/shimmer/ui_shimmer_shapes.dart';
export 'src/widgets/shimmer/ui_shimmer_presets.dart';

View File

@@ -0,0 +1,27 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
/// Core shimmer wrapper that applies an animated gradient effect to its child.
///
/// Wraps the `shimmer` package's [Shimmer.fromColors] using design system
/// color tokens. Place shimmer shape primitives as children.
class UiShimmer extends StatelessWidget {
/// Creates a shimmer effect wrapper around [child].
const UiShimmer({
super.key,
required this.child,
});
/// The widget tree to apply the shimmer gradient over.
final Widget child;
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: UiColors.muted,
highlightColor: UiColors.background,
child: child,
);
}
}

View File

@@ -0,0 +1,123 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// List-row shimmer skeleton with a leading circle, two text lines, and a
/// trailing box.
///
/// Mimics a typical list item layout during loading. Wrap with [UiShimmer]
/// to activate the animated gradient.
class UiShimmerListItem extends StatelessWidget {
/// Creates a list-row shimmer skeleton.
const UiShimmerListItem({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space2,
),
child: Row(
children: [
const UiShimmerCircle(size: UiConstants.space10),
const SizedBox(width: UiConstants.space3),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const UiShimmerLine(width: 160),
const SizedBox(height: UiConstants.space2),
const UiShimmerLine(width: 100, height: 12),
],
),
),
const SizedBox(width: UiConstants.space3),
const UiShimmerBox(width: 48, height: 24),
],
),
);
}
}
/// Stats-card shimmer skeleton with an icon placeholder, a short label line,
/// and a taller value line.
///
/// Wrapped in a bordered container matching the design system card pattern.
/// Wrap with [UiShimmer] to activate the animated gradient.
class UiShimmerStatsCard extends StatelessWidget {
/// Creates a stats-card shimmer skeleton.
const UiShimmerStatsCard({super.key});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
border: Border.all(color: UiColors.border),
borderRadius: UiConstants.radiusLg,
color: UiColors.cardViewBackground,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const UiShimmerCircle(size: UiConstants.space8),
const SizedBox(height: UiConstants.space3),
const UiShimmerLine(width: 80, height: 12),
const SizedBox(height: UiConstants.space2),
const UiShimmerLine(width: 120, height: 20),
],
),
);
}
}
/// Section-header shimmer skeleton rendering a single wide line placeholder.
///
/// Wrap with [UiShimmer] to activate the animated gradient.
class UiShimmerSectionHeader extends StatelessWidget {
/// Creates a section-header shimmer skeleton.
const UiShimmerSectionHeader({super.key});
@override
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: UiConstants.space2),
child: UiShimmerLine(width: 200, height: 18),
);
}
}
/// Repeats a shimmer widget [itemCount] times in a [Column] with spacing.
///
/// Use [itemBuilder] to produce each item. Wrap the entire list with
/// [UiShimmer] to share a single animated gradient across all items.
class UiShimmerList extends StatelessWidget {
/// Creates a shimmer list with [itemCount] items built by [itemBuilder].
const UiShimmerList({
super.key,
required this.itemBuilder,
this.itemCount = 3,
this.spacing,
});
/// Builder that produces each shimmer placeholder item by index.
final Widget Function(int index) itemBuilder;
/// Number of shimmer items to render. Defaults to 3.
final int itemCount;
/// Vertical spacing between items. Defaults to [UiConstants.space3].
final double? spacing;
@override
Widget build(BuildContext context) {
final gap = spacing ?? UiConstants.space3;
return Column(
children: List.generate(itemCount, (index) {
return Padding(
padding: EdgeInsets.only(bottom: index < itemCount - 1 ? gap : 0),
child: itemBuilder(index),
);
}),
);
}
}

View File

@@ -0,0 +1,95 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// Rectangular shimmer placeholder with configurable dimensions and corner radius.
///
/// Renders as a solid white container; the parent [UiShimmer] applies the
/// animated gradient.
class UiShimmerBox extends StatelessWidget {
/// Creates a rectangular shimmer placeholder.
const UiShimmerBox({
super.key,
required this.width,
required this.height,
this.borderRadius,
});
/// Width of the placeholder rectangle.
final double width;
/// Height of the placeholder rectangle.
final double height;
/// Corner radius. Defaults to [UiConstants.radiusMd] when null.
final BorderRadius? borderRadius;
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: borderRadius ?? UiConstants.radiusMd,
),
);
}
}
/// Circular shimmer placeholder with a configurable diameter.
///
/// Renders as a solid white circle; the parent [UiShimmer] applies the
/// animated gradient.
class UiShimmerCircle extends StatelessWidget {
/// Creates a circular shimmer placeholder with the given [size] as diameter.
const UiShimmerCircle({
super.key,
required this.size,
});
/// Diameter of the circle.
final double size;
@override
Widget build(BuildContext context) {
return Container(
width: size,
height: size,
decoration: const BoxDecoration(
color: UiColors.white,
shape: BoxShape.circle,
),
);
}
}
/// Text-line shimmer placeholder with configurable width and height.
///
/// Useful for simulating a single line of text. Renders as a solid white
/// rounded rectangle; the parent [UiShimmer] applies the animated gradient.
class UiShimmerLine extends StatelessWidget {
/// Creates a text-line shimmer placeholder.
const UiShimmerLine({
super.key,
this.width = double.infinity,
this.height = 14,
});
/// Width of the line. Defaults to [double.infinity].
final double width;
/// Height of the line. Defaults to 14 logical pixels.
final double height;
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: UiConstants.radiusSm,
),
);
}
}

View File

@@ -15,6 +15,7 @@ dependencies:
google_fonts: ^7.0.2
lucide_icons: ^0.257.0
font_awesome_flutter: ^10.7.0
shimmer: ^3.0.0
dev_dependencies:
flutter_test: