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:
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user