WoltModalSheet
repository·main·Indexed 20 days ago
https://github.com/woltapp/wolt_modal_sheetA high-quality Flutter library for creating advanced, multi-page modal sheets with polished animations and responsive layouts. It supports various modal types including bottom sheets, dialogs, side sheets, and alert dialogs, and provides flexible navigation patterns and theme customization via WoltModalSheetThemeData.
What's inside wolt_modal_sheet
- WoltModalSheet is a Flutter UI component designed for high-quality, customizable modal sheets. It is used extensively in Wolt products and supports features like multi-page layouts, smooth motion for page transitions, scrollable content within pages, and responsive design across different screen sizes. It supports both imperative and declarative navigation and integrates easily with various state management solutions.
Use the playground to test WoltModalSheet use cases
mainTheplaygrounddirectory contains a Flutter application designed to demonstrate and test various use cases for theWoltModalSheetbottom sheet. It serves as a sandbox for exploring different implementation patterns.Implement dynamic navigation with ValueNotifiers
mainFor more flexible or declarative navigation, you can use
ValueNotifierto control the modal state.Navigation by Page Index
Use a
ValueNotifier<int> pageIndexNotifierto control which page is currently visible. This is ideal for simple state-driven transitions.Declarative Page List Management
Use a
ValueNotifier<WoltModalSheetPageListBuilder> pageListBuilderNotifierto manage the entire page stack dynamically. This is suitable for Navigator 2.0-style declarative navigation.Note: Do not mix
pageListBuilderNotifierwith imperative navigation methods (likeshowNext()).When using the dynamic path approach, use
WoltModalSheet.showWithDynamicPathinstead of the standardshowmethod.// 1. Navigation by Page Index final pageIndexNotifier = ValueNotifier(0); WoltModalSheet.show( context: context, pageListBuilder: (modalSheetContext) => [ PageOne(onNextButtonPressed: () => pageIndexNotifier.value = 1), PageTwo(onBackButtonPressed: () => pageIndexNotifier.value = pageIndexNotifier.value - 1), ], pageIndexNotifier: pageIndexNotifier, ); // 2. Dynamic Page List (Declarative) final pageIndexNotifier = ValueNotifier(0); final pageListBuilderNotifier = ValueNotifier((context) => [ PageOne(onNextButtonPressed: () => pageIndexNotifier.value++), PageTwo(onBackButtonPressed: () => pageIndexNotifier.value--), ]); WoltModalSheet.showWithDynamicPath( context: context, pageListBuilderNotifier: pageListBuilderNotifier, pageIndexNotifier: pageIndexNotifier, );Understand the WoltModalSheet layer architecture
mainWoltModalSheet organizes its UI elements across several layers on the z-axis to create a cohesive interactive experience. Understanding these layers is key to designing your modal content:
- Navigation Bar Layer: The topmost transparent layer. It contains navigational widgets like the
leadingwidget (typically a back button) and thetrailingwidget (typically a close button). The middle area is reserved for the top bar title. - Top Bar Layer: Sits below the navigation bar. It displays the
topBarTitleand provides context. It can be configured to be sticky or hidden based on scroll position. - Sticky Action Bar (SAB) Layer: Positioned at the top of the z-axis (anchored to the bottom of the view). It guides users to the next step and often includes a subtle gradient to hint at scrollable content below.
- Main Content Layer: The base layer containing the
heroImage,pageTitle, and the scrollablemainContent.
- Navigation Bar Layer: The topmost transparent layer. It contains navigational widgets like the
Difference between pageContentDecorator and modalDecorator
mainPage Content Decoration
- Purpose: Applies decorations to the modal page content only, excluding the barrier.
- Signature:
Widget Function(Widget)? pageContentDecorator; - Use Case: Modifying the appearance of the content without affecting the surrounding barrier or modal placement.
Modal Decoration
- Purpose: Applies decorations to the entire modal, including the barrier and the page content.
- Signature:
Widget Function(Widget)? modalDecorator; - Use Case: Wrapping the entire modal with a widget that manages state (e.g.,
ChangeNotifierProvider) to ensure state is accessible throughout the entire modal lifecycle and all its pages.
Understand ValueState
mainValueState<T>is a sealed class representing the current state of a value of typeT. It can be in one of three states:- Idle: The value is successfully loaded or initialized. Use
ValueState.idle(value). - Loading: The value is being processed. Use
ValueState.loading({T? lastKnownValue}). You can optionally provide thelastKnownValueto retain the previous state during loading. - Error: An error occurred. Use
ValueState.error(Exception error, [T? lastKnownValue]). You can optionally provide thelastKnownValueto show the previous data alongside the error.
// Idle ValueState<int> state = ValueState.idle(42); // Loading ValueState<int> loadingState = ValueState.loading(lastKnownValue: 42); // Error ValueState<int> errorState = ValueState.error(Exception('An error occurred'), 42);- Idle: The value is successfully loaded or initialized. Use
Decorate modal types, modals, and pages
mainWoltModalSheet uses the decorator pattern to allow dynamic addition of behavior to objects. You can apply decorations at two levels:
- Modal Type Level: Applied to all modals of a specific type (e.g., all bottom sheets). This is achieved by extending a
WoltModalTypeclass and overridingdecoratePageContentanddecorateModal. - Modal Level: Applied specifically when calling
WoltModalSheet.show. This is done using thepageContentDecoratorandmodalDecoratorproperties.
class MyCustomBottomSheetType extends WoltBottomSheetType { const MyCustomBottomSheetType() : super(); @override Widget decoratePageContent(BuildContext context, Widget child, bool useSafeArea) { return Padding( padding: const EdgeInsets.all(16.0), child: child, ); } @override Widget decorateModal(BuildContext context, Widget modal, bool useSafeArea) { return useSafeArea ? SafeArea(child: modal) : modal; } }- Modal Type Level: Applied to all modals of a specific type (e.g., all bottom sheets). This is achieved by extending a
Getting started with the playground app
mainThe playground is a standard Flutter project. To run it, ensure you have the Flutter SDK installed and follow the standard Flutter development workflow (e.g.,
flutter run).If you are new to Flutter, refer to the official Flutter resources:
WoltModalSheet design guidelines and breakpoints
mainThe modal sheet component adapts its layout based on the screen width according to these breakpoints:
- Breakpoint Large: Width $\ge$ 1400px
- Breakpoint Medium: 768px $\le$ Width < 1400px
- Breakpoint Small: 524px $\le$ Width < 768px
- Breakpoint XSmall: Width < 524px
Modal Types and Usage
- Alert Dialog: For critical information requiring immediate attention. Must be dismissed by user interaction.
- Dialog: For single user actions or state change info (success/errors). Recommended for Small, Medium, and Large window sizes.
- Side Sheet: To focus attention on a task while keeping context visible. Recommended for Small, Medium, and Large window sizes.
- Bottom Sheet: For additional options without leaving context. Recommended ONLY for XSmall window sizes. (In XSmall, Side Sheet and Dialog content should be shown as a Bottom Sheet).
Add the Wolt State Management package to your project
mainSince this is an internal package, add it to your
pubspec.yamlusing a local path:dependencies: wolt_state_management: path: ./packages/state_managementImplement responsive Modal Types
mainYou can make your modal sheet responsive by using the
modalTypeBuilderinWoltModalSheet.show. This allows you to return different modal types based on the current screen width or other device conditions.WoltModalSheet.show( context: context, modalTypeBuilder: (BuildContext context) { final width = MediaQuery.sizeOf(context).width; if (width < 523) { return WoltModalType.bottomSheet(); } else if (width < 800) { return WoltModalType.dialog(); } else { return WoltModalType.sideSheet(); } }, // ... other parameters );Customize existing Modal Types
mainYou can customize built-in modal types in two ways:
- Extending the class: Create a new class that extends a specific type (e.g.,
WoltBottomSheetType) to set permanent default properties likeshapeBorder,showDragHandle, orbarrierDismissible. - Using
copyWith: Use thecopyWithmethod on an existing modal type instance to override specific properties during theWoltModalSheet.showcall.
// Option 1: Extending the class class MyCustomBottomSheetType extends WoltBottomSheetType { const MyCustomBottomSheetType() : super( shapeBorder: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(24))), showDragHandle: false, barrierDismissible: false, ); } // Option 2: Using copyWith WoltModalSheet.show( context: context, modalTypeBuilder: (_) => WoltModalType.bottomSheet().copyWith(barrierDismissible: false), );- Extending the class: Create a new class that extends a specific type (e.g.,