From bec4da496df7bd735a60903c8f771d3f4c1bc85a Mon Sep 17 00:00:00 2001
From: Marie <51697796+ijreilly@users.noreply.github.com>
Date: Mon, 11 Nov 2024 14:06:38 +0100
Subject: [PATCH] Data settings new layout - anchor navigation (#8334)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Follow-up of https://github.com/twentyhq/twenty/pull/7979
Navigation between settings and fields tabs is now reflected in URL.
---------
Co-authored-by: gitstart-twenty
Co-authored-by: gitstart-twenty <140154534+gitstart-twenty@users.noreply.github.com>
Co-authored-by: Weiko
Co-authored-by: Charles Bochet
---
.github/workflows/ci-tinybird.yaml | 2 +-
.github/workflows/ci-website.yaml | 1 -
...tingsAccountsCalendarChannelsContainer.tsx | 4 +-
...ttingsAccountsMessageChannelsContainer.tsx | 4 +-
...ettingsServerlessFunctionCodeEditorTab.tsx | 2 +-
.../components/ShowPageSubContainer.tsx | 3 +-
.../modules/ui/layout/tab/components/Tab.tsx | 16 ++-
.../ui/layout/tab/components/TabList.tsx | 24 ++--
.../TabListFromUrlOptionalEffect.tsx | 33 ++++++
.../__stories__/Tablist.stories.tsx | 6 +-
.../data-model/SettingsObjectDetailPage.tsx | 112 +++++++++---------
.../constants/SettingsObjectDetailTabs.ts | 8 ++
.../SettingsServerlessFunctionDetail.tsx | 2 +-
13 files changed, 143 insertions(+), 74 deletions(-)
create mode 100644 packages/twenty-front/src/modules/ui/layout/tab/components/TabListFromUrlOptionalEffect.tsx
create mode 100644 packages/twenty-front/src/pages/settings/data-model/constants/SettingsObjectDetailTabs.ts
diff --git a/.github/workflows/ci-tinybird.yaml b/.github/workflows/ci-tinybird.yaml
index cdeb97af15c7..44328556408c 100644
--- a/.github/workflows/ci-tinybird.yaml
+++ b/.github/workflows/ci-tinybird.yaml
@@ -14,6 +14,7 @@ jobs:
ci:
timeout-minutes: 10
runs-on: ubuntu-latest
+ uses: tinybirdco/ci/.github/workflows/ci.yml@main
steps:
- name: Check for changed files
id: changed-files
@@ -28,7 +29,6 @@ jobs:
run: echo "No relevant changes. Skipping CI."
- name: Check twenty-tinybird package
- uses: tinybirdco/ci/.github/workflows/ci.yml@main
with:
data_project_dir: packages/twenty-tinybird
tb_admin_token: ${{ secrets.TB_ADMIN_TOKEN }}
diff --git a/.github/workflows/ci-website.yaml b/.github/workflows/ci-website.yaml
index a7300430288f..770381855bd9 100644
--- a/.github/workflows/ci-website.yaml
+++ b/.github/workflows/ci-website.yaml
@@ -1,5 +1,4 @@
name: CI Website
-timeout-minutes: 10
on:
push:
branches:
diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx
index 9556441e6267..ae4e0b59a1a7 100644
--- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx
+++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx
@@ -64,7 +64,9 @@ export const SettingsAccountsCalendarChannelsContainer = () => {
{tabs.length > 1 && (
diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx
index 2c5e1102d3b3..672d66aa3ea0 100644
--- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx
+++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx
@@ -63,7 +63,9 @@ export const SettingsAccountsMessageChannelsContainer = () => {
{tabs.length > 1 && (
diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionCodeEditorTab.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionCodeEditorTab.tsx
index d9fffdb0600a..e9b948366808 100644
--- a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionCodeEditorTab.tsx
+++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionCodeEditorTab.tsx
@@ -84,7 +84,7 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
const HeaderTabList = (
file.path !== '.env')
.map((file) => {
diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx
index b9ab15777d10..beb0961bb10a 100644
--- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx
@@ -131,8 +131,9 @@ export const ShowPageSubContainer = ({
diff --git a/packages/twenty-front/src/modules/ui/layout/tab/components/Tab.tsx b/packages/twenty-front/src/modules/ui/layout/tab/components/Tab.tsx
index 93a96eb46a86..987eea1706bf 100644
--- a/packages/twenty-front/src/modules/ui/layout/tab/components/Tab.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/tab/components/Tab.tsx
@@ -1,6 +1,7 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { ReactElement } from 'react';
+import { Link } from 'react-router-dom';
import { IconComponent, Pill } from 'twenty-ui';
type TabProps = {
@@ -12,9 +13,14 @@ type TabProps = {
onClick?: () => void;
disabled?: boolean;
pill?: string | ReactElement;
+ to?: string;
};
-const StyledTab = styled.div<{ active?: boolean; disabled?: boolean }>`
+const StyledTab = styled.button<{
+ active?: boolean;
+ disabled?: boolean;
+ to?: string;
+}>`
align-items: center;
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
border-color: ${({ theme, active }) =>
@@ -26,6 +32,10 @@ const StyledTab = styled.div<{ active?: boolean; disabled?: boolean }>`
? theme.font.color.light
: theme.font.color.secondary};
cursor: pointer;
+ background-color: transparent;
+ border-left: none;
+ border-right: none;
+ border-top: none;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
@@ -33,6 +43,7 @@ const StyledTab = styled.div<{ active?: boolean; disabled?: boolean }>`
margin-bottom: 0;
padding: ${({ theme }) => theme.spacing(2) + ' 0'};
pointer-events: ${({ disabled }) => (disabled ? 'none' : '')};
+ text-decoration: none;
`;
const StyledHover = styled.span`
@@ -61,6 +72,7 @@ export const Tab = ({
className,
disabled,
pill,
+ to,
}: TabProps) => {
const theme = useTheme();
return (
@@ -70,6 +82,8 @@ export const Tab = ({
className={className}
disabled={disabled}
data-testid={'tab-' + id}
+ as={to ? Link : 'button'}
+ to={to}
>
{Icon && }
diff --git a/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx b/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx
index 9fcb497c62a4..7d724d67e918 100644
--- a/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx
@@ -7,6 +7,7 @@ import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { TabListScope } from '@/ui/layout/tab/scopes/TabListScope';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
+import { TabListFromUrlOptionalEffect } from '@/ui/layout/tab/components/TabListFromUrlOptionalEffect';
import { LayoutCard } from '@/ui/layout/tab/types/LayoutCard';
import { Tab } from './Tab';
@@ -21,10 +22,11 @@ export type SingleTabProps = {
};
type TabListProps = {
- tabListId: string;
+ tabListInstanceId: string;
tabs: SingleTabProps[];
loading?: boolean;
className?: string;
+ behaveAsLinks?: boolean;
};
const StyledContainer = styled.div`
@@ -38,13 +40,14 @@ const StyledContainer = styled.div`
export const TabList = ({
tabs,
- tabListId,
+ tabListInstanceId,
loading,
className,
+ behaveAsLinks = true,
}: TabListProps) => {
const initialActiveTabId = tabs.find((tab) => !tab.hide)?.id || '';
- const { activeTabIdState, setActiveTabId } = useTabList(tabListId);
+ const { activeTabIdState, setActiveTabId } = useTabList(tabListInstanceId);
const activeTabId = useRecoilValue(activeTabIdState);
@@ -53,7 +56,11 @@ export const TabList = ({
}, [initialActiveTabId, setActiveTabId]);
return (
-
+
+ tab.id)}
+ />
{tabs
@@ -65,11 +72,14 @@ export const TabList = ({
title={tab.title}
Icon={tab.Icon}
active={tab.id === activeTabId}
- onClick={() => {
- setActiveTabId(tab.id);
- }}
disabled={tab.disabled ?? loading}
pill={tab.pill}
+ to={behaveAsLinks ? `#${tab.id}` : undefined}
+ onClick={() => {
+ if (!behaveAsLinks) {
+ setActiveTabId(tab.id);
+ }
+ }}
/>
))}
diff --git a/packages/twenty-front/src/modules/ui/layout/tab/components/TabListFromUrlOptionalEffect.tsx b/packages/twenty-front/src/modules/ui/layout/tab/components/TabListFromUrlOptionalEffect.tsx
new file mode 100644
index 000000000000..8abbe61b70f7
--- /dev/null
+++ b/packages/twenty-front/src/modules/ui/layout/tab/components/TabListFromUrlOptionalEffect.tsx
@@ -0,0 +1,33 @@
+import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
+import { useEffect } from 'react';
+import { useLocation } from 'react-router-dom';
+import { useRecoilValue } from 'recoil';
+
+type TabListFromUrlOptionalEffectProps = {
+ componentInstanceId: string;
+ tabListIds: string[];
+};
+
+export const TabListFromUrlOptionalEffect = ({
+ componentInstanceId,
+ tabListIds,
+}: TabListFromUrlOptionalEffectProps) => {
+ const location = useLocation();
+ const { activeTabIdState } = useTabList(componentInstanceId);
+ const { setActiveTabId } = useTabList(componentInstanceId);
+
+ const hash = location.hash.replace('#', '');
+ const activeTabId = useRecoilValue(activeTabIdState);
+
+ useEffect(() => {
+ if (hash === activeTabId) {
+ return;
+ }
+
+ if (tabListIds.includes(hash)) {
+ setActiveTabId(hash);
+ }
+ }, [hash, activeTabId, setActiveTabId, tabListIds]);
+
+ return <>>;
+};
diff --git a/packages/twenty-front/src/modules/ui/layout/tab/components/__stories__/Tablist.stories.tsx b/packages/twenty-front/src/modules/ui/layout/tab/components/__stories__/Tablist.stories.tsx
index 7c4c63256e31..d2c2cdf4781c 100644
--- a/packages/twenty-front/src/modules/ui/layout/tab/components/__stories__/Tablist.stories.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/tab/components/__stories__/Tablist.stories.tsx
@@ -1,6 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, within } from '@storybook/test';
-import { ComponentDecorator, IconCheckbox } from 'twenty-ui';
+import { ComponentWithRouterDecorator, IconCheckbox } from 'twenty-ui';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
@@ -39,7 +39,7 @@ const meta: Meta = {
title: 'UI/Layout/Tab/TabList',
component: TabList,
args: {
- tabListId: 'tab-list-id',
+ tabListInstanceId: 'tab-list-id',
tabs: tabs,
},
decorators: [
@@ -48,7 +48,7 @@ const meta: Meta = {
),
- ComponentDecorator,
+ ComponentWithRouterDecorator,
],
};
diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx
index 52d22747aba8..915791cc69a7 100644
--- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx
+++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx
@@ -26,10 +26,11 @@ import {
IconPlus,
IconSettings,
IconTool,
- isDefined,
MAIN_COLORS,
UndecoratedLink,
+ isDefined,
} from 'twenty-ui';
+import { SETTINGS_OBJECT_DETAIL_TABS } from '~/pages/settings/data-model/constants/SettingsObjectDetailTabs';
import { updatedObjectSlugState } from '~/pages/settings/data-model/states/updatedObjectSlugState';
const StyledTabListContainer = styled.div`
@@ -63,11 +64,6 @@ const StyledTitleContainer = styled.div`
display: flex;
`;
-const TAB_LIST_COMPONENT_ID = 'object-details-tab-list';
-const FIELDS_TAB_ID = 'fields';
-const SETTINGS_TAB_ID = 'settings';
-const INDEXES_TAB_ID = 'indexes';
-
export const SettingsObjectDetailPage = () => {
const navigate = useNavigate();
@@ -82,7 +78,9 @@ export const SettingsObjectDetailPage = () => {
findActiveObjectMetadataItemBySlug(objectSlug) ??
findActiveObjectMetadataItemBySlug(updatedObjectSlug);
- const { activeTabIdState } = useTabList(TAB_LIST_COMPONENT_ID);
+ const { activeTabIdState } = useTabList(
+ SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID,
+ );
const activeTabId = useRecoilValue(activeTabIdState);
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
@@ -105,19 +103,19 @@ export const SettingsObjectDetailPage = () => {
const tabs = [
{
- id: FIELDS_TAB_ID,
+ id: SETTINGS_OBJECT_DETAIL_TABS.TABS_IDS.FIELDS,
title: 'Fields',
Icon: IconListDetails,
hide: false,
},
{
- id: SETTINGS_TAB_ID,
+ id: SETTINGS_OBJECT_DETAIL_TABS.TABS_IDS.SETTINGS,
title: 'Settings',
Icon: IconSettings,
hide: false,
},
{
- id: INDEXES_TAB_ID,
+ id: SETTINGS_OBJECT_DETAIL_TABS.TABS_IDS.INDEXES,
title: 'Indexes',
Icon: IconCodeCircle,
hide: !isAdvancedModeEnabled || !isUniqueIndexesEnabled,
@@ -127,11 +125,11 @@ export const SettingsObjectDetailPage = () => {
const renderActiveTabContent = () => {
switch (activeTabId) {
- case FIELDS_TAB_ID:
+ case SETTINGS_OBJECT_DETAIL_TABS.TABS_IDS.FIELDS:
return ;
- case SETTINGS_TAB_ID:
+ case SETTINGS_OBJECT_DETAIL_TABS.TABS_IDS.SETTINGS:
return ;
- case INDEXES_TAB_ID:
+ case SETTINGS_OBJECT_DETAIL_TABS.TABS_IDS.INDEXES:
return ;
default:
return <>>;
@@ -141,49 +139,51 @@ export const SettingsObjectDetailPage = () => {
const objectTypeLabel = getObjectTypeLabel(objectMetadataItem);
return (
-
-
-
-
- }
- links={[
- {
- children: 'Workspace',
- href: getSettingsPagePath(SettingsPath.Workspace),
- },
- { children: 'Objects', href: '/settings/objects' },
- {
- children: objectMetadataItem.labelPlural,
- },
- ]}
- actionButton={
- activeTabId === FIELDS_TAB_ID && (
-
-
+ )
+ }
+ >
+
+
+
+
+
+
+
-
- )
- }
- >
-
-
-
-
-
- {renderActiveTabContent()}
-
-
-
+
+
+ {renderActiveTabContent()}
+
+
+
+ >
);
};
diff --git a/packages/twenty-front/src/pages/settings/data-model/constants/SettingsObjectDetailTabs.ts b/packages/twenty-front/src/pages/settings/data-model/constants/SettingsObjectDetailTabs.ts
new file mode 100644
index 000000000000..6b8ce7f11404
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/data-model/constants/SettingsObjectDetailTabs.ts
@@ -0,0 +1,8 @@
+export const SETTINGS_OBJECT_DETAIL_TABS = {
+ COMPONENT_INSTANCE_ID: 'setting-object-details-tab-list',
+ TABS_IDS: {
+ FIELDS: 'fields',
+ SETTINGS: 'settings',
+ INDEXES: 'indexes',
+ },
+} as const;
diff --git a/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionDetail.tsx b/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionDetail.tsx
index 7fadbf3bccc5..32c74cec542a 100644
--- a/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionDetail.tsx
+++ b/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionDetail.tsx
@@ -263,7 +263,7 @@ export const SettingsServerlessFunctionDetail = () => {
>
{renderActiveTabContent()}