diff --git a/.eslintrc.js b/.eslintrc.js
index d4a90084c7..b610270dda 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -5,5 +5,11 @@ module.exports = {
'react-native/no-inline-styles': 'off',
'react/react-in-jsx-scope': 'off',
curly: ['error', 'multi-line'],
+ 'react-hooks/exhaustive-deps': [
+ 'error',
+ {
+ additionalHooks: 'useStyle',
+ },
+ ],
},
};
diff --git a/examples/expo-example/package.json b/examples/expo-example/package.json
index acfd9402b6..41a8f9c382 100644
--- a/examples/expo-example/package.json
+++ b/examples/expo-example/package.json
@@ -1,6 +1,6 @@
{
"name": "expo-example",
- "version": "8.5.2-alpha.0",
+ "version": "8.5.2-alpha.3",
"private": true,
"main": "index.js",
"scripts": {
@@ -25,24 +25,24 @@
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-community/datetimepicker": "8.2.0",
"@react-native-community/slider": "4.5.5",
- "@storybook/addon-essentials": "^8.4.2",
- "@storybook/addon-interactions": "^8.4.2",
- "@storybook/addon-links": "^8.4.2",
- "@storybook/addon-ondevice-actions": "^8.5.2-alpha.0",
- "@storybook/addon-ondevice-backgrounds": "^8.5.2-alpha.0",
- "@storybook/addon-ondevice-controls": "^8.5.2-alpha.0",
- "@storybook/addon-ondevice-notes": "^8.5.2-alpha.0",
+ "@storybook/addon-essentials": "^8.5.1",
+ "@storybook/addon-interactions": "^8.5.1",
+ "@storybook/addon-links": "^8.5.1",
+ "@storybook/addon-ondevice-actions": "^8.5.2-alpha.3",
+ "@storybook/addon-ondevice-backgrounds": "^8.5.2-alpha.3",
+ "@storybook/addon-ondevice-controls": "^8.5.2-alpha.3",
+ "@storybook/addon-ondevice-notes": "^8.5.2-alpha.3",
"@storybook/addon-react-native-server": "0.0.6",
"@storybook/addon-react-native-web": "^0.0.26",
"@storybook/addon-webpack5-compiler-babel": "^3.0.3",
- "@storybook/blocks": "^8.4.2",
- "@storybook/builder-webpack5": "^8.4.2",
+ "@storybook/blocks": "^8.5.1",
+ "@storybook/builder-webpack5": "^8.5.1",
"@storybook/global": "^5.0.0",
- "@storybook/react": "^8.4.2",
- "@storybook/react-native": "^8.5.2-alpha.0",
- "@storybook/react-native-theming": "^8.5.2-alpha.0",
- "@storybook/react-webpack5": "^8.4.2",
- "@storybook/test": "^8.4.2",
+ "@storybook/react": "^8.5.1",
+ "@storybook/react-native": "^8.5.2-alpha.3",
+ "@storybook/react-native-theming": "^8.5.2-alpha.3",
+ "@storybook/react-webpack5": "^8.5.1",
+ "@storybook/test": "^8.5.1",
"expo": "~52.0.11",
"history": "^5.3.0",
"querystring": "^0.2.1",
@@ -55,7 +55,7 @@
"react-native-svg": "15.8.0",
"react-native-web": "~0.19.13",
"react-router": "^6.26.2",
- "storybook": "^8.4.2",
+ "storybook": "^8.5.1",
"storybook-addon-deep-controls": "^0.9.2",
"ws": "^8.18.0"
},
diff --git a/examples/expo-example/scripts/generatePerfTests.ts b/examples/expo-example/scripts/generatePerfTests.ts
new file mode 100644
index 0000000000..e37fb2e419
--- /dev/null
+++ b/examples/expo-example/scripts/generatePerfTests.ts
@@ -0,0 +1,57 @@
+import * as fs from 'fs';
+import * as path from 'path';
+
+const template = (num: number) => `import { Meta, StoryObj } from '@storybook/react'
+import { View } from 'react-native'
+
+const Test${num} = () => {
+ return
+}
+
+const meta: Meta = {
+ title: 'PerfTesting/Test${num}',
+ component: Test${num},
+}
+
+export default meta
+
+export const Default: StoryObj = {}
+export const one: StoryObj = {}
+export const two: StoryObj = {}
+export const three: StoryObj = {}
+export const four: StoryObj = {}
+export const five: StoryObj = {}
+export const six: StoryObj = {}
+export const seven: StoryObj = {}
+export const eight: StoryObj = {}
+export const nine: StoryObj = {}
+export const ten: StoryObj = {}
+export const eleven: StoryObj = {}
+export const twelve: StoryObj = {}
+export const thirteen: StoryObj = {}
+export const fourteen: StoryObj = {}
+export const fifteen: StoryObj = {}
+export const sixteen: StoryObj = {}
+export const seventeen: StoryObj = {}
+export const eighteen: StoryObj = {}
+export const nineteen: StoryObj = {}`;
+
+const generateFiles = () => {
+ const baseDir = path.join(__dirname, '../components/PerfTesting');
+
+ // Create directory if it doesn't exist
+ if (!fs.existsSync(baseDir)) {
+ fs.mkdirSync(baseDir, { recursive: true });
+ }
+
+ // Generate files from Test11 to Test50 (since Test1-10 already exist)
+ for (let i = 1; i <= 200; i++) {
+ const fileName = `Test${i}.stories.tsx`;
+ const filePath = path.join(baseDir, fileName);
+
+ fs.writeFileSync(filePath, template(i));
+ console.log(`Generated ${fileName}`);
+ }
+};
+
+generateFiles();
diff --git a/lerna.json b/lerna.json
index 7bd0630776..a6728c8b71 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"registry": "https://registry.npmjs.org",
- "version": "8.5.2-alpha.0"
+ "version": "8.5.2-alpha.3"
}
\ No newline at end of file
diff --git a/packages/ondevice-actions/package.json b/packages/ondevice-actions/package.json
index 9d94e6c0cc..c861ec9789 100644
--- a/packages/ondevice-actions/package.json
+++ b/packages/ondevice-actions/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-ondevice-actions",
- "version": "8.5.2-alpha.0",
+ "version": "8.5.2-alpha.3",
"description": "Action Logger addon for react-native storybook",
"keywords": [
"storybook"
@@ -27,8 +27,8 @@
"prepare": "tsc"
},
"dependencies": {
- "@storybook/addon-actions": "^8.4.2",
- "@storybook/core": "^8.4.2",
+ "@storybook/addon-actions": "^8.5.1",
+ "@storybook/core": "^8.5.1",
"@storybook/global": "^5.0.0",
"fast-deep-equal": "^2.0.1"
},
diff --git a/packages/ondevice-backgrounds/package.json b/packages/ondevice-backgrounds/package.json
index 4e799190ae..8e7a02e192 100644
--- a/packages/ondevice-backgrounds/package.json
+++ b/packages/ondevice-backgrounds/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-ondevice-backgrounds",
- "version": "8.5.2-alpha.0",
+ "version": "8.5.2-alpha.3",
"description": "A react-native storybook addon to show different backgrounds for your preview",
"keywords": [
"addon",
@@ -32,8 +32,8 @@
"dev": "tsc --watch"
},
"dependencies": {
- "@storybook/core": "^8.4.2",
- "@storybook/react-native-theming": "^8.5.2-alpha.0"
+ "@storybook/core": "^8.5.1",
+ "@storybook/react-native-theming": "^8.5.2-alpha.3"
},
"devDependencies": {
"typescript": "^5.3.3"
diff --git a/packages/ondevice-controls/package.json b/packages/ondevice-controls/package.json
index 94c907477f..10f98c82cc 100644
--- a/packages/ondevice-controls/package.json
+++ b/packages/ondevice-controls/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-ondevice-controls",
- "version": "8.5.2-alpha.0",
+ "version": "8.5.2-alpha.3",
"description": "Display storybook controls on your device.",
"keywords": [
"addon",
@@ -30,10 +30,10 @@
"copyimages": "cross-env-shell cp -r src/components/color-picker/resources dist/components/color-picker/resources"
},
"dependencies": {
- "@storybook/addon-controls": "^8.4.2",
- "@storybook/core": "^8.4.2",
- "@storybook/react-native-theming": "^8.5.2-alpha.0",
- "@storybook/react-native-ui": "^8.5.2-alpha.0",
+ "@storybook/addon-controls": "^8.5.1",
+ "@storybook/core": "^8.5.1",
+ "@storybook/react-native-theming": "^8.5.2-alpha.3",
+ "@storybook/react-native-ui": "^8.5.2-alpha.3",
"deep-equal": "^1.0.1",
"prop-types": "^15.7.2",
"react-native-modal-datetime-picker": "^14.0.0",
diff --git a/packages/ondevice-notes/package.json b/packages/ondevice-notes/package.json
index cf434d8db3..3e0b171e16 100644
--- a/packages/ondevice-notes/package.json
+++ b/packages/ondevice-notes/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-ondevice-notes",
- "version": "8.5.2-alpha.0",
+ "version": "8.5.2-alpha.3",
"description": "Write notes for your react-native Storybook stories.",
"keywords": [
"addon",
@@ -29,8 +29,8 @@
"dev": "tsc --watch"
},
"dependencies": {
- "@storybook/core": "^8.4.2",
- "@storybook/react-native-theming": "^8.5.2-alpha.0",
+ "@storybook/core": "^8.5.1",
+ "@storybook/react-native-theming": "^8.5.2-alpha.3",
"react-native-markdown-display": "^7.0.2"
},
"devDependencies": {
diff --git a/packages/react-native-theming/package.json b/packages/react-native-theming/package.json
index 17cce80919..75c5a3fef0 100644
--- a/packages/react-native-theming/package.json
+++ b/packages/react-native-theming/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/react-native-theming",
- "version": "8.5.2-alpha.0",
+ "version": "8.5.2-alpha.3",
"description": "A wrapper library around emotion 11 to provide theming support for react-native storybook",
"keywords": [
"react",
diff --git a/packages/react-native-ui/package.json b/packages/react-native-ui/package.json
index 5f5a87cdf0..a743026784 100644
--- a/packages/react-native-ui/package.json
+++ b/packages/react-native-ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/react-native-ui",
- "version": "8.5.2-alpha.0",
+ "version": "8.5.2-alpha.3",
"description": "ui components for react native storybook",
"keywords": [
"react",
@@ -58,9 +58,9 @@
"typescript": "^5.3.3"
},
"dependencies": {
- "@storybook/core": "^8.4.2",
- "@storybook/react": "^8.4.2",
- "@storybook/react-native-theming": "^8.5.2-alpha.0",
+ "@storybook/core": "^8.5.1",
+ "@storybook/react": "^8.5.1",
+ "@storybook/react-native-theming": "^8.5.2-alpha.3",
"fuse.js": "^7.0.0",
"memoizerific": "^1.11.3",
"polished": "^4.3.1",
diff --git a/packages/react-native-ui/src/Layout.tsx b/packages/react-native-ui/src/Layout.tsx
index 4074fa47ef..70731fc4ce 100644
--- a/packages/react-native-ui/src/Layout.tsx
+++ b/packages/react-native-ui/src/Layout.tsx
@@ -3,8 +3,8 @@ import { addons } from '@storybook/core/manager-api';
import { type API_IndexHash, type Args, type StoryContext } from '@storybook/core/types';
import type { ReactRenderer } from '@storybook/react';
import { styled, useTheme } from '@storybook/react-native-theming';
-import { ReactNode, useRef, useState } from 'react';
-import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
+import { ReactNode, useRef, useState, useCallback } from 'react';
+import { ScrollView, Text, TouchableOpacity, View, ViewStyle } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { IconButton } from './IconButton';
import { useLayout } from './LayoutProvider';
@@ -18,6 +18,39 @@ import { BottomBarToggleIcon } from './icon/BottomBarToggleIcon';
import { CloseFullscreenIcon } from './icon/CloseFullscreenIcon';
import { FullscreenIcon } from './icon/FullscreenIcon';
import { MenuIcon } from './icon/MenuIcon';
+import { useStyle } from './util/useStyle';
+
+const desktopLogoContainer = {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingTop: 10,
+ paddingLeft: 16,
+ paddingBottom: 4,
+ paddingRight: 10,
+ justifyContent: 'space-between',
+} satisfies ViewStyle;
+
+const desktopContentContainerStyle = { flex: 1 } satisfies ViewStyle;
+
+const desktopContentStyle = { flex: 1, overflow: 'hidden' } satisfies ViewStyle;
+
+const mobileContentStyle = { flex: 1, overflow: 'hidden' } satisfies ViewStyle;
+
+const placeholderObject = {};
+
+const placeholderArray = [];
+
+const iconFloatRightStyle = { marginLeft: 'auto' } satisfies ViewStyle;
+
+const navButtonStyle = { flexShrink: 1 } satisfies ViewStyle;
+
+const navButtonHitSlop = { bottom: 10, left: 10, right: 10, top: 10 };
+
+const mobileMenuDrawerContentStyle = {
+ paddingLeft: 16,
+ paddingTop: 4,
+ paddingBottom: 4,
+} satisfies ViewStyle;
export const Layout = ({
storyHash,
@@ -46,58 +79,114 @@ export const Layout = ({
const [uiHidden, setUiHidden] = useState(false);
+ const desktopContainerStyle = useStyle(
+ () => ({
+ flex: 1,
+ paddingTop: insets.top,
+ backgroundColor: theme.background.content,
+ flexDirection: 'row',
+ }),
+ [theme.background.content, insets.top]
+ );
+
+ const desktopSidebarStyle = useStyle(
+ () => ({
+ width: desktopSidebarOpen ? 240 : undefined,
+ padding: desktopSidebarOpen ? 0 : 10,
+ borderColor: theme.appBorderColor,
+ borderRightWidth: 1,
+ }),
+ [desktopSidebarOpen, theme.appBorderColor]
+ );
+
+ const desktopScrollViewContentContainerStyle = useStyle(
+ () => ({
+ paddingBottom: insets.bottom,
+ }),
+ [insets.bottom]
+ );
+
+ const desktopAddonsPanelStyle = useStyle(
+ () => ({
+ height: desktopAddonsPanelOpen ? 300 : undefined,
+ borderTopWidth: 1,
+ borderColor: theme.appBorderColor,
+ paddingTop: desktopAddonsPanelOpen ? 4 : 0,
+ padding: desktopAddonsPanelOpen ? 0 : 10,
+ }),
+ [desktopAddonsPanelOpen, theme.appBorderColor]
+ );
+
+ const mobileContainerStyle = useStyle(
+ () => ({
+ flex: 1,
+ paddingTop: story?.parameters?.noSafeArea ? 0 : insets.top,
+ backgroundColor: theme.background.content,
+ }),
+ [theme.background.content, insets.top, story?.parameters?.noSafeArea]
+ );
+
+ const fullScreenButtonStyle = useStyle(
+ () => ({
+ position: 'absolute',
+ bottom: uiHidden ? 56 + insets.bottom : 16,
+ right: 16,
+ backgroundColor: theme.background.content,
+ padding: 4,
+ borderRadius: 4,
+ borderWidth: 1,
+ borderColor: theme.appBorderColor,
+ }),
+ [uiHidden, insets.bottom, theme.background.content, theme.appBorderColor]
+ );
+
+ const containerStyle = useStyle(
+ () => ({
+ marginBottom: insets.bottom,
+ }),
+ [insets.bottom]
+ );
+
+ const navButtonTextStyle = useStyle(
+ () => ({
+ flexShrink: 1,
+ color: theme.color.defaultText,
+ }),
+ [theme.color.defaultText]
+ );
+
+ const openMobileMenu = useCallback(() => {
+ mobileMenuDrawerRef.current.setMobileMenuOpen(true);
+ }, [mobileMenuDrawerRef]);
+
+ const setSelection = useCallback(({ storyId: newStoryId }: { storyId: string }) => {
+ const channel = addons.getChannel();
+
+ channel.emit(SET_CURRENT_STORY, { storyId: newStoryId });
+ }, []);
+
if (isDesktop) {
return (
-
-
+
+
{desktopSidebarOpen ? (
-
+
setDesktopSidebarOpen(false)} Icon={MenuIcon} />
{
- const channel = addons.getChannel();
-
- channel.emit(SET_CURRENT_STORY, { storyId: newStoryId });
- }}
- status={{}}
+ refs={placeholderObject}
+ setSelection={setSelection}
+ status={placeholderObject}
index={storyHash}
storyId={story?.id}
refId={DEFAULT_REF_ID}
@@ -108,23 +197,15 @@ export const Layout = ({
)}
-
- {children}
-
-
+
+ {children}
+
+
{desktopAddonsPanelOpen ? (
setDesktopAddonsPanelOpen(false)} />
) : (
setDesktopAddonsPanelOpen(true)}
Icon={BottomBarToggleIcon}
/>
@@ -136,28 +217,13 @@ export const Layout = ({
}
return (
-
-
+
+
{children}
{story?.parameters?.hideFullScreenButton ? null : (
setUiHidden((prev) => !prev)}
>
{uiHidden ? (
@@ -169,43 +235,17 @@ export const Layout = ({
)}
-
-
-
-
-
- {
- const channel = addons.getChannel();
-
- channel.emit(SET_CURRENT_STORY, { storyId: newStoryId });
- }}
- status={{}}
- index={storyHash}
- storyId={story?.id}
- refId={DEFAULT_REF_ID}
- />
-
-
-
-
{!uiHidden ? (
-
+
) : null}
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/packages/react-native-ui/src/MobileAddonsPanel.tsx b/packages/react-native-ui/src/MobileAddonsPanel.tsx
index da22081c7a..75d2f46ce1 100644
--- a/packages/react-native-ui/src/MobileAddonsPanel.tsx
+++ b/packages/react-native-ui/src/MobileAddonsPanel.tsx
@@ -2,8 +2,8 @@ import { BottomSheetModal } from '@gorhom/bottom-sheet';
import { addons } from '@storybook/core/manager-api';
import { styled } from '@storybook/react-native-theming';
import { Addon_TypesEnum } from '@storybook/core/types';
-import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
-import { Platform, Text, View, useWindowDimensions } from 'react-native';
+import { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react';
+import { Platform, StyleProp, Text, View, ViewStyle, useWindowDimensions } from 'react-native';
import { ScrollView } from 'react-native-gesture-handler';
import Animated, {
useAnimatedKeyboard,
@@ -15,11 +15,20 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useTheme } from '@storybook/react-native-theming';
import { IconButton } from './IconButton';
import { CloseIcon } from './icon/CloseIcon';
+import { useStyle } from './util/useStyle';
export interface MobileAddonsPanelRef {
setAddonsPanelOpen: (isOpen: boolean) => void;
}
+const bottomSheetStyle = {
+ paddingTop: 8,
+} satisfies StyleProp;
+
+const contentStyle = {
+ flex: 1,
+} satisfies StyleProp;
+
export const MobileAddonsPanel = forwardRef(
({ storyId }, ref) => {
const theme = useTheme();
@@ -53,24 +62,31 @@ export const MobileAddonsPanel = forwardRef {
+ return {
+ borderRadius: 0,
+ borderTopColor: theme.appBorderColor,
+ borderTopWidth: 1,
+ backgroundColor: theme.background.content,
+ };
+ });
+
+ const handleIndicatorStyle = useStyle(() => {
+ return {
+ backgroundColor: theme.textMutedColor,
+ };
+ });
+
return (
-
+
{
addonsPanelBottomSheetRef.current?.dismiss();
@@ -92,6 +108,38 @@ export const MobileAddonsPanel = forwardRef;
+
+const addonsTabsStyle = {
+ flexDirection: 'row',
+ borderBottomWidth: 1,
+ borderBottomColor: 'lightgrey',
+} satisfies StyleProp;
+
+const addonsTabsContentContainerStyle = {
+ justifyContent: 'center',
+} satisfies StyleProp;
+
+const closeIconStyle = {
+ marginRight: 4,
+ marginBottom: 4,
+ alignItems: 'center',
+ justifyContent: 'center',
+} satisfies StyleProp;
+
+const addonsScrollStyle = {
+ flex: 1,
+} satisfies StyleProp;
+
+const centeredStyle = {
+ alignItems: 'center',
+ justifyContent: 'center',
+} satisfies StyleProp;
+
+const hitSlop = { top: 10, right: 10, bottom: 10, left: 10 };
+
export const AddonsTabs = ({ onClose, storyId }: { onClose?: () => void; storyId?: string }) => {
const panels = addons.getElements(Addon_TypesEnum.PANEL);
@@ -99,15 +147,39 @@ export const AddonsTabs = ({ onClose, storyId }: { onClose?: () => void; storyId
const insets = useSafeAreaInsets();
+ const scrollContentContainerStyle = useStyle(() => {
+ return {
+ paddingBottom: insets.bottom + 16,
+ };
+ });
+
+ const panel = useMemo(() => {
+ if (!storyId) {
+ return (
+
+ No Story Selected
+
+ );
+ }
+
+ if (Object.keys(panels).length === 0) {
+ return (
+
+ No addons loaded.
+
+ );
+ }
+
+ return panels[addonSelected].render({ active: true });
+ }, [addonSelected, panels, storyId]);
+
return (
-
-
+
+
{Object.values(panels).map(({ id, title }) => {
const resolvedTitle = typeof title === 'function' ? title({}) : title;
@@ -124,43 +196,18 @@ export const AddonsTabs = ({ onClose, storyId }: { onClose?: () => void; storyId
onClose?.()}
/>
- {(() => {
- if (!storyId) {
- return (
-
- No Story Selected
-
- );
- }
-
- if (Object.keys(panels).length === 0) {
- return (
-
- No addons loaded.
-
- );
- }
-
- return panels[addonSelected].render({ active: true });
- })()}
+ {panel}
);
diff --git a/packages/react-native-ui/src/MobileMenuDrawer.tsx b/packages/react-native-ui/src/MobileMenuDrawer.tsx
index 98090f3fc6..6da2ea5405 100644
--- a/packages/react-native-ui/src/MobileMenuDrawer.tsx
+++ b/packages/react-native-ui/src/MobileMenuDrawer.tsx
@@ -1,10 +1,9 @@
-import {
+import BottomSheet, {
BottomSheetBackdrop,
BottomSheetBackdropProps,
- BottomSheetModal,
BottomSheetScrollView,
} from '@gorhom/bottom-sheet';
-import { ReactNode, forwardRef, useImperativeHandle, useRef } from 'react';
+import { ReactNode, forwardRef, memo, useImperativeHandle, useMemo, useRef } from 'react';
import { Keyboard } from 'react-native';
import { useReducedMotion } from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -28,52 +27,67 @@ export const BottomSheetBackdropComponent = (backdropComponentProps: BottomSheet
/>
);
-export const MobileMenuDrawer = forwardRef(
- ({ children }, ref) => {
+const snapPoints = ['50%', '75%'];
+
+export const MobileMenuDrawer = memo(
+ forwardRef(({ children }, ref) => {
const reducedMotion = useReducedMotion();
const insets = useSafeAreaInsets();
const theme = useTheme();
- const menuBottomSheetRef = useRef(null);
+ const menuBottomSheetRef = useRef(null);
useImperativeHandle(ref, () => ({
setMobileMenuOpen: (open: boolean) => {
if (open) {
- menuBottomSheetRef.current?.present();
+ // menuBottomSheetRef.current?.present();
+ menuBottomSheetRef.current?.snapToIndex(1);
} else {
Keyboard.dismiss();
- menuBottomSheetRef.current?.dismiss();
+ // menuBottomSheetRef.current?.dismiss();
+ menuBottomSheetRef.current?.close();
}
},
}));
+ const bgColorStyle = useMemo(() => {
+ return { backgroundColor: theme.background.content };
+ }, [theme.background.content]);
+
+ const handleIndicatorStyle = useMemo(() => {
+ return { backgroundColor: theme.textMutedColor };
+ }, [theme.textMutedColor]);
+
+ const contentContainerStyle = useMemo(() => {
+ return { paddingBottom: insets.bottom };
+ }, [insets.bottom]);
+
return (
-
{children}
-
+
);
- }
+ })
);
diff --git a/packages/react-native-ui/src/Search.tsx b/packages/react-native-ui/src/Search.tsx
index 9634182d71..26812ee636 100644
--- a/packages/react-native-ui/src/Search.tsx
+++ b/packages/react-native-ui/src/Search.tsx
@@ -2,8 +2,8 @@ import { BottomSheetTextInput } from '@gorhom/bottom-sheet';
import { styled } from '@storybook/react-native-theming';
import type { IFuseOptions } from 'fuse.js';
import Fuse from 'fuse.js';
-import React, { useCallback, useRef, useState } from 'react';
-import { TextInput, View } from 'react-native';
+import React, { useCallback, useDeferredValue, useRef, useState } from 'react';
+import { Platform, TextInput, View } from 'react-native';
import { CloseIcon } from './icon/CloseIcon';
import { SearchIcon } from './icon/SearchIcon';
import { useLayout } from './LayoutProvider';
@@ -61,6 +61,8 @@ const BottomSheetInput = styled(BottomSheetTextInput)(({ theme }) => ({
height: 32,
paddingLeft: 28,
paddingRight: 28,
+ paddingTop: Platform.OS === 'android' ? 0 : undefined,
+ paddingBottom: Platform.OS === 'android' ? 0 : undefined,
borderWidth: 1,
borderColor: theme.appBorderColor,
backgroundColor: 'transparent',
@@ -219,8 +221,8 @@ export const Search = React.memo<{
},
[allComponents, dataset.hash, getLastViewed, makeFuse]
);
-
- const input = inputValue ? inputValue.trim() : '';
+ const deferredQuery = useDeferredValue(inputValue);
+ const input = deferredQuery ? deferredQuery.trim() : '';
const results = input ? getResults(input) : [];
return (
diff --git a/packages/react-native-ui/src/StorybookLogo.tsx b/packages/react-native-ui/src/StorybookLogo.tsx
index 4441110cf0..df13fa9d87 100644
--- a/packages/react-native-ui/src/StorybookLogo.tsx
+++ b/packages/react-native-ui/src/StorybookLogo.tsx
@@ -96,9 +96,12 @@ const BrandTitle: FC<{ theme: Theme }> = ({ theme }) => {
};
export const StorybookLogo: FC<{ theme: Theme }> = ({ theme }) => {
- if (theme.brand?.image) {
+ const image = useMemo(() => theme.brand?.image, [theme.brand?.image]);
+ const title = useMemo(() => theme.brand?.title, [theme.brand?.title]);
+
+ if (image) {
return ;
- } else if (theme.brand?.title) {
+ } else if (title) {
return ;
} else {
return ;
diff --git a/packages/react-native-ui/src/TreeNode.tsx b/packages/react-native-ui/src/TreeNode.tsx
index bcbc3a0b4a..1e2953a58e 100644
--- a/packages/react-native-ui/src/TreeNode.tsx
+++ b/packages/react-native-ui/src/TreeNode.tsx
@@ -4,7 +4,7 @@ import { GroupIcon } from './icon/GroupIcon';
import { StoryIcon } from './icon/StoryIcon';
import { CollapseIcon } from './icon/CollapseIcon';
-import React, { ComponentProps, FC } from 'react';
+import React, { ComponentProps, FC, useMemo } from 'react';
import { transparentize } from 'polished';
export interface NodeProps {
@@ -95,15 +95,15 @@ export const GroupNode: FC<
}) {
const theme = useTheme();
+ const color = useMemo(() => {
+ return theme.base === 'dark' ? theme.color.primary : theme.color.ultraviolet;
+ }, [theme.base, theme.color.primary, theme.color.ultraviolet]);
+
return (
{isExpandable && }
-
+
{children}
@@ -113,11 +113,16 @@ export const GroupNode: FC<
export const ComponentNode: FC> = React.memo(
function ComponentNode({ children, isExpanded, isExpandable, ...props }) {
const theme = useTheme();
+
+ const color = useMemo(() => {
+ return theme.color.secondary;
+ }, [theme.color.secondary]);
+
return (
{isExpandable && }
-
+
{children}
@@ -131,14 +136,14 @@ export const StoryNode: FC> = React.memo(functio
}) {
const theme = useTheme();
+ const color = useMemo(() => {
+ return props.selected ? theme.color.lightest : theme.color.seafoam;
+ }, [props.selected, theme.color.lightest, theme.color.seafoam]);
+
return (
-
+
{children}
diff --git a/packages/react-native-ui/src/icon/CloseFullscreenIcon.tsx b/packages/react-native-ui/src/icon/CloseFullscreenIcon.tsx
index df73dd7cc4..3ac4aae3aa 100644
--- a/packages/react-native-ui/src/icon/CloseFullscreenIcon.tsx
+++ b/packages/react-native-ui/src/icon/CloseFullscreenIcon.tsx
@@ -1,18 +1,16 @@
-import * as React from 'react';
+import { useMemo } from 'react';
import Svg, { Path, SvgProps } from 'react-native-svg';
import { useTheme } from '@storybook/react-native-theming';
export function CloseFullscreenIcon({ color, width = 14, height = 14, ...props }: SvgProps) {
const theme = useTheme();
+ const fillColor = useMemo(() => {
+ return color ?? theme.color.defaultText;
+ }, [color, theme.color.defaultText]);
+
return (
-