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:
Félix Malfait
2024-06-14 12:35:23 +02:00
committed by GitHub
parent 8d8bf1c128
commit a2e89af6b2
8 changed files with 222 additions and 104 deletions

View File

@ -2,21 +2,34 @@ import styled from '@emotion/styled';
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { NavigationDrawerSectionTitleSkeletonLoader } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
type NavigationDrawerSectionTitleProps = {
onClick?: () => void;
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};
display: flex;
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
height: ${({ theme }) => theme.spacing(4)};
padding: ${({ theme }) => theme.spacing(1)};
padding-top: 0;
${({ onClick, theme }) =>
!isUndefinedOrNull(onClick)
? `&:hover {
cursor: pointer;
background-color:${theme.background.transparent.light};
}`
: ''}
`;
export const NavigationDrawerSectionTitle = ({
onClick,
label,
}: NavigationDrawerSectionTitleProps) => {
const loading = useIsPrefetchLoading();
@ -24,5 +37,5 @@ export const NavigationDrawerSectionTitle = ({
if (loading) {
return <NavigationDrawerSectionTitleSkeletonLoader />;
}
return <StyledTitle>{label}</StyledTitle>;
return <StyledTitle onClick={onClick}>{label}</StyledTitle>;
};

View File

@ -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,
};
};

View File

@ -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()],
});

View File

@ -1,16 +1,19 @@
import { atomFamily } from 'recoil';
import { AtomEffect, atomFamily } from 'recoil';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
export const createComponentState = <ValueType>({
key,
defaultValue,
effects,
}: {
key: string;
defaultValue: ValueType;
effects?: AtomEffect<ValueType>[];
}) => {
return atomFamily<ValueType, ComponentStateKey>({
key,
default: defaultValue,
effects: effects,
});
};