bee_date_picker 0.1.0 copy "bee_date_picker: ^0.1.0" to clipboard
bee_date_picker: ^0.1.0 copied to clipboard

A highly customizable, performant, and accessible date picker widget for Flutter. Supports single date selection, date range selection, custom themes, and localization out of the box.

example/lib/main.dart

// ignore_for_file: public_member_api_docs

import 'package:bee_date_picker/bee_date_picker.dart';
import 'package:flutter/material.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initializeDateFormatting('en', null);
  runApp(const BeePickerShowcaseApp());
}

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

  @override
  State<BeePickerShowcaseApp> createState() => _BeePickerShowcaseAppState();
}

class _BeePickerShowcaseAppState extends State<BeePickerShowcaseApp> {
  bool _isDark = false;

  @override
  Widget build(BuildContext context) {
    const beeYellow = Color(0xFFFACC15);

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Bee Date Picker Showcase',
      themeMode: _isDark ? ThemeMode.dark : ThemeMode.light,
      theme: ThemeData(
        fontFamily: 'Inter',
        useMaterial3: true,
        brightness: Brightness.light,
        colorSchemeSeed: beeYellow,
        scaffoldBackgroundColor: const Color(0xFFFDFDFD),
      ),
      darkTheme: ThemeData(
        fontFamily: 'Inter',
        useMaterial3: true,
        brightness: Brightness.dark,
        colorSchemeSeed: beeYellow,
        scaffoldBackgroundColor: const Color(0xFF121212),
      ),
      home: ShowcasePage(
        isDark: _isDark,
        onThemeToggle: () => setState(() => _isDark = !_isDark),
      ),
    );
  }
}

class ShowcasePage extends StatefulWidget {
  final bool isDark;
  final VoidCallback onThemeToggle;

  const ShowcasePage({
    required this.isDark,
    required this.onThemeToggle,
    super.key,
  });

  @override
  State<ShowcasePage> createState() => _ShowcasePageState();
}

class _ShowcasePageState extends State<ShowcasePage> {
  late final BeeDateController _singleController;
  late final BeeDateRangeController _rangeController;
  String _selectionSummary = 'No date selected yet';
  String _activeTab = 'Dialogs';
  bool _minDateRestricted = false;
  bool _maxDateRestricted = false;
  bool _mondayFirst = false;
  bool _showOutsideDays = true;

  @override
  void initState() {
    super.initState();
    _singleController = BeeDateController();
    _rangeController = BeeDateRangeController();
  }

  @override
  void dispose() {
    _singleController.dispose();
    _rangeController.dispose();
    super.dispose();
  }

