nova_drawer 1.0.0 copy "nova_drawer: ^1.0.0" to clipboard
nova_drawer: ^1.0.0 copied to clipboard

NovaDrawer – A modern, fully dynamic, and highly responsive app drawer component for Flutter supporting mobile, tablet, and desktop with 16+ animation types (including floating, wave, parallax, and cu [...]

example/lib/main.dart

// Copyright (c) 2024 NovaDrawer Contributors
// Licensed under the MIT License.

/// Example demo application for the NovaDrawer package.
///
/// This demo showcases all major features including:
/// - 10 header variants (classic, glassmorphism, compact, hero, etc.)
/// - 10 drawer surface styles
/// - Content widgets (search, stats, shortcuts, etc.)
/// - 10+ animation types
/// - Responsive layout (mobile, tablet, desktop)
/// - Theming (light/dark mode)
/// - Nested menu items
/// - Dynamic data loading
/// - Gesture controls
/// - Mini-drawer mode
/// - Custom backgrounds
/// - RTL support
/// - Accessibility
library;

import 'dart:async';

import 'package:flutter/material.dart';

import 'package:nova_drawer/nova_drawer.dart';

import 'screens/header_showcase_screen.dart';
import 'screens/surface_showcase_screen.dart';
import 'screens/content_showcase_screen.dart';
import 'screens/animation_showcase_screen.dart';

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

/// Root application widget.
class NovaDrawerDemoApp extends StatefulWidget {
  const NovaDrawerDemoApp({super.key});

  @override
  State<NovaDrawerDemoApp> createState() => _NovaDrawerDemoAppState();
}

class _NovaDrawerDemoAppState extends State<NovaDrawerDemoApp> {
  ThemeMode _themeMode = ThemeMode.system;
  bool _isRtl = false;
  NovaDrawerAnimationType _animationType = NovaDrawerAnimationType.slide;

  void _toggleTheme() {
    setState(() {
      _themeMode = _themeMode == ThemeMode.dark
          ? ThemeMode.light
          : ThemeMode.dark;
    });
  }

  void _toggleRtl() {
    setState(() {
      _isRtl = !_isRtl;
    });
  }

  void _setAnimationType(NovaDrawerAnimationType type) {
    setState(() {
      _animationType = type;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'NovaDrawer Demo',
      debugShowCheckedModeBanner: false,
      themeMode: _themeMode,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.indigo,
          brightness: Brightness.light,
        ),
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.indigo,
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      builder: (context, child) {
        return Directionality(
          textDirection: _isRtl ? TextDirection.rtl : TextDirection.ltr,
          child: child ?? const SizedBox.shrink(),
        );
      },
      home: DemoHomePage(
        onToggleTheme: _toggleTheme,
        onToggleRtl: _toggleRtl,
        onAnimationTypeChanged: _setAnimationType,
        currentAnimationType: _animationType,
        isRtl: _isRtl,
        isDark: _themeMode == ThemeMode.dark,
      ),
    );
  }
}

/// The main demo page with the drawer.
class DemoHomePage extends StatefulWidget {
  const DemoHomePage({
    super.key,
    required this.onToggleTheme,
    required this.onToggleRtl,
    required this.onAnimationTypeChanged,
    required this.currentAnimationType,
    required this.isRtl,
    required this.isDark,
  });

  final VoidCallback onToggleTheme;
  final VoidCallback onToggleRtl;
  final void Function(NovaDrawerAnimationType) onAnimationTypeChanged;
  final NovaDrawerAnimationType currentAnimationType;
  final bool isRtl;
  final bool isDark;

  @override
  State<DemoHomePage> createState() => _DemoHomePageState();
}

class _DemoHomePageState extends State<DemoHomePage> {
  late NovaDrawerController _drawerController;
  String _selectedPageId = 'home';
  String _selectedPageTitle = 'Home';
  bool _showGradientBg = false;
  bool _showParticleBg = false;
  bool _useDynamicLoading = false;
  NovaHeaderVariant _headerVariant = NovaHeaderVariant.classic;

  @override
  void initState() {
    super.initState();
    _drawerController = NovaDrawerController(
      initialSelectedItemId: 'home',
      initiallyOpen: false,
    );
  }

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

  // ── Sample user profile for the header ─────────────────────────────

  static const _profile = NovaHeaderUserProfile(
    name: 'Jane Developer',
    email: 'jane@novadev.io',
    role: 'Senior Engineer',
    status: NovaUserStatus.online,
    notificationCount: 5,
  );

  static const _accounts = [
    NovaHeaderUserProfile(
      name: 'Alice',
      email: 'alice@team.io',
      status: NovaUserStatus.online,
    ),
    NovaHeaderUserProfile(
      name: 'Bob',
      email: 'bob@team.io',
      status: NovaUserStatus.busy,
    ),
    NovaHeaderUserProfile(
      name: 'Carol',
      email: 'carol@team.io',
      status: NovaUserStatus.away,
    ),
  ];

  // ── Drawer Data ──────────────────────────────────────────────────────

  List<NovaDrawerSectionData> get _sections => [
    NovaDrawerSectionData(
      id: 'main',
      title: 'Main',
      items: [
        NovaDrawerItem(
          id: 'home',
          title: 'Home',
          icon: Icons.home_outlined,
          selectedIcon: Icons.home,
          route: '/home',
          badge: const NovaDrawerItemBadge(count: 3),
        ),
        NovaDrawerItem(
          id: 'dashboard',
          title: 'Dashboard',
          icon: Icons.dashboard_outlined,
          selectedIcon: Icons.dashboard,
          route: '/dashboard',
        ),
        NovaDrawerItem(
          id: 'analytics',
          title: 'Analytics',
          icon: Icons.analytics_outlined,
          selectedIcon: Icons.analytics,
          route: '/analytics',
          badge: const NovaDrawerItemBadge(label: 'NEW'),
        ),
      ],
    ),
    NovaDrawerSectionData(
      id: 'content',
      title: 'Content',
      items: [
        NovaDrawerItem(
          id: 'messages',
          title: 'Messages',
          icon: Icons.message_outlined,
          selectedIcon: Icons.message,
          route: '/messages',
          badge: const NovaDrawerItemBadge(count: 42),
        ),
        NovaDrawerItem(
          id: 'files',
          title: 'Files & Media',
          icon: Icons.folder_outlined,
          selectedIcon: Icons.folder,
          children: [
            NovaDrawerItem(
              id: 'documents',
              title: 'Documents',
              icon: Icons.description_outlined,
              route: '/files/documents',
            ),
            NovaDrawerItem(
              id: 'images',
              title: 'Images',
              icon: Icons.image_outlined,
              route: '/files/images',
            ),
            NovaDrawerItem(
              id: 'videos',
              title: 'Videos',
              icon: Icons.video_library_outlined,
              route: '/files/videos',
              children: [
                NovaDrawerItem(
                  id: 'recent_videos',
                  title: 'Recent',
                  icon: Icons.access_time,
                  route: '/files/videos/recent',
                ),
                NovaDrawerItem(
                  id: 'favorites_videos',
                  title: 'Favorites',
                  icon: Icons.star_outline,
                  route: '/files/videos/favorites',
                ),
              ],
            ),
          ],
        ),
        NovaDrawerItem(
          id: 'calendar',
          title: 'Calendar',
          icon: Icons.calendar_today_outlined,
          selectedIcon: Icons.calendar_today,
          route: '/calendar',
        ),
      ],
    ),
    NovaDrawerSectionData(
      id: 'showcase',
      title: 'Showcase',
      icon: Icons.auto_awesome,
      items: [
        NovaDrawerItem(
          id: 'header_showcase',
          title: 'Header Variants',
          icon: Icons.account_circle_outlined,
          selectedIcon: Icons.account_circle,
          route: '/showcase/headers',
        ),
        NovaDrawerItem(
          id: 'surface_showcase',
          title: 'Surface Styles',
          icon: Icons.layers_outlined,
          selectedIcon: Icons.layers,
          route: '/showcase/surfaces',
        ),
        NovaDrawerItem(
          id: 'content_showcase',
          title: 'Content Widgets',
          icon: Icons.widgets_outlined,
          selectedIcon: Icons.widgets,
          route: '/showcase/content',
        ),
        NovaDrawerItem(
          id: 'animation_showcase',
          title: 'Animations',
          icon: Icons.animation_outlined,
          selectedIcon: Icons.animation,
          route: '/showcase/animations',
        ),
      ],
    ),
    NovaDrawerSectionData(
      id: 'settings_section',
      title: 'Settings & Tools',
      items: [
        NovaDrawerItem(
          id: 'settings',
          title: 'Settings',
          icon: Icons.settings_outlined,
          selectedIcon: Icons.settings,
          route: '/settings',
        ),
        NovaDrawerItem(
          id: 'help',
          title: 'Help & Support',
          icon: Icons.help_outline,
          selectedIcon: Icons.help,
          route: '/help',
        ),
        NovaDrawerItem(
          id: 'about',
          title: 'About',
          icon: Icons.info_outline,
          selectedIcon: Icons.info,
          route: '/about',
          subtitle: 'v2.0.0',
        ),
      ],
    ),
  ];

  /// Simulates loading drawer items from an API.
  Future<List<NovaDrawerItem>> _loadDynamicItems() async {
    await Future<void>.delayed(const Duration(seconds: 2));
    return [
      NovaDrawerItem(
        id: 'dynamic_1',
        title: 'Dynamic Item 1',
        icon: Icons.cloud_outlined,
        route: '/dynamic/1',
      ),
      NovaDrawerItem(
        id: 'dynamic_2',
        title: 'Dynamic Item 2',
        icon: Icons.cloud_download_outlined,
        route: '/dynamic/2',
        badge: const NovaDrawerItemBadge(label: 'API'),
      ),
      NovaDrawerItem(
        id: 'dynamic_3',
        title: 'Dynamic Item 3',
        icon: Icons.cloud_upload_outlined,
        route: '/dynamic/3',
      ),
    ];
  }

  @override
  Widget build(BuildContext context) {
    final drawerTheme = widget.isDark
        ? NovaDrawerTheme.dark()
        : NovaDrawerTheme.light();

    final config = NovaDrawerConfig(
      animationType: widget.currentAnimationType,
      displayMode: NovaDrawerDisplayMode.auto,
      isPinnable: true,
      showMiniOnCollapse: true,
      gestureConfig: const NovaDrawerGestureConfig(
        enableSwipeToOpen: true,
        enableSwipeToClose: true,
      ),
      animationConfig: const NovaDrawerAnimationConfig(
        duration: Duration(milliseconds: 350),
        curve: Curves.easeOutCubic,
        enableStaggeredAnimations: true,
      ),
    );

    // Build the new header using the header system.
    final headerConfig = NovaHeaderConfig(
      variant: _headerVariant,
      profile: _profile,
      accounts: _accounts,
      showCloseButton: true,
      showPinButton: true,
      showEditProfileButton: true,
      showStatusIndicator: true,
      showNotificationBadge: true,
      enableCollapseExpand: true,
      gradientColors: const [
        Color(0xFF6366F1),
        Color(0xFF8B5CF6),
        Color(0xFFEC4899),
      ],
      actions: [
        NovaHeaderAction(
          id: 'notifications',
          icon: Icons.notifications_outlined,
          tooltip: 'Notifications',
          badge: 5,
          onTap: () {},
        ),
        NovaHeaderAction(
          id: 'settings',
          icon: Icons.settings_outlined,
          tooltip: 'Settings',
          onTap: () {},
        ),
      ],
      onEditProfile: () {},
      onSwitchAccount: () {},
    );

    // Build the drawer widget
    final drawer = NovaAppDrawer(
      controller: _drawerController,
      sections: _sections,
      header: NovaDrawerHeader(config: headerConfig),
      footer: _buildDrawerFooter(),
      onItemTap: (item) {
        setState(() {
          _selectedPageId = item.id;
          _selectedPageTitle = item.title;
        });
      },
      theme: drawerTheme,
      config: config,
      enableGradientBackground: _showGradientBg,
      enableParticleBackground: _showParticleBg,
      particleCount: 15,
    );

    return NovaDrawerScaffold(
      controller: _drawerController,
      drawer: drawer,
      theme: drawerTheme,
      config: config,
      onItemTap: (item) {
        setState(() {
          _selectedPageId = item.id;
          _selectedPageTitle = item.title;
        });
      },
      miniDrawerHeader: Icon(
        Icons.widgets_outlined,
        color: Theme.of(context).colorScheme.primary,
      ),
      appBar: AppBar(
        title: Text(_selectedPageTitle),
        leading: IconButton(
          icon: const Icon(Icons.menu),
          onPressed: () => _drawerController.toggle(),
          tooltip: 'Toggle drawer',
        ),
        actions: [
          // Theme toggle
          IconButton(
            icon: Icon(
              widget.isDark
                  ? Icons.light_mode_outlined
                  : Icons.dark_mode_outlined,
            ),
            onPressed: widget.onToggleTheme,
            tooltip: 'Toggle theme',
          ),
          // RTL toggle
          IconButton(
            icon: const Icon(Icons.swap_horiz),
            onPressed: widget.onToggleRtl,
            tooltip: widget.isRtl ? 'Switch to LTR' : 'Switch to RTL',
          ),
        ],
      ),
      body: _buildBody(context),
    );
  }

  Widget _buildDrawerFooter() {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        const Divider(height: 1),
        NovaDrawerAppStatusWidget(
          status: const NovaDrawerAppStatus(
            isOnline: true,
            version: '2.0.0',
            buildNumber: '42',
            statusMessage: 'All systems operational',
          ),
        ),
      ],
    );
  }

  Widget _buildBody(BuildContext context) {
    // Route to showcase screens based on selected drawer item.
    switch (_selectedPageId) {
      case 'header_showcase':
        return const HeaderShowcaseScreen();
      case 'surface_showcase':
        return const SurfaceShowcaseScreen();
      case 'content_showcase':
        return const ContentShowcaseScreen();
      case 'animation_showcase':
        return const AnimationShowcaseScreen();
      default:
        return _buildMainBody(context);
    }
  }

  Widget _buildMainBody(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(24.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Current page indicator
          _buildPageIndicator(),
          const SizedBox(height: 24),

          // Header variant selector
          _buildHeaderVariantSelector(),
          const SizedBox(height: 24),

          // Animation type selector
          _buildAnimationSelector(),
          const SizedBox(height: 24),

          // Feature toggles
          _buildFeatureToggles(),
          const SizedBox(height: 24),

          // Dynamic loading demo
          _buildDynamicLoadingDemo(),
          const SizedBox(height: 24),

          // Responsive info card
          _buildResponsiveInfoCard(context),
          const SizedBox(height: 24),

          // Feature showcase grid
          _buildFeatureShowcase(),
        ],
      ),
    );
  }

  Widget _buildPageIndicator() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Row(
          children: [
            Icon(
              Icons.navigation_outlined,
              color: Theme.of(context).colorScheme.primary,
            ),
            const SizedBox(width: 12),
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Current Page',
                  style: Theme.of(context).textTheme.labelMedium,
                ),
                Text(
                  _selectedPageTitle,
                  style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildHeaderVariantSelector() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Header Variant',
              style: Theme.of(
                context,
              ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 4),
            Text(
              'Select a header style for the drawer',
              style: Theme.of(context).textTheme.bodySmall,
            ),
            const SizedBox(height: 16),
            Wrap(
              spacing: 8.0,
              runSpacing: 8.0,
              children: NovaHeaderVariant.values.map((variant) {
                final isSelected = variant == _headerVariant;
                return ChoiceChip(
                  label: Text(_headerVariantName(variant)),
                  selected: isSelected,
                  onSelected: (_) {
                    setState(() => _headerVariant = variant);
                  },
                );
              }).toList(),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildAnimationSelector() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Animation Type',
              style: Theme.of(
                context,
              ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 4),
            Text(
              'Select an animation type for the drawer transition',
              style: Theme.of(context).textTheme.bodySmall,
            ),
            const SizedBox(height: 16),
            Wrap(
              spacing: 8.0,
              runSpacing: 8.0,
              children: NovaDrawerAnimationType.values.map((type) {
                final isSelected = type == widget.currentAnimationType;
                return ChoiceChip(
                  label: Text(_animationTypeName(type)),
                  selected: isSelected,
                  onSelected: (_) => widget.onAnimationTypeChanged(type),
                );
              }).toList(),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildFeatureToggles() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Background Effects',
              style: Theme.of(
                context,
              ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 12),
            SwitchListTile(
              title: const Text('Gradient Background'),
              subtitle: const Text('Animated color gradient on drawer'),
              value: _showGradientBg,
              onChanged: (value) => setState(() => _showGradientBg = value),
            ),
            SwitchListTile(
              title: const Text('Particle Effects'),
              subtitle: const Text('Floating particles on drawer'),
              value: _showParticleBg,
              onChanged: (value) => setState(() => _showParticleBg = value),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildDynamicLoadingDemo() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Dynamic Loading',
              style: Theme.of(
                context,
              ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            Text(
              'Simulate loading drawer items from an API',
              style: Theme.of(context).textTheme.bodySmall,
            ),
            const SizedBox(height: 12),
            Row(
              children: [
                FilledButton.icon(
                  onPressed: () {
                    _drawerController.loadItems(_loadDynamicItems);
                    setState(() => _useDynamicLoading = true);
                  },
                  icon: const Icon(Icons.cloud_download, size: 18),
                  label: const Text('Load Dynamic Items'),
                ),
                const SizedBox(width: 12),
                if (_useDynamicLoading)
                  OutlinedButton(
                    onPressed: () {
                      _drawerController.setSections(_sections);
                      setState(() => _useDynamicLoading = false);
                    },
                    child: const Text('Reset'),
                  ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildResponsiveInfoCard(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    final deviceType = NovaResponsiveUtils.getDeviceType(width);

    return Card(
      color: Theme.of(context).colorScheme.primaryContainer,
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Row(
          children: [
            Icon(
              deviceType == NovaDeviceType.mobile
                  ? Icons.phone_android
                  : deviceType == NovaDeviceType.tablet
                  ? Icons.tablet_mac
                  : Icons.desktop_mac,
              size: 48,
              color: Theme.of(context).colorScheme.onPrimaryContainer,
            ),
            const SizedBox(width: 16),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Device: ${deviceType.name.toUpperCase()}',
                    style: Theme.of(context).textTheme.titleMedium?.copyWith(
                      fontWeight: FontWeight.bold,
                      color: Theme.of(context).colorScheme.onPrimaryContainer,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    'Width: ${width.toStringAsFixed(0)}px',
                    style: TextStyle(
                      color: Theme.of(
                        context,
                      ).colorScheme.onPrimaryContainer.withAlpha(179),
                    ),
                  ),
                  Text(
                    'Mode: ${NovaResponsiveUtils.resolveDisplayMode(NovaDrawerDisplayMode.auto, deviceType).name}',
                    style: TextStyle(
                      color: Theme.of(
                        context,
                      ).colorScheme.onPrimaryContainer.withAlpha(179),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildFeatureShowcase() {
    final features = [
      _FeatureItem(
        Icons.account_circle,
        'Header System',
        '10 header variants: classic, glassmorphism, hero, collapsible, and more',
      ),
      _FeatureItem(
        Icons.layers,
        'Surface Styles',
        '10 surface styles: glass, neumorphic, gradient, mesh, and more',
      ),
      _FeatureItem(
        Icons.widgets,
        'Content Blocks',
        'Search bar, stats card, shortcuts grid, recent items, filter chips',
      ),
      _FeatureItem(
        Icons.animation,
        '10+ Animations',
        'Slide, fade, scale, rotate, morph, elastic, spring, shimmer, blur, gradient',
      ),
      _FeatureItem(
        Icons.devices,
        'Responsive',
        'Adapts to mobile, tablet, and desktop layouts',
      ),
      _FeatureItem(
        Icons.format_textdirection_r_to_l,
        'RTL Support',
        'Full right-to-left language support',
      ),
      _FeatureItem(
        Icons.account_tree,
        'Nested Menus',
        'Multi-level expandable menu items',
      ),
      _FeatureItem(
        Icons.cloud_download,
        'Dynamic Loading',
        'Load menu items from API with loading states',
      ),
      _FeatureItem(
        Icons.accessibility,
        'Accessibility',
        'Screen reader support, focus management, scalable text',
      ),
      _FeatureItem(
        Icons.palette,
        'Theming',
        'Full light/dark mode and custom theme support',
      ),
      _FeatureItem(
        Icons.view_sidebar,
        'Mini Drawer',
        'Collapsed icon-only mode for tablet/desktop',
      ),
      _FeatureItem(
        Icons.build,
        'Builder APIs',
        'Slot-based builder callbacks for deep customization',
      ),
    ];

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'Features',
          style: Theme.of(
            context,
          ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 16),
        LayoutBuilder(
          builder: (context, constraints) {
            final crossAxisCount = constraints.maxWidth > 900
                ? 4
                : constraints.maxWidth > 600
                ? 3
                : 2;
            return GridView.builder(
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(),
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: crossAxisCount,
                crossAxisSpacing: 12,
                mainAxisSpacing: 12,
                childAspectRatio: 1.3,
              ),
              itemCount: features.length,
              itemBuilder: (context, index) {
                final feature = features[index];
                return Card(
                  child: Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Icon(
                          feature.icon,
                          color: Theme.of(context).colorScheme.primary,
                          size: 28,
                        ),
                        const SizedBox(height: 8),
                        Text(
                          feature.title,
                          style: Theme.of(context).textTheme.titleSmall
                              ?.copyWith(fontWeight: FontWeight.bold),
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                        ),
                        const SizedBox(height: 4),
                        Expanded(
                          child: Text(
                            feature.description,
                            style: Theme.of(context).textTheme.bodySmall,
                            maxLines: 3,
                            overflow: TextOverflow.ellipsis,
                          ),
                        ),
                      ],
                    ),
                  ),
                );
              },
            );
          },
        ),
      ],
    );
  }

  String _headerVariantName(NovaHeaderVariant variant) {
    switch (variant) {
      case NovaHeaderVariant.classic:
        return 'Classic';
      case NovaHeaderVariant.glassmorphism:
        return 'Glass';
      case NovaHeaderVariant.compact:
        return 'Compact';
      case NovaHeaderVariant.hero:
        return 'Hero';
      case NovaHeaderVariant.expanded:
        return 'Expanded';
      case NovaHeaderVariant.animatedGradient:
        return 'Gradient';
      case NovaHeaderVariant.avatarStack:
        return 'Avatars';
      case NovaHeaderVariant.multiAction:
        return 'Actions';
      case NovaHeaderVariant.statusAware:
        return 'Status';
      case NovaHeaderVariant.collapsible:
        return 'Collapsible';
    }
  }

  String _animationTypeName(NovaDrawerAnimationType type) {
    switch (type) {
      case NovaDrawerAnimationType.slide:
        return 'Slide';
      case NovaDrawerAnimationType.fade:
        return 'Fade';
      case NovaDrawerAnimationType.scale:
        return 'Scale';
      case NovaDrawerAnimationType.rotate:
        return 'Rotate';
      case NovaDrawerAnimationType.morph:
        return 'Morph';
      case NovaDrawerAnimationType.elastic:
        return 'Elastic';
      case NovaDrawerAnimationType.spring:
        return 'Spring';
      case NovaDrawerAnimationType.shimmer:
        return 'Shimmer';
      case NovaDrawerAnimationType.blur:
        return 'Blur';
      case NovaDrawerAnimationType.gradient:
        return 'Gradient';
      default:
        return 'Blue';
    }
  }
}

/// Simple data class for feature showcase.
class _FeatureItem {
  const _FeatureItem(this.icon, this.title, this.description);

  final IconData icon;
  final String title;
  final String description;
}
0
likes
150
points
96
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

NovaDrawer – A modern, fully dynamic, and highly responsive app drawer component for Flutter supporting mobile, tablet, and desktop with 16+ animation types (including floating, wave, parallax, and curtain), nested menus, RTL, theming, accessibility, mini-drawer mode, gesture controls, search_plus-powered search, and background effects.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, search_plus, vector_math

More

Packages that depend on nova_drawer