Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/no-native-animated-style-deps.md
Original file line number Diff line number Diff line change
@@ -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").
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ module.exports = {
__esModule: true,
default: renderChildren(),
BottomSheetBackdrop: jest.fn(() => null),
BottomSheetModal: jest.fn(() => null),
BottomSheetScrollView: jest.fn(() => null),
};
Original file line number Diff line number Diff line change
@@ -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),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// `useStyle` memoizes in the real package; recomputing is enough here.
module.exports = {
IconButton: 'IconButton',
useStyle: (styleFactory) => styleFactory(),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
ScrollView: 'ScrollView',
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
module.exports = {
useAnimatedKeyboard: jest.fn(),
useAnimatedStyle: jest.fn(() => ({})),
useReducedMotion: jest.fn(() => false),
useSharedValue: jest.fn((initial) => ({ value: initial })),
};
4 changes: 4 additions & 0 deletions packages/react-native-ui/__mocks__/react-native-svg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
Path: 'Path',
Svg: 'Svg',
};
3 changes: 3 additions & 0 deletions packages/react-native-ui/__mocks__/react-native.js
Original file line number Diff line number Diff line change
@@ -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 })),
};
3 changes: 3 additions & 0 deletions packages/react-native-ui/__mocks__/storybook/manager-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
addons: { getElements: jest.fn(() => ({})) },
};
60 changes: 60 additions & 0 deletions packages/react-native-ui/src/MobileAddonsPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -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(<MobileAddonsPanel storyId="button--primary" />);
});

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();
});
});
});
16 changes: 10 additions & 6 deletions packages/react-native-ui/src/MobileAddonsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,16 @@ export const MobileAddonsPanel = forwardRef<MobileAddonsPanelRef, MobileAddonsPa

const { height } = useWindowDimensions();

const adjustedBottomSheetSize = useAnimatedStyle(() => {
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 {
Expand Down
Loading