Collapsible menu (#5846)
A mini PR to discuss with @Bonapara tomorrow Separating remote objects from others and making the menu collapsible (style to be changed) <img width="225" alt="Screenshot 2024-06-12 at 23 25 59" src="https://github.com/twentyhq/twenty/assets/6399865/b4b69d36-6770-43a2-a5e8-bfcdf0a629ea"> Biggest issue is we don't use local storage today so the collapsed state gets lost. I see we have localStorageEffect with recoil. Maybe store it there? Seems easy but don't want to introduce a bad pattern. Todo: - style update - collapsible favorites - persistent storage
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
import { Avatar } from 'twenty-ui';
|
import { Avatar } from 'twenty-ui';
|
||||||
|
|
||||||
import { FavoritesSkeletonLoader } from '@/favorites/components/FavoritesSkeletonLoader';
|
import { FavoritesSkeletonLoader } from '@/favorites/components/FavoritesSkeletonLoader';
|
||||||
@ -8,6 +9,7 @@ import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableLi
|
|||||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||||
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
||||||
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
||||||
|
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
|
||||||
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
|
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
|
||||||
|
|
||||||
import { useFavorites } from '../hooks/useFavorites';
|
import { useFavorites } from '../hooks/useFavorites';
|
||||||
@ -36,6 +38,10 @@ export const Favorites = () => {
|
|||||||
const { favorites, handleReorderFavorite } = useFavorites();
|
const { favorites, handleReorderFavorite } = useFavorites();
|
||||||
const loading = useIsPrefetchLoading();
|
const loading = useIsPrefetchLoading();
|
||||||
|
|
||||||
|
const { toggleNavigationSection, isNavigationSectionOpenState } =
|
||||||
|
useNavigationSection('Favorites');
|
||||||
|
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <FavoritesSkeletonLoader />;
|
return <FavoritesSkeletonLoader />;
|
||||||
}
|
}
|
||||||
@ -44,48 +50,53 @@ export const Favorites = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<NavigationDrawerSectionTitle label="Favorites" />
|
<NavigationDrawerSectionTitle
|
||||||
<DraggableList
|
label="Favorites"
|
||||||
onDragEnd={handleReorderFavorite}
|
onClick={() => toggleNavigationSection()}
|
||||||
draggableItems={
|
|
||||||
<>
|
|
||||||
{favorites.map((favorite, index) => {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
labelIdentifier,
|
|
||||||
avatarUrl,
|
|
||||||
avatarType,
|
|
||||||
link,
|
|
||||||
recordId,
|
|
||||||
} = favorite;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DraggableItem
|
|
||||||
key={id}
|
|
||||||
draggableId={id}
|
|
||||||
index={index}
|
|
||||||
itemComponent={
|
|
||||||
<StyledNavigationDrawerItem
|
|
||||||
key={id}
|
|
||||||
label={labelIdentifier}
|
|
||||||
Icon={() => (
|
|
||||||
<StyledAvatar
|
|
||||||
entityId={recordId}
|
|
||||||
avatarUrl={getImageAbsoluteURIOrBase64(avatarUrl)}
|
|
||||||
type={avatarType}
|
|
||||||
placeholder={labelIdentifier}
|
|
||||||
className="fav-avatar"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
to={link}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
{isNavigationSectionOpen && (
|
||||||
|
<DraggableList
|
||||||
|
onDragEnd={handleReorderFavorite}
|
||||||
|
draggableItems={
|
||||||
|
<>
|
||||||
|
{favorites.map((favorite, index) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
labelIdentifier,
|
||||||
|
avatarUrl,
|
||||||
|
avatarType,
|
||||||
|
link,
|
||||||
|
recordId,
|
||||||
|
} = favorite;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DraggableItem
|
||||||
|
key={id}
|
||||||
|
draggableId={id}
|
||||||
|
index={index}
|
||||||
|
itemComponent={
|
||||||
|
<StyledNavigationDrawerItem
|
||||||
|
key={id}
|
||||||
|
label={labelIdentifier}
|
||||||
|
Icon={() => (
|
||||||
|
<StyledAvatar
|
||||||
|
entityId={recordId}
|
||||||
|
avatarUrl={getImageAbsoluteURIOrBase64(avatarUrl)}
|
||||||
|
type={avatarType}
|
||||||
|
placeholder={labelIdentifier}
|
||||||
|
className="fav-avatar"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
to={link}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import { Favorites } from '@/favorites/components/Favorites';
|
|||||||
import { ObjectMetadataNavItems } from '@/object-metadata/components/ObjectMetadataNavItems';
|
import { ObjectMetadataNavItems } from '@/object-metadata/components/ObjectMetadataNavItems';
|
||||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||||
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
||||||
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
|
||||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
@ -56,10 +55,8 @@ export const MainNavigationDrawerItems = () => {
|
|||||||
|
|
||||||
<Favorites />
|
<Favorites />
|
||||||
|
|
||||||
<NavigationDrawerSection>
|
<ObjectMetadataNavItems isRemote={false} />
|
||||||
<NavigationDrawerSectionTitle label="Workspace" />
|
<ObjectMetadataNavItems isRemote={true} />
|
||||||
<ObjectMetadataNavItems />
|
|
||||||
</NavigationDrawerSection>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
import { useIcons } from 'twenty-ui';
|
import { useIcons } from 'twenty-ui';
|
||||||
|
|
||||||
import { ObjectMetadataNavItemsSkeletonLoader } from '@/object-metadata/components/ObjectMetadataNavItemsSkeletonLoader';
|
import { ObjectMetadataNavItemsSkeletonLoader } from '@/object-metadata/components/ObjectMetadataNavItemsSkeletonLoader';
|
||||||
@ -7,11 +8,21 @@ import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
|||||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||||
|
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
||||||
|
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
||||||
|
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
|
||||||
import { View } from '@/views/types/View';
|
import { View } from '@/views/types/View';
|
||||||
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
|
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
|
||||||
|
|
||||||
export const ObjectMetadataNavItems = () => {
|
export const ObjectMetadataNavItems = ({ isRemote }: { isRemote: boolean }) => {
|
||||||
|
const { toggleNavigationSection, isNavigationSectionOpenState } =
|
||||||
|
useNavigationSection('Objects' + (isRemote ? 'Remote' : 'Workspace'));
|
||||||
|
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
|
||||||
|
|
||||||
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||||
|
const filteredActiveObjectMetadataItems = activeObjectMetadataItems.filter(
|
||||||
|
(item) => (isRemote ? item.isRemote : !item.isRemote),
|
||||||
|
);
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
const currentPath = useLocation().pathname;
|
const currentPath = useLocation().pathname;
|
||||||
|
|
||||||
@ -23,55 +34,69 @@ export const ObjectMetadataNavItems = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
filteredActiveObjectMetadataItems.length > 0 && (
|
||||||
{[
|
<NavigationDrawerSection>
|
||||||
...activeObjectMetadataItems
|
<NavigationDrawerSectionTitle
|
||||||
.filter((item) =>
|
label={isRemote ? 'Remote' : 'Workspace'}
|
||||||
['person', 'company', 'opportunity'].includes(item.nameSingular),
|
onClick={() => toggleNavigationSection()}
|
||||||
)
|
/>
|
||||||
.sort((objectMetadataItemA, objectMetadataItemB) => {
|
|
||||||
const order = ['person', 'company', 'opportunity'];
|
|
||||||
const indexA = order.indexOf(objectMetadataItemA.nameSingular);
|
|
||||||
const indexB = order.indexOf(objectMetadataItemB.nameSingular);
|
|
||||||
if (indexA === -1 || indexB === -1) {
|
|
||||||
return objectMetadataItemA.nameSingular.localeCompare(
|
|
||||||
objectMetadataItemB.nameSingular,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return indexA - indexB;
|
|
||||||
}),
|
|
||||||
...activeObjectMetadataItems
|
|
||||||
.filter(
|
|
||||||
(item) =>
|
|
||||||
!['person', 'company', 'opportunity'].includes(item.nameSingular),
|
|
||||||
)
|
|
||||||
.sort((objectMetadataItemA, objectMetadataItemB) => {
|
|
||||||
return new Date(objectMetadataItemA.createdAt) <
|
|
||||||
new Date(objectMetadataItemB.createdAt)
|
|
||||||
? 1
|
|
||||||
: -1;
|
|
||||||
}),
|
|
||||||
].map((objectMetadataItem) => {
|
|
||||||
const objectMetadataViews = getObjectMetadataItemViews(
|
|
||||||
objectMetadataItem.id,
|
|
||||||
views,
|
|
||||||
);
|
|
||||||
const viewId = objectMetadataViews[0]?.id;
|
|
||||||
|
|
||||||
const navigationPath = `/objects/${objectMetadataItem.namePlural}${
|
{isNavigationSectionOpen &&
|
||||||
viewId ? `?view=${viewId}` : ''
|
[
|
||||||
}`;
|
...filteredActiveObjectMetadataItems
|
||||||
|
.filter((item) =>
|
||||||
|
['person', 'company', 'opportunity'].includes(
|
||||||
|
item.nameSingular,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.sort((objectMetadataItemA, objectMetadataItemB) => {
|
||||||
|
const order = ['person', 'company', 'opportunity'];
|
||||||
|
const indexA = order.indexOf(objectMetadataItemA.nameSingular);
|
||||||
|
const indexB = order.indexOf(objectMetadataItemB.nameSingular);
|
||||||
|
if (indexA === -1 || indexB === -1) {
|
||||||
|
return objectMetadataItemA.nameSingular.localeCompare(
|
||||||
|
objectMetadataItemB.nameSingular,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return indexA - indexB;
|
||||||
|
}),
|
||||||
|
...filteredActiveObjectMetadataItems
|
||||||
|
.filter(
|
||||||
|
(item) =>
|
||||||
|
!['person', 'company', 'opportunity'].includes(
|
||||||
|
item.nameSingular,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.sort((objectMetadataItemA, objectMetadataItemB) => {
|
||||||
|
return new Date(objectMetadataItemA.createdAt) <
|
||||||
|
new Date(objectMetadataItemB.createdAt)
|
||||||
|
? 1
|
||||||
|
: -1;
|
||||||
|
}),
|
||||||
|
].map((objectMetadataItem) => {
|
||||||
|
const objectMetadataViews = getObjectMetadataItemViews(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
views,
|
||||||
|
);
|
||||||
|
const viewId = objectMetadataViews[0]?.id;
|
||||||
|
|
||||||
return (
|
const navigationPath = `/objects/${objectMetadataItem.namePlural}${
|
||||||
<NavigationDrawerItem
|
viewId ? `?view=${viewId}` : ''
|
||||||
key={objectMetadataItem.id}
|
}`;
|
||||||
label={objectMetadataItem.labelPlural}
|
|
||||||
to={navigationPath}
|
return (
|
||||||
active={currentPath === `/objects/${objectMetadataItem.namePlural}`}
|
<NavigationDrawerItem
|
||||||
Icon={getIcon(objectMetadataItem.icon)}
|
key={objectMetadataItem.id}
|
||||||
/>
|
label={objectMetadataItem.labelPlural}
|
||||||
);
|
to={navigationPath}
|
||||||
})}
|
active={
|
||||||
</>
|
currentPath === `/objects/${objectMetadataItem.namePlural}`
|
||||||
|
}
|
||||||
|
Icon={getIcon(objectMetadataItem.icon)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</NavigationDrawerSection>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,21 +2,34 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
||||||
import { NavigationDrawerSectionTitleSkeletonLoader } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader';
|
import { NavigationDrawerSectionTitleSkeletonLoader } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader';
|
||||||
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
|
|
||||||
type NavigationDrawerSectionTitleProps = {
|
type NavigationDrawerSectionTitleProps = {
|
||||||
|
onClick?: () => void;
|
||||||
label: string;
|
label: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledTitle = styled.div`
|
const StyledTitle = styled.div<{ onClick?: () => void }>`
|
||||||
|
align-items: center;
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: ${({ theme }) => theme.font.size.xs};
|
font-size: ${({ theme }) => theme.font.size.xs};
|
||||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||||
|
height: ${({ theme }) => theme.spacing(4)};
|
||||||
padding: ${({ theme }) => theme.spacing(1)};
|
padding: ${({ theme }) => theme.spacing(1)};
|
||||||
padding-top: 0;
|
|
||||||
|
${({ onClick, theme }) =>
|
||||||
|
!isUndefinedOrNull(onClick)
|
||||||
|
? `&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color:${theme.background.transparent.light};
|
||||||
|
}`
|
||||||
|
: ''}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const NavigationDrawerSectionTitle = ({
|
export const NavigationDrawerSectionTitle = ({
|
||||||
|
onClick,
|
||||||
label,
|
label,
|
||||||
}: NavigationDrawerSectionTitleProps) => {
|
}: NavigationDrawerSectionTitleProps) => {
|
||||||
const loading = useIsPrefetchLoading();
|
const loading = useIsPrefetchLoading();
|
||||||
@ -24,5 +37,5 @@ export const NavigationDrawerSectionTitle = ({
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return <NavigationDrawerSectionTitleSkeletonLoader />;
|
return <NavigationDrawerSectionTitleSkeletonLoader />;
|
||||||
}
|
}
|
||||||
return <StyledTitle>{label}</StyledTitle>;
|
return <StyledTitle onClick={onClick}>{label}</StyledTitle>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,60 @@
|
|||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { isNavigationSectionOpenComponentState } from '@/ui/navigation/navigation-drawer/states/isNavigationSectionOpenComponentState';
|
||||||
|
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||||
|
|
||||||
|
export const useNavigationSection = (scopeId: string) => {
|
||||||
|
const closeNavigationSection = useRecoilCallback(
|
||||||
|
({ set }) =>
|
||||||
|
() => {
|
||||||
|
set(
|
||||||
|
isNavigationSectionOpenComponentState({
|
||||||
|
scopeId,
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[scopeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const openNavigationSection = useRecoilCallback(
|
||||||
|
({ set }) =>
|
||||||
|
() => {
|
||||||
|
set(
|
||||||
|
isNavigationSectionOpenComponentState({
|
||||||
|
scopeId,
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[scopeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleNavigationSection = useRecoilCallback(
|
||||||
|
({ snapshot }) =>
|
||||||
|
() => {
|
||||||
|
const isNavigationSectionOpen = snapshot
|
||||||
|
.getLoadable(isNavigationSectionOpenComponentState({ scopeId }))
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
if (isNavigationSectionOpen) {
|
||||||
|
closeNavigationSection();
|
||||||
|
} else {
|
||||||
|
openNavigationSection();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[closeNavigationSection, openNavigationSection, scopeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isNavigationSectionOpenState = extractComponentState(
|
||||||
|
isNavigationSectionOpenComponentState,
|
||||||
|
scopeId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isNavigationSectionOpenState,
|
||||||
|
closeNavigationSection,
|
||||||
|
openNavigationSection,
|
||||||
|
toggleNavigationSection,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||||
|
import { localStorageEffect } from '~/utils/recoil-effects';
|
||||||
|
|
||||||
|
export const isNavigationSectionOpenComponentState =
|
||||||
|
createComponentState<boolean>({
|
||||||
|
key: 'isNavigationSectionOpenComponentState',
|
||||||
|
defaultValue: true,
|
||||||
|
effects: [localStorageEffect()],
|
||||||
|
});
|
||||||
@ -1,16 +1,19 @@
|
|||||||
import { atomFamily } from 'recoil';
|
import { AtomEffect, atomFamily } from 'recoil';
|
||||||
|
|
||||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||||
|
|
||||||
export const createComponentState = <ValueType>({
|
export const createComponentState = <ValueType>({
|
||||||
key,
|
key,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
|
effects,
|
||||||
}: {
|
}: {
|
||||||
key: string;
|
key: string;
|
||||||
defaultValue: ValueType;
|
defaultValue: ValueType;
|
||||||
|
effects?: AtomEffect<ValueType>[];
|
||||||
}) => {
|
}) => {
|
||||||
return atomFamily<ValueType, ComponentStateKey>({
|
return atomFamily<ValueType, ComponentStateKey>({
|
||||||
key,
|
key,
|
||||||
default: defaultValue,
|
default: defaultValue,
|
||||||
|
effects: effects,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,17 +5,17 @@ import { cookieStorage } from '~/utils/cookie-storage';
|
|||||||
import { isDefined } from './isDefined';
|
import { isDefined } from './isDefined';
|
||||||
|
|
||||||
export const localStorageEffect =
|
export const localStorageEffect =
|
||||||
<T>(key: string): AtomEffect<T> =>
|
<T>(key?: string): AtomEffect<T> =>
|
||||||
({ setSelf, onSet }) => {
|
({ setSelf, onSet, node }) => {
|
||||||
const savedValue = localStorage.getItem(key);
|
const savedValue = localStorage.getItem(key ?? node.key);
|
||||||
if (savedValue != null) {
|
if (savedValue != null) {
|
||||||
setSelf(JSON.parse(savedValue));
|
setSelf(JSON.parse(savedValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
onSet((newValue, _, isReset) => {
|
onSet((newValue, _, isReset) => {
|
||||||
isReset
|
isReset
|
||||||
? localStorage.removeItem(key)
|
? localStorage.removeItem(key ?? node.key)
|
||||||
: localStorage.setItem(key, JSON.stringify(newValue));
|
: localStorage.setItem(key ?? node.key, JSON.stringify(newValue));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user