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:
-83
@@ -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>
|
||||
);
|
||||
};
|
||||
+32
-62
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+9
-4
@@ -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 />
|
||||
|
||||
+15
-14
@@ -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
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
+29
-4
@@ -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>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
+39
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user