horizontal_weekly_calendar 2.0.0 copy "horizontal_weekly_calendar: ^2.0.0" to clipboard
horizontal_weekly_calendar: ^2.0.0 copied to clipboard

A beautiful, reliable, and fully customizable horizontal calendar UI kit for Flutter, with events, agendas, timelines, and foldable month views.

example/lib/main.dart

// ignore_for_file: deprecated_member_use

import 'package:flutter/material.dart';
import 'package:horizontal_weekly_calendar/horizontal_weekly_calendar.dart';
import 'package:horizontal_weekly_calendar/weekly_calendar.dart' as legacy;

import 'calendar_playground.dart';
import 'home_widget_studio.dart';
import 'real_world/index.dart';

/// Route the app opens on, so a screenshot run can land on one screen.
///
/// Pass `--dart-define=CALENDAR_ROUTE=/real-world/journal` to open an example
/// directly instead of navigating to it.
const String _routeOverride = String.fromEnvironment('CALENDAR_ROUTE');

void main() => runApp(const CalendarGalleryApp());

enum _GalleryMotion {
  classic,
  none,
  subtle,
  fluid,
  spring,
  playful,
  snappy,
  gentle,
  cinematic,
  premium,
}

enum _GalleryCategory {
  all,
  horizontal,
  selection,
  pickers,
  native,
  motion,
  events,
  planning,
  data,
  custom,
  legacy,
}

CalendarMotion? _motionFor(_GalleryMotion motion) {
  return switch (motion) {
    _GalleryMotion.classic => null,
    _GalleryMotion.none => CalendarMotion.none(),
    _GalleryMotion.subtle => CalendarMotion.subtle(),
    _GalleryMotion.fluid => CalendarMotion.fluid(),
    _GalleryMotion.spring => CalendarMotion.spring(),
    _GalleryMotion.playful => CalendarMotion.playful(),
    _GalleryMotion.snappy => CalendarMotion.snappy(),
    _GalleryMotion.gentle => CalendarMotion.gentle(),
    _GalleryMotion.cinematic => CalendarMotion.cinematic(),
    _GalleryMotion.premium => CalendarMotion.premium(),
  };
}

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

  @override
  State<CalendarGalleryApp> createState() => _CalendarGalleryAppState();
}

class _CalendarGalleryAppState extends State<CalendarGalleryApp> {
  ThemeMode _themeMode = ThemeMode.light;
  CalendarDensity _density = CalendarDensity.comfortable;
  CalendarStyle _style = CalendarStyle.adaptive;
  _GalleryMotion _motion = _GalleryMotion.fluid;
  double _textScale = 1;
  bool _rtl = false;
  bool _highContrast = false;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Horizontal Calendar 2.0',
      themeMode: _themeMode,
      theme: _theme(Brightness.light),
      darkTheme: _theme(Brightness.dark),
      builder: (context, child) {
        final media = MediaQuery.of(context);
        return Directionality(
          textDirection: _rtl ? TextDirection.rtl : TextDirection.ltr,
          child: MediaQuery(
            data: media.copyWith(
              textScaler: TextScaler.linear(_textScale),
              highContrast: _highContrast,
            ),
            child: child!,
          ),
        );
      },
      routes: {
        ...realWorldRoutes,
        '/real-world': (_) => const RealWorldIndexPage(),
        '/playground': (_) => const CalendarPlaygroundPage(),
        '/home-widget-studio': (_) => const HomeWidgetStudioPage(),
        '/showcase': (_) => const RecordingShowcase(),
        '/capture/styles': (_) => const CaptureShowcase(kind: 'styles'),
        '/capture/motion': (_) => const CaptureShowcase(kind: 'motion'),
        '/capture/foldable': (_) => const CaptureShowcase(kind: 'foldable'),
        '/capture/native': (_) => const CaptureShowcase(kind: 'native'),
        '/capture/planning': (_) => const CaptureShowcase(kind: 'planning'),
        '/capture/data': (_) => const CaptureShowcase(kind: 'data'),
        '/capture/selection': (_) => const CaptureShowcase(kind: 'selection'),
        '/capture/celestial': (_) => const CaptureShowcase(kind: 'celestial'),
        '/capture/widgets': (_) => const CaptureShowcase(kind: 'widgets'),
        '/capture/legacy': (_) => const CaptureShowcase(kind: 'legacy'),
        '/capture/carousel': (_) => const CaptureShowcase(kind: 'carousel'),
        '/capture/horizon': (_) => const CaptureShowcase(kind: 'horizon'),
        '/capture/heatmaps': (_) => const CaptureShowcase(kind: 'heatmaps'),
        '/capture/responsive': (_) => const CaptureShowcase(kind: 'responsive'),
        '/capture/home-widgets': (_) =>
            const CaptureShowcase(kind: 'home-widgets'),
      },
      initialRoute: _initialRoute(),
      home: _GalleryHome(
        themeMode: _themeMode,
        density: _density,
        style: _style,
        motion: _motion,
        textScale: _textScale,
        rtl: _rtl,
        highContrast: _highContrast,
        onThemeModeChanged: (value) => setState(() => _themeMode = value),
        onDensityChanged: (value) => setState(() => _density = value),
        onStyleChanged: (value) => setState(() => _style = value),
        onMotionChanged: (value) => setState(() => _motion = value),
        onTextScaleChanged: (value) => setState(() => _textScale = value),
        onRtlChanged: (value) => setState(() => _rtl = value),
        onHighContrastChanged: (value) => setState(() => _highContrast = value),
      ),
    );
  }

  String _initialRoute() {
    if (_routeOverride.isNotEmpty) return _routeOverride;
    if (Uri.base.queryParameters['showcase'] == '1') return '/showcase';
    final capture = Uri.base.queryParameters['capture'];
    if (capture != null &&
        const {
          'styles',
          'motion',
          'foldable',
          'native',
          'planning',
          'data',
          'selection',
          'celestial',
          'widgets',
          'legacy',
          'carousel',
          'horizon',
          'heatmaps',
          'responsive',
          'home-widgets',
        }.contains(capture)) {
      return '/capture/$capture';
    }
    return '/';
  }

  ThemeData _theme(Brightness brightness) {
    final dark = brightness == Brightness.dark;
    final scheme = ColorScheme.fromSeed(
      seedColor: const Color(0xFF5547D7),
      brightness: brightness,
    );
    return ThemeData(
      useMaterial3: true,
      brightness: brightness,
      colorScheme: scheme,
      scaffoldBackgroundColor:
          dark ? const Color(0xFF090B13) : const Color(0xFFF5F6FA),
      cardTheme: CardThemeData(
        elevation: 0,
        color: dark ? const Color(0xFF121521) : Colors.white,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
      ),
    );
  }
}

class _GalleryHome extends StatefulWidget {
  const _GalleryHome({
    required this.themeMode,
    required this.density,
    required this.style,
    required this.motion,
    required this.textScale,
    required this.rtl,
    required this.highContrast,
    required this.onThemeModeChanged,
    required this.onDensityChanged,
    required this.onStyleChanged,
    required this.onMotionChanged,
    required this.onTextScaleChanged,
    required this.onRtlChanged,
    required this.onHighContrastChanged,
  });

  final ThemeMode themeMode;
  final CalendarDensity density;
  final CalendarStyle style;
  final _GalleryMotion motion;
  final double textScale;
  final bool rtl;
  final bool highContrast;
  final ValueChanged<ThemeMode> onThemeModeChanged;
  final ValueChanged<CalendarDensity> onDensityChanged;
  final ValueChanged<CalendarStyle> onStyleChanged;
  final ValueChanged<_GalleryMotion> onMotionChanged;
  final ValueChanged<double> onTextScaleChanged;
  final ValueChanged<bool> onRtlChanged;
  final ValueChanged<bool> onHighContrastChanged;

  @override
  State<_GalleryHome> createState() => _GalleryHomeState();
}

class _GalleryHomeState extends State<_GalleryHome> {
  var _selectedDate = DateTime(2026, 8, 5);
  var _focusedDate = DateTime(2026, 8, 5);
  var _selection = CalendarSelection.range(
    CalendarDateRange(DateTime(2026, 8, 5), DateTime(2026, 8, 8)),
  );
  var _foldState = CalendarFoldState.collapsed;
  var _category = _GalleryCategory.all;
  var _nativeDate = DateTime(2026, 8, 5);
  var _multipleSelection = CalendarSelection.multiple([
    DateTime(2026, 8, 5),
    DateTime(2026, 8, 7),
  ]);
  var _rangeSelection = CalendarSelection.range(null);
  var _celestialDate = DateTime(2026, 8, 10);
  var _railDate = DateTime(2026, 8, 5);
  String? _selectedSlot = 'slot-1030';

