fix: update settings page for mobile viewport (#18991)

## Description

- This PR fixes
https://github.com/twentyhq/core-team-issues/issues/2313#issuecomment-4116380772
- Fixes Admin Panel Apps table, Data model table and Roles table
- made data model table scrollable for mobile viewport keeping name
column fixed.


## Visual Appearance

## Before

- Roles Table

<img width="636" height="1039" alt="Screenshot 2026-03-26 at 1 28 30 PM"
src="https://github.com/user-attachments/assets/fa7532bd-aeb0-4c8f-a40e-08cc976cd2c2"
/>



- Admin Apps table

<img width="610" height="998" alt="Screenshot 2026-03-26 at 1 28 12 PM"
src="https://github.com/user-attachments/assets/8712473c-350f-46f8-9e68-9cb0a5fa9d80"
/>





- Data Model table



https://github.com/user-attachments/assets/c48075c5-56dd-4b76-acd4-76330d6dab94






## After

- Roles Table

<img width="761" height="1045" alt="Screenshot 2026-03-26 at 1 23 19 PM"
src="https://github.com/user-attachments/assets/17099956-816a-4d41-b987-563f6931a995"
/>

- Admin Apps table

<img width="794" height="1044" alt="Screenshot 2026-03-26 at 1 23 34 PM"
src="https://github.com/user-attachments/assets/0463a087-2363-4192-9adb-7d27076f303a"
/>


- Data model Table


https://github.com/user-attachments/assets/fd44e353-cc7c-44cf-bda0-d2f3cd531f2e

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This commit is contained in:
Harshit Singh
2026-03-26 21:17:40 +05:30
committed by GitHub
parent 7a341c6475
commit 34832815e6
13 changed files with 192 additions and 110 deletions
@@ -17,9 +17,8 @@ const StyledText = styled.div`
text-overflow: ellipsis;
`;
const StyledCloseButtonContainer = styled.div`
color: ${themeCssVariables.grayScale.gray1};
display: flex;
const StyledInvertedIconButton = styled(IconButton)`
color: ${themeCssVariables.font.color.inverted} !important;
`;
const StyledContent = styled.div<{ hasCloseButton: boolean }>`
@@ -78,15 +77,13 @@ export const InformationBanner = ({
)}
</StyledContent>
{onClose && (
<StyledCloseButtonContainer>
<IconButton
Icon={IconX}
size="small"
variant="tertiary"
onClick={onClose}
ariaLabel={t`Close banner`}
/>
</StyledCloseButtonContainer>
<StyledInvertedIconButton
Icon={IconX}
size="small"
variant="tertiary"
onClick={onClose}
ariaLabel={t`Close banner`}
/>
)}
</Banner>
)}
@@ -6,6 +6,7 @@ import { IconHelpCircle, IconSettings } from 'twenty-ui/display';
import { AnimatedExpandableContainer } from 'twenty-ui/layout';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useOpenSettingsMenu } from '@/navigation/hooks/useOpenSettings';
import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState';
@@ -36,6 +37,8 @@ export const NavigationDrawerOtherSection = () => {
navigationMemorizedUrlState,
);
const { openSettingsMenu } = useOpenSettingsMenu();
const { toggleNavigationSection } = useNavigationSection('Other');
const isNavigationSectionOpen = useAtomFamilyStateValue(
isNavigationSectionOpenFamilyState,
@@ -46,6 +49,7 @@ export const NavigationDrawerOtherSection = () => {
setNavigationDrawerExpandedMemorized(isNavigationDrawerExpanded);
setIsNavigationDrawerExpanded(true);
setNavigationMemorizedUrl(location.pathname + location.search);
openSettingsMenu();
navigate(getSettingsPath(SettingsPath.ProfilePage));
};
@@ -41,9 +41,11 @@ const StyledMainContainerLayoutForMobile = styled.div`
const StyledPageBodyForMobileContainer = styled.div`
display: flex;
flex: 1;
flex: 1 1 0;
flex-direction: column;
min-height: 0;
min-width: 0;
width: 0;
> * {
padding-bottom: 0;
@@ -28,6 +28,7 @@ const StyledTableHeaderRowContainer = styled.div`
`;
const TABLE_GRID = '1fr 1fr 100px 80px';
const TABLE_GRID_MOBILE = '3fr 3fr 1fr 70px';
export const SettingsAdminApps = () => {
const [searchQuery, setSearchQuery] = useState('');
@@ -64,7 +65,10 @@ export const SettingsAdminApps = () => {
<StyledTableContainer>
<Table>
<StyledTableHeaderRowContainer>
<TableRow gridTemplateColumns={TABLE_GRID}>
<TableRow
gridAutoColumns={TABLE_GRID}
mobileGridAutoColumns={TABLE_GRID_MOBILE}
>
<TableHeader>{t`Name`}</TableHeader>
<TableHeader>{t`Source`}</TableHeader>
<TableHeader>{t`Listed`}</TableHeader>
@@ -81,9 +85,13 @@ export const SettingsAdminApps = () => {
)}
fullWidth
>
<TableRow gridTemplateColumns={TABLE_GRID} isClickable>
<TableRow
gridAutoColumns={TABLE_GRID}
mobileGridAutoColumns={TABLE_GRID_MOBILE}
isClickable
>
<TableCell>{registration.name}</TableCell>
<TableCell>
<TableCell overflow="hidden">
{registration.sourcePackage ?? registration.sourceType}
</TableCell>
<TableCell>
@@ -11,6 +11,7 @@ import {
SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
StyledActionTableCell,
StyledNameTableCell,
StyledStickyFirstCell,
} from '@/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { useIcons } from 'twenty-ui/display';
@@ -70,25 +71,27 @@ export const SettingsObjectMetadataItemTableRow = ({
key={objectMetadataItem.namePlural}
to={link}
>
<StyledNameTableCell>
{isDefined(Icon) && (
<Icon
style={{
minWidth: theme.icon.size.md,
}}
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
)}
<StyledNameContainer>
<StyledNameLabel title={objectMetadataItem.labelPlural}>
{objectMetadataItem.labelPlural}
</StyledNameLabel>
{!objectMetadataItem.isActive && (
<StyledInactiveLabel>{t`Deactivated`}</StyledInactiveLabel>
<StyledStickyFirstCell>
<StyledNameTableCell>
{isDefined(Icon) && (
<Icon
style={{
minWidth: theme.icon.size.md,
}}
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
)}
</StyledNameContainer>
</StyledNameTableCell>
<StyledNameContainer>
<StyledNameLabel title={objectMetadataItem.labelPlural}>
{objectMetadataItem.labelPlural}
</StyledNameLabel>
{!objectMetadataItem.isActive && (
<StyledInactiveLabel>{t`Deactivated`}</StyledInactiveLabel>
)}
</StyledNameContainer>
</StyledNameTableCell>
</StyledStickyFirstCell>
<TableCell>
<SettingsItemTypeTag item={objectMetadataItem} />
</TableCell>
@@ -1,11 +1,23 @@
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { styled } from '@linaria/react';
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
import React from 'react';
export const SETTINGS_OBJECT_TABLE_COLUMN_WIDTH = '98.7px';
export const SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS = `180px ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} 36px`;
export const SETTINGS_OBJECT_TABLE_ROW_MOBILE_MIN_WIDTH = '520px';
export const StyledStickyFirstCell = styled.div`
@media (max-width: ${MOBILE_VIEWPORT}px) {
background: ${themeCssVariables.background.primary};
left: 0;
position: sticky;
z-index: 1;
}
`;
export const StyledNameTableCell = (
props: React.ComponentProps<typeof TableCell>,
) => (
@@ -6,7 +6,10 @@ import { Trans } from '@lingui/react/macro';
export const SettingsRolesTableHeader = () => {
return (
<Table>
<TableRow gridAutoColumns="332px 3fr 2fr 1fr">
<TableRow
mobileGridAutoColumns="5fr 1fr 1fr 35px"
gridAutoColumns="332px 3fr 2fr 1fr"
>
<TableHeader>
<Trans>Name</Trans>
</TableHeader>
@@ -14,6 +17,7 @@ export const SettingsRolesTableHeader = () => {
<Trans>Assigned to</Trans>
</TableHeader>
<TableHeader></TableHeader>
<TableHeader></TableHeader>
</TableRow>
</Table>
);
@@ -83,6 +83,7 @@ export const SettingsRolesTableRow = ({ role }: SettingsRolesTableRowProps) => {
<TableRow
key={role.id}
gridAutoColumns="332px 3fr 2fr 1fr"
mobileGridAutoColumns="5fr 1fr 1fr 35px"
to={getSettingsPath(SettingsPath.RoleDetail, { roleId: role.id })}
>
<TableCell>
@@ -36,6 +36,7 @@ const StyledLinkContainer = styled.div`
const StyledText = styled.span`
color: inherit;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -129,7 +129,7 @@ export const NavigationDrawer = ({
isExpanded={isNavigationDrawerExpanded}
>
{isSettingsDrawer && title ? (
!isMobile && <NavigationDrawerBackButton title={title} />
<NavigationDrawerBackButton title={title} />
) : (
<NavigationDrawerHeader showCollapseButton />
)}
@@ -1,5 +1,6 @@
import { styled } from '@linaria/react';
import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
@@ -53,6 +54,9 @@ export const NavigationDrawerBackButton = ({
const setIsNavigationDrawerExpanded = useSetAtomState(
isNavigationDrawerExpandedState,
);
const setCurrentMobileNavigationDrawer = useSetAtomState(
currentMobileNavigationDrawerState,
);
const navigationDrawerExpandedMemorized = useAtomStateValue(
navigationDrawerExpandedMemorizedState,
);
@@ -70,9 +74,10 @@ export const NavigationDrawerBackButton = ({
<UndecoratedLink
to={navigationMemorizedUrl}
replace
onClick={() =>
setIsNavigationDrawerExpanded(navigationDrawerExpandedMemorized)
}
onClick={() => {
setIsNavigationDrawerExpanded(navigationDrawerExpandedMemorized);
setCurrentMobileNavigationDrawer('main');
}}
>
<StyledIconAndButtonContainer>
<IconX
@@ -6,7 +6,11 @@ import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField
import { useCombinedGetTotalCount } from '@/object-record/multiple-objects/hooks/useCombinedGetTotalCount';
import { SettingsObjectMetadataItemTableRow } from '@/settings/data-model/object-details/components/SettingsObjectItemTableRow';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS } from '@/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents';
import {
SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
SETTINGS_OBJECT_TABLE_ROW_MOBILE_MIN_WIDTH,
StyledStickyFirstCell,
} from '@/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents';
import { SettingsObjectInactiveMenuDropDown } from '@/settings/data-model/objects/components/SettingsObjectInactiveMenuDropDown';
import { getItemTagInfo } from '@/settings/data-model/utils/getItemTagInfo';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
@@ -26,7 +30,11 @@ import { getSettingsPath } from 'twenty-shared/utils';
import { IconArchive, IconChevronRight, IconSettings } from 'twenty-ui/display';
import { SearchInput } from 'twenty-ui/input';
import { MenuItemToggle } from 'twenty-ui/navigation';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import {
MOBILE_VIEWPORT,
ThemeContext,
themeCssVariables,
} from 'twenty-ui/theme-constants';
import { GET_SETTINGS_OBJECT_TABLE_METADATA } from '~/pages/settings/data-model/constants/SettingsObjectTableMetadata';
import type { SettingsObjectTableItem } from '~/pages/settings/data-model/types/SettingsObjectTableItem';
import { normalizeSearchText } from '~/utils/normalizeSearchText';
@@ -39,6 +47,19 @@ const StyledSearchInputContainer = styled.div`
padding-bottom: ${themeCssVariables.spacing[2]};
`;
const StyledScrollWrapper = styled.div`
@media (max-width: ${MOBILE_VIEWPORT}px) {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
`;
const StyledScrollableContent = styled.div`
@media (max-width: ${MOBILE_VIEWPORT}px) {
min-width: ${SETTINGS_OBJECT_TABLE_ROW_MOBILE_MIN_WIDTH};
}
`;
export const SettingsObjectTable = ({
objectMetadataItems,
withSearchBar = true,
@@ -170,74 +191,98 @@ export const SettingsObjectTable = ({
</StyledSearchInputContainer>
)}
<Table>
<TableRow
gridTemplateColumns={SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
>
{GET_SETTINGS_OBJECT_TABLE_METADATA.fields.map(
(settingsObjectsTableMetadataField) => (
<SortableTableHeader
key={settingsObjectsTableMetadataField.fieldName}
fieldName={settingsObjectsTableMetadataField.fieldName}
label={t(settingsObjectsTableMetadataField.fieldLabel)}
tableId={GET_SETTINGS_OBJECT_TABLE_METADATA.tableId}
align={settingsObjectsTableMetadataField.align}
initialSort={GET_SETTINGS_OBJECT_TABLE_METADATA.initialSort}
/>
),
)}
<TableHeader></TableHeader>
</TableRow>
{filteredObjectSettingsItems.map((objectSettingsItem) => {
const isActive = objectSettingsItem.objectMetadataItem.isActive;
return (
<SettingsObjectMetadataItemTableRow
key={objectSettingsItem.objectMetadataItem.namePlural}
objectMetadataItem={objectSettingsItem.objectMetadataItem}
totalObjectCount={objectSettingsItem.totalObjectCount}
action={
isActive ? (
<StyledIconChevronRightContainer>
<IconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
<StyledScrollWrapper>
<StyledScrollableContent>
<Table>
<TableRow
gridTemplateColumns={
SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS
}
>
{GET_SETTINGS_OBJECT_TABLE_METADATA.fields.map(
(settingsObjectsTableMetadataField, index) =>
index === 0 ? (
<StyledStickyFirstCell
key={settingsObjectsTableMetadataField.fieldName}
>
<SortableTableHeader
fieldName={settingsObjectsTableMetadataField.fieldName}
label={t(settingsObjectsTableMetadataField.fieldLabel)}
tableId={GET_SETTINGS_OBJECT_TABLE_METADATA.tableId}
align={settingsObjectsTableMetadataField.align}
initialSort={
GET_SETTINGS_OBJECT_TABLE_METADATA.initialSort
}
/>
</StyledStickyFirstCell>
) : (
<SortableTableHeader
key={settingsObjectsTableMetadataField.fieldName}
fieldName={settingsObjectsTableMetadataField.fieldName}
label={t(settingsObjectsTableMetadataField.fieldLabel)}
tableId={GET_SETTINGS_OBJECT_TABLE_METADATA.tableId}
align={settingsObjectsTableMetadataField.align}
initialSort={
GET_SETTINGS_OBJECT_TABLE_METADATA.initialSort
}
/>
</StyledIconChevronRightContainer>
) : (
<SettingsObjectInactiveMenuDropDown
isCustomObject={
objectSettingsItem.objectMetadataItem.isCustom
}
objectMetadataItemNamePlural={
objectSettingsItem.objectMetadataItem.namePlural
}
onActivate={() =>
updateOneObjectMetadataItem({
idToUpdate: objectSettingsItem.objectMetadataItem.id,
updatePayload: { isActive: true },
})
}
onDelete={() =>
deleteOneObjectMetadataItem(
objectSettingsItem.objectMetadataItem.id,
)
}
/>
)
}
link={
isActive
? getSettingsPath(SettingsPath.ObjectDetail, {
objectNamePlural:
objectSettingsItem.objectMetadataItem.namePlural,
})
: undefined
}
/>
);
})}
</Table>
),
)}
<TableHeader></TableHeader>
</TableRow>
{filteredObjectSettingsItems.map((objectSettingsItem) => {
const isActive = objectSettingsItem.objectMetadataItem.isActive;
return (
<SettingsObjectMetadataItemTableRow
key={objectSettingsItem.objectMetadataItem.namePlural}
objectMetadataItem={objectSettingsItem.objectMetadataItem}
totalObjectCount={objectSettingsItem.totalObjectCount}
action={
isActive ? (
<StyledIconChevronRightContainer>
<IconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
</StyledIconChevronRightContainer>
) : (
<SettingsObjectInactiveMenuDropDown
isCustomObject={
objectSettingsItem.objectMetadataItem.isCustom
}
objectMetadataItemNamePlural={
objectSettingsItem.objectMetadataItem.namePlural
}
onActivate={() =>
updateOneObjectMetadataItem({
idToUpdate:
objectSettingsItem.objectMetadataItem.id,
updatePayload: { isActive: true },
})
}
onDelete={() =>
deleteOneObjectMetadataItem(
objectSettingsItem.objectMetadataItem.id,
)
}
/>
)
}
link={
isActive
? getSettingsPath(SettingsPath.ObjectDetail, {
objectNamePlural:
objectSettingsItem.objectMetadataItem.namePlural,
})
: undefined
}
/>
);
})}
</Table>
</StyledScrollableContent>
</StyledScrollWrapper>
</>
);
};
@@ -10,7 +10,7 @@ const StyledBanner = styled.div<{ variant?: BannerVariant }>`
: themeCssVariables.color.blue};
display: flex;
gap: ${themeCssVariables.spacing[3]};
height: 40px;
min-height: 40px;
justify-content: center;
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[3]};
width: 100%;