@storybook/react-native-ui-lite 10.0.1 → 10.0.2-alpha.1

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.
package/dist/index.d.ts CHANGED
@@ -7,8 +7,8 @@ import { Item, ExpandAction, CombinedDataset, Selection, SBUI } from '@storybook
7
7
  import { State, StoriesHash } from 'storybook/manager-api';
8
8
  import { API_LoadedRefData, API_IndexHash } from 'storybook/internal/types';
9
9
  import * as react_jsx_runtime from 'react/jsx-runtime';
10
- import { StoryContext, Args } from 'storybook/internal/csf';
11
10
  import { ReactRenderer } from '@storybook/react';
11
+ import { StoryContext, Args } from 'storybook/internal/csf';
12
12
 
13
13
  interface NodeProps$1 {
14
14
  children: React.ReactNode | React.ReactNode[];
package/dist/index.js CHANGED
@@ -391,7 +391,7 @@ var GroupNode = import_react2.default.memo(function GroupNode2({
391
391
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Wrapper, { children: [
392
392
  isExpandable && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CollapseIcon, { isExpanded }),
393
393
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GroupIcon, { width: 14, height: 14, color })
394
- ] }),
394
+ ] }, `group-${props.id}-${color}`),
395
395
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BranchNodeText, { children })
396
396
  ] });
397
397
  });
@@ -405,7 +405,7 @@ var ComponentNode = import_react2.default.memo(
405
405
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Wrapper, { children: [
406
406
  isExpandable && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CollapseIcon, { isExpanded }),
407
407
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ComponentIcon, { width: 12, height: 12, color })
408
- ] }),
408
+ ] }, `component-${props.id}-${color}`),
409
409
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BranchNodeText, { children })
410
410
  ] });
411
411
  }
@@ -417,7 +417,7 @@ var StoryNode = import_react2.default.memo(
417
417
  return props.selected ? theme.color.lightest : theme.color.seafoam;
418
418
  }, [props.selected, theme.color.lightest, theme.color.seafoam]);
419
419
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(LeafNode, { ...props, ref, children: [
420
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Wrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StoryIcon, { width: 14, height: 14, color }) }),
420
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Wrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StoryIcon, { width: 14, height: 14, color }) }, `story-${props.id}-${color}`),
421
421
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LeafNodeText, { selected: props.selected, children })
422
422
  ] });
423
423
  })
@@ -919,9 +919,20 @@ var SearchField = import_react_native_theming5.styled.View({
919
919
  flexDirection: "column",
920
920
  position: "relative"
921
921
  });
