From b14195428a7064956d04a78e7bf0c62c3dc5b9e4 Mon Sep 17 00:00:00 2001 From: Thomas des Francs Date: Sat, 13 Jun 2026 13:13:55 +0200 Subject: [PATCH] Fix navigation and settings UI polish (#21523) ## Summary This PR groups the requested UI polish pass across navigation, settings, AI settings, data model, community/lab, and dashboard chips. ### Navigation and drawer polish - Smooths app/settings route switching with a 300ms content transition. - Smooths app menu/settings menu drawer swaps with a fade transition while preserving the existing drawer dimensions. - Keeps navigation and AI chat history panes mounted to avoid flicker when switching tabs. - Aligns the first app/settings section with the AI chat history section title. - Removes the main app navigation "Other" section. - Aligns the settings exit header with the workspace switcher header. - Sets the settings exit icon gap to 8px, uses the small icon stroke token, and keeps the icon color on text-secondary. - Makes the workspace switcher button 28px high inside its 32px container, moves the dropdown up so the workspace name does not jump, and keeps a 2px gap between header icon buttons. - Moves Settings below Support in the workspace switcher menu. - Pins the Advanced toggle to the bottom of the settings drawer and aligns its right padding with nav items. ### Settings surface polish - Fixes vertically cropped dropdown menu headers. - Makes wizard parent titles tertiary when a nested wizard title is visible. - Places danger-zone buttons side by side. - Uses a 32px Visualize button. - Restores 8px top/bottom padding on settings tables. - Separates AI overview counts into three columns like layout overview. - Adds separators inside setting cards, including the Smart Model / Fast Model card. - Groups lab/early-access toggles into one card with separators and full-width row backgrounds. - Replaces deprecated Enterprise adornment tags with the Organization adornment on gated AI/security/settings surfaces. - Uses the Discord brand icon in Community while keeping the standard icon component rendering pattern. - Tightens Skills/Tools search-to-table spacing so switching tabs does not shake the table top. - Fixes the small gap in nested navigation breadcrumbs between Emails and Calendars. ### Dashboard/data table polish - Fixes vertically cropped "Not shared" chips on dashboards. - Keeps table row/header spacing stable after the settings table padding restoration. ## Validation - `npx nx lint:diff-with-main twenty-front` - `npx tsc -p packages/twenty-front/tsconfig.json --noEmit` - Manual Chrome pass on `apple.localhost:3001` for profile, app navigation, workspace switcher, AI overview/models/skills/tools/usage, community/lab, data model, new-field wizard, and dashboards. ## Visual QA ### Settings surface fixes Settings surface fixes before/after
board ### Navigation drawer fixes Navigation drawer fixes
before/after board ### Tables, wizards, gates and chips Tables, wizards, gates and chips
before/after board --- .../components/LeftPanelSkeletonLoader.tsx | 2 +- .../NavigationDrawerAiChatContent.tsx | 3 +- .../NavigationDrawerAiChatThreadSection.tsx | 11 +-- .../components/FavoritesSectionDispatcher.tsx | 8 ++ .../components/MainNavigationDrawer.tsx | 1 + .../MainNavigationDrawerScrollableItems.tsx | 8 -- .../NavigationDrawerTabbedContent.tsx | 12 +-- .../components/SettingsNavigationDrawer.tsx | 26 ++++-- .../components/ForbiddenFieldDisplay.tsx | 7 +- .../ai/components/SettingsAdminAI.tsx | 26 ++---- .../SettingsOptionCardContentSelect.tsx | 36 +++++---- .../components/layout/SettingsPageLayout.tsx | 3 + .../lab/components/SettingsLabContent.tsx | 80 +++++++++++++------ .../profile/components/DeleteAccount.tsx | 57 ++++++------- ...gsRolePermissionsObjectLevelObjectForm.tsx | 2 + .../components/SettingsSecuritySettings.tsx | 21 +---- .../BackgroundMockNavigationDrawer.tsx | 12 --- .../DropdownMenuHeader/DropdownMenuHeader.tsx | 1 + .../components/MainAppLayoutWithSidePanel.tsx | 57 ++++++++++++- .../layout/page/components/PageCardHeader.tsx | 13 ++- .../ui/layout/table/components/Table.tsx | 17 +++- .../layout/table/components/TableHeader.tsx | 18 ++++- .../ui/layout/table/components/TableRow.tsx | 1 + .../MultiWorkspaceDropdownButton.tsx | 2 +- ...ultiWorkspaceDropdownDefaultComponents.tsx | 14 ++-- .../MultiWorkspacesDropdownStyles.tsx | 1 + .../components/NavigationDrawer.tsx | 2 +- .../components/NavigationDrawerBackButton.tsx | 18 +++-- .../NavigationDrawerFixedContent.tsx | 1 + .../components/NavigationDrawerHeader.tsx | 6 +- .../NavigationDrawerItemBreadcrumb.tsx | 4 +- .../NavigationDrawerScrollableContent.tsx | 12 ++- .../ai/components/SettingsAgentSkillsTab.tsx | 58 +++++++------- .../ai/components/SettingsAiModelsTab.tsx | 1 + .../ai/components/SettingsAiOverviewTab.tsx | 4 + .../ai/components/SettingsAiUsageTab.tsx | 13 +-- .../settings/community/SettingsCommunity.tsx | 8 +- .../settings/data-model/SettingsObjects.tsx | 2 +- .../SettingsObjectNewFieldConfigure.tsx | 2 + .../SettingsObjectNewFieldSelect.tsx | 2 + .../roles/SettingsRoleAddObjectLevel.tsx | 2 + .../components/AdvancedSettingsToggle.tsx | 4 +- 42 files changed, 349 insertions(+), 229 deletions(-) diff --git a/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx b/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx index badd24e1d7..142010b863 100644 --- a/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx +++ b/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx @@ -19,7 +19,7 @@ const StyledAnimatedContainer = styled(motion.div)` flex-shrink: 0; height: 100%; overflow: hidden; - padding: ${themeCssVariables.spacing[3]} 0 ${themeCssVariables.spacing[4]} + padding: ${themeCssVariables.spacing[2]} 0 ${themeCssVariables.spacing[4]} ${themeCssVariables.spacing[2]}; `; diff --git a/packages/twenty-front/src/modules/ai/components/NavigationDrawerAiChatContent.tsx b/packages/twenty-front/src/modules/ai/components/NavigationDrawerAiChatContent.tsx index defafb2ce2..eee406cd44 100644 --- a/packages/twenty-front/src/modules/ai/components/NavigationDrawerAiChatContent.tsx +++ b/packages/twenty-front/src/modules/ai/components/NavigationDrawerAiChatContent.tsx @@ -27,8 +27,7 @@ const StyledThreadList = styled.div` flex: 1; flex-direction: column; min-height: 0; - padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[0]}; - width: calc(100% - ${themeCssVariables.spacing[2]}); + width: 100%; `; const StyledSectionsContainer = styled.div` diff --git a/packages/twenty-front/src/modules/ai/components/NavigationDrawerAiChatThreadSection.tsx b/packages/twenty-front/src/modules/ai/components/NavigationDrawerAiChatThreadSection.tsx index 5bd91f2be5..866618cd5d 100644 --- a/packages/twenty-front/src/modules/ai/components/NavigationDrawerAiChatThreadSection.tsx +++ b/packages/twenty-front/src/modules/ai/components/NavigationDrawerAiChatThreadSection.tsx @@ -5,20 +5,15 @@ import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants'; import { NavigationDrawerAiChatThreadItem } from '@/ai/components/NavigationDrawerAiChatThreadItem'; import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper'; +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 { type AgentChatThread } from '~/generated-metadata/graphql'; -const StyledSection = styled.section` - display: flex; - flex-direction: column; -`; - const StyledThreadList = styled.div` display: flex; flex-direction: column; gap: ${themeCssVariables.spacing['0.5']}; - padding-top: ${themeCssVariables.betweenSiblingsGap}; `; export type NavigationDrawerAiChatThreadSectionProps = { @@ -44,7 +39,7 @@ export const NavigationDrawerAiChatThreadSection = ({ useNavigationSection(sectionId); return ( - + ) : null} - + ); }; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/display/sections/favorites/components/FavoritesSectionDispatcher.tsx b/packages/twenty-front/src/modules/navigation-menu-item/display/sections/favorites/components/FavoritesSectionDispatcher.tsx index d1fb1a950a..8b7a243aee 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/display/sections/favorites/components/FavoritesSectionDispatcher.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/display/sections/favorites/components/FavoritesSectionDispatcher.tsx @@ -1,6 +1,7 @@ import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState'; import { NavigationSections } from '@/navigation-menu-item/common/constants/NavigationSections.constants'; import { NavigationMenuItemDndKitProvider } from '@/navigation-menu-item/display/dnd/providers/NavigationMenuItemDndKitProvider'; +import { useSortedNavigationMenuItems } from '@/navigation-menu-item/display/hooks/useSortedNavigationMenuItems'; import { FavoritesSection } from '@/navigation-menu-item/display/sections/favorites/components/FavoritesSection'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { AnimatedEaseInOut } from 'twenty-ui-deprecated/utilities'; @@ -9,6 +10,13 @@ export const FavoritesSectionDispatcher = () => { const isLayoutCustomizationModeEnabled = useAtomStateValue( isLayoutCustomizationModeEnabledState, ); + const { navigationMenuItemsSorted } = useSortedNavigationMenuItems(); + + const hasFavorites = navigationMenuItemsSorted.some((item) => !item.folderId); + + if (!hasFavorites) { + return null; + } return ( diff --git a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawer.tsx b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawer.tsx index ff8072c49a..2fd59b610a 100644 --- a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawer.tsx +++ b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawer.tsx @@ -34,6 +34,7 @@ export const MainNavigationDrawer = ({ className }: { className?: string }) => { } /> diff --git a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerScrollableItems.tsx b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerScrollableItems.tsx index 6f6836a703..9c45b6c38a 100644 --- a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerScrollableItems.tsx +++ b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerScrollableItems.tsx @@ -1,9 +1,6 @@ import { NavigationDrawerOpenedSection } from '@/navigation-menu-item/display/sections/components/NavigationDrawerOpenedSection'; import { NavigationDrawerWorkspaceSectionSkeletonLoader } from '@/object-metadata/components/NavigationDrawerWorkspaceSectionSkeletonLoader'; -import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState'; -import { NavigationDrawerOtherSection } from '@/navigation/components/NavigationDrawerOtherSection'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { styled } from '@linaria/react'; import { lazy, Suspense } from 'react'; @@ -32,10 +29,6 @@ const StyledScrollableItemsContainer = styled.div` `; export const MainNavigationDrawerScrollableItems = () => { - const isLayoutCustomizationModeEnabled = useAtomStateValue( - isLayoutCustomizationModeEnabledState, - ); - return ( @@ -43,7 +36,6 @@ export const MainNavigationDrawerScrollableItems = () => { - {!isLayoutCustomizationModeEnabled && } ); }; diff --git a/packages/twenty-front/src/modules/navigation/components/NavigationDrawerTabbedContent.tsx b/packages/twenty-front/src/modules/navigation/components/NavigationDrawerTabbedContent.tsx index fffac0d093..28b50bac9a 100644 --- a/packages/twenty-front/src/modules/navigation/components/NavigationDrawerTabbedContent.tsx +++ b/packages/twenty-front/src/modules/navigation/components/NavigationDrawerTabbedContent.tsx @@ -1,9 +1,10 @@ import { NavigationDrawerAiChatContent } from '@/ai/components/NavigationDrawerAiChatContent'; import { styled } from '@linaria/react'; -import { type ReactNode, useState } from 'react'; +import { type ReactNode } from 'react'; type NavigationDrawerTabbedContentProps = { showAiChatContent: boolean; + shouldMountAiChatContent: boolean; navigationContent: ReactNode; }; @@ -13,20 +14,15 @@ const StyledTabContent = styled.div<{ isHidden: boolean }>` export const NavigationDrawerTabbedContent = ({ showAiChatContent, + shouldMountAiChatContent, navigationContent, }: NavigationDrawerTabbedContentProps) => { - const [hasOpenedAiChat, setHasOpenedAiChat] = useState(showAiChatContent); - - if (showAiChatContent && !hasOpenedAiChat) { - setHasOpenedAiChat(true); - } - return ( <> {navigationContent} - {hasOpenedAiChat && ( + {shouldMountAiChatContent && ( diff --git a/packages/twenty-front/src/modules/navigation/components/SettingsNavigationDrawer.tsx b/packages/twenty-front/src/modules/navigation/components/SettingsNavigationDrawer.tsx index 721bc4933a..848fdf993a 100644 --- a/packages/twenty-front/src/modules/navigation/components/SettingsNavigationDrawer.tsx +++ b/packages/twenty-front/src/modules/navigation/components/SettingsNavigationDrawer.tsx @@ -4,12 +4,14 @@ import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFla import { SettingsNavigationDrawerItems } from '@/settings/components/SettingsNavigationDrawerItems'; import { NavigationDrawer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawer'; import { NavigationDrawerFixedContent } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerFixedContent'; +import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection'; import { NavigationDrawerScrollableContent } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerScrollableContent'; import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; import { navigationDrawerActiveTabState } from '@/ui/navigation/states/navigationDrawerActiveTabState'; import { NAVIGATION_DRAWER_TABS } from '@/ui/navigation/states/navigationDrawerTabs'; import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { css } from '@linaria/core'; import { styled } from '@linaria/react'; import { useLingui } from '@lingui/react/macro'; import { useIsMobile } from 'twenty-ui-deprecated/utilities'; @@ -17,9 +19,17 @@ import { AdvancedSettingsToggle } from 'twenty-ui-deprecated/navigation'; import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants'; import { PermissionFlagType } from '~/generated-metadata/graphql'; -const StyledAdvancedToggleWrapper = styled.div<{ isMobile: boolean }>` +const StyledAdvancedToggleFixedContent = styled.div<{ isMobile: boolean }>` + flex-shrink: 0; + margin-top: auto; + padding-left: ${({ isMobile }) => + isMobile ? themeCssVariables.spacing[5] : '0'}; padding-right: ${({ isMobile }) => - isMobile ? '0' : themeCssVariables.spacing[1]}; + isMobile ? themeCssVariables.spacing[5] : '0'}; +`; + +const advancedSettingsToggleClassName = css` + padding-right: 0; `; export const SettingsNavigationDrawer = ({ @@ -42,7 +52,7 @@ export const SettingsNavigationDrawer = ({ navigationDrawerActiveTab === NAVIGATION_DRAWER_TABS.AI_CHAT_HISTORY; return ( - + {hasAiPermission && ( @@ -52,20 +62,22 @@ export const SettingsNavigationDrawer = ({ } /> {!showAiChatContent && ( - - + + - - + + )} ); diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/ForbiddenFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/ForbiddenFieldDisplay.tsx index 1e57be96ba..a6b13aa2db 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/ForbiddenFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/ForbiddenFieldDisplay.tsx @@ -17,11 +17,12 @@ const StyledContainer = styled.div` font-size: ${themeCssVariables.font.size.md}; font-weight: ${themeCssVariables.font.weight.regular}; gap: ${themeCssVariables.spacing[1]}; - height: ${themeCssVariables.spacing[3]}; + height: ${themeCssVariables.spacing[5]}; + line-height: ${themeCssVariables.text.lineHeight.md}; overflow: hidden; - - padding: ${themeCssVariables.spacing[1]}; + padding: 0 ${themeCssVariables.spacing[1]}; user-select: none; + white-space: nowrap; `; export const ForbiddenFieldDisplay = () => { diff --git a/packages/twenty-front/src/modules/settings/admin-panel/ai/components/SettingsAdminAI.tsx b/packages/twenty-front/src/modules/settings/admin-panel/ai/components/SettingsAdminAI.tsx index de98860f00..76c58928c9 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/ai/components/SettingsAdminAI.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/ai/components/SettingsAdminAI.tsx @@ -4,13 +4,7 @@ import { useMutation, useQuery } from '@apollo/client/react'; import { t } from '@lingui/core/macro'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; -import { Tag } from 'twenty-ui-deprecated/components'; -import { - H2Title, - IconBolt, - IconLock, - IconRobot, -} from 'twenty-ui-deprecated/display'; +import { H2Title, IconBolt, IconRobot } from 'twenty-ui-deprecated/display'; import { Card, Section } from 'twenty-ui-deprecated/layout'; import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants'; @@ -49,6 +43,7 @@ import { AiModelRole, type AdminAiModelConfig, } from '~/generated-admin/graphql'; +import { OrganizationAdornment } from '~/pages/settings/enterprise/components/OrganizationAdornment'; const USAGE_TABLE_GRID_TEMPLATE_COLUMNS = '1fr 120px'; @@ -202,14 +197,7 @@ export const SettingsAdminAI = () => { - } + adornment={} /> { Icon={IconRobot} title={t`Smart Model`} description={t`Default model for chats and complex reasoning`} + divider > { label: t`Conversations`, value: stats ? stats.conversationsCount.toString() : '—', }, + ], + [ { Icon: IconSparkles, label: t`Skills`, value: stats ? stats.skillsCount.toString() : '—', }, + ], + [ { Icon: IconTool, label: t`Tools`, diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAiUsageTab.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAiUsageTab.tsx index 9245c3c864..617f4d768f 100644 --- a/packages/twenty-front/src/pages/settings/ai/components/SettingsAiUsageTab.tsx +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsAiUsageTab.tsx @@ -14,9 +14,9 @@ import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomState import { t } from '@lingui/core/macro'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; -import { Tag } from 'twenty-ui-deprecated/components'; -import { H2Title, IconLock } from 'twenty-ui-deprecated/display'; +import { H2Title } from 'twenty-ui-deprecated/display'; import { Section } from 'twenty-ui-deprecated/layout'; +import { OrganizationAdornment } from '~/pages/settings/enterprise/components/OrganizationAdornment'; export const SettingsAiUsageTab = () => { const currentWorkspace = useAtomStateValue(currentWorkspaceState); @@ -41,14 +41,7 @@ export const SettingsAiUsageTab = () => { - } + adornment={} /> { const { theme } = useContext(ThemeContext); + const { getIcon } = useIcons(); + const IconBrandDiscord = getIcon('IconBrandDiscord'); const communityLinks: SettingsCommunityLink[] = [ { @@ -42,7 +44,7 @@ export const SettingsCommunity = () => { title: t`Discord`, description: t`Join our community to get help and share feedback.`, href: 'https://discord.com/invite/cx5n4Jzs57', - Icon: IconMessage, + Icon: IconBrandDiscord, cardTitle: t`Join our Discord`, }, { diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx index 5b02bbd5a7..a2a3d60d7e 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx @@ -108,7 +108,7 @@ export const SettingsObjects = () => {