bee_date_picker 0.1.1
bee_date_picker: ^0.1.1 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.
// 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,
),
),
);
}
}