Skip to content

Commit d1beac5

Browse files
authored
feat: add storybookUIVisibility and layout parameter (#815)
* feat: add fullscreenDefaultValue support and improve keyboard handling in layout components * docs: add UI-related parameters for React Native Storybook, including fullscreen and safe area options * fix: formatting * use layout effect * feat: layout parameter and updated ui visiblity param * docs: update Storybook parameters for UI visibility and layout options
1 parent 5979d47 commit d1beac5

File tree

8 files changed

+89
-22
lines changed

8 files changed

+89
-22
lines changed

docs/docs/intro/configuration/index.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,10 @@ The `preview.tsx` file configures the story rendering environment and global par
6060

6161
```typescript
6262
import { Preview } from '@storybook/react-native';
63-
import { View } from 'react-native';
6463
import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds';
6564

6665
const preview: Preview = {
6766
decorators: [
68-
// Global wrapper for all stories
69-
(Story) => (
70-
<View style={{ padding: 8 }}>
71-
<Story />
72-
</View>
73-
),
74-
7567
// backgrounds addon decorator
7668
withBackgrounds,
7769
],
@@ -89,6 +81,8 @@ const preview: Preview = {
8981
// UI Configuration
9082
hideFullScreenButton: false,
9183
noSafeArea: false,
84+
storybookUIVisibility: 'visible', // 'visible' | 'hidden'
85+
layout: 'padded', // 'fullscreen' | 'centered' | 'padded'
9286

9387
// Background Configuration
9488
backgrounds: {
@@ -114,6 +108,8 @@ Other than story sort the other parameters can be overwritten per story.
114108
- `parameters.options.storySort`: Story sorting configuration
115109
- `parameters.hideFullScreenButton`: Toggle fullscreen button visibility
116110
- `parameters.noSafeArea`: Disable safe area insets
111+
- `parameters.storybookUIVisibility`: Controls UI visibility (`'visible'` | `'hidden'`)
112+
- `parameters.layout`: Controls story container layout (`'fullscreen'` | `'centered'` | `'padded'`)
117113
- `parameters.backgrounds`: Background addon configuration
118114

119115
## index.tsx

docs/docs/intro/writing-stories.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,61 @@ export default meta;
133133

134134
This parameter would instruct the backgrounds addon to reconfigure itself whenever a Button story is selected. Most addons are configured via a parameter-based API and can be influenced at a global, component and story level.
135135

136+
#### UI-related parameters
137+
138+
React Native Storybook provides several built-in parameters to control the on-device UI behavior:
139+
140+
| Parameter | Type | Description |
141+
| ----------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
142+
| `noSafeArea` | `boolean` | When `true`, removes the top safe area padding, allowing your story to render edge-to-edge |
143+
| `storybookUIVisibility` | `'visible'` \| `'hidden'` | Controls the initial visibility of the Storybook UI. When `'hidden'`, the story starts in fullscreen mode |
144+
| `hideFullScreenButton` | `boolean` | When `true`, hides the fullscreen toggle button |
145+
| `layout` | `'padded'` \| `'centered'` \| `'fullscreen'` | Controls the layout of the story container. `'padded'` adds padding, `'centered'` centers the content, `'fullscreen'` removes any default spacing |
146+
147+
```tsx
148+
// Button.stories.tsx
149+
import type { Meta, StoryObj } from '@storybook/react-native';
150+
151+
import { Button } from './Button';
152+
153+
const meta: Meta<typeof Button> = {
154+
component: Button,
155+
};
156+
157+
export default meta;
158+
type Story = StoryObj<typeof Button>;
159+
160+
// Story that starts with the UI hidden
161+
export const UIHidden: Story = {
162+
args: {
163+
label: 'Button',
164+
},
165+
parameters: {
166+
storybookUIVisibility: 'hidden',
167+
},
168+
};
169+
170+
// Story with centered layout
171+
export const Centered: Story = {
172+
args: {
173+
label: 'Button',
174+
},
175+
parameters: {
176+
layout: 'centered',
177+
},
178+
};
179+
180+
// Story without safe area padding (edge-to-edge)
181+
export const EdgeToEdge: Story = {
182+
args: {
183+
label: 'Button',
184+
},
185+
parameters: {
186+
noSafeArea: true,
187+
},
188+
};
189+
```
190+
136191
### Using decorators
137192

138193
Decorators are a mechanism to wrap a component in arbitrary markup when rendering a story. Components are often created with assumptions about ‘where’ they render. Your styles might expect a theme or layout wrapper, or your UI might expect specific context or data providers.

examples/expo-example/.rnstorybook/preview.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
import { View, Appearance } from 'react-native';
1+
import { Appearance } from 'react-native';
22
import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds';
33
import type { Preview } from '@storybook/react-native';
44

55
const preview: Preview = {
6-
decorators: [
7-
(Story) => (
8-
<View style={{ padding: 8, flex: 1 }}>
9-
<Story />
10-
</View>
11-
),
12-
withBackgrounds,
13-
],
6+
decorators: [withBackgrounds],
147
parameters: {
158
actions: { argTypesRegex: '^on[A-Z].*' },
169
controls: {
@@ -29,6 +22,8 @@ const preview: Preview = {
2922
hideFullScreenButton: false,
3023
noSafeArea: false,
3124
my_param: 'anything',
25+
layout: 'padded', // fullscreen, centered, padded
26+
storybookUIVisibility: 'visible', // visible, hidden
3227
backgrounds: {
3328
default: Appearance.getColorScheme() === 'dark' ? 'dark' : 'plain',
3429
values: [

examples/expo-example/components/ControlExamples/Array/Array.stories.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const meta = {
1717
notes: `
1818
This is actually not a proper control type and should be inferred from the object type but is currently an inconsistency in the rn addon.
1919
`,
20+
storybookUIVisibility: 'hidden', // visible, hidden
21+
layout: 'centered', // fullscreen, centered, padded
2022
},
2123
} satisfies Meta<typeof Array>;
2224

packages/react-native-ui-lite/src/Layout.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
useStoreBooleanState,
1111
useStyle,
1212
} from '@storybook/react-native-ui-common';
13-
import { ReactElement, ReactNode, useCallback, useRef, useState } from 'react';
13+
import { ReactElement, ReactNode, useCallback, useLayoutEffect, useRef, useState } from 'react';
1414
import { ScrollView, Text, TouchableOpacity, View, ViewStyle } from 'react-native';
1515
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
1616
import { SET_CURRENT_STORY } from 'storybook/internal/core-events';
@@ -102,6 +102,10 @@ export const Layout = ({
102102

103103
const [uiHidden, setUiHidden] = useState(false);
104104

105+
useLayoutEffect(() => {
106+
setUiHidden(story?.parameters?.storybookUIVisibility === 'hidden');
107+
}, [story?.parameters?.storybookUIVisibility]);
108+
105109
const desktopSidebarStyle = useStyle(
106110
() => ({
107111
width: desktopSidebarOpen ? 240 : undefined,

packages/react-native-ui-lite/src/MobileAddonsPanel.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ export const MobileAddonsPanel = forwardRef<MobileAddonsPanelRef, { storyId?: st
167167
<AddonsTabs
168168
onClose={() => {
169169
setMobileMenuOpen(false);
170+
Keyboard.dismiss();
170171
}}
171172
storyId={storyId}
172173
/>
@@ -250,6 +251,7 @@ export const AddonsTabs = ({ onClose, storyId }: { onClose?: () => void; storyId
250251
horizontal
251252
showsHorizontalScrollIndicator={false}
252253
contentContainerStyle={addonsTabsContentContainerStyle}
254+
keyboardShouldPersistTaps="handled"
253255
>
254256
{Object.values(panels).map(({ id, title }) => {
255257
const resolvedTitle = typeof title === 'function' ? title({}) : title;

packages/react-native-ui/src/Layout.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
2-
import { PortalHost, PortalProvider } from '@gorhom/portal';
2+
import { PortalHost } from '@gorhom/portal';
33
import type { ReactRenderer } from '@storybook/react';
44
import { styled, ThemeProvider, useTheme } from '@storybook/react-native-theming';
55
import {
@@ -11,7 +11,7 @@ import {
1111
useStyle,
1212
type SBUI,
1313
} from '@storybook/react-native-ui-common';
14-
import { ReactNode, useCallback, useRef, useState } from 'react';
14+
import { ReactNode, useCallback, useLayoutEffect, useRef, useState } from 'react';
1515
import { ScrollView, Text, TouchableOpacity, View, ViewStyle } from 'react-native';
1616
import { GestureHandlerRootView } from 'react-native-gesture-handler';
1717
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -108,6 +108,10 @@ export const Layout = ({
108108

109109
const [uiHidden, setUiHidden] = useState(false);
110110

111+
useLayoutEffect(() => {
112+
setUiHidden(story?.parameters?.storybookUIVisibility === 'hidden');
113+
}, [story?.id, story?.parameters?.storybookUIVisibility]);
114+
111115
const desktopSidebarStyle = useStyle(
112116
() => ({
113117
width: desktopSidebarOpen ? 240 : undefined,

packages/react-native/src/components/StoryView/StoryView.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ const errorContainerStyle = {
3232
justifyContent: 'center',
3333
} satisfies ViewStyle;
3434

35+
const layoutStyles = {
36+
padded: { padding: 8 },
37+
centered: { alignItems: 'center', justifyContent: 'center' },
38+
fullscreen: {},
39+
} satisfies Record<string, ViewStyle>;
40+
3541
const StoryView = ({ useWrapper = true }: { useWrapper?: boolean }) => {
3642
const context = useStoryContext();
3743

@@ -40,12 +46,15 @@ const StoryView = ({ useWrapper = true }: { useWrapper?: boolean }) => {
4046
const theme = useTheme();
4147

4248
const containerStyle = useMemo(() => {
49+
const layout = context?.parameters?.layout;
50+
const layoutStyle = layout ? layoutStyles[layout] : {};
4351
return {
4452
flex: 1,
4553
backgroundColor: theme.background?.content,
4654
overflow: 'hidden',
55+
...layoutStyle,
4756
} satisfies ViewStyle;
48-
}, [theme.background?.content]);
57+
}, [theme.background?.content, context?.parameters?.layout]);
4958

5059
const onError = useCallback(() => {
5160
console.log(`Error rendering story for ${context?.title} ${context?.name}`);
@@ -80,7 +89,7 @@ const StoryView = ({ useWrapper = true }: { useWrapper?: boolean }) => {
8089

8190
return (
8291
<View style={errorContainerStyle}>
83-
<Text>Please open the sidebar and select a story to preview.</Text>
92+
<Text>Please select a story to preview.</Text>
8493
</View>
8594
);
8695
};

0 commit comments

Comments
 (0)