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 <img width="1324" height="2668" alt="Settings surface fixes before/after board" src="https://github.com/user-attachments/assets/312131fa-3922-4724-9460-46fd373754d1" /> ### Navigation drawer fixes <img width="1324" height="1816" alt="Navigation drawer fixes before/after board" src="https://github.com/user-attachments/assets/ca0b29e5-e393-46c0-81f7-e6bed2557b59" /> ### Tables, wizards, gates and chips <img width="1324" height="2242" alt="Tables, wizards, gates and chips before/after board" src="https://github.com/user-attachments/assets/5f358240-c31c-4c15-8ca8-04ed761fbf85" />
This commit is contained in:
committed by
GitHub
parent
869680a5a1
commit
b14195428a
@@ -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]};
|
||||
`;
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
+3
-8
@@ -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 (
|
||||
<StyledSection>
|
||||
<NavigationDrawerSection>
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<NavigationDrawerSectionTitle
|
||||
label={title}
|
||||
@@ -74,6 +69,6 @@ export const NavigationDrawerAiChatThreadSection = ({
|
||||
</StyledThreadList>
|
||||
</AnimatedExpandableContainer>
|
||||
) : null}
|
||||
</StyledSection>
|
||||
</NavigationDrawerSection>
|
||||
);
|
||||
};
|
||||
|
||||
+8
@@ -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 (
|
||||
<AnimatedEaseInOut isOpen={!isLayoutCustomizationModeEnabled} initial>
|
||||
|
||||
@@ -34,6 +34,7 @@ export const MainNavigationDrawer = ({ className }: { className?: string }) => {
|
||||
<NavigationDrawerScrollableContent>
|
||||
<NavigationDrawerTabbedContent
|
||||
showAiChatContent={showAiChatContent}
|
||||
shouldMountAiChatContent={hasAiPermission}
|
||||
navigationContent={<MainNavigationDrawerNavigationContent />}
|
||||
/>
|
||||
</NavigationDrawerScrollableContent>
|
||||
|
||||
-8
@@ -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 (
|
||||
<StyledScrollableItemsContainer>
|
||||
<NavigationDrawerOpenedSection />
|
||||
@@ -43,7 +36,6 @@ export const MainNavigationDrawerScrollableItems = () => {
|
||||
<FavoritesSectionDispatcher />
|
||||
<WorkspaceSectionDispatcher />
|
||||
</Suspense>
|
||||
{!isLayoutCustomizationModeEnabled && <NavigationDrawerOtherSection />}
|
||||
</StyledScrollableItemsContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-8
@@ -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 (
|
||||
<>
|
||||
<StyledTabContent isHidden={showAiChatContent}>
|
||||
{navigationContent}
|
||||
</StyledTabContent>
|
||||
{hasOpenedAiChat && (
|
||||
{shouldMountAiChatContent && (
|
||||
<StyledTabContent isHidden={!showAiChatContent}>
|
||||
<NavigationDrawerAiChatContent />
|
||||
</StyledTabContent>
|
||||
|
||||
+19
-7
@@ -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 (
|
||||
<NavigationDrawer className={className} title={t`Exit Settings`}>
|
||||
<NavigationDrawer className={className} title={t`Settings`}>
|
||||
{hasAiPermission && (
|
||||
<NavigationDrawerFixedContent>
|
||||
<MainNavigationDrawerTabsRow />
|
||||
@@ -52,20 +62,22 @@ export const SettingsNavigationDrawer = ({
|
||||
<NavigationDrawerScrollableContent>
|
||||
<NavigationDrawerTabbedContent
|
||||
showAiChatContent={showAiChatContent}
|
||||
shouldMountAiChatContent={hasAiPermission}
|
||||
navigationContent={<SettingsNavigationDrawerItems />}
|
||||
/>
|
||||
</NavigationDrawerScrollableContent>
|
||||
|
||||
{!showAiChatContent && (
|
||||
<NavigationDrawerFixedContent>
|
||||
<StyledAdvancedToggleWrapper isMobile={isMobile}>
|
||||
<StyledAdvancedToggleFixedContent isMobile={isMobile}>
|
||||
<NavigationDrawerSection>
|
||||
<AdvancedSettingsToggle
|
||||
className={advancedSettingsToggleClassName}
|
||||
isAdvancedModeEnabled={isAdvancedModeEnabled}
|
||||
setIsAdvancedModeEnabled={setIsAdvancedModeEnabled}
|
||||
label={t`Advanced`}
|
||||
/>
|
||||
</StyledAdvancedToggleWrapper>
|
||||
</NavigationDrawerFixedContent>
|
||||
</NavigationDrawerSection>
|
||||
</StyledAdvancedToggleFixedContent>
|
||||
)}
|
||||
</NavigationDrawer>
|
||||
);
|
||||
|
||||
+4
-3
@@ -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 = () => {
|
||||
|
||||
+5
-21
@@ -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 = () => {
|
||||
<H2Title
|
||||
title={t`Custom Providers`}
|
||||
description={t`Add custom endpoints, private gateways, or additional regions.`}
|
||||
adornment={
|
||||
<Tag
|
||||
text={t`Enterprise`}
|
||||
color="transparent"
|
||||
Icon={IconLock}
|
||||
variant="border"
|
||||
/>
|
||||
}
|
||||
adornment={<OrganizationAdornment />}
|
||||
/>
|
||||
|
||||
<SettingsAdminAiProviderListCard
|
||||
@@ -230,6 +218,7 @@ export const SettingsAdminAI = () => {
|
||||
Icon={IconRobot}
|
||||
title={t`Smart Model`}
|
||||
description={t`Default model for chats and complex reasoning`}
|
||||
divider
|
||||
>
|
||||
<Select
|
||||
dropdownId="admin-smart-model-select"
|
||||
@@ -318,12 +307,7 @@ export const SettingsAdminAI = () => {
|
||||
selectSizeVariant="small"
|
||||
/>
|
||||
) : (
|
||||
<Tag
|
||||
text={t`Enterprise`}
|
||||
color="transparent"
|
||||
Icon={IconLock}
|
||||
variant="border"
|
||||
/>
|
||||
<OrganizationAdornment />
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
+21
-15
@@ -5,6 +5,7 @@ import {
|
||||
StyledSettingsCardTextContainer,
|
||||
StyledSettingsCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsCardContentBase';
|
||||
import { Separator } from '@/settings/components/Separator';
|
||||
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
|
||||
import { styled } from '@linaria/react';
|
||||
import {
|
||||
@@ -16,6 +17,7 @@ type SettingsOptionCardContentSelectProps = {
|
||||
Icon?: IconComponent;
|
||||
title: React.ReactNode;
|
||||
description?: string;
|
||||
divider?: boolean;
|
||||
disabled?: boolean;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
@@ -31,25 +33,29 @@ export const SettingsOptionCardContentSelect = ({
|
||||
Icon,
|
||||
title,
|
||||
description,
|
||||
divider,
|
||||
disabled = false,
|
||||
children,
|
||||
}: SettingsOptionCardContentSelectProps) => {
|
||||
return (
|
||||
<StyledSettingsCardContent disabled={disabled}>
|
||||
{Icon && (
|
||||
<StyledSettingsCardIcon>
|
||||
<SettingsOptionIconCustomizer Icon={Icon} />
|
||||
</StyledSettingsCardIcon>
|
||||
)}
|
||||
<StyledSettingsCardTextContainer>
|
||||
<StyledSettingsCardTitle>{title}</StyledSettingsCardTitle>
|
||||
{description && (
|
||||
<StyledSettingsCardDescription>
|
||||
<OverflowingTextWithTooltip text={description} />
|
||||
</StyledSettingsCardDescription>
|
||||
<>
|
||||
<StyledSettingsCardContent disabled={disabled}>
|
||||
{Icon && (
|
||||
<StyledSettingsCardIcon>
|
||||
<SettingsOptionIconCustomizer Icon={Icon} />
|
||||
</StyledSettingsCardIcon>
|
||||
)}
|
||||
</StyledSettingsCardTextContainer>
|
||||
<StyledSelectContainer>{children}</StyledSelectContainer>
|
||||
</StyledSettingsCardContent>
|
||||
<StyledSettingsCardTextContainer>
|
||||
<StyledSettingsCardTitle>{title}</StyledSettingsCardTitle>
|
||||
{description && (
|
||||
<StyledSettingsCardDescription>
|
||||
<OverflowingTextWithTooltip text={description} />
|
||||
</StyledSettingsCardDescription>
|
||||
)}
|
||||
</StyledSettingsCardTextContainer>
|
||||
<StyledSelectContainer>{children}</StyledSelectContainer>
|
||||
</StyledSettingsCardContent>
|
||||
{divider && <Separator />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ type SettingsPageLayoutProps = {
|
||||
secondaryBar?: ReactNode;
|
||||
children: ReactNode;
|
||||
tag?: JSX.Element;
|
||||
titleColor?: string;
|
||||
};
|
||||
|
||||
export const SettingsPageLayout = ({
|
||||
@@ -21,6 +22,7 @@ export const SettingsPageLayout = ({
|
||||
secondaryBar,
|
||||
children,
|
||||
tag,
|
||||
titleColor,
|
||||
}: SettingsPageLayoutProps) => (
|
||||
<PageCardLayout
|
||||
header={
|
||||
@@ -30,6 +32,7 @@ export const SettingsPageLayout = ({
|
||||
tag={tag}
|
||||
actionButton={actionButton}
|
||||
centerTitle
|
||||
titleColor={titleColor}
|
||||
/>
|
||||
}
|
||||
secondaryBar={
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||
import { useLabPublicFeatureFlags } from '@/settings/lab/hooks/useLabPublicFeatureFlags';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { styled } from '@linaria/react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
IconRelationManyToMany,
|
||||
IconSparkles,
|
||||
type IconComponent,
|
||||
} from 'twenty-ui-deprecated/display';
|
||||
import { Card } from 'twenty-ui-deprecated/layout';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
import { type FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledCardGrid = styled.div`
|
||||
display: grid;
|
||||
@@ -22,6 +28,11 @@ const StyledImage = styled.img`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const labFeatureFlagIcons: Partial<Record<FeatureFlagKey, IconComponent>> = {
|
||||
[FeatureFlagKey.IS_JUNCTION_RELATIONS_ENABLED]: IconRelationManyToMany,
|
||||
[FeatureFlagKey.IS_SETTINGS_DISCOVERY_HERO_ENABLED]: IconSparkles,
|
||||
};
|
||||
|
||||
export const SettingsLabContent = () => {
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
const { labPublicFeatureFlags, handleLabPublicFeatureFlagUpdate } =
|
||||
@@ -38,38 +49,59 @@ export const SettingsLabContent = () => {
|
||||
setHasImageLoadingError((prev) => ({ ...prev, [key]: true }));
|
||||
};
|
||||
|
||||
const labPublicFeatureFlagsWithImage = labPublicFeatureFlags.filter((flag) =>
|
||||
isNonEmptyString(flag.metadata.imagePath),
|
||||
);
|
||||
const labPublicFeatureFlagsWithoutImage = labPublicFeatureFlags.filter(
|
||||
(flag) => !isNonEmptyString(flag.metadata.imagePath),
|
||||
);
|
||||
|
||||
return (
|
||||
currentWorkspace?.id && (
|
||||
<StyledCardGrid>
|
||||
{[...labPublicFeatureFlags]
|
||||
.sort((a, b) => {
|
||||
// Sort flags with images first
|
||||
if (a.metadata.imagePath !== '' && b.metadata.imagePath === '')
|
||||
return -1;
|
||||
if (a.metadata.imagePath === '' && b.metadata.imagePath !== '')
|
||||
return 1;
|
||||
return 0;
|
||||
})
|
||||
.map((flag) => (
|
||||
<Card key={flag.key} rounded>
|
||||
{flag.metadata.imagePath && !hasImageLoadingError[flag.key] ? (
|
||||
<StyledImage
|
||||
src={flag.metadata.imagePath}
|
||||
alt={flag.metadata.label}
|
||||
onError={() => handleImageError(flag.key)}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{labPublicFeatureFlagsWithImage.map((flag) => (
|
||||
<Card
|
||||
key={flag.key}
|
||||
rounded
|
||||
backgroundColor={themeCssVariables.background.secondary}
|
||||
>
|
||||
{!hasImageLoadingError[flag.key] && (
|
||||
<StyledImage
|
||||
src={flag.metadata.imagePath ?? undefined}
|
||||
alt={flag.metadata.label}
|
||||
onError={() => handleImageError(flag.key)}
|
||||
/>
|
||||
)}
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={labFeatureFlagIcons[flag.key]}
|
||||
title={flag.metadata.label}
|
||||
description={flag.metadata.description}
|
||||
checked={flag.value}
|
||||
onChange={(value) => handleToggle(flag.key, value)}
|
||||
toggleCentered={false}
|
||||
/>
|
||||
</Card>
|
||||
))}
|
||||
|
||||
{labPublicFeatureFlagsWithoutImage.length > 0 && (
|
||||
<Card
|
||||
rounded
|
||||
backgroundColor={themeCssVariables.background.secondary}
|
||||
>
|
||||
{labPublicFeatureFlagsWithoutImage.map((flag, index) => (
|
||||
<SettingsOptionCardContentToggle
|
||||
key={flag.key}
|
||||
Icon={labFeatureFlagIcons[flag.key]}
|
||||
title={flag.metadata.label}
|
||||
description={flag.metadata.description}
|
||||
checked={flag.value}
|
||||
onChange={(value) => handleToggle(flag.key, value)}
|
||||
toggleCentered={false}
|
||||
divider={index < labPublicFeatureFlagsWithoutImage.length - 1}
|
||||
/>
|
||||
</Card>
|
||||
))}
|
||||
))}
|
||||
</Card>
|
||||
)}
|
||||
</StyledCardGrid>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -22,8 +22,10 @@ import {
|
||||
const DELETE_ACCOUNT_MODAL_ID = 'delete-account-modal';
|
||||
const LEAVE_WORKSPACE_MODAL_ID = 'leave-workspace-modal';
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
const StyledDangerActions = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
export const DeleteAccount = () => {
|
||||
@@ -75,38 +77,39 @@ export const DeleteAccount = () => {
|
||||
: t`Delete account and all the associated data`
|
||||
}
|
||||
/>
|
||||
{userHasMultipleWorkspaces && (
|
||||
<StyledDiv>
|
||||
<StyledDangerActions>
|
||||
{userHasMultipleWorkspaces && (
|
||||
<Button
|
||||
accent="danger"
|
||||
onClick={() => openModal(LEAVE_WORKSPACE_MODAL_ID)}
|
||||
variant="secondary"
|
||||
title={t`Leave workspace`}
|
||||
/>
|
||||
|
||||
<ConfirmationModal
|
||||
confirmationValue={userEmail}
|
||||
confirmationPlaceholder={userEmail ?? ''}
|
||||
modalInstanceId={LEAVE_WORKSPACE_MODAL_ID}
|
||||
title={t`Leave workspace`}
|
||||
subtitle={
|
||||
<>
|
||||
{t`This action cannot be undone. This will permanently remove your membership from this workspace.`}
|
||||
<br />
|
||||
{t`Please type in your email to confirm.`}
|
||||
</>
|
||||
}
|
||||
onConfirmClick={leaveWorkspace}
|
||||
confirmButtonText={t`Leave workspace`}
|
||||
/>
|
||||
</StyledDiv>
|
||||
)}
|
||||
<Button
|
||||
accent="danger"
|
||||
onClick={() => openModal(DELETE_ACCOUNT_MODAL_ID)}
|
||||
variant="secondary"
|
||||
title={t`Delete account`}
|
||||
/>
|
||||
</StyledDangerActions>
|
||||
{userHasMultipleWorkspaces && (
|
||||
<ConfirmationModal
|
||||
confirmationValue={userEmail}
|
||||
confirmationPlaceholder={userEmail ?? ''}
|
||||
modalInstanceId={LEAVE_WORKSPACE_MODAL_ID}
|
||||
title={t`Leave workspace`}
|
||||
subtitle={
|
||||
<>
|
||||
{t`This action cannot be undone. This will permanently remove your membership from this workspace.`}
|
||||
<br />
|
||||
{t`Please type in your email to confirm.`}
|
||||
</>
|
||||
}
|
||||
onConfirmClick={leaveWorkspace}
|
||||
confirmButtonText={t`Leave workspace`}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
accent="danger"
|
||||
onClick={() => openModal(DELETE_ACCOUNT_MODAL_ID)}
|
||||
variant="secondary"
|
||||
title={t`Delete account`}
|
||||
/>
|
||||
<ConfirmationModal
|
||||
confirmationValue={userEmail}
|
||||
confirmationPlaceholder={userEmail ?? ''}
|
||||
|
||||
+2
@@ -19,6 +19,7 @@ import {
|
||||
isRecordFilterValueValid,
|
||||
} from 'twenty-shared/utils';
|
||||
import { Button } from 'twenty-ui-deprecated/input';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import {
|
||||
type BillingEntitlement,
|
||||
@@ -149,6 +150,7 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({
|
||||
return (
|
||||
<SettingsPageLayout
|
||||
title={headerTitle}
|
||||
titleColor={themeCssVariables.font.color.tertiary}
|
||||
links={breadcrumbLinks}
|
||||
secondaryBar={
|
||||
<SettingsWizardStepBar
|
||||
|
||||
+3
-18
@@ -25,18 +25,17 @@ import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { Tag } from 'twenty-ui-deprecated/components';
|
||||
import {
|
||||
H2Title,
|
||||
IconClockHour8,
|
||||
IconHistory,
|
||||
IconLock,
|
||||
IconMail,
|
||||
IconTrash,
|
||||
} from 'twenty-ui-deprecated/display';
|
||||
import { Card, Section } from 'twenty-ui-deprecated/layout';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
import { UpdateWorkspaceDocument } from '~/generated-metadata/graphql';
|
||||
import { OrganizationAdornment } from '~/pages/settings/enterprise/components/OrganizationAdornment';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
width: 100%;
|
||||
@@ -190,14 +189,7 @@ export const SettingsSecuritySettings = () => {
|
||||
<H2Title
|
||||
title={t`SSO`}
|
||||
description={t`Configure an SSO connection`}
|
||||
adornment={
|
||||
<Tag
|
||||
text={t`Enterprise`}
|
||||
color="transparent"
|
||||
Icon={IconLock}
|
||||
variant="border"
|
||||
/>
|
||||
}
|
||||
adornment={<OrganizationAdornment />}
|
||||
/>
|
||||
<SettingsSSOIdentitiesProvidersListCard />
|
||||
</Section>
|
||||
@@ -246,14 +238,7 @@ export const SettingsSecuritySettings = () => {
|
||||
<H2Title
|
||||
title={t`Audit Logs`}
|
||||
description={t`Configure how long audit logs are retained`}
|
||||
adornment={
|
||||
<Tag
|
||||
text={t`Enterprise`}
|
||||
color="transparent"
|
||||
Icon={IconLock}
|
||||
variant="border"
|
||||
/>
|
||||
}
|
||||
adornment={<OrganizationAdornment />}
|
||||
/>
|
||||
{hasEnterpriseAccess ? (
|
||||
<Card rounded>
|
||||
|
||||
-12
@@ -8,7 +8,6 @@ import {
|
||||
useIsMobile,
|
||||
} from 'twenty-ui-deprecated/utilities';
|
||||
|
||||
import { BACKGROUND_MOCK_OTHER_ITEMS } from '@/sign-in-background-mock/constants/BackgroundMockOtherItems';
|
||||
import { BACKGROUND_MOCK_WORKSPACE_ITEMS } from '@/sign-in-background-mock/constants/BackgroundMockNavigationItems';
|
||||
import { NavigationDrawer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
|
||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||
@@ -63,17 +62,6 @@ export const BackgroundMockNavigationDrawer = ({
|
||||
/>
|
||||
))}
|
||||
</NavigationDrawerSection>
|
||||
<NavigationDrawerSection>
|
||||
<NavigationDrawerSectionTitle label={t`Other`} />
|
||||
{BACKGROUND_MOCK_OTHER_ITEMS.map((item) => (
|
||||
<NavigationDrawerItem
|
||||
key={item.label}
|
||||
label={item.label}
|
||||
Icon={item.Icon}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
))}
|
||||
</NavigationDrawerSection>
|
||||
</NavigationDrawer>
|
||||
);
|
||||
};
|
||||
|
||||
+1
@@ -8,6 +8,7 @@ const StyledHeader = styled.li`
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.light};
|
||||
border-top-left-radius: ${themeCssVariables.border.radius.sm};
|
||||
border-top-right-radius: ${themeCssVariables.border.radius.sm};
|
||||
box-sizing: content-box;
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
cursor: ${({ onClick }) => (onClick ? 'pointer' : 'default')};
|
||||
display: flex;
|
||||
|
||||
+55
-2
@@ -3,7 +3,19 @@ import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKey
|
||||
import { SidePanelForDesktop } from '@/side-panel/components/SidePanelForDesktop';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { styled } from '@linaria/react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useLocation, useOutlet } from 'react-router-dom';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
|
||||
const APP_TO_SETTINGS_TRANSITION_DURATION_IN_SECONDS = 0.3;
|
||||
|
||||
const SETTINGS_PATH_PREFIX = `/${AppPath.Settings}`;
|
||||
|
||||
const getMainAppLayoutRouteSection = (pathname: string) =>
|
||||
pathname === SETTINGS_PATH_PREFIX ||
|
||||
pathname.startsWith(`${SETTINGS_PATH_PREFIX}/`)
|
||||
? 'settings'
|
||||
: 'app';
|
||||
|
||||
const StyledRow = styled.div`
|
||||
display: flex;
|
||||
@@ -21,6 +33,47 @@ const StyledContent = styled.div`
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const StyledContentTransitionContainer = styled.div`
|
||||
display: grid;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const StyledContentTransitionPage = styled(motion.div)`
|
||||
display: flex;
|
||||
grid-area: 1 / 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const MainAppLayoutOutlet = () => {
|
||||
const { pathname } = useLocation();
|
||||
const outlet = useOutlet();
|
||||
const routeSection = getMainAppLayoutRouteSection(pathname);
|
||||
|
||||
return (
|
||||
<StyledContentTransitionContainer>
|
||||
<AnimatePresence initial={false}>
|
||||
<StyledContentTransitionPage
|
||||
key={routeSection}
|
||||
initial={{ opacity: 0, y: 4 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -4 }}
|
||||
transition={{
|
||||
duration: APP_TO_SETTINGS_TRANSITION_DURATION_IN_SECONDS,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
>
|
||||
{outlet}
|
||||
</StyledContentTransitionPage>
|
||||
</AnimatePresence>
|
||||
</StyledContentTransitionContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const MainAppLayoutWithSidePanel = () => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
@@ -29,7 +82,7 @@ export const MainAppLayoutWithSidePanel = () => {
|
||||
return (
|
||||
<StyledRow>
|
||||
<StyledContent>
|
||||
<Outlet />
|
||||
<MainAppLayoutOutlet />
|
||||
</StyledContent>
|
||||
{isMobile ? <CommandMenuForMobile /> : <SidePanelForDesktop />}
|
||||
</StyledRow>
|
||||
|
||||
@@ -20,6 +20,7 @@ type PageCardHeaderProps = {
|
||||
tag?: ReactNode;
|
||||
actionButton?: ReactNode;
|
||||
centerTitle?: boolean;
|
||||
titleColor?: string;
|
||||
};
|
||||
|
||||
const StyledHeader = styled.div`
|
||||
@@ -43,9 +44,10 @@ const StyledLeft = styled.div`
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
const StyledTitle = styled.div<{ titleColor?: string }>`
|
||||
align-items: center;
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
color: ${({ titleColor }) =>
|
||||
titleColor ?? themeCssVariables.font.color.primary};
|
||||
display: flex;
|
||||
font-size: ${themeCssVariables.font.size.md};
|
||||
font-weight: ${themeCssVariables.font.weight.semiBold};
|
||||
@@ -79,6 +81,7 @@ export const PageCardHeader = ({
|
||||
tag,
|
||||
actionButton,
|
||||
centerTitle = false,
|
||||
titleColor,
|
||||
}: PageCardHeaderProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
const isNavigationDrawerExpanded = useNavigationDrawerExpanded();
|
||||
@@ -104,11 +107,13 @@ export const PageCardHeader = ({
|
||||
? breadcrumb
|
||||
: isDefined(links) && <Breadcrumb links={links} />}
|
||||
{!centerTitle && hasTitleContent && (
|
||||
<StyledTitle>{titleContent}</StyledTitle>
|
||||
<StyledTitle titleColor={titleColor}>{titleContent}</StyledTitle>
|
||||
)}
|
||||
</StyledLeft>
|
||||
{centerTitle && hasTitleContent && (
|
||||
<StyledCenteredTitle>{titleContent}</StyledCenteredTitle>
|
||||
<StyledCenteredTitle titleColor={titleColor}>
|
||||
{titleContent}
|
||||
</StyledCenteredTitle>
|
||||
)}
|
||||
<StyledRight
|
||||
data-click-outside-id={PAGE_ACTION_CONTAINER_CLICK_OUTSIDE_ID}
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
const StyledTable = styled.div``;
|
||||
const StyledTable = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
> [data-table-row]:first-child:not(:has([data-table-header])),
|
||||
> [data-table-row]:has([data-table-header])
|
||||
+ [data-table-row]:not(:has([data-table-header])) {
|
||||
margin-top: ${themeCssVariables.spacing[2]};
|
||||
}
|
||||
|
||||
> [data-table-row]:last-child:not(:has([data-table-header])) {
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
}
|
||||
`;
|
||||
|
||||
export { StyledTable as Table };
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { type ComponentPropsWithoutRef, type Ref, forwardRef } from 'react';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
const StyledTableHeader = styled.div<{
|
||||
type TableHeaderProps = Omit<ComponentPropsWithoutRef<'div'>, 'onClick'> & {
|
||||
align?: 'left' | 'center' | 'right';
|
||||
onClick?: () => void;
|
||||
padding?: string;
|
||||
}>`
|
||||
};
|
||||
|
||||
const StyledTableHeader = styled.div<TableHeaderProps>`
|
||||
align-items: center;
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.light};
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
@@ -24,4 +27,13 @@ const StyledTableHeader = styled.div<{
|
||||
text-align: ${({ align }) => align ?? 'left'};
|
||||
`;
|
||||
|
||||
export { StyledTableHeader as TableHeader };
|
||||
export const TableHeader = forwardRef(
|
||||
(props: TableHeaderProps, ref: Ref<HTMLDivElement>) => (
|
||||
<StyledTableHeader
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
ref={ref}
|
||||
data-table-header
|
||||
/>
|
||||
),
|
||||
);
|
||||
|
||||
@@ -107,6 +107,7 @@ export const TableRow = ({
|
||||
className={className}
|
||||
style={style}
|
||||
data-clickable={isClickable}
|
||||
data-table-row
|
||||
mobileGridAutoColumns={mobileGridAutoColumns}
|
||||
height={height}
|
||||
cursor={cursor}
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ export const MultiWorkspaceDropdownButton = () => {
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={MULTI_WORKSPACE_DROPDOWN_ID}
|
||||
dropdownOffset={{ y: -29, x: -5 }}
|
||||
dropdownOffset={{ y: -31, x: -5 }}
|
||||
clickableComponent={
|
||||
<MultiWorkspaceDropdownClickableComponent
|
||||
disabled={isLayoutCustomizationModeEnabled}
|
||||
|
||||
+7
-7
@@ -224,6 +224,13 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
|
||||
>
|
||||
<MenuItem LeftIcon={IconUserPlus} text={t`Invite user`} />
|
||||
</UndecoratedLink>
|
||||
{isSupportChatConfigured && (
|
||||
<MenuItem
|
||||
LeftIcon={IconMessage}
|
||||
text={t`Support`}
|
||||
onClick={handleSupport}
|
||||
/>
|
||||
)}
|
||||
<UndecoratedLink
|
||||
to={getSettingsPath(SettingsPath.ProfilePage)}
|
||||
onClick={() => {
|
||||
@@ -233,13 +240,6 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
|
||||
>
|
||||
<MenuItem LeftIcon={IconSettings} text={t`Settings`} />
|
||||
</UndecoratedLink>
|
||||
{isSupportChatConfigured && (
|
||||
<MenuItem
|
||||
LeftIcon={IconMessage}
|
||||
text={t`Support`}
|
||||
onClick={handleSupport}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
);
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ export const StyledContainer = styled.div<{
|
||||
display: flex;
|
||||
gap: ${({ isNavigationDrawerExpanded }) =>
|
||||
isNavigationDrawerExpanded ? themeCssVariables.spacing[2] : '0'};
|
||||
height: ${themeCssVariables.spacing[7]};
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
opacity: ${({ disabled }) => (disabled ? 0.5 : 1)};
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ const StyledContainer = styled.div<{
|
||||
flex-direction: column;
|
||||
gap: ${themeCssVariables.spacing[3]};
|
||||
height: 100%;
|
||||
padding: ${themeCssVariables.spacing[3]} 0 ${themeCssVariables.spacing[4]}
|
||||
padding: ${themeCssVariables.spacing[2]} 0 ${themeCssVariables.spacing[4]}
|
||||
${themeCssVariables.spacing[2]};
|
||||
width: ${({ isExpanded }) =>
|
||||
isExpanded ? `var(${NAVIGATION_DRAWER_WIDTH_VAR})` : '100%'};
|
||||
|
||||
+11
-7
@@ -23,19 +23,22 @@ type NavigationDrawerBackButtonProps = {
|
||||
const StyledIconAndButtonContainer = styled.button`
|
||||
align-items: center;
|
||||
background: inherit;
|
||||
border: none;
|
||||
color: ${themeCssVariables.font.color.secondary};
|
||||
border: 1px solid transparent;
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
box-sizing: border-box;
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: ${themeCssVariables.font.family};
|
||||
font-size: ${themeCssVariables.font.size.md};
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
padding: ${themeCssVariables.spacing[1]};
|
||||
width: 100%;
|
||||
height: ${themeCssVariables.spacing[7]};
|
||||
padding: 2px ${themeCssVariables.spacing[1]} 2px 2px;
|
||||
width: fit-content;
|
||||
&:hover {
|
||||
background: ${themeCssVariables.background.transparent.light};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -43,6 +46,7 @@ const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-shrink: 0;
|
||||
height: ${themeCssVariables.spacing[8]};
|
||||
justify-content: space-between;
|
||||
`;
|
||||
@@ -84,8 +88,8 @@ export const NavigationDrawerBackButton = ({
|
||||
<StyledIconAndButtonContainer>
|
||||
<IconX
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.lg}
|
||||
color={theme.font.color.tertiary}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.secondary}
|
||||
/>
|
||||
<span>{title}</span>
|
||||
</StyledIconAndButtonContainer>
|
||||
|
||||
+1
@@ -6,6 +6,7 @@ import { useIsMobile } from 'twenty-ui-deprecated/utilities';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
const StyledFixedContainer = styled.div<{ isMobile?: boolean }>`
|
||||
flex-shrink: 0;
|
||||
padding-left: ${({ isMobile }) =>
|
||||
isMobile ? themeCssVariables.spacing[5] : '0'};
|
||||
padding-right: ${({ isMobile }) =>
|
||||
|
||||
+4
-2
@@ -19,6 +19,7 @@ const StyledContainer = styled.div<{ isExpanded: boolean }>`
|
||||
align-items: ${({ isExpanded }) => (isExpanded ? 'center' : 'flex-start')};
|
||||
display: flex;
|
||||
flex-direction: ${({ isExpanded }) => (isExpanded ? 'row' : 'column')};
|
||||
flex-shrink: 0;
|
||||
gap: ${({ isExpanded }) => (isExpanded ? '0' : themeCssVariables.spacing[4])};
|
||||
min-height: ${PAGE_BAR_MIN_HEIGHT}px;
|
||||
padding-right: ${themeCssVariables.spacing[2]};
|
||||
@@ -37,7 +38,8 @@ const StyledRightActions = styled.div<{ isExpanded: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: ${({ isExpanded }) => (isExpanded ? 'row' : 'column')};
|
||||
flex-shrink: 0;
|
||||
gap: ${({ isExpanded }) => (isExpanded ? '0' : themeCssVariables.spacing[1])};
|
||||
gap: ${({ isExpanded }) =>
|
||||
isExpanded ? '2px' : themeCssVariables.spacing[1]};
|
||||
margin-left: ${({ isExpanded }) => (isExpanded ? 'auto' : '0')};
|
||||
transition: gap calc(${themeCssVariables.animation.duration.normal} * 1s) ease;
|
||||
`;
|
||||
@@ -45,7 +47,7 @@ const StyledRightActions = styled.div<{ isExpanded: boolean }>`
|
||||
const StyledNavigationDrawerCollapseButtonContainer = styled.div`
|
||||
> * {
|
||||
height: ${themeCssVariables.spacing[6]};
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
padding-right: 0;
|
||||
width: ${themeCssVariables.spacing[6]};
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -40,13 +40,13 @@ const StyledSecondaryFullVerticalBar = styled.div<{ darker: boolean }>`
|
||||
? themeCssVariables.font.color.tertiary
|
||||
: themeCssVariables.border.color.strong};
|
||||
|
||||
height: 28px;
|
||||
height: calc(${themeCssVariables.spacing[7]} + 1px);
|
||||
position: relative;
|
||||
top: -17px;
|
||||
width: 1px;
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
height: ${themeCssVariables.spacing[8]};
|
||||
height: calc(${themeCssVariables.spacing[8]} + 1px);
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
+10
-2
@@ -9,10 +9,18 @@ const StyledItemsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledNavigationDrawerScrollWrapper = styled(ScrollWrapper)`
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
`;
|
||||
|
||||
const StyledScrollableMobileInnerContainer = styled.div`
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
padding-left: ${themeCssVariables.spacing[5]};
|
||||
padding-right: ${themeCssVariables.spacing[5]};
|
||||
`;
|
||||
@@ -26,7 +34,7 @@ export const NavigationDrawerScrollableContent = ({
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<ScrollWrapper
|
||||
<StyledNavigationDrawerScrollWrapper
|
||||
componentInstanceId={`scroll-wrapper-${
|
||||
isSettingsDrawer ? 'settings-' : ''
|
||||
}navigation-drawer`}
|
||||
@@ -41,6 +49,6 @@ export const NavigationDrawerScrollableContent = ({
|
||||
<>{children}</>
|
||||
)}
|
||||
</StyledItemsContainer>
|
||||
</ScrollWrapper>
|
||||
</StyledNavigationDrawerScrollWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,8 +22,8 @@ import { SETTINGS_SKILL_TABLE_METADATA } from '~/pages/settings/ai/constants/Set
|
||||
import { normalizeSearchText } from '~/utils/normalizeSearchText';
|
||||
import { SettingsAgentSkillsTable } from './SettingsAgentSkillsTable';
|
||||
|
||||
const StyledSearchInput = styled(SearchInput)`
|
||||
margin-bottom: ${themeCssVariables.spacing[4]};
|
||||
const StyledSearchContainer = styled.div`
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
export const SettingsAgentSkillsTab = () => {
|
||||
@@ -89,32 +89,34 @@ export const SettingsAgentSkillsTab = () => {
|
||||
description={t`Use filter to see existing skills or create your own`}
|
||||
/>
|
||||
|
||||
<StyledSearchInput
|
||||
placeholder={t`Search a skill...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
filterDropdown={(filterButton) => (
|
||||
<Dropdown
|
||||
dropdownId="settings-skills-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
clickableComponent={filterButton}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconArchive}
|
||||
onToggleChange={setShowDeactivated}
|
||||
toggled={showDeactivated}
|
||||
text={t`Deactivated`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<StyledSearchContainer>
|
||||
<SearchInput
|
||||
placeholder={t`Search a skill...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
filterDropdown={(filterButton) => (
|
||||
<Dropdown
|
||||
dropdownId="settings-skills-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
clickableComponent={filterButton}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconArchive}
|
||||
onToggleChange={setShowDeactivated}
|
||||
toggled={showDeactivated}
|
||||
text={t`Deactivated`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</StyledSearchContainer>
|
||||
<SettingsAgentSkillsTable
|
||||
skills={filteredSkills}
|
||||
loading={loading}
|
||||
|
||||
@@ -129,6 +129,7 @@ export const SettingsAiModelsTab = () => {
|
||||
Icon={IconBrain}
|
||||
title={t`Smart Model`}
|
||||
description={t`Used for chats, agents, and complex reasoning`}
|
||||
divider
|
||||
>
|
||||
<Select
|
||||
dropdownId="models-tab-smart-model-select"
|
||||
|
||||
@@ -98,11 +98,15 @@ export const SettingsAiOverviewTab = () => {
|
||||
label: t`Conversations`,
|
||||
value: stats ? stats.conversationsCount.toString() : '—',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
Icon: IconSparkles,
|
||||
label: t`Skills`,
|
||||
value: stats ? stats.skillsCount.toString() : '—',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
Icon: IconTool,
|
||||
label: t`Tools`,
|
||||
|
||||
@@ -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 = () => {
|
||||
<H2Title
|
||||
title={t`AI Usage`}
|
||||
description={t`Track AI consumption across your workspace.`}
|
||||
adornment={
|
||||
<Tag
|
||||
text={t`Enterprise`}
|
||||
color="transparent"
|
||||
Icon={IconLock}
|
||||
variant="border"
|
||||
/>
|
||||
}
|
||||
adornment={<OrganizationAdornment />}
|
||||
/>
|
||||
<SettingsEnterpriseFeatureGateCard
|
||||
title={t`Enterprise feature`}
|
||||
|
||||
@@ -8,9 +8,9 @@ import { useContext } from 'react';
|
||||
import {
|
||||
H2Title,
|
||||
IconBriefcase,
|
||||
type IconComponent,
|
||||
IconMessage,
|
||||
IconTransform,
|
||||
type IconComponent,
|
||||
useIcons,
|
||||
} from 'twenty-ui-deprecated/display';
|
||||
import { Section } from 'twenty-ui-deprecated/layout';
|
||||
import { ThemeContext } from 'twenty-ui-deprecated/theme-constants';
|
||||
@@ -29,6 +29,8 @@ type SettingsCommunityLink = {
|
||||
|
||||
export const SettingsCommunity = () => {
|
||||
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`,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -108,7 +108,7 @@ export const SettingsObjects = () => {
|
||||
<Button
|
||||
title={t`Visualize`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
size="medium"
|
||||
Icon={IconEye}
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
|
||||
+2
@@ -24,6 +24,7 @@ import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
import { H2Title } from 'twenty-ui-deprecated/display';
|
||||
import { Button } from 'twenty-ui-deprecated/input';
|
||||
import { Section } from 'twenty-ui-deprecated/layout';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
import { type z } from 'zod';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
@@ -182,6 +183,7 @@ export const SettingsObjectNewFieldConfigure = () => {
|
||||
>
|
||||
<SettingsPageLayout
|
||||
title={activeObjectMetadataItem.labelPlural}
|
||||
titleColor={themeCssVariables.font.color.tertiary}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
|
||||
+2
@@ -13,6 +13,7 @@ import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { AppPath, SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
import { z } from 'zod';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
@@ -66,6 +67,7 @@ export const SettingsObjectNewFieldSelect = () => {
|
||||
>
|
||||
<SettingsPageLayout
|
||||
title={activeObjectMetadataItem.labelPlural}
|
||||
titleColor={themeCssVariables.font.color.tertiary}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { t } from '@lingui/core/macro';
|
||||
import { Navigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { FindOneAgentDocument } from '~/generated-metadata/graphql';
|
||||
|
||||
@@ -84,6 +85,7 @@ export const SettingsRoleAddObjectLevel = () => {
|
||||
<SettingsRolesQueryEffect />
|
||||
<SettingsPageLayout
|
||||
title={headerTitle}
|
||||
titleColor={themeCssVariables.font.color.tertiary}
|
||||
links={breadcrumbLinks}
|
||||
secondaryBar={<SettingsWizardStepBar label={t`1. Select an object`} />}
|
||||
>
|
||||
|
||||
+3
-1
@@ -24,12 +24,14 @@ type AdvancedSettingsToggleProps = {
|
||||
isAdvancedModeEnabled: boolean;
|
||||
setIsAdvancedModeEnabled: (enabled: boolean) => void;
|
||||
label?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const AdvancedSettingsToggle = ({
|
||||
isAdvancedModeEnabled,
|
||||
setIsAdvancedModeEnabled,
|
||||
label = 'Advanced',
|
||||
className,
|
||||
}: AdvancedSettingsToggleProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
@@ -39,7 +41,7 @@ export const AdvancedSettingsToggle = ({
|
||||
const instanceId = useId();
|
||||
|
||||
return (
|
||||
<StyledContainer htmlFor={instanceId}>
|
||||
<StyledContainer className={className} htmlFor={instanceId}>
|
||||
<StyledText>{label}</StyledText>
|
||||
<Toggle
|
||||
id={instanceId}
|
||||
|
||||
Reference in New Issue
Block a user