WoltModalSheet

repository·main·Indexed 20 days ago

https://github.com/woltapp/wolt_modal_sheet

A 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.

Tokens
9.9K
Snippets
27
Records
37
Agent score
69%

What's inside wolt_modal_sheet

  1. Overview of WoltModalSheet

    main
    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.
  2. Implement dynamic navigation with ValueNotifiers

    main

    For more flexible or declarative navigation, you can use ValueNotifier to control the modal state.

    Use a ValueNotifier<int> pageIndexNotifier to control which page is currently visible. This is ideal for simple state-driven transitions.

    Declarative Page List Management

    Use a ValueNotifier<WoltModalSheetPageListBuilder> pageListBuilderNotifier to manage the entire page stack dynamically. This is suitable for Navigator 2.0-style declarative navigation.

    Note: Do not mix pageListBuilderNotifier with imperative navigation methods (like showNext()).

    When using the dynamic path approach, use WoltModalSheet.showWithDynamicPath instead of the standard show method.

    // 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,
    );
  3. Understand the WoltModalSheet layer architecture

    main

    WoltModalSheet 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:

    1. Navigation Bar Layer: The topmost transparent layer. It contains navigational widgets like the leading widget (typically a back button) and the trailing widget (typically a close button). The middle area is reserved for the top bar title.
    2. Top Bar Layer: Sits below the navigation bar. It displays the topBarTitle and provides context. It can be configured to be sticky or hidden based on scroll position.
    3. 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.
    4. Main Content Layer: The base layer containing the heroImage, pageTitle, and the scrollable mainContent.
  4. Difference between pageContentDecorator and modalDecorator

    main

    Page 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.
    • 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.
  5. Understand ValueState

    main

    ValueState<T> is a sealed class representing the current state of a value of type T. 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 the lastKnownValue to retain the previous state during loading.
    • Error: An error occurred. Use ValueState.error(Exception error, [T? lastKnownValue]). You can optionally provide the lastKnownValue to 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);
  6. Decorate modal types, modals, and pages

    main

    WoltModalSheet uses the decorator pattern to allow dynamic addition of behavior to objects. You can apply decorations at two levels:

    1. Modal Type Level: Applied to all modals of a specific type (e.g., all bottom sheets). This is achieved by extending a WoltModalType class and overriding decoratePageContent and decorateModal.
    2. Modal Level: Applied specifically when calling WoltModalSheet.show. This is done using the pageContentDecorator and modalDecorator properties.
    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;
      }
    }
  7. WoltModalSheet design guidelines and breakpoints

    main

    The 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
    • 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).
  8. Implement responsive Modal Types

    main

    You can make your modal sheet responsive by using the modalTypeBuilder in WoltModalSheet.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
    );
  9. Customize existing Modal Types

    main

    You can customize built-in modal types in two ways:

    1. Extending the class: Create a new class that extends a specific type (e.g., WoltBottomSheetType) to set permanent default properties like shapeBorder, showDragHandle, or barrierDismissible.
    2. Using copyWith: Use the copyWith method on an existing modal type instance to override specific properties during the WoltModalSheet.show call.
    // 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),
    );