New Settings Layout (#6867)
#### \ Description - **Added "Exit Settings" Back Button**: Introduced a new back button labeled "Exit Settings" for easy navigation back to the app content. - **Implemented Settings Navbar Breadcrumb**: A breadcrumb navigation bar for each settings page has been added to improve navigation within the settings. This ensures users can easily trace their location within the settings. - **Persistent CTA Zone**: The Call-to-Action (CTA) zone at the top of the page now remains visible even when the user scrolls down, preventing unresponsive behavior and improving accessibility. - **Page Title**: The page title has been added to each settings page and separated from the breadcrumb, following the app's layout standards. - we could not reproduce the Billing and CMR Migrations settings on the app, but we updated the files according, please let's us know if is there any way to log in with access to these pages, or if is everything ok. - Some breadcrumbs are not following the Figma, are following the current app sections isntead, because we would need to change the sidebar structure, and we should not do it on this PR ### Demo <https://www.loom.com/share/21b20a2cd2f3471e94d61563c9901b19?sid=9dc49456-6cae-48e1-9149-8d706f00ab65> ### Refs: #6144 Fixes #6144 --------- Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
committed by
GitHub
parent
fe4ca2133d
commit
c42ea57b97
@ -7,7 +7,6 @@ import { z } from 'zod';
|
||||
import { useCreateOneObjectMetadataItem } from '@/object-metadata/hooks/useCreateOneObjectMetadataItem';
|
||||
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import {
|
||||
SettingsDataModelObjectAboutForm,
|
||||
@ -20,7 +19,6 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
|
||||
const newObjectFormSchema = settingsDataModelObjectAboutFormSchema;
|
||||
|
||||
@ -72,17 +70,18 @@ export const SettingsNewObject = () => {
|
||||
<FormProvider {...formConfig}>
|
||||
<SubMenuTopBarContainer
|
||||
Icon={IconHierarchy2}
|
||||
title={
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{
|
||||
children: 'Objects',
|
||||
href: settingsObjectsPagePath,
|
||||
},
|
||||
{ children: 'New' },
|
||||
]}
|
||||
/>
|
||||
}
|
||||
title="New Object"
|
||||
links={[
|
||||
{
|
||||
children: 'Workspace',
|
||||
href: getSettingsPagePath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: 'Objects',
|
||||
href: settingsObjectsPagePath,
|
||||
},
|
||||
{ children: 'New' },
|
||||
]}
|
||||
actionButton={
|
||||
<SaveAndCancelButtons
|
||||
isSaveDisabled={!canSave}
|
||||
@ -93,7 +92,6 @@ export const SettingsNewObject = () => {
|
||||
}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<SettingsHeaderContainer></SettingsHeaderContainer>
|
||||
<Section>
|
||||
<H2Title
|
||||
title="About"
|
||||
|
||||
@ -8,7 +8,6 @@ import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
@ -49,14 +48,15 @@ export const SettingsObjectDetailPageContent = ({
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
Icon={IconHierarchy2}
|
||||
title={
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{ children: 'Objects', href: '/settings/objects' },
|
||||
{ children: objectMetadataItem.labelPlural },
|
||||
]}
|
||||
/>
|
||||
}
|
||||
title={objectMetadataItem.labelPlural}
|
||||
links={[
|
||||
{
|
||||
children: 'Workspace',
|
||||
href: getSettingsPagePath(SettingsPath.Workspace),
|
||||
},
|
||||
{ children: 'Objects', href: '/settings/objects' },
|
||||
{ children: objectMetadataItem.labelPlural },
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<Section>
|
||||
|
||||
@ -13,7 +13,6 @@ import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdat
|
||||
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
|
||||
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import {
|
||||
SettingsDataModelObjectAboutForm,
|
||||
@ -30,7 +29,6 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
|
||||
const objectEditFormSchema = z
|
||||
.object({})
|
||||
@ -110,35 +108,36 @@ export const SettingsObjectEdit = () => {
|
||||
<FormProvider {...formConfig}>
|
||||
<SubMenuTopBarContainer
|
||||
Icon={IconHierarchy2}
|
||||
title={
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{
|
||||
children: 'Objects',
|
||||
href: settingsObjectsPagePath,
|
||||
},
|
||||
{
|
||||
children: activeObjectMetadataItem.labelPlural,
|
||||
href: `${settingsObjectsPagePath}/${objectSlug}`,
|
||||
},
|
||||
{ children: 'Edit' },
|
||||
]}
|
||||
/>
|
||||
title="Edit"
|
||||
links={[
|
||||
{
|
||||
children: 'Workspace',
|
||||
href: getSettingsPagePath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: 'Objects',
|
||||
href: settingsObjectsPagePath,
|
||||
},
|
||||
{
|
||||
children: activeObjectMetadataItem.labelPlural,
|
||||
href: `${settingsObjectsPagePath}/${objectSlug}`,
|
||||
},
|
||||
{ children: 'Edit Object' },
|
||||
]}
|
||||
actionButton={
|
||||
activeObjectMetadataItem.isCustom && (
|
||||
<SaveAndCancelButtons
|
||||
isSaveDisabled={!canSave}
|
||||
isCancelDisabled={isSubmitting}
|
||||
onCancel={() =>
|
||||
navigate(`${settingsObjectsPagePath}/${objectSlug}`)
|
||||
}
|
||||
onSave={formConfig.handleSubmit(handleSave)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<SettingsHeaderContainer>
|
||||
{activeObjectMetadataItem.isCustom && (
|
||||
<SaveAndCancelButtons
|
||||
isSaveDisabled={!canSave}
|
||||
isCancelDisabled={isSubmitting}
|
||||
onCancel={() =>
|
||||
navigate(`${settingsObjectsPagePath}/${objectSlug}`)
|
||||
}
|
||||
onSave={formConfig.handleSubmit(handleSave)}
|
||||
/>
|
||||
)}
|
||||
</SettingsHeaderContainer>
|
||||
<Section>
|
||||
<H2Title
|
||||
title="About"
|
||||
|
||||
@ -26,13 +26,14 @@ import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/field
|
||||
import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
|
||||
import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema';
|
||||
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
@ -173,23 +174,24 @@ export const SettingsObjectFieldEdit = () => {
|
||||
<FormProvider {...formConfig}>
|
||||
<SubMenuTopBarContainer
|
||||
Icon={IconHierarchy2}
|
||||
title={
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{
|
||||
children: 'Objects',
|
||||
href: '/settings/objects',
|
||||
styles: { minWidth: 'max-content' },
|
||||
},
|
||||
{
|
||||
children: activeObjectMetadataItem.labelPlural,
|
||||
href: `/settings/objects/${objectSlug}`,
|
||||
styles: { maxWidth: '60%' },
|
||||
},
|
||||
{ children: activeMetadataField.label },
|
||||
]}
|
||||
/>
|
||||
}
|
||||
title={activeMetadataField?.label}
|
||||
links={[
|
||||
{
|
||||
children: 'Workspace',
|
||||
href: getSettingsPagePath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: 'Objects',
|
||||
href: '/settings/objects',
|
||||
},
|
||||
{
|
||||
children: activeObjectMetadataItem.labelPlural,
|
||||
href: `/settings/objects/${objectSlug}`,
|
||||
},
|
||||
{
|
||||
children: activeMetadataField.label,
|
||||
},
|
||||
]}
|
||||
actionButton={
|
||||
shouldDisplaySaveAndCancel && (
|
||||
<SaveAndCancelButtons
|
||||
|
||||
@ -9,11 +9,12 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
|
||||
|
||||
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
|
||||
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable';
|
||||
|
||||
@ -86,18 +87,18 @@ export const SettingsObjectNewFieldStep1 = () => {
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
Icon={IconHierarchy2}
|
||||
title={
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{ children: 'Objects', href: '/settings/objects' },
|
||||
{
|
||||
children: activeObjectMetadataItem.labelPlural,
|
||||
href: `/settings/objects/${objectSlug}`,
|
||||
},
|
||||
{ children: 'New Field' },
|
||||
]}
|
||||
/>
|
||||
}
|
||||
links={[
|
||||
{
|
||||
children: 'Workspace',
|
||||
href: getSettingsPagePath(SettingsPath.Workspace),
|
||||
},
|
||||
{ children: 'Objects', href: '/settings/objects' },
|
||||
{
|
||||
children: activeObjectMetadataItem.labelPlural,
|
||||
href: `/settings/objects/${objectSlug}`,
|
||||
},
|
||||
{ children: 'New Field' },
|
||||
]}
|
||||
actionButton={
|
||||
!activeObjectMetadataItem.isRemote && (
|
||||
<SaveAndCancelButtons
|
||||
|
||||
@ -19,7 +19,6 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { View } from '@/views/types/View';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
@ -41,6 +40,7 @@ type SettingsDataModelNewFieldFormValues = z.infer<
|
||||
|
||||
const StyledH1Title = styled(H1Title)`
|
||||
margin-bottom: 0;
|
||||
padding-top: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
export const SettingsObjectNewFieldStep2 = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -177,30 +177,24 @@ export const SettingsObjectNewFieldStep2 = () => {
|
||||
>
|
||||
<SubMenuTopBarContainer
|
||||
Icon={IconHierarchy2}
|
||||
title={
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{
|
||||
children: 'Objects',
|
||||
href: '/settings/objects',
|
||||
styles: { minWidth: 'max-content' },
|
||||
},
|
||||
{
|
||||
children: activeObjectMetadataItem.labelPlural,
|
||||
href: `/settings/objects/${objectSlug}`,
|
||||
styles: { maxWidth: '50%' },
|
||||
},
|
||||
{
|
||||
children: (
|
||||
<SettingsDataModelNewFieldBreadcrumbDropDown
|
||||
isConfigureStep={isConfigureStep}
|
||||
onBreadcrumbClick={setIsConfigureStep}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
links={[
|
||||
{
|
||||
children: 'Objects',
|
||||
href: '/settings/objects',
|
||||
},
|
||||
{
|
||||
children: activeObjectMetadataItem.labelPlural,
|
||||
href: `/settings/objects/${objectSlug}`,
|
||||
},
|
||||
{
|
||||
children: (
|
||||
<SettingsDataModelNewFieldBreadcrumbDropDown
|
||||
isConfigureStep={isConfigureStep}
|
||||
onBreadcrumbClick={setIsConfigureStep}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
actionButton={
|
||||
!activeObjectMetadataItem.isRemote && (
|
||||
<SaveAndCancelButtons
|
||||
|
||||
@ -1,24 +1,25 @@
|
||||
import { ReactFlowProvider } from 'reactflow';
|
||||
|
||||
import { SettingsDataModelOverview } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverview';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { IconHierarchy2 } from 'twenty-ui';
|
||||
|
||||
export const SettingsObjectOverview = () => {
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
Icon={IconHierarchy2}
|
||||
title={
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{ children: 'Data model', href: '/settings/objects' },
|
||||
{
|
||||
children: 'Overview',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
links={[
|
||||
{
|
||||
children: 'Workspace',
|
||||
href: getSettingsPagePath(SettingsPath.Workspace),
|
||||
},
|
||||
{ children: 'Objects', href: '/settings/objects' },
|
||||
{
|
||||
children: 'Overview',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ReactFlowProvider>
|
||||
<SettingsDataModelOverview />
|
||||
|
||||
@ -145,6 +145,15 @@ export const SettingsObjects = () => {
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
}
|
||||
links={[
|
||||
{
|
||||
children: 'Workspace',
|
||||
href: getSettingsPagePath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: 'Objects',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<>
|
||||
|
||||
@ -30,7 +30,6 @@ export type Story = StoryObj<typeof SettingsObjectNewFieldStep1>;
|
||||
export const Default: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findByText('Settings');
|
||||
await canvas.findByText('Objects');
|
||||
await canvas.findByText('Companies');
|
||||
await canvas.findByText('Check deactivated fields');
|
||||
|
||||
Reference in New Issue
Block a user