fix(twenty-front): new layout fast-follows — settings drawer, loading & command menu (#21389)
Second batch of new-layout fast-follows (master: twentyhq/core-team-issues#2478). All changes verified live against a running workspace. ## Settings drawer & header - **twentyhq/core-team-issues#2489** — sidebar icons render as plain 16px icons, no background tiles. - **twentyhq/core-team-issues#2488** — Advanced toggle spans the full drawer width; yellow dot removed. - **twentyhq/core-team-issues#2497** — page title stays centered in the settings header (breadcrumb stays left). - **twentyhq/core-team-issues#2490** — Exit Settings control aligned to the workspace switcher (24px, matching padding/gap). - **twentyhq/core-team-issues#2499** — 2px vertical gap restored between collapsible drawer section items. - **twentyhq/core-team-issues#2491** — settings drawer rhythm now matches the main app (28px items, 2px gaps, 28px section headers). - **twentyhq/core-team-issues#2492** — Home/Chat tab switch no longer flickers: both tab subtrees stay mounted (a shared `NavigationDrawerTabbedContent` toggles visibility instead of remounting + flashing the chat skeleton). ## Loading states - **twentyhq/core-team-issues#2486** — metadata loading shows an empty body (no dense skeleton rows). - **twentyhq/core-team-issues#2487** — settings table keeps its layout while loading, with the shimmer localized to the first row's first cell. ## Command menu & navigation - **twentyhq/core-team-issues#2501** — navigation section header height matches the nav item rhythm (28px). - **twentyhq/core-team-issues#2502 (part 1)** — the page side-panel toggle stays as the dots glyph while the command menu is open, instead of morphing into a second close control. ## New-field flow - **twentyhq/core-team-issues#2494** — the new-field stepper moved from a breadcrumb dropdown into a centered secondary wizard bar (back chevron + Save on the configure step); breadcrumb stays clean and the object label is the centered title. ## Descoped (substantive bugs already fixed) - **twentyhq/core-team-issues#2500** — command-menu highlight right gutter: the menu-item base measures full-width, so it's likely a scrollbar gutter on the list, not the shared component. Left for a focused follow-up. - **twentyhq/core-team-issues#2502 part 2** — moving the command-menu close from left to right is cosmetic (the duplicate-control bug is fixed by part 1) and would touch the shared `SidePanelTopBar` used by search/AI panels. ## Verification typecheck (tsgo) + oxlint + oxfmt green for all changed files; each change DOM-measured / screenshotted in the running app.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { NavigationDrawerAiChatContent } from '@/ai/components/NavigationDrawerAiChatContent';
|
||||
import { MainNavigationDrawerNavigationContent } from '@/navigation/components/MainNavigationDrawerNavigationContent';
|
||||
import { MainNavigationDrawerTabsRow } from '@/navigation/components/MainNavigationDrawerTabsRow';
|
||||
import { NavigationDrawerTabbedContent } from '@/navigation/components/NavigationDrawerTabbedContent';
|
||||
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
|
||||
import { NavigationDrawer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
|
||||
import { NavigationDrawerFixedContent } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerFixedContent';
|
||||
@@ -32,11 +32,10 @@ export const MainNavigationDrawer = ({ className }: { className?: string }) => {
|
||||
</NavigationDrawerFixedContent>
|
||||
|
||||
<NavigationDrawerScrollableContent>
|
||||
{showAiChatContent ? (
|
||||
<NavigationDrawerAiChatContent />
|
||||
) : (
|
||||
<MainNavigationDrawerNavigationContent />
|
||||
)}
|
||||
<NavigationDrawerTabbedContent
|
||||
showAiChatContent={showAiChatContent}
|
||||
navigationContent={<MainNavigationDrawerNavigationContent />}
|
||||
/>
|
||||
</NavigationDrawerScrollableContent>
|
||||
</NavigationDrawer>
|
||||
);
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { NavigationDrawerAiChatContent } from '@/ai/components/NavigationDrawerAiChatContent';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type ReactNode, useState } from 'react';
|
||||
|
||||
type NavigationDrawerTabbedContentProps = {
|
||||
showAiChatContent: boolean;
|
||||
navigationContent: ReactNode;
|
||||
};
|
||||
|
||||
const StyledTabContent = styled.div<{ isHidden: boolean }>`
|
||||
display: ${({ isHidden }) => (isHidden ? 'none' : 'contents')};
|
||||
`;
|
||||
|
||||
export const NavigationDrawerTabbedContent = ({
|
||||
showAiChatContent,
|
||||
navigationContent,
|
||||
}: NavigationDrawerTabbedContentProps) => {
|
||||
const [hasOpenedAiChat, setHasOpenedAiChat] = useState(showAiChatContent);
|
||||
|
||||
if (showAiChatContent && !hasOpenedAiChat) {
|
||||
setHasOpenedAiChat(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledTabContent isHidden={showAiChatContent}>
|
||||
{navigationContent}
|
||||
</StyledTabContent>
|
||||
{hasOpenedAiChat && (
|
||||
<StyledTabContent isHidden={!showAiChatContent}>
|
||||
<NavigationDrawerAiChatContent />
|
||||
</StyledTabContent>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+7
-10
@@ -1,5 +1,5 @@
|
||||
import { NavigationDrawerAiChatContent } from '@/ai/components/NavigationDrawerAiChatContent';
|
||||
import { MainNavigationDrawerTabsRow } from '@/navigation/components/MainNavigationDrawerTabsRow';
|
||||
import { NavigationDrawerTabbedContent } from '@/navigation/components/NavigationDrawerTabbedContent';
|
||||
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
|
||||
import { SettingsNavigationDrawerItems } from '@/settings/components/SettingsNavigationDrawerItems';
|
||||
import { NavigationDrawer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
|
||||
@@ -18,10 +18,8 @@ import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
import { PermissionFlagType } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledAdvancedToggleWrapper = styled.div<{ isMobile: boolean }>`
|
||||
padding-left: ${({ isMobile }) =>
|
||||
isMobile ? '0' : themeCssVariables.spacing[5]};
|
||||
padding-right: ${({ isMobile }) =>
|
||||
isMobile ? '0' : themeCssVariables.spacing[8]};
|
||||
isMobile ? '0' : themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
export const SettingsNavigationDrawer = ({
|
||||
@@ -52,11 +50,10 @@ export const SettingsNavigationDrawer = ({
|
||||
)}
|
||||
|
||||
<NavigationDrawerScrollableContent>
|
||||
{showAiChatContent ? (
|
||||
<NavigationDrawerAiChatContent />
|
||||
) : (
|
||||
<SettingsNavigationDrawerItems />
|
||||
)}
|
||||
<NavigationDrawerTabbedContent
|
||||
showAiChatContent={showAiChatContent}
|
||||
navigationContent={<SettingsNavigationDrawerItems />}
|
||||
/>
|
||||
</NavigationDrawerScrollableContent>
|
||||
|
||||
{!showAiChatContent && (
|
||||
@@ -65,7 +62,7 @@ export const SettingsNavigationDrawer = ({
|
||||
<AdvancedSettingsToggle
|
||||
isAdvancedModeEnabled={isAdvancedModeEnabled}
|
||||
setIsAdvancedModeEnabled={setIsAdvancedModeEnabled}
|
||||
label={t`Advanced:`}
|
||||
label={t`Advanced`}
|
||||
/>
|
||||
</StyledAdvancedToggleWrapper>
|
||||
</NavigationDrawerFixedContent>
|
||||
|
||||
@@ -39,7 +39,6 @@ export const SettingsNavigationDrawerItem = ({
|
||||
label={item.label}
|
||||
to={href}
|
||||
Icon={item.Icon}
|
||||
withIconBackground
|
||||
active={isActive}
|
||||
modifier={item.modifier}
|
||||
onClick={item.onClick}
|
||||
@@ -55,7 +54,6 @@ export const SettingsNavigationDrawerItem = ({
|
||||
label={item.label}
|
||||
to={href || undefined}
|
||||
Icon={item.Icon}
|
||||
withIconBackground
|
||||
active={isActive}
|
||||
modifier={item.modifier}
|
||||
onClick={item.onClick}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
|
||||
import { PageCardHeader } from '@/ui/layout/page/components/PageCardHeader';
|
||||
import { PageCardLayout } from '@/ui/layout/page/components/PageCardLayout';
|
||||
import { useContext } from 'react';
|
||||
@@ -39,9 +37,7 @@ export const SettingsSkeletonLoader = () => {
|
||||
}
|
||||
showInformationBanner={false}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<SettingsSectionSkeletonLoader />
|
||||
</SettingsPageContainer>
|
||||
{null}
|
||||
</PageCardLayout>
|
||||
</SkeletonTheme>
|
||||
);
|
||||
|
||||
@@ -29,6 +29,7 @@ export const SettingsPageLayout = ({
|
||||
title={title}
|
||||
tag={tag}
|
||||
actionButton={actionButton}
|
||||
centerTitle
|
||||
/>
|
||||
}
|
||||
secondaryBar={
|
||||
|
||||
-135
@@ -1,135 +0,0 @@
|
||||
import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useContext } from 'react';
|
||||
import { useLocation, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconChevronDown } from 'twenty-ui-deprecated/display';
|
||||
import { Button } from 'twenty-ui-deprecated/input';
|
||||
import { MenuItem } from 'twenty-ui-deprecated/navigation';
|
||||
import {
|
||||
ThemeContext,
|
||||
themeCssVariables,
|
||||
} from 'twenty-ui-deprecated/theme-constants';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
cursor: default;
|
||||
display: flex;
|
||||
font-size: ${themeCssVariables.font.size.md};
|
||||
`;
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledDownChevronContainer = styled.span`
|
||||
align-items: center;
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
display: flex;
|
||||
position: absolute;
|
||||
right: ${themeCssVariables.spacing['1.5']};
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
`;
|
||||
|
||||
const StyledMenuItemWrapper = styled.div<{ disabled?: boolean }>`
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledSpan = styled.span`
|
||||
margin-left: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledButtonWrapper = styled.div`
|
||||
button {
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
padding-right: ${themeCssVariables.spacing[6]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const dropdownId = `settings-object-new-field-breadcrumb-dropdown`;
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
const navigate = useNavigateSettings();
|
||||
const location = useLocation();
|
||||
const { objectNamePlural = '' } = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const fieldType = searchParams.get('fieldType') as SettingsFieldType;
|
||||
const isConfigureStep = location.pathname.includes('/configure');
|
||||
|
||||
const handleClick = (step: 'select' | 'configure') => {
|
||||
if (step === 'configure' && isDefined(fieldType)) {
|
||||
navigate(
|
||||
SettingsPath.ObjectNewFieldConfigure,
|
||||
{ objectNamePlural },
|
||||
{ fieldType },
|
||||
);
|
||||
} else {
|
||||
navigate(
|
||||
SettingsPath.ObjectNewFieldSelect,
|
||||
{ objectNamePlural },
|
||||
fieldType ? { fieldType } : undefined,
|
||||
);
|
||||
}
|
||||
closeDropdown(dropdownId);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
{t`New Field`} <StyledSpan>-</StyledSpan>
|
||||
<Dropdown
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownId={dropdownId}
|
||||
clickableComponent={
|
||||
<StyledButtonContainer>
|
||||
<StyledDownChevronContainer>
|
||||
<IconChevronDown size={theme.icon.size.md} />
|
||||
</StyledDownChevronContainer>
|
||||
{isConfigureStep ? (
|
||||
<StyledButtonWrapper>
|
||||
<Button variant="tertiary" title={t`2. Configure`} />
|
||||
</StyledButtonWrapper>
|
||||
) : (
|
||||
<StyledButtonWrapper>
|
||||
<Button variant="tertiary" title={t`1. Type`} />
|
||||
</StyledButtonWrapper>
|
||||
)}
|
||||
</StyledButtonContainer>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<StyledMenuItemWrapper>
|
||||
<MenuItem
|
||||
text={t`1. Type`}
|
||||
onClick={() => handleClick('select')}
|
||||
selected={!isConfigureStep}
|
||||
/>
|
||||
</StyledMenuItemWrapper>
|
||||
<StyledMenuItemWrapper disabled={!isDefined(fieldType)}>
|
||||
<MenuItem
|
||||
text={t`2. Configure`}
|
||||
onClick={() => handleClick('configure')}
|
||||
selected={isConfigureStep}
|
||||
disabled={!isDefined(fieldType)}
|
||||
/>
|
||||
</StyledMenuItemWrapper>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
+15
-16
@@ -71,15 +71,10 @@ const StyledLoadingMore = styled.div`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const StyledSkeletonContainer = styled.div`
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledIntersectionObserver = styled.div`
|
||||
height: 1px;
|
||||
`;
|
||||
|
||||
const SKELETON_ROW_COUNT = 8;
|
||||
const EVENT_LOG_SCROLL_WRAPPER_INSTANCE_ID = 'event-log-results-table';
|
||||
|
||||
const buildGridTemplateColumns = (
|
||||
@@ -192,19 +187,23 @@ export const EventLogResultsTable = ({
|
||||
<TableHeader key={column.id}>{t(column.label)}</TableHeader>
|
||||
))}
|
||||
</TableRow>
|
||||
<TableRow gridTemplateColumns={gridTemplateColumns}>
|
||||
{baseColumns.map((column, index) => (
|
||||
<TableCell key={column.id}>
|
||||
{index === 0 && (
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<Skeleton width={120} height={16} />
|
||||
</SkeletonTheme>
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</Table>
|
||||
</StyledTableContainer>
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<StyledSkeletonContainer>
|
||||
{Array.from({ length: SKELETON_ROW_COUNT }).map((_, index) => (
|
||||
<Skeleton height={40} key={index} style={{ marginBottom: 4 }} />
|
||||
))}
|
||||
</StyledSkeletonContainer>
|
||||
</SkeletonTheme>
|
||||
</ScrollWrapper>
|
||||
</StyledScrollWrapperContainer>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { SIDE_PANEL_TOP_BAR_HEIGHT_MOBILE } from '@/side-panel/constants/SidePanelTopBarHeightMobile';
|
||||
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
|
||||
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
|
||||
import { sidePanelPageState } from '@/side-panel/states/sidePanelPageState';
|
||||
import { SidePanelPages } from 'twenty-shared/types';
|
||||
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
|
||||
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
|
||||
import { PAGE_HEADER_SIDE_PANEL_BUTTON_CLICK_OUTSIDE_ID } from '@/ui/layout/page-header/constants/PageHeaderSidePanelButtonClickOutsideId';
|
||||
@@ -130,12 +132,23 @@ const AnimatedIcon = ({
|
||||
export const SidePanelToggleButton = () => {
|
||||
const { toggleSidePanelMenu } = useSidePanelMenu();
|
||||
const isSidePanelOpened = useAtomStateValue(isSidePanelOpenedState);
|
||||
const sidePanelPage = useAtomStateValue(sidePanelPageState);
|
||||
const isLayoutCustomizationModeEnabled = useAtomStateValue(
|
||||
isLayoutCustomizationModeEnabledState,
|
||||
);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const isCommandMenuOpened =
|
||||
isSidePanelOpened &&
|
||||
[
|
||||
SidePanelPages.CommandMenuDisplay,
|
||||
SidePanelPages.CommandMenuEdit,
|
||||
SidePanelPages.SearchRecords,
|
||||
].includes(sidePanelPage);
|
||||
|
||||
const showAsOpen = isSidePanelOpened && !isCommandMenuOpened;
|
||||
|
||||
const alignWithSidePanelTopBar =
|
||||
isMobile && isLayoutCustomizationModeEnabled && isSidePanelOpened;
|
||||
|
||||
@@ -148,7 +161,7 @@ export const SidePanelToggleButton = () => {
|
||||
<StyledButtonWrapper alignToTop={alignWithSidePanelTopBar}>
|
||||
<div id="toggle-side-panel-button">
|
||||
<AnimatedButton
|
||||
animatedSvg={<AnimatedIcon isSidePanelOpened={isSidePanelOpened} />}
|
||||
animatedSvg={<AnimatedIcon isSidePanelOpened={showAsOpen} />}
|
||||
dataClickOutsideId={PAGE_HEADER_SIDE_PANEL_BUTTON_CLICK_OUTSIDE_ID}
|
||||
dataTestId="page-header-side-panel-button"
|
||||
size={isMobile ? 'medium' : 'small'}
|
||||
@@ -158,7 +171,7 @@ export const SidePanelToggleButton = () => {
|
||||
ariaLabel={ariaLabel}
|
||||
onClick={toggleSidePanelMenu}
|
||||
animate={{
|
||||
rotate: isSidePanelOpened ? 90 : 0,
|
||||
rotate: showAsOpen ? 90 : 0,
|
||||
}}
|
||||
transition={{
|
||||
duration: theme.animation.duration.normal,
|
||||
|
||||
@@ -19,6 +19,7 @@ type PageCardHeaderProps = {
|
||||
title?: ReactNode;
|
||||
tag?: ReactNode;
|
||||
actionButton?: ReactNode;
|
||||
centerTitle?: boolean;
|
||||
};
|
||||
|
||||
const StyledHeader = styled.div`
|
||||
@@ -30,6 +31,7 @@ const StyledHeader = styled.div`
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
min-height: ${SIDE_PANEL_TOP_BAR_HEIGHT}px;
|
||||
padding: 0 ${themeCssVariables.spacing[3]};
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@@ -51,6 +53,15 @@ const StyledTitle = styled.div`
|
||||
min-width: 0;
|
||||
`;
|
||||
|
||||
const StyledCenteredTitle = styled(StyledTitle)`
|
||||
bottom: 0;
|
||||
justify-content: center;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transform: translateX(-50%);
|
||||
`;
|
||||
|
||||
const StyledRight = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@@ -67,10 +78,22 @@ export const PageCardHeader = ({
|
||||
title,
|
||||
tag,
|
||||
actionButton,
|
||||
centerTitle = false,
|
||||
}: PageCardHeaderProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
const isNavigationDrawerExpanded = useNavigationDrawerExpanded();
|
||||
|
||||
const hasTitleContent =
|
||||
!isMobile && (isDefined(icon) || isDefined(title) || isDefined(tag));
|
||||
|
||||
const titleContent = (
|
||||
<>
|
||||
{icon}
|
||||
{isDefined(title) && title}
|
||||
{tag}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledHeader>
|
||||
<StyledLeft>
|
||||
@@ -80,15 +103,13 @@ export const PageCardHeader = ({
|
||||
{isDefined(breadcrumb)
|
||||
? breadcrumb
|
||||
: isDefined(links) && <Breadcrumb links={links} />}
|
||||
{!isMobile &&
|
||||
(isDefined(icon) || isDefined(title) || isDefined(tag)) && (
|
||||
<StyledTitle>
|
||||
{icon}
|
||||
{isDefined(title) && title}
|
||||
{tag}
|
||||
</StyledTitle>
|
||||
)}
|
||||
{!centerTitle && hasTitleContent && (
|
||||
<StyledTitle>{titleContent}</StyledTitle>
|
||||
)}
|
||||
</StyledLeft>
|
||||
{centerTitle && hasTitleContent && (
|
||||
<StyledCenteredTitle>{titleContent}</StyledCenteredTitle>
|
||||
)}
|
||||
<StyledRight
|
||||
data-click-outside-id={PAGE_ACTION_CONTAINER_CLICK_OUTSIDE_ID}
|
||||
>
|
||||
|
||||
+9
-1
@@ -1,8 +1,16 @@
|
||||
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 { styled } from '@linaria/react';
|
||||
import { type ReactNode } from 'react';
|
||||
import { AnimatedExpandableContainer } from 'twenty-ui-deprecated/layout';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
const StyledItems = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${themeCssVariables.betweenSiblingsGap};
|
||||
`;
|
||||
|
||||
type CollapsibleNavigationDrawerSectionProps = {
|
||||
// Namespaced id (e.g. 'settings/User') used to persist open/closed state.
|
||||
@@ -39,7 +47,7 @@ export const CollapsibleNavigationDrawerSection = ({
|
||||
containAnimation
|
||||
initial={false}
|
||||
>
|
||||
{children}
|
||||
<StyledItems>{children}</StyledItems>
|
||||
</AnimatedExpandableContainer>
|
||||
</NavigationDrawerSection>
|
||||
);
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ const StyledIconAndButtonContainer = styled.button`
|
||||
font-family: ${themeCssVariables.font.family};
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
padding: ${themeCssVariables.spacing['1.5']} ${themeCssVariables.spacing[1]};
|
||||
padding: ${themeCssVariables.spacing[1]};
|
||||
width: 100%;
|
||||
&:hover {
|
||||
background: ${themeCssVariables.background.transparent.light};
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ const StyledTitle = styled.div`
|
||||
align-items: center;
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
display: flex;
|
||||
height: ${themeCssVariables.spacing[5]};
|
||||
height: ${themeCssVariables.spacing[7]};
|
||||
justify-content: space-between;
|
||||
padding-bottom: ${themeCssVariables.spacing[1]};
|
||||
padding-left: ${themeCssVariables.spacing[1]};
|
||||
|
||||
+20
-18
@@ -1,9 +1,8 @@
|
||||
import { isDDLLockedState } from '@/client-config/states/isDDLLockedState';
|
||||
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsDataModelNewFieldBreadcrumbDropDown } from '@/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown';
|
||||
import { SettingsWizardStepBar } from '@/settings/components/layout/SettingsWizardStepBar';
|
||||
import { FIELD_NAME_MAXIMUM_LENGTH } from '@/settings/data-model/constants/FieldNameMaximumLength';
|
||||
import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm';
|
||||
import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
} from 'twenty-shared/types';
|
||||
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 { type z } from 'zod';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
@@ -181,7 +181,7 @@ export const SettingsObjectNewFieldConfigure = () => {
|
||||
{...formConfig}
|
||||
>
|
||||
<SettingsPageLayout
|
||||
title={t`2. Configure field`}
|
||||
title={activeObjectMetadataItem.labelPlural}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
@@ -197,26 +197,28 @@ export const SettingsObjectNewFieldConfigure = () => {
|
||||
objectNamePlural,
|
||||
}),
|
||||
},
|
||||
|
||||
{ children: <SettingsDataModelNewFieldBreadcrumbDropDown /> },
|
||||
{ children: t`New field` },
|
||||
]}
|
||||
actionButton={
|
||||
<SaveAndCancelButtons
|
||||
isLoading={isSaving}
|
||||
isSaveDisabled={!canSave}
|
||||
isCancelDisabled={isSubmitting}
|
||||
onCancel={() =>
|
||||
secondaryBar={
|
||||
<SettingsWizardStepBar
|
||||
label={t`2. Configure field`}
|
||||
onBack={() =>
|
||||
navigate(
|
||||
SettingsPath.ObjectNewFieldSelect,
|
||||
{
|
||||
objectNamePlural,
|
||||
},
|
||||
{
|
||||
fieldType,
|
||||
},
|
||||
{ objectNamePlural },
|
||||
{ fieldType },
|
||||
)
|
||||
}
|
||||
onSave={formConfig.handleSubmit(handleSave)}
|
||||
trailing={
|
||||
<Button
|
||||
title={t`Save`}
|
||||
variant="primary"
|
||||
size="small"
|
||||
accent="blue"
|
||||
onClick={formConfig.handleSubmit(handleSave)}
|
||||
disabled={!canSave || isSaving}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
||||
+7
-4
@@ -1,11 +1,11 @@
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsDataModelNewFieldBreadcrumbDropDown } from '@/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown';
|
||||
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
|
||||
import { SettingsWizardStepBar } from '@/settings/components/layout/SettingsWizardStepBar';
|
||||
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
||||
import { SettingsObjectNewFieldSelector } from '@/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector';
|
||||
import { type FieldType } from '@/settings/data-model/types/FieldType';
|
||||
import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useEffect } from 'react';
|
||||
@@ -65,7 +65,7 @@ export const SettingsObjectNewFieldSelect = () => {
|
||||
{...formMethods}
|
||||
>
|
||||
<SettingsPageLayout
|
||||
title={t`1. Select a field type`}
|
||||
title={activeObjectMetadataItem.labelPlural}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
@@ -78,8 +78,11 @@ export const SettingsObjectNewFieldSelect = () => {
|
||||
objectNamePlural,
|
||||
}),
|
||||
},
|
||||
{ children: <SettingsDataModelNewFieldBreadcrumbDropDown /> },
|
||||
{ children: t`New field` },
|
||||
]}
|
||||
secondaryBar={
|
||||
<SettingsWizardStepBar label={t`1. Select a field type`} />
|
||||
}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<SettingsObjectNewFieldSelector
|
||||
|
||||
+14
-36
@@ -1,16 +1,17 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { IconPoint } from '@ui/display';
|
||||
import { Toggle } from '@ui/input';
|
||||
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
|
||||
import { useContext, useId } from 'react';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
const StyledContainer = styled.label`
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
position: relative;
|
||||
height: ${themeCssVariables.spacing[5]};
|
||||
justify-content: space-between;
|
||||
padding: ${themeCssVariables.spacing[1]};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledText = styled.div`
|
||||
@@ -19,21 +20,6 @@ const StyledText = styled.div`
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
`;
|
||||
|
||||
const StyledIconContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
left: calc(-1 * ${themeCssVariables.spacing[5]});
|
||||
position: absolute;
|
||||
`;
|
||||
|
||||
const StyledToggleContainer = styled.label`
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type AdvancedSettingsToggleProps = {
|
||||
isAdvancedModeEnabled: boolean;
|
||||
setIsAdvancedModeEnabled: (enabled: boolean) => void;
|
||||
@@ -43,7 +29,7 @@ type AdvancedSettingsToggleProps = {
|
||||
export const AdvancedSettingsToggle = ({
|
||||
isAdvancedModeEnabled,
|
||||
setIsAdvancedModeEnabled,
|
||||
label = 'Advanced:',
|
||||
label = 'Advanced',
|
||||
}: AdvancedSettingsToggleProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
@@ -52,23 +38,15 @@ export const AdvancedSettingsToggle = ({
|
||||
};
|
||||
const instanceId = useId();
|
||||
|
||||
const yellowColor = theme.color.yellow;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledIconContainer>
|
||||
<IconPoint size={12} color={yellowColor} fill={yellowColor} />
|
||||
</StyledIconContainer>
|
||||
<StyledToggleContainer htmlFor={instanceId}>
|
||||
<StyledText>{label}</StyledText>
|
||||
|
||||
<Toggle
|
||||
id={instanceId}
|
||||
onChange={onChange}
|
||||
color={yellowColor}
|
||||
value={isAdvancedModeEnabled}
|
||||
/>
|
||||
</StyledToggleContainer>
|
||||
<StyledContainer htmlFor={instanceId}>
|
||||
<StyledText>{label}</StyledText>
|
||||
<Toggle
|
||||
id={instanceId}
|
||||
onChange={onChange}
|
||||
color={theme.color.yellow}
|
||||
value={isAdvancedModeEnabled}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user