|
| 1 | +import { Meta, Story, Canvas, Controls } from '@storybook/addon-docs/blocks'; |
| 2 | +import * as BottomSheetStories from './BottomSheet.stories'; |
| 3 | +import { BottomSheet } from './BottomSheet'; |
| 4 | +import { CustomTabs, Tab } from '../../../../.storybook/CustomTabs'; |
| 5 | + |
| 6 | +<Meta title="Components/BottomSheet" of={BottomSheetStories} /> |
| 7 | + |
| 8 | +# 📋 BottomSheet |
| 9 | + |
| 10 | +<CustomTabs> |
| 11 | + <Tab label="Overview"> |
| 12 | + |
| 13 | +## Introduction |
| 14 | + |
| 15 | +BottomSheet is a modal component that slides up from the bottom of the screen, built on top of [@gorhom/bottom-sheet v5](https://gorhom.dev/react-native-bottom-sheet/). It provides a flexible and performant way to present content, forms, or actions in a mobile-friendly interface with support for scrollable content, dynamic sizing, and gesture controls. |
| 16 | + |
| 17 | +> View in [Figma](https://www.figma.com/design/JxaLVMTWirCpU0rsbZ30k7/2.-Components-Library?node-id=XXXXX&p=f&m=dev). |
| 18 | +
|
| 19 | +## Anatomy |
| 20 | + |
| 21 | +<Canvas of={BottomSheetStories.Base} /> |
| 22 | + |
| 23 | +- **Handle**: Visual indicator for draggable area |
| 24 | +- **Backdrop**: Semi-transparent overlay behind the sheet |
| 25 | +- **Header**: Optional header with title, description, back button, and close button |
| 26 | +- **Content**: Scrollable or static content using specialized scrollable components |
| 27 | + |
| 28 | +## Properties |
| 29 | + |
| 30 | +### Overview |
| 31 | + |
| 32 | +The base bottom sheet with standard configuration. This example uses `BottomSheetView` for static content, shows a compact header, and snaps to full height. Use this as your starting point for most implementations. |
| 33 | + |
| 34 | +<Canvas of={BottomSheetStories.Base} /> |
| 35 | +<Controls of={BottomSheetStories.Base} /> |
| 36 | + |
| 37 | +### Header Appearances |
| 38 | + |
| 39 | +Bottom sheets support two header appearances: |
| 40 | + |
| 41 | +- **compact**: Centered title with optional description (ideal for shorter titles) |
| 42 | +- **expanded**: Left-aligned title with larger typography (ideal for longer titles and detailed descriptions) |
| 43 | + |
| 44 | +The expanded appearance provides more breathing room for content-heavy headers and is recommended when your title exceeds 2-3 words or when you need to display substantial description text. |
| 45 | + |
| 46 | +<Canvas of={BottomSheetStories.TitleExpanded} /> |
| 47 | + |
| 48 | +### Scrollable Content |
| 49 | + |
| 50 | +#### ScrollView |
| 51 | + |
| 52 | +Use `BottomSheetScrollView` when you have a moderate amount of scrollable content (typically under 20-30 items) that can safely fit in memory. Perfect for forms, article content, or settings menus. Note: Always use `BottomSheetScrollView`, not React Native's regular `ScrollView`. |
| 53 | + |
| 54 | +<Canvas of={BottomSheetStories.ScrollView} /> |
| 55 | + |
| 56 | +#### FlatList (Virtual List) |
| 57 | + |
| 58 | +Use `BottomSheetFlatList` for efficiently rendering large lists (100+ items) with simple data structures. It virtualizes items to improve performance by only rendering what's visible on screen. Ideal for contact lists, search results, or product catalogs. This example demonstrates a list with 100 items. |
| 59 | + |
| 60 | +<Canvas of={BottomSheetStories.VirtualList} /> |
| 61 | + |
| 62 | +#### VirtualizedList |
| 63 | + |
| 64 | +Use `BottomSheetVirtualizedList` when you need more control over item access patterns or work with complex data structures. Requires explicit `getItem` and `getItemCount` implementations. This is the lower-level API that `FlatList` is built upon—use it for custom optimizations or non-array data sources. |
| 65 | + |
| 66 | +<Canvas of={BottomSheetStories.VirtualizedList} /> |
| 67 | + |
| 68 | +> **Additional List Types:** |
| 69 | +> |
| 70 | +> - **SectionList**: Available via `BottomSheetSectionList` but **not compatible with react-native-web**. See [Gorhom documentation](https://gorhom.dev/react-native-bottom-sheet/components/bottomsheetsectionlist) for usage details. |
| 71 | +> - **FlashList**: High-performance list from Shopify. Requires `@shopify/flash-list` package. See [Gorhom documentation](https://gorhom.dev/react-native-bottom-sheet/components/bottomsheetflashlist) for usage details. |
| 72 | +
|
| 73 | +### Dynamic Sizing |
| 74 | + |
| 75 | +Bottom sheets can automatically adapt to content height instead of using fixed snap points. Enable this with `enableDynamicSizing` prop. The sheet will automatically resize as content changes—perfect for dynamic forms, expandable sections, or content that varies in length. This example shows a sheet that sizes itself to fit 5 items. |
| 76 | + |
| 77 | +<Canvas of={BottomSheetStories.DynamicSizing} /> |
| 78 | + |
| 79 | +When using dynamic sizing with potentially long content, set `maxDynamicContentSize` to prevent the sheet from taking over the entire screen. The content becomes scrollable once it exceeds this limit. This example constrains the height to 400px and displays 15 items, making the content scrollable. |
| 80 | + |
| 81 | +<Canvas of={BottomSheetStories.DynamicSizingAndMaxSize} /> |
| 82 | + |
| 83 | +### Non-Closeable |
| 84 | + |
| 85 | +Prevent user dismissal for critical flows where users must complete an action or make a decision. Set both `closeable={false}` and `enablePanDownToClose={false}` to disable all dismiss mechanisms. Use this pattern for required confirmations, important notices, or multi-step processes. The sheet can only be closed programmatically via ref. |
| 86 | + |
| 87 | +<Canvas of={BottomSheetStories.NonCloseable} /> |
| 88 | + |
| 89 | +## Accessibility |
| 90 | + |
| 91 | +To be implemented: |
| 92 | + |
| 93 | +- **Keyboard navigation**: Focus management and tab order |
| 94 | +- **Screen readers**: Proper ARIA labels and announcements |
| 95 | +- **Gesture alternatives**: Keyboard shortcuts for dismiss actions |
| 96 | +- **Focus trapping**: Keep focus within modal when open |
| 97 | +- **Reduced motion**: Respect user motion preferences |
| 98 | + |
| 99 | +</Tab> |
| 100 | +<Tab label="Implementation"> |
| 101 | + |
| 102 | +## Setup |
| 103 | + |
| 104 | +Install and set up the library with our [Setup Guide →](?path=/docs/getting-started-setup--docs). |
| 105 | + |
| 106 | +### Basic Usage |
| 107 | + |
| 108 | +```tsx |
| 109 | +import { BottomSheet, BottomSheetView, BottomSheetHeader, useBottomSheetRef } from '@ledgerhq/ldls-ui-rnative'; |
| 110 | +import { Button, View, Text } from 'react-native'; |
| 111 | + |
| 112 | +function MyComponent() { |
| 113 | + const bottomSheetRef = useBottomSheetRef(); |
| 114 | + |
| 115 | + return ( |
| 116 | + <> |
| 117 | + <Button onPress={() => bottomSheetRef.current?.expand()}> |
| 118 | + Open Bottom Sheet |
| 119 | + </Button> |
| 120 | + |
| 121 | + <BottomSheet ref={bottomSheetRef} snapPoints="full" closeable> |
| 122 | + <BottomSheetView> |
| 123 | + <BottomSheetHeader |
| 124 | + title="Welcome" |
| 125 | + appearance="compact" |
| 126 | + description="Get started with our platform" |
| 127 | + /> |
| 128 | + <Text>Your content here</Text> |
| 129 | + </BottomSheetView> |
| 130 | + </BottomSheet> |
| 131 | + </> |
| 132 | + ); |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +### useBottomSheetRef Hook |
| 137 | + |
| 138 | +The `useBottomSheetRef` hook provides a typed ref for programmatic control: |
| 139 | + |
| 140 | +```tsx |
| 141 | +import { useBottomSheetRef } from '@ledgerhq/ldls-ui-rnative'; |
| 142 | + |
| 143 | +function MyComponent() { |
| 144 | + const bottomSheetRef = useBottomSheetRef(); |
| 145 | + |
| 146 | + return ( |
| 147 | + <BottomSheet ref={bottomSheetRef} snapPoints={['25%', '50%', '90%']}> |
| 148 | + {/* Content */} |
| 149 | + </BottomSheet> |
| 150 | + ); |
| 151 | +} |
| 152 | +``` |
| 153 | + |
| 154 | +<table style={{width: "100%"}}> |
| 155 | + <thead> |
| 156 | + <tr> |
| 157 | + <th style={{textAlign: "left"}}>Method</th> |
| 158 | + <th style={{textAlign: "left"}}>Description</th> |
| 159 | + </tr> |
| 160 | + </thead> |
| 161 | + <tbody> |
| 162 | + <tr> |
| 163 | + <td><b>expand()</b></td> |
| 164 | + <td>Expand to the highest snap point</td> |
| 165 | + </tr> |
| 166 | + <tr> |
| 167 | + <td><b>collapse()</b></td> |
| 168 | + <td>Collapse to the lowest snap point</td> |
| 169 | + </tr> |
| 170 | + <tr> |
| 171 | + <td><b>close()</b></td> |
| 172 | + <td>Close the bottom sheet completely</td> |
| 173 | + </tr> |
| 174 | + <tr> |
| 175 | + <td><b>snapToIndex(index: number)</b></td> |
| 176 | + <td>Snap to a specific snap point by index</td> |
| 177 | + </tr> |
| 178 | + <tr> |
| 179 | + <td><b>snapToPosition(position: string | number)</b></td> |
| 180 | + <td>Snap to a specific position</td> |
| 181 | + </tr> |
| 182 | + <tr> |
| 183 | + <td><b>forceClose()</b></td> |
| 184 | + <td>Force close without animation</td> |
| 185 | + </tr> |
| 186 | + </tbody> |
| 187 | +</table> |
| 188 | + |
| 189 | +### Snap Points |
| 190 | + |
| 191 | +```tsx |
| 192 | +// Preset snap points |
| 193 | +<BottomSheet snapPoints="full"> {/* 95% of screen */} |
| 194 | +<BottomSheet snapPoints="half"> {/* 50% of screen */} |
| 195 | +<BottomSheet snapPoints="quarter"> {/* 25% of screen */} |
| 196 | + |
| 197 | +// Custom snap points (pixels, percentages, or mixed) |
| 198 | +<BottomSheet snapPoints={[200, 400, 600]}> |
| 199 | +<BottomSheet snapPoints={['30%', '60%', '90%']}> |
| 200 | +<BottomSheet snapPoints={[150, '50%', '90%']}> |
| 201 | +``` |
| 202 | + |
| 203 | +### Scrollable Components |
| 204 | + |
| 205 | +Always use specialized scrollable components, never regular React Native components: |
| 206 | + |
| 207 | +```tsx |
| 208 | +import { |
| 209 | + BottomSheetView, // Static content |
| 210 | + BottomSheetScrollView, // Scrollable content |
| 211 | + BottomSheetFlatList, // Large lists (100+ items) |
| 212 | + BottomSheetVirtualizedList, // Custom data access patterns |
| 213 | +} from '@ledgerhq/ldls-ui-rnative'; |
| 214 | + |
| 215 | +// Use spacing prop on header when using list components |
| 216 | +<BottomSheet ref={ref} snapPoints="full"> |
| 217 | + <BottomSheetHeader spacing title="List" appearance="compact" /> |
| 218 | + <BottomSheetFlatList data={data} renderItem={renderItem} /> |
| 219 | +</BottomSheet> |
| 220 | +``` |
| 221 | + |
| 222 | +### Important Notes |
| 223 | + |
| 224 | +- **Dynamic Sizing**: When using `enableDynamicSizing`, do not define `snapPoints` |
| 225 | +- **Header Spacing**: Use `spacing` prop on `BottomSheetHeader` when using FlatList/VirtualizedList |
| 226 | +- **SectionList**: Not compatible with react-native-web. See [Gorhom documentation](https://gorhom.dev/react-native-bottom-sheet/components/bottomsheetsectionlist). |
| 227 | +- **FlashList**: Requires `@shopify/flash-list` package. See [Gorhom docs](https://gorhom.dev/react-native-bottom-sheet/components/bottomsheetflashlist) |
| 228 | + |
| 229 | +## Do's and Don'ts |
| 230 | + |
| 231 | +✅ **Do** |
| 232 | + |
| 233 | +```tsx |
| 234 | +// Use specialized scrollable components |
| 235 | +<BottomSheet ref={bottomSheetRef} snapPoints="full"> |
| 236 | + <BottomSheetScrollView> |
| 237 | + <BottomSheetHeader title="Title" appearance="compact" /> |
| 238 | + {/* Content */} |
| 239 | + </BottomSheetScrollView> |
| 240 | +</BottomSheet> |
| 241 | + |
| 242 | +// Use the provided hook |
| 243 | +const bottomSheetRef = useBottomSheetRef(); |
| 244 | +bottomSheetRef.current?.expand(); |
| 245 | + |
| 246 | +// Disable both for non-dismissible sheets |
| 247 | +<BottomSheet closeable={false} enablePanDownToClose={false}> |
| 248 | +``` |
| 249 | + |
| 250 | +❌ **Don't** |
| 251 | + |
| 252 | +```tsx |
| 253 | +// Don't use regular React Native components |
| 254 | +<BottomSheet ref={ref}> |
| 255 | + <ScrollView> {/* Wrong! Use BottomSheetScrollView */} |
| 256 | + <Text>Content</Text> |
| 257 | + </ScrollView> |
| 258 | +</BottomSheet> |
| 259 | + |
| 260 | +// Don't mix snapPoints with enableDynamicSizing |
| 261 | +<BottomSheet snapPoints="half" enableDynamicSizing> {/* Conflicting! */} |
| 262 | + |
| 263 | +// Don't call methods without optional chaining |
| 264 | +bottomSheetRef.current.expand(); // May crash! Use current?.expand() |
| 265 | +``` |
| 266 | + |
| 267 | +## Learn More |
| 268 | + |
| 269 | +For advanced features and customization options, refer to the [@gorhom/bottom-sheet documentation](https://gorhom.dev/react-native-bottom-sheet/). |
| 270 | + |
| 271 | +</Tab> |
| 272 | +</CustomTabs> |
0 commit comments