xtal_dashboard 0.2.0 copy "xtal_dashboard: ^0.2.0" to clipboard
xtal_dashboard: ^0.2.0 copied to clipboard

A grid-based dashboard layout library with drag-and-drop reordering, resizing, and physics-based animations.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:xtal_dashboard/xtal_dashboard.dart';

void main() {
  runApp(const DemoApp());
}

class DemoApp extends StatelessWidget {
  const DemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'xtal_dashboard Demo',
      theme: ThemeData.light(useMaterial3: true),
      debugShowCheckedModeBanner: false,
      home: const DemoPage(),
    );
  }
}

class DemoPage extends StatefulWidget {
  const DemoPage({super.key});

  @override
  State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {
  bool _collisionEnabled = true;
  bool _editingEnabled = true;
  bool _shelfEnabled = true;
  late List<DashboardItemData> _items;
  late List<DashboardItemData> _shelfItems;
  final ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    _items = _createInitialItems();
    _shelfItems = _createInitialShelfItems();
  }

  // ---- Initial shelf items ----

  static const _shelfItemConfigs = <(String, int, int, int, String)>[
    // (id, columnSpan, rowSpan, colorValue, title)
    ('S1', 1, 1, 0xFFEF5350, 'CPU'), // Red 400
    ('S2', 2, 1, 0xFF7E57C2, 'Recent Activity Log'), // Deep Purple 400
    ('S3', 1, 1, 0xFF29B6F6, 'System Performance Monitor'), // Light Blue 400
  ];