922
+ var inputPlatformSpecificStyles = import_react_native3.Platform.select({
923
+ macos: {
924
+ paddingVertical: 6
925
+ },
926
+ android: {
927
+ minHeight: 32
928
+ },
929
+ default: {
930
+ minHeight: 32,
931
+ height: 32
932
+ }
933
+ });
922
934
  var Input = (0, import_react_native_theming5.styled)(import_react_native3.TextInput)(({ theme }) => ({
923
- height: import_react_native3.Platform.OS === "android" ? "auto" : 32,
924
- minHeight: 32,
935
+ ...inputPlatformSpecificStyles,
925
936
  paddingLeft: 28,
926
937
  paddingRight: 28,
927
938
  borderWidth: 1,
@@ -1354,10 +1365,12 @@ var Sidebar = import_react11.default.memo(function Sidebar2({
1354
1365
  });
1355
1366
 
1356
1367
  // src/Layout.tsx
1368
+ var import_portal = require("@gorhom/portal");
1357
1369
  var import_react_native_theming10 = require("@storybook/react-native-theming");
1358
1370
  var import_react_native_ui_common7 = require("@storybook/react-native-ui-common");
1359
1371
  var import_react16 = require("react");
1360
1372
  var import_react_native10 = require("react-native");
1373
+ var import_react_native_safe_area_context2 = require("react-native-safe-area-context");
1361
1374
  var import_core_events = require("storybook/internal/core-events");
1362
1375
  var import_manager_api2 = require("storybook/manager-api");
1363
1376
 
@@ -1386,44 +1399,86 @@ var import_jsx_runtime10 = require("react/jsx-runtime");
1386
1399
  var MobileAddonsPanel = (0, import_react13.forwardRef)(
1387
1400
  ({ storyId }, ref) => {
1388
1401
  const theme = (0, import_react_native_theming8.useTheme)();
1389
- const [mobileMenuOpen, setMobileMenuOpen] = (0, import_react13.useState)(false);
1390
1402
  const { height } = (0, import_react_native7.useWindowDimensions)();
1391
- const panelHeight = useAnimatedValue(height / 2);
1392
- const positionBottomAnimation = useAnimatedValue(0);
1403
+ const panelHeight = useAnimatedValue(0);
1404
+ const positionBottomAnimation = useAnimatedValue(height / 2);
1405
+ const [isOpen, setIsOpen] = (0, import_react13.useState)(false);
1406
+ const setMobileMenuOpen = (0, import_react13.useCallback)(
1407
+ (open) => {
1408
+ setIsOpen(open);
1409
+ if (open) {
1410
+ import_react_native7.Animated.parallel([
1411
+ import_react_native7.Animated.timing(positionBottomAnimation, {
1412
+ toValue: 0,
1413
+ // Negative to move up
1414
+ duration: 350,
1415
+ useNativeDriver: false,
1416
+ easing: import_react_native7.Easing.inOut(import_react_native7.Easing.cubic)
1417
+ }),
1418
+ import_react_native7.Animated.timing(panelHeight, {
1419
+ toValue: height / 2,
1420
+ duration: 350,
1421
+ useNativeDriver: false,
1422
+ easing: import_react_native7.Easing.inOut(import_react_native7.Easing.cubic)
1423
+ })
1424
+ ]).start();
1425
+ } else {
1426
+ import_react_native7.Animated.parallel([
1427
+ import_react_native7.Animated.timing(positionBottomAnimation, {
1428
+ toValue: height / 2,
1429
+ duration: 350,
1430
+ useNativeDriver: false,
1431
+ easing: import_react_native7.Easing.inOut(import_react_native7.Easing.cubic)
1432
+ }),
1433
+ import_react_native7.Animated.timing(panelHeight, {
1434
+ toValue: 0,
1435
+ duration: 350,
1436
+ useNativeDriver: false,
1437
+ easing: import_react_native7.Easing.inOut(import_react_native7.Easing.cubic)
1438
+ })
1439
+ ]).start();
1440
+ }
1441
+ },
1442
+ [height, positionBottomAnimation, panelHeight]
1443
+ );
1393
1444
  (0, import_react13.useEffect)(() => {
1394
1445
  const handleKeyboardShow = ({ endCoordinates, duration, easing }) => {
1395
- import_react_native7.Animated.parallel([
1396
- import_react_native7.Animated.timing(positionBottomAnimation, {
1397
- toValue: -endCoordinates.height,
1398
- // Negative to move up
1399
- duration,
1400
- useNativeDriver: false,
1401
- easing: import_react_native7.Easing[easing] || import_react_native7.Easing.out(import_react_native7.Easing.ease)
1402
- }),
1403
- import_react_native7.Animated.timing(panelHeight, {
1404
- toValue: (height - endCoordinates.height) / 2,
1405
- duration: duration + 250,
1406
- useNativeDriver: false,
1407
- easing: import_react_native7.Easing[easing] || import_react_native7.Easing.out(import_react_native7.Easing.ease)
1408
- })
1409
- ]).start();
1446
+ if (isOpen) {
1447
+ import_react_native7.Animated.parallel([
1448
+ import_react_native7.Animated.timing(panelHeight, {
1449
+ toValue: (height - endCoordinates.height) / 2,
1450
+ duration,
1451
+ useNativeDriver: false,
1452
+ easing: import_react_native7.Easing[easing] || import_react_native7.Easing.out(import_react_native7.Easing.ease)
1453
+ }),
1454
+ import_react_native7.Animated.timing(positionBottomAnimation, {
1455
+ toValue: -endCoordinates.height,
1456
+ // Negative to move up
1457
+ duration,
1458
+ useNativeDriver: false,
1459
+ easing: import_react_native7.Easing[easing] || import_react_native7.Easing.out(import_react_native7.Easing.ease)
1460
+ })
1461
+ ]).start();
1462
+ }
1410
1463
  };
1411
1464
  const handleKeyboardHide = ({ duration, easing }) => {
1412
- import_react_native7.Animated.parallel([
1413
- import_react_native7.Animated.timing(positionBottomAnimation, {
1414
- toValue: 0,
1415
- // Back to original position
1416
- duration,
1417
- useNativeDriver: false,
1418
- easing: import_react_native7.Easing[easing] || import_react_native7.Easing.out(import_react_native7.Easing.ease)
1419
- }),
1420
- import_react_native7.Animated.timing(panelHeight, {
1421
- toValue: height / 2,
1422
- duration,
1423
- useNativeDriver: false,
1424
- easing: import_react_native7.Easing[easing] || import_react_native7.Easing.out(import_react_native7.Easing.ease)
1425
- })
1426
- ]).start();
1465
+ if (isOpen) {
1466
+ import_react_native7.Animated.parallel([
1467
+ import_react_native7.Animated.timing(positionBottomAnimation, {
1468
+ toValue: 0,
1469
+ // Back to original position
1470
+ duration,
1471
+ useNativeDriver: false,
1472
+ easing: import_react_native7.Easing[easing] || import_react_native7.Easing.out(import_react_native7.Easing.ease)
1473
+ }),
1474
+ import_react_native7.Animated.timing(panelHeight, {
1475
+ toValue: height / 2,
1476
+ duration,
1477
+ useNativeDriver: false,
1478
+ easing: import_react_native7.Easing[easing] || import_react_native7.Easing.out(import_react_native7.Easing.ease)
1479
+ })
1480
+ ]).start();
1481
+ }
1427
1482
  };
1428
1483
  const showSubscription = import_react_native7.Keyboard.addListener("keyboardDidShow", handleKeyboardShow);
1429
1484
  const willShowSubscription = import_react_native7.Keyboard.addListener("keyboardWillShow", handleKeyboardShow);
@@ -1435,7 +1490,7 @@ var MobileAddonsPanel = (0, import_react13.forwardRef)(
1435
1490
  hideSubscription.remove();
1436
1491
  didHideSubscription.remove();
1437
1492
  };
1438
- }, [height, panelHeight, positionBottomAnimation]);
1493
+ }, [height, panelHeight, positionBottomAnimation, isOpen]);
1439
1494
  (0, import_react13.useImperativeHandle)(ref, () => ({
1440
1495
  setAddonsPanelOpen: (open) => {
1441
1496
  if (open) {
@@ -1445,9 +1500,6 @@ var MobileAddonsPanel = (0, import_react13.forwardRef)(
1445
1500
  }
1446
1501
  }
1447
1502
  }));
1448
- if (!mobileMenuOpen) {
1449
- return null;
1450
- }
1451
1503
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1452
1504
  import_react_native7.Animated.View,
1453
1505
  {
@@ -1576,7 +1628,8 @@ var AddonsTabs = ({ onClose, storyId }) => {
1576
1628
  style: addonsScrollStyle,
1577
1629
  contentContainerStyle: scrollContentContainerStyle,
1578
1630
  children: panel
1579
- }
1631
+ },
1632
+ `addons-scroll-${storyId}`
1580
1633
  )
1581
1634
  ] });
1582
1635
  };
