@storybook/react-native 6.5.0-rc.0 → 6.5.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/hooks.d.ts +22 -0
  2. package/dist/hooks.js +35 -0
  3. package/dist/index.d.ts +1 -1
  4. package/dist/preview/View.d.ts +4 -4
  5. package/dist/preview/View.js +6 -4
  6. package/dist/preview/components/OnDeviceUI/OnDeviceUI.d.ts +1 -4
  7. package/dist/preview/components/OnDeviceUI/OnDeviceUI.js +61 -43
  8. package/dist/preview/components/OnDeviceUI/absolute-positioned-keyboard-aware-view.d.ts +1 -1
  9. package/dist/preview/components/OnDeviceUI/absolute-positioned-keyboard-aware-view.js +7 -4
  10. package/dist/preview/components/OnDeviceUI/animation.d.ts +34 -11
  11. package/dist/preview/components/OnDeviceUI/animation.js +49 -18
  12. package/dist/preview/components/OnDeviceUI/navigation/Navigation.d.ts +3 -1
  13. package/dist/preview/components/OnDeviceUI/navigation/Navigation.js +2 -2
  14. package/dist/preview/components/Shared/icons.d.ts +1 -0
  15. package/dist/preview/components/Shared/icons.js +25 -12
  16. package/dist/preview/components/Shared/text.js +9 -3
  17. package/dist/preview/components/Shared/theme.d.ts +2 -0
  18. package/dist/preview/components/Shared/theme.js +2 -0
  19. package/dist/preview/components/StoryListView/StoryListView.d.ts +3 -6
  20. package/dist/preview/components/StoryListView/StoryListView.js +74 -45
  21. package/dist/preview/components/StoryView/StoryView.d.ts +3 -8
  22. package/dist/preview/components/StoryView/StoryView.js +4 -2
  23. package/dist/types/types-6.0.d.ts +10 -10
  24. package/dist/types/types.d.ts +3 -3
  25. package/package.json +6 -6
  26. package/scripts/__snapshots__/loader.test.js.snap +77 -28
  27. package/scripts/loader.js +29 -16
  28. package/scripts/loader.test.js +10 -0
  29. package/scripts/mocks/configuration-objects/components/FakeComponent.tsx +1 -0
  30. package/scripts/mocks/configuration-objects/components/FakeStory.stories.tsx +9 -0
  31. package/scripts/mocks/configuration-objects/main.js +15 -0
  32. package/scripts/mocks/configuration-objects/preview.js +24 -0
@@ -0,0 +1,22 @@
1
+ import type { StoryContext } from '@storybook/csf';
2
+ import type { ReactNativeFramework } from './types/types-6.0';
3
+ /**
4
+ * Hook that returns a function to set the current story context.
5
+ */
6
+ export declare function useSetStoryContext(): (args_0: StoryContext<ReactNativeFramework, import("@storybook/csf").Args> | ((prev: StoryContext<ReactNativeFramework, import("@storybook/csf").Args>) => StoryContext<ReactNativeFramework, import("@storybook/csf").Args>)) => void;
7
+ /**
8
+ * Hook to read the current story context.
9
+ */
10
+ export declare function useStoryContext(): StoryContext<ReactNativeFramework, import("@storybook/csf").Args>;
11
+ /**
12
+ * Hook that reads the value of a specific story context parameter.
13
+ */
14
+ export declare function useStoryContextParam<T = any>(name: string, defaultValue?: T): T;
15
+ /**
16
+ * Hook that indicates if `storyId` is the currently selected story.
17
+ */
18
+ export declare function useIsStorySelected(storyId: string): boolean;
19
+ /**
20
+ * Hook that indicates if `title` is the currently selected story section.
21
+ */
22
+ export declare function useIsStorySectionSelected(title: string): boolean;
package/dist/hooks.js ADDED
@@ -0,0 +1,35 @@
1
+ import { useMemo } from 'react';
2
+ import { atom, useAtomValue, useSetAtom } from 'jotai';
3
+ const storyContextAtom = atom(null);
4
+ /**
5
+ * Hook that returns a function to set the current story context.
6
+ */
7
+ export function useSetStoryContext() {
8
+ return useSetAtom(storyContextAtom);
9
+ }
10
+ /**
11
+ * Hook to read the current story context.
12
+ */
13
+ export function useStoryContext() {
14
+ return useAtomValue(storyContextAtom);
15
+ }
16
+ /**
17
+ * Hook that reads the value of a specific story context parameter.
18
+ */
19
+ export function useStoryContextParam(name, defaultValue) {
20
+ var _a;
21
+ const paramAtom = useMemo(() => atom(get => { var _a, _b; return (_b = (_a = get(storyContextAtom)) === null || _a === void 0 ? void 0 : _a.parameters) === null || _b === void 0 ? void 0 : _b[name]; }), [name]);
22
+ return (_a = useAtomValue(paramAtom)) !== null && _a !== void 0 ? _a : defaultValue;
23
+ }
24
+ /**
25
+ * Hook that indicates if `storyId` is the currently selected story.
26
+ */
27
+ export function useIsStorySelected(storyId) {
28
+ return useAtomValue(useMemo(() => atom(get => { var _a; return ((_a = get(storyContextAtom)) === null || _a === void 0 ? void 0 : _a.id) === storyId; }), [storyId]));
29
+ }
30
+ /**
31
+ * Hook that indicates if `title` is the currently selected story section.
32
+ */
33
+ export function useIsStorySectionSelected(title) {
34
+ return useAtomValue(useMemo(() => atom(get => { var _a; return ((_a = get(storyContextAtom)) === null || _a === void 0 ? void 0 : _a.title) === title; }), [title]));
35
+ }
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ import { ReactNode } from 'react';
6
6
  import type { ReactNativeFramework } from './types/types-6.0';
