mirror of
https://github.com/twentyhq/twenty.git
synced 2026-06-12 01:46:39 -04:00
feat(twenty-front): generalize the page primary/secondary bars (flat redesign) (#21308)
Replaces #21279 and #21282 with one clean PR from `main`. Generalizes the settings primary-bar / secondary-bar card chrome to the record index, record show and standalone pages via a shared `PageCardLayout` + `PageCardHeader` (the side panel sits as a sibling of the content card), and applies the new flat design direction: square corners on the card, side panel and loading skeletons. Iterating toward the new design (Figma node 102282-221623); the confirmed direction and the explicit "remove rounded corners" change are in, remaining designer specifics to follow.
This commit is contained in:
@@ -1,59 +1,58 @@
|
||||
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
|
||||
import { PageBody } from '@/ui/layout/page/components/PageBody';
|
||||
import { PAGE_BAR_MIN_HEIGHT } from '@/ui/layout/page/constants/PageBarMinHeight';
|
||||
import { PageCardHeader } from '@/ui/layout/page/components/PageCardHeader';
|
||||
import { PageCardLayout } from '@/ui/layout/page/components/PageCardLayout';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useContext } from 'react';
|
||||
import { type ReactNode, useContext } from 'react';
|
||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||
import {
|
||||
ThemeContext,
|
||||
themeCssVariables,
|
||||
} from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
const StyledHeaderSkeleton = styled.div`
|
||||
align-items: center;
|
||||
background: ${themeCssVariables.background.noisy};
|
||||
const StyledBody = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
justify-content: space-between;
|
||||
min-height: ${PAGE_BAR_MIN_HEIGHT}px;
|
||||
padding: ${themeCssVariables.spacing[3]};
|
||||
`;
|
||||
|
||||
const StyledHeaderLeft = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
type PageContentSkeletonLoaderProps = {
|
||||
secondaryBar?: ReactNode;
|
||||
};
|
||||
|
||||
export const PageContentSkeletonLoader = () => {
|
||||
export const PageContentSkeletonLoader = ({
|
||||
secondaryBar,
|
||||
}: PageContentSkeletonLoaderProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledHeaderSkeleton>
|
||||
<StyledHeaderLeft>
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<Skeleton
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
width={104}
|
||||
/>
|
||||
</SkeletonTheme>
|
||||
</StyledHeaderLeft>
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<Skeleton
|
||||
width={132}
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<PageCardLayout
|
||||
header={
|
||||
<PageCardHeader
|
||||
icon={<Skeleton width={20} height={20} />}
|
||||
title={
|
||||
<Skeleton
|
||||
width={120}
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SkeletonTheme>
|
||||
</StyledHeaderSkeleton>
|
||||
<PageBody>{null}</PageBody>
|
||||
</>
|
||||
}
|
||||
secondaryBar={secondaryBar}
|
||||
showInformationBanner={false}
|
||||
>
|
||||
<StyledBody>
|
||||
<Skeleton
|
||||
count={8}
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.l}
|
||||
/>
|
||||
</StyledBody>
|
||||
</PageCardLayout>
|
||||
</SkeletonTheme>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { PageContentSkeletonLoader } from '~/loading/components/PageContentSkeletonLoader';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
const StyledRightPanelContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const RightPanelSkeletonLoader = () => (
|
||||
<StyledRightPanelContainer>
|
||||
<PageContentSkeletonLoader />
|
||||
</StyledRightPanelContainer>
|
||||
);
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from 'twenty-ui-deprecated/theme-constants';
|
||||
import { ModalBackdrop } from 'twenty-ui-deprecated/layout';
|
||||
import { LeftPanelSkeletonLoader } from '~/loading/components/LeftPanelSkeletonLoader';
|
||||
import { RightPanelSkeletonLoader } from '~/loading/components/RightPanelSkeletonLoader';
|
||||
import { PageContentSkeletonLoader } from '~/loading/components/PageContentSkeletonLoader';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
background: ${themeCssVariables.background.noisy};
|
||||
@@ -44,7 +44,7 @@ export const UserOrMetadataLoader = () => {
|
||||
<StyledLeftPanelWrapper>
|
||||
<LeftPanelSkeletonLoader />
|
||||
</StyledLeftPanelWrapper>
|
||||
<RightPanelSkeletonLoader />
|
||||
<PageContentSkeletonLoader />
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
|
||||
import { type ReactNode, Suspense } from 'react';
|
||||
import { PageContentSkeletonLoader } from '~/loading/components/PageContentSkeletonLoader';
|
||||
|
||||
@@ -7,13 +6,7 @@ type LazyRouteProps = {
|
||||
fallback?: ReactNode;
|
||||
};
|
||||
|
||||
const LazyRouteFallback = () => (
|
||||
<PageContainer>
|
||||
<PageContentSkeletonLoader />
|
||||
</PageContainer>
|
||||
);
|
||||
|
||||
export const LazyRoute = ({
|
||||
children,
|
||||
fallback = <LazyRouteFallback />,
|
||||
fallback = <PageContentSkeletonLoader />,
|
||||
}: LazyRouteProps) => <Suspense fallback={fallback}>{children}</Suspense>;
|
||||
|
||||
@@ -5,8 +5,10 @@ import { VerifyLoginTokenEffect } from '@/auth/components/VerifyLoginTokenEffect
|
||||
|
||||
import { VerifyEmailEffect } from '@/auth/components/VerifyEmailEffect';
|
||||
import indexAppPath from '@/navigation/utils/indexAppPath';
|
||||
import { RecordIndexSkeletonLoader } from '@/object-record/record-index/components/RecordIndexSkeletonLoader';
|
||||
import { BlankLayout } from '@/ui/layout/page/components/BlankLayout';
|
||||
import { DefaultLayout } from '@/ui/layout/page/components/DefaultLayout';
|
||||
import { MainAppLayoutWithSidePanel } from '@/ui/layout/page/components/MainAppLayoutWithSidePanel';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
|
||||
import { lazy } from 'react';
|
||||
@@ -209,48 +211,50 @@ export const useCreateAppRouter = (
|
||||
</LazyRoute>
|
||||
}
|
||||
/>
|
||||
<Route path={indexAppPath.getIndexAppPath()} element={<></>} />
|
||||
<Route
|
||||
path={AppPath.RecordIndexPage}
|
||||
element={
|
||||
<LazyRoute>
|
||||
<RecordIndexPage />
|
||||
</LazyRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.RecordShowPage}
|
||||
element={
|
||||
<LazyRoute>
|
||||
<RecordShowPage />
|
||||
</LazyRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.PageLayoutPage}
|
||||
element={
|
||||
<LazyRoute>
|
||||
<StandalonePageLayoutPage />
|
||||
</LazyRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.SettingsCatchAll}
|
||||
element={
|
||||
<SettingsRoutes
|
||||
isFunctionSettingsEnabled={isFunctionSettingsEnabled}
|
||||
isAdminPageEnabled={isAdminPageEnabled}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.NotFoundWildcard}
|
||||
element={
|
||||
<LazyRoute>
|
||||
<NotFound />
|
||||
</LazyRoute>
|
||||
}
|
||||
/>
|
||||
<Route element={<MainAppLayoutWithSidePanel />}>
|
||||
<Route path={indexAppPath.getIndexAppPath()} element={<></>} />
|
||||
<Route
|
||||
path={AppPath.RecordIndexPage}
|
||||
element={
|
||||
<LazyRoute fallback={<RecordIndexSkeletonLoader />}>
|
||||
<RecordIndexPage />
|
||||
</LazyRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.RecordShowPage}
|
||||
element={
|
||||
<LazyRoute>
|
||||
<RecordShowPage />
|
||||
</LazyRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.PageLayoutPage}
|
||||
element={
|
||||
<LazyRoute>
|
||||
<StandalonePageLayoutPage />
|
||||
</LazyRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.SettingsCatchAll}
|
||||
element={
|
||||
<SettingsRoutes
|
||||
isFunctionSettingsEnabled={isFunctionSettingsEnabled}
|
||||
isAdminPageEnabled={isAdminPageEnabled}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.NotFoundWildcard}
|
||||
element={
|
||||
<LazyRoute>
|
||||
<NotFound />
|
||||
</LazyRoute>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
</Route>
|
||||
<Route element={<BlankLayout />}>
|
||||
<Route
|
||||
|
||||
@@ -31,12 +31,14 @@ const StyledPreviewWrapper = styled.div`
|
||||
|
||||
type CommandMenuItemRendererProps = {
|
||||
item: CommandMenuItemFieldsFragment;
|
||||
isPrimaryAction?: boolean;
|
||||
};
|
||||
|
||||
type CommandMenuItemButtonRendererProps = CommandMenuItemRendererProps;
|
||||
|
||||
const CommandMenuItemButtonRenderer = ({
|
||||
item,
|
||||
isPrimaryAction = false,
|
||||
}: CommandMenuItemButtonRendererProps) => {
|
||||
const { commandMenuContextApi, isInPreviewMode } =
|
||||
useContext(CommandMenuContext);
|
||||
@@ -60,7 +62,10 @@ const CommandMenuItemButtonRenderer = ({
|
||||
if (isInPreviewMode) {
|
||||
return (
|
||||
<StyledPreviewWrapper>
|
||||
<CommandMenuButton command={command} />
|
||||
<CommandMenuButton
|
||||
command={command}
|
||||
isPrimaryAction={isPrimaryAction}
|
||||
/>
|
||||
</StyledPreviewWrapper>
|
||||
);
|
||||
}
|
||||
@@ -70,6 +75,7 @@ const CommandMenuItemButtonRenderer = ({
|
||||
command={command}
|
||||
onClick={disabled ? undefined : handleClick}
|
||||
disabled={disabled}
|
||||
isPrimaryAction={isPrimaryAction}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -167,11 +173,17 @@ const CommandMenuItemSelectableRenderer = ({
|
||||
// oxlint-disable-next-line twenty/effect-components
|
||||
export const CommandMenuItemRenderer = ({
|
||||
item,
|
||||
isPrimaryAction,
|
||||
}: CommandMenuItemRendererProps) => {
|
||||
const { displayType } = useContext(CommandMenuContext);
|
||||
|
||||
if (displayType === 'button') {
|
||||
return <CommandMenuItemButtonRenderer item={item} />;
|
||||
return (
|
||||
<CommandMenuItemButtonRenderer
|
||||
item={item}
|
||||
isPrimaryAction={isPrimaryAction}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (displayType === 'listItem' || displayType === 'dropdownItem') {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { styled } from '@linaria/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { ThemeContext } from 'twenty-ui-deprecated/theme-constants';
|
||||
import { EngineComponentKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledCommandMenuItemContainer = styled(motion.div)`
|
||||
align-items: center;
|
||||
@@ -80,7 +81,13 @@ export const PinnedCommandMenuItemButtons = () => {
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
>
|
||||
<CommandMenuItemRenderer item={item} />
|
||||
<CommandMenuItemRenderer
|
||||
item={item}
|
||||
isPrimaryAction={
|
||||
item.engineComponentKey ===
|
||||
EngineComponentKey.CREATE_NEW_RECORD
|
||||
}
|
||||
/>
|
||||
</StyledCommandMenuItemContainer>
|
||||
))}
|
||||
</StyledItemsContainer>
|
||||
|
||||
@@ -28,6 +28,7 @@ export type CommandMenuButtonProps = {
|
||||
onClick?: (event?: MouseEvent<HTMLElement>) => void;
|
||||
to?: string;
|
||||
disabled?: boolean;
|
||||
isPrimaryAction?: boolean;
|
||||
};
|
||||
|
||||
export const CommandMenuButton = ({
|
||||
@@ -35,6 +36,7 @@ export const CommandMenuButton = ({
|
||||
onClick,
|
||||
to,
|
||||
disabled = false,
|
||||
isPrimaryAction = false,
|
||||
}: CommandMenuButtonProps) => {
|
||||
const resolvedLabel = getCommandMenuItemLabel(command.label);
|
||||
|
||||
@@ -50,8 +52,8 @@ export const CommandMenuButton = ({
|
||||
<Button
|
||||
Icon={command.Icon}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
accent={buttonAccent}
|
||||
variant={isPrimaryAction ? 'primary' : 'secondary'}
|
||||
accent={isPrimaryAction ? 'blue' : buttonAccent}
|
||||
to={to}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
@@ -63,8 +65,8 @@ export const CommandMenuButton = ({
|
||||
<IconButton
|
||||
Icon={command.Icon}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
accent={buttonAccent}
|
||||
variant={isPrimaryAction ? 'primary' : 'secondary'}
|
||||
accent={isPrimaryAction ? 'blue' : buttonAccent}
|
||||
to={to}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import { CommandMenuForMobile } from '@/command-menu/components/CommandMenuForMobile';
|
||||
import { SidePanelForDesktop } from '@/side-panel/components/SidePanelForDesktop';
|
||||
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
|
||||
import { PageBody } from '@/ui/layout/page/components/PageBody';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type ReactNode } from 'react';
|
||||
import { useIsMobile } from 'twenty-ui-deprecated/utilities';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
type MainContainerLayoutWithSidePanelProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const StyledMainContainerLayoutForDesktop = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding-bottom: ${themeCssVariables.spacing[3]};
|
||||
padding-right: ${themeCssVariables.spacing[3]};
|
||||
`;
|
||||
|
||||
const StyledPageBodyForDesktopContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
width: 0;
|
||||
|
||||
> * {
|
||||
padding-bottom: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledMainContainerLayoutForMobile = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
`;
|
||||
|
||||
const StyledPageBodyForMobileContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
width: 0;
|
||||
|
||||
> * {
|
||||
padding-bottom: 0;
|
||||
padding-left: ${themeCssVariables.spacing[1]};
|
||||
padding-right: ${themeCssVariables.spacing['1.5']};
|
||||
}
|
||||
`;
|
||||
|
||||
export const MainContainerLayoutWithSidePanel = ({
|
||||
children,
|
||||
}: MainContainerLayoutWithSidePanelProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
useCommandMenuHotKeys();
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<StyledMainContainerLayoutForMobile>
|
||||
<StyledPageBodyForMobileContainer>
|
||||
<PageBody>{children}</PageBody>
|
||||
</StyledPageBodyForMobileContainer>
|
||||
<CommandMenuForMobile />
|
||||
</StyledMainContainerLayoutForMobile>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledMainContainerLayoutForDesktop>
|
||||
<StyledPageBodyForDesktopContainer>
|
||||
<PageBody>{children}</PageBody>
|
||||
</StyledPageBodyForDesktopContainer>
|
||||
<SidePanelForDesktop />
|
||||
</StyledMainContainerLayoutForDesktop>
|
||||
);
|
||||
};
|
||||
@@ -1,21 +1,16 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { ObjectOptionsDropdown } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdown';
|
||||
import { RecordBoardContainer } from '@/object-record/record-board/components/RecordBoardContainer';
|
||||
import { RecordIndexTableContainer } from '@/object-record/record-index/components/RecordIndexTableContainer';
|
||||
import { RecordIndexViewBarEffect } from '@/object-record/record-index/components/RecordIndexViewBarEffect';
|
||||
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
|
||||
import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
||||
|
||||
import { RecordIndexCalendarContainer } from '@/object-record/record-index/components/RecordIndexCalendarContainer';
|
||||
import { RecordIndexEmptyStateNotShared } from '@/object-record/record-index/components/RecordIndexEmptyStateNotShared';
|
||||
import { RecordIndexFiltersToContextStoreEffect } from '@/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect';
|
||||
import { useHasCurrentViewNonReadableFields } from '@/object-record/record-index/hooks/useHasCurrentViewNonReadableFields';
|
||||
import { ViewBar } from '@/views/components/ViewBar';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
@@ -24,7 +19,6 @@ const StyledContainer = styled.div`
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@@ -38,67 +32,43 @@ const StyledContainerWithPadding = styled.div`
|
||||
export const RecordIndexContainer = () => {
|
||||
const recordIndexViewType = useAtomStateValue(recordIndexViewTypeState);
|
||||
|
||||
const {
|
||||
objectNamePlural,
|
||||
recordIndexId,
|
||||
objectMetadataItem,
|
||||
objectNameSingular,
|
||||
} = useRecordIndexContextOrThrow();
|
||||
const { recordIndexId, objectMetadataItem, objectNameSingular } =
|
||||
useRecordIndexContextOrThrow();
|
||||
|
||||
const { hasCurrentViewNonReadableFields, nonReadableViewFieldInfo } =
|
||||
useHasCurrentViewNonReadableFields(objectMetadataItem);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContainer>
|
||||
<InformationBannerWrapper />
|
||||
<SpreadsheetImportProvider>
|
||||
<ViewBar
|
||||
isReadOnly={hasCurrentViewNonReadableFields}
|
||||
viewBarId={recordIndexId}
|
||||
optionsDropdownButton={
|
||||
<ObjectOptionsDropdown
|
||||
recordIndexId={recordIndexId}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
viewType={recordIndexViewType ?? ViewType.TABLE}
|
||||
<StyledContainer>
|
||||
{hasCurrentViewNonReadableFields ? (
|
||||
<RecordIndexEmptyStateNotShared
|
||||
nonReadableViewFieldInfo={nonReadableViewFieldInfo}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<RecordIndexFiltersToContextStoreEffect />
|
||||
{recordIndexViewType === ViewType.TABLE && (
|
||||
<RecordIndexTableContainer recordTableId={recordIndexId} />
|
||||
)}
|
||||
{recordIndexViewType === ViewType.KANBAN && (
|
||||
<StyledContainerWithPadding>
|
||||
<RecordBoardContainer
|
||||
recordBoardId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<RecordIndexViewBarEffect
|
||||
objectNamePlural={objectNamePlural}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
</SpreadsheetImportProvider>
|
||||
{hasCurrentViewNonReadableFields ? (
|
||||
<RecordIndexEmptyStateNotShared
|
||||
nonReadableViewFieldInfo={nonReadableViewFieldInfo}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<RecordIndexFiltersToContextStoreEffect />
|
||||
{recordIndexViewType === ViewType.TABLE && (
|
||||
<RecordIndexTableContainer recordTableId={recordIndexId} />
|
||||
)}
|
||||
{recordIndexViewType === ViewType.KANBAN && (
|
||||
<StyledContainerWithPadding>
|
||||
<RecordBoardContainer
|
||||
recordBoardId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
</StyledContainerWithPadding>
|
||||
)}
|
||||
{recordIndexViewType === ViewType.CALENDAR && (
|
||||
<StyledContainerWithPadding>
|
||||
<RecordIndexCalendarContainer
|
||||
recordCalendarInstanceId={recordIndexId}
|
||||
viewBarInstanceId={recordIndexId}
|
||||
/>
|
||||
</StyledContainerWithPadding>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</StyledContainer>
|
||||
</>
|
||||
</StyledContainerWithPadding>
|
||||
)}
|
||||
{recordIndexViewType === ViewType.CALENDAR && (
|
||||
<StyledContainerWithPadding>
|
||||
<RecordIndexCalendarContainer
|
||||
recordCalendarInstanceId={recordIndexId}
|
||||
viewBarInstanceId={recordIndexId}
|
||||
/>
|
||||
</StyledContainerWithPadding>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { RecordIndexContextProvider } from '@/object-record/record-index/context
|
||||
import { getCommandMenuIdFromRecordIndexId } from '@/command-menu-item/utils/getCommandMenuIdFromRecordIndexId';
|
||||
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { MainContainerLayoutWithSidePanel } from '@/object-record/components/MainContainerLayoutWithSidePanel';
|
||||
import { RecordIndexViewBar } from '@/object-record/record-index/components/RecordIndexViewBar';
|
||||
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { lastShowPageRecordIdState } from '@/object-record/record-field/ui/states/lastShowPageRecordId';
|
||||
@@ -16,6 +16,7 @@ import { useHandleIndexIdentifierClick } from '@/object-record/record-index/hook
|
||||
import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates';
|
||||
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
|
||||
import { RECORD_INDEX_DRAG_SELECT_BOUNDARY_CLASS } from '@/ui/utilities/drag-select/constants/RecordIndecDragSelectBoundaryClass';
|
||||
import { PageCardLayout } from '@/ui/layout/page/components/PageCardLayout';
|
||||
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { styled } from '@linaria/react';
|
||||
@@ -91,8 +92,12 @@ export const RecordIndexContainerGater = () => {
|
||||
}}
|
||||
>
|
||||
<PageTitle title={objectMetadataItem.labelPlural} />
|
||||
<RecordIndexPageHeader />
|
||||
<MainContainerLayoutWithSidePanel>
|
||||
<PageCardLayout
|
||||
header={<RecordIndexPageHeader />}
|
||||
secondaryBar={
|
||||
hasObjectReadPermissions && <RecordIndexViewBar />
|
||||
}
|
||||
>
|
||||
<StyledIndexContainer
|
||||
className={RECORD_INDEX_DRAG_SELECT_BOUNDARY_CLASS}
|
||||
>
|
||||
@@ -105,7 +110,7 @@ export const RecordIndexContainerGater = () => {
|
||||
<RecordIndexEmptyStateNotShared />
|
||||
)}
|
||||
</StyledIndexContainer>
|
||||
</MainContainerLayoutWithSidePanel>
|
||||
</PageCardLayout>
|
||||
</CommandMenuComponentInstanceContext.Provider>
|
||||
</RecordComponentInstanceContextsWrapper>
|
||||
<RecordIndexLoadBaseOnContextStoreEffect />
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { RecordIndexCommandMenu } from '@/command-menu-item/components/RecordIndexCommandMenu';
|
||||
import { SidePanelToggleButton } from '@/side-panel/components/SidePanelToggleButton';
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
@@ -7,7 +6,8 @@ import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/st
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { RecordIndexPageHeaderIcon } from '@/object-record/record-index/components/RecordIndexPageHeaderIcon';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
|
||||
import { SidePanelToggleButton } from '@/side-panel/components/SidePanelToggleButton';
|
||||
import { PageCardHeader } from '@/ui/layout/page/components/PageCardHeader';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { styled } from '@linaria/react';
|
||||
@@ -68,18 +68,19 @@ export const RecordIndexPageHeader = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<PageHeader
|
||||
title={pageHeaderTitle}
|
||||
Icon={() => (
|
||||
<PageCardHeader
|
||||
icon={
|
||||
<RecordIndexPageHeaderIcon objectMetadataItem={objectMetadataItem} />
|
||||
)}
|
||||
>
|
||||
{isDefined(contextStoreCurrentViewId) && (
|
||||
<>
|
||||
<RecordIndexCommandMenu />
|
||||
{!isLayoutCustomizationModeEnabled && <SidePanelToggleButton />}
|
||||
</>
|
||||
)}
|
||||
</PageHeader>
|
||||
}
|
||||
title={pageHeaderTitle}
|
||||
actionButton={
|
||||
isDefined(contextStoreCurrentViewId) ? (
|
||||
<>
|
||||
<RecordIndexCommandMenu />
|
||||
{!isLayoutCustomizationModeEnabled && <SidePanelToggleButton />}
|
||||
</>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
|
||||
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
|
||||
import { styled } from '@linaria/react';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
import { PageContentSkeletonLoader } from '~/loading/components/PageContentSkeletonLoader';
|
||||
|
||||
const StyledSecondaryBar = styled.div`
|
||||
align-items: center;
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.light};
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: space-between;
|
||||
min-height: ${themeCssVariables.spacing[10]};
|
||||
padding: 0 ${themeCssVariables.spacing[3]};
|
||||
`;
|
||||
|
||||
export const RecordIndexSkeletonLoader = () => (
|
||||
<PageContainer>
|
||||
<PageContentSkeletonLoader />
|
||||
</PageContainer>
|
||||
<PageContentSkeletonLoader
|
||||
secondaryBar={
|
||||
<StyledSecondaryBar>
|
||||
<Skeleton
|
||||
width={120}
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
/>
|
||||
<Skeleton
|
||||
width={180}
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
/>
|
||||
</StyledSecondaryBar>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ObjectOptionsDropdown } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdown';
|
||||
import { RecordIndexViewBarEffect } from '@/object-record/record-index/components/RecordIndexViewBarEffect';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useHasCurrentViewNonReadableFields } from '@/object-record/record-index/hooks/useHasCurrentViewNonReadableFields';
|
||||
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
|
||||
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { ViewBar } from '@/views/components/ViewBar';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
|
||||
export const RecordIndexViewBar = () => {
|
||||
const recordIndexViewType = useAtomStateValue(recordIndexViewTypeState);
|
||||
|
||||
const { objectNamePlural, recordIndexId, objectMetadataItem } =
|
||||
useRecordIndexContextOrThrow();
|
||||
|
||||
const { hasCurrentViewNonReadableFields } =
|
||||
useHasCurrentViewNonReadableFields(objectMetadataItem);
|
||||
|
||||
return (
|
||||
<SpreadsheetImportProvider>
|
||||
<ViewBar
|
||||
isReadOnly={hasCurrentViewNonReadableFields}
|
||||
viewBarId={recordIndexId}
|
||||
optionsDropdownButton={
|
||||
<ObjectOptionsDropdown
|
||||
recordIndexId={recordIndexId}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
viewType={recordIndexViewType ?? ViewType.TABLE}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<RecordIndexViewBarEffect
|
||||
objectNamePlural={objectNamePlural}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
</SpreadsheetImportProvider>
|
||||
);
|
||||
};
|
||||
@@ -1,51 +1,24 @@
|
||||
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
|
||||
import { SettingsPageHeader } from '@/settings/components/layout/SettingsPageHeader';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { styled } from '@linaria/react';
|
||||
import { PageCardHeader } from '@/ui/layout/page/components/PageCardHeader';
|
||||
import { PageCardLayout } from '@/ui/layout/page/components/PageCardLayout';
|
||||
import { useContext } from 'react';
|
||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||
import {
|
||||
ThemeContext,
|
||||
themeCssVariables,
|
||||
} from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
const StyledRoot = styled.div<{ isMobile: boolean }>`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
padding: ${({ isMobile }) =>
|
||||
isMobile ? themeCssVariables.spacing[1] : themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledCard = styled.div`
|
||||
background: ${themeCssVariables.background.primary};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
`;
|
||||
import { ThemeContext } from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
export const SettingsSkeletonLoader = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledRoot isMobile={isMobile}>
|
||||
<StyledCard>
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<SettingsPageHeader
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<PageCardLayout
|
||||
header={
|
||||
<PageCardHeader
|
||||
links={[
|
||||
{
|
||||
children: (
|
||||
@@ -63,11 +36,13 @@ export const SettingsSkeletonLoader = () => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingsPageContainer>
|
||||
<SettingsSectionSkeletonLoader />
|
||||
</SettingsPageContainer>
|
||||
</SkeletonTheme>
|
||||
</StyledCard>
|
||||
</StyledRoot>
|
||||
}
|
||||
showInformationBanner={false}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<SettingsSectionSkeletonLoader />
|
||||
</SettingsPageContainer>
|
||||
</PageCardLayout>
|
||||
</SkeletonTheme>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { CommandMenuForMobile } from '@/command-menu/components/CommandMenuForMobile';
|
||||
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
|
||||
import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper';
|
||||
import { SettingsPageHeader } from '@/settings/components/layout/SettingsPageHeader';
|
||||
import { SettingsSecondaryBar } from '@/settings/components/layout/SettingsSecondaryBar';
|
||||
import { SidePanelForDesktop } from '@/side-panel/components/SidePanelForDesktop';
|
||||
import { PageCardHeader } from '@/ui/layout/page/components/PageCardHeader';
|
||||
import { PageCardLayout } from '@/ui/layout/page/components/PageCardLayout';
|
||||
import { type BreadcrumbProps } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type JSX, type ReactNode } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
type SettingsPageLayoutProps = {
|
||||
links: BreadcrumbProps['links'];
|
||||
@@ -20,44 +14,6 @@ type SettingsPageLayoutProps = {
|
||||
tag?: JSX.Element;
|
||||
};
|
||||
|
||||
const StyledRoot = styled.div<{ isMobile: boolean }>`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
padding: ${({ isMobile }) =>
|
||||
isMobile ? themeCssVariables.spacing[1] : themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledMainCardWrapper = styled.div`
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
width: 0;
|
||||
`;
|
||||
|
||||
const StyledCard = styled.div`
|
||||
background: ${themeCssVariables.background.primary};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledBodyContent = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const SettingsPageLayout = ({
|
||||
links,
|
||||
title,
|
||||
@@ -65,31 +21,22 @@ export const SettingsPageLayout = ({
|
||||
secondaryBar,
|
||||
children,
|
||||
tag,
|
||||
}: SettingsPageLayoutProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
useCommandMenuHotKeys();
|
||||
|
||||
return (
|
||||
<StyledRoot isMobile={isMobile}>
|
||||
<StyledMainCardWrapper>
|
||||
<StyledCard>
|
||||
<SettingsPageHeader
|
||||
links={links}
|
||||
title={title}
|
||||
tag={tag}
|
||||
actionButton={actionButton}
|
||||
/>
|
||||
{isDefined(secondaryBar) && (
|
||||
<SettingsSecondaryBar>{secondaryBar}</SettingsSecondaryBar>
|
||||
)}
|
||||
<StyledBodyContent>
|
||||
<InformationBannerWrapper />
|
||||
{children}
|
||||
</StyledBodyContent>
|
||||
</StyledCard>
|
||||
</StyledMainCardWrapper>
|
||||
{isMobile ? <CommandMenuForMobile /> : <SidePanelForDesktop />}
|
||||
</StyledRoot>
|
||||
);
|
||||
};
|
||||
}: SettingsPageLayoutProps) => (
|
||||
<PageCardLayout
|
||||
header={
|
||||
<PageCardHeader
|
||||
links={links}
|
||||
title={title}
|
||||
tag={tag}
|
||||
actionButton={actionButton}
|
||||
/>
|
||||
}
|
||||
secondaryBar={
|
||||
isDefined(secondaryBar) ? (
|
||||
<SettingsSecondaryBar>{secondaryBar}</SettingsSecondaryBar>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</PageCardLayout>
|
||||
);
|
||||
|
||||
@@ -37,8 +37,6 @@ const StyledSidePanelWrapper = styled.div<{
|
||||
|
||||
const StyledSidePanel = styled.aside`
|
||||
background: ${themeCssVariables.background.primary};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -58,8 +56,6 @@ const StyledModalContainer = styled.div`
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
const GAP_WIDTH = 8;
|
||||
|
||||
export const SidePanelForDesktop = () => {
|
||||
const isSidePanelOpened = useAtomStateValue(isSidePanelOpenedState);
|
||||
const isSidePanelClosing = useAtomStateValue(isSidePanelClosingState);
|
||||
@@ -130,7 +126,7 @@ export const SidePanelForDesktop = () => {
|
||||
currentWidth={sidePanelWidth}
|
||||
onWidthChange={handleWidthChange}
|
||||
onCollapse={handleCollapse}
|
||||
gapWidth={isSidePanelOpened ? GAP_WIDTH : 0}
|
||||
gapWidth={0}
|
||||
cssVariableName={SIDE_PANEL_WIDTH_VAR}
|
||||
onResizeStart={handleResizeStart}
|
||||
/>
|
||||
|
||||
@@ -15,10 +15,7 @@ import {
|
||||
TooltipPosition,
|
||||
} from 'twenty-ui-deprecated/display';
|
||||
import { AnimatedButton } from 'twenty-ui-deprecated/input';
|
||||
import {
|
||||
getOsControlSymbol,
|
||||
useIsMobile,
|
||||
} from 'twenty-ui-deprecated/utilities';
|
||||
import { useIsMobile } from 'twenty-ui-deprecated/utilities';
|
||||
import {
|
||||
ThemeContext,
|
||||
themeCssVariables,
|
||||
@@ -157,7 +154,6 @@ export const SidePanelToggleButton = () => {
|
||||
size={isMobile ? 'medium' : 'small'}
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
hotkeys={[getOsControlSymbol(), 'K']}
|
||||
ariaLabel={ariaLabel}
|
||||
onClick={toggleSidePanelMenu}
|
||||
animate={{
|
||||
|
||||
@@ -11,53 +11,55 @@ import { Button, LightIconButton } from 'twenty-ui-deprecated/input';
|
||||
|
||||
import { BackgroundMockTable } from '@/sign-in-background-mock/components/BackgroundMockTable';
|
||||
import { BackgroundMockViewBar } from '@/sign-in-background-mock/components/BackgroundMockViewBar';
|
||||
import { PageBody } from '@/ui/layout/page/components/PageBody';
|
||||
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
|
||||
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
|
||||
import { PageCardHeader } from '@/ui/layout/page/components/PageCardHeader';
|
||||
import { PageCardLayout } from '@/ui/layout/page/components/PageCardLayout';
|
||||
|
||||
const StyledTableContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const BackgroundMockPage = () => {
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title={t`Companies`}
|
||||
Icon={() => (
|
||||
<TintedIconTile Icon={IconBuildingSkyscraper} color="blue" />
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`New Company`}
|
||||
variant="primary"
|
||||
accent="default"
|
||||
size="small"
|
||||
<PageCardLayout
|
||||
header={
|
||||
<PageCardHeader
|
||||
icon={<TintedIconTile Icon={IconBuildingSkyscraper} color="blue" />}
|
||||
title={t`Companies`}
|
||||
actionButton={
|
||||
<>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`New Company`}
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
size="small"
|
||||
/>
|
||||
<LightIconButton
|
||||
Icon={IconDotsVertical}
|
||||
accent="tertiary"
|
||||
size="small"
|
||||
/>
|
||||
<Button
|
||||
Icon={IconLayoutSidebarRight}
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
size="small"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<LightIconButton
|
||||
Icon={IconDotsVertical}
|
||||
accent="tertiary"
|
||||
size="small"
|
||||
/>
|
||||
<Button
|
||||
Icon={IconLayoutSidebarRight}
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
size="small"
|
||||
/>
|
||||
</PageHeader>
|
||||
<PageBody>
|
||||
<StyledTableContainer>
|
||||
<BackgroundMockViewBar />
|
||||
<BackgroundMockTable />
|
||||
</StyledTableContainer>
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
}
|
||||
secondaryBar={<BackgroundMockViewBar />}
|
||||
showInformationBanner={false}
|
||||
>
|
||||
<StyledTableContainer>
|
||||
<BackgroundMockTable />
|
||||
</StyledTableContainer>
|
||||
</PageCardLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { CommandMenuForMobile } from '@/command-menu/components/CommandMenuForMobile';
|
||||
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
|
||||
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';
|
||||
|
||||
const StyledRow = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
`;
|
||||
|
||||
const StyledContent = styled.div`
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const MainAppLayoutWithSidePanel = () => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
useCommandMenuHotKeys();
|
||||
|
||||
return (
|
||||
<StyledRow>
|
||||
<StyledContent>
|
||||
<Outlet />
|
||||
</StyledContent>
|
||||
{isMobile ? <CommandMenuForMobile /> : <SidePanelForDesktop />}
|
||||
</StyledRow>
|
||||
);
|
||||
};
|
||||
@@ -11,23 +11,22 @@ import { type ReactNode } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
type SettingsPageHeaderProps = {
|
||||
links: BreadcrumbProps['links'];
|
||||
type PageCardHeaderProps = {
|
||||
links?: BreadcrumbProps['links'];
|
||||
breadcrumb?: ReactNode;
|
||||
icon?: ReactNode;
|
||||
title?: ReactNode;
|
||||
tag?: ReactNode;
|
||||
actionButton?: ReactNode;
|
||||
};
|
||||
|
||||
// minmax(0, 1fr) side tracks (not 1fr) let a long breadcrumb truncate instead of
|
||||
// pushing the centered title off its shared axis with the tabs and body.
|
||||
const StyledHeader = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${themeCssVariables.background.secondary};
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.medium};
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
||||
min-height: ${SIDE_PANEL_TOP_BAR_HEIGHT}px;
|
||||
padding: 0 ${themeCssVariables.spacing[3]};
|
||||
width: 100%;
|
||||
@@ -36,7 +35,7 @@ const StyledHeader = styled.div`
|
||||
const StyledLeft = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
`;
|
||||
@@ -49,23 +48,25 @@ const StyledTitle = styled.div`
|
||||
font-weight: ${themeCssVariables.font.weight.semiBold};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const StyledRight = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
`;
|
||||
|
||||
export const SettingsPageHeader = ({
|
||||
export const PageCardHeader = ({
|
||||
links,
|
||||
breadcrumb,
|
||||
icon,
|
||||
title,
|
||||
tag,
|
||||
actionButton,
|
||||
}: SettingsPageHeaderProps) => {
|
||||
}: PageCardHeaderProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
const isNavigationDrawerExpanded = useNavigationDrawerExpanded();
|
||||
|
||||
@@ -75,12 +76,18 @@ export const SettingsPageHeader = ({
|
||||
{!isNavigationDrawerExpanded && (
|
||||
<NavigationDrawerCollapseButton direction="right" />
|
||||
)}
|
||||
<Breadcrumb links={links} />
|
||||
{isDefined(breadcrumb)
|
||||
? breadcrumb
|
||||
: isDefined(links) && <Breadcrumb links={links} />}
|
||||
{!isMobile &&
|
||||
(isDefined(icon) || isDefined(title) || isDefined(tag)) && (
|
||||
<StyledTitle>
|
||||
{icon}
|
||||
{isDefined(title) && title}
|
||||
{tag}
|
||||
</StyledTitle>
|
||||
)}
|
||||
</StyledLeft>
|
||||
<StyledTitle>
|
||||
{!isMobile && isDefined(title) && title}
|
||||
{!isMobile && tag}
|
||||
</StyledTitle>
|
||||
<StyledRight>{actionButton}</StyledRight>
|
||||
</StyledHeader>
|
||||
);
|
||||
@@ -0,0 +1,69 @@
|
||||
import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type ReactNode } from 'react';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
type PageCardLayoutProps = {
|
||||
header: ReactNode;
|
||||
secondaryBar?: ReactNode;
|
||||
children: ReactNode;
|
||||
showInformationBanner?: boolean;
|
||||
};
|
||||
|
||||
const StyledRoot = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
`;
|
||||
|
||||
const StyledMainCardWrapper = styled.div`
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
width: 0;
|
||||
`;
|
||||
|
||||
const StyledCard = styled.div`
|
||||
background: ${themeCssVariables.background.primary};
|
||||
border-left: 1px solid ${themeCssVariables.border.color.medium};
|
||||
border-right: 1px solid ${themeCssVariables.border.color.medium};
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledBodyContent = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const PageCardLayout = ({
|
||||
header,
|
||||
secondaryBar,
|
||||
children,
|
||||
showInformationBanner = true,
|
||||
}: PageCardLayoutProps) => {
|
||||
return (
|
||||
<StyledRoot>
|
||||
<StyledMainCardWrapper>
|
||||
<StyledCard>
|
||||
{header}
|
||||
{secondaryBar}
|
||||
<StyledBodyContent>
|
||||
{showInformationBanner && <InformationBannerWrapper />}
|
||||
{children}
|
||||
</StyledBodyContent>
|
||||
</StyledCard>
|
||||
</StyledMainCardWrapper>
|
||||
</StyledRoot>
|
||||
);
|
||||
};
|
||||
@@ -5,7 +5,6 @@ import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
const StyledPanel = styled.div`
|
||||
background: ${themeCssVariables.background.primary};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
@@ -21,7 +21,6 @@ const StyledInnerContainer = styled.div`
|
||||
|
||||
const StyledScrollWrapperContainer = styled.div`
|
||||
background-color: ${themeCssVariables.background.secondary};
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
|
||||
@@ -4,12 +4,19 @@ import { useResizablePanel } from '@/ui/layout/resizable-panel/hooks/useResizabl
|
||||
import { type ResizablePanelConstraints } from '@/ui/layout/resizable-panel/types/ResizablePanelConstraints';
|
||||
import { type ResizablePanelSide } from '@/ui/layout/resizable-panel/types/ResizablePanelSide';
|
||||
|
||||
// Horizontal padding offset by an equal negative margin keeps the resize
|
||||
// handle grabbable even when the visual gap is 0, without shifting neighbors.
|
||||
const StyledGap = styled.div<{ gapWidth: number }>`
|
||||
box-sizing: content-box;
|
||||
cursor: col-resize;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
margin: 0 -4px;
|
||||
padding: 0 4px;
|
||||
position: relative;
|
||||
transition: width 0.15s ease;
|
||||
width: ${({ gapWidth }) => gapWidth}px;
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
type ResizablePanelGapProps = {
|
||||
|
||||
@@ -7,13 +7,12 @@ import { TimelineActivityContext } from '@/activities/timeline-activities/contex
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
|
||||
import { MainContainerLayoutWithSidePanel } from '@/object-record/components/MainContainerLayoutWithSidePanel';
|
||||
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
|
||||
import { PageLayoutRecordPageRenderer } from '@/object-record/record-show/components/PageLayoutRecordPageRenderer';
|
||||
import { RecordShowPageSSESubscribeEffect } from '@/object-record/record-show/components/RecordShowPageSSESubscribeEffect';
|
||||
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
||||
import { computeRecordShowComponentInstanceId } from '@/object-record/record-show/utils/computeRecordShowComponentInstanceId';
|
||||
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
|
||||
import { PageCardLayout } from '@/ui/layout/page/components/PageCardLayout';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { RecordShowPageHeader } from '~/pages/object-record/RecordShowPageHeader';
|
||||
import { RecordShowPageTitle } from '~/pages/object-record/RecordShowPageTitle';
|
||||
@@ -46,38 +45,39 @@ export const RecordShowPage = () => {
|
||||
<CommandMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: recordShowComponentInstanceId }}
|
||||
>
|
||||
<PageContainer>
|
||||
<RecordShowPageTitle
|
||||
objectNameSingular={objectNameSingular}
|
||||
objectRecordId={objectRecordId}
|
||||
/>
|
||||
<RecordShowPageHeader
|
||||
objectNameSingular={objectNameSingular}
|
||||
objectRecordId={objectRecordId}
|
||||
>
|
||||
<RecordShowCommandMenu />
|
||||
{!isLayoutCustomizationModeEnabled && <SidePanelToggleButton />}
|
||||
</RecordShowPageHeader>
|
||||
<MainContainerLayoutWithSidePanel>
|
||||
<TimelineActivityContext.Provider
|
||||
value={{
|
||||
recordId: objectRecordId,
|
||||
}}
|
||||
<RecordShowPageTitle
|
||||
objectNameSingular={objectNameSingular}
|
||||
objectRecordId={objectRecordId}
|
||||
/>
|
||||
<PageCardLayout
|
||||
header={
|
||||
<RecordShowPageHeader
|
||||
objectNameSingular={objectNameSingular}
|
||||
objectRecordId={objectRecordId}
|
||||
>
|
||||
<PageLayoutRecordPageRenderer
|
||||
targetRecordIdentifier={{
|
||||
id: objectRecordId,
|
||||
targetObjectNameSingular: objectNameSingular,
|
||||
}}
|
||||
isInSidePanel={false}
|
||||
/>
|
||||
<RecordShowPageSSESubscribeEffect
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordId={objectRecordId}
|
||||
/>
|
||||
</TimelineActivityContext.Provider>
|
||||
</MainContainerLayoutWithSidePanel>
|
||||
</PageContainer>
|
||||
<RecordShowCommandMenu />
|
||||
{!isLayoutCustomizationModeEnabled && <SidePanelToggleButton />}
|
||||
</RecordShowPageHeader>
|
||||
}
|
||||
>
|
||||
<TimelineActivityContext.Provider
|
||||
value={{
|
||||
recordId: objectRecordId,
|
||||
}}
|
||||
>
|
||||
<PageLayoutRecordPageRenderer
|
||||
targetRecordIdentifier={{
|
||||
id: objectRecordId,
|
||||
targetObjectNameSingular: objectNameSingular,
|
||||
}}
|
||||
isInSidePanel={false}
|
||||
/>
|
||||
<RecordShowPageSSESubscribeEffect
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordId={objectRecordId}
|
||||
/>
|
||||
</TimelineActivityContext.Provider>
|
||||
</PageCardLayout>
|
||||
</CommandMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</RecordComponentInstanceContextsWrapper>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getObjectMetadataIdentifierFields } from '@/object-metadata/utils/getObjectMetadataIdentifierFields';
|
||||
import { ObjectRecordShowPageBreadcrumb } from '@/object-record/record-show/components/ObjectRecordShowPageBreadcrumb';
|
||||
import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination';
|
||||
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
|
||||
import { PageCardHeader } from '@/ui/layout/page/components/PageCardHeader';
|
||||
|
||||
export const RecordShowPageHeader = ({
|
||||
objectNameSingular,
|
||||
@@ -21,8 +21,8 @@ export const RecordShowPageHeader = ({
|
||||
getObjectMetadataIdentifierFields({ objectMetadataItem });
|
||||
|
||||
return (
|
||||
<PageHeader
|
||||
title={
|
||||
<PageCardHeader
|
||||
breadcrumb={
|
||||
<ObjectRecordShowPageBreadcrumb
|
||||
objectNameSingular={objectNameSingular}
|
||||
objectRecordId={objectRecordId}
|
||||
@@ -30,8 +30,7 @@ export const RecordShowPageHeader = ({
|
||||
labelIdentifierFieldMetadataItem={labelIdentifierFieldMetadataItem}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</PageHeader>
|
||||
actionButton={children}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,10 +2,12 @@ import { StandalonePageCommandMenu } from '@/command-menu-item/components/Standa
|
||||
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
|
||||
import { navigationMenuItemsSelector } from '@/navigation-menu-item/common/states/navigationMenuItemsSelector';
|
||||
import { SidePanelToggleButton } from '@/side-panel/components/SidePanelToggleButton';
|
||||
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
|
||||
import { PageCardHeader } from '@/ui/layout/page/components/PageCardHeader';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui-deprecated/display';
|
||||
import { ThemeContext } from 'twenty-ui-deprecated/theme-constants';
|
||||
|
||||
type StandalonePageHeaderProps = {
|
||||
pageLayoutId: string;
|
||||
@@ -15,6 +17,7 @@ export const StandalonePageHeader = ({
|
||||
pageLayoutId,
|
||||
}: StandalonePageHeaderProps) => {
|
||||
const { getIcon } = useIcons();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const navigationMenuItems = useAtomStateValue(navigationMenuItemsSelector);
|
||||
const isLayoutCustomizationModeEnabled = useAtomStateValue(
|
||||
isLayoutCustomizationModeEnabledState,
|
||||
@@ -30,9 +33,15 @@ export const StandalonePageHeader = ({
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<PageHeader title={title} Icon={Icon}>
|
||||
<StandalonePageCommandMenu />
|
||||
{!isLayoutCustomizationModeEnabled && <SidePanelToggleButton />}
|
||||
</PageHeader>
|
||||
<PageCardHeader
|
||||
icon={isDefined(Icon) ? <Icon size={theme.icon.size.md} /> : undefined}
|
||||
title={title}
|
||||
actionButton={
|
||||
<>
|
||||
<StandalonePageCommandMenu />
|
||||
{!isLayoutCustomizationModeEnabled && <SidePanelToggleButton />}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { MainContainerLayoutWithSidePanel } from '@/object-record/components/MainContainerLayoutWithSidePanel';
|
||||
import { PageLayoutRenderer } from '@/page-layout/components/PageLayoutRenderer';
|
||||
import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
|
||||
import { PageCardLayout } from '@/ui/layout/page/components/PageCardLayout';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { PageLayoutType } from '~/generated-metadata/graphql';
|
||||
import { StandalonePageHeader } from '~/pages/page-layout/StandalonePageHeader';
|
||||
|
||||
const StyledPageLayoutContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
export const StandalonePageLayoutPage = () => {
|
||||
const { pageLayoutId } = useParams<{ pageLayoutId: string }>();
|
||||
|
||||
@@ -19,14 +27,15 @@ export const StandalonePageLayoutPage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{ instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID }}
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{ instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID }}
|
||||
>
|
||||
<CommandMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: pageLayoutId }}
|
||||
>
|
||||
<CommandMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: pageLayoutId }}
|
||||
<PageCardLayout
|
||||
header={<StandalonePageHeader pageLayoutId={pageLayoutId} />}
|
||||
>
|
||||
<StandalonePageHeader pageLayoutId={pageLayoutId} />
|
||||
<LayoutRenderingProvider
|
||||
value={{
|
||||
targetRecordIdentifier: undefined,
|
||||
@@ -34,12 +43,12 @@ export const StandalonePageLayoutPage = () => {
|
||||
isInSidePanel: false,
|
||||
}}
|
||||
>
|
||||
<MainContainerLayoutWithSidePanel>
|
||||
<StyledPageLayoutContainer>
|
||||
<PageLayoutRenderer pageLayoutId={pageLayoutId} />
|
||||
</MainContainerLayoutWithSidePanel>
|
||||
</StyledPageLayoutContainer>
|
||||
</LayoutRenderingProvider>
|
||||
</CommandMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</PageContainer>
|
||||
</PageCardLayout>
|
||||
</CommandMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user