@@ -1604,11 +1657,60 @@ var import_react_native_theming9 = require("@storybook/react-native-theming");
1604
1657
  var import_react14 = require("react");
1605
1658
  var import_react_native8 = require("react-native");
1606
1659
  var import_jsx_runtime11 = require("react/jsx-runtime");
1660
+ var useAnimatedModalHeight = () => {
1661
+ const { height } = (0, import_react_native8.useWindowDimensions)();
1662
+ const animatedHeight = useAnimatedValue(0.65 * height);
1663
+ (0, import_react14.useEffect)(() => {
1664
+ const modalHeight = 0.65 * height;
1665
+ const maxModalHeight = 0.85 * height;
1666
+ const expand = (duration = 250) => import_react_native8.Animated.timing(animatedHeight, {
1667
+ toValue: maxModalHeight,
1668
+ duration,
1669
+ useNativeDriver: false
1670
+ }).start();
1671
+ const collapse = (duration = 250) => import_react_native8.Animated.timing(animatedHeight, {
1672
+ toValue: modalHeight,
1673
+ duration,
1674
+ useNativeDriver: false
1675
+ }).start();
1676
+ const handleKeyboardWillShow = (e) => {
1677
+ if (import_react_native8.Platform.OS === "ios") {
1678
+ expand(e.duration);
1679
+ }
1680
+ };
1681
+ const handleKeyboardDidShow = (e) => {
1682
+ if (import_react_native8.Platform.OS === "android") {
1683
+ expand();
1684
+ }
1685
+ };
1686
+ const handleKeyboardWillHide = (e) => {
1687
+ if (import_react_native8.Platform.OS === "ios") {
1688
+ collapse(e.duration);
1689
+ }
1690
+ };
1691
+ const handleKeyboardDidHide = (e) => {
1692
+ if (import_react_native8.Platform.OS === "android") {
1693
+ collapse();
1694
+ }
1695
+ };
1696
+ const subscriptions = [
1697
+ import_react_native8.Keyboard.addListener("keyboardWillShow", handleKeyboardWillShow),
1698
+ import_react_native8.Keyboard.addListener("keyboardDidShow", handleKeyboardDidShow),
1699
+ import_react_native8.Keyboard.addListener("keyboardWillHide", handleKeyboardWillHide),
1700
+ import_react_native8.Keyboard.addListener("keyboardDidHide", handleKeyboardDidHide)
1701
+ ];
1702
+ return () => {
1703
+ subscriptions.forEach((subscription) => subscription.remove());
1704
+ };
1705
+ }, [animatedHeight, height]);
1706
+ return animatedHeight;
1707
+ };
1607
1708
  var MobileMenuDrawer = (0, import_react14.memo)(
1608
1709
  (0, import_react14.forwardRef)(({ children }, ref) => {
1609
1710
  const [mobileMenuOpen, setMobileMenuOpen] = (0, import_react14.useState)(false);
1610
1711
  const { scrollToSelectedNode, scrollRef } = useSelectedNode();
1611
1712
  const theme = (0, import_react_native_theming9.useTheme)();
1713
+ const animatedHeight = useAnimatedModalHeight();
1612
1714
  const dragY = useAnimatedValue(0);
1613
1715
  const panResponder = (0, import_react14.useRef)(
1614
1716
  import_react_native8.PanResponder.create({
@@ -1666,14 +1768,14 @@ var MobileMenuDrawer = (0, import_react14.memo)(
1666
1768
  transparent: true,
1667
1769
  statusBarTranslucent: true,
1668
1770
  onRequestClose: () => setMobileMenuOpen(false),
1669
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native8.KeyboardAvoidingView, { behavior: "height", style: { flex: 1 }, children: [
1771
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native8.Animated.View, { style: { flex: 1 }, children: [
1670
1772
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native8.View, { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native8.Pressable, { style: { flex: 1 }, onPress: () => setMobileMenuOpen(false) }) }),
1671
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1773
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native8.Animated.View, { style: { height: animatedHeight }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1672
1774
  import_react_native8.Animated.View,
1673
1775
  {
1674
1776
  style: [
1675
1777
  {
1676
- height: "65%",
1778
+ flex: 1,
1677
1779
  borderTopColor: theme.appBorderColor,
1678
1780
  borderTopWidth: 1,
1679
1781
  borderStyle: "solid",
@@ -1712,7 +1814,7 @@ var MobileMenuDrawer = (0, import_react14.memo)(
1712
1814
  )
1713
1815
  ]
1714
1816
  }
1715
- )
1817
+ ) })
1716
1818
  ] })
1717
1819
  }
1718
1820
  );
@@ -1803,7 +1905,6 @@ var StorybookLogo = ({ theme }) => {
1803
1905
  };
1804
1906
 
1805
1907
  // src/Layout.tsx
1806
- var import_react_native_safe_area_context2 = require("react-native-safe-area-context");
1807
1908
  var import_jsx_runtime13 = require("react/jsx-runtime");
1808
1909
  var desktopLogoContainer = {
1809
1910
  flexDirection: "row",
@@ -1825,7 +1926,10 @@ var mobileMenuDrawerContentStyle = {
1825
1926
  paddingTop: 4,
1826
1927
  paddingBottom: 4
1827
1928
  };
1828
- var LiteUI = ({ storage, theme, storyHash, story, children }) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native_safe_area_context2.SafeAreaProvider, { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native_theming10.ThemeProvider, { theme, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native_ui_common7.StorageProvider, { storage, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native_ui_common7.LayoutProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Layout, { storyHash, story, children }) }) }) }) });
1929
+ var LiteUI = ({ storage, theme, storyHash, story, children }) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native_safe_area_context2.SafeAreaProvider, { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native_theming10.ThemeProvider, { theme, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native_ui_common7.StorageProvider, { storage, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native_ui_common7.LayoutProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_portal.PortalProvider, { shouldAddRootHost: false, children: [
1930
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Layout, { storyHash, story, children }),
1931
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_portal.PortalHost, { name: "storybook-lite-ui-root" })
1932
+ ] }) }) }) }) });
1829
1933
  var Layout = ({
1830
1934
  storyHash,
1831
1935
  story,
@@ -1885,7 +1989,7 @@ var Layout = ({
1885
1989
  const fullScreenButtonStyle = (0, import_react_native_ui_common7.useStyle)(
1886
1990
  () => ({
1887
1991
  position: "absolute",
1888
- bottom: uiHidden ? 56 : 16,
1992
+ bottom: uiHidden ? insets.bottom + 56 : 16,
1889
1993
  right: 16,
1890
1994
  backgroundColor: theme.background.content,
1891
1995
  padding: 4,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storybook/react-native-ui-lite",
3
- "version": "10.0.1",
3
+ "version": "10.0.2-alpha.1",
4
4
  "description": "lightweight ui components for react native storybook",
5
5
  "keywords": [
6
6
  "react",
@@ -58,9 +58,10 @@
58
58
  "typescript": "~5.9.3"
59
59
  },
60
60
  "dependencies": {
61
+ "@gorhom/portal": "^1.0.14",
61
62
  "@storybook/react": "^10.0.1",
62
- "@storybook/react-native-theming": "^10.0.1",
63
- "@storybook/react-native-ui-common": "^10.0.1",
63
+ "@storybook/react-native-theming": "^10.0.2-alpha.1",
64
+ "@storybook/react-native-ui-common": "^10.0.2-alpha.1",
64
65
  "fuse.js": "^7.1.0",
65
66
  "polished": "^4.3.1",
66
67
  "react-native-safe-area-context": "^5"
@@ -76,5 +77,5 @@
76
77
  "publishConfig": {
77
78
  "access": "public"
78
79
  },
79
- "gitHead": "7604d1c21f15767940d30d9c5dcf9da73d0a9fe7"
80
+ "gitHead": "14fb007dc99d435fd405054b84bedb38b96ab8c5"
80
81
  }
package/src/Layout.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import type { Args, StoryContext } from 'storybook/internal/csf';
1
+ import { PortalHost, PortalProvider } from '@gorhom/portal';
2
2
  import type { ReactRenderer } from '@storybook/react';
3
3
  import { styled, ThemeProvider, useTheme } from '@storybook/react-native-theming';
4
4
  import {
@@ -11,10 +11,12 @@ import {
11
11
  useStyle,
12
12
  } from '@storybook/react-native-ui-common';
13
13
  import { ReactElement, ReactNode, useCallback, useRef, useState } from 'react';
14
- import { Platform, ScrollView, Text, TouchableOpacity, View, ViewStyle } from 'react-native';
14
+ import { ScrollView, Text, TouchableOpacity, View, ViewStyle } from 'react-native';
15
+ import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
15
16
  import { SET_CURRENT_STORY } from 'storybook/internal/core-events';
16
- import { addons } from 'storybook/manager-api';
17
+ import type { Args, StoryContext } from 'storybook/internal/csf';
17
18
  import { type API_IndexHash } from 'storybook/internal/types';
19
+ import { addons } from 'storybook/manager-api';
18
20
  import { AddonsTabs, MobileAddonsPanel, MobileAddonsPanelRef } from './MobileAddonsPanel';
19
21
  import { MobileMenuDrawer, MobileMenuDrawerRef } from './MobileMenuDrawer';
20
22
  import { SelectedNodeProvider } from './SelectedNodeProvider';
@@ -27,7 +29,6 @@ import {
27
29
  FullscreenIcon,
28
30
  MenuIcon,
29
31
  } from './icon/iconDataUris';
30
- import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
31
32
 
32
33
  const desktopLogoContainer = {
33
34
  flexDirection: 'row',
@@ -62,9 +63,12 @@ export const LiteUI: SBUI = ({ storage, theme, storyHash, story, children }): Re
62
63
  <ThemeProvider theme={theme}>
63
64
  <StorageProvider storage={storage}>
64
65
  <LayoutProvider>
65
- <Layout storyHash={storyHash} story={story}>
66
- {children}
67
- </Layout>
66
+ <PortalProvider shouldAddRootHost={false}>
67
+ <Layout storyHash={storyHash} story={story}>
68
+ {children}
69
+ </Layout>
70
+ <PortalHost name="storybook-lite-ui-root" />
71
+ </PortalProvider>
68
72
  </LayoutProvider>
69
73
  </StorageProvider>
70
74
  </ThemeProvider>
@@ -145,7 +149,7 @@ export const Layout = ({
145
149
  const fullScreenButtonStyle = useStyle(
146
150
  () => ({
147
151
  position: 'absolute',
148
- bottom: uiHidden ? 56 : 16,
152
+ bottom: uiHidden ? insets.bottom + 56 : 16,
149
153
  right: 16,
150
154
  backgroundColor: theme.background.content,
151
155
  padding: 4,
@@ -1,6 +1,6 @@
1
1
  import { styled, useTheme } from '@storybook/react-native-theming';
2
2
  import { IconButton, useStyle } from '@storybook/react-native-ui-common';
3
- import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react';
3
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';
4
4
  import {
5
5
  Animated,
6
6
  Easing,
@@ -26,48 +26,91 @@ export interface MobileAddonsPanelRef {
26
26
  export const MobileAddonsPanel = forwardRef<MobileAddonsPanelRef, { storyId?: string }>(
27
27
  ({ storyId }, ref) => {
28
28
  const theme = useTheme();
29
- const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
30
29
  const { height } = useWindowDimensions();
31
- const panelHeight = useAnimatedValue(height / 2);
32
- const positionBottomAnimation = useAnimatedValue(0);
30
+ const panelHeight = useAnimatedValue(0);
31
+ const positionBottomAnimation = useAnimatedValue(height / 2);
32
+ const [isOpen, setIsOpen] = useState(false);
33
+
34
+ const setMobileMenuOpen = useCallback(
35
+ (open: boolean) => {
36
+ setIsOpen(open);
37
+
38
+ if (open) {
39
+ Animated.parallel([
40
+ Animated.timing(positionBottomAnimation, {
41
+ toValue: 0, // Negative to move up
42
+ duration: 350,
43
+ useNativeDriver: false,
44
+ easing: Easing.inOut(Easing.cubic),
45
+ }),
46
+
47
+ Animated.timing(panelHeight, {
48
+ toValue: height / 2,
49
+ duration: 350,
50
+ useNativeDriver: false,
51
+ easing: Easing.inOut(Easing.cubic),
52
+ }),
53
+ ]).start();
54
+ } else {
55
+ Animated.parallel([
56
+ Animated.timing(positionBottomAnimation, {
57
+ toValue: height / 2,
58
+ duration: 350,
59
+ useNativeDriver: false,
60
+ easing: Easing.inOut(Easing.cubic),
61
+ }),
62
+ Animated.timing(panelHeight, {
63
+ toValue: 0,
64
+ duration: 350,
65
+ useNativeDriver: false,
66
+ easing: Easing.inOut(Easing.cubic),
67
+ }),
68
+ ]).start();
69
+ }
70
+ },
71
+ [height, positionBottomAnimation, panelHeight]
72
+ );
33
73
 
34
74
  useEffect(() => {
35
75
  // Define keyboard show handler
36
76
  const handleKeyboardShow = ({ endCoordinates, duration, easing }) => {
37
- Animated.parallel([
38
- Animated.timing(positionBottomAnimation, {
39
- toValue: -endCoordinates.height, // Negative to move up
40
- duration,
41
- useNativeDriver: false,
42
- easing: Easing[easing] || Easing.out(Easing.ease),
43
- }),
44
-
45
- Animated.timing(panelHeight, {
46
- toValue: (height - endCoordinates.height) / 2,
47
- duration: duration + 250,
48
- useNativeDriver: false,
49
- easing: Easing[easing] || Easing.out(Easing.ease),
50
- }),
51
- ]).start();
77
+ if (isOpen) {
78
+ Animated.parallel([
79
+ Animated.timing(panelHeight, {
80
+ toValue: (height - endCoordinates.height) / 2,
81
+ duration,
82
+ useNativeDriver: false,
83
+ easing: Easing[easing] || Easing.out(Easing.ease),
84
+ }),
85
+ Animated.timing(positionBottomAnimation, {
86
+ toValue: -endCoordinates.height, // Negative to move up
87
+ duration,
88
+ useNativeDriver: false,
89
+ easing: Easing[easing] || Easing.out(Easing.ease),
90
+ }),
91
+ ]).start();
92
+ }
52
93
  };
53
94
 
54
95
  // Define keyboard hide handler
55
96
  const handleKeyboardHide = ({ duration, easing }) => {
56
- Animated.parallel([
57
- Animated.timing(positionBottomAnimation, {
58
- toValue: 0, // Back to original position
59
- duration,
60
- useNativeDriver: false,
61
- easing: Easing[easing] || Easing.out(Easing.ease),
62
- }),
97
+ if (isOpen) {
98
+ Animated.parallel([
99
+ Animated.timing(positionBottomAnimation, {
100
+ toValue: 0, // Back to original position
101
+ duration,
102
+ useNativeDriver: false,
103
+ easing: Easing[easing] || Easing.out(Easing.ease),
104
+ }),
63
105
 
64
- Animated.timing(panelHeight, {
65
- toValue: height / 2,
66
- duration,
67
- useNativeDriver: false,
68
- easing: Easing[easing] || Easing.out(Easing.ease),
69
- }),
70
- ]).start();
106
+ Animated.timing(panelHeight, {
107
+ toValue: height / 2,
108
+ duration,
109
+ useNativeDriver: false,
110
+ easing: Easing[easing] || Easing.out(Easing.ease),
111
+ }),
112
+ ]).start();
113
+ }
71
114
  };
72
115
 
73
116
  // Add keyboard event listeners
@@ -83,7 +126,7 @@ export const MobileAddonsPanel = forwardRef<MobileAddonsPanelRef, { storyId?: st
83
126
  hideSubscription.remove();
84
127
  didHideSubscription.remove();
85
128
  };
86
- }, [height, panelHeight, positionBottomAnimation]);
129
+ }, [height, panelHeight, positionBottomAnimation, isOpen]);
87
130
 
88
131
  useImperativeHandle(ref, () => ({
89
132
  setAddonsPanelOpen: (open: boolean) => {
@@ -95,10 +138,6 @@ export const MobileAddonsPanel = forwardRef<MobileAddonsPanelRef, { storyId?: st
95
138
  },
96
139
  }));
97
140
 
98
- if (!mobileMenuOpen) {
99
- return null;
100
- }
101
-
102
141
  return (
103
142
  <Animated.View
104
143
  style={{
@@ -234,6 +273,7 @@ export const AddonsTabs = ({ onClose, storyId }: { onClose?: () => void; storyId
234
273
  />
235
274
  </View>
236
275
  <ScrollView
276
+ key={`addons-scroll-${storyId}`}
237
277
  style={addonsScrollStyle}
238
278
  // keyboardShouldPersistTaps="handled"
239
279
  contentContainerStyle={scrollContentContainerStyle}
@@ -1,15 +1,26 @@
1
1
  import { useTheme } from '@storybook/react-native-theming';
2
- import { forwardRef, memo, ReactNode, useImperativeHandle, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ forwardRef,
4
+ memo,
5
+ ReactNode,
6
+ useEffect,
7
+ useImperativeHandle,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ } from 'react';
3
12
  import {
4
13
  Animated,
5
14
  Keyboard,
6
- KeyboardAvoidingView,
15
+ useWindowDimensions,
7
16
  Modal,
8
17
  PanResponder,
9
18
  PanResponderInstance,
10
19
  Pressable,
11
20
  ScrollView,
12
21
  View,
22
+ KeyboardEventListener,
23
+ Platform,
13
24
  } from 'react-native';
14
25
  import { useSelectedNode } from './SelectedNodeProvider';
15
26
  import useAnimatedValue from './useAnimatedValue';
@@ -22,11 +33,73 @@ export interface MobileMenuDrawerRef {
22
33
  setMobileMenuOpen: (isOpen: boolean) => void;
23
34
  }
24
35
 
36
+ export const useAnimatedModalHeight = () => {
37
+ const { height } = useWindowDimensions();
38
+ const animatedHeight = useAnimatedValue(0.65 * height);
39
+
40
+ useEffect(() => {
41
+ const modalHeight = 0.65 * height;
42
+ const maxModalHeight = 0.85 * height;
43
+
44
+ const expand = (duration: number = 250) =>
45
+ Animated.timing(animatedHeight, {
46
+ toValue: maxModalHeight,
47
+ duration,
48
+ useNativeDriver: false,
49
+ }).start();
50
+
51
+ const collapse = (duration: number = 250) =>
52
+ Animated.timing(animatedHeight, {
53
+ toValue: modalHeight,
54
+ duration,
55
+ useNativeDriver: false,
56
+ }).start();
57
+
58
+ const handleKeyboardWillShow: KeyboardEventListener = (e) => {
59
+ if (Platform.OS === 'ios') {
60
+ expand(e.duration);
61
+ }
62
+ };
63
+
64
+ const handleKeyboardDidShow: KeyboardEventListener = (e) => {
65
+ if (Platform.OS === 'android') {
66
+ expand();
67
+ }
68
+ };
69
+
70
+ const handleKeyboardWillHide: KeyboardEventListener = (e) => {
71
+ if (Platform.OS === 'ios') {
72
+ collapse(e.duration);
73
+ }
74
+ };
75
+
76
+ const handleKeyboardDidHide: KeyboardEventListener = (e) => {
77
+ if (Platform.OS === 'android') {
78
+ collapse();
79
+ }
80
+ };
81
+
82
+ const subscriptions = [
83
+ Keyboard.addListener('keyboardWillShow', handleKeyboardWillShow),
84
+ Keyboard.addListener('keyboardDidShow', handleKeyboardDidShow),
85
+ Keyboard.addListener('keyboardWillHide', handleKeyboardWillHide),
86
+ Keyboard.addListener('keyboardDidHide', handleKeyboardDidHide),
87
+ ];
88
+
89
+ return () => {
90
+ subscriptions.forEach((subscription) => subscription.remove());
91
+ };
92
+ }, [animatedHeight, height]);
93
+
94
+ return animatedHeight;
95
+ };
96
+
25
97
  export const MobileMenuDrawer = memo(
26
98
  forwardRef<MobileMenuDrawerRef, MobileMenuDrawerProps>(({ children }, ref) => {
27
99
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
28
100
  const { scrollToSelectedNode, scrollRef } = useSelectedNode();
29
101
  const theme = useTheme();
102
+ const animatedHeight = useAnimatedModalHeight();
30
103
 
31
104
  // Create a reference for the drag handle animation
32
105
  const dragY = useAnimatedValue(0);
@@ -95,51 +168,53 @@ export const MobileMenuDrawer = memo(
95
168
  statusBarTranslucent
96
169
  onRequestClose={() => setMobileMenuOpen(false)}
97
170
  >
98
- <KeyboardAvoidingView behavior="height" style={{ flex: 1 }}>
171
+ <Animated.View style={{ flex: 1 }}>
99
172
  <View style={{ flex: 1 }}>
100
173
  <Pressable style={{ flex: 1 }} onPress={() => setMobileMenuOpen(false)}></Pressable>
101
174
  </View>
102
175
 
103
- <Animated.View
104
- style={[
105
- {
106
- height: '65%',
107
- borderTopColor: theme.appBorderColor,
108
- borderTopWidth: 1,
109
- borderStyle: 'solid',
110
- backgroundColor: theme.background.content,
111
- elevation: 8,
112
- },
113
- { transform: [{ translateY: dragY }] },
114
- ]}
115
- >
116
- {/* Drag handle */}
117
- <View
118
- {...panResponder.panHandlers}
119
- style={{
120
- alignItems: 'center',
121
- justifyContent: 'center',
122
- backgroundColor: theme.background.content,
123
- }}
124
- >
125
- <View style={handleStyle} />
126
- </View>
127
-
128
- <ScrollView
129
- ref={scrollRef}
130
- keyboardShouldPersistTaps="handled"
131
- style={{
132
- flex: 1,
133
- paddingBottom: 150,
134
- alignSelf: 'flex-end',
135
- width: '100%',
136
- backgroundColor: theme.background.content,
137
- }}
176
+ <Animated.View style={{ height: animatedHeight }}>
177
+ <Animated.View
178
+ style={[
179
+ {
180
+ flex: 1,
181
+ borderTopColor: theme.appBorderColor,
182
+ borderTopWidth: 1,
183
+ borderStyle: 'solid',
184
+ backgroundColor: theme.background.content,
185
+ elevation: 8,
186
+ },
187
+ { transform: [{ translateY: dragY }] },
188
+ ]}
138
189
  >
139
- {children}
140
- </ScrollView>
190
+ {/* Drag handle */}
191
+ <View
192
+ {...panResponder.panHandlers}
193
+ style={{
194
+ alignItems: 'center',
195
+ justifyContent: 'center',
196
+ backgroundColor: theme.background.content,
197
+ }}
198
+ >
199
+ <View style={handleStyle} />
200
+ </View>
201
+
202
+ <ScrollView
203
+ ref={scrollRef}
204
+ keyboardShouldPersistTaps="handled"
205
+ style={{
206
+ flex: 1,
207
+ paddingBottom: 150,
208
+ alignSelf: 'flex-end',
209
+ width: '100%',
210
+ backgroundColor: theme.background.content,
211
+ }}
212
+ >
213
+ {children}
214
+ </ScrollView>
215
+ </Animated.View>
141
216
  </Animated.View>
142
- </KeyboardAvoidingView>
217
+ </Animated.View>
143
218
  </Modal>
144
219
  );
145
220
  })
package/src/Search.tsx CHANGED
@@ -53,9 +53,21 @@ const SearchField = styled.View({
53
53
  position: 'relative',
54
54
  });
55
55
 
56
+ const inputPlatformSpecificStyles = Platform.select({
57
+ macos: {
58
+ paddingVertical: 6,
59
+ },
60
+ android: {
61
+ minHeight: 32,
62
+ },
63
+ default: {
64
+ minHeight: 32,
65
+ height: 32,
66
+ },
67
+ });
68
+
56
69
  const Input = styled(TextInput)(({ theme }) => ({
57
- height: Platform.OS === 'android' ? 'auto' : 32,
58
- minHeight: 32,
70
+ ...inputPlatformSpecificStyles,
59
71
  paddingLeft: 28,
60
72
  paddingRight: 28,
61
73
  borderWidth: 1,
package/src/TreeNode.tsx CHANGED
@@ -100,7 +100,7 @@ export const GroupNode: FC<
100
100
 
101
101
  return (
102
102
  <BranchNode isExpandable={isExpandable} {...props}>
103
- <Wrapper>
103
+ <Wrapper key={`group-${props.id}-${color}`}>
104
104
  {isExpandable && <CollapseIcon isExpanded={isExpanded} />}
105
105
  <GroupIcon width={14} height={14} color={color} />
106
106
  </Wrapper>
@@ -119,7 +119,8 @@ export const ComponentNode: FC<ComponentProps<typeof BranchNode>> = React.memo(
119
119
 
120
120
  return (
121
121
  <BranchNode isExpandable={isExpandable} {...props}>
122
- <Wrapper>
122
+ {/* workaround for macos icon color bug */}
123
+ <Wrapper key={`component-${props.id}-${color}`}>
123
124
  {isExpandable && <CollapseIcon isExpanded={isExpanded} />}
124
125
  <ComponentIcon width={12} height={12} color={color} />
125
126
  </Wrapper>
@@ -142,7 +143,7 @@ export const StoryNode = React.memo(
142
143
 
143
144
  return (
144
145
  <LeafNode {...props} ref={ref}>
145
- <Wrapper>
146
+ <Wrapper key={`story-${props.id}-${color}`}>
146
147
  <StoryIcon width={14} height={14} color={color} />
147
148
  </Wrapper>
148
149
  <LeafNodeText selected={props.selected}>{children}</LeafNodeText>
@@ -0,0 +1,63 @@
1
+ // taken from https://github.com/react-native-community/hooks/blob/main/src/useKeyboard.ts
2
+ import { useEffect, useState } from 'react';
3
+ import { Keyboard, KeyboardEventListener, KeyboardMetrics } from 'react-native';
4
+
5
+ const emptyCoordinates = Object.freeze({
6
+ screenX: 0,
7
+ screenY: 0,
8
+ width: 0,
9
+ height: 0,
10
+ });
11
+ const initialValue = {
12
+ start: emptyCoordinates,
13
+ end: emptyCoordinates,
14
+ };
15
+
16
+ export function useKeyboard() {
17
+ const [shown, setShown] = useState(false);
18
+ const [coordinates, setCoordinates] = useState<{
19
+ start: undefined | KeyboardMetrics;
20
+ end: KeyboardMetrics;
21
+ }>(initialValue);
22
+ const [keyboardHeight, setKeyboardHeight] = useState<number>(0);
23
+
24
+ const handleKeyboardWillShow: KeyboardEventListener = (e) => {
25
+ setCoordinates({ start: e.startCoordinates, end: e.endCoordinates });
26
+ };
27
+ const handleKeyboardDidShow: KeyboardEventListener = (e) => {
28
+ setShown(true);
29
+ setCoordinates({ start: e.startCoordinates, end: e.endCoordinates });
30
+ setKeyboardHeight(e.endCoordinates.height);
31
+ };
32
+ const handleKeyboardWillHide: KeyboardEventListener = (e) => {
33
+ setCoordinates({ start: e.startCoordinates, end: e.endCoordinates });
34
+ };
35
+ const handleKeyboardDidHide: KeyboardEventListener = (e) => {
36
+ setShown(false);
37
+ if (e) {
38
+ setCoordinates({ start: e.startCoordinates, end: e.endCoordinates });
39
+ } else {
40
+ setCoordinates(initialValue);
41
+ setKeyboardHeight(0);
42
+ }
43
+ };
44
+
45
+ useEffect(() => {
46
+ const subscriptions = [
47
+ Keyboard.addListener('keyboardWillShow', handleKeyboardWillShow),
48
+ Keyboard.addListener('keyboardDidShow', handleKeyboardDidShow),
49
+ Keyboard.addListener('keyboardWillHide', handleKeyboardWillHide),
50
+ Keyboard.addListener('keyboardDidHide', handleKeyboardDidHide),
51
+ ];
52
+
53
+ return () => {
54
+ subscriptions.forEach((subscription) => subscription.remove());
55
+ };
56
+ }, []);
57
+
58
+ return {
59
+ keyboardShown: shown,
60
+ coordinates,
61
+ keyboardHeight,
62
+ };
63
+ }