diff --git a/.changeset/no-native-animated-style-deps.md b/.changeset/no-native-animated-style-deps.md
new file mode 100644
index 0000000000..95b3062ffe
--- /dev/null
+++ b/.changeset/no-native-animated-style-deps.md
@@ -0,0 +1,5 @@
+---
+'@storybook/react-native-ui': patch
+---
+
+The addons panel no longer passes `useAnimatedStyle` dependencies on native, where Reanimated 4.6+ ignores them and warns ("Dependencies should only be used on the web").
diff --git a/packages/react-native-ui/__mocks__/@gorhom/bottom-sheet.js b/packages/react-native-ui/__mocks__/@gorhom/bottom-sheet.js
index 159d67a596..4fb2e96460 100644
--- a/packages/react-native-ui/__mocks__/@gorhom/bottom-sheet.js
+++ b/packages/react-native-ui/__mocks__/@gorhom/bottom-sheet.js
@@ -9,5 +9,6 @@ module.exports = {
__esModule: true,
default: renderChildren(),
BottomSheetBackdrop: jest.fn(() => null),
+ BottomSheetModal: jest.fn(() => null),
BottomSheetScrollView: jest.fn(() => null),
};
diff --git a/packages/react-native-ui/__mocks__/@storybook/react-native-theming.js b/packages/react-native-ui/__mocks__/@storybook/react-native-theming.js
index a4b286d681..0bd2c5a96d 100644
--- a/packages/react-native-ui/__mocks__/@storybook/react-native-theming.js
+++ b/packages/react-native-ui/__mocks__/@storybook/react-native-theming.js
@@ -1,8 +1,13 @@
const theme = {
+ appBorderColor: '#e0e0e0',
+ barSelectedColor: '#1b71ba',
+ color: { mediumdark: '#999999' },
background: { content: '#ffffff' },
textMutedColor: '#999999',
};
module.exports = {
+ // `styled.X(styleFn)` returns a component; host names keep the elements renderable.
+ styled: { Text: () => 'Text', TouchableOpacity: () => 'TouchableOpacity' },
useTheme: jest.fn(() => theme),
};
diff --git a/packages/react-native-ui/__mocks__/@storybook/react-native-ui-common.js b/packages/react-native-ui/__mocks__/@storybook/react-native-ui-common.js
new file mode 100644
index 0000000000..b9c45472f6
--- /dev/null
+++ b/packages/react-native-ui/__mocks__/@storybook/react-native-ui-common.js
@@ -0,0 +1,5 @@
+// `useStyle` memoizes in the real package; recomputing is enough here.
+module.exports = {
+ IconButton: 'IconButton',
+ useStyle: (styleFactory) => styleFactory(),
+};
diff --git a/packages/react-native-ui/__mocks__/react-native-gesture-handler.js b/packages/react-native-ui/__mocks__/react-native-gesture-handler.js
new file mode 100644
index 0000000000..18218db7f9
--- /dev/null
+++ b/packages/react-native-ui/__mocks__/react-native-gesture-handler.js
@@ -0,0 +1,3 @@
+module.exports = {
+ ScrollView: 'ScrollView',
+};
diff --git a/packages/react-native-ui/__mocks__/react-native-reanimated.js b/packages/react-native-ui/__mocks__/react-native-reanimated.js
index 45bbd9f0d5..f6db54c864 100644
--- a/packages/react-native-ui/__mocks__/react-native-reanimated.js
+++ b/packages/react-native-ui/__mocks__/react-native-reanimated.js
@@ -1,4 +1,6 @@
module.exports = {
+ useAnimatedKeyboard: jest.fn(),
useAnimatedStyle: jest.fn(() => ({})),
useReducedMotion: jest.fn(() => false),
+ useSharedValue: jest.fn((initial) => ({ value: initial })),
};
diff --git a/packages/react-native-ui/__mocks__/react-native-svg.js b/packages/react-native-ui/__mocks__/react-native-svg.js
new file mode 100644
index 0000000000..6fd2745e15
--- /dev/null
+++ b/packages/react-native-ui/__mocks__/react-native-svg.js
@@ -0,0 +1,4 @@
+module.exports = {
+ Path: 'Path',
+ Svg: 'Svg',
+};
diff --git a/packages/react-native-ui/__mocks__/react-native.js b/packages/react-native-ui/__mocks__/react-native.js
index fb6091e68d..fe7c6b50ba 100644
--- a/packages/react-native-ui/__mocks__/react-native.js
+++ b/packages/react-native-ui/__mocks__/react-native.js
@@ -1,4 +1,7 @@
module.exports = {
Keyboard: { dismiss: jest.fn() },
Platform: { OS: 'ios' },
+ Text: 'Text',
+ View: 'View',
+ useWindowDimensions: jest.fn(() => ({ width: 390, height: 844, scale: 3, fontScale: 1 })),
};
diff --git a/packages/react-native-ui/__mocks__/storybook/manager-api.js b/packages/react-native-ui/__mocks__/storybook/manager-api.js
new file mode 100644
index 0000000000..739eb9ac10
--- /dev/null
+++ b/packages/react-native-ui/__mocks__/storybook/manager-api.js
@@ -0,0 +1,3 @@
+module.exports = {
+ addons: { getElements: jest.fn(() => ({})) },
+};
diff --git a/packages/react-native-ui/src/MobileAddonsPanel.test.tsx b/packages/react-native-ui/src/MobileAddonsPanel.test.tsx
new file mode 100644
index 0000000000..7857b7eb7a
--- /dev/null
+++ b/packages/react-native-ui/src/MobileAddonsPanel.test.tsx
@@ -0,0 +1,60 @@
+import { act } from 'react';
+import { Platform } from 'react-native';
+import { useAnimatedStyle } from 'react-native-reanimated';
+import { createRoot } from 'test-renderer';
+import { MobileAddonsPanel } from './MobileAddonsPanel';
+
+const animatedStyle = useAnimatedStyle as unknown as jest.Mock;
+const platform = Platform as { OS: string };
+
+// shared value, then the window height from the react-native mock and the bottom inset from
+// __mocks__/react-native-safe-area-context.js
+const deps = [expect.any(Object), 844, 34];
+
+const renderPanel = async () => {
+ const root = createRoot();
+
+ await act(async () => {
+ root.render();
+ });
+
+ return root;
+};
+
+const depsArgument = () => animatedStyle.mock.calls[0][1];
+
+describe('MobileAddonsPanel', () => {
+ const originalOS = platform.OS;
+
+ afterEach(() => {
+ platform.OS = originalOS;
+ animatedStyle.mockClear();
+ });
+
+ it.each(['ios', 'android'])(
+ 'does not pass dependencies to useAnimatedStyle on %s',
+ async (os) => {
+ platform.OS = os;
+ const root = await renderPanel();
+
+ expect(animatedStyle).toHaveBeenCalled();
+ expect(depsArgument()).toBeUndefined();
+
+ await act(async () => {
+ root.unmount();
+ });
+ }
+ );
+
+ it('passes dependencies to useAnimatedStyle on web', async () => {
+ platform.OS = 'web';
+ const root = await renderPanel();
+
+ // web builds without the worklets babel plugin only recompute the style when deps change
+ expect(depsArgument()).toEqual(deps);
+
+ await act(async () => {
+ root.unmount();
+ });
+ });
+});
diff --git a/packages/react-native-ui/src/MobileAddonsPanel.tsx b/packages/react-native-ui/src/MobileAddonsPanel.tsx
index 65af43fc40..b22ee18c14 100644
--- a/packages/react-native-ui/src/MobileAddonsPanel.tsx
+++ b/packages/react-native-ui/src/MobileAddonsPanel.tsx
@@ -60,12 +60,16 @@ export const MobileAddonsPanel = forwardRef {
- const extraPadding = Platform.OS === 'android' ? 32 : 16 + insets.bottom;
- return {
- maxHeight: height - animatedPosition.value - extraPadding,
- };
- }, [animatedPosition, height, insets.bottom]);
+ const adjustedBottomSheetSize = useAnimatedStyle(
+ () => {
+ const extraPadding = Platform.OS === 'android' ? 32 : 16 + insets.bottom;
+ return {
+ maxHeight: height - animatedPosition.value - extraPadding,
+ };
+ },
+ // ignored (and warned about) on native; web without the babel plugin needs them
+ Platform.OS === 'web' ? [animatedPosition, height, insets.bottom] : undefined
+ );
const backgroundStyle = useStyle(() => {
return {