  List<DashboardItemData> _createInitialShelfItems() {
    return _shelfItemConfigs.map((config) {
      final (id, colSpan, rowSpan, colorValue, title) = config;
      final color = Color(colorValue);
      return DashboardItemData(
        gridItem: GridItem(
          id: id,
          column: 0,
          row: 0,
          columnSpan: colSpan,
          rowSpan: rowSpan,
        ),
        builder: (context, isBeingDragged) => _buildCard(color, id),
        title: title,
      );
    }).toList();
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  // ---- Initial grid layout (4 columns, 8 items, mixed spans) ----
  //
  //   Col:  0     1     2     3
  // Row 0: [ A  (2x2)   ] [ B (2x1)  ]
  // Row 1: [             ] [ C  ] [ D  ]
  // Row 2: [ E  ] [ F (2x1)  ] [ G  ]
  // Row 3: [ H (3x1)         ] [    ]

  static const _itemConfigs = <(String, int, int, int, int, int, String)>[
    // (id, column, row, columnSpan, rowSpan, colorValue, title)
    ('A', 0, 0, 2, 2, 0xFF42A5F5, 'Overview Dashboard'), // Blue 400
    ('B', 2, 0, 2, 1, 0xFF26A69A, 'Network'), // Teal 400
    ('C', 2, 1, 1, 1, 0xFFAB47BC, 'Mem'), // Purple 300
    ('D', 3, 1, 1, 1, 0xFFEC407A, 'IO'), // Pink 300
    ('E', 0, 2, 1, 1, 0xFFFFCA28, 'Alerts'), // Amber 400
    ('F', 1, 2, 2, 1, 0xFFFFA726, 'Request Throughput'), // Orange 400
    ('G', 3, 2, 1, 1, 0xFF66BB6A, 'Up'), // Green 400
    (
      'H',
      0,
      3,
      3,
      1,
      0xFF5C6BC0,
      'Latency Distribution (p50 / p95 / p99)',
    ), // Indigo 300
  ];

  List<DashboardItemData> _createInitialItems() {
    return _itemConfigs.map((config) {
      final (id, col, row, colSpan, rowSpan, colorValue, title) = config;
      final color = Color(colorValue);
      return DashboardItemData(
        gridItem: GridItem(
          id: id,
          column: col,
          row: row,
          columnSpan: colSpan,
          rowSpan: rowSpan,
        ),
        builder: (context, isBeingDragged) => _buildCard(color, id),
        title: title,
      );
    }).toList();
  }

  // ---- Card widget with visual patterns for FittedBox verification ----

  static Widget _buildCard(Color color, String label) {
    final darker = Color.lerp(color, const Color(0xFF000000), 0.3)!;

    return DecoratedBox(
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: [color, darker],
        ),
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: const Color(0x30FFFFFF), width: 1.5),
      ),
      child: Stack(
        children: [
          // Corner dots — visible when layout is correct.
          for (final alignment in [
            Alignment.topLeft,
            Alignment.topRight,
            Alignment.bottomLeft,
            Alignment.bottomRight,
          ])
            Align(
              alignment: alignment,
              child: Padding(
                padding: const EdgeInsets.all(6),
                child: DecoratedBox(
                  decoration: BoxDecoration(
                    color: const Color(0x40FFFFFF),
                    borderRadius: BorderRadius.circular(3),
                  ),
                  child: const SizedBox(width: 6, height: 6),
                ),
              ),
            ),
          // Thin horizontal rule across the middle.
          const Center(
            child: Padding(
              padding: EdgeInsets.symmetric(horizontal: 16),
              child: DecoratedBox(
                decoration: BoxDecoration(color: Color(0x20FFFFFF)),
                child: SizedBox(height: 1, width: double.infinity),
              ),
            ),
          ),
          // Label + subtitle.
          Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Text(
                  label,
                  style: const TextStyle(
                    color: Color(0xFFFFFFFF),
                    fontSize: 28,
                    fontWeight: FontWeight.bold,
                    decoration: TextDecoration.none,
                  ),
                ),
                const SizedBox(height: 2),
                Text(
                  'Widget $label',
                  style: const TextStyle(
                    color: Color(0x99FFFFFF),
                    fontSize: 10,
                    fontWeight: FontWeight.w500,
                    decoration: TextDecoration.none,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  // ---- Custom ghost: blue border + glow ----

  static Widget _buildGhost(BuildContext context, Widget child, Size size) {
    return DecoratedBox(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: const Color(0xFF2196F3), width: 3),
        boxShadow: const [
          BoxShadow(color: Color(0x402196F3), blurRadius: 16, spreadRadius: 4),
        ],
      ),
      child: ClipRRect(
        borderRadius: BorderRadius.circular(12),
        child: Opacity(opacity: 0.85, child: child),
      ),
    );
  }

  // ---- Build ----

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('xtal_dashboard (shelf: ${_shelfItems.length})'),
        actions: [
          Padding(
            padding: const EdgeInsets.only(right: 8),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Text('Edit'),
                Switch.adaptive(
                  value: _editingEnabled,
                  onChanged: (value) {
                    setState(() => _editingEnabled = value);
                  },
                ),
                const SizedBox(width: 8),
                const Text('Shelf'),
                Switch.adaptive(
                  value: _shelfEnabled,
                  onChanged: (value) {
                    setState(() => _shelfEnabled = value);
                  },
                ),
                const SizedBox(width: 8),
                const Text('Collision'),
                Switch.adaptive(
                  value: _collisionEnabled,
                  onChanged: (value) {
                    setState(() => _collisionEnabled = value);
                  },
                ),
              ],
            ),
          ),
        ],
      ),
      body: SafeArea(
        child: SingleChildScrollView(
          controller: _scrollController,
          padding: const EdgeInsets.all(12),
          child: XtalDashboard(
            scrollController: _scrollController,
            columns: 4,
            items: _items,
            gap: 12,
            padding: const EdgeInsets.all(12),
            collisionDetectionEnabled: _collisionEnabled,
            editingEnabled: _editingEnabled,
            animationConfig: const AnimationConfig(stiffness: 350, damping: 22),
            gestureConfig: const GestureConfig(
              resizeHandleSize: 28,
              autoScrollConfig: AutoScrollConfig(),
            ),
            resizeConstraints: const ResizeConstraints(
              maxColumnSpan: 4,
              maxRowSpan: 4,
            ),
            shelfConfig: _shelfEnabled
                ? ShelfConfig(
                    position: ShelfPosition.right,
                    showTitles: true,
                    borderRadius: BorderRadius.circular(12),
                    backgroundColor: const Color(0x0D000000),
                    dropTargetColor: const Color(0x262196F3),
                    gapFromGrid: 16,
                    hoverOpenDelay: Duration.zero,
                    autoCloseDelay: const Duration(seconds: 3),
                  )
                : null,
            initialShelfItems: _shelfItems,
            ghostBuilder: _buildGhost,
            onItemsChanged: (items) {
              setState(() => _items = items);
            },
            onShelfChanged: (shelfItems) {
              setState(() => _shelfItems = shelfItems);
            },
          ),
        ),
      ),
    );
  }
}
0
likes
0
points
310
downloads

Publisher

unverified uploader

Weekly Downloads

A grid-based dashboard layout library with drag-and-drop reordering, resizing, and physics-based animations.

Repository (GitHub)
View/report issues

Topics

#dashboard #drag-and-drop #grid #layout #widget

License

unknown (license)

Dependencies

flutter

More

Packages that depend on xtal_dashboard