  CalendarAppearance _appearance({
    CalendarStyle? style,
    HorizontalCalendarThemeData? theme,
    EventIndicatorStyle indicator = EventIndicatorStyle.stack,
  }) {
    return CalendarAppearance(
      style: style ?? widget.style,
      density: widget.density,
      eventIndicatorStyle: indicator,
      theme: theme,
      motion: _motionFor(widget.motion),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Horizontal Calendar 2.0'),
            Text(
              'Interactive UI kit gallery',
              style: TextStyle(fontSize: 12, fontWeight: FontWeight.w400),
            ),
          ],
        ),
        actions: [
          PopupMenuButton<String>(
            tooltip: 'Capture routes',
            icon: const Icon(Icons.video_library_outlined),
            onSelected: (route) => Navigator.pushNamed(context, route),
            itemBuilder: (context) => const [
              PopupMenuItem(value: '/showcase', child: Text('Hero capture')),
              PopupMenuItem(
                value: '/capture/styles',
                child: Text('Style capture'),
              ),
              PopupMenuItem(
                value: '/capture/motion',
                child: Text('Motion capture'),
              ),
              PopupMenuItem(
                value: '/capture/foldable',
                child: Text('Foldable capture'),
              ),
              PopupMenuItem(
                value: '/capture/native',
                child: Text('Native capture'),
              ),
              PopupMenuItem(
                value: '/capture/planning',
                child: Text('Planning capture'),
              ),
              PopupMenuItem(
                value: '/capture/data',
                child: Text('Data capture'),
              ),
              PopupMenuItem(
                value: '/capture/selection',
                child: Text('Selection capture'),
              ),
              PopupMenuItem(
                value: '/capture/celestial',
                child: Text('Celestial capture'),
              ),
              PopupMenuItem(
                value: '/capture/widgets',
                child: Text('Date widgets capture'),
              ),
              PopupMenuItem(
                value: '/capture/carousel',
                child: Text('Carousel capture'),
              ),
              PopupMenuItem(
                value: '/capture/horizon',
                child: Text('Horizon capture'),
              ),
              PopupMenuItem(
                value: '/capture/heatmaps',
                child: Text('Heatmap capture'),
              ),
              PopupMenuItem(
                value: '/capture/responsive',
                child: Text('Responsive widgets capture'),
              ),
              PopupMenuItem(
                value: '/capture/legacy',
                child: Text('Legacy compatibility capture'),
              ),
              PopupMenuItem(
                value: '/capture/home-widgets',
                child: Text('Home widgets capture'),
              ),
            ],
          ),
          const SizedBox(width: 8),
        ],
      ),
      body: CustomScrollView(
        slivers: [
          SliverToBoxAdapter(child: _galleryHero(context)),
          SliverToBoxAdapter(child: _controls(context)),
          SliverToBoxAdapter(child: _categoryFilters()),
          SliverPadding(
            padding: const EdgeInsets.fromLTRB(20, 8, 20, 48),
            sliver: SliverList.list(
              children: [
                _section(
                  context,
                  category: _GalleryCategory.custom,
                  eyebrow: 'DEVELOPER TOOLS',
                  title: 'Explore the API instead of guessing at it',
                  description:
                      'Two live studios expose the real calendar and home-widget options, callback state, responsive behavior, and copyable Dart recipes.',
                  child: LayoutBuilder(
                    builder: (context, constraints) {
                      final width = constraints.maxWidth >= 720
                          ? (constraints.maxWidth - 14) / 2
                          : constraints.maxWidth;
                      return Wrap(
                        spacing: 14,
                        runSpacing: 14,
                        children: [
                          SizedBox(
                            width: width,
                            child: _catalogToolCard(
                              context,
                              icon: Icons.auto_awesome_mosaic_rounded,
                              title: 'Real-world examples',
                              description:
                                  'Thirteen small products — training, booking, rotas, tracking, habits, reminders, and widgets — one per calendar surface.',
                              route: '/real-world',
                            ),
                          ),
                          SizedBox(
                            width: width,
                            child: _catalogToolCard(
                              context,
                              icon: Icons.tune_rounded,
                              title: 'Calendar playground',
                              description:
                                  'Style, density, selection, motion, scrolling, events, disabled dates, and foldable mode.',
                              route: '/playground',
                            ),
                          ),
                          SizedBox(
                            width: width,
                            child: _catalogToolCard(
                              context,
                              icon: Icons.widgets_rounded,
                              title: 'Home-widget studio',
                              description:
                                  'Every size and content type, portable themes, information rules, fine tuning, and native updates.',
                              route: '/home-widget-studio',
                            ),
                          ),
                        ],
                      );
                    },
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.horizontal,
                  eyebrow: 'THREE-LINE SETUP',
                  title: 'Beautiful by default',
                  description:
                      'Adaptive controls, locale-aware labels, keyboard navigation, and resilient compact scrolling.',
                  child: _calendarFrame(
                    HorizontalCalendar<Object?>(
                      selectedDate: _selectedDate,
                      onDateSelected: (date) =>
                          setState(() => _selectedDate = date),
                    ),
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.horizontal,
                  eyebrow: 'SIGNATURE PRESETS',
                  title: 'One API, distinct personalities',
                  description:
                      'Complete themes can be used as-is, inherited, copied, or replaced at the component level.',
                  child: _presetCarousel(context),
                ),
                _section(
                  context,
                  category: _GalleryCategory.motion,
                  eyebrow: 'MOTION LAB',
                  title:
                      'From silent to playful — without custom animation code',
                  description:
                      'Selection, paging, event markers, hover, and folding use one reusable motion choreography.',
                  child: _calendarFrame(
                    HorizontalCalendar<DemoEvent>(
                      selectedDate: _selectedDate,
                      onDateSelected: (date) =>
                          setState(() => _selectedDate = date),
                      appearance: _appearance(
                        style: CalendarStyle.materialExpressive,
                      ),
                      events: demoEvents,
                    ),
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.native,
                  eyebrow: 'NATIVE ADAPTATION',
                  title:
                      'Material where it belongs. Cupertino where it belongs.',
                  description:
                      'Navigation controls, target sizes, motion, modal presentation, and visual tokens adapt together.',
                  child: _calendarFrame(
                    Column(
                      children: [
                        AdaptiveCalendarNavigationBar(
                          focusedDate: _nativeDate,
                          onPrevious: () => setState(() {
                            _nativeDate = DateTime(
                              _nativeDate.year,
                              _nativeDate.month - 1,
                              _nativeDate.day,
                            );
                          }),
                          onNext: () => setState(() {
                            _nativeDate = DateTime(
                              _nativeDate.year,
                              _nativeDate.month + 1,
                              _nativeDate.day,
                            );
                          }),
                          onToday: () =>
                              setState(() => _nativeDate = DateTime.now()),
                          appearance: _appearance(),
                        ),
                        const SizedBox(height: 12),
                        Wrap(
                          spacing: 10,
                          runSpacing: 10,
                          alignment: WrapAlignment.center,
                          children: [
                            FilledButton.icon(
                              onPressed: () => _openAdaptivePicker(),
                              icon: const Icon(Icons.calendar_month_outlined),
                              label: Text(
                                'Open calendar · ${_nativeDate.day}',
                              ),
                            ),
                            OutlinedButton.icon(
                              onPressed: () => _openCupertinoWheel(),
                              icon: const Icon(Icons.view_week_outlined),
                              label: const Text('Open Cupertino wheel'),
                            ),
                          ],
                        ),
                        const SizedBox(height: 10),
                        CalendarCupertinoDatePicker(
                          value: _nativeDate,
                          onChanged: (date) =>
                              setState(() => _nativeDate = date),
                          bounds: CalendarDateRange(
                            DateTime(2026, 1, 1),
                            DateTime(2027, 12, 31),
                          ),
                          configuration:
                              const CalendarCupertinoPickerConfiguration(
                            showDayOfWeek: true,
                          ),
                          appearance: _appearance(
                            style: CalendarStyle.cupertinoTinted,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.horizontal,
                  eyebrow: 'DATE CAROUSEL',
                  title: 'Horizontal cards for booking, travel, and content',
                  description:
                      'Typed metadata, badges, events, bounds, custom cards, and optional page snapping.',
                  child: CalendarDateCarousel<DemoEvent>(
                    startDate: DateTime(2026, 8, 3),
                    dayCount: 14,
                    selectedDate: _selectedDate,
                    onDateSelected: (date) =>
                        setState(() => _selectedDate = date),
                    items: demoCarouselItems,
                    events: demoEvents,
                    appearance: _appearance(style: CalendarStyle.pill),
                    scrolling: CalendarScrollBehavior.page,
                    visualStyle: const CalendarCarouselVisualStyle(
                      layout: CalendarCarouselLayout.spotlight,
                      selectedScale: 1.035,
                      inactiveScale: .96,
                      inactiveOpacity: .76,
                      spacing: 12,
                      elevation: 8,
                      borderRadius: 24,
                    ),
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.selection,
                  eyebrow: 'SELECTION LAB',
                  title: 'Single, multiple, and range — all truly controlled',
                  description:
                      'Every tap updates visible state immediately. Limits, empty selection, completed ranges, and disabled dates use one rule engine.',
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: [
                      const _DemoLabel('SINGLE DATE'),
                      HorizontalCalendar<Object?>(
                        selectedDate: _selectedDate,
                        onDateSelected: (date) =>
                            setState(() => _selectedDate = date),
                        appearance:
                            _appearance(style: CalendarStyle.materialYou),
                      ),
                      const SizedBox(height: 18),
                      const _DemoLabel('MULTIPLE · UP TO 4'),
                      HorizontalCalendar<Object?>.controlled(
                        focusedDate: _focusedDate,
                        selection: _multipleSelection,
                        onFocusedDateChanged: (date) =>
                            setState(() => _focusedDate = date),
                        onSelectionChanged: (_, next) =>
                            setState(() => _multipleSelection = next),
                        behavior: const CalendarBehavior(
                          selectionBehavior: CalendarSelectionBehavior(
                            maximumMultipleDates: 4,
                            allowEmptyMultiple: true,
                          ),
                        ),
                        appearance: _appearance(style: CalendarStyle.aurora),
                      ),
                      const SizedBox(height: 18),
                      const _DemoLabel('DATE RANGE · TAP TO RESTART'),
                      HorizontalCalendar<Object?>.controlled(
                        focusedDate: _focusedDate,
                        selection: _rangeSelection,
                        onFocusedDateChanged: (date) =>
                            setState(() => _focusedDate = date),
                        onSelectionChanged: (_, next) =>
                            setState(() => _rangeSelection = next),
                        behavior: const CalendarBehavior(
                          selectionBehavior: CalendarSelectionBehavior(
                            maximumRangeDays: 10,
                          ),
                        ),
                        appearance: _appearance(style: CalendarStyle.sunset),
                      ),
                    ],
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.pickers,
                  eyebrow: 'HORIZON PICKER',
                  title: 'Move through dates as the sky changes',
                  description:
                      'An original date scrubber with decorative sun, moon, phase, drag, keyboard, month controls, bounds, and disabled-date handling.',
                  child: CelestialDatePicker(
                    value: _celestialDate,
                    onChanged: (date) => setState(() => _celestialDate = date),
                    bounds: CalendarDateRange(
                      DateTime(2026, 1, 1),
                      DateTime(2027, 12, 31),
                    ),
                    selectableDayPredicate: (date) =>
                        date.weekday != DateTime.friday,
                    appearance: _appearance(style: CalendarStyle.midnight),
                    celestialMotion: const CelestialMotion(
                      duration: Duration(milliseconds: 900),
                      arcHeight: .78,
                      drift: .28,
                      parallax: .42,
                      trailLength: .72,
                      starTwinkle: .78,
                    ),
                    style: const CelestialDatePickerStyle(
                      skyHeight: 210,
                      skyStyle: CelestialSkyStyle.aurora,
                      composition: CelestialComposition.cinematic,
                      showClouds: true,
                      showConstellations: true,
                      showPhaseOrbit: true,
                      showDateProgress: true,
                    ),
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.data,
                  eyebrow: 'CALENDAR INSIGHTS',
                  title:
                      'Heatmaps and streaks that speak the same design language',
                  description:
                      'Activity intensity and completion state stay accessible, tappable, localized, and horizontally compact.',
                  child: Column(
                    children: [
                      CalendarHeatmapStrip(
                        startDate: DateTime(2026, 8, 1),
                        dayCount: 31,
                        values: demoHeatmapValues,
                        selectedDate: _selectedDate,
                        onDateTap: (date) =>
                            setState(() => _selectedDate = date),
                        appearance: _appearance(style: CalendarStyle.neon),
                        style: const CalendarHeatmapStyle(
                          design: CalendarHeatmapDesign.ring,
                          animate: true,
                          showPercentage: true,
                        ),
                      ),
                      const SizedBox(height: 18),
                      SizedBox(
                        height: 196,
                        child: CalendarContributionHeatmap(
                          startDate: DateTime(2026, 1, 1),
                          dayCount: 365,
                          values: demoContributionValues,
                          selectedDate: _selectedDate,
                          onDateTap: (date) =>
                              setState(() => _selectedDate = date),
                          appearance: _appearance(style: CalendarStyle.aurora),
                          style: const CalendarHeatmapStyle(
                            design: CalendarHeatmapDesign.pill,
                            cellExtent: 32,
                            cellSpacing: 5,
                            showLabels: false,
                            animate: true,
                          ),
                        ),
                      ),
                      const SizedBox(height: 18),
                      CalendarStreakStrip(
                        startDate: DateTime(2026, 8, 1),
                        dayCount: 31,
                        today: DateTime(2026, 8, 10),
                        selectedDate: _selectedDate,
                        completedDates: demoCompletedDates,
                        onDateTap: (date) =>
                            setState(() => _selectedDate = date),
                        appearance: _appearance(style: CalendarStyle.soft),
                      ),
                      const SizedBox(height: 18),
                      CalendarInsightsDashboard<DemoEvent>(
                        title: 'Your calendar, decoded',
                        subtitle: 'A useful summary without losing the dates.',
                        metrics: demoInsightMetrics,
                        design: CalendarInsightsDesign.glass,
                        appearance:
                            _appearance(style: CalendarStyle.cupertinoGlass),
                      ),
                    ],
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.data,
                  eyebrow: 'PHONE HOME WIDGETS',
                  title: 'Calendar context before the app even opens',
                  description:
                      'One serializable contract powers responsive Flutter previews, Android launcher widgets, and the WidgetKit extension.',
                  child: Column(
                    children: [
                      SizedBox(
                        height: 170,
                        child: CalendarHomeWidget(
                          data: demoHomeWidgetData,
                          family: CalendarHomeWidgetFamily.medium,
                          content: CalendarHomeWidgetContent.week,
                        ),
                      ),
                      const SizedBox(height: 14),
                      Row(
                        children: [
                          Expanded(
                            child: AspectRatio(
                              aspectRatio: 1,
                              child: CalendarHomeWidget(
                                data: demoHomeWidgetData,
                                family: CalendarHomeWidgetFamily.small,
                                content: CalendarHomeWidgetContent.today,
                              ),
                            ),
                          ),
                          const SizedBox(width: 14),
                          Expanded(
                            child: AspectRatio(
                              aspectRatio: 1,
                              child: CalendarHomeWidget(
                                data: demoHomeWidgetData,
                                family: CalendarHomeWidgetFamily.small,
                                content: CalendarHomeWidgetContent.countdown,
                              ),
                            ),
                          ),
                        ],
                      ),
                      const SizedBox(height: 14),
                      FilledButton.icon(
                        onPressed: () => Navigator.pushNamed(
                          context,
                          '/home-widget-studio',
                        ),
                        icon: const Icon(Icons.tune_rounded),
                        label: const Text('Open all home-widget controls'),
                      ),
                    ],
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.planning,
                  eyebrow: 'FOLDABLE',
                  title:
                      'Week when you need speed. Month when you need context.',
                  description:
                      'Tap the handle or drag vertically. Focus, selection, events, and bounds remain shared.',
                  child: _calendarFrame(
                    FoldableCalendar<DemoEvent>(
                      focusedDate: _focusedDate,
                      selection: _selection,
                      foldState: _foldState,
                      onFocusedDateChanged: (date) =>
                          setState(() => _focusedDate = date),
                      onSelectionChanged: (_, next) =>
                          setState(() => _selection = next),
                      onFoldStateChanged: (state) =>
                          setState(() => _foldState = state),
                      appearance: _appearance(
                        theme: HorizontalCalendarThemeData.glass(
                          brightness: Theme.of(context).brightness,
                        ),
                      ),
                      events: demoEvents,
                      foldControl: CalendarFoldControl.button,
                      expandLabel: 'Open month view',
                      collapseLabel: 'Return to week',
                    ),
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.events,
                  eyebrow: 'EVENT-RICH STRIP',
                  title: 'Typed data without calendar boilerplate',
                  description:
                      'Cross-midnight events are bucketed once per date and custom builders receive complete immutable day state.',
                  child: _calendarFrame(
                    HorizontalCalendar<DemoEvent>.controlled(
                      focusedDate: _focusedDate,
                      selection: _multipleSelection,
                      onFocusedDateChanged: (date) =>
                          setState(() => _focusedDate = date),
                      onSelectionChanged: (_, next) =>
                          setState(() => _multipleSelection = next),
                      events: demoEvents,
                      appearance:
                          _appearance(indicator: EventIndicatorStyle.count),
                    ),
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.events,
                  eyebrow: 'AGENDA',
                  title: 'The story behind each day',
                  description:
                      'Synchronous or asynchronous events, grouped by civil date with typed callbacks and refresh states.',
                  height: 520,
                  child: CalendarAgenda<DemoEvent>(
                    interval: CalendarVisibleInterval(
                      DateTime(2026, 8, 3),
                      DateTime(2026, 8, 10),
                    ),
                    events: demoEvents,
                    appearance: _appearance(),
                    onEventTap: _showEvent,
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.custom,
                  eyebrow: 'CUSTOM BUILDERS',
                  title: 'Own every pixel without rebuilding the engine',
                  description:
                      'Custom content still receives normalized selection, focus, availability, events, range position, and semantics.',
                  child: _calendarFrame(
                    HorizontalCalendar<DemoEvent>.controlled(
                      focusedDate: _focusedDate,
                      selection: _selection,
                      onFocusedDateChanged: (date) =>
                          setState(() => _focusedDate = date),
                      onSelectionChanged: (_, next) =>
                          setState(() => _selection = next),
                      events: demoEvents,
                      appearance: _appearance(),
                      builders: CalendarBuilders<DemoEvent>(
                        dayBuilder: (context, state) =>
                            _GalleryDay(state: state),
                      ),
                    ),
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.planning,
                  eyebrow: 'DATE RAIL',
                  title: 'A vertical date axis for feeds and journeys',
                  description:
                      'Lazy, typed, event-aware date rows work as a schedule sidebar or as the spine of a chronological feed.',
                  child: SizedBox(
                    height: 360,
                    child: CalendarDateRail<DemoEvent>(
                      startDate: DateTime(2026, 8, 1),
                      dayCount: 31,
                      selectedDate: _railDate,
                      onDateSelected: (date) =>
                          setState(() => _railDate = date),
                      items: demoCarouselItems,
                      events: demoEvents,
                      appearance: _appearance(style: CalendarStyle.paper),
                    ),
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.planning,
                  eyebrow: 'PLANNING BUILDING BLOCKS',
                  title: 'Ribbons, milestones, and bookable availability',
                  description:
                      'Compose complete scheduling flows with overlap-aware intervals, project dates, and controlled time slots.',
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: [
                      const _DemoLabel('SCHEDULE RIBBON'),
                      CalendarScheduleRibbon<DemoEvent>(
                        date: DateTime(2026, 8, 5),
                        intervals: demoScheduleIntervals,
                        startHour: 8,
                        endHour: 18,
                        minuteWidth: 1.4,
                        now: DateTime(2026, 8, 5, 11, 20),
                        appearance: _appearance(style: CalendarStyle.terminal),
                      ),
                      const SizedBox(height: 20),
                      const _DemoLabel('MILESTONES'),
                      SizedBox(
                        height: 138,
                        child: CalendarMilestoneTimeline<DemoEvent>(
                          milestones: demoMilestones,
                          currentDate: DateTime(2026, 8, 10),
                          design: CalendarMilestoneDesign.roadmap,
                          appearance: _appearance(style: CalendarStyle.luxury),
                        ),
                      ),
                      const SizedBox(height: 20),
                      const _DemoLabel('AVAILABILITY'),
                      CalendarAvailabilityStrip<DemoEvent>(
                        slots: demoAvailability,
                        selectedSlotId: _selectedSlot,
                        onSlotSelected: (slot) =>
                            setState(() => _selectedSlot = slot.id),
                        layout: CalendarAvailabilityLayout.auto,
                        design: CalendarAvailabilityDesign.schedule,
                        appearance:
                            _appearance(style: CalendarStyle.cupertinoTinted),
                      ),
                    ],
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.planning,
                  eyebrow: 'DATE INDICATORS',
                  title: 'Small surfaces with real calendar intelligence',
                  description:
                      'Countdowns, weekly progress, and range summaries adapt their geometry to narrow screens and large text.',
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: [
                      CalendarCountdownCard<DemoEvent>(
                        startDate: DateTime(2026, 8, 1),
                        referenceDate: DateTime(2026, 8, 5),
                        targetDate: DateTime(2026, 8, 18),
                        title: 'Design system launch',
                        data: const DemoEvent('Calendar team', 'launch'),
                        appearance: _appearance(style: CalendarStyle.sunset),
                      ),
                      const SizedBox(height: 18),
                      CalendarWeekProgress(
                        startDate: DateTime(2026, 8, 3),
                        currentDate: DateTime(2026, 8, 5),
                        selectedDate: _selectedDate,
                        onDateTap: (date) =>
                            setState(() => _selectedDate = date),
                        appearance: _appearance(style: CalendarStyle.soft),
                      ),
                      const SizedBox(height: 18),
                      CalendarDateRangeSummary<DemoEvent>(
                        range: CalendarDateRange(
                          DateTime(2026, 8, 3),
                          DateTime(2026, 8, 18),
                        ),
                        referenceDate: DateTime(2026, 8, 8),
                        title: 'Release runway',
                        data: const DemoEvent('Release team', 'range'),
                        appearance: _appearance(style: CalendarStyle.luxury),
                      ),
                    ],
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.legacy,
                  eyebrow: 'LEGACY SAFE',
                  title: 'Every 1.x calendar still has a home',
                  description:
                      'The original widgets remain usable and deprecated with clear migration paths, so upgrading never forces a rewrite.',
                  child: _legacyGallery(),
                ),
                _section(
                  context,
                  category: _GalleryCategory.planning,
                  eyebrow: 'DAY TIMELINE',
                  title: 'Overlaps that stay readable',
                  description:
                      'Connected collisions use deterministic columns while taps return the original typed event.',
                  child: DayTimeline<DemoEvent>(
                    date: DateTime(2026, 8, 5),
                    events: demoEvents,
                    now: DateTime(2026, 8, 5, 11, 20),
                    configuration: const CalendarTimelineConfiguration(
                      startHour: 8,
                      endHour: 18,
                      viewportHeight: 560,
                    ),
                    appearance: _appearance(),
                    onEventTap: _showEvent,
                  ),
                ),
                _section(
                  context,
                  category: _GalleryCategory.planning,
                  eyebrow: 'WEEK TIMELINE',
                  title: 'The full working week',
                  description:
                      'Contiguous day columns, configurable hours, current time, and compact horizontal navigation.',
                  child: WeekTimeline<DemoEvent>(
                    startDate: DateTime(2026, 8, 3),
                    dayCount: 5,
                    events: demoEvents,
                    now: DateTime(2026, 8, 5, 11, 20),
                    configuration: const CalendarTimelineConfiguration(
                      startHour: 8,
                      endHour: 18,
                      viewportHeight: 560,
                      dayColumnWidth: 210,
                    ),
                    appearance: _appearance(),
                    onEventTap: _showEvent,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _galleryHero(BuildContext context) {
    final colors = Theme.of(context).colorScheme;
    return Padding(
      padding: const EdgeInsets.fromLTRB(20, 20, 20, 10),
      child: DecoratedBox(
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(32),
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              colors.primary,
              Color.lerp(colors.primary, colors.tertiary, .72)!,
              const Color(0xFF111938),
            ],
          ),
          boxShadow: [
            BoxShadow(
              color: colors.primary.withValues(alpha: .25),
              blurRadius: 34,
              offset: const Offset(0, 18),
            ),
          ],
        ),
        child: Padding(
          padding: const EdgeInsets.all(28),
          child: LayoutBuilder(builder: (context, constraints) {
            final compact = constraints.maxWidth < 700;
            final copy = Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'TIME, DESIGNED.',
                  style: Theme.of(context).textTheme.labelLarge?.copyWith(
                        color: Colors.white70,
                        letterSpacing: 2.2,
                        fontWeight: FontWeight.w800,
                      ),
                ),
                const SizedBox(height: 10),
                Text(
                  'The horizontal calendar\nUI kit for Flutter.',
                  style: Theme.of(context).textTheme.displaySmall?.copyWith(
                        color: Colors.white,
                        height: .98,
                        letterSpacing: -1.4,
                        fontWeight: FontWeight.w900,
                      ),
                ),
                const SizedBox(height: 14),
                const Text(
                  'Week strips, native pickers, celestial motion, planning surfaces, insights, and legacy-safe migration—built around one predictable date engine.',
                  style: TextStyle(color: Colors.white70, height: 1.45),
                ),
                const SizedBox(height: 18),
                const Wrap(
                  spacing: 8,
                  runSpacing: 8,
                  children: [
                    _HeroTag('21 visual styles'),
                    _HeroTag('11 page transitions'),
                    _HeroTag('Material + Cupertino'),
                    _HeroTag('Legacy safe'),
                  ],
                ),
              ],
            );
            final emblem = Container(
              width: compact ? 116 : 172,
              height: compact ? 116 : 172,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.white.withValues(alpha: .1),
                border: Border.all(color: Colors.white24),
              ),
              child: Stack(
                alignment: Alignment.center,
                children: [
                  Icon(
                    Icons.calendar_month_rounded,
                    color: Colors.white,
                    size: compact ? 58 : 82,
                  ),
                  Positioned(
                    top: compact ? 15 : 20,
                    right: compact ? 6 : 10,
                    child: const Icon(
                      Icons.auto_awesome_rounded,
                      color: Color(0xFFFFD166),
                      size: 28,
                    ),
                  ),
                ],
              ),
            );
            if (compact) {
              return Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [emblem, const SizedBox(height: 22), copy],
              );
            }
            return Row(
              children: [
                Expanded(child: copy),
                const SizedBox(width: 36),
                emblem,
              ],
            );
          }),
        ),
      ),
    );
  }

  Widget _controls(BuildContext context) {
    return Card(
      margin: const EdgeInsets.fromLTRB(20, 16, 20, 12),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Wrap(
          spacing: 12,
          runSpacing: 12,
          crossAxisAlignment: WrapCrossAlignment.center,
          children: [
            SegmentedButton<ThemeMode>(
              segments: const [
                ButtonSegment(
                    value: ThemeMode.light, icon: Icon(Icons.light_mode)),
                ButtonSegment(
                    value: ThemeMode.dark, icon: Icon(Icons.dark_mode)),
              ],
              selected: {widget.themeMode},
              onSelectionChanged: (value) =>
                  widget.onThemeModeChanged(value.single),
            ),
            DropdownButton<CalendarDensity>(
              value: widget.density,
              items: CalendarDensity.values
                  .map((value) => DropdownMenuItem(
                        value: value,
                        child: Text(value.name),
                      ))
                  .toList(),
              onChanged: (value) {
                if (value != null) widget.onDensityChanged(value);
              },
            ),
            DropdownButton<CalendarStyle>(
              value: widget.style,
              items: CalendarStyle.values
                  .map((value) => DropdownMenuItem(
                        value: value,
                        child: Text(value.name),
                      ))
                  .toList(),
              onChanged: (value) {
                if (value != null) widget.onStyleChanged(value);
              },
            ),
            DropdownButton<_GalleryMotion>(
              value: widget.motion,
              items: _GalleryMotion.values
                  .map((value) => DropdownMenuItem(
                        value: value,
                        child: Text('${value.name} motion'),
                      ))
                  .toList(),
              onChanged: (value) {
                if (value != null) widget.onMotionChanged(value);
              },
            ),
            FilterChip(
              label: const Text('RTL'),
              selected: widget.rtl,
              onSelected: widget.onRtlChanged,
            ),
            FilterChip(
              label: const Text('High contrast'),
              selected: widget.highContrast,
              onSelected: widget.onHighContrastChanged,
            ),
            SizedBox(
              width: 190,
              child: Row(
                children: [
                  const Icon(Icons.text_fields, size: 18),
                  Expanded(
                    child: Slider(
                      value: widget.textScale,
                      min: 1,
                      max: 2,
                      divisions: 4,
                      label: '${widget.textScale}×',
                      onChanged: widget.onTextScaleChanged,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _categoryFilters() {
    return SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4),
      child: Row(
        children: [
          for (final category in _GalleryCategory.values)
            Padding(
              padding: const EdgeInsetsDirectional.only(end: 8),
              child: ChoiceChip(
                label: Text(category.name),
                selected: _category == category,
                onSelected: (_) => setState(() => _category = category),
              ),
            ),
        ],
      ),
    );
  }

  Widget _presetCarousel(BuildContext context) {
    final brightness = Theme.of(context).brightness;
    final presets = <(String, HorizontalCalendarThemeData)>[
      (
        'Material 3',
        HorizontalCalendarThemeData.material3(brightness: brightness)
      ),
      (
        'Cupertino',
        HorizontalCalendarThemeData.cupertino(brightness: brightness)
      ),
      ('Neutral', HorizontalCalendarThemeData.neutral(brightness: brightness)),
      ('Glass', HorizontalCalendarThemeData.glass(brightness: brightness)),
      (
        'Editorial',
        HorizontalCalendarThemeData.editorial(brightness: brightness)
      ),
      ('Bold', HorizontalCalendarThemeData.bold(brightness: brightness)),
      (
        'Material Expressive',
        HorizontalCalendarThemeData.materialExpressive(brightness: brightness)
      ),
      (
        'Cupertino Glass',
        HorizontalCalendarThemeData.cupertinoGlass(brightness: brightness)
      ),
      ('Minimal', HorizontalCalendarThemeData.minimal(brightness: brightness)),
      ('Pill', HorizontalCalendarThemeData.pill(brightness: brightness)),
      ('Soft', HorizontalCalendarThemeData.soft(brightness: brightness)),
      ('Neon', HorizontalCalendarThemeData.neon(brightness: brightness)),
      (
        'Monochrome',
        HorizontalCalendarThemeData.monochrome(brightness: brightness)
      ),
      ('Aurora', HorizontalCalendarThemeData.aurora(brightness: brightness)),
      ('Sunset', HorizontalCalendarThemeData.sunset(brightness: brightness)),
      (
        'Midnight',
        HorizontalCalendarThemeData.midnight(brightness: brightness)
      ),
      ('Paper', HorizontalCalendarThemeData.paper(brightness: brightness)),
      (
        'Terminal',
        HorizontalCalendarThemeData.terminal(brightness: brightness)
      ),
      ('Luxury', HorizontalCalendarThemeData.luxury(brightness: brightness)),
      (
        'Material You',
        HorizontalCalendarThemeData.materialYou(brightness: brightness)
      ),
      (
        'Cupertino Tinted',
        HorizontalCalendarThemeData.cupertinoTinted(brightness: brightness)
      ),
    ];
    return SizedBox(
      height: 220,
      child: ListView.separated(
        scrollDirection: Axis.horizontal,
        itemCount: presets.length,
        separatorBuilder: (_, __) => const SizedBox(width: 14),
        itemBuilder: (context, index) {
          final preset = presets[index];
          return SizedBox(
            width: 430,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Padding(
                  padding: const EdgeInsets.only(bottom: 8),
                  child: Text(preset.$1,
                      style: const TextStyle(fontWeight: FontWeight.w700)),
                ),
                HorizontalCalendar<DemoEvent>(
                  selectedDate: _selectedDate,
                  onDateSelected: (date) =>
                      setState(() => _selectedDate = date),
                  appearance: _appearance(theme: preset.$2),
                  events: demoEvents,
                ),
              ],
            ),
          );
        },
      ),
    );
  }

  Widget _legacyGallery() {
    const legacyStyle = legacy.HorizontalCalendarStyle(
      activeDayColor: Color(0xFF5547D7),
      dayIndicatorSize: 42,
      selectionAnimationDuration: Duration(milliseconds: 260),
    );
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        const _DemoLabel('ORIGINAL HORIZONTAL WEEKLY CALENDAR'),
        legacy.HorizontalWeeklyCalendar(
          initialDate: _focusedDate,
          selectedDate: _selectedDate,
          onDateSelected: (date) => setState(() => _selectedDate = date),
          onNextMonth: () => setState(() {
            _focusedDate = DateTime(
              _focusedDate.year,
              _focusedDate.month + 1,
              1,
            );
          }),
          onPreviousMonth: () => setState(() {
            _focusedDate = DateTime(
              _focusedDate.year,
              _focusedDate.month - 1,
              1,
            );
          }),
          calendarType: legacy.HorizontalCalendarType.elevated,
          calendarStyle: legacyStyle,
        ),
        const SizedBox(height: 24),
        const _DemoLabel('TABLE WEEKLY CALENDAR'),
        legacy.TableWeeklyCalendar(
          initialDate: _focusedDate,
          selectedDate: _selectedDate,
          onDateSelected: (date) => setState(() => _selectedDate = date),
          onMonthChanged: (date) => setState(() => _focusedDate = date),
          focusDates: [
            legacy.FocusDate(
              date: DateTime(2026, 8, 12),
              backgroundColor: const Color(0xFFFFD166),
              foregroundColor: const Color(0xFF191510),
            ),
          ],
          calendarStyle: legacyStyle,
        ),
        const SizedBox(height: 24),
        const _DemoLabel('EVENT CALENDAR'),
        SizedBox(
          height: 520,
          child: legacy.EventCalendar(
            currentMonth: _focusedDate,
            selectedDate: _selectedDate,
            events: _legacyEvents,
            startHour: 8,
            endHour: 16,
            onDateSelected: (date) => setState(() => _selectedDate = date),
            onNextMonth: () => setState(() {
              _focusedDate = DateTime(
                _focusedDate.year,
                _focusedDate.month + 1,
                1,
              );
            }),
            onPreviousMonth: () => setState(() {
              _focusedDate = DateTime(
                _focusedDate.year,
                _focusedDate.month - 1,
                1,
              );
            }),
            style: const legacy.EventCalendarStyle(
              timeSlotHeight: 48,
              hourSlotHeight: 48,
              calendarStyle: legacyStyle,
            ),
          ),
        ),
      ],
    );
  }

  Widget _catalogToolCard(
    BuildContext context, {
    required IconData icon,
    required String title,
    required String description,
    required String route,
  }) {
    final colors = Theme.of(context).colorScheme;
    return Card(
      margin: EdgeInsets.zero,
      color: colors.surfaceContainerHigh,
      child: InkWell(
        borderRadius: BorderRadius.circular(24),
        onTap: () => Navigator.pushNamed(context, route),
        child: Padding(
          padding: const EdgeInsets.all(20),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              CircleAvatar(
                backgroundColor: colors.primaryContainer,
                foregroundColor: colors.onPrimaryContainer,
                child: Icon(icon),
              ),
              const SizedBox(height: 16),
              Text(
                title,
                style: Theme.of(context).textTheme.titleLarge?.copyWith(
                      fontWeight: FontWeight.w800,
                    ),
              ),
              const SizedBox(height: 6),
              Text(description),
              const SizedBox(height: 16),
              Row(
                children: [
                  Text(
                    'Open live studio',
                    style: TextStyle(
                      color: colors.primary,
                      fontWeight: FontWeight.w700,
                    ),
                  ),
                  const SizedBox(width: 6),
                  Icon(Icons.arrow_forward_rounded, color: colors.primary),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _calendarFrame(Widget child) {
    return Align(
      alignment: AlignmentDirectional.centerStart,
      child: ConstrainedBox(
        constraints: const BoxConstraints(maxWidth: 820),
        child: child,
      ),
    );
  }

  Widget _section(
    BuildContext context, {
    required _GalleryCategory category,
    required String eyebrow,
    required String title,
    required String description,
    required Widget child,
    double? height,
  }) {
    if (_category != _GalleryCategory.all && _category != category) {
      return const SizedBox.shrink();
    }
    return Padding(
      padding: const EdgeInsets.only(top: 32),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Text(
            eyebrow,
            style: TextStyle(
              color: Theme.of(context).colorScheme.primary,
              fontWeight: FontWeight.w800,
              letterSpacing: 1.4,
              fontSize: 11,
            ),
          ),
          const SizedBox(height: 6),
          Text(title, style: Theme.of(context).textTheme.headlineSmall),
          const SizedBox(height: 6),
          Text(description, style: Theme.of(context).textTheme.bodyMedium),
          const SizedBox(height: 16),
          Card(
            clipBehavior: Clip.antiAlias,
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: height == null
                  ? child
                  : SizedBox(height: height, child: child),
            ),
          ),
        ],
      ),
    );
  }

  Future<void> _openAdaptivePicker() async {
    final date = await showAdaptiveCalendarPicker(
      context: context,
      initialDate: _nativeDate,
      bounds: CalendarDateRange(
        DateTime(2026, 1, 1),
        DateTime(2027, 12, 31),
      ),
      appearance: _appearance(),
      materialConfiguration: const CalendarMaterialPickerConfiguration(
        confirmSelection: true,
        showQuickActions: true,
        headline: 'Plan something remarkable',
        helpText: 'Pick a day, then confirm when it feels right.',
      ),
    );
    if (date != null && mounted) setState(() => _nativeDate = date);
  }

  Future<void> _openCupertinoWheel() async {
    final date = await showAdaptiveCalendarPicker(
      context: context,
      initialDate: _nativeDate,
      bounds: CalendarDateRange(
        DateTime(2026, 1, 1),
        DateTime(2027, 12, 31),
      ),
      appearance: _appearance(style: CalendarStyle.cupertinoTinted),
      cupertinoPresentation: CalendarCupertinoPickerPresentation.wheel,
      cupertinoWheelConfiguration:
          const CalendarCupertinoPickerConfiguration(showDayOfWeek: true),
    );
    if (date != null && mounted) setState(() => _nativeDate = date);
  }

  void _showEvent(CalendarEvent<DemoEvent> event) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('${event.title} · ${event.data!.owner}')),
    );
  }
}

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

  @override
  State<RecordingShowcase> createState() => _RecordingShowcaseState();
}

class _RecordingShowcaseState extends State<RecordingShowcase> {
  var _selected = DateTime(2026, 8, 5);

  @override
  Widget build(BuildContext context) {
    final theme = HorizontalCalendarThemeData.bold(
      brightness: Theme.of(context).brightness,
    );
    return Scaffold(
      body: DecoratedBox(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              Theme.of(context).colorScheme.primaryContainer,
              Theme.of(context).scaffoldBackgroundColor,
            ],
          ),
        ),
        child: SafeArea(
          child: Center(
            child: SingleChildScrollView(
              padding: const EdgeInsets.all(24),
              child: ConstrainedBox(
                constraints: const BoxConstraints(maxWidth: 980),
                child: Column(
                  children: [
                    Text(
                      'Your calendar should feel like your app.',
                      textAlign: TextAlign.center,
                      style: Theme.of(context).textTheme.displaySmall?.copyWith(
                            fontWeight: FontWeight.w900,
                            letterSpacing: -1.5,
                          ),
                    ),
                    const SizedBox(height: 12),
                    Text(
                      'Horizontal Calendar 2.0',
                      style: Theme.of(context).textTheme.titleLarge,
                    ),
                    const SizedBox(height: 36),
                    HorizontalCalendar<DemoEvent>(
                      selectedDate: _selected,
                      onDateSelected: (date) =>
                          setState(() => _selected = date),
                      appearance: CalendarAppearance(
                        theme: theme,
                        eventIndicatorStyle: EventIndicatorStyle.stack,
                        motion: CalendarMotion.playful(),
                      ),
                      events: demoEvents,
                    ),
                    const SizedBox(height: 24),
                    SizedBox(
                      height: 330,
                      child: CalendarAgenda<DemoEvent>(
                        interval: CalendarVisibleInterval(
                          DateTime(2026, 8, 3),
                          DateTime(2026, 8, 10),
                        ),
                        events: demoEvents,
                        appearance: CalendarAppearance(theme: theme),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class CaptureShowcase extends StatefulWidget {
  const CaptureShowcase({super.key, required this.kind});

  final String kind;

  @override
  State<CaptureShowcase> createState() => _CaptureShowcaseState();
}

class _CaptureShowcaseState extends State<CaptureShowcase> {
  var _selected = DateTime(2026, 8, 5);
  var _focused = DateTime(2026, 8, 5);
  var _fold = CalendarFoldState.collapsed;
  var _selection = CalendarSelection.single(DateTime(2026, 8, 5));
  String? _selectedSlot = 'slot-1030';

  @override
  Widget build(BuildContext context) {
    final title = switch (widget.kind) {
      'styles' => 'A calendar for every product language.',
      'motion' => 'Motion that makes time feel tangible.',
      'foldable' => 'One week. One month. One continuous thought.',
      'native' => 'Native where it matters.',
      'planning' => 'From date choice to the shape of the day.',
      'data' => 'Turn dates into progress.',
      'selection' => 'Every selection mode. One predictable contract.',
      'celestial' => 'Let the sky carry you through time.',
      'widgets' => 'The building blocks of a complete scheduling flow.',
      'legacy' => 'The future arrived without breaking the past.',
      'carousel' => 'Dates with depth, hierarchy, and momentum.',
      'horizon' => 'A date transition written across the sky.',
      'heatmaps' => 'Make every day of progress visible.',
      'responsive' => 'Calendar UI that respects every screen.',
      'home-widgets' => 'Your calendar, alive on the home screen.',
      _ => 'Horizontal Calendar 2.0',
    };
    return Scaffold(
      body: DecoratedBox(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              Theme.of(context).colorScheme.primaryContainer,
              Theme.of(context).scaffoldBackgroundColor,
              Theme.of(context).colorScheme.secondaryContainer,
            ],
          ),
        ),
        child: SafeArea(
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(28),
            child: Center(
              child: ConstrainedBox(
                constraints: const BoxConstraints(maxWidth: 1100),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    Text(
                      title,
                      textAlign: TextAlign.center,
                      style: Theme.of(context).textTheme.displaySmall?.copyWith(
                            fontWeight: FontWeight.w900,
                            letterSpacing: -1.4,
                          ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      'horizontal_weekly_calendar 2.0',
                      textAlign: TextAlign.center,
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 34),
                    _surface(context),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget _surface(BuildContext context) {
    return switch (widget.kind) {
      'styles' => Column(
          children: [
            _captureCalendar(CalendarStyle.materialExpressive),
            const SizedBox(height: 18),
            _captureCalendar(CalendarStyle.cupertinoGlass),
            const SizedBox(height: 18),
            _captureCalendar(CalendarStyle.neon),
          ],
        ),
      'motion' => Column(
          children: [
            _captureCalendar(
              CalendarStyle.bold,
              motion: CalendarMotion.cinematic(),
            ),
            const SizedBox(height: 20),
            CalendarDateCarousel<DemoEvent>(
              startDate: DateTime(2026, 8, 3),
              dayCount: 10,
              selectedDate: _selected,
              onDateSelected: (date) => setState(() => _selected = date),
              items: demoCarouselItems,
              events: demoEvents,
              appearance: CalendarAppearance(
                style: CalendarStyle.pill,
                motion: CalendarMotion.premium(),
              ),
              visualStyle: const CalendarCarouselVisualStyle(
                layout: CalendarCarouselLayout.spotlight,
              ),
            ),
          ],
        ),
      'foldable' => FoldableCalendar<DemoEvent>(
          focusedDate: _focused,
          selection: _selection,
          foldState: _fold,
          onFocusedDateChanged: (date) => setState(() => _focused = date),
          onSelectionChanged: (_, next) => setState(() => _selection = next),
          onFoldStateChanged: (state) => setState(() => _fold = state),
          events: demoEvents,
          foldControl: CalendarFoldControl.button,
          appearance: CalendarAppearance(
            style: CalendarStyle.cupertinoGlass,
            motion: CalendarMotion.spring(),
          ),
        ),
      'native' => Column(
          children: [
            AdaptiveCalendarNavigationBar(
              focusedDate: _focused,
              onPrevious: () => setState(() {
                _focused = DateTime(_focused.year, _focused.month - 1, 1);
              }),
              onNext: () => setState(() {
                _focused = DateTime(_focused.year, _focused.month + 1, 1);
              }),
              onToday: () => setState(() => _focused = DateTime.now()),
              appearance: const CalendarAppearance(
                style: CalendarStyle.cupertinoGlass,
              ),
            ),
            const SizedBox(height: 14),
            _captureCalendar(CalendarStyle.cupertinoGlass),
            const SizedBox(height: 14),
            FilledButton.icon(
              onPressed: () => showAdaptiveCalendarPicker(
                context: context,
                initialDate: _selected,
                appearance: const CalendarAppearance(
                  style: CalendarStyle.cupertinoTinted,
                ),
                cupertinoPresentation:
                    CalendarCupertinoPickerPresentation.wheel,
              ),
              icon: const Icon(Icons.calendar_month_outlined),
              label: const Text('Open styled Cupertino wheel'),
            ),
          ],
        ),
      'planning' => LayoutBuilder(
          builder: (context, constraints) {
            final timeline = DayTimeline<DemoEvent>(
              date: DateTime(2026, 8, 5),
              events: demoEvents,
              now: DateTime(2026, 8, 5, 11, 20),
              configuration: const CalendarTimelineConfiguration(
                startHour: 8,
                endHour: 18,
                viewportHeight: 520,
              ),
              appearance: const CalendarAppearance(
                style: CalendarStyle.materialExpressive,
              ),
            );
            final agenda = SizedBox(
              height: 520,
              child: CalendarAgenda<DemoEvent>(
                interval: CalendarVisibleInterval(
                  DateTime(2026, 8, 3),
                  DateTime(2026, 8, 10),
                ),
                events: demoEvents,
                appearance: const CalendarAppearance(
                  style: CalendarStyle.minimal,
                ),
              ),
            );
            if (constraints.maxWidth < 820) {
              return Column(
                  children: [timeline, const SizedBox(height: 18), agenda]);
            }
            return Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Expanded(child: timeline),
                const SizedBox(width: 18),
                Expanded(child: agenda),
              ],
            );
          },
        ),
      'data' => Column(
          children: [
            CalendarInsightsDashboard<DemoEvent>(
              title: 'Your calendar, decoded',
              subtitle: 'Signals that remain useful at a glance.',
              metrics: demoInsightMetrics,
              design: CalendarInsightsDesign.glass,
              appearance: const CalendarAppearance(
                style: CalendarStyle.cupertinoGlass,
              ),
            ),
            const SizedBox(height: 20),
            CalendarHeatmapStrip(
              startDate: DateTime(2026, 8, 1),
              dayCount: 31,
              values: demoHeatmapValues,
              selectedDate: _selected,
              onDateTap: (date) => setState(() => _selected = date),
              style: const CalendarHeatmapStyle(
                design: CalendarHeatmapDesign.ring,
                showPercentage: true,
              ),
              appearance: const CalendarAppearance(
                style: CalendarStyle.neon,
              ),
            ),
            const SizedBox(height: 20),
            SizedBox(
              height: 196,
              child: CalendarContributionHeatmap(
                startDate: DateTime(2026, 1, 1),
                dayCount: 365,
                values: demoContributionValues,
                selectedDate: _selected,
                onDateTap: (date) => setState(() => _selected = date),
                appearance: const CalendarAppearance(
                  style: CalendarStyle.aurora,
                ),
                style: const CalendarHeatmapStyle(
                  design: CalendarHeatmapDesign.pill,
                  cellExtent: 32,
                  cellSpacing: 5,
                  showLabels: false,
                ),
              ),
            ),
            const SizedBox(height: 20),
            CalendarStreakStrip(
              startDate: DateTime(2026, 8, 1),
              dayCount: 31,
              today: DateTime(2026, 8, 10),
              selectedDate: _selected,
              completedDates: demoCompletedDates,
              onDateTap: (date) => setState(() => _selected = date),
              appearance: const CalendarAppearance(style: CalendarStyle.soft),
            ),
          ],
        ),
      'selection' => Column(
          children: [
            HorizontalCalendar<DemoEvent>.controlled(
              focusedDate: _focused,
              selection: _selection,
              onFocusedDateChanged: (date) => setState(() => _focused = date),
              onSelectionChanged: (_, next) =>
                  setState(() => _selection = next),
              behavior: const CalendarBehavior(
                selectionBehavior: CalendarSelectionBehavior(
                  singleTap: CalendarSingleTapBehavior.toggle,
                ),
              ),
              events: demoEvents,
              appearance: CalendarAppearance(
                style: CalendarStyle.materialYou,
                motion: CalendarMotion.fluid(),
              ),
            ),
            const SizedBox(height: 20),
            CalendarDateCarousel<DemoEvent>.controlled(
              startDate: DateTime(2026, 8, 1),
              dayCount: 18,
              selection: _selection,
              onSelectionChanged: (_, next) =>
                  setState(() => _selection = next),
              items: demoCarouselItems,
              events: demoEvents,
              scrolling: CalendarScrollBehavior.page,
              appearance: const CalendarAppearance(
                style: CalendarStyle.aurora,
              ),
            ),
          ],
        ),
      'celestial' => CelestialDatePicker(
          value: _selected,
          onChanged: (date) => setState(() => _selected = date),
          bounds: CalendarDateRange(
            DateTime(2026, 1, 1),
            DateTime(2027, 12, 31),
          ),
          appearance: CalendarAppearance(
            style: CalendarStyle.midnight,
            motion: CalendarMotion.fluid(),
          ),
        ),
      'widgets' => Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            CalendarScheduleRibbon<DemoEvent>(
              date: DateTime(2026, 8, 5),
              intervals: demoScheduleIntervals,
              startHour: 8,
              endHour: 18,
              now: DateTime(2026, 8, 5, 11, 20),
              appearance:
                  const CalendarAppearance(style: CalendarStyle.terminal),
            ),
            const SizedBox(height: 20),
            SizedBox(
              height: 138,
              child: CalendarMilestoneTimeline<DemoEvent>(
                milestones: demoMilestones,
                currentDate: DateTime(2026, 8, 10),
                design: CalendarMilestoneDesign.roadmap,
                appearance:
                    const CalendarAppearance(style: CalendarStyle.luxury),
              ),
            ),
            const SizedBox(height: 20),
            CalendarAvailabilityStrip<DemoEvent>(
              slots: demoAvailability,
              selectedSlotId: _selectedSlot,
              onSlotSelected: (slot) => setState(() => _selectedSlot = slot.id),
              layout: CalendarAvailabilityLayout.auto,
              design: CalendarAvailabilityDesign.schedule,
              appearance: const CalendarAppearance(
                style: CalendarStyle.cupertinoTinted,
              ),
            ),
            const SizedBox(height: 20),
            CalendarCountdownCard<DemoEvent>(
              startDate: DateTime(2026, 8, 1),
              referenceDate: DateTime(2026, 8, 5),
              targetDate: DateTime(2026, 8, 18),
              title: 'Package 2.0 launch',
              data: const DemoEvent('Calendar team', 'launch'),
              appearance: const CalendarAppearance(
                style: CalendarStyle.sunset,
              ),
            ),
          ],
        ),
      'carousel' => Column(
          children: [
            CalendarDateCarousel<DemoEvent>(
              startDate: DateTime(2026, 8, 1),
              dayCount: 18,
              selectedDate: _selected,
              onDateSelected: (date) => setState(() => _selected = date),
              items: demoCarouselItems,
              events: demoEvents,
              scrolling: CalendarScrollBehavior.page,
              appearance: CalendarAppearance(
                style: CalendarStyle.aurora,
                motion: CalendarMotion.premium(),
              ),
              visualStyle: const CalendarCarouselVisualStyle(
                layout: CalendarCarouselLayout.spotlight,
                selectedScale: 1.035,
                inactiveScale: .96,
                inactiveOpacity: .76,
                spacing: 12,
                elevation: 8,
                borderRadius: 24,
              ),
            ),
            const SizedBox(height: 28),
            CalendarDateCarousel<DemoEvent>(
              startDate: DateTime(2026, 8, 1),
              dayCount: 18,
              selectedDate: _selected,
              onDateSelected: (date) => setState(() => _selected = date),
              items: demoCarouselItems,
              events: demoEvents,
              appearance:
                  const CalendarAppearance(style: CalendarStyle.editorial),
              visualStyle: const CalendarCarouselVisualStyle(
                layout: CalendarCarouselLayout.editorial,
                inactiveScale: .95,
                inactiveOpacity: .72,
                useGradient: false,
              ),
            ),
          ],
        ),
      'horizon' => CelestialDatePicker(
          value: _selected,
          onChanged: (date) => setState(() => _selected = date),
          bounds: CalendarDateRange(
            DateTime(2026, 1, 1),
            DateTime(2027, 12, 31),
          ),
          appearance: const CalendarAppearance(style: CalendarStyle.midnight),
          celestialMotion: const CelestialMotion(
            duration: Duration(milliseconds: 1100),
            arcHeight: .82,
            drift: .36,
            parallax: .5,
            rotation: .36,
            trailLength: .86,
            starTwinkle: .9,
          ),
          style: const CelestialDatePickerStyle(
            skyHeight: 280,
            skyStyle: CelestialSkyStyle.aurora,
            composition: CelestialComposition.cinematic,
            showClouds: true,
            showConstellations: true,
            showPhaseOrbit: true,
            showDateProgress: true,
          ),
        ),
      'heatmaps' => Column(
          children: [
            for (final design in CalendarHeatmapDesign.values) ...[
              Align(
                alignment: AlignmentDirectional.centerStart,
                child: Text(
                  design.name.toUpperCase(),
                  style: Theme.of(context).textTheme.labelLarge?.copyWith(
                        fontWeight: FontWeight.w800,
                        letterSpacing: 1.3,
                      ),
                ),
              ),
              const SizedBox(height: 8),
              CalendarHeatmapStrip(
                startDate: DateTime(2026, 8, 1),
                dayCount: 31,
                selectedDate: _selected,
                values: demoHeatmapValues,
                onDateTap: (date) => setState(() => _selected = date),
                style: CalendarHeatmapStyle(
                  design: design,
                  animate: true,
                  showPercentage: design == CalendarHeatmapDesign.ring,
                ),
                appearance: const CalendarAppearance(
                  style: CalendarStyle.neon,
                ),
              ),
              const SizedBox(height: 18),
            ],
            SizedBox(
              height: 196,
              child: CalendarContributionHeatmap(
                startDate: DateTime(2026, 1, 1),
                dayCount: 365,
                values: demoContributionValues,
                selectedDate: _selected,
                onDateTap: (date) => setState(() => _selected = date),
                appearance:
                    const CalendarAppearance(style: CalendarStyle.aurora),
                style: const CalendarHeatmapStyle(
                  design: CalendarHeatmapDesign.pill,
                  cellExtent: 32,
                  cellSpacing: 5,
                  showLabels: false,
                ),
              ),
            ),
          ],
        ),
      'responsive' => Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            CalendarCountdownCard<DemoEvent>(
              startDate: DateTime(2026, 8, 1),
              referenceDate: DateTime(2026, 8, 5),
              targetDate: DateTime(2026, 8, 18),
              title: 'Package of the week premiere',
              data: const DemoEvent('Calendar team', 'launch'),
              appearance: const CalendarAppearance(style: CalendarStyle.sunset),
            ),
            const SizedBox(height: 20),
            CalendarWeekProgress(
              startDate: DateTime(2026, 8, 3),
              currentDate: DateTime(2026, 8, 5),
              selectedDate: _selected,
              onDateTap: (date) => setState(() => _selected = date),
              appearance:
                  const CalendarAppearance(style: CalendarStyle.materialYou),
            ),
            const SizedBox(height: 20),
            CalendarDateRangeSummary<DemoEvent>(
              range: CalendarDateRange(
                DateTime(2026, 8, 3),
                DateTime(2026, 8, 18),
              ),
              referenceDate: DateTime(2026, 8, 9),
              title: 'Launch runway',
              data: const DemoEvent('Release team', 'range'),
              appearance: const CalendarAppearance(style: CalendarStyle.luxury),
            ),
          ],
        ),
      'home-widgets' => Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Wrap(
              alignment: WrapAlignment.center,
              spacing: 18,
              runSpacing: 18,
              children: [
                SizedBox(
                  width: 170,
                  height: 170,
                  child: CalendarHomeWidget(
                    data: demoHomeWidgetData,
                    family: CalendarHomeWidgetFamily.small,
                    content: CalendarHomeWidgetContent.today,
                    theme: const CalendarHomeWidgetTheme(
                      backgroundColor: Color(0xfffffbf3),
                      foregroundColor: Color(0xff201c17),
                      secondaryColor: Color(0xff74695c),
                      accentColor: Color(0xff9a5b32),
                      surfaceStyle: CalendarHomeWidgetSurfaceStyle.gradient,
                      gradientColors: [
                        Color(0xfffffbf3),
                        Color(0xffeee1c9),
                      ],
                      headerStyle: CalendarHomeWidgetHeaderStyle.compact,
                      cornerRadius: 32,
                    ),
                  ),
                ),
                SizedBox(
                  width: 360,
                  height: 170,
                  child: CalendarHomeWidget(
                    data: demoHomeWidgetData,
                    family: CalendarHomeWidgetFamily.medium,
                    content: CalendarHomeWidgetContent.week,
                    theme: const CalendarHomeWidgetTheme(
                      backgroundColor: Color(0xff19132f),
                      foregroundColor: Color(0xfffffaff),
                      secondaryColor: Color(0xffcabfe5),
                      accentColor: Color(0xffb8ffdc),
                      surfaceStyle: CalendarHomeWidgetSurfaceStyle.gradient,
                      gradientColors: [
                        Color(0xff19132f),
                        Color(0xff47327d),
                      ],
                      weekdayFormat: CalendarHomeWidgetWeekdayFormat.short,
                      dateShape: CalendarHomeWidgetDateShape.rounded,
                    ),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 18),
            SizedBox(
              height: 340,
              child: CalendarHomeWidget(
                data: demoHomeWidgetData,
                family: CalendarHomeWidgetFamily.large,
                content: CalendarHomeWidgetContent.agenda,
                theme: const CalendarHomeWidgetTheme(
                  backgroundColor: Color(0xff171225),
                  foregroundColor: Color(0xfffff8ed),
                  secondaryColor: Color(0xffffc9ae),
                  accentColor: Color(0xffffbc66),
                  surfaceStyle: CalendarHomeWidgetSurfaceStyle.gradient,
                  gradientColors: [
                    Color(0xff171225),
                    Color(0xff552a32),
                  ],
                  eventStyle: CalendarHomeWidgetEventStyle.card,
                  maximumEvents: 4,
                  itemSpacing: 8,
                  cornerRadius: 34,
                ),
              ),
            ),
            const SizedBox(height: 18),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                SizedBox(
                  width: 72,
                  height: 72,
                  child: CalendarHomeWidget(
                    data: demoHomeWidgetData,
                    family: CalendarHomeWidgetFamily.accessory,
                    theme: const CalendarHomeWidgetTheme(
                      backgroundColor: Color(0xff07111f),
                      accentColor: Color(0xff55d6be),
                      surfaceStyle: CalendarHomeWidgetSurfaceStyle.outlined,
                      borderColor: Color(0xff55d6be),
                      cornerRadius: 24,
                    ),
                  ),
                ),
                const SizedBox(width: 14),
                Expanded(
                  child: SizedBox(
                    height: 110,
                    child: CalendarHomeWidget(
                      data: demoHomeWidgetData,
                      family: CalendarHomeWidgetFamily.compact,
                      content: CalendarHomeWidgetContent.progress,
                      theme: const CalendarHomeWidgetTheme(
                        backgroundColor: Color(0xff07111f),
                        foregroundColor: Color(0xfff7fbff),
                        secondaryColor: Color(0xff9fb1c7),
                        accentColor: Color(0xff55d6be),
                        progressStyle:
                            CalendarHomeWidgetProgressStyle.segmented,
                        surfaceStyle: CalendarHomeWidgetSurfaceStyle.gradient,
                        gradientColors: [
                          Color(0xff07111f),
                          Color(0xff183f4d),
                        ],
                      ),
                    ),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 18),
            SizedBox(
              height: 170,
              child: CalendarHomeWidget(
                data: demoHomeWidgetData,
                family: CalendarHomeWidgetFamily.medium,
                content: CalendarHomeWidgetContent.nextEvent,
                theme: const CalendarHomeWidgetTheme(
                  backgroundColor: Color(0xff321410),
                  foregroundColor: Color(0xfffff8ed),
                  secondaryColor: Color(0xffffc9ae),
                  accentColor: Color(0xffffd166),
                  surfaceStyle: CalendarHomeWidgetSurfaceStyle.outlined,
                  borderColor: Color(0xffffd166),
                  borderWidth: 2,
                  headerStyle: CalendarHomeWidgetHeaderStyle.month,
                  eventStyle: CalendarHomeWidgetEventStyle.dot,
                  cornerRadius: 22,
                ),
              ),
            ),
          ],
        ),
      'legacy' => _legacySurface(),
      _ => _captureCalendar(CalendarStyle.bold),
    };
  }

  Widget _legacySurface() {
    const legacyStyle = legacy.HorizontalCalendarStyle(
      activeDayColor: Color(0xFF5547D7),
      dayIndicatorSize: 42,
    );
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        const _DemoLabel('HORIZONTAL WEEKLY CALENDAR · 1.x'),
        legacy.HorizontalWeeklyCalendar(
          initialDate: _focused,
          selectedDate: _selected,
          onDateSelected: (date) => setState(() => _selected = date),
          onNextMonth: () => setState(() {
            _focused = DateTime(_focused.year, _focused.month + 1, 1);
          }),
          onPreviousMonth: () => setState(() {
            _focused = DateTime(_focused.year, _focused.month - 1, 1);
          }),
          calendarType: legacy.HorizontalCalendarType.elevated,
          calendarStyle: legacyStyle,
        ),
        const SizedBox(height: 28),
        const _DemoLabel('TABLE WEEKLY CALENDAR · 1.x'),
        legacy.TableWeeklyCalendar(
          initialDate: _focused,
          selectedDate: _selected,
          onDateSelected: (date) => setState(() => _selected = date),
          onMonthChanged: (date) => setState(() => _focused = date),
          focusDates: [
            legacy.FocusDate(
              date: DateTime(2026, 8, 12),
              backgroundColor: const Color(0xFFFFD166),
              foregroundColor: const Color(0xFF191510),
            ),
          ],
          calendarStyle: legacyStyle,
        ),
        const SizedBox(height: 28),
        const _DemoLabel('EVENT CALENDAR · 1.x'),
        SizedBox(
          height: 520,
          child: legacy.EventCalendar(
            currentMonth: _focused,
            selectedDate: _selected,
            events: _legacyEvents,
            startHour: 8,
            endHour: 16,
            onDateSelected: (date) => setState(() => _selected = date),
            onNextMonth: () => setState(() {
              _focused = DateTime(_focused.year, _focused.month + 1, 1);
            }),
            onPreviousMonth: () => setState(() {
              _focused = DateTime(_focused.year, _focused.month - 1, 1);
            }),
            style: const legacy.EventCalendarStyle(
              timeSlotHeight: 48,
              hourSlotHeight: 48,
              calendarStyle: legacyStyle,
            ),
          ),
        ),
      ],
    );
  }

  Widget _captureCalendar(
    CalendarStyle style, {
    CalendarMotion? motion,
  }) {
    return HorizontalCalendar<DemoEvent>(
      selectedDate: _selected,
      onDateSelected: (date) => setState(() => _selected = date),
      events: demoEvents,
      appearance: CalendarAppearance(
        style: style,
        eventIndicatorStyle: EventIndicatorStyle.stack,
        motion: motion ?? CalendarMotion.fluid(),
      ),
    );
  }
}

class DemoEvent {
  const DemoEvent(this.owner, this.category);

  final String owner;
  final String category;
}

class _HeroTag extends StatelessWidget {
  const _HeroTag(this.label);

  final String label;

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: BoxDecoration(
        color: Colors.white.withValues(alpha: .1),
        borderRadius: BorderRadius.circular(99),
        border: Border.all(color: Colors.white24),
      ),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 11, vertical: 7),
        child: Text(
          label,
          style: const TextStyle(
            color: Colors.white,
            fontSize: 12,
            fontWeight: FontWeight.w700,
          ),
        ),
      ),
    );
  }
}

class _DemoLabel extends StatelessWidget {
  const _DemoLabel(this.label);

  final String label;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Text(
        label,
        style: Theme.of(context).textTheme.labelSmall?.copyWith(
              fontWeight: FontWeight.w800,
              letterSpacing: 1.2,
            ),
      ),
    );
  }
}

final _legacyEvents = <legacy.CalendarEvent>[
  legacy.CalendarEvent(
    id: 'legacy-planning',
    title: 'Planning',
    startTime: DateTime(2026, 8, 5, 9),
    endTime: DateTime(2026, 8, 5, 10, 30),
    backgroundColor: const Color(0xFF5547D7),
  ),
  legacy.CalendarEvent(
    id: 'legacy-review',
    title: 'Review',
    startTime: DateTime(2026, 8, 6, 12),
    endTime: DateTime(2026, 8, 6, 13),
    backgroundColor: const Color(0xFF00A896),
  ),
];

final demoCarouselItems = <CalendarCarouselItem<DemoEvent>>[
  CalendarCarouselItem(
    date: DateTime(2026, 8, 4),
    title: 'Launch plan',
    subtitle: 'Product studio',
    badge: '2',
    data: const DemoEvent('Maya', 'Product'),
  ),
  CalendarCarouselItem(
    date: DateTime(2026, 8, 5),
    title: 'Design day',
    subtitle: 'Reviews and research',
    badge: '3',
    data: const DemoEvent('Noah', 'Design'),
  ),
  CalendarCarouselItem(
    date: DateTime(2026, 8, 7),
    title: 'Ship night',
    subtitle: 'Production deploy',
    badge: 'LIVE',
    data: const DemoEvent('Omar', 'Engineering'),
  ),
];

final demoHeatmapValues = <DateTime, double>{
  for (var day = 1; day <= 31; day += 1)
    DateTime(2026, 8, day): ((day * 37) % 100) / 100,
};

final demoContributionValues = <DateTime, double>{
  for (var day = 0; day < 365; day += 1)
    DateTime(2026, 1, 1 + day): ((day * 29 + (day ~/ 7) * 17) % 100) / 100,
};

final demoCompletedDates = <DateTime>{
  for (final day in [1, 2, 3, 5, 6, 7, 8, 10]) DateTime(2026, 8, day),
};

final demoScheduleIntervals = <CalendarScheduleInterval<DemoEvent>>[
  CalendarScheduleInterval(
    id: 'standup',
    title: 'Stand-up',
    start: DateTime(2026, 8, 5, 9),
    end: DateTime(2026, 8, 5, 9, 45),
    color: const Color(0xFF6857E5),
    data: const DemoEvent('Core team', 'Engineering'),
  ),
  CalendarScheduleInterval(
    id: 'research',
    title: 'Research calls',
    start: DateTime(2026, 8, 5, 9, 20),
    end: DateTime(2026, 8, 5, 11, 30),
    color: const Color(0xFF008E87),
    data: const DemoEvent('Lina', 'Research'),
  ),
  CalendarScheduleInterval(
    id: 'prototype',
    title: 'Prototype',
    start: DateTime(2026, 8, 5, 13),
    end: DateTime(2026, 8, 5, 16),
    color: const Color(0xFFE34B87),
    data: const DemoEvent('Noah', 'Design'),
  ),
];

final demoInsightMetrics = <CalendarInsightMetric<DemoEvent>>[
  CalendarInsightMetric(
    id: 'focus',
    label: 'Focus time',
    value: '14.5h',
    supportingText: '2.5h above last week',
    trend: CalendarInsightTrend.up,
    progress: .72,
    icon: Icons.bolt_rounded,
    data: const DemoEvent('Focus', 'insight-focus'),
  ),
  CalendarInsightMetric(
    id: 'streak',
    label: 'Active streak',
    value: '12 days',
    supportingText: 'Your longest this quarter',
    trend: CalendarInsightTrend.steady,
    progress: .86,
    icon: Icons.local_fire_department_rounded,
    accentColor: Color(0xffff8a65),
    data: const DemoEvent('Streak', 'insight-streak'),
  ),
  CalendarInsightMetric(
    id: 'open',
    label: 'Open windows',
    value: '6',
    supportingText: 'Across the next seven days',
    trend: CalendarInsightTrend.down,
    icon: Icons.timelapse_rounded,
    accentColor: Color(0xff4dd0e1),
    data: const DemoEvent('Availability', 'insight-open'),
  ),
  CalendarInsightMetric(
    id: 'balance',
    label: 'Meeting balance',
    value: '68%',
    supportingText: 'Two focus-first days protected',
    trend: CalendarInsightTrend.up,
    progress: .68,
    icon: Icons.balance_rounded,
    accentColor: Color(0xff9575cd),
    data: const DemoEvent('Balance', 'insight-balance'),
  ),
];

final demoHomeWidgetData = CalendarHomeWidgetData(
  generatedAt: DateTime(2026, 8, 10, 8),
  selectedDate: DateTime(2026, 8, 10),
  title: 'Creative week',
  subtitle: 'Three focused days ahead',
  targetDate: DateTime(2026, 8, 18),
  completedCount: 5,
  totalCount: 7,
  events: [
    CalendarHomeWidgetEvent(
      id: 'design-review',
      title: 'Design review',
      start: DateTime(2026, 8, 10, 9),
      end: DateTime(2026, 8, 10, 10),
      location: 'Studio',
      colorValue: 0xff9f8cff,
    ),
    CalendarHomeWidgetEvent(
      id: 'release-sync',
      title: 'Release sync',
      start: DateTime(2026, 8, 10, 11, 30),
      end: DateTime(2026, 8, 10, 12),
      location: 'Product room',
      colorValue: 0xffffbc66,
    ),
    CalendarHomeWidgetEvent(
      id: 'deep-work',
      title: 'Deep work',
      start: DateTime(2026, 8, 10, 14),
      end: DateTime(2026, 8, 10, 16),
      colorValue: 0xff4dd0e1,
    ),
  ],
  action: const CalendarHomeWidgetAction(
    uri: 'calendar-example://day/2026-08-10',
    label: 'Open August 10',
  ),
);

final demoMilestones = <CalendarMilestone<DemoEvent>>[
  CalendarMilestone(
    id: 'research-complete',
    date: DateTime(2026, 8, 3),
    title: 'Research complete',
    subtitle: '42 interviews',
    data: const DemoEvent('Lina', 'Research'),
  ),
  CalendarMilestone(
    id: 'beta',
    date: DateTime(2026, 8, 10),
    title: 'Private beta',
    subtitle: 'Today',
    data: const DemoEvent('Maya', 'Product'),
  ),
  CalendarMilestone(
    id: 'launch',
    date: DateTime(2026, 8, 24),
    title: 'Public launch',
    subtitle: 'All platforms',
    data: const DemoEvent('Core team', 'Product'),
  ),
];

final demoAvailability = <CalendarAvailabilitySlot<DemoEvent>>[
  CalendarAvailabilitySlot(
    id: 'slot-0900',
    start: DateTime(2026, 8, 5, 9),
    end: DateTime(2026, 8, 5, 9, 30),
    label: '9:00',
    data: const DemoEvent('Studio A', 'Booking'),
  ),
  CalendarAvailabilitySlot(
    id: 'slot-1030',
    start: DateTime(2026, 8, 5, 10, 30),
    end: DateTime(2026, 8, 5, 11),
    state: CalendarAvailabilityState.limited,
    label: '10:30 · 1 left',
    data: const DemoEvent('Studio B', 'Booking'),
  ),
  CalendarAvailabilitySlot(
    id: 'slot-1200',
    start: DateTime(2026, 8, 5, 12),
    end: DateTime(2026, 8, 5, 12, 30),
    state: CalendarAvailabilityState.unavailable,
    label: '12:00 · full',
    data: const DemoEvent('Studio A', 'Booking'),
  ),
  CalendarAvailabilitySlot(
    id: 'slot-1430',
    start: DateTime(2026, 8, 5, 14, 30),
    end: DateTime(2026, 8, 5, 15),
    label: '14:30',
    data: const DemoEvent('Studio C', 'Booking'),
  ),
];

class _GalleryDay extends StatelessWidget {
  const _GalleryDay({required this.state});

  final CalendarDayState<DemoEvent> state;

  @override
  Widget build(BuildContext context) {
    final scheme = Theme.of(context).colorScheme;
    return AnimatedContainer(
      duration: MediaQuery.disableAnimationsOf(context)
          ? Duration.zero
          : const Duration(milliseconds: 180),
      decoration: BoxDecoration(
        color: state.isSelected ? scheme.primary : scheme.surfaceContainerHigh,
        borderRadius: BorderRadius.circular(18),
      ),
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
      child: FittedBox(
        fit: BoxFit.scaleDown,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              '${state.date.day}',
              style: TextStyle(
                color: state.isSelected ? scheme.onPrimary : scheme.onSurface,
                fontSize: 20,
                fontWeight: FontWeight.w900,
              ),
            ),
            if (state.eventCount > 0)
              Text(
                '${state.eventCount} event${state.eventCount == 1 ? '' : 's'}',
                style: TextStyle(
                  color: state.isSelected
                      ? scheme.onPrimary
                      : scheme.onSurfaceVariant,
                  fontSize: 10,
                ),
              ),
          ],
        ),
      ),
    );
  }
}

final demoEvents = <CalendarEvent<DemoEvent>>[
  CalendarEvent(
    id: 'launch-plan',
    title: 'Launch planning',
    semanticLabel: 'Launch planning with the product team',
    start: DateTime(2026, 8, 4, 9),
    end: DateTime(2026, 8, 4, 10, 30),
    color: const Color(0xFF6857E5),
    data: const DemoEvent('Maya', 'Product'),
  ),
  CalendarEvent(
    id: 'design-review',
    title: 'Design review',
    start: DateTime(2026, 8, 5, 10),
    end: DateTime(2026, 8, 5, 12),
    color: const Color(0xFFE34B87),
    data: const DemoEvent('Noah', 'Design'),
  ),
  CalendarEvent(
    id: 'customer-call',
    title: 'Customer call',
    start: DateTime(2026, 8, 5, 10, 30),
    end: DateTime(2026, 8, 5, 11, 30),
    color: const Color(0xFF008E87),
    data: const DemoEvent('Lina', 'Research'),
  ),
  CalendarEvent(
    id: 'build-sprint',
    title: 'Build sprint',
    start: DateTime(2026, 8, 6),
    end: DateTime(2026, 8, 8),
    isAllDay: true,
    color: const Color(0xFFF29C38),
    data: const DemoEvent('Core team', 'Engineering'),
  ),
  CalendarEvent(
    id: 'night-deploy',
    title: 'Production deploy',
    start: DateTime(2026, 8, 7, 23),
    end: DateTime(2026, 8, 8, 1),
    color: const Color(0xFF4385F5),
    data: const DemoEvent('Omar', 'Engineering'),
  ),
];
31
likes
160
points
241
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A beautiful, reliable, and fully customizable horizontal calendar UI kit for Flutter, with events, agendas, timelines, and foldable month views.

Repository (GitHub)
View/report issues

Topics

#calendar #horizontal-calendar #schedule #events #date-picker

License

MIT (license)

Dependencies

flutter, intl

More

Packages that depend on horizontal_weekly_calendar