feat(ui): add StreamSheet, StreamSheetRoute and StreamSheetTheme#109
feat(ui): add StreamSheet, StreamSheetRoute and StreamSheetTheme#109
Conversation
Adds a Stream-styled modal bottom sheet primitive with scroll-aware drag-to-dismiss and stacking support, plus its theme: - `StreamSheet` and `StreamSheetDragHandle` widgets - `StreamSheetRoute` and `StreamSheetTransition` for the modal route and its transition (including stacked-sheet behaviour) - `showStreamSheet` helper for opening the sheet - `StreamSheetTheme`/`StreamSheetThemeData` exposed via `StreamTheme.sheetTheme` and `BuildContext.streamSheetTheme` Made-with: Cursor
When the header is rendered inside a `StreamSheetRoute` (directly or inside its nested navigator), the auto-implied leading button now adapts to the sheet's stacking and nesting state: - A root sheet's first content shows a close cross that dismisses the whole sheet. - A stacked sheet's first content shows a back chevron that pops back to the parent sheet. - Deeper nested routes inside a sheet show a back chevron that pops one level inside the sheet. Also adds a `primary` flag that controls the header's own `SafeArea(top)`, so it doesn't double-pad the system inset when sitting on a surface that already consumed it. Made-with: Cursor
`StreamEmojiPickerSheet.show` now resolves its background color and border radius from the ambient `StreamSheetTheme` (falling back to `backgroundElevation1` and a top `xxxxl` radius) so the picker matches other Stream-styled sheets out of the box. Snap-to-half drag behaviour is preserved. Made-with: Cursor
- Adds a Widgetbook usecase for `StreamSheet` showcasing the modal route, drag-to-dismiss, and stacked sheets. - Migrates the existing `StreamSheetHeader` bottom-sheet demo from `showModalBottomSheet` to `showStreamSheet` so it presents on the Stream-styled sheet route. Made-with: Cursor
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds a full Stream-styled modal sheet system: new showStreamSheet API, StreamSheet and StreamSheetRoute (with stacked sheets, drag-to-dismiss, scroll-aware gestures), StreamSheetTheme, header updates, gallery Widgetbook entries, and extensive tests. Changes
Sequence Diagram(s)sequenceDiagram
participant App
participant Navigator
participant StreamSheetRoute
participant Theme
participant GestureDetector
participant Scrollable
App->>Navigator: showStreamSheet(builder, settings...)
Navigator->>StreamSheetRoute: push route instance
StreamSheetRoute->>Theme: resolve StreamSheetThemeData
StreamSheetRoute->>GestureDetector: attach drag handlers
StreamSheetRoute->>Scrollable: provide ScrollController to builder
alt User drags down
GestureDetector->>Scrollable: isAtTop?
alt at top
GestureDetector->>StreamSheetRoute: compute velocity -> close?
StreamSheetRoute->>Navigator: pop(result)
else
Scrollable->>Scrollable: consume scroll
end
else User taps barrier
alt isDismissible
GestureDetector->>Navigator: pop()
end
end
sequenceDiagram
participant App
participant StreamSheetRoute as Sheet
participant NestedNavigator
participant InnerRoute
App->>Sheet: showStreamSheet(useNestedNavigation: true)
Sheet->>NestedNavigator: embed nested Navigator
NestedNavigator->>Sheet: render root content
App->>NestedNavigator: push InnerRoute
InnerRoute->>NestedNavigator: render inner content
alt Tap header close in inner route
InnerRoute->>Sheet: StreamSheetRoute.popSheet() -- dismiss whole sheet
else Tap inner back
InnerRoute->>NestedNavigator: pop() -- only inner route
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 29 minutes and 13 seconds.Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
apps/design_system_gallery/lib/app/gallery_app.directories.g.dart (1)
1119-1121: Use-case naming should follow the component convention.
Showcaseshould be renamed to the convention-backed label (e.g.,Type/Size VariantsorReal-world Example) in the source use-case annotation, then regenerate this file.As per coding guidelines, "Component use cases should follow the naming convention: 1) Playground (interactive with knobs), 2) Type/Size Variants (all variants), 3) Real-world Example (contextual usage)."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/design_system_gallery/lib/app/gallery_app.directories.g.dart` around lines 1119 - 1121, The generated entry uses the label 'Showcase' and must follow the component use-case naming convention; open the original source use-case annotation that registers the builder _design_system_gallery_components_sheet_stream_sheet.buildStreamSheetShowcase, change the annotated name from 'Showcase' to the appropriate convention-backed label (e.g., 'Type/Size Variants' or 'Real-world Example' as applicable), save and then regenerate the gallery_app.directories.g.dart so the name value is updated from 'Showcase' to the chosen convention-compliant label.apps/design_system_gallery/lib/components/sheet/stream_sheet.dart (2)
253-259: UseforegroundDecorationfor the card border.Using
borderinsidedecorationis the pattern the gallery guidelines explicitly avoid here because it can interact badly with clipping/radius. Keep the fill/background indecorationand move the border toforegroundDecoration.Suggested fix
Container( decoration: BoxDecoration( color: colorScheme.backgroundSurface, borderRadius: BorderRadius.all(radius.lg), - border: Border.all(color: colorScheme.borderSubtle), + ), + foregroundDecoration: BoxDecoration( + borderRadius: BorderRadius.all(radius.lg), + border: Border.all(color: colorScheme.borderSubtle), ), padding: EdgeInsets.all(spacing.md),As per coding guidelines, "Use
foregroundDecorationfor borders to prevent clipping issues, rather than usingborderproperty inBoxDecoration".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/design_system_gallery/lib/components/sheet/stream_sheet.dart` around lines 253 - 259, The Container currently places the border inside its BoxDecoration; move that border to the Container's foregroundDecoration to avoid clipping issues: keep colorScheme.backgroundSurface and borderRadius (radius.lg) in decoration, remove the border from that BoxDecoration, and add a foregroundDecoration: BoxDecoration(border: Border.all(color: colorScheme.borderSubtle), borderRadius: BorderRadius.all(radius.lg)) so the visible stroke is drawn in the foreground while background and radius remain in decoration.
10-14: Align the Widgetbook entries with the gallery taxonomy.These use cases won’t group/sort with the rest of the component gallery as-is: the path should use the component name (
[Components]/StreamSheet), andShowcaseshould use one of the prescribed names such asReal-world ExampleorType/Size Variants.As per coding guidelines, "Component use cases should follow the naming convention: 1) Playground (interactive with knobs), 2) Type/Size Variants (all variants), 3) Real-world Example (contextual usage)" and "Use the
@widgetbook.UseCaseannotation withpath: '[Components]/ComponentName'for component use cases to ensure proper categorization".Also applies to: 75-79
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/design_system_gallery/lib/components/sheet/stream_sheet.dart` around lines 10 - 14, Update the `@widgetbook.UseCase` annotations for StreamSheetRoute so the path uses the component name and follows gallery taxonomy: change path: '[Components]/Sheet' to '[Components]/StreamSheet' and ensure use case names follow the convention (e.g., keep 'Playground' for interactive knobs, rename any "Showcase" use cases to one of the prescribed names such as 'Real-world Example' or 'Type/Size Variants'); apply the same fix to the other related `@widgetbook.UseCase` entries referenced around the StreamSheetRoute (the entries noted in the comment) so all StreamSheet use cases are consistently categorized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/stream_core_flutter/lib/src/components.dart`:
- Line 59: Replace the unnecessary lambda `(context) => pushOnce(context)` with
the tearoff `pushOnce` where the callback is assigned (the onPushed parameter)
to satisfy the analyzer; locate the usage in the component that currently passes
the lambda and change the argument to just `pushOnce` so the signature matches
`Future<Object?> Function(BuildContext)` without creating an extra closure.
In `@packages/stream_core_flutter/lib/src/components/sheet/stream_sheet.dart`:
- Around line 1506-1508: The clipBehavior getter in StreamSheet (the override of
get clipBehavior) currently returns Clip.none which contradicts the documented
rounded-sheet contract and StreamSheetThemeData; change the getter to return
Clip.antiAlias so child content is clipped to the rounded top corners (update
the override in the StreamSheet class to return Clip.antiAlias and ensure any
related documentation/comments remain consistent with StreamSheetThemeData).
- Around line 1524-1526: The current override of the constraints getter
(constraints => const BoxConstraints(maxWidth: 640)) forces a 640px max width
for every sheet; remove this hardcoded fallback so sheets remain full-width by
default and only apply constraints when explicitly provided—either delete the
override entirely or make the getter return null/a nullable value and wire it to
an optional constructor property (e.g., a nullable BoxConstraints field) so
callers opt-in to constrained width; update any usages of the constraints getter
to handle the nullable case accordingly.
In `@packages/stream_core_flutter/test/components/sheet/stream_sheet_test.dart`:
- Around line 409-412: The lambda passed to _SheetLauncher's onPushed is
redundant; replace onPushed: (context) => pushOnce(context) with a tear-off by
passing pushOnce directly (i.e., onPushed: pushOnce) in the
_withStreamTheme(_SheetLauncher(...)) call so dart analyze no longer reports
unnecessary_lambdas.
---
Nitpick comments:
In `@apps/design_system_gallery/lib/app/gallery_app.directories.g.dart`:
- Around line 1119-1121: The generated entry uses the label 'Showcase' and must
follow the component use-case naming convention; open the original source
use-case annotation that registers the builder
_design_system_gallery_components_sheet_stream_sheet.buildStreamSheetShowcase,
change the annotated name from 'Showcase' to the appropriate convention-backed
label (e.g., 'Type/Size Variants' or 'Real-world Example' as applicable), save
and then regenerate the gallery_app.directories.g.dart so the name value is
updated from 'Showcase' to the chosen convention-compliant label.
In `@apps/design_system_gallery/lib/components/sheet/stream_sheet.dart`:
- Around line 253-259: The Container currently places the border inside its
BoxDecoration; move that border to the Container's foregroundDecoration to avoid
clipping issues: keep colorScheme.backgroundSurface and borderRadius (radius.lg)
in decoration, remove the border from that BoxDecoration, and add a
foregroundDecoration: BoxDecoration(border: Border.all(color:
colorScheme.borderSubtle), borderRadius: BorderRadius.all(radius.lg)) so the
visible stroke is drawn in the foreground while background and radius remain in
decoration.
- Around line 10-14: Update the `@widgetbook.UseCase` annotations for
StreamSheetRoute so the path uses the component name and follows gallery
taxonomy: change path: '[Components]/Sheet' to '[Components]/StreamSheet' and
ensure use case names follow the convention (e.g., keep 'Playground' for
interactive knobs, rename any "Showcase" use cases to one of the prescribed
names such as 'Real-world Example' or 'Type/Size Variants'); apply the same fix
to the other related `@widgetbook.UseCase` entries referenced around the
StreamSheetRoute (the entries noted in the comment) so all StreamSheet use cases
are consistently categorized.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6cbdd6d0-10df-47a2-aba5-18b2b1e10421
📒 Files selected for processing (16)
apps/design_system_gallery/lib/app/gallery_app.directories.g.dartapps/design_system_gallery/lib/components/header/stream_sheet_header.dartapps/design_system_gallery/lib/components/sheet/stream_sheet.dartpackages/stream_core_flutter/CHANGELOG.mdpackages/stream_core_flutter/lib/src/components.dartpackages/stream_core_flutter/lib/src/components/emoji/stream_emoji_picker_sheet.dartpackages/stream_core_flutter/lib/src/components/header/stream_sheet_header.dartpackages/stream_core_flutter/lib/src/components/sheet/stream_sheet.dartpackages/stream_core_flutter/lib/src/theme.dartpackages/stream_core_flutter/lib/src/theme/components/stream_sheet_theme.dartpackages/stream_core_flutter/lib/src/theme/components/stream_sheet_theme.g.theme.dartpackages/stream_core_flutter/lib/src/theme/stream_theme.dartpackages/stream_core_flutter/lib/src/theme/stream_theme.g.theme.dartpackages/stream_core_flutter/lib/src/theme/stream_theme_extensions.dartpackages/stream_core_flutter/test/components/header/stream_sheet_header_test.dartpackages/stream_core_flutter/test/components/sheet/stream_sheet_test.dart
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #109 +/- ##
==========================================
+ Coverage 26.08% 30.24% +4.15%
==========================================
Files 159 161 +2
Lines 5807 6230 +423
==========================================
+ Hits 1515 1884 +369
- Misses 4292 4346 +54 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/stream_core_flutter/lib/src/components/sheet/stream_sheet.dart`:
- Around line 1033-1037: The ShapeDecoration block uses an invalid Dart
expression for RoundedSuperellipseBorder's borderRadius: replace the incorrect
".all(radius.max)" with a proper BorderRadius construction so the property reads
BorderRadius.all(radius.max); locate the ShapeDecoration where
RoundedSuperellipseBorder is instantiated (around the StreamSheet component) and
change the borderRadius value to use BorderRadius.all(radius.max) to fix the
syntax error.
- Around line 1025-1030: The tap handler currently calls
Navigator.of(context).maybePop which can target a nested navigator; change it to
dismiss the enclosing sheet route instead by using the sheet-route helper (e.g.
call StreamSheetRoute.of(context)?.maybePop() or an equivalent static helper on
StreamSheetRoute) so the drag-handle always dismisses the enclosing sheet;
update the onTap in the Semantics widget (currently using
Navigator.of(context).maybePop) to call that enclosing-sheet helper.
- Around line 779-781: The public StreamSheetRoute exposes a barrierColor getter
that currently falls back to Colors.transparent via _barrierColor, which
conflicts with showStreamSheet's documented default of
StreamColorScheme.backgroundScrim; fix this by resolving the actual default at
construction time or by changing the getter to return _barrierColor ??
StreamColorScheme.backgroundScrim and update the constructor docs accordingly so
direct instantiation of StreamSheetRoute uses the same scrim color as
showStreamSheet (refer to StreamSheetRoute, barrierColor getter, _barrierColor,
showStreamSheet, and StreamColorScheme.backgroundScrim).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: cfccffab-97aa-4ae6-bb86-c4fc7743eac0
📒 Files selected for processing (3)
packages/stream_core_flutter/lib/src/components/sheet/stream_sheet.dartpackages/stream_core_flutter/lib/src/theme/components/stream_sheet_theme.dartpackages/stream_core_flutter/test/components/sheet/stream_sheet_test.dart
✅ Files skipped from review due to trivial changes (1)
- packages/stream_core_flutter/test/components/sheet/stream_sheet_test.dart
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/stream_core_flutter/lib/src/theme/components/stream_sheet_theme.dart
… behavior in StreamSheet
Description of the pull request
Adds a Stream-styled modal bottom sheet primitive with scroll-aware
drag-to-dismiss and stacking support, plus its theme:
StreamSheetandStreamSheetDragHandlewidgetsStreamSheetRouteandStreamSheetTransitionfor the modal routeand its transition (including stacked-sheet behaviour)
showStreamSheethelper for opening the sheetStreamSheetTheme/StreamSheetThemeDataexposed viaStreamTheme.sheetThemeandBuildContext.streamSheetThemeSummary by CodeRabbit
New Features
Tests