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. Capture d’écran 2024-11-07 à 18 38 57 --------- 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 && ( - -