diff --git a/packages/react-native-ui/src/Layout.tsx b/packages/react-native-ui/src/Layout.tsx index 0f277edc08..922cd0ec16 100644 --- a/packages/react-native-ui/src/Layout.tsx +++ b/packages/react-native-ui/src/Layout.tsx @@ -3,7 +3,7 @@ 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 { ReactNode, useRef } from 'react'; import { ScrollView, Text, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { IconButton } from './IconButton'; @@ -16,6 +16,7 @@ import { BottomBarToggleIcon } from './icon/BottomBarToggleIcon'; import { DarkLogo } from './icon/DarkLogo'; import { Logo } from './icon/Logo'; import { MenuIcon } from './icon/MenuIcon'; +import { useStoreBooleanState } from './hooks/useStoreState'; export const Layout = ({ storyHash, @@ -32,9 +33,15 @@ export const Layout = ({ const insets = useSafeAreaInsets(); const { isDesktop } = useLayout(); - const [desktopSidebarOpen, setDesktopSidebarOpen] = useState(true); + const [desktopSidebarOpen, setDesktopSidebarOpen] = useStoreBooleanState( + 'desktopSidebarState', + true + ); - const [desktopAddonsPanelOpen, setDesktopAddonsPanelOpen] = useState(true); + const [desktopAddonsPanelOpen, setDesktopAddonsPanelOpen] = useStoreBooleanState( + 'desktopPanelState', + true + ); if (isDesktop) { return ( diff --git a/packages/react-native-ui/src/Sidebar.tsx b/packages/react-native-ui/src/Sidebar.tsx index afce1e24ad..6dc34699e7 100644 --- a/packages/react-native-ui/src/Sidebar.tsx +++ b/packages/react-native-ui/src/Sidebar.tsx @@ -10,7 +10,7 @@ import { Explorer } from './Explorer'; import { Search } from './Search'; import { SearchResults } from './SearchResults'; import type { CombinedDataset, Selection } from './types'; -import { useLastViewed } from './useLastViewed'; +import { useLastViewed } from './hooks/useLastViewed'; import { DEFAULT_REF_ID } from './constants'; import { View } from 'react-native'; diff --git a/packages/react-native-ui/src/StorageProvider.tsx b/packages/react-native-ui/src/StorageProvider.tsx new file mode 100644 index 0000000000..0c68715854 --- /dev/null +++ b/packages/react-native-ui/src/StorageProvider.tsx @@ -0,0 +1,21 @@ +import type { FC, PropsWithChildren } from 'react'; +import { createContext, useContext } from 'react'; + +interface Storage { + getItem: (key: string) => Promise; + setItem: (key: string, value: string) => Promise; +} + +const StorageContext = createContext({ + getItem: async () => null, + setItem: async () => {}, +}); + +export const StorageProvider: FC> = ({ + storage, + children, +}) => { + return {children}; +}; + +export const useStorage = () => useContext(StorageContext); diff --git a/packages/react-native-ui/src/Tree.tsx b/packages/react-native-ui/src/Tree.tsx index 61e686e638..b1f7da404b 100644 --- a/packages/react-native-ui/src/Tree.tsx +++ b/packages/react-native-ui/src/Tree.tsx @@ -14,8 +14,8 @@ import { CollapseAllIcon } from './icon/CollapseAllIcon'; import { CollapseIcon } from './icon/CollapseIcon'; import { ExpandAllIcon } from './icon/ExpandAllIcon'; import { Item } from './types'; -import type { ExpandAction, ExpandedState } from './useExpanded'; -import { useExpanded } from './useExpanded'; +import type { ExpandAction, ExpandedState } from './hooks/useExpanded'; +import { useExpanded } from './hooks/useExpanded'; import { getGroupStatus, statusMapping } from './util/status'; import { createId, getAncestorIds, getDescendantIds, isStoryHoistable } from './util/tree'; diff --git a/packages/react-native-ui/src/useExpanded.ts b/packages/react-native-ui/src/hooks/useExpanded.ts similarity index 97% rename from packages/react-native-ui/src/useExpanded.ts rename to packages/react-native-ui/src/hooks/useExpanded.ts index f3b39aa204..fb489ec228 100644 --- a/packages/react-native-ui/src/useExpanded.ts +++ b/packages/react-native-ui/src/hooks/useExpanded.ts @@ -1,7 +1,7 @@ import type { StoriesHash } from '@storybook/core/manager-api'; import type { Dispatch, Reducer } from 'react'; import { useCallback, useEffect, useReducer } from 'react'; -import { getAncestorIds } from './util/tree'; +import { getAncestorIds } from '../util/tree'; export type ExpandedState = Record; diff --git a/packages/react-native-ui/src/useLastViewed.ts b/packages/react-native-ui/src/hooks/useLastViewed.ts similarity index 96% rename from packages/react-native-ui/src/useLastViewed.ts rename to packages/react-native-ui/src/hooks/useLastViewed.ts index d91f52a6d0..4dd0f881dc 100644 --- a/packages/react-native-ui/src/useLastViewed.ts +++ b/packages/react-native-ui/src/hooks/useLastViewed.ts @@ -2,7 +2,7 @@ import debounce from 'lodash/debounce.js'; import { useCallback, useEffect, useMemo, useRef } from 'react'; import store from 'store2'; -import type { Selection, StoryRef } from './types'; +import type { Selection, StoryRef } from '../types'; const save = debounce((value) => store.set('lastViewedStoryIds', value), 1000); diff --git a/packages/react-native-ui/src/hooks/useStoreState.ts b/packages/react-native-ui/src/hooks/useStoreState.ts new file mode 100644 index 0000000000..467a452301 --- /dev/null +++ b/packages/react-native-ui/src/hooks/useStoreState.ts @@ -0,0 +1,27 @@ +import { useEffect, useState } from 'react'; +import { useStorage } from '../StorageProvider'; + +export const useStoreBooleanState = ( + key: string, + defaultValue: boolean +): ReturnType> => { + const storage = useStorage(); + + const [val, setVal] = useState(defaultValue); + + useEffect(() => { + storage.getItem(key).then((newVal) => { + if (newVal === null || newVal === undefined) { + setVal(defaultValue); + } else { + setVal(newVal === 'true'); + } + }); + }, [key, storage, defaultValue]); + + useEffect(() => { + storage.setItem(key, val.toString()); + }, [key, storage, val]); + + return [val, setVal]; +}; diff --git a/packages/react-native-ui/src/index.tsx b/packages/react-native-ui/src/index.tsx index 9235bb606c..415ed139d2 100644 --- a/packages/react-native-ui/src/index.tsx +++ b/packages/react-native-ui/src/index.tsx @@ -8,3 +8,4 @@ export * from './Sidebar'; export * from './types'; export * from './Layout'; export * from './util/StoryHash'; +export * from './StorageProvider'; diff --git a/packages/react-native/src/View.tsx b/packages/react-native/src/View.tsx index c7c0d66e07..f22c108966 100644 --- a/packages/react-native/src/View.tsx +++ b/packages/react-native/src/View.tsx @@ -9,6 +9,7 @@ import { Theme, ThemeProvider, darkTheme, theme } from '@storybook/react-native- import { Layout, LayoutProvider, + StorageProvider, transformStoryIndexToStoriesHash, } from '@storybook/react-native-ui'; import type { API_IndexHash, PreparedStory, StoryId, StoryIndex } from '@storybook/core/types'; @@ -287,11 +288,13 @@ export class View { {/* @ts-ignore something weird with story type */} - - - - - + + + + + + +