diff --git a/packages/twenty-front/src/modules/navigation/components/AppNavigationDrawer.tsx b/packages/twenty-front/src/modules/navigation/components/AppNavigationDrawer.tsx index da636fe02..a6d3b1045 100644 --- a/packages/twenty-front/src/modules/navigation/components/AppNavigationDrawer.tsx +++ b/packages/twenty-front/src/modules/navigation/components/AppNavigationDrawer.tsx @@ -4,7 +4,6 @@ import { useRecoilValue, useSetRecoilState } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { SettingsNavigationDrawerItems } from '@/settings/components/SettingsNavigationDrawerItems'; import { SupportDropdown } from '@/support/components/SupportDropdown'; -import { GithubVersionLink } from '@/ui/navigation/link/components/GithubVersionLink'; import { NavigationDrawer, NavigationDrawerProps, @@ -16,6 +15,7 @@ import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI'; import { useIsSettingsPage } from '../hooks/useIsSettingsPage'; import { currentMobileNavigationDrawerState } from '../states/currentMobileNavigationDrawerState'; +import { AdvancedSettingsToggle } from '@/ui/navigation/link/components/AdvancedSettingsToggle'; import { MainNavigationDrawerItems } from './MainNavigationDrawerItems'; export type AppNavigationDrawerProps = { @@ -44,7 +44,7 @@ export const AppNavigationDrawer = ({ isSubMenu: true, title: 'Exit Settings', children: , - footer: , + footer: , } : { logo: diff --git a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx index 04f110207..e721e8d8e 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx @@ -13,13 +13,16 @@ import { IconMail, IconRocket, IconSettings, + IconTool, IconUserCircle, IconUsers, + MAIN_COLORS, } from 'twenty-ui'; import { useAuth } from '@/auth/hooks/useAuth'; import { billingState } from '@/client-config/states/billingState'; import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem'; +import { useExpandedHeightAnimation } from '@/settings/hooks/useExpandedHeightAnimation'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; import { @@ -29,10 +32,38 @@ import { import { NavigationDrawerItemGroup } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemGroup'; import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection'; import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle'; +import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; import { getNavigationSubItemState } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemState'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import styled from '@emotion/styled'; +import { AnimatePresence, motion } from 'framer-motion'; import { matchPath, resolvePath, useLocation } from 'react-router-dom'; +const StyledNavigationDrawerSection = styled(NavigationDrawerSection)<{ + withLeftMargin?: boolean; +}>` + margin-left: ${({ withLeftMargin, theme }) => + withLeftMargin && theme.spacing(5)}; + margin-top: ${({ theme }) => theme.spacing(3)}; +`; + +const StyledIconContainer = styled.div` + border-right: 1px solid ${MAIN_COLORS.yellow}; + display: flex; + margin-top: ${({ theme }) => theme.spacing(5)}; + width: 16px; +`; + +const StyledDeveloperSection = styled.div` + display: flex; + width: 100%; + gap: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledIconTool = styled(IconTool)` + margin-right: ${({ theme }) => theme.spacing(0.5)}; +`; + type SettingsNavigationItem = { label: string; path: SettingsPath; @@ -42,6 +73,10 @@ type SettingsNavigationItem = { }; export const SettingsNavigationDrawerItems = () => { + const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState); + const { contentRef, motionAnimationVariants } = useExpandedHeightAnimation( + isAdvancedModeEnabled, + ); const { signOut } = useAuth(); const billing = useRecoilValue(billingState); @@ -88,7 +123,7 @@ export const SettingsNavigationDrawerItems = () => { return ( <> - + { /> ))} - - + + { Icon={IconHierarchy2} matchSubPages /> - - {isFunctionSettingsEnabled && ( - - )} { Icon={IconCode} /> )} - - + + + {isAdvancedModeEnabled && ( + + + + + + + + + {isFunctionSettingsEnabled && ( + + )} + + + + )} + + { onClick={signOut} Icon={IconDoorEnter} /> - + ); }; diff --git a/packages/twenty-front/src/modules/settings/hooks/useExpandedHeightAnimation.tsx b/packages/twenty-front/src/modules/settings/hooks/useExpandedHeightAnimation.tsx new file mode 100644 index 000000000..3a48c3bb2 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/hooks/useExpandedHeightAnimation.tsx @@ -0,0 +1,57 @@ +import { useEffect, useRef, useState } from 'react'; +import { isDefined } from 'twenty-ui'; + +const transitionValues = { + transition: { + opactity: { duration: 0.2 }, + height: { duration: 0.4 }, + }, + transitionEnd: { + overflow: 'visible', + }, +}; + +const commonStyles = { + opacity: 0, + height: 0, + overflow: 'hidden', + ...transitionValues, +}; + +const advancedSectionAnimationConfig = ( + isExpanded: boolean, + measuredHeight: number, +) => ({ + initial: { + ...commonStyles, + }, + animate: { + opacity: 1, + height: isExpanded ? measuredHeight : 0, + ...transitionValues, + overflow: 'hidden', + }, + exit: { + ...commonStyles, + }, +}); + +export const useExpandedHeightAnimation = (isExpanded: boolean) => { + const contentRef = useRef(null); + const [measuredHeight, setMeasuredHeight] = useState(0); + + useEffect(() => { + if (isDefined(contentRef.current)) { + setMeasuredHeight(contentRef.current.scrollHeight); + } + }, [isExpanded]); + + return { + contentRef, + measuredHeight, + motionAnimationVariants: advancedSectionAnimationConfig( + isExpanded, + measuredHeight, + ), + }; +}; diff --git a/packages/twenty-front/src/modules/ui/layout/page/DefaultLayout.tsx b/packages/twenty-front/src/modules/ui/layout/page/DefaultLayout.tsx index 4108f6a5c..555e068d6 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/DefaultLayout.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/DefaultLayout.tsx @@ -1,7 +1,4 @@ -import { css, Global, useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; -import { AnimatePresence, LayoutGroup, motion } from 'framer-motion'; -import { Outlet } from 'react-router-dom'; +import { AuthModal } from '@/auth/components/AuthModal'; import { CommandMenu } from '@/command-menu/components/CommandMenu'; import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary'; import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu'; @@ -14,7 +11,10 @@ import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal'; import { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useScreenSize } from '@/ui/utilities/screen-size/hooks/useScreenSize'; -import { AuthModal } from '@/auth/components/AuthModal'; +import { css, Global, useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { AnimatePresence, LayoutGroup, motion } from 'framer-motion'; +import { Outlet } from 'react-router-dom'; const StyledLayout = styled.div` background: ${({ theme }) => theme.background.noisy}; @@ -85,7 +85,7 @@ export const DefaultLayout = () => { ? (windowsWidth - (OBJECT_SETTINGS_WIDTH + DESKTOP_NAV_DRAWER_WIDTHS.menu + - 64)) / + 88)) / 2 : 0, }} diff --git a/packages/twenty-front/src/modules/ui/navigation/link/components/AdvancedSettingsToggle.tsx b/packages/twenty-front/src/modules/ui/navigation/link/components/AdvancedSettingsToggle.tsx new file mode 100644 index 000000000..a45814691 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/navigation/link/components/AdvancedSettingsToggle.tsx @@ -0,0 +1,62 @@ +import { Toggle } from '@/ui/input/components/Toggle'; +import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; +import styled from '@emotion/styled'; +import { useRecoilState } from 'recoil'; +import { IconTool, MAIN_COLORS } from 'twenty-ui'; + +const StyledContainer = styled.div` + align-items: center; + display: flex; + width: 100%; + gap: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledText = styled.span` + color: ${({ theme }) => theme.font.color.secondary}; + font-size: ${({ theme }) => theme.font.size.sm}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + padding: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledIconContainer = styled.div` + border-right: 1px solid ${MAIN_COLORS.yellow}; + display: flex; + height: 16px; +`; + +const StyledToggleContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +`; + +const StyledIconTool = styled(IconTool)` + margin-right: ${({ theme }) => theme.spacing(0.5)}; +`; + +export const AdvancedSettingsToggle = () => { + const [isAdvancedModeEnabled, setIsAdvancedModeEnabled] = useRecoilState( + isAdvancedModeEnabledState, + ); + + const onChange = (newValue: boolean) => { + setIsAdvancedModeEnabled(newValue); + }; + + return ( + + + + + + Advanced + + + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawer.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawer.tsx index 35fb3cae0..28b3640cc 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawer.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawer.tsx @@ -40,6 +40,7 @@ const StyledContainer = styled.div<{ isSubMenu?: boolean }>` ${({ isSubMenu, theme }) => isSubMenu ? css` + padding-left: ${theme.spacing(0)}; padding-right: ${theme.spacing(8)}; ` : ''} @@ -48,13 +49,12 @@ const StyledContainer = styled.div<{ isSubMenu?: boolean }>` width: 100%; } `; - -const StyledItemsContainer = styled.div` +const StyledItemsContainer = styled.div<{ isSubMenu?: boolean }>` display: flex; flex-direction: column; - gap: ${({ theme }) => theme.spacing(3)}; margin-bottom: auto; overflow-y: auto; + ${({ isSubMenu, theme }) => !isSubMenu && `gap: ${theme.spacing(3)}`} `; export const NavigationDrawer = ({ @@ -111,7 +111,9 @@ export const NavigationDrawer = ({ showCollapseButton={isHovered} /> )} - {children} + + {children} + {footer} diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerBackButton.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerBackButton.tsx index 96490d7f0..d906f45aa 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerBackButton.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerBackButton.tsx @@ -35,6 +35,7 @@ const StyledContainer = styled.div` flex-direction: row; height: ${({ theme }) => theme.spacing(8)}; justify-content: space-between; + margin-left: ${({ theme }) => theme.spacing(5)}; `; export const NavigationDrawerBackButton = ({ diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSection.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSection.tsx index bea1d0f8e..c1fd93847 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSection.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSection.tsx @@ -4,6 +4,7 @@ const StyledSection = styled.div` display: flex; flex-direction: column; gap: ${({ theme }) => theme.betweenSiblingsGap}; + width: 100%; `; export { StyledSection as NavigationDrawerSection }; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState.ts b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState.ts new file mode 100644 index 000000000..05fd34d43 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState.ts @@ -0,0 +1,8 @@ +import { atom } from 'recoil'; +import { localStorageEffect } from '~/utils/recoil-effects'; + +export const isAdvancedModeEnabledState = atom({ + key: 'isAdvancedModeEnabledAtom', + default: false, + effects: [localStorageEffect()], +}); diff --git a/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx b/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx index 359961d24..4f32d08b2 100644 --- a/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx @@ -9,6 +9,7 @@ import { WorkspaceLogoUploader } from '@/settings/workspace/components/Workspace import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { Section } from '@/ui/layout/section/components/Section'; +import { GithubVersionLink } from '@/ui/navigation/link/components/GithubVersionLink'; export const SettingsWorkspace = () => ( (
+
+ +
); diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index 2daafb4ec..97a8013b6 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -173,6 +173,7 @@ export { IconTestPipe, IconTextSize, IconTimelineEvent, + IconTool, IconTrash, IconUnlink, IconUpload,