  void _updateStatus(String text) {
    setState(() {
      _selectionSummary = text;
    });
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final isDark = theme.brightness == Brightness.dark;

    final beePickerTheme = BeePickerTheme(
      backgroundColor: isDark ? const Color(0xFF1E1E1E) : Colors.white,
      selectedDayColor: const Color(0xFFFACC15),
      selectedDayTextStyle: const TextStyle(
        color: Colors.black,
        fontWeight: FontWeight.bold,
      ),
      rangeColor: const Color(0xFFFACC15).withValues(alpha: 0.15),
      todayBorderColor: const Color(0xFFEAB308),
      borderRadius: 16,
      summaryBackgroundColor: isDark ? Colors.black : const Color(0xFFFACC15),
      dialogHeaderTextStyle: TextStyle(
        color: isDark ? Colors.white : Colors.black87,
        fontWeight: FontWeight.w800,
        fontSize: 18,
      ),
      headerIconColor: isDark ? const Color(0xFFFACC15) : Colors.black87,
      summaryDateTextStyle: TextStyle(
        color: isDark ? Colors.white : Colors.black,
        fontWeight: FontWeight.w800,
        fontSize: 20,
      ),
      summaryLabelTextStyle: TextStyle(
        color: isDark ? Colors.white54 : Colors.black54,
        fontSize: 10,
        fontWeight: FontWeight.w800,
        letterSpacing: 1.2,
      ),
      actionButtonColor: isDark ? const Color(0xFFFACC15) : Colors.black,
      actionButtonTextColor: isDark ? Colors.black : Colors.white,
    );

    return BeePickerThemeScope(
      theme: beePickerTheme,
      child: Scaffold(
        body: CustomScrollView(
          slivers: [
            _buildAppBar(context),
            SliverToBoxAdapter(
              child: Padding(
                padding: const EdgeInsets.all(20),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    _buildStatusCard(theme),
                    const SizedBox(height: 32),
                    _buildTabSwitcher(theme),
                    const SizedBox(height: 24),
                    _buildActiveContent(theme, beePickerTheme),
                    const SizedBox(height: 60),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildAppBar(BuildContext context) {
    return SliverAppBar(
      expandedHeight: 120,
      floating: false,
      pinned: true,
      backgroundColor: widget.isDark ? Colors.black : Colors.white,
      elevation: 0,
      flexibleSpace: FlexibleSpaceBar(
        titlePadding: const EdgeInsets.only(left: 20, bottom: 16),
        title: Row(
          children: [
            const Text(
              '🐝 Bee Picker',
              style: TextStyle(
                fontWeight: FontWeight.w900,
                letterSpacing: -0.5,
              ),
            ),
            const Spacer(),
            IconButton(
              key: const ValueKey('theme_toggle'),
              onPressed: widget.onThemeToggle,
              icon: Icon(
                widget.isDark
                    ? Icons.light_mode_rounded
                    : Icons.dark_mode_rounded,
                size: 20,
              ),
            ),
            const SizedBox(width: 8),
          ],
        ),
      ),
    );
  }

  Widget _buildStatusCard(ThemeData theme) {
    final isDark = theme.brightness == Brightness.dark;
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: isDark
              ? [const Color(0xFF1E1E1E), const Color(0xFF121212)]
              : [const Color(0xFFFFD60A), const Color(0xFFFACC15)],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(24),
        boxShadow: [
          BoxShadow(
            color: (isDark ? Colors.black : const Color(0xFFFACC15)).withValues(
              alpha: 0.25,
            ),
            blurRadius: 30,
            offset: const Offset(0, 15),
          ),
        ],
      ),
      child: Stack(
        children: [
          Positioned(
            right: -20,
            top: -20,
            child: Icon(
              Icons.hive_rounded,
              size: 150,
              color: (isDark
                  ? Colors.white.withValues(alpha: 0.1)
                  : Colors.black.withValues(alpha: 0.1)),
            ),
          ),
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Container(
                padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
                decoration: BoxDecoration(
                  color: isDark ? Colors.black26 : Colors.white24,
                  borderRadius: BorderRadius.circular(4),
                ),
                child: Text(
                  'LATEST SELECTION',
                  style: TextStyle(
                    color: isDark ? Colors.white70 : Colors.black54,
                    fontSize: 9,
                    fontWeight: FontWeight.w900,
                    letterSpacing: 2.0,
                  ),
                ),
              ),
              const SizedBox(height: 12),
              Text(
                _selectionSummary,
                style: TextStyle(
                  color: isDark ? Colors.white : Colors.black,
                  fontSize: 24,
                  fontWeight: FontWeight.w900,
                  letterSpacing: -0.5,
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildTabSwitcher(ThemeData theme) {
    final isDark = theme.brightness == Brightness.dark;
    return Container(
      decoration: BoxDecoration(
        color: isDark
            ? Colors.white.withValues(alpha: 0.05)
            : Colors.black.withValues(alpha: 0.03),
        borderRadius: BorderRadius.circular(16),
      ),
      padding: const EdgeInsets.all(6),
      child: Row(
        children: ['Dialogs', 'Inline', 'Settings'].map((tab) {
          final isSelected = _activeTab == tab;
          return Expanded(
            child: GestureDetector(
              onTap: () => setState(() => _activeTab = tab),
              child: AnimatedContainer(
                duration: const Duration(milliseconds: 300),
                curve: Curves.easeOutCubic,
                padding: const EdgeInsets.symmetric(vertical: 10),
                decoration: BoxDecoration(
                  color: isSelected
                      ? (isDark ? const Color(0xFFFACC15) : Colors.black)
                      : Colors.transparent,
                  borderRadius: BorderRadius.circular(12),
                  boxShadow: isSelected
                      ? [
                          BoxShadow(
                            color: Colors.black.withValues(alpha: 0.1),
                            blurRadius: 10,
                            offset: const Offset(0, 4),
                          ),
                        ]
                      : [],
                ),
                child: Center(
                  child: Text(
                    tab,
                    style: TextStyle(
                      fontWeight: FontWeight.w900,
                      fontSize: 13,
                      color: isSelected
                          ? (isDark ? Colors.black : Colors.white)
                          : theme.textTheme.bodyMedium?.color?.withValues(
                              alpha: 0.5,
                            ),
                    ),
                  ),
                ),
              ),
            ),
          );
        }).toList(),
      ),
    );
  }

  Widget _buildActiveContent(ThemeData theme, BeePickerTheme pickerTheme) {
    switch (_activeTab) {
      case 'Dialogs':
        return _buildDialogGrid(theme, pickerTheme);
      case 'Inline':
        return _buildInlineView(theme);
      case 'Settings':
        return _buildSettingsView(theme);
      default:
        return const SizedBox();
    }
  }

  Widget _buildDialogGrid(ThemeData theme, BeePickerTheme pickerTheme) {
    return GridView.count(
      shrinkWrap: true,
      physics: const NeverScrollableScrollPhysics(),
      crossAxisCount: 2,
      mainAxisSpacing: 16,
      crossAxisSpacing: 16,
      childAspectRatio: 1.1,
      children: [
        _buildDialogCard(
          theme,
          'Single Date',
          'Standard popup explorer',
          Icons.calendar_today_rounded,
          () async {
            final date = await showBeeDatePicker(
              context: context,
              title: 'Schedule Meeting',
              theme: pickerTheme,
            );
            if (date != null) _updateStatus(DateFormat.yMMMMd().format(date));
          },
        ),
        _buildDialogCard(
          theme,
          'Range Date',
          'Premium booking flow',
          Icons.date_range_rounded,
          () async {
            final range = await showBeeDateRangePicker(
              context: context,
              title: 'Trip Duration',
              theme: pickerTheme,
            );
            if (range != null) {
              _updateStatus(
                '${DateFormat.yMMMMd().format(range.start)} - ${range.end != null ? DateFormat.yMMMMd().format(range.end!) : 'Select end'}',
              );
            }
          },
        ),
        _buildDialogCard(
          theme,
          'Month Only',
          'Targeted month picker',
          Icons.calendar_month_rounded,
          () async {
            final date = await showBeeMonthPicker(
              context: context,
              title: 'Subscription Expiry',
              theme: pickerTheme,
            );
            if (date != null) _updateStatus(DateFormat.yMMMM().format(date));
          },
        ),
        _buildDialogCard(
          theme,
          'Year Only',
          'Quick year selector',
          Icons.auto_awesome_motion_rounded,
          () async {
            final year = await showBeeYearPicker(
              context: context,
              title: 'Model Year',
              theme: pickerTheme,
            );
            if (year != null) _updateStatus('Year $year');
          },
        ),
      ],
    );
  }

  Widget _buildDialogCard(
    ThemeData theme,
    String title,
    String sub,
    IconData icon,
    VoidCallback tap,
  ) {
    return AnimatedContainer(
      duration: const Duration(milliseconds: 300),
      decoration: BoxDecoration(
        color: theme.cardColor,
        borderRadius: BorderRadius.circular(24),
        border: Border.all(color: theme.dividerColor.withValues(alpha: 0.05)),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withValues(alpha: 0.02),
            blurRadius: 10,
            offset: const Offset(0, 5),
          ),
        ],
      ),
      clipBehavior: Clip.antiAlias,
      child: Material(
        color: Colors.transparent,
        child: InkWell(
          onTap: tap,
          hoverColor: theme.colorScheme.primary.withValues(alpha: 0.05),
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: theme.colorScheme.primary.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Icon(icon, color: theme.colorScheme.primary, size: 24),
                ),
                const Spacer(),
                Text(
                  title,
                  style: const TextStyle(
                    fontWeight: FontWeight.w900,
                    fontSize: 15,
                    height: 1.2,
                  ),
                ),
                const SizedBox(height: 4),
                Text(
                  sub,
                  style: TextStyle(
                    fontSize: 11,
                    fontWeight: FontWeight.w500,
                    color: theme.textTheme.bodySmall?.color?.withValues(
                      alpha: 0.4,
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildInlineView(ThemeData theme) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Container(
          padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
          decoration: BoxDecoration(
            color: theme.colorScheme.primary.withValues(alpha: 0.1),
            borderRadius: BorderRadius.circular(6),
          ),
          child: const Text(
            'SINGLE DATE INLINE',
            style: TextStyle(
              fontWeight: FontWeight.w900,
              fontSize: 10,
              letterSpacing: 2.0,
            ),
          ),
        ),
        const SizedBox(height: 20),
        BeeDatePicker(
          controller: _singleController,
          onDateSelected: (date) =>
              _updateStatus('Inline Single: ${DateFormat.yMd().format(date)}'),
        ),
        const SizedBox(height: 32),
        const SizedBox(height: 40),
        Container(
          padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
          decoration: BoxDecoration(
            color: theme.colorScheme.primary.withValues(alpha: 0.1),
            borderRadius: BorderRadius.circular(6),
          ),
          child: const Text(
            'RANGE PICKER INLINE',
            style: TextStyle(
              fontWeight: FontWeight.w900,
              fontSize: 10,
              letterSpacing: 2.0,
            ),
          ),
        ),
        const SizedBox(height: 20),
        BeeDateRangePicker(
          controller: _rangeController,
          onRangeSelected: (s, e) => _updateStatus(
            'Inline Range: ${DateFormat.yMd().format(s)} - ${e != null ? DateFormat.yMd().format(e) : '...'}',
          ),
        ),
      ],
    );
  }

  Widget _buildSettingsView(ThemeData theme) {
    return Column(
      children: [
        _buildSettingsTile(
          theme,
          'Min Date Restricted',
          'Bound selection to 2026+',
          _minDateRestricted,
          (v) => setState(() => _minDateRestricted = v),
        ),
        _buildSettingsTile(
          theme,
          'Max Date Restricted',
          'Disable future selections',
          _maxDateRestricted,
          (v) => setState(() => _maxDateRestricted = v),
        ),
        _buildSettingsTile(
          theme,
          'First Day: Monday',
          'Shift calendar perspective',
          _mondayFirst,
          (v) => setState(() => _mondayFirst = v),
        ),
        _buildSettingsTile(
          theme,
          'Show Outside Days',
          'Render adjacent month dates',
          _showOutsideDays,
          (v) => setState(() => _showOutsideDays = v),
        ),
      ],
    );
  }

  Widget _buildSettingsTile(
    ThemeData theme,
    String title,
    String sub,
    bool val,
    ValueChanged<bool> onChanged,
  ) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: ListTile(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        tileColor: theme.cardColor,
        title: Text(
          title,
          style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
        ),
        subtitle: Text(sub, style: const TextStyle(fontSize: 12)),
        trailing: Switch(
          value: val,
          onChanged: onChanged,
          activeTrackColor: const Color(0xFFFACC15),
          activeThumbColor: Colors.black,
        ),
      ),
    );
  }
}
1
likes
150
points
110
downloads

Documentation

Documentation
API reference

Publisher

verified publisherorionbproject.web.id

Weekly Downloads

A highly customizable, performant, and accessible date picker widget for Flutter. Supports single date selection, date range selection, custom themes, and localization out of the box.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#date-picker #calendar #widget #date-range #ui

License

BSD-3-Clause (license)

Dependencies

flutter, intl

More

Packages that depend on bee_date_picker