fix(front): settings skeleton, app-detail header & empty favorites (#21209)
Three small post-redesign UI fixes. Each is an independent commit, so they can be split into separate PRs if preferred. ## 1. Settings loading skeleton — match the rounded-card layout The redesign (#21131) moved settings chrome into a rounded card (`SettingsPageLayout`: bordered header with breadcrumb + centered title, optional secondary bar, 760px body), but `SettingsSkeletonLoader` still rendered the old flat `PageHeader` + `PageBody` — so pages painted as a full-width flat bar then snapped into the card. - `SettingsSkeletonLoader` now reproduces the card and **reuses the real `SettingsPageHeader` + `SettingsPageContainer`**, so the frame aligns by construction; the card CSS is replicated (not `SettingsPageLayout`) to avoid the layout's side effects (hotkeys, side panel, info banner). - It's **composed with `SettingsSectionSkeletonLoader`** so the loading body is identical whether or not chrome is present. Rule: no chrome on screen yet → full-page skeleton; chrome already on screen → body-only `SettingsSectionSkeletonLoader` (the admin Enterprise tab now uses it, matching its sibling tabs). A short comment on each component documents this. ## 2. Application detail header — pass a plain title `SettingsApplicationDetails` / `SettingsAvailableApplicationDetails` passed a custom `SettingsApplicationDetailTitle` (avatar + name + multi-line description, fixed width) into `SettingsPageLayout`'s **centered single-line title slot**, which broke the header. They now pass the app's display name like every other page. The available-app "unlisted" notice moves into the body as a reusable `InlineBanner`; the now-unused `SettingsApplicationDetailTitle` is removed. ## 3. Navigation — hide Favorites when empty Always rendering the Favorites section (#21087) left a stray "Favorites" title above Workspace for users with no favorites. It now renders only when at least one favorite exists (redundant per-child guards dropped). Note: the "+ add favorite" entry point therefore appears once you have ≥1 favorite; the first favorite is created from a record/view as before. ## Verification - `nx typecheck twenty-front` ✅ · `oxlint` + `oxfmt --check` on changed files ✅ - i18n catalogs intentionally untouched — handled by the repo's separate i18n pipeline.
This commit is contained in:
+2
-2
@@ -5,7 +5,7 @@ import { SettingsAdminConfigVariables } from '@/settings/admin-panel/config-vari
|
||||
import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAdminTabs';
|
||||
import { SETTINGS_ADMIN_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminTabsId';
|
||||
import { SettingsAdminHealthStatus } from '@/settings/admin-panel/health-status/components/SettingsAdminHealthStatus';
|
||||
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
|
||||
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { lazy, Suspense } from 'react';
|
||||
@@ -34,7 +34,7 @@ export const SettingsAdminTabContent = () => {
|
||||
return <SettingsAdminHealthStatus />;
|
||||
case SETTINGS_ADMIN_TABS.ENTERPRISE:
|
||||
return (
|
||||
<Suspense fallback={<SettingsSkeletonLoader />}>
|
||||
<Suspense fallback={<SettingsSectionSkeletonLoader />}>
|
||||
<SettingsEnterprise isAdminPanelTab />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -1,54 +1,70 @@
|
||||
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
|
||||
import { PageBody } from '@/ui/layout/page/components/PageBody';
|
||||
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
|
||||
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 { useContext } from 'react';
|
||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
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%;
|
||||
`;
|
||||
|
||||
const StyledTitleLoaderContainer = styled.div`
|
||||
margin: ${themeCssVariables.spacing[8]} ${themeCssVariables.spacing[8]}
|
||||
${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
export const SettingsSkeletonLoader = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<PageHeader
|
||||
title={
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<Skeleton
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.m}
|
||||
width={120}
|
||||
/>{' '}
|
||||
</SkeletonTheme>
|
||||
}
|
||||
/>
|
||||
<PageBody>
|
||||
<StyledTitleLoaderContainer>
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<Skeleton
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.m}
|
||||
width={200}
|
||||
/>
|
||||
</SkeletonTheme>
|
||||
</StyledTitleLoaderContainer>
|
||||
</PageBody>
|
||||
</StyledContainer>
|
||||
<StyledRoot isMobile={isMobile}>
|
||||
<StyledCard>
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<SettingsPageHeader
|
||||
links={[
|
||||
{
|
||||
children: (
|
||||
<Skeleton
|
||||
width={64}
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
title={
|
||||
<Skeleton
|
||||
width={120}
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingsPageContainer>
|
||||
<SettingsSectionSkeletonLoader />
|
||||
</SettingsPageContainer>
|
||||
</SkeletonTheme>
|
||||
</StyledCard>
|
||||
</StyledRoot>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user