Polish settings page titles and admin tables (#22305)
## Summary - Reuse the shared settings title presentation for read-only and editable settings page titles. - Polish settings AI/app icons, breadcrumb cropping, and admin detail title icons. - Align admin panel table/card typography and spacing with existing settings tables. ## Before/After <img width="1524" height="2214" alt="Settings pages before and after" src="https://github.com/user-attachments/assets/91037b25-b442-4eb2-b244-1d8280ce2cd9" /> <img width="2200" height="3268" alt="Additional settings UI before and after" src="https://github.com/user-attachments/assets/f10d7283-7031-4958-8539-649c3067cd31" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22305?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
committed by
GitHub
parent
b199dde840
commit
2bb7fb2e9b
+7
-12
@@ -1,5 +1,6 @@
|
||||
import { AppChip } from '@/applications/components/AppChip';
|
||||
import { ApplicationDisplay } from '@/applications/components/ApplicationDisplay';
|
||||
import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient';
|
||||
import { StyledNameTableCell } from '@/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
@@ -209,20 +210,14 @@ const SettingsAdminAppsTableRow = ({
|
||||
mobileGridAutoColumns={TABLE_GRID_MOBILE}
|
||||
isClickable
|
||||
>
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.primary}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
minWidth="0"
|
||||
overflow="hidden"
|
||||
>
|
||||
<AppChip
|
||||
size="md"
|
||||
fallbackApplicationData={{
|
||||
logo: registration.logoUrl,
|
||||
<StyledNameTableCell minWidth="0" overflow="hidden">
|
||||
<ApplicationDisplay
|
||||
application={{
|
||||
name: registration.name,
|
||||
logo: registration.logoUrl,
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</StyledNameTableCell>
|
||||
<TableCell overflow="hidden" align="right">
|
||||
{getFormattedSource(registration)}
|
||||
</TableCell>
|
||||
|
||||
+37
-29
@@ -36,6 +36,10 @@ const StyledEmptyState = styled.div`
|
||||
padding: ${themeCssVariables.spacing[4]} 0;
|
||||
`;
|
||||
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const RECENT_USERS_GRID_TEMPLATE_COLUMNS = '1fr 2fr 1fr 36px';
|
||||
const TOP_WORKSPACES_GRID_TEMPLATE_COLUMNS = '2fr 1fr 36px';
|
||||
|
||||
@@ -99,13 +103,15 @@ export const SettingsAdminGeneral = () => {
|
||||
: t`Last 10 users created. Click to impersonate.`
|
||||
}
|
||||
/>
|
||||
<SettingsTextInput
|
||||
instanceId="admin-panel-user-search"
|
||||
value={userSearchTerm}
|
||||
onChange={setUserSearchTerm}
|
||||
placeholder={t`Search by name, email, or user ID...`}
|
||||
fullWidth
|
||||
/>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="admin-panel-user-search"
|
||||
value={userSearchTerm}
|
||||
onChange={setUserSearchTerm}
|
||||
placeholder={t`Search by name, email, or user ID...`}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
{isLoadingUsers ? (
|
||||
<SettingsSectionSkeletonLoader />
|
||||
) : recentUsers.length === 0 ? (
|
||||
@@ -114,15 +120,15 @@ export const SettingsAdminGeneral = () => {
|
||||
</StyledEmptyState>
|
||||
) : (
|
||||
<Table>
|
||||
<TableRow
|
||||
gridTemplateColumns={RECENT_USERS_GRID_TEMPLATE_COLUMNS}
|
||||
>
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Email`}</TableHeader>
|
||||
<TableHeader>{t`Workspace`}</TableHeader>
|
||||
<TableHeader />
|
||||
</TableRow>
|
||||
<TableBody>
|
||||
<TableRow
|
||||
gridTemplateColumns={RECENT_USERS_GRID_TEMPLATE_COLUMNS}
|
||||
>
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Email`}</TableHeader>
|
||||
<TableHeader>{t`Workspace`}</TableHeader>
|
||||
<TableHeader />
|
||||
</TableRow>
|
||||
{recentUsers.map((user) => (
|
||||
<TableRow
|
||||
key={user.id}
|
||||
@@ -195,13 +201,15 @@ export const SettingsAdminGeneral = () => {
|
||||
title={t`Top Workspaces`}
|
||||
description={t`Top 10 workspaces by number of users`}
|
||||
/>
|
||||
<SettingsTextInput
|
||||
instanceId="admin-panel-workspace-search"
|
||||
value={workspaceSearchTerm}
|
||||
onChange={setWorkspaceSearchTerm}
|
||||
placeholder={t`Search by workspace name, subdomain, or ID...`}
|
||||
fullWidth
|
||||
/>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="admin-panel-workspace-search"
|
||||
value={workspaceSearchTerm}
|
||||
onChange={setWorkspaceSearchTerm}
|
||||
placeholder={t`Search by workspace name, subdomain, or ID...`}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
{isLoadingWorkspaces ? (
|
||||
<SettingsSectionSkeletonLoader />
|
||||
) : topWorkspaces.length === 0 ? (
|
||||
@@ -210,14 +218,14 @@ export const SettingsAdminGeneral = () => {
|
||||
</StyledEmptyState>
|
||||
) : (
|
||||
<Table>
|
||||
<TableRow
|
||||
gridTemplateColumns={TOP_WORKSPACES_GRID_TEMPLATE_COLUMNS}
|
||||
>
|
||||
<TableHeader>{t`Workspace`}</TableHeader>
|
||||
<TableHeader align="right">{t`Users`}</TableHeader>
|
||||
<TableHeader />
|
||||
</TableRow>
|
||||
<TableBody>
|
||||
<TableRow
|
||||
gridTemplateColumns={TOP_WORKSPACES_GRID_TEMPLATE_COLUMNS}
|
||||
>
|
||||
<TableHeader>{t`Workspace`}</TableHeader>
|
||||
<TableHeader align="right">{t`Users`}</TableHeader>
|
||||
<TableHeader />
|
||||
</TableRow>
|
||||
{topWorkspaces.map((workspace) => (
|
||||
<TableRow
|
||||
key={workspace.id}
|
||||
|
||||
+7
-7
@@ -18,7 +18,7 @@ import { Section } from 'twenty-ui/layout';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { GetServerAdminsDocument } from '~/generated-admin/graphql';
|
||||
|
||||
const SERVER_ADMINS_GRID_TEMPLATE_COLUMNS = '2fr 1fr 1fr 36px';
|
||||
const SERVER_ADMINS_GRID_TEMPLATE_COLUMNS = '1fr 2fr 1fr 36px';
|
||||
|
||||
const StyledEmptyState = styled.div`
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
@@ -49,13 +49,13 @@ export const SettingsAdminServerAdmins = () => {
|
||||
<StyledEmptyState>{t`No server administrators found.`}</StyledEmptyState>
|
||||
) : (
|
||||
<Table>
|
||||
<TableRow gridTemplateColumns={SERVER_ADMINS_GRID_TEMPLATE_COLUMNS}>
|
||||
<TableHeader>{t`Administrator`}</TableHeader>
|
||||
<TableHeader>{t`Admin panel`}</TableHeader>
|
||||
<TableHeader>{t`Impersonation`}</TableHeader>
|
||||
<TableHeader />
|
||||
</TableRow>
|
||||
<TableBody>
|
||||
<TableRow gridTemplateColumns={SERVER_ADMINS_GRID_TEMPLATE_COLUMNS}>
|
||||
<TableHeader>{t`Administrator`}</TableHeader>
|
||||
<TableHeader>{t`Admin panel`}</TableHeader>
|
||||
<TableHeader>{t`Impersonation`}</TableHeader>
|
||||
<TableHeader />
|
||||
</TableRow>
|
||||
{serverAdmins.map((admin) => {
|
||||
const adminLabel =
|
||||
`${admin.firstName || ''} ${admin.lastName || ''}`.trim() ||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
TitleInput,
|
||||
type TitleInputProps,
|
||||
} from '@/ui/input/components/TitleInput';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
const StyledTitleInputContainer = styled.div`
|
||||
max-width: 420px;
|
||||
min-width: 0;
|
||||
width: fit-content;
|
||||
`;
|
||||
|
||||
export type SettingsEditableTitleProps = TitleInputProps;
|
||||
|
||||
export const SettingsEditableTitle = (props: SettingsEditableTitleProps) => (
|
||||
<StyledTitleInputContainer>
|
||||
<TitleInput
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
sizeVariant={props.sizeVariant ?? 'sm'}
|
||||
/>
|
||||
</StyledTitleInputContainer>
|
||||
);
|
||||
+3
-2
@@ -9,11 +9,12 @@ import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
const StyledRowContainer = styled.div`
|
||||
> * {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
font-size: ${themeCssVariables.font.size.sm};
|
||||
font-size: ${themeCssVariables.font.size.md};
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
min-height: ${themeCssVariables.spacing[6]};
|
||||
height: ${themeCssVariables.spacing[10]};
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
padding-left: ${themeCssVariables.spacing[3]};
|
||||
}
|
||||
|
||||
+39
-22
@@ -1,8 +1,9 @@
|
||||
import { SettingsEditableTitle } from '@/settings/components/SettingsEditableTitle';
|
||||
import { SettingsSecondaryBar } from '@/settings/components/layout/SettingsSecondaryBar';
|
||||
import { PageCardHeader } from '@/ui/layout/page/components/PageCardHeader';
|
||||
import { PageCardLayout } from '@/ui/layout/page/components/PageCardLayout';
|
||||
import { type BreadcrumbProps } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { type JSX, type ReactNode } from 'react';
|
||||
import { useId, type JSX, type ReactNode } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type SettingsPageLayoutProps = {
|
||||
@@ -25,25 +26,41 @@ export const SettingsPageLayout = ({
|
||||
children,
|
||||
tag,
|
||||
titleColor,
|
||||
}: SettingsPageLayoutProps) => (
|
||||
<PageCardLayout
|
||||
header={
|
||||
<PageCardHeader
|
||||
links={links}
|
||||
title={title}
|
||||
icon={icon}
|
||||
tag={tag}
|
||||
actionButton={actionButton}
|
||||
centerTitle
|
||||
titleColor={titleColor}
|
||||
}: SettingsPageLayoutProps) => {
|
||||
const titleInstanceId = useId();
|
||||
|
||||
const formattedTitle =
|
||||
typeof title === 'string' ? (
|
||||
<SettingsEditableTitle
|
||||
disabled
|
||||
instanceId={`settings-page-layout-title-${titleInstanceId}`}
|
||||
textColor={titleColor}
|
||||
value={title}
|
||||
/>
|
||||
}
|
||||
secondaryBar={
|
||||
isDefined(secondaryBar) ? (
|
||||
<SettingsSecondaryBar>{secondaryBar}</SettingsSecondaryBar>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</PageCardLayout>
|
||||
);
|
||||
) : (
|
||||
title
|
||||
);
|
||||
|
||||
return (
|
||||
<PageCardLayout
|
||||
header={
|
||||
<PageCardHeader
|
||||
links={links}
|
||||
title={formattedTitle}
|
||||
icon={icon}
|
||||
tag={tag}
|
||||
actionButton={actionButton}
|
||||
centerTitle
|
||||
titleColor={titleColor}
|
||||
/>
|
||||
}
|
||||
secondaryBar={
|
||||
isDefined(secondaryBar) ? (
|
||||
<SettingsSecondaryBar>{secondaryBar}</SettingsSecondaryBar>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</PageCardLayout>
|
||||
);
|
||||
};
|
||||
|
||||
+8
-24
@@ -1,18 +1,5 @@
|
||||
import { TitleInput } from '@/ui/input/components/TitleInput';
|
||||
import { styled } from '@linaria/react';
|
||||
import { SettingsEditableTitle } from '@/settings/components/SettingsEditableTitle';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledHeaderTitle = styled.div`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
font-size: ${themeCssVariables.font.size.lg};
|
||||
font-weight: ${themeCssVariables.font.weight.semiBold};
|
||||
max-width: 420px;
|
||||
width: fit-content;
|
||||
& > input:disabled {
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
type SettingsLogicFunctionLabelContainerProps = {
|
||||
value: string;
|
||||
@@ -26,15 +13,12 @@ export const SettingsLogicFunctionLabelContainer = ({
|
||||
readonly = false,
|
||||
}: SettingsLogicFunctionLabelContainerProps) => {
|
||||
return (
|
||||
<StyledHeaderTitle>
|
||||
<TitleInput
|
||||
instanceId="logic-function-name-input"
|
||||
sizeVariant="md"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={t`Function name`}
|
||||
disabled={readonly}
|
||||
/>
|
||||
</StyledHeaderTitle>
|
||||
<SettingsEditableTitle
|
||||
instanceId="logic-function-name-input"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={t`Function name`}
|
||||
disabled={readonly}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
+8
-37
@@ -1,34 +1,8 @@
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { SettingsEditableTitle } from '@/settings/components/SettingsEditableTitle';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
|
||||
import { TitleInput } from '@/ui/input/components/TitleInput';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledHeaderTitle = styled.div`
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
max-width: 420px;
|
||||
padding-left: ${themeCssVariables.spacing[1]};
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
width: fit-content;
|
||||
|
||||
&:hover {
|
||||
background: ${themeCssVariables.background.transparent.light};
|
||||
}
|
||||
|
||||
& > div:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
& > div :hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
& > input:disabled {
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
type SettingsRoleLabelContainerProps = {
|
||||
roleId: string;
|
||||
@@ -54,15 +28,12 @@ export const SettingsRoleLabelContainer = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledHeaderTitle>
|
||||
<TitleInput
|
||||
instanceId="role-label-input"
|
||||
disabled={!settingsDraftRole.isEditable}
|
||||
sizeVariant="sm"
|
||||
value={settingsDraftRole.label}
|
||||
onChange={handleChange}
|
||||
placeholder={t`Role name`}
|
||||
/>
|
||||
</StyledHeaderTitle>
|
||||
<SettingsEditableTitle
|
||||
instanceId="role-label-input"
|
||||
disabled={!settingsDraftRole.isEditable}
|
||||
value={settingsDraftRole.label}
|
||||
onChange={handleChange}
|
||||
placeholder={t`Role name`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user