7
7
  declare const configure: (loadable: import("@storybook/core-client").Loadable, m: NodeModule) => void;
8
8
  export { configure };
9
- declare type C = ClientApi<ReactNativeFramework>;
9
+ type C = ClientApi<ReactNativeFramework>;
10
10
  export declare const setAddon: C['setAddon'];
11
11
  export declare const addDecorator: C['addDecorator'];
12
12
  export declare const addParameters: C['addParameters'];
@@ -4,9 +4,9 @@ import { StoryContext } from '@storybook/csf';
4
4
  import { theme } from './components/Shared/theme';
5
5
  import type { ReactNativeFramework } from '../types/types-6.0';
6
6
  import { PreviewWeb } from '@storybook/preview-web';
7
- declare type StoryKind = string;
8
- declare type StoryName = string;
9
- declare type InitialSelection = `${StoryKind}--${StoryName}` | {
7
+ type StoryKind = string;
8
+ type StoryName = string;
9
+ type InitialSelection = `${StoryKind}--${StoryName}` | {
10
10
  /**
11
11
  * Kind is the default export name or the storiesOf("name") name
12
12
  */
@@ -16,7 +16,7 @@ declare type InitialSelection = `${StoryKind}--${StoryName}` | {
16
16
  */
17
17
  name: StoryName;
18
18
  };
19
- export declare type Params = {
19
+ export type Params = {
20
20
  onDeviceUI?: boolean;
21
21
  enableWebsockets?: boolean;
22
22
  query?: string;
@@ -7,12 +7,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import React, { useEffect, useState, useReducer } from 'react';
10
+ import React, { useEffect, useReducer } from 'react';
11
11
  import AsyncStorage from '@react-native-async-storage/async-storage';
12
12
  import { toId } from '@storybook/csf';
13
13
  import { addons } from '@storybook/addons';
14
14
  import { ThemeProvider } from 'emotion-theming';
15
15
  import { SafeAreaProvider } from 'react-native-safe-area-context';
16
+ import { useSetStoryContext } from '../hooks';
16
17
  import OnDeviceUI from './components/OnDeviceUI';
17
18
  import { theme } from './components/Shared/theme';
18
19
  import StoryView from './components/StoryView';
@@ -88,7 +89,7 @@ export class View {
88
89
  const self = this;
89
90
  const appliedTheme = Object.assign(Object.assign({}, theme), params.theme);
90
91
  return () => {
91
- const [context, setContext] = useState();
92
+ const setContext = useSetStoryContext();
92
93
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
93
94
  useEffect(() => {
94
95
  self._setStory = (newStory) => {
@@ -104,14 +105,15 @@ export class View {
104
105
  self._preview.urlStore.selectionSpecifier = story;
105
106
  self._preview.selectSpecifiedStory();
106
107
  });
108
+ // eslint-disable-next-line react-hooks/exhaustive-deps
107
109
  }, []);
108
110
  if (onDeviceUI) {
109
111
  return (React.createElement(SafeAreaProvider, null,
110
112
  React.createElement(ThemeProvider, { theme: appliedTheme },
111
- React.createElement(OnDeviceUI, { context: context, storyIndex: self._storyIndex, isUIHidden: params.isUIHidden, tabOpen: params.tabOpen, shouldDisableKeyboardAvoidingView: params.shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset: params.keyboardAvoidingViewVerticalOffset }))));
113
+ React.createElement(OnDeviceUI, { storyIndex: self._storyIndex, isUIHidden: params.isUIHidden, tabOpen: params.tabOpen, shouldDisableKeyboardAvoidingView: params.shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset: params.keyboardAvoidingViewVerticalOffset }))));
112
114
  }
113
115
  else {
114
- return React.createElement(StoryView, { context: context });
116
+ return React.createElement(StoryView, null);
115
117
  }
116
118
  };
117
119
  };
@@ -1,10 +1,7 @@
1
1
  import { StoryIndex } from '@storybook/client-api';
2
2
  import React from 'react';
3
- import { StoryContext } from '@storybook/csf';
4
- import { ReactNativeFramework } from 'src/types/types-6.0';
5
3
  export declare const IS_EXPO: boolean;
6
4
  interface OnDeviceUIProps {
7
- context: StoryContext<ReactNativeFramework>;
8
5
  storyIndex: StoryIndex;
9
6
  url?: string;
10
7
  tabOpen?: number;
@@ -12,5 +9,5 @@ interface OnDeviceUIProps {
12
9
  shouldDisableKeyboardAvoidingView?: boolean;
13
10
  keyboardAvoidingViewVerticalOffset?: number;
14
11
  }
15
- declare const _default: React.MemoExoticComponent<({ context, storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset, tabOpen: initialTabOpen, }: OnDeviceUIProps) => JSX.Element>;
12
+ declare const _default: React.MemoExoticComponent<({ storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset, tabOpen: initialTabOpen, }: OnDeviceUIProps) => JSX.Element>;
16
13
  export default _default;
@@ -1,17 +1,19 @@
1
1
  import styled from '@emotion/native';
2
+ import { useTheme } from 'emotion-theming';
2
3
  import React, { useState, useRef } from 'react';
3
- import { Animated, Dimensions, Keyboard, KeyboardAvoidingView, Platform, SafeAreaView, TouchableOpacity, StatusBar, StyleSheet, View, } from 'react-native';
4
+ import { Animated, Dimensions, Easing, Keyboard, KeyboardAvoidingView, Platform, TouchableOpacity, StatusBar, StyleSheet, View, } from 'react-native';
5
+ import { useStoryContextParam } from '../../../hooks';
4
6
  import StoryListView from '../StoryListView';
5
7
  import StoryView from '../StoryView';
6
8
  import AbsolutePositionedKeyboardAwareView from './absolute-positioned-keyboard-aware-view';
7
9
  import Addons from './addons/Addons';
8
- import { getAddonPanelPosition, getNavigatorPanelPosition, getPreviewPosition, getPreviewScale, } from './animation';
10
+ import { getAddonPanelPosition, getNavigatorPanelPosition, getPreviewShadowStyle, getPreviewStyle, } from './animation';
9
11
  import Navigation from './navigation';
10
12
  import { PREVIEW, ADDONS } from './navigation/constants';
11
13
  import Panel from './Panel';
12
14
  import { useWindowDimensions } from 'react-native';
13
15
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
14
- const ANIMATION_DURATION = 300;
16
+ const ANIMATION_DURATION = 400;
15
17
  const IS_IOS = Platform.OS === 'ios';
16
18
  // @ts-ignore: Property 'Expo' does not exist on type 'Global'
17
19
  const getExpoRoot = () => global.Expo || global.__expo || global.__exponent;
@@ -19,84 +21,100 @@ export const IS_EXPO = getExpoRoot() !== undefined;
19
21
  const IS_ANDROID = Platform.OS === 'android';
20
22
  const BREAKPOINT = 1024;
21
23
  const flex = { flex: 1 };
22
- const Preview = styled.View(flex, ({ disabled, theme }) => ({
23
- borderLeftWidth: disabled ? 0 : 1,
24
- borderTopWidth: disabled ? 0 : 1,
25
- borderRightWidth: disabled ? 0 : 1,
26
- borderBottomWidth: disabled ? 0 : 1,
27
- borderColor: disabled ? 'transparent' : theme.previewBorderColor,
28
- }));
29
- const absolutePosition = {
30
- position: 'absolute',
31
- top: 0,
32
- bottom: 0,
33
- left: 0,
34
- right: 0,
35
- };
24
+ /**
25
+ * Story preview container.
26
+ */
27
+ function Preview({ animatedValue, style, children }) {
28
+ const theme = useTheme();
29
+ const containerStyle = Object.assign({ backgroundColor: theme.backgroundColor }, getPreviewShadowStyle(animatedValue));
30
+ return (React.createElement(Animated.View, { style: [flex, containerStyle] },
31
+ React.createElement(View, { style: [flex, style] }, children)));
32
+ }
36
33
  const styles = StyleSheet.create({
37
34
  expoAndroidContainer: { paddingTop: StatusBar.currentHeight },
38
35
  });
39
- const OnDeviceUI = ({ context, storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset, tabOpen: initialTabOpen, }) => {
40
- var _a, _b;
36
+ const Container = styled.View(({ theme }) => (Object.assign({ flex: 1, backgroundColor: theme.backgroundColor }, (IS_ANDROID && IS_EXPO ? styles.expoAndroidContainer : undefined))));
37
+ const OnDeviceUI = ({ storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset, tabOpen: initialTabOpen, }) => {
41
38
  const [tabOpen, setTabOpen] = useState(initialTabOpen || PREVIEW);
42
- const [slideBetweenAnimation, setSlideBetweenAnimation] = useState(false);
43
- const [previewDimensions, setPreviewDimensions] = useState({
39
+ const lastTabOpen = React.useRef(tabOpen);
40
+ const [previewDimensions, setPreviewDimensions] = useState(() => ({
44
41
  width: Dimensions.get('window').width,
45
42
  height: Dimensions.get('window').height,
46
- });
43
+ }));
47
44
  const animatedValue = useRef(new Animated.Value(tabOpen));
48
45
  const wide = useWindowDimensions().width >= BREAKPOINT;
49
46
  const insets = useSafeAreaInsets();
47
+ const theme = useTheme();
50
48
  const [isUIVisible, setIsUIVisible] = useState(isUIHidden !== undefined ? !isUIHidden : true);
51
- const handleToggleTab = (newTabOpen) => {
49
+ const handleToggleTab = React.useCallback((newTabOpen) => {
52
50
  if (newTabOpen === tabOpen) {
53
51
  return;
54
52
  }
53
+ lastTabOpen.current = tabOpen;
55
54
  Animated.timing(animatedValue.current, {
56
55
  toValue: newTabOpen,
57
56
  duration: ANIMATION_DURATION,
57
+ easing: Easing.inOut(Easing.cubic),
58
58
  useNativeDriver: true,
59
59
  }).start();
60
60
  setTabOpen(newTabOpen);
61
- const isSwipingBetweenNavigatorAndAddons = tabOpen + newTabOpen === PREVIEW;
62
- setSlideBetweenAnimation(isSwipingBetweenNavigatorAndAddons);
63
61
  // close the keyboard opened from a TextInput from story list or knobs
64
62
  if (newTabOpen === PREVIEW) {
65
63
  Keyboard.dismiss();
66
64
  }
67
- };
68
- const noSafeArea = (_b = (_a = context === null || context === void 0 ? void 0 : context.parameters) === null || _a === void 0 ? void 0 : _a.noSafeArea) !== null && _b !== void 0 ? _b : false;
65
+ }, [tabOpen]);
66
+ const noSafeArea = useStoryContextParam('noSafeArea', false);
69
67
  const previewWrapperStyles = [
70
68
  flex,
71
- getPreviewPosition({
69
+ getPreviewStyle({
72
70
  animatedValue: animatedValue.current,
73
71
  previewDimensions,
74
- slideBetweenAnimation,
75
72
  wide,
76
- noSafeArea,
77
73
  insets,
74
+ tabOpen,
75
+ lastTabOpen: lastTabOpen.current,
78
76
  }),
79
77
  ];
80
- const previewStyles = [flex, getPreviewScale(animatedValue.current, slideBetweenAnimation, wide)];
81
- const WrapperView = noSafeArea ? View : SafeAreaView;
82
- const wrapperMargin = { marginBottom: isUIVisible ? insets.bottom + 40 : 0 };
78
+ // The initial value is just a guess until the layout calculation has been done.
79
+ const [navBarHeight, setNavBarHeight] = React.useState(insets.bottom + 40);
80
+ const measureNavigation = React.useCallback(({ nativeEvent }) => {
81
+ const inset = insets.bottom;
82
+ setNavBarHeight(isUIVisible ? nativeEvent.layout.height - inset : 0);
83
+ }, [isUIVisible, insets]);
84
+ // There are 4 cases for the additional UI margin:
85
+ // 1. Storybook UI is visible, and `noSafeArea` is false: Include top and
86
+ // bottom safe area insets, and also include the navigation bar height.
87
+ //
88
+ // 2. Storybook UI is not visible, and `noSafeArea` is false: Include top
89
+ // and bottom safe area insets.
90
+ //
91
+ // 3. Storybook UI is visible, and `noSafeArea` is true: Include only the
92
+ // bottom safe area inset and the navigation bar height.
93
+ //
94
+ // 4. Storybook UI is not visible, and `noSafeArea` is true: No margin.
95
+ const safeAreaMargins = {
96
+ paddingBottom: isUIVisible ? insets.bottom + navBarHeight : noSafeArea ? 0 : insets.bottom,
97
+ paddingTop: !noSafeArea ? insets.top : 0,
98
+ };
83
99
  return (React.createElement(React.Fragment, null,
84
- React.createElement(View, { style: [flex, IS_ANDROID && IS_EXPO && styles.expoAndroidContainer] },
100
+ React.createElement(Container, null,
85
101
  React.createElement(KeyboardAvoidingView, { enabled: !shouldDisableKeyboardAvoidingView || tabOpen !== PREVIEW, behavior: IS_IOS ? 'padding' : null, keyboardVerticalOffset: keyboardAvoidingViewVerticalOffset, style: flex },
86
102
  React.createElement(AbsolutePositionedKeyboardAwareView, { onLayout: setPreviewDimensions, previewDimensions: previewDimensions },
87
103
  React.createElement(Animated.View, { style: previewWrapperStyles },
88
- React.createElement(Animated.View, { style: previewStyles },
89
- React.createElement(Preview, { disabled: tabOpen === PREVIEW },
90
- React.createElement(WrapperView, { style: [flex, wrapperMargin] },
91
- React.createElement(StoryView, { context: context }))),
92
- tabOpen !== PREVIEW ? (React.createElement(TouchableOpacity, { style: absolutePosition, onPress: () => handleToggleTab(PREVIEW) })) : null)),
93
- React.createElement(Panel, { style: getNavigatorPanelPosition(animatedValue.current, previewDimensions.width, wide) },
94
- React.createElement(StoryListView, { storyIndex: storyIndex, selectedStoryContext: context })),
104
+ React.createElement(Preview, { style: safeAreaMargins, animatedValue: animatedValue.current },
105
+ React.createElement(StoryView, null)),
106
+ tabOpen !== PREVIEW ? (React.createElement(TouchableOpacity, { style: StyleSheet.absoluteFillObject, onPress: () => handleToggleTab(PREVIEW) })) : null),
107
+ React.createElement(Panel, { style: [
108
+ getNavigatorPanelPosition(animatedValue.current, previewDimensions.width, wide),
109
+ safeAreaMargins,
110
+ { backgroundColor: theme.storyListBackgroundColor },
111
+ ] },
112
+ React.createElement(StoryListView, { storyIndex: storyIndex })),
95
113
  React.createElement(Panel, { style: [
96
114
  getAddonPanelPosition(animatedValue.current, previewDimensions.width, wide),
97
- wrapperMargin,
115
+ safeAreaMargins,
98
116
  ] },
99
117
  React.createElement(Addons, { active: tabOpen === ADDONS })))),
100
- React.createElement(Navigation, { tabOpen: tabOpen, onChangeTab: handleToggleTab, isUIVisible: isUIVisible, setIsUIVisible: setIsUIVisible }))));
118
+ React.createElement(Navigation, { onLayout: measureNavigation, tabOpen: tabOpen, onChangeTab: handleToggleTab, isUIVisible: isUIVisible, setIsUIVisible: setIsUIVisible }))));
101
119
  };
102
120
  export default React.memo(OnDeviceUI);
@@ -3,7 +3,7 @@ export interface PreviewDimens {
3
3
  width: number;
4
4
  height: number;
5
5
  }
6
- declare type Props = {
6
+ type Props = {
7
7
  onLayout: (dimens: PreviewDimens) => void;
8
8
  previewDimensions: PreviewDimens;
9
9
  children: ReactNode;
@@ -4,10 +4,13 @@ import { View, StyleSheet } from 'react-native';
4
4
  // To avoid issues we use absolute positioned element with predefined screen size
5
5
  const AbsolutePositionedKeyboardAwareView = ({ onLayout, previewDimensions: { width, height }, children, }) => {
6
6
  const onLayoutHandler = ({ nativeEvent }) => {
7
- onLayout({
8
- height: nativeEvent.layout.height,
9
- width: nativeEvent.layout.width,
10
- });
7
+ const { height: layoutHeight, width: layoutWidth } = nativeEvent.layout;
8
+ if (layoutHeight !== height || layoutWidth !== width) {
9
+ onLayout({
10
+ height: layoutHeight,
11
+ width: layoutWidth,
12
+ });
13
+ }
11
14
  };
12
15
  return (React.createElement(View, { style: styles.container, onLayout: onLayoutHandler },
13
16
  React.createElement(View, { style: width === 0
@@ -1,5 +1,4 @@
1
- import { Animated } from 'react-native';
2
- import { EdgeInsets } from 'react-native-safe-area-context';
1
+ import { Animated, Insets } from 'react-native';
3
2
  import { PreviewDimens } from './absolute-positioned-keyboard-aware-view';
4
3
  export declare const getNavigatorPanelPosition: (animatedValue: Animated.Value, previewWidth: number, wide: boolean) => {
5
4
  transform: {
@@ -13,26 +12,50 @@ export declare const getAddonPanelPosition: (animatedValue: Animated.Value, prev
13
12
  }[];
14
13
  width: number;
15
14
  }[];
16
- declare type PreviewPositionArgs = {
15
+ type PreviewPositionArgs = {
17
16
  animatedValue: Animated.Value;
18
17
  previewDimensions: PreviewDimens;
19
- slideBetweenAnimation: boolean;
20
18
  wide: boolean;
21
- noSafeArea: boolean;
22
- insets: EdgeInsets;
19
+ insets: Insets;
20
+ tabOpen: number;
21
+ lastTabOpen: number;
23
22
  };
24
- export declare const getPreviewPosition: ({ animatedValue, previewDimensions: { width: previewWidth, height: previewHeight }, slideBetweenAnimation, wide, noSafeArea, insets, }: PreviewPositionArgs) => {
23
+ /**
24
+ * Build the animated style for the preview container view.
25
+ *
26
+ * When the navigator or addons panel is focused, the preview container is
27
+ * scaled down and translated to the left (or right) of the panel.
28
+ */
29
+ export declare const getPreviewStyle: ({ animatedValue, previewDimensions: { width: previewWidth, height: previewHeight }, wide, insets, tabOpen, lastTabOpen, }: PreviewPositionArgs) => {
25
30
  transform: ({
26
31
  translateX: Animated.AnimatedInterpolation<string | number>;
27
32
  translateY?: undefined;
33
+ scale?: undefined;
28
34
  } | {
29
35
  translateY: Animated.AnimatedInterpolation<string | number>;
30
36
  translateX?: undefined;
37
+ scale?: undefined;
38
+ } | {
39
+ scale: Animated.AnimatedInterpolation<string | number>;
40
+ translateX?: undefined;
41
+ translateY?: undefined;
31
42
  })[];
32
43
  };
33
- export declare const getPreviewScale: (animatedValue: Animated.Value, slideBetweenAnimation: boolean, wide: boolean) => {
34
- transform: {
35
- scale: Animated.AnimatedInterpolation<string | number>;
36
- }[];
44
+ /**
45
+ * Build the animated shadow style for the preview.
46
+ *
47
+ * When the navigator or addons panel are visible the scaled preview will have
48
+ * a shadow, and when going to the preview tab the shadow will be invisible.
49
+ */
50
+ export declare const getPreviewShadowStyle: (animatedValue: Animated.Value) => {
51
+ elevation: number;
52
+ shadowColor: string;
53
+ shadowOpacity: Animated.AnimatedInterpolation<string | number>;
54
+ shadowRadius: number;
55
+ shadowOffset: {
56
+ width: number;
57
+ height: number;
58
+ };
59
+ overflow: "visible";
37
60
  };
38
61
  export {};
@@ -1,13 +1,17 @@
1
1
  import { I18nManager } from 'react-native';
2
2
  import { NAVIGATOR, PREVIEW, ADDONS } from './navigation/constants';
3
+ // Factor that will flip the animation orientation in RTL locales.
3
4
  const RTL_SCALE = I18nManager.isRTL ? -1 : 1;
5
+ // Percentage to scale the preview area by when opening a panel.
4
6
  const PREVIEW_SCALE = 0.3;
5
- const PREVIEW_WIDE_SCREEN = 0.7;
7
+ // Percentage to scale the preview area by when opening a panel, on wide screens.
8
+ const PREVIEW_SCALE_WIDE = 0.7;
9
+ // Percentage to shrink the visible preview by, without affecting the panel size.
10
+ const PREVIEW_SCALE_SHRINK = 0.9;
6
11
  const SCALE_OFFSET = 0.025;
7
- const TRANSLATE_X_OFFSET = 6;
8
12
  const TRANSLATE_Y_OFFSET = 12;
9
13
  const panelWidth = (width, wide) => {
10
- const scale = wide ? PREVIEW_WIDE_SCREEN : PREVIEW_SCALE;
14
+ const scale = wide ? PREVIEW_SCALE_WIDE : PREVIEW_SCALE;
11
15
  return width * (1 - scale - SCALE_OFFSET);
12
16
  };
13
17
  export const getNavigatorPanelPosition = (animatedValue, previewWidth, wide) => {
@@ -32,7 +36,10 @@ export const getAddonPanelPosition = (animatedValue, previewWidth, wide) => {
32
36
  {
33
37
  translateX: animatedValue.interpolate({
34
38
  inputRange: [PREVIEW, ADDONS],
35
- outputRange: [previewWidth * RTL_SCALE, (previewWidth - panelWidth(previewWidth, wide)) * RTL_SCALE],
39
+ outputRange: [
40
+ previewWidth * RTL_SCALE,
41
+ (previewWidth - panelWidth(previewWidth, wide)) * RTL_SCALE,
42
+ ],
36
43
  }),
37
44
  },
38
45
  ],
@@ -40,11 +47,25 @@ export const getAddonPanelPosition = (animatedValue, previewWidth, wide) => {
40
47
  },
41
48
  ];
42
49
  };
43
- export const getPreviewPosition = ({ animatedValue, previewDimensions: { width: previewWidth, height: previewHeight }, slideBetweenAnimation, wide, noSafeArea, insets, }) => {
44
- const scale = wide ? PREVIEW_WIDE_SCREEN : PREVIEW_SCALE;
45
- const translateX = (previewWidth / 2 - (previewWidth * scale) / 2 - TRANSLATE_X_OFFSET) * RTL_SCALE;
46
- const marginTop = noSafeArea ? 0 : insets.top;
47
- const translateY = -(previewHeight / 2 - (previewHeight * scale) / 2 - TRANSLATE_Y_OFFSET) + marginTop;
50
+ /**
51
+ * Build the animated style for the preview container view.
52
+ *
53
+ * When the navigator or addons panel is focused, the preview container is
54
+ * scaled down and translated to the left (or right) of the panel.
55
+ */
56
+ export const getPreviewStyle = ({ animatedValue, previewDimensions: { width: previewWidth, height: previewHeight }, wide, insets, tabOpen, lastTabOpen, }) => {
57
+ const scale = (wide ? PREVIEW_SCALE_WIDE : PREVIEW_SCALE) * PREVIEW_SCALE_SHRINK;
58
+ const scaledPreviewWidth = previewWidth * scale;
59
+ const scaledPreviewHeight = previewHeight * scale;
60
+ // Horizontally center the scaled preview in the available space beside the panel.
61
+ const nonPanelWidth = previewWidth - panelWidth(previewWidth, wide);
62
+ const translateXOffset = (nonPanelWidth - scaledPreviewWidth) / 2;
63
+ const translateX = (previewWidth / 2 - (previewWidth * scale) / 2 - translateXOffset) * RTL_SCALE;
64
+ // Translate the preview to the top edge of the screen, move it down by the
65
+ // safe area inset, then by the preview Y offset.
66
+ const translateY = -(previewHeight / 2 - scaledPreviewHeight / 2) + insets.top + TRANSLATE_Y_OFFSET;
67
+ // Is navigation moving from one panel to another, skipping preview?
68
+ const skipPreview = lastTabOpen !== PREVIEW && tabOpen !== PREVIEW;
48
69
  return {
49
70
  transform: [
50
71
  {
@@ -56,22 +77,32 @@ export const getPreviewPosition = ({ animatedValue, previewDimensions: { width:
56
77
  {
57
78
  translateY: animatedValue.interpolate({
58
79
  inputRange: [NAVIGATOR, PREVIEW, ADDONS],
59
- outputRange: [translateY, slideBetweenAnimation ? translateY : marginTop, translateY],
80
+ outputRange: [translateY, skipPreview ? translateY : 0, translateY],
60
81
  }),
61
82
  },
62
- ],
63
- };
64
- };
65
- export const getPreviewScale = (animatedValue, slideBetweenAnimation, wide) => {
66
- const scale = wide ? PREVIEW_WIDE_SCREEN : PREVIEW_SCALE;
67
- return {
68
- transform: [
69
83
  {
70
84
  scale: animatedValue.interpolate({
71
85
  inputRange: [NAVIGATOR, PREVIEW, ADDONS],
72
- outputRange: [scale, slideBetweenAnimation ? scale : 1, scale],
86
+ outputRange: [scale, skipPreview ? scale : 1, scale],
73
87
  }),
74
88
  },
75
89
  ],
76
90
  };
77
91
  };
92
+ /**
93
+ * Build the animated shadow style for the preview.
94
+ *
95
+ * When the navigator or addons panel are visible the scaled preview will have
96
+ * a shadow, and when going to the preview tab the shadow will be invisible.
97
+ */
98
+ export const getPreviewShadowStyle = (animatedValue) => ({
99
+ elevation: 8,
100
+ shadowColor: '#000',
101
+ shadowOpacity: animatedValue.interpolate({
102
+ inputRange: [NAVIGATOR, PREVIEW, ADDONS],
103
+ outputRange: [0.25, 0, 0.25],
104
+ }),
105
+ shadowRadius: 8,
106
+ shadowOffset: { width: 0, height: 0 },
107
+ overflow: 'visible',
108
+ });
@@ -1,9 +1,11 @@
1
1
  import React, { Dispatch, SetStateAction } from 'react';
2
+ import { ViewProps } from 'react-native';
2
3
  interface Props {
3
4
  tabOpen: number;
4
5
  onChangeTab: (index: number) => void;
5
6
  isUIVisible: boolean;
6
7
  setIsUIVisible: Dispatch<SetStateAction<boolean>>;
8
+ onLayout: ViewProps['onLayout'];
7
9
  }
8
- declare const _default: React.MemoExoticComponent<({ tabOpen, onChangeTab, isUIVisible, setIsUIVisible }: Props) => JSX.Element>;
10
+ declare const _default: React.MemoExoticComponent<({ tabOpen, onChangeTab, isUIVisible, setIsUIVisible, onLayout }: Props) => JSX.Element>;
9
11
  export default _default;
@@ -14,7 +14,7 @@ const navStyle = {
14
14
  right: 0,
15
15
  bottom: 0,
16
16
  };
17
- const Navigation = ({ tabOpen, onChangeTab, isUIVisible, setIsUIVisible }) => {
17
+ const Navigation = ({ tabOpen, onChangeTab, isUIVisible, setIsUIVisible, onLayout }) => {
18
18
  const insets = useSafeAreaInsets();
19
19
  const handleToggleUI = () => {
20
20
  setIsUIVisible((oldIsUIVisible) => !oldIsUIVisible);
@@ -29,7 +29,7 @@ const Navigation = ({ tabOpen, onChangeTab, isUIVisible, setIsUIVisible }) => {
29
29
  onChangeTab(tabOpen - 1);
30
30
  }
31
31
  };
32
- return (React.createElement(View, { style: navStyle },
32
+ return (React.createElement(View, { style: navStyle, onLayout: onLayout },
33
33
  isUIVisible && (React.createElement(GestureRecognizer, { onSwipeLeft: handleSwipeLeft, onSwipeRight: handleSwipeRight, config: SWIPE_CONFIG },
34
34
  React.createElement(Bar, { index: tabOpen, onPress: onChangeTab, style: { paddingBottom: insets.bottom } }))),
35
35
  React.createElement(VisibilityButton, { onPress: handleToggleUI, style: { marginBottom: insets.bottom } })));
@@ -3,3 +3,4 @@ export declare const GridIcon: () => JSX.Element;
3
3
  export declare const StoryIcon: ({ selected }: {
4
4
  selected: boolean;
5
5
  }) => JSX.Element;
6
+ export declare const SearchIcon: () => JSX.Element;