Remove all styled(Component) patterns in favor of parent wrappers and props (#18430)

## Summary

Eliminates all ~350 `styled(Component)` usages across `twenty-front` and
`twenty-ui` (212 files changed). Each was replaced following these
rules:

- **Margin/layout CSS** (margin, padding, flex, align-self, width) →
wrapped in a `styled.div`/`styled.span` parent container
- **Third-party components** (Link, TextareaAutosize,
ReactPhoneNumberInput, Handle, etc.) → parent container with child CSS
selectors (`> a`, `> textarea`, `> input`, etc.)
- **Intrinsic behavior via existing props** (TableRow
`gridTemplateColumns`, TableCell `color`/`align`) → replaced
`styled(TableRow)` / `styled(TableCell)` with direct prop usage
- **Other visual overrides on twenty-ui components** (Card, Section,
TabList, Button, MenuItem, ScrollWrapper, etc.) → parent wrappers with
`> div` / `> *` child selectors
- **Extending styled.div/span** → merged all CSS into a single
`styled.div`/`styled.span`

Also adds `overflow: hidden` to parent containers wrapping
`ScrollWrapper` so scroll activates correctly with the new wrapper
structure.

### Migration patterns

| Before | After |
|--------|-------|
| `styled(Avatar)` with `margin-right` | `<StyledAvatarContainer><Avatar
/></StyledAvatarContainer>` |
| `styled(Link)` with `text-decoration: none` |
`<StyledLinkContainer><Link /></StyledLinkContainer>` with `> a { ... }`
|
| `styled(TableRow)` with `grid-template-columns` | `<TableRow
gridTemplateColumns="..." />` |
| `styled(TableCell)` with `color` / `align` | `<TableCell color={...}
align="right" />` |
| `styled(Card)` with `margin-top` | `<StyledCardContainer><Card
/></StyledCardContainer>` |
| `styled(TabList)` with `background` |
`<StyledTabListContainer><TabList /></StyledTabListContainer>` with `>
div { ... }` |
| `styled(StyledBase)` extending a `styled.div` | Single merged
`styled.div` with all styles inlined |
This commit is contained in:
Charles Bochet
2026-03-05 18:16:25 +01:00
committed by GitHub
parent e5e3132ddd
commit c53a13417e
224 changed files with 4765 additions and 3837 deletions
@@ -13,11 +13,11 @@ type SettingsAccountsBlocklistTableProps = {
handleBlockedEmailRemove: (id: string) => void;
};
const StyledTable = styled(Table)`
const StyledTableContainer = styled.div`
margin-top: ${themeCssVariables.spacing[4]};
`;
const StyledTableBody = styled(TableBody)`
const StyledTableBodyContainer = styled.div`
border-bottom: 1px solid ${themeCssVariables.border.color.light};
`;
@@ -28,25 +28,29 @@ export const SettingsAccountsBlocklistTable = ({
return (
<>
{blocklist.length > 0 && (
<StyledTable>
<TableRow
gridAutoColumns="200px 1fr 20px"
mobileGridAutoColumns="120px 1fr 20px"
>
<TableHeader>{t`Email/Domain`}</TableHeader>
<TableHeader>{t`Added to blocklist`}</TableHeader>
<TableHeader></TableHeader>
</TableRow>
<StyledTableBody>
{blocklist.map((blocklistItem) => (
<SettingsAccountsBlocklistTableRow
key={blocklistItem.id}
blocklistItem={blocklistItem}
onRemove={handleBlockedEmailRemove}
/>
))}
</StyledTableBody>
</StyledTable>
<StyledTableContainer>
<Table>
<TableRow
gridAutoColumns="200px 1fr 20px"
mobileGridAutoColumns="120px 1fr 20px"
>
<TableHeader>{t`Email/Domain`}</TableHeader>
<TableHeader>{t`Added to blocklist`}</TableHeader>
<TableHeader></TableHeader>
</TableRow>
<StyledTableBodyContainer>
<TableBody>
{blocklist.map((blocklistItem) => (
<SettingsAccountsBlocklistTableRow
key={blocklistItem.id}
blocklistItem={blocklistItem}
onRemove={handleBlockedEmailRemove}
/>
))}
</TableBody>
</StyledTableBodyContainer>
</Table>
</StyledTableContainer>
)}
</>
);
@@ -11,8 +11,10 @@ type SettingsAccountsEventVisibilitySettingsCardProps = {
value?: CalendarChannelVisibility;
};
const StyledCardMedia = styled(SettingsAccountsVisibilityIcon)`
height: ${themeCssVariables.spacing[6]};
const StyledCardMediaContainer = styled.div`
> * {
height: ${themeCssVariables.spacing[6]};
}
`;
const eventSettingsVisibilityOptions = [
@@ -20,13 +22,21 @@ const eventSettingsVisibilityOptions = [
title: msg`Everything`,
description: msg`The whole event details will be shared with your team.`,
value: CalendarChannelVisibility.SHARE_EVERYTHING,
cardMedia: <StyledCardMedia subject="active" body="active" />,
cardMedia: (
<StyledCardMediaContainer>
<SettingsAccountsVisibilityIcon subject="active" body="active" />
</StyledCardMediaContainer>
),
},
{
title: msg`Metadata`,
description: msg`Only date & participants will be shared with your team.`,
value: CalendarChannelVisibility.METADATA,
cardMedia: <StyledCardMedia subject="active" body="inactive" />,
cardMedia: (
<StyledCardMediaContainer>
<SettingsAccountsVisibilityIcon subject="active" body="inactive" />
</StyledCardMediaContainer>
),
},
];
@@ -19,11 +19,13 @@ const StyledTableRows = styled.div`
padding-top: ${themeCssVariables.spacing[2]};
`;
const StyledAddAccountSection = styled(Section)`
border-top: 1px solid ${themeCssVariables.border.color.light};
display: flex;
justify-content: flex-end;
padding-top: ${themeCssVariables.spacing[2]};
const StyledAddAccountSectionContainer = styled.div`
> * {
border-top: 1px solid ${themeCssVariables.border.color.light};
display: flex;
justify-content: flex-end;
padding-top: ${themeCssVariables.spacing[2]};
}
`;
export const SettingsAccountsConnectedAccountsListCard = ({
@@ -51,15 +53,17 @@ export const SettingsAccountsConnectedAccountsListCard = ({
))}
</StyledTableRows>
</Table>
<StyledAddAccountSection>
<Button
Icon={IconPlus}
title={t`Add account`}
variant="secondary"
size="small"
onClick={() => navigateSettings(SettingsPath.NewAccount)}
/>
</StyledAddAccountSection>
<StyledAddAccountSectionContainer>
<Section>
<Button
Icon={IconPlus}
title={t`Add account`}
variant="secondary"
size="small"
onClick={() => navigateSettings(SettingsPath.NewAccount)}
/>
</Section>
</StyledAddAccountSectionContainer>
</Section>
);
};
@@ -1,6 +1,5 @@
import { styled } from '@linaria/react';
import { SettingsAccountsCardMedia } from '@/settings/accounts/components/SettingsAccountsCardMedia';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type SettingsAccountsMessageAutoCreationIconProps = {
@@ -9,8 +8,18 @@ type SettingsAccountsMessageAutoCreationIconProps = {
isReceivedActive?: boolean;
};
const StyledIconContainer = styled(SettingsAccountsCardMedia)`
const StyledIconContainer = styled.div`
align-items: stretch;
border: 2px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.sm};
color: ${themeCssVariables.font.color.light};
display: flex;
flex-direction: column;
gap: ${themeCssVariables.spacing['0.5']};
height: ${themeCssVariables.spacing[8]};
justify-content: center;
padding: ${themeCssVariables.spacing['0.5']};
width: ${themeCssVariables.spacing[6]};
`;
const StyledDirectionSkeleton = styled.div<{ isActive?: boolean }>`
@@ -1,7 +1,6 @@
import { styled } from '@linaria/react';
import { MessageFolderImportPolicy } from '@/accounts/types/MessageChannel';
import { SettingsAccountsCardMedia } from '@/settings/accounts/components/SettingsAccountsCardMedia';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type SettingsAccountsMessageFolderIconProps = {
@@ -9,9 +8,18 @@ type SettingsAccountsMessageFolderIconProps = {
value?: MessageFolderImportPolicy;
};
const StyledCardMedia = styled(SettingsAccountsCardMedia)`
const StyledCardMedia = styled.div`
align-items: stretch;
border: 2px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.sm};
color: ${themeCssVariables.font.color.light};
display: flex;
flex-direction: column;
gap: ${themeCssVariables.spacing['0.5']};
height: ${themeCssVariables.spacing[8]};
justify-content: center;
padding: ${themeCssVariables.spacing['0.5']};
width: ${themeCssVariables.spacing[6]};
`;
const StyledFolderRow = styled.div`
@@ -14,11 +14,13 @@ type SettingsAccountsRadioSettingsCardProps<Option extends { value: string }> =
name: string;
};
const StyledCardContent = styled(CardContent)`
cursor: pointer;
const StyledCardContentContainer = styled.div`
> * {
cursor: pointer;
&:hover {
background: ${themeCssVariables.background.transparent.lighter};
&:hover {
background: ${themeCssVariables.background.transparent.lighter};
}
}
`;
@@ -39,8 +41,10 @@ const StyledDescription = styled.div`
font-size: ${themeCssVariables.font.size.sm};
`;
const StyledRadio = styled(Radio)`
const StyledRadioContainer = styled.span`
margin-left: auto;
display: flex;
align-items: center;
`;
const StyledExpandedContent = styled.div`
@@ -63,34 +67,37 @@ export const SettingsAccountsRadioSettingsCard = <
}: SettingsAccountsRadioSettingsCardProps<Option>) => (
<Card rounded>
{options.map((option, index) => (
<StyledCardContent
key={option.value}
divider={index < options.length - 1}
onClick={() => onChange(option.value)}
>
<StyledOptionHeader>
{option.cardMedia}
<div>
<StyledTitle>
<Trans id={option.title.id} />
</StyledTitle>
<StyledDescription>
<Trans id={option.description.id} />
</StyledDescription>
</div>
<StyledRadio
name={name}
value={option.value}
onCheckedChange={() => onChange(option.value)}
checked={value === option.value}
/>
</StyledOptionHeader>
{option.cardContentExpanded && value === option.value && (
<StyledExpandedContent>
{option.cardContentExpanded}
</StyledExpandedContent>
)}
</StyledCardContent>
<StyledCardContentContainer key={option.value}>
<CardContent
divider={index < options.length - 1}
onClick={() => onChange(option.value)}
>
<StyledOptionHeader>
{option.cardMedia}
<div>
<StyledTitle>
<Trans id={option.title.id} />
</StyledTitle>
<StyledDescription>
<Trans id={option.description.id} />
</StyledDescription>
</div>
<StyledRadioContainer>
<Radio
name={name}
value={option.value}
onCheckedChange={() => onChange(option.value)}
checked={value === option.value}
/>
</StyledRadioContainer>
</StyledOptionHeader>
{option.cardContentExpanded && value === option.value && (
<StyledExpandedContent>
{option.cardContentExpanded}
</StyledExpandedContent>
)}
</CardContent>
</StyledCardContentContainer>
))}
</Card>
);
@@ -14,14 +14,16 @@ type SettingsAccountsToggleSettingCardProps = {
parameters: Parameter[];
};
const StyledCardContent = styled(CardContent)`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[4]};
cursor: pointer;
const StyledCardContentContainer = styled.div`
> * {
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[4]};
cursor: pointer;
&:hover {
background: ${themeCssVariables.background.transparent.lighter};
&:hover {
background: ${themeCssVariables.background.transparent.lighter};
}
}
`;
@@ -36,8 +38,10 @@ const StyledDescription = styled.div`
font-size: ${themeCssVariables.font.size.sm};
`;
const StyledToggle = styled(Toggle)`
const StyledToggleContainer = styled.span`
margin-left: auto;
display: flex;
align-items: center;
`;
export const SettingsAccountsToggleSettingCard = ({
@@ -45,17 +49,20 @@ export const SettingsAccountsToggleSettingCard = ({
}: SettingsAccountsToggleSettingCardProps) => (
<Card rounded>
{parameters.map((parameter, index) => (
<StyledCardContent
key={index}
divider={index < parameters.length - 1}
onClick={() => parameter.onToggle(!parameter.value)}
>
<div>
<StyledTitle>{parameter.title}</StyledTitle>
<StyledDescription>{parameter.description}</StyledDescription>
</div>
<StyledToggle value={parameter.value} onChange={parameter.onToggle} />
</StyledCardContent>
<StyledCardContentContainer key={index}>
<CardContent
divider={index < parameters.length - 1}
onClick={() => parameter.onToggle(!parameter.value)}
>
<div>
<StyledTitle>{parameter.title}</StyledTitle>
<StyledDescription>{parameter.description}</StyledDescription>
</div>
<StyledToggleContainer>
<Toggle value={parameter.value} onChange={parameter.onToggle} />
</StyledToggleContainer>
</CardContent>
</StyledCardContentContainer>
))}
</Card>
);
@@ -1,6 +1,5 @@
import { styled } from '@linaria/react';
import { SettingsAccountsCardMedia } from '@/settings/accounts/components/SettingsAccountsCardMedia';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type VisibilityElementState = 'active' | 'inactive';
@@ -12,8 +11,18 @@ type SettingsAccountsVisibilityIconProps = {
body?: VisibilityElementState;
};
const StyledCardMedia = styled(SettingsAccountsCardMedia)`
const StyledCardMedia = styled.div`
align-items: stretch;
border: 2px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.sm};
color: ${themeCssVariables.font.color.light};
display: flex;
flex-direction: column;
gap: ${themeCssVariables.spacing['0.5']};
height: ${themeCssVariables.spacing[8]};
justify-content: center;
padding: ${themeCssVariables.spacing['0.5']};
width: ${themeCssVariables.spacing[6]};
`;
const StyledSubjectSkeleton = styled.div<{ isActive?: boolean }>`
@@ -25,13 +34,24 @@ const StyledSubjectSkeleton = styled.div<{ isActive?: boolean }>`
height: 3px;
`;
const StyledMetadataSkeleton = styled(StyledSubjectSkeleton)`
const StyledMetadataSkeleton = styled.div<{ isActive?: boolean }>`
background-color: ${({ isActive }) =>
isActive
? themeCssVariables.accent.accent4060
: themeCssVariables.background.quaternary};
border-radius: 1px;
height: 3px;
margin-right: ${themeCssVariables.spacing[2]};
`;
const StyledBodySkeleton = styled(StyledSubjectSkeleton)`
const StyledBodySkeleton = styled.div<{ isActive?: boolean }>`
background-color: ${({ isActive }) =>
isActive
? themeCssVariables.accent.accent4060
: themeCssVariables.background.quaternary};
border-radius: ${themeCssVariables.border.radius.xs};
flex: 1 0 auto;
height: 3px;
`;
export const SettingsAccountsVisibilityIcon = ({
@@ -1,24 +1,24 @@
import { Table } from '@/ui/layout/table/components/Table';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { styled } from '@linaria/react';
import { Trans } from '@lingui/react/macro';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledTableHeader = styled(TableHeader)`
padding-right: ${themeCssVariables.spacing[14]};
`;
export const SettingsConnectedAccountsTableHeader = () => {
return (
<Table>
<TableRow gridAutoColumns="332px 1fr">
<StyledTableHeader>
<TableHeader
padding={`0 ${themeCssVariables.spacing[14]} 0 ${themeCssVariables.spacing[2]}`}
>
<Trans>Account</Trans>
</StyledTableHeader>
<StyledTableHeader align="right">
</TableHeader>
<TableHeader
align="right"
padding={`0 ${themeCssVariables.spacing[14]} 0 ${themeCssVariables.spacing[2]}`}
>
<Trans>Status</Trans>
</StyledTableHeader>
</TableHeader>
</TableRow>
</Table>
);
@@ -37,18 +37,11 @@ const StyledFoldersContainer = styled.div`
padding-top: ${themeCssVariables.spacing[2]};
`;
const StyledSearchInput = styled(SettingsTextInput)`
const StyledSearchInputContainer = styled.div`
margin-bottom: ${themeCssVariables.spacing[2]};
width: 100%;
`;
const StyledCheckboxCell = styled(TableCell)`
align-items: center;
display: flex;
padding-right: ${themeCssVariables.spacing[1]};
justify-content: flex-end;
`;
const StyledSectionHeader = styled.div`
align-items: center;
background-color: ${themeCssVariables.background.transparent.lighter};
@@ -61,8 +54,10 @@ const StyledSectionHeader = styled.div`
text-align: left;
`;
const StyledLabel = styled(Label)`
const StyledLabelContainer = styled.span`
align-items: center;
color: ${themeCssVariables.font.color.tertiary};
display: flex;
margin-bottom: ${themeCssVariables.spacing[2]};
margin-top: ${themeCssVariables.spacing[2]};
`;
@@ -165,23 +160,30 @@ export const SettingsAccountsMessageFoldersCard = () => {
return (
<Section>
<Table>
<StyledSearchInput
placeholder={t`Search folders...`}
value={search}
onChange={setSearch}
instanceId={'message-folders-search'}
/>
<StyledLabel>{t`Folders`}</StyledLabel>
<StyledSearchInputContainer>
<SettingsTextInput
placeholder={t`Search folders...`}
value={search}
onChange={setSearch}
instanceId={'message-folders-search'}
/>
</StyledSearchInputContainer>
<StyledLabelContainer>
<Label>{t`Folders`}</Label>
</StyledLabelContainer>
<StyledSectionHeader>
<Label>{t`Toggle all folders`}</Label>
<StyledCheckboxCell>
<TableCell
align="right"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
>
<Checkbox
checked={allFoldersToggled}
onChange={() => handleToggleAllFolders(messageFolders)}
size={CheckboxSize.Small}
/>
</StyledCheckboxCell>
</TableCell>
</StyledSectionHeader>
<StyledFoldersContainer>
@@ -37,7 +37,7 @@ const StyledSearchAndFilterContainer = styled.div`
width: 100%;
`;
const StyledSearchInput = styled(SettingsTextInput)`
const StyledSearchInputContainer = styled.div`
flex: 1;
`;
@@ -163,13 +163,15 @@ export const SettingsAdminAI = () => {
/>
<StyledSearchAndFilterContainer>
<StyledSearchInput
instanceId="admin-model-search"
LeftIcon={IconSearch}
placeholder={t`Search a model...`}
value={searchQuery}
onChange={setSearchQuery}
/>
<StyledSearchInputContainer>
<SettingsTextInput
instanceId="admin-model-search"
LeftIcon={IconSearch}
placeholder={t`Search a model...`}
value={searchQuery}
onChange={setSearchQuery}
/>
</StyledSearchInputContainer>
<Dropdown
dropdownId="admin-ai-models-filter-dropdown"
dropdownPlacement="bottom-end"
@@ -3,7 +3,7 @@ import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { IconSearch } from 'twenty-ui/display';
const StyledSearchInput = styled(SettingsTextInput)`
const StyledSearchInputContainer = styled.div`
width: 100%;
`;
@@ -17,13 +17,15 @@ export const ConfigVariableSearchInput = ({
onChange,
}: ConfigVariableSearchInputProps) => {
return (
<StyledSearchInput
instanceId="config-variable-search"
placeholder={t`Search config variables`}
value={value}
onChange={onChange}
autoFocus={false}
LeftIcon={IconSearch}
/>
<StyledSearchInputContainer>
<SettingsTextInput
instanceId="config-variable-search"
placeholder={t`Search config variables`}
value={value}
onChange={onChange}
autoFocus={false}
LeftIcon={IconSearch}
/>
</StyledSearchInputContainer>
);
};
@@ -12,16 +12,11 @@ type SettingsAdminConfigVariablesRowProps = {
variable: ConfigVariable;
};
const StyledTruncatedCell = styled(TableCell)`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
`;
const StyledTableRow = styled(TableRow)`
&:hover {
background-color: ${themeCssVariables.background.transparent.light};
const StyledTableRowContainer = styled.div`
> * {
&:hover {
background-color: ${themeCssVariables.background.transparent.light};
}
}
`;
@@ -48,24 +43,38 @@ export const SettingsAdminConfigVariablesRow = ({
: variable.value;
return (
<StyledTableRow
gridAutoColumns="5fr 3fr 1fr"
to={getSettingsPath(SettingsPath.AdminPanelConfigVariableDetails, {
variableName: variable.name,
})}
>
<StyledTruncatedCell color={theme.font.color.primary}>
<StyledEllipsisLabel>{variable.name}</StyledEllipsisLabel>
</StyledTruncatedCell>
<StyledTruncatedCell align="right">
<StyledEllipsisLabel>{displayValue}</StyledEllipsisLabel>
</StyledTruncatedCell>
<TableCell align="right">
<IconChevronRight
size={theme.icon.size.md}
color={theme.font.color.tertiary}
/>
</TableCell>
</StyledTableRow>
<StyledTableRowContainer>
<TableRow
gridAutoColumns="5fr 3fr 1fr"
to={getSettingsPath(SettingsPath.AdminPanelConfigVariableDetails, {
variableName: variable.name,
})}
>
<TableCell
color={theme.font.color.primary}
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
clickable
>
<StyledEllipsisLabel>{variable.name}</StyledEllipsisLabel>
</TableCell>
<TableCell
align="right"
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
clickable
>
<StyledEllipsisLabel>{displayValue}</StyledEllipsisLabel>
</TableCell>
<TableCell align="right">
<IconChevronRight
size={theme.icon.size.md}
color={theme.font.color.tertiary}
/>
</TableCell>
</TableRow>
</StyledTableRowContainer>
);
};
@@ -8,7 +8,7 @@ import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { type ConfigVariable } from '~/generated-metadata/graphql';
const StyledTableBody = styled(TableBody)`
const StyledTableBodyContainer = styled.div`
border-bottom: 1px solid ${themeCssVariables.border.color.light};
`;
@@ -26,14 +26,16 @@ export const SettingsAdminConfigVariablesTable = ({
<TableHeader align="right">{t`Value`}</TableHeader>
<TableHeader align="right"></TableHeader>
</TableRow>
<StyledTableBody>
{variables.map((variable) => (
<SettingsAdminConfigVariablesRow
key={variable.name}
variable={variable}
/>
))}
</StyledTableBody>
<StyledTableBodyContainer>
<TableBody>
{variables.map((variable) => (
<SettingsAdminConfigVariablesRow
key={variable.name}
variable={variable}
/>
))}
</TableBody>
</StyledTableBodyContainer>
</Table>
);
};
@@ -5,9 +5,11 @@ import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledSettingsAdminTableCard = styled(SettingsAdminTableCard)`
padding-left: ${themeCssVariables.spacing[2]};
padding-right: ${themeCssVariables.spacing[2]};
const StyledSettingsAdminTableCardContainer = styled.div`
> * {
padding-left: ${themeCssVariables.spacing[2]};
padding-right: ${themeCssVariables.spacing[2]};
}
`;
export const SettingsAdminHealthAccountSyncCountersTable = ({
@@ -47,13 +49,15 @@ export const SettingsAdminHealthAccountSyncCountersTable = ({
return (
<Section>
<H2Title title={title} description={description} />
<StyledSettingsAdminTableCard
items={items}
rounded
gridAutoColumns="1fr 1fr"
labelAlign="left"
valueAlign="right"
/>
<StyledSettingsAdminTableCardContainer>
<SettingsAdminTableCard
items={items}
rounded
gridAutoColumns="1fr 1fr"
labelAlign="left"
valueAlign="right"
/>
</StyledSettingsAdminTableCardContainer>
</Section>
);
};
@@ -29,9 +29,11 @@ const StyledNoDataMessage = styled.div`
justify-content: center;
`;
const StyledSettingsAdminTableCard = styled(SettingsAdminTableCard)`
padding-left: ${themeCssVariables.spacing[2]};
padding-right: ${themeCssVariables.spacing[2]};
const StyledSettingsAdminTableCardContainer = styled.div`
> * {
padding-left: ${themeCssVariables.spacing[2]};
padding-right: ${themeCssVariables.spacing[2]};
}
`;
type SettingsAdminWorkerMetricsGraphProps = {
@@ -200,23 +202,25 @@ export const SettingsAdminWorkerMetricsGraph = ({
)}
</StyledGraphContainer>
{metricsDetails && (
<StyledSettingsAdminTableCard
rounded
items={Object.entries(metricsDetails)
.filter(([key]) => key !== '__typename')
.map(([key, value]) => ({
label: key.charAt(0).toUpperCase() + key.slice(1),
value:
typeof value === 'number'
? value
: Array.isArray(value)
? value.length
: String(value),
}))}
gridAutoColumns="1fr 1fr"
labelAlign="left"
valueAlign="right"
/>
<StyledSettingsAdminTableCardContainer>
<SettingsAdminTableCard
rounded
items={Object.entries(metricsDetails)
.filter(([key]) => key !== '__typename')
.map(([key, value]) => ({
label: key.charAt(0).toUpperCase() + key.slice(1),
value:
typeof value === 'number'
? value
: Array.isArray(value)
? value.length
: String(value),
}))}
gridAutoColumns="1fr 1fr"
labelAlign="left"
valueAlign="right"
/>
</StyledSettingsAdminTableCardContainer>
)}
</>
);
@@ -26,8 +26,10 @@ const StyledDotContainer = styled.div<{ dotPosition: DotPosition }>`
dotPosition === 'top' ? 'stretch' : 'center'};
`;
const StyledIconPoint = styled(IconPoint)`
const StyledIconPointContainer = styled.span`
margin-right: 0;
display: flex;
align-items: center;
`;
export const AdvancedSettingsContentWrapperWithDot = ({
@@ -41,11 +43,13 @@ export const AdvancedSettingsContentWrapperWithDot = ({
<StyledWrapper>
{!hideDot && (
<StyledDotContainer dotPosition={dotPosition}>
<StyledIconPoint
size={12}
color={theme.color.yellow}
fill={theme.color.yellow}
/>
<StyledIconPointContainer>
<IconPoint
size={12}
color={theme.color.yellow}
fill={theme.color.yellow}
/>
</StyledIconPointContainer>
</StyledDotContainer>
)}
{children}
@@ -18,28 +18,34 @@ type SettingsCardProps = {
Status?: ReactNode;
};
const StyledCard = styled(Card)<{
const StyledCardWrapper = styled.div<{
disabled?: boolean;
onClick?: () => void;
clickable?: boolean;
}>`
color: ${({ disabled }) =>
disabled
? themeCssVariables.font.color.extraLight
: themeCssVariables.font.color.tertiary};
cursor: ${({ disabled, onClick }) =>
disabled ? 'not-allowed' : onClick ? 'pointer' : 'default'};
cursor: ${({ disabled, clickable }) =>
disabled ? 'not-allowed' : clickable ? 'pointer' : 'default'};
width: 100%;
> * {
color: inherit;
}
`;
const StyledCardContent = styled(CardContent)`
display: flex;
flex-direction: column;
gap: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]};
const StyledCardContentContainer = styled.div`
> * {
display: flex;
flex-direction: column;
gap: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]};
&:hover {
background-color: ${themeCssVariables.background.quaternary};
cursor: pointer;
&:hover {
background-color: ${themeCssVariables.background.quaternary};
cursor: pointer;
}
}
`;
@@ -61,8 +67,10 @@ const StyledTitle = styled.div<{ disabled?: boolean }>`
justify-content: flex-start;
`;
const StyledIconChevronRight = styled(IconChevronRight)`
const StyledIconChevronRightContainer = styled.span`
align-items: center;
color: ${themeCssVariables.font.color.light};
display: flex;
`;
const StyledDescription = styled.div`
@@ -91,24 +99,31 @@ export const SettingsCard = ({
const { theme } = useContext(ThemeContext);
return (
<StyledCard
<StyledCardWrapper
disabled={disabled}
onClick={disabled ? undefined : onClick}
clickable={!!onClick}
className={className}
rounded={true}
>
<StyledCardContent>
<StyledHeader>
<StyledIconContainer>{Icon}</StyledIconContainer>
<StyledTitle disabled={disabled}>
{title}
{soon && <Pill label={t`Soon`} />}
</StyledTitle>
{Status && Status}
<StyledIconChevronRight size={theme.icon.size.sm} />
</StyledHeader>
{description && <StyledDescription>{description}</StyledDescription>}
</StyledCardContent>
</StyledCard>
<Card onClick={disabled ? undefined : onClick} rounded={true} fullWidth>
<StyledCardContentContainer>
<CardContent>
<StyledHeader>
<StyledIconContainer>{Icon}</StyledIconContainer>
<StyledTitle disabled={disabled}>
{title}
{soon && <Pill label={t`Soon`} />}
</StyledTitle>
{Status && Status}
<StyledIconChevronRightContainer>
<IconChevronRight size={theme.icon.size.sm} />
</StyledIconChevronRightContainer>
</StyledHeader>
{description && (
<StyledDescription>{description}</StyledDescription>
)}
</CardContent>
</StyledCardContentContainer>
</Card>
</StyledCardWrapper>
);
};
@@ -14,10 +14,12 @@ const StyledNameCell = styled.div`
gap: ${themeCssVariables.spacing[1]};
`;
const StyledTableRow = styled(TableRow)`
&:hover {
background: ${themeCssVariables.background.transparent.light};
cursor: pointer;
const StyledTableRowContainer = styled.div`
> * {
&:hover {
background-color: ${themeCssVariables.background.transparent.light};
cursor: pointer;
}
}
`;
@@ -32,19 +34,23 @@ export const SettingsConnectedAccountsTableRow = ({
const IconComponent = SettingsConnectedAccountIcon({ account });
return (
<StyledTableRow key={account.id} gridAutoColumns="332px 1fr">
<TableCell>
<StyledNameCell>
<IconComponent
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
<StyledTableRowContainer>
<TableRow key={account.id} gridAutoColumns="332px 1fr">
<TableCell>
<StyledNameCell>
<IconComponent
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
{account.handle}
</StyledNameCell>
</TableCell>
<TableCell align="right">
<SettingsAccountsConnectedAccountsRowRightContainer
account={account}
/>
{account.handle}
</StyledNameCell>
</TableCell>
<TableCell align="right">
<SettingsAccountsConnectedAccountsRowRightContainer account={account} />
</TableCell>
</StyledTableRow>
</TableCell>
</TableRow>
</StyledTableRowContainer>
);
};
@@ -25,9 +25,10 @@ const StyledCounterContainer = styled.div<{ showButtons: boolean }>`
: themeCssVariables.spacing[16]};
`;
const StyledTextInput = styled(SettingsTextInput)`
const StyledTextInputContainer = styled.div`
width: ${themeCssVariables.spacing[16]};
input {
> * input {
width: ${themeCssVariables.spacing[16]};
height: ${themeCssVariables.spacing[6]};
text-align: center;
@@ -84,14 +85,16 @@ export const SettingsCounter = ({
disabled={disabled}
/>
)}
<StyledTextInput
instanceId="settings-counter-input"
name="counter"
fullWidth
value={value.toString()}
onChange={handleTextInputChange}
disabled={disabled}
/>
<StyledTextInputContainer>
<SettingsTextInput
instanceId="settings-counter-input"
name="counter"
fullWidth
value={value.toString()}
onChange={handleTextInputChange}
disabled={disabled}
/>
</StyledTextInputContainer>
{showButtons && (
<IconButton
size="small"
@@ -28,15 +28,16 @@ type SettingsDnsRecordsTableProps = {
records: DnsRecord[];
};
const StyledTableRow = styled(TableRow)`
& > * {
const StyledTableRowContainer = styled.div`
> * > * {
min-width: 0;
max-width: 100%;
overflow: hidden;
}
`;
const StyledTableCell = styled(TableCell)`
const StyledTableCellFontWrapper = styled.div`
display: contents;
font-family: monospace;
`;
@@ -69,53 +70,69 @@ export const SettingsDnsRecordsTable = ({
return (
<Table>
<StyledTableRow gridAutoColumns={gridAutoColumns}>
<TableHeader align="center">{t`Type`}</TableHeader>
<TableHeader align="center">{t`Key`}</TableHeader>
<TableHeader align="center">{t`Value`}</TableHeader>
{hasPriorityRecords && (
<TableHeader align="center">{t`Priority`}</TableHeader>
)}
{hasTtlRecords && <TableHeader align="center">{t`TTL`}</TableHeader>}
{hasStatusRecords && (
<TableHeader align="center">{t`Status`}</TableHeader>
)}
</StyledTableRow>
<StyledTableRowContainer>
<TableRow gridAutoColumns={gridAutoColumns}>
<TableHeader align="center">{t`Type`}</TableHeader>
<TableHeader align="center">{t`Key`}</TableHeader>
<TableHeader align="center">{t`Value`}</TableHeader>
{hasPriorityRecords && (
<TableHeader align="center">{t`Priority`}</TableHeader>
)}
{hasTtlRecords && <TableHeader align="center">{t`TTL`}</TableHeader>}
{hasStatusRecords && (
<TableHeader align="center">{t`Status`}</TableHeader>
)}
</TableRow>
</StyledTableRowContainer>
{records.map((record) => (
<StyledTableRow key={record.value} gridAutoColumns={gridAutoColumns}>
<TableCell>{record.type}</TableCell>
<StyledTableCell
onClick={() => {
copyToClipboard(record.key || '');
}}
>
<OverflowingTextWithTooltip text={record.key} />
</StyledTableCell>
<StyledTableRowContainer key={record.value}>
<TableRow gridAutoColumns={gridAutoColumns}>
<TableCell>{record.type}</TableCell>
<StyledTableCellFontWrapper>
<TableCell
onClick={() => {
copyToClipboard(record.key || '');
}}
>
<OverflowingTextWithTooltip text={record.key} />
</TableCell>
</StyledTableCellFontWrapper>
<StyledTableCell
onClick={() => {
copyToClipboard(record.value);
}}
>
<OverflowingTextWithTooltip text={record.value} />
</StyledTableCell>
<StyledTableCellFontWrapper>
<TableCell
onClick={() => {
copyToClipboard(record.value);
}}
>
<OverflowingTextWithTooltip text={record.value} />
</TableCell>
</StyledTableCellFontWrapper>
{hasPriorityRecords && (
<StyledTableCell>{record.priority}</StyledTableCell>
)}
{hasTtlRecords && <StyledTableCell>{record.ttl}</StyledTableCell>}
{hasStatusRecords && (
<StyledTableCell>
{'status' in record ? (
<Status
color={record.statusColor}
text={capitalize(record.status)}
/>
) : null}
</StyledTableCell>
)}
</StyledTableRow>
{hasPriorityRecords && (
<StyledTableCellFontWrapper>
<TableCell>{record.priority}</TableCell>
</StyledTableCellFontWrapper>
)}
{hasTtlRecords && (
<StyledTableCellFontWrapper>
<TableCell>{record.ttl}</TableCell>
</StyledTableCellFontWrapper>
)}
{hasStatusRecords && (
<StyledTableCellFontWrapper>
<TableCell>
{'status' in record ? (
<Status
color={record.statusColor}
text={capitalize(record.status)}
/>
) : null}
</TableCell>
</StyledTableCellFontWrapper>
)}
</TableRow>
</StyledTableRowContainer>
))}
</Table>
);
@@ -8,10 +8,12 @@ import { Card, CardFooter } from 'twenty-ui/layout';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { SettingsListItemCardContent } from './SettingsListItemCardContent';
const StyledFooter = styled(CardFooter)`
align-items: center;
display: flex;
padding: ${themeCssVariables.spacing[1]};
const StyledFooterContainer = styled.div`
> * {
align-items: center;
display: flex;
padding: ${themeCssVariables.spacing[1]};
}
`;
const StyledButton = styled.button`
@@ -91,12 +93,14 @@ export const SettingsListCard = <
/>
))}
{hasFooter && (
<StyledFooter divider={!!items.length}>
<StyledButton onClick={onFooterButtonClick}>
<IconPlus size={theme.icon.size.md} />
{footerButtonLabel}
</StyledButton>
</StyledFooter>
<StyledFooterContainer>
<CardFooter divider={!!items.length}>
<StyledButton onClick={onFooterButtonClick}>
<IconPlus size={theme.icon.size.md} />
{footerButtonLabel}
</StyledButton>
</CardFooter>
</StyledFooterContainer>
)}
</Card>
);
@@ -6,15 +6,17 @@ import { IconChevronRight, type IconComponent } from 'twenty-ui/display';
import { CardContent } from 'twenty-ui/layout';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledRow = styled(CardContent)`
align-items: center;
display: flex;
font-size: ${themeCssVariables.font.size.sm};
font-weight: ${themeCssVariables.font.weight.medium};
gap: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing[2]};
padding-left: ${themeCssVariables.spacing[3]};
min-height: ${themeCssVariables.spacing[6]};
const StyledRowContainer = styled.div`
> * {
align-items: center;
display: flex;
font-size: ${themeCssVariables.font.size.sm};
font-weight: ${themeCssVariables.font.weight.medium};
gap: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing[2]};
padding-left: ${themeCssVariables.spacing[3]};
min-height: ${themeCssVariables.spacing[6]};
}
`;
const StyledRightContainer = styled.div`
@@ -43,9 +45,11 @@ const StyledDescription = styled.span`
padding-left: ${themeCssVariables.spacing[1]};
`;
const StyledLink = styled(Link)`
color: ${themeCssVariables.font.color.secondary};
text-decoration: none;
const StyledLinkContainer = styled.div`
> a {
color: ${themeCssVariables.font.color.secondary};
text-decoration: none;
}
`;
type SettingsListItemCardContentProps = {
@@ -72,36 +76,44 @@ export const SettingsListItemCardContent = ({
const { theme } = useContext(ThemeContext);
const content = (
<StyledRow
onClick={onClick}
divider={divider}
isClickable={!!onClick || !!to}
hasHoverHighlight={!!to}
>
{!!LeftIcon && (
<LeftIcon
size={theme.icon.size.md}
color={LeftIconColor ?? 'currentColor'}
/>
)}
<StyledContent>
<StyledLabel>{label}</StyledLabel>
{!!description && <StyledDescription>{description}</StyledDescription>}
</StyledContent>
<StyledRightContainer>
{rightComponent}
{!!to && (
<IconChevronRight
<StyledRowContainer>
<CardContent
onClick={onClick}
divider={divider}
isClickable={!!onClick || !!to}
hasHoverHighlight={!!to}
>
{!!LeftIcon && (
<LeftIcon
size={theme.icon.size.md}
color={theme.font.color.tertiary}
color={LeftIconColor ?? 'currentColor'}
/>
)}
</StyledRightContainer>
</StyledRow>
<StyledContent>
<StyledLabel>{label}</StyledLabel>
{!!description && (
<StyledDescription>{description}</StyledDescription>
)}
</StyledContent>
<StyledRightContainer>
{rightComponent}
{!!to && (
<IconChevronRight
size={theme.icon.size.md}
color={theme.font.color.tertiary}
/>
)}
</StyledRightContainer>
</CardContent>
</StyledRowContainer>
);
if (isDefined(to)) {
return <StyledLink to={to}>{content}</StyledLink>;
return (
<StyledLinkContainer>
<Link to={to}>{content}</Link>
</StyledLinkContainer>
);
}
return content;
@@ -2,9 +2,17 @@ import { styled } from '@linaria/react';
import { Card } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledCard = styled(Card)`
background-color: ${themeCssVariables.background.secondary};
const StyledCardContainer = styled.div`
height: 40px;
> * {
background-color: ${themeCssVariables.background.secondary};
height: 100%;
}
`;
export { StyledCard as SettingsListSkeletonCard };
export const SettingsListSkeletonCard = () => (
<StyledCardContainer>
<Card />
</StyledCardContainer>
);
@@ -1,6 +1,5 @@
import { Separator } from '@/settings/components/Separator';
import {
StyledSettingsCardContent,
StyledSettingsCardDescription,
StyledSettingsCardIcon,
StyledSettingsCardTextContainer,
@@ -16,7 +15,12 @@ import {
import { Toggle } from 'twenty-ui/input';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledSettingsCardToggleContent = styled(StyledSettingsCardContent)`
const StyledSettingsCardToggleContent = styled.div<{ disabled?: boolean }>`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[3]};
background-color: ${themeCssVariables.background.secondary};
padding: ${themeCssVariables.spacing[4]};
cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')};
position: relative;
pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')};
@@ -26,7 +30,9 @@ const StyledSettingsCardToggleContent = styled(StyledSettingsCardContent)`
}
`;
const StyledSettingsCardToggleButton = styled(Toggle)`
const StyledSettingsCardToggleButtonContainer = styled.span`
align-items: center;
display: flex;
flex-shrink: 0;
margin-left: auto;
`;
@@ -84,15 +90,17 @@ export const SettingsOptionCardContentToggle = ({
</StyledSettingsCardDescription>
)}
</StyledSettingsCardTextContainer>
<StyledSettingsCardToggleButton
id={toggleId}
value={checked}
onChange={onChange}
disabled={disabled}
toggleSize="small"
color={advancedMode ? theme.color.yellow : theme.color.blue}
centered={toggleCentered}
/>
<StyledSettingsCardToggleButtonContainer>
<Toggle
id={toggleId}
value={checked}
onChange={onChange}
disabled={disabled}
toggleSize="small"
color={advancedMode ? theme.color.yellow : theme.color.blue}
centered={toggleCentered}
/>
</StyledSettingsCardToggleButtonContainer>
</StyledSettingsCardToggleContent>
{divider && <Separator />}
</>
@@ -5,22 +5,26 @@ import { type IconComponent } from 'twenty-ui/display';
import { Radio } from 'twenty-ui/input';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledRadioCardContent = styled(CardContent)`
display: flex;
align-items: center;
padding: ${themeCssVariables.spacing[2]};
border: 1px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.sm};
flex-grow: 1;
gap: ${themeCssVariables.spacing[2]};
cursor: pointer;
const StyledRadioCardContentContainer = styled.div`
> * {
display: flex;
align-items: center;
padding: ${themeCssVariables.spacing[2]};
border: 1px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.sm};
flex-grow: 1;
gap: ${themeCssVariables.spacing[2]};
cursor: pointer;
&:hover {
background: ${themeCssVariables.background.transparent.lighter};
&:hover {
background: ${themeCssVariables.background.transparent.lighter};
}
}
`;
const StyledRadio = styled(Radio)`
const StyledRadioContainer = styled.span`
align-items: center;
display: flex;
margin-left: auto;
padding: ${themeCssVariables.spacing[1]};
`;
@@ -58,13 +62,17 @@ export const SettingsRadioCard = ({
const onClick = () => handleSelect(value);
return (
<StyledRadioCardContent tabIndex={0} onClick={onClick}>
{Icon && <Icon size={theme.icon.size.xl} color={theme.color.gray10} />}
<span>
{title && <StyledTitle>{title}</StyledTitle>}
{description && <StyledDescription>{description}</StyledDescription>}
</span>
<StyledRadio value={value} checked={isSelected} />
</StyledRadioCardContent>
<StyledRadioCardContentContainer>
<CardContent tabIndex={0} onClick={onClick}>
{Icon && <Icon size={theme.icon.size.xl} color={theme.color.gray10} />}
<span>
{title && <StyledTitle>{title}</StyledTitle>}
{description && <StyledDescription>{description}</StyledDescription>}
</span>
<StyledRadioContainer>
<Radio value={value} checked={isSelected} />
</StyledRadioContainer>
</CardContent>
</StyledRadioCardContentContainer>
);
};
@@ -8,12 +8,14 @@ type SettingsSummaryCardProps = {
rightComponent: ReactNode;
};
const StyledCardContent = styled(CardContent)`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing[2]};
min-height: ${themeCssVariables.spacing[6]};
const StyledCardContentContainer = styled.div`
> * {
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing[2]};
min-height: ${themeCssVariables.spacing[6]};
}
`;
const StyledTitle = styled.div`
@@ -29,9 +31,11 @@ export const SettingsSummaryCard = ({
rightComponent,
}: SettingsSummaryCardProps) => (
<Card>
<StyledCardContent>
<StyledTitle>{title}</StyledTitle>
{rightComponent}
</StyledCardContent>
<StyledCardContentContainer>
<CardContent>
<StyledTitle>{title}</StyledTitle>
{rightComponent}
</CardContent>
</StyledCardContentContainer>
</Card>
);
@@ -28,8 +28,10 @@ const StyledButtonContainer = styled.div`
width: 100%;
`;
const StyledDownChevron = styled(IconChevronDown)`
const StyledDownChevronContainer = styled.span`
align-items: center;
color: ${themeCssVariables.font.color.primary};
display: flex;
position: absolute;
right: ${themeCssVariables.spacing['1.5']};
top: 50%;
@@ -45,9 +47,11 @@ const StyledSpan = styled.span`
margin-left: ${themeCssVariables.spacing[2]};
`;
const StyledButton = styled(Button)`
color: ${themeCssVariables.font.color.primary};
padding-right: ${themeCssVariables.spacing[6]};
const StyledButtonWrapper = styled.div`
> button {
color: ${themeCssVariables.font.color.primary};
padding-right: ${themeCssVariables.spacing[6]};
}
`;
export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
@@ -87,11 +91,17 @@ export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
dropdownId={dropdownId}
clickableComponent={
<StyledButtonContainer>
<StyledDownChevron size={theme.icon.size.md} />
<StyledDownChevronContainer>
<IconChevronDown size={theme.icon.size.md} />
</StyledDownChevronContainer>
{isConfigureStep ? (
<StyledButton variant="tertiary" title={t`2. Configure`} />
<StyledButtonWrapper>
<Button variant="tertiary" title={t`2. Configure`} />
</StyledButtonWrapper>
) : (
<StyledButton variant="tertiary" title={t`1. Type`} />
<StyledButtonWrapper>
<Button variant="tertiary" title={t`1. Type`} />
</StyledButtonWrapper>
)}
</StyledButtonContainer>
}
@@ -13,12 +13,16 @@ type SettingsDataModelPreviewFormCardProps = {
disabled?: boolean;
};
const StyledPreviewContainer = styled(CardContent)`
background-color: ${themeCssVariables.background.transparent.lighter};
const StyledPreviewContainerWrapper = styled.div`
> * {
background-color: ${themeCssVariables.background.transparent.lighter};
}
`;
const StyledFormContainer = styled(CardContent)`
padding: 0;
const StyledFormContainerWrapper = styled.div`
> * {
padding: 0;
}
`;
export const SettingsDataModelPreviewFormCard = ({
@@ -27,12 +31,18 @@ export const SettingsDataModelPreviewFormCard = ({
form,
}: SettingsDataModelPreviewFormCardProps) => (
<Card className={className} fullWidth rounded>
<StyledPreviewContainer divider={!!form}>
<StyledFormCardTitle>
<Trans>Preview</Trans>
</StyledFormCardTitle>
{preview}
</StyledPreviewContainer>
{!!form && <StyledFormContainer>{form}</StyledFormContainer>}
<StyledPreviewContainerWrapper>
<CardContent divider={!!form}>
<StyledFormCardTitle>
<Trans>Preview</Trans>
</StyledFormCardTitle>
{preview}
</CardContent>
</StyledPreviewContainerWrapper>
{!!form && (
<StyledFormContainerWrapper>
<CardContent>{form}</CardContent>
</StyledFormContainerWrapper>
)}
</Card>
);
@@ -56,7 +56,7 @@ const StyledCardContainer = styled.div`
width: calc(50% - ${themeCssVariables.spacing[1]});
`;
const StyledSearchInput = styled(SettingsTextInput)`
const StyledSearchInputContainer = styled.div`
width: 100%;
`;
@@ -108,13 +108,15 @@ export const SettingsObjectNewFieldSelector = ({
<>
{' '}
<Section>
<StyledSearchInput
instanceId="new-field-type-search"
LeftIcon={IconSearch}
placeholder={t`Search a type`}
value={searchQuery}
onChange={setSearchQuery}
/>
<StyledSearchInputContainer>
<SettingsTextInput
instanceId="new-field-type-search"
LeftIcon={IconSearch}
placeholder={t`Search a type`}
value={searchQuery}
onChange={setSearchQuery}
/>
</StyledSearchInputContainer>
</Section>
<Controller
name="type"
@@ -33,7 +33,7 @@ export const settingsDataModelFieldDateFormSchema = z.object({
settings: fieldDateSettings.optional(),
});
const StyledTextInput = styled(SettingsTextInput)`
const StyledTextInputContainer = styled.div`
padding: ${themeCssVariables.spacing[4]};
padding-top: 0;
`;
@@ -117,14 +117,16 @@ export const SettingsDataModelFieldDateForm = ({
control={control}
defaultValue={initialCustomUnicodeDateFormat}
render={({ field: { onChange, value } }) => (
<StyledTextInput
instanceId="custom-date-format-input"
placeholder={t`Format e.g. d-MMM-y (qqq''yy)`}
value={value}
onChange={(value) => onChange(value)}
disabled={false}
fullWidth
/>
<StyledTextInputContainer>
<SettingsTextInput
instanceId="custom-date-format-input"
placeholder={t`Format e.g. d-MMM-y (qqq''yy)`}
value={value}
onChange={(value) => onChange(value)}
disabled={false}
fullWidth
/>
</StyledTextInputContainer>
)}
/>
</AnimatedExpandableContainer>
@@ -67,8 +67,10 @@ type SettingsDataModelFieldSelectFormProps = {
disabled?: boolean;
};
const StyledContainer = styled(CardContent)`
padding-bottom: 14px;
const StyledContainerWrapper = styled.div`
> * {
padding-bottom: 14px;
}
`;
const StyledOptionsLabel = styled.div<{
@@ -119,18 +121,24 @@ const StyledIconContainer = styled.div`
margin-top: ${themeCssVariables.spacing[1]};
`;
const StyledIconPoint = styled(IconPoint)`
const StyledIconPointContainer = styled.span`
margin-right: ${themeCssVariables.spacing['0.5']};
display: flex;
align-items: center;
`;
const StyledFooter = styled(CardFooter)`
background-color: ${themeCssVariables.background.secondary};
padding: ${themeCssVariables.spacing[1]};
const StyledFooterContainer = styled.div`
> * {
background-color: ${themeCssVariables.background.secondary};
padding: ${themeCssVariables.spacing[1]};
}
`;
const StyledButton = styled(LightButton)`
justify-content: center;
width: 100%;
const StyledButtonContainer = styled.div`
> button {
justify-content: center;
width: 100%;
}
`;
const StyledOptionsHeaderContainer = styled.div`
@@ -316,199 +324,212 @@ export const SettingsDataModelFieldSelectForm = ({
defaultValue={initialOptions}
render={({ field: { onChange, value: options } }) => (
<>
<StyledContainer>
<StyledOptionsHeaderContainer>
<StyledLabelContainer>
{!isBulkInputMode && (
<AdvancedSettingsWrapper animationDimension="width" hideDot>
<StyledApiKeyContainer>
<StyledIconContainer>
<StyledIconPoint
size={12}
color={theme.color.yellow}
fill={theme.color.yellow}
/>
</StyledIconContainer>
<StyledApiKey>{t`API values`}</StyledApiKey>
</StyledApiKeyContainer>
</AdvancedSettingsWrapper>
)}
<StyledOptionsLabel
isAdvancedModeEnabled={isAdvancedModeEnabled}
isBulkInputMode={isBulkInputMode}
>
{t`Options`}
</StyledOptionsLabel>
</StyledLabelContainer>
{!disabled && (
<Dropdown
dropdownId={OPTIONS_DROPDOWN_ID}
clickableComponent={
<LightIconButton
Icon={IconDotsVertical}
accent="tertiary"
/>
}
dropdownComponents={
<DropdownContent
widthInPixels={GenericDropdownContentWidth.Narrow}
<StyledContainerWrapper>
<CardContent>
<StyledOptionsHeaderContainer>
<StyledLabelContainer>
{!isBulkInputMode && (
<AdvancedSettingsWrapper
animationDimension="width"
hideDot
>
<DropdownMenuItemsContainer>
<MenuItem
text={
isBulkInputMode ? t`Single edit` : t`Bulk edit`
}
LeftIcon={IconPencil}
onClick={() => {
if (!isBulkInputMode) {
setBulkInputText(
convertOptionsToBulkText(options),
);
}
setIsBulkInputMode(
(currentInputMode) => !currentInputMode,
);
closeOptionsDropdown(OPTIONS_DROPDOWN_ID);
}}
/>
<MenuItem
text={t`Remove all`}
accent="danger"
LeftIcon={IconTrash}
onClick={() => {
onChange([]);
closeOptionsDropdown(OPTIONS_DROPDOWN_ID);
}}
/>
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
)}
</StyledOptionsHeaderContainer>
{isBulkInputMode ? (
<StyledTextAreaContainer>
<TextArea
textAreaId="bulk-options-input"
placeholder={t`Enter one option per line`}
value={bulkInputText}
onChange={(nextOptionAsText) => {
if (disabled) {
return;
}
const nextOptions = convertBulkTextToOptions(
nextOptionAsText,
options,
);
onChange(nextOptions);
setBulkInputText(nextOptionAsText);
}}
minRows={5}
maxRows={15}
disabled={disabled}
/>
<StyledHelpText>
{t`Enter one option per line. Each line will become a new option.`}
</StyledHelpText>
</StyledTextAreaContainer>
) : (
<>
<DraggableList
onDragEnd={(result) =>
!disabled
? handleDragEnd(options, result, onChange)
: undefined
}
draggableItems={
<>
{options.map((option, index) => (
<DraggableItem
isInsideScrollableContainer
key={option.id}
draggableId={option.id}
index={index}
isDragDisabled={options.length === 1}
itemComponent={
<SettingsDataModelFieldSelectFormOptionRow
key={option.id}
option={option}
isNewRow={index === options.length - 1}
onChange={(nextOption) => {
if (disabled) {
return;
}
const nextOptions = toSpliced(
options,
index,
1,
nextOption,
);
onChange(nextOptions);
// Update option value in defaultValue if value has changed
if (
nextOption.value !== option.value &&
isOptionDefaultValue(option.value)
) {
handleRemoveOptionAsDefault(option.value);
handleSetOptionAsDefault(nextOption.value);
}
}}
onRemove={() => {
if (disabled) {
return;
}
const nextOptions = toSpliced(
options,
index,
1,
).map((option, nextOptionIndex) => ({
...option,
position: nextOptionIndex,
}));
onChange(nextOptions);
}}
isDefault={isOptionDefaultValue(option.value)}
fieldIsNullable={!!isNullable}
onSetAsDefault={() => {
if (disabled) {
return;
}
handleSetOptionAsDefault(option.value);
}}
onRemoveAsDefault={() => {
if (disabled) {
return;
}
handleRemoveOptionAsDefault(option.value);
}}
onInputEnter={() => {
if (disabled) {
return;
}
handleInputEnter();
}}
<StyledApiKeyContainer>
<StyledIconContainer>
<StyledIconPointContainer>
<IconPoint
size={12}
color={theme.color.yellow}
fill={theme.color.yellow}
/>
}
/>
))}
</>
}
/>
</>
)}
</StyledContainer>
</StyledIconPointContainer>
</StyledIconContainer>
<StyledApiKey>{t`API values`}</StyledApiKey>
</StyledApiKeyContainer>
</AdvancedSettingsWrapper>
)}
<StyledOptionsLabel
isAdvancedModeEnabled={isAdvancedModeEnabled}
isBulkInputMode={isBulkInputMode}
>
{t`Options`}
</StyledOptionsLabel>
</StyledLabelContainer>
{!disabled && (
<Dropdown
dropdownId={OPTIONS_DROPDOWN_ID}
clickableComponent={
<LightIconButton
Icon={IconDotsVertical}
accent="tertiary"
/>
}
dropdownComponents={
<DropdownContent
widthInPixels={GenericDropdownContentWidth.Narrow}
>
<DropdownMenuItemsContainer>
<MenuItem
text={
isBulkInputMode ? t`Single edit` : t`Bulk edit`
}
LeftIcon={IconPencil}
onClick={() => {
if (!isBulkInputMode) {
setBulkInputText(
convertOptionsToBulkText(options),
);
}
setIsBulkInputMode(
(currentInputMode) => !currentInputMode,
);
closeOptionsDropdown(OPTIONS_DROPDOWN_ID);
}}
/>
<MenuItem
text={t`Remove all`}
accent="danger"
LeftIcon={IconTrash}
onClick={() => {
onChange([]);
closeOptionsDropdown(OPTIONS_DROPDOWN_ID);
}}
/>
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
)}
</StyledOptionsHeaderContainer>
{isBulkInputMode ? (
<StyledTextAreaContainer>
<TextArea
textAreaId="bulk-options-input"
placeholder={t`Enter one option per line`}
value={bulkInputText}
onChange={(nextOptionAsText) => {
if (disabled) {
return;
}
const nextOptions = convertBulkTextToOptions(
nextOptionAsText,
options,
);
onChange(nextOptions);
setBulkInputText(nextOptionAsText);
}}
minRows={5}
maxRows={15}
disabled={disabled}
/>
<StyledHelpText>
{t`Enter one option per line. Each line will become a new option.`}
</StyledHelpText>
</StyledTextAreaContainer>
) : (
<>
<DraggableList
onDragEnd={(result) =>
!disabled
? handleDragEnd(options, result, onChange)
: undefined
}
draggableItems={
<>
{options.map((option, index) => (
<DraggableItem
isInsideScrollableContainer
key={option.id}
draggableId={option.id}
index={index}
isDragDisabled={options.length === 1}
itemComponent={
<SettingsDataModelFieldSelectFormOptionRow
key={option.id}
option={option}
isNewRow={index === options.length - 1}
onChange={(nextOption) => {
if (disabled) {
return;
}
const nextOptions = toSpliced(
options,
index,
1,
nextOption,
);
onChange(nextOptions);
// Update option value in defaultValue if value has changed
if (
nextOption.value !== option.value &&
isOptionDefaultValue(option.value)
) {
handleRemoveOptionAsDefault(option.value);
handleSetOptionAsDefault(
nextOption.value,
);
}
}}
onRemove={() => {
if (disabled) {
return;
}
const nextOptions = toSpliced(
options,
index,
1,
).map((option, nextOptionIndex) => ({
...option,
position: nextOptionIndex,
}));
onChange(nextOptions);
}}
isDefault={isOptionDefaultValue(option.value)}
fieldIsNullable={!!isNullable}
onSetAsDefault={() => {
if (disabled) {
return;
}
handleSetOptionAsDefault(option.value);
}}
onRemoveAsDefault={() => {
if (disabled) {
return;
}
handleRemoveOptionAsDefault(option.value);
}}
onInputEnter={() => {
if (disabled) {
return;
}
handleInputEnter();
}}
/>
}
/>
))}
</>
}
/>
</>
)}
</CardContent>
</StyledContainerWrapper>
{!disabled && !isBulkInputMode && (
<StyledFooter>
<StyledButton
title={t`Add option`}
Icon={IconPlus}
onClick={handleAddOption}
/>
</StyledFooter>
<StyledFooterContainer>
<CardFooter>
<StyledButtonContainer>
<LightButton
title={t`Add option`}
Icon={IconPlus}
onClick={handleAddOption}
/>
</StyledButtonContainer>
</CardFooter>
</StyledFooterContainer>
)}
</>
)}
@@ -75,13 +75,14 @@ const StyledRow = styled.div`
padding: ${themeCssVariables.spacing['1.5']} 0;
`;
const StyledColorSample = styled(ColorSample)`
const StyledColorSampleContainer = styled.span`
cursor: pointer;
margin-top: ${themeCssVariables.spacing[1]};
margin-bottom: ${themeCssVariables.spacing[1]};
margin-right: 14px;
margin-left: 14px;
display: flex;
align-items: center;
`;
const StyledOptionInputContainer = styled.div`
@@ -93,12 +94,16 @@ const StyledOptionInputContainer = styled.div`
}
`;
const StyledIconGripVertical = styled(IconGripVertical)`
const StyledIconGripVerticalContainer = styled.span`
margin-right: 3px;
display: flex;
align-items: center;
`;
const StyledLightIconButton = styled(LightIconButton)`
const StyledLightIconButtonContainer = styled.span`
margin-left: ${themeCssVariables.spacing[2]};
display: flex;
align-items: center;
`;
export const SettingsDataModelFieldSelectFormOptionRow = ({
@@ -129,14 +134,16 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
return (
<StyledRow className={className}>
<StyledIconGripVertical
style={{
minWidth: theme.icon.size.md,
}}
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
color={theme.font.color.extraLight}
/>
<StyledIconGripVerticalContainer>
<IconGripVertical
style={{
minWidth: theme.icon.size.md,
}}
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
color={theme.font.color.extraLight}
/>
</StyledIconGripVerticalContainer>
<AdvancedSettingsWrapper animationDimension="width" hideDot>
<StyledOptionInputContainer>
<SettingsTextInput
@@ -156,7 +163,11 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
<Dropdown
dropdownId={SELECT_COLOR_DROPDOWN_ID}
dropdownPlacement="bottom-start"
clickableComponent={<StyledColorSample colorName={option.color} />}
clickableComponent={
<StyledColorSampleContainer>
<ColorSample colorName={option.color} />
</StyledColorSampleContainer>
}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
@@ -203,11 +214,13 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
dropdownId={SELECT_ACTIONS_DROPDOWN_ID}
dropdownPlacement="right-start"
clickableComponent={
<StyledLightIconButton
accent="tertiary"
Icon={IconDotsVertical}
disabled={shouldForbidRemoveAsDefault}
/>
<StyledLightIconButtonContainer>
<LightIconButton
accent="tertiary"
Icon={IconDotsVertical}
disabled={shouldForbidRemoveAsDefault}
/>
</StyledLightIconButtonContainer>
}
dropdownComponents={
shouldForbidRemoveAsDefault ? null : (
@@ -21,13 +21,17 @@ type SettingsDataModelFieldPreviewWidgetProps = {
fullWidth?: boolean;
};
const StyledCard = styled(Card)`
border-radius: ${themeCssVariables.border.radius.md};
color: ${themeCssVariables.font.color.primary};
const StyledCardContainer = styled.div`
> * {
border-radius: ${themeCssVariables.border.radius.md};
color: ${themeCssVariables.font.color.primary};
}
`;
const StyledCardContent = styled(CardContent)`
padding: ${themeCssVariables.spacing[2]};
const StyledCardContentContainer = styled.div`
> * {
padding: ${themeCssVariables.spacing[2]};
}
`;
export const SettingsDataModelFieldPreviewWidget = ({
@@ -43,27 +47,31 @@ export const SettingsDataModelFieldPreviewWidget = ({
});
return (
<StyledCard className={className} fullWidth>
<StyledCardContent>
<SettingsDataModelObjectPreview
objectMetadataItems={[objectMetadataItem]}
pluralizeLabel={pluralizeLabel}
/>
<SettingsDataModelFieldPreview
objectNameSingular={objectNameSingular}
fieldMetadataItem={{
label: fieldMetadataItem.label,
icon: fieldMetadataItem.icon,
defaultValue: fieldMetadataItem.defaultValue,
options: fieldMetadataItem.options,
settings: fieldMetadataItem.settings,
type: fieldMetadataItem.type,
name: computeMetadataNameFromLabel(fieldMetadataItem.label),
}}
shrink={shrink}
withFieldLabel={withFieldLabel}
/>
</StyledCardContent>
</StyledCard>
<StyledCardContainer className={className}>
<Card fullWidth>
<StyledCardContentContainer>
<CardContent>
<SettingsDataModelObjectPreview
objectMetadataItems={[objectMetadataItem]}
pluralizeLabel={pluralizeLabel}
/>
<SettingsDataModelFieldPreview
objectNameSingular={objectNameSingular}
fieldMetadataItem={{
label: fieldMetadataItem.label,
icon: fieldMetadataItem.icon,
defaultValue: fieldMetadataItem.defaultValue,
options: fieldMetadataItem.options,
settings: fieldMetadataItem.settings,
type: fieldMetadataItem.type,
name: computeMetadataNameFromLabel(fieldMetadataItem.label),
}}
shrink={shrink}
withFieldLabel={withFieldLabel}
/>
</CardContent>
</StyledCardContentContainer>
</Card>
</StyledCardContainer>
);
};
@@ -21,14 +21,19 @@ export type SettingsDataModelRelationFieldPreviewSubWidgetProps = {
pluralizeLabel?: boolean;
};
const StyledCard = styled(Card)`
border-radius: ${themeCssVariables.border.radius.md};
color: ${themeCssVariables.font.color.primary};
const StyledCardContainer = styled.div`
margin: auto;
> * {
border-radius: ${themeCssVariables.border.radius.md};
color: ${themeCssVariables.font.color.primary};
}
`;
const StyledCardContent = styled(CardContent)`
padding: ${themeCssVariables.spacing[2]};
const StyledCardContentContainer = styled.div`
> * {
padding: ${themeCssVariables.spacing[2]};
}
`;
export const SettingsDataModelRelationFieldPreviewSubWidget = ({
@@ -49,21 +54,25 @@ export const SettingsDataModelRelationFieldPreviewSubWidget = ({
.filter(isDefined);
return (
<StyledCard className={className} fullWidth>
<StyledCardContent>
<SettingsDataModelObjectPreview
objectMetadataItems={targetObjectMetadataItems}
pluralizeLabel={pluralizeLabel}
/>
<SettingsDataModelRelationFieldPreview
fieldMetadataItem={fieldMetadataItem}
relationTargetObjectNameSingular={
fieldPreviewTargetObjectNameSingular
}
shrink={shrink}
withFieldLabel={withFieldLabel}
/>
</StyledCardContent>
</StyledCard>
<StyledCardContainer className={className}>
<Card fullWidth>
<StyledCardContentContainer>
<CardContent>
<SettingsDataModelObjectPreview
objectMetadataItems={targetObjectMetadataItems}
pluralizeLabel={pluralizeLabel}
/>
<SettingsDataModelRelationFieldPreview
fieldMetadataItem={fieldMetadataItem}
relationTargetObjectNameSingular={
fieldPreviewTargetObjectNameSingular
}
shrink={shrink}
withFieldLabel={withFieldLabel}
/>
</CardContent>
</StyledCardContentContainer>
</Card>
</StyledCardContainer>
);
};
@@ -86,15 +86,17 @@ const StyledObjectInstanceCount = styled.div`
color: ${themeCssVariables.font.color.tertiary};
`;
const StyledObjectLink = styled(Link)`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[1]};
text-decoration: none;
color: ${themeCssVariables.font.color.primary};
const StyledObjectLinkContainer = styled.div`
> a {
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[1]};
text-decoration: none;
color: ${themeCssVariables.font.color.primary};
&:hover {
color: ${themeCssVariables.font.color.secondary};
&:hover {
color: ${themeCssVariables.font.color.secondary};
}
}
`;
@@ -123,14 +125,16 @@ export const SettingsDataModelOverviewObject = ({
<StyledNode>
<StyledHeader>
<StyledObjectName onMouseEnter={() => {}} onMouseLeave={() => {}}>
<StyledObjectLink
to={getSettingsPath(SettingsPath.Objects, {
objectNamePlural: objectMetadataItem.namePlural,
})}
>
{Icon && <Icon size={theme.icon.size.md} />}
{objectMetadataItem.labelPlural}
</StyledObjectLink>
<StyledObjectLinkContainer>
<Link
to={getSettingsPath(SettingsPath.Objects, {
objectNamePlural: objectMetadataItem.namePlural,
})}
>
{Icon && <Icon size={theme.icon.size.md} />}
{objectMetadataItem.labelPlural}
</Link>
</StyledObjectLinkContainer>
<StyledObjectInstanceCount> · {totalCount}</StyledObjectInstanceCount>
</StyledObjectName>
<SettingsItemTypeTag item={objectMetadataItem} />
@@ -1,34 +1,21 @@
import { styled } from '@linaria/react';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { Checkbox } from 'twenty-ui/input';
import { useIcons } from 'twenty-ui/display';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
export const AVAILABLE_STANDARD_OBJECTS_GRID_TEMPLATE_COLUMNS =
'28px 148px 256px 80px';
type SettingsAvailableStandardObjectItemTableRowProps = {
isSelected?: boolean;
objectItem: ObjectMetadataItem;
onClick?: () => void;
};
export const StyledAvailableStandardObjectTableRow = styled(TableRow)`
grid-template-columns: 28px 148px 256px 80px;
`;
const StyledCheckboxTableCell = styled(TableCell)`
justify-content: center;
padding: 0;
padding-left: ${themeCssVariables.spacing[1]};
`;
const StyledNameTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[2]};
`;
const StyledDescription = styled.div`
overflow: hidden;
text-overflow: ellipsis;
@@ -45,22 +32,29 @@ export const SettingsAvailableStandardObjectItemTableRow = ({
const Icon = getIcon(objectItem.icon);
return (
<StyledAvailableStandardObjectTableRow
<TableRow
gridTemplateColumns={AVAILABLE_STANDARD_OBJECTS_GRID_TEMPLATE_COLUMNS}
key={objectItem.namePlural}
isSelected={isSelected}
onClick={onClick}
>
<StyledCheckboxTableCell>
<TableCell
align="center"
padding={`0 0 0 ${themeCssVariables.spacing[1]}`}
>
<Checkbox checked={!!isSelected} />
</StyledCheckboxTableCell>
<StyledNameTableCell>
</TableCell>
<TableCell
color={themeCssVariables.font.color.primary}
gap={themeCssVariables.spacing[2]}
>
{!!Icon && <Icon size={theme.icon.size.md} />}
{objectItem.labelPlural}
</StyledNameTableCell>
</TableCell>
<TableCell>
<StyledDescription>{objectItem.description}</StyledDescription>
</TableCell>
<TableCell align="right">{objectItem.fields.length}</TableCell>
</StyledAvailableStandardObjectTableRow>
</TableRow>
);
};
@@ -6,9 +6,10 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { t } from '@lingui/core/macro';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import {
AVAILABLE_STANDARD_OBJECTS_GRID_TEMPLATE_COLUMNS,
SettingsAvailableStandardObjectItemTableRow,
StyledAvailableStandardObjectTableRow,
} from './SettingsAvailableStandardObjectItemTableRow';
type SettingsAvailableStandardObjectsSectionProps = {
@@ -28,12 +29,14 @@ export const SettingsAvailableStandardObjectsSection = ({
description={t`Select one or several standard objects to activate below`}
/>
<Table>
<StyledAvailableStandardObjectTableRow>
<TableRow
gridTemplateColumns={AVAILABLE_STANDARD_OBJECTS_GRID_TEMPLATE_COLUMNS}
>
<TableHeader></TableHeader>
<TableHeader>{t`Name`}</TableHeader>
<TableHeader>{t`Description`}</TableHeader>
<TableHeader align="right">{t`Fields`}</TableHeader>
</StyledAvailableStandardObjectTableRow>
</TableRow>
<TableBody>
{objectItems.map((objectItem) => (
<SettingsAvailableStandardObjectItemTableRow
@@ -36,14 +36,8 @@ type SettingsObjectFieldItemTableRowProps = {
mode: 'view' | 'new-field';
};
export const StyledObjectFieldTableRow = styled(TableRow)`
grid-template-columns: minmax(0, 1fr) 148px 148px 36px;
`;
const StyledNameTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[2]};
`;
export const OBJECT_FIELD_TABLE_ROW_GRID_TEMPLATE_COLUMNS =
'minmax(0, 1fr) 148px 148px 36px';
const StyledNameContainer = styled.div`
display: flex;
@@ -74,13 +68,10 @@ const StyledInactiveLabel = styled.span`
}
`;
const StyledIconTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${themeCssVariables.spacing[1]};
`;
const StyledIconChevronRight = styled(IconChevronRight)`
const StyledIconChevronRightContainer = styled.span`
align-items: center;
color: ${themeCssVariables.font.color.tertiary};
display: flex;
`;
export const SettingsObjectFieldItemTableRow = ({
@@ -177,11 +168,15 @@ export const SettingsObjectFieldItemTableRow = ({
: relationObjectMetadataItem?.labelPlural;
return (
<StyledObjectFieldTableRow
<TableRow
gridTemplateColumns={OBJECT_FIELD_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
onClick={mode === 'view' ? navigateToFieldEdit : undefined}
>
<UndecoratedLink to={linkToNavigate}>
<StyledNameTableCell>
<TableCell
color={themeCssVariables.font.color.primary}
gap={themeCssVariables.spacing[2]}
>
{!!Icon && (
<Icon
style={{
@@ -199,7 +194,7 @@ export const SettingsObjectFieldItemTableRow = ({
<StyledInactiveLabel>{t`Deactivated`}</StyledInactiveLabel>
)}
</StyledNameContainer>
</StyledNameTableCell>
</TableCell>
</UndecoratedLink>
<TableCell>
@@ -232,14 +227,19 @@ export const SettingsObjectFieldItemTableRow = ({
}}
/>
</TableCell>
<StyledIconTableCell>
<TableCell
align="center"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
>
{status === 'active' ? (
mode === 'view' ? (
<UndecoratedLink to={linkToNavigate}>
<StyledIconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
<StyledIconChevronRightContainer>
<IconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
</StyledIconChevronRightContainer>
</UndecoratedLink>
) : (
canToggleField && (
@@ -273,7 +273,7 @@ export const SettingsObjectFieldItemTableRow = ({
onClick={handleToggleField}
/>
)}
</StyledIconTableCell>
</StyledObjectFieldTableRow>
</TableCell>
</TableRow>
);
};
@@ -5,10 +5,11 @@ import { useLingui } from '@lingui/react/macro';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import {
SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
StyledActionTableCell,
StyledNameTableCell,
StyledObjectTableRow,
} from '@/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { useIcons } from 'twenty-ui/display';
@@ -63,7 +64,11 @@ export const SettingsObjectMetadataItemTableRow = ({
const Icon = getIcon(objectMetadataItem.icon);
return (
<StyledObjectTableRow key={objectMetadataItem.namePlural} to={link}>
<TableRow
gridTemplateColumns={SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
key={objectMetadataItem.namePlural}
to={link}
>
<StyledNameTableCell>
{!!Icon && (
<Icon
@@ -95,6 +100,6 @@ export const SettingsObjectMetadataItemTableRow = ({
</TableCell>
<TableCell align="right">{totalObjectCount}</TableCell>
<StyledActionTableCell>{action}</StyledActionTableCell>
</StyledObjectTableRow>
</TableRow>
);
};
@@ -1,21 +0,0 @@
import { styled } from '@linaria/react';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const SETTINGS_OBJECT_TABLE_COLUMN_WIDTH = '98.7px';
export const StyledObjectTableRow = styled(TableRow)`
grid-template-columns: 180px ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} 36px;
`;
export const StyledNameTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[2]};
`;
export const StyledActionTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${themeCssVariables.spacing[1]};
`;
@@ -0,0 +1,29 @@
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { 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 StyledNameTableCell = (
props: React.ComponentProps<typeof TableCell>,
) => (
<TableCell
color={themeCssVariables.font.color.primary}
gap={themeCssVariables.spacing[2]}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
);
export const StyledActionTableCell = (
props: React.ComponentProps<typeof TableCell>,
) => (
<TableCell
align="center"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
);
@@ -25,14 +25,8 @@ type SettingsObjectRelationItemTableRowProps = {
objectMetadataItem: ObjectMetadataItem;
};
export const StyledObjectRelationTableRow = styled(TableRow)`
grid-template-columns: minmax(0, 1fr) 148px 148px 36px;
`;
const StyledNameTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[2]};
`;
export const OBJECT_RELATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS =
'minmax(0, 1fr) 148px 148px 36px';
const StyledNameContainer = styled.div`
display: flex;
@@ -63,13 +57,10 @@ const StyledInactiveLabel = styled.span`
}
`;
const StyledIconTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${themeCssVariables.spacing[1]};
`;
const StyledIconChevronRight = styled(IconChevronRight)`
const StyledIconChevronRightContainer = styled.span`
align-items: center;
color: ${themeCssVariables.font.color.tertiary};
display: flex;
`;
const StyledRelationType = styled.div`
@@ -79,18 +70,24 @@ const StyledRelationType = styled.div`
gap: ${themeCssVariables.spacing[1]};
`;
const StyledLink = styled(Link)`
color: ${themeCssVariables.font.color.primary};
text-decoration: underline;
text-decoration-color: ${themeCssVariables.border.color.strong};
text-underline-offset: 2px;
const StyledLinkContainer = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
color: ${themeCssVariables.color.blue};
text-decoration-color: ${themeCssVariables.color.blue};
> a {
color: ${themeCssVariables.font.color.primary};
text-decoration: underline;
text-decoration-color: ${themeCssVariables.border.color.strong};
text-underline-offset: 2px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
color: ${themeCssVariables.color.blue};
text-decoration-color: ${themeCssVariables.color.blue};
}
}
`;
@@ -157,9 +154,14 @@ export const SettingsObjectRelationItemTableRow = ({
: fieldMetadataItem.label;
return (
// eslint-disable-next-line twenty/no-navigate-prefer-link
<StyledObjectRelationTableRow onClick={navigateToFieldEdit}>
<StyledNameTableCell>
<TableRow
gridTemplateColumns={OBJECT_RELATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
to={linkToNavigate}
>
<TableCell
color={themeCssVariables.font.color.primary}
gap={themeCssVariables.spacing[2]}
>
{!!Icon && (
<Icon
style={{
@@ -171,15 +173,17 @@ export const SettingsObjectRelationItemTableRow = ({
)}
<StyledNameContainer>
{isRelatedObjectLinkable ? (
<StyledLink
to={getSettingsPath(SettingsPath.ObjectDetail, {
objectNamePlural: relationObjectMetadataItem.namePlural,
})}
onClick={(event) => event.stopPropagation()}
title={targetObjectLabel}
>
{targetObjectLabel}
</StyledLink>
<StyledLinkContainer>
<Link
to={getSettingsPath(SettingsPath.ObjectDetail, {
objectNamePlural: relationObjectMetadataItem.namePlural,
})}
onClick={(event) => event.stopPropagation()}
title={targetObjectLabel}
>
{targetObjectLabel}
</Link>
</StyledLinkContainer>
) : (
<StyledNameLabel title={targetObjectLabel}>
{targetObjectLabel}
@@ -189,7 +193,7 @@ export const SettingsObjectRelationItemTableRow = ({
<StyledInactiveLabel>{t`Deactivated`}</StyledInactiveLabel>
)}
</StyledNameContainer>
</StyledNameTableCell>
</TableCell>
<TableCell>
<SettingsItemTypeTag
@@ -213,13 +217,18 @@ export const SettingsObjectRelationItemTableRow = ({
</StyledRelationType>
</TableCell>
<StyledIconTableCell>
<TableCell
align="center"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
>
{fieldMetadataItem.isActive ? (
<UndecoratedLink to={linkToNavigate}>
<StyledIconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
<StyledIconChevronRightContainer>
<IconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
</StyledIconChevronRightContainer>
</UndecoratedLink>
) : (
<SettingsObjectFieldInactiveActionDropdown
@@ -238,7 +247,7 @@ export const SettingsObjectRelationItemTableRow = ({
}
/>
)}
</StyledIconTableCell>
</StyledObjectRelationTableRow>
</TableCell>
</TableRow>
);
};
@@ -27,9 +27,10 @@ import { Button } from 'twenty-ui/input';
import { MenuItemToggle } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { normalizeSearchText } from '~/utils/normalizeSearchText';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import {
OBJECT_RELATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
SettingsObjectRelationItemTableRow,
StyledObjectRelationTableRow,
} from './SettingsObjectRelationItemTableRow';
const StyledSearchAndFilterContainer = styled.div`
@@ -39,7 +40,7 @@ const StyledSearchAndFilterContainer = styled.div`
width: 100%;
`;
const StyledSearchInput = styled(SettingsTextInput)`
const StyledSearchInputContainer = styled.div`
flex: 1;
`;
@@ -118,13 +119,15 @@ export const SettingsObjectRelationsTable = ({
return (
<>
<StyledSearchAndFilterContainer>
<StyledSearchInput
instanceId="object-relation-table-search"
LeftIcon={IconSearch}
placeholder={t`Search a field...`}
value={searchTerm}
onChange={setSearchTerm}
/>
<StyledSearchInputContainer>
<SettingsTextInput
instanceId="object-relation-table-search"
LeftIcon={IconSearch}
placeholder={t`Search a field...`}
value={searchTerm}
onChange={setSearchTerm}
/>
</StyledSearchInputContainer>
<Dropdown
dropdownId="settings-relations-filter-dropdown"
dropdownPlacement="bottom-end"
@@ -165,7 +168,9 @@ export const SettingsObjectRelationsTable = ({
/>
</StyledSearchAndFilterContainer>
<Table>
<StyledObjectRelationTableRow>
<TableRow
gridTemplateColumns={OBJECT_RELATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
>
{tableMetadata.fields.map((item) => (
<SortableTableHeader
key={item.fieldName}
@@ -176,7 +181,7 @@ export const SettingsObjectRelationsTable = ({
/>
))}
<TableHeader></TableHeader>
</StyledObjectRelationTableRow>
</TableRow>
{filteredRelationFields.map((fieldMetadataItem) => (
<SettingsObjectRelationItemTableRow
key={fieldMetadataItem.id}
@@ -29,8 +29,10 @@ const StyledContentContainer = styled.div`
gap: ${themeCssVariables.spacing[8]};
`;
const StyledFormSection = styled(Section)`
padding-left: 0 !important;
const StyledFormSectionContainer = styled.div`
> * {
padding-left: 0 !important;
}
`;
const StyledDangerButtonsContainer = styled.div`
@@ -90,16 +92,18 @@ export const ObjectSettings = ({
return (
<StyledContentContainer>
<StyledFormSection>
<H2Title
title={t`About`}
description={t`Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms.`}
/>
<SettingsUpdateDataModelObjectAboutForm
objectMetadataItem={objectMetadataItem}
/>
</StyledFormSection>
<StyledFormSection>
<StyledFormSectionContainer>
<Section>
<H2Title
title={t`About`}
description={t`Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms.`}
/>
<SettingsUpdateDataModelObjectAboutForm
objectMetadataItem={objectMetadataItem}
/>
</Section>
</StyledFormSectionContainer>
<StyledFormSectionContainer>
<Section>
<H2Title
title={t`Options`}
@@ -109,9 +113,9 @@ export const ObjectSettings = ({
objectMetadataItem={objectMetadataItem}
/>
</Section>
</StyledFormSection>
</StyledFormSectionContainer>
{!isReadOnly && (
<StyledFormSection>
<StyledFormSectionContainer>
<Section>
<H2Title
title={t`Danger zone`}
@@ -136,7 +140,7 @@ export const ObjectSettings = ({
)}
</StyledDangerButtonsContainer>
</Section>
</StyledFormSection>
</StyledFormSectionContainer>
)}
<ConfirmationModal
modalInstanceId={DELETE_OBJECT_MODAL_ID}
@@ -6,12 +6,12 @@ import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { IconEye } from 'twenty-ui/display';
import { FloatingButton } from 'twenty-ui/input';
import { Card } from 'twenty-ui/layout';
import DarkCoverImage from '@/settings/data-model/assets/cover-dark.png';
import LightCoverImage from '@/settings/data-model/assets/cover-light.png';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledCoverImageContainer = styled(Card)`
const StyledCoverImageContainer = styled.div`
align-items: center;
background-size: cover;
border-radius: ${themeCssVariables.border.radius.md};
@@ -95,14 +95,16 @@ const StyledBannerText = styled.span`
flex: 1;
`;
const StyledConflictButton = styled(Button)`
border-color: ${themeCssVariables.color.blue};
color: ${themeCssVariables.color.blue};
&:hover {
background: ${themeCssVariables.accent.secondary};
}
&:focus-visible {
box-shadow: 0 0 0 3px ${themeCssVariables.accent.tertiary};
const StyledConflictButtonContainer = styled.div`
> button {
border-color: ${themeCssVariables.color.blue};
color: ${themeCssVariables.color.blue};
&:hover {
background: ${themeCssVariables.accent.secondary};
}
&:focus-visible {
box-shadow: 0 0 0 3px ${themeCssVariables.accent.tertiary};
}
}
`;
@@ -281,18 +283,20 @@ export const SettingsDataModelObjectAboutForm = ({
{t`An object with this name already exists`}
</StyledBannerText>
</StyledBannerContent>
<StyledConflictButton
size="small"
variant="secondary"
accent="blue"
title={t`Open`}
onClick={() =>
navigateSettings(SettingsPath.ObjectDetail, {
objectNamePlural:
conflictingObjectMetadataItem.namePlural,
})
}
/>
<StyledConflictButtonContainer>
<Button
size="small"
variant="secondary"
accent="blue"
title={t`Open`}
onClick={() =>
navigateSettings(SettingsPath.ObjectDetail, {
objectNamePlural:
conflictingObjectMetadataItem.namePlural,
})
}
/>
</StyledConflictButtonContainer>
</StyledConflictBanner>
)}
{[
@@ -15,18 +15,25 @@ type SettingsDataModelObjectSettingsFormCardProps = {
objectMetadataItem: ObjectMetadataItem;
};
const StyledTopCardContent = styled(CardContent)`
background-color: ${themeCssVariables.background.transparent.lighter};
const StyledTopCardContentContainer = styled.div`
> * {
background-color: ${themeCssVariables.background.transparent.lighter};
}
`;
const StyledObjectSummaryCard = styled(Card)`
border-radius: ${themeCssVariables.border.radius.md};
color: ${themeCssVariables.font.color.primary};
const StyledObjectSummaryCardContainer = styled.div`
max-width: 480px;
> * {
border-radius: ${themeCssVariables.border.radius.md};
color: ${themeCssVariables.font.color.primary};
}
`;
const StyledObjectSummaryCardContent = styled(CardContent)`
padding: ${themeCssVariables.spacing[2]};
const StyledObjectSummaryCardContentContainer = styled.div`
> * {
padding: ${themeCssVariables.spacing[2]};
}
`;
export const SettingsDataModelObjectSettingsFormCard = ({
@@ -42,26 +49,32 @@ export const SettingsDataModelObjectSettingsFormCard = ({
return (
<Card fullWidth>
<StyledTopCardContent divider>
<SettingsDataModelCardTitle>
<Trans>Preview</Trans>
</SettingsDataModelCardTitle>
{labelIdentifierFieldMetadataItem ? (
<SettingsDataModelFieldPreviewWidget
objectNameSingular={objectMetadataItem.nameSingular}
fieldMetadataItem={labelIdentifierFieldMetadataItem}
withFieldLabel={false}
/>
) : (
<StyledObjectSummaryCard>
<StyledObjectSummaryCardContent>
<SettingsDataModelObjectPreview
objectMetadataItems={[objectMetadataItem]}
/>
</StyledObjectSummaryCardContent>
</StyledObjectSummaryCard>
)}
</StyledTopCardContent>
<StyledTopCardContentContainer>
<CardContent divider>
<SettingsDataModelCardTitle>
<Trans>Preview</Trans>
</SettingsDataModelCardTitle>
{labelIdentifierFieldMetadataItem ? (
<SettingsDataModelFieldPreviewWidget
objectNameSingular={objectMetadataItem.nameSingular}
fieldMetadataItem={labelIdentifierFieldMetadataItem}
withFieldLabel={false}
/>
) : (
<StyledObjectSummaryCardContainer>
<Card>
<StyledObjectSummaryCardContentContainer>
<CardContent>
<SettingsDataModelObjectPreview
objectMetadataItems={[objectMetadataItem]}
/>
</CardContent>
</StyledObjectSummaryCardContentContainer>
</Card>
</StyledObjectSummaryCardContainer>
)}
</CardContent>
</StyledTopCardContentContainer>
<CardContent>
<SettingsDataModelObjectIdentifiersForm
objectMetadataItem={objectMetadataItem}
@@ -1,5 +1,3 @@
import { styled } from '@linaria/react';
import {
formatExpiration,
isExpired,
@@ -7,23 +5,11 @@ import {
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { IconChevronRight } from 'twenty-ui/display';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { MOBILE_VIEWPORT, ThemeContext } from 'twenty-ui/theme-constants';
import { ThemeContext } from 'twenty-ui/theme-constants';
import { type ApiKey } from '~/generated-metadata/graphql';
export const StyledApisFieldTableRow = styled(TableRow)`
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: 100%;
}
`;
const StyledTruncatedCell = styled(TableCell)`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
`;
const StyledEllipsisLabel = styled.div`
white-space: nowrap;
text-overflow: ellipsis;
@@ -49,24 +35,40 @@ export const SettingsApiKeysFieldItemTableRow = ({
const gridColumns = '5fr 2fr 3fr 1fr';
return (
<StyledApisFieldTableRow gridAutoColumns={gridColumns} to={to}>
<StyledTruncatedCell color={theme.font.color.primary}>
<TableRow gridAutoColumns={gridColumns} to={to}>
<TableCell
color={theme.font.color.primary}
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
clickable
>
<StyledEllipsisLabel>{apiKey.name}</StyledEllipsisLabel>
</StyledTruncatedCell>
</TableCell>
<StyledTruncatedCell color={theme.font.color.tertiary}>
<TableCell
color={theme.font.color.tertiary}
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
clickable
>
<StyledEllipsisLabel>{apiKey.role?.label || '-'}</StyledEllipsisLabel>
</StyledTruncatedCell>
</TableCell>
<StyledTruncatedCell
<TableCell
color={
isExpired(apiKey.expiresAt || null)
? theme.font.color.danger
: theme.font.color.tertiary
}
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
clickable
>
<StyledEllipsisLabel>{formattedExpiration}</StyledEllipsisLabel>
</StyledTruncatedCell>
</TableCell>
<TableCell align="right">
<IconChevronRight
@@ -74,6 +76,6 @@ export const SettingsApiKeysFieldItemTableRow = ({
color={theme.font.color.tertiary}
/>
</TableCell>
</StyledApisFieldTableRow>
</TableRow>
);
};
@@ -10,7 +10,7 @@ import { getSettingsPath } from 'twenty-shared/utils';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useGetApiKeysQuery } from '~/generated-metadata/graphql';
const StyledTableBody = styled(TableBody)`
const StyledTableBodyContainer = styled.div`
border-bottom: 1px solid ${themeCssVariables.border.color.light};
`;
@@ -36,17 +36,19 @@ export const SettingsApiKeysTable = () => {
<TableHeader></TableHeader>
</TableRow>
{!!apiKeys?.length && (
<StyledTableBody>
{apiKeys.map((apiKey) => (
<SettingsApiKeysFieldItemTableRow
key={apiKey.id}
apiKey={apiKey}
to={getSettingsPath(SettingsPath.ApiKeyDetail, {
apiKeyId: apiKey.id,
})}
/>
))}
</StyledTableBody>
<StyledTableBodyContainer>
<TableBody>
{apiKeys.map((apiKey) => (
<SettingsApiKeysFieldItemTableRow
key={apiKey.id}
apiKey={apiKey}
to={getSettingsPath(SettingsPath.ApiKeyDetail, {
apiKeyId: apiKey.id,
})}
/>
))}
</TableBody>
</StyledTableBodyContainer>
)}
</Table>
);
@@ -11,23 +11,12 @@ import { useContext } from 'react';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { type Webhook } from '~/generated-metadata/graphql';
export const StyledApisFieldTableRow = styled(TableRow)`
grid-template-columns: 1fr 28px;
`;
const WEBHOOK_TABLE_ROW_GRID_TEMPLATE_COLUMNS = '1fr 28px';
const StyledIconTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${themeCssVariables.spacing[1]};
padding-left: 0;
`;
const StyledUrlTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.primary};
overflow: hidden;
`;
const StyledIconChevronRight = styled(IconChevronRight)`
const StyledIconChevronRightContainer = styled.span`
align-items: center;
color: ${themeCssVariables.font.color.tertiary};
display: flex;
`;
export const SettingsDevelopersWebhookTableRow = ({
@@ -42,8 +31,11 @@ export const SettingsDevelopersWebhookTableRow = ({
}) => {
const { theme } = useContext(ThemeContext);
return (
<StyledApisFieldTableRow to={to}>
<StyledUrlTableCell>
<TableRow
gridTemplateColumns={WEBHOOK_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
to={to}
>
<TableCell color={themeCssVariables.font.color.primary} overflow="hidden">
<OverflowingTextWithTooltip
text={
isValidUrl(webhook.targetUrl)
@@ -51,13 +43,18 @@ export const SettingsDevelopersWebhookTableRow = ({
: webhook.targetUrl
}
/>
</StyledUrlTableCell>
<StyledIconTableCell>
<StyledIconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
</StyledIconTableCell>
</StyledApisFieldTableRow>
</TableCell>
<TableCell
align="center"
padding={`0 ${themeCssVariables.spacing[1]} 0 0`}
>
<StyledIconChevronRightContainer>
<IconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
</StyledIconChevronRightContainer>
</TableCell>
</TableRow>
);
};
@@ -10,16 +10,12 @@ import { getSettingsPath } from 'twenty-shared/utils';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useGetWebhooksQuery } from '~/generated-metadata/graphql';
const StyledTableBody = styled(TableBody)`
const StyledTableBodyContainer = styled.div`
border-bottom: 1px solid ${themeCssVariables.border.color.light};
max-height: 260px;
overflow-y: auto;
`;
const StyledTableRow = styled(TableRow)`
grid-template-columns: 444px 68px;
`;
export const SettingsWebhooksTable = () => {
const { data: webhooksData } = useGetWebhooksQuery();
@@ -27,22 +23,24 @@ export const SettingsWebhooksTable = () => {
return (
<Table>
<StyledTableRow>
<TableRow gridTemplateColumns="444px 68px">
<TableHeader>URL</TableHeader>
<TableHeader></TableHeader>
</StyledTableRow>
</TableRow>
{!!webhooks?.length && (
<StyledTableBody>
{webhooks.map((webhookFieldItem) => (
<SettingsDevelopersWebhookTableRow
key={webhookFieldItem.id}
webhook={webhookFieldItem}
to={getSettingsPath(SettingsPath.WebhookDetail, {
webhookId: webhookFieldItem.id,
})}
/>
))}
</StyledTableBody>
<StyledTableBodyContainer>
<TableBody>
{webhooks.map((webhookFieldItem) => (
<SettingsDevelopersWebhookTableRow
key={webhookFieldItem.id}
webhook={webhookFieldItem}
to={getSettingsPath(SettingsPath.WebhookDetail, {
webhookId: webhookFieldItem.id,
})}
/>
))}
</TableBody>
</StyledTableBodyContainer>
)}
</Table>
);
@@ -60,13 +60,15 @@ const StyledControlLabel = styled.span`
white-space: nowrap;
`;
const StyledControlIconChevronDown = styled(IconChevronDown)<{
const StyledControlIconChevronDownContainer = styled.span<{
disabled?: boolean;
}>`
align-items: center;
color: ${({ disabled }) =>
disabled
? themeCssVariables.font.color.extraLight
: themeCssVariables.font.color.tertiary};
display: flex;
`;
type WebhookEntitySelectProps = {
@@ -166,10 +168,9 @@ export const WebhookEntitySelect = ({
clickableComponent={
<StyledControlContainer disabled={disabled}>
<StyledControlLabel>{getSelectedLabel()}</StyledControlLabel>
<StyledControlIconChevronDown
disabled={disabled}
size={theme.icon.size.md}
/>
<StyledControlIconChevronDownContainer disabled={disabled}>
<IconChevronDown size={theme.icon.size.md} />
</StyledControlIconChevronDownContainer>
</StyledControlContainer>
}
dropdownComponents={
@@ -25,13 +25,13 @@ import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons
import { getDomainValidationSchema } from '@/settings/domains/utils/get-domain-validation-schema';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledButtonGroup = styled(ButtonGroup)`
& > :not(:first-of-type) > button {
const StyledButtonGroupContainer = styled.div`
> * > :not(:first-of-type) > button {
border-left: none;
}
`;
const StyledButton = styled(Button)`
const StyledButtonContainer = styled.div`
align-self: flex-start;
`;
@@ -171,23 +171,29 @@ export const SettingPublicDomain = () => {
fullWidth
/>
{isDefined(selectedPublicDomain) && (
<StyledButtonGroup>
<StyledButton
isLoading={isLoading}
Icon={IconReload}
title={t`Reload`}
variant="primary"
onClick={() =>
checkPublicDomainRecords(selectedPublicDomain.domain)
}
type="button"
/>
<StyledButton
Icon={IconTrash}
variant="primary"
onClick={onDelete}
/>
</StyledButtonGroup>
<StyledButtonGroupContainer>
<ButtonGroup>
<StyledButtonContainer>
<Button
isLoading={isLoading}
Icon={IconReload}
title={t`Reload`}
variant="primary"
onClick={() =>
checkPublicDomainRecords(selectedPublicDomain.domain)
}
type="button"
/>
</StyledButtonContainer>
<StyledButtonContainer>
<Button
Icon={IconTrash}
variant="primary"
onClick={onDelete}
/>
</StyledButtonContainer>
</ButtonGroup>
</StyledButtonGroupContainer>
)}
</StyledDomainFormWrapper>
{isDefined(selectedPublicDomain) && publicDomainRecords?.domain && (
@@ -19,13 +19,13 @@ const StyledDomainFormWrapper = styled.div`
gap: ${themeCssVariables.spacing[2]};
`;
const StyledButtonGroup = styled(ButtonGroup)`
& > :not(:first-of-type) > button {
const StyledButtonGroupContainer = styled.div`
> * > :not(:first-of-type) > button {
border-left: none;
}
`;
const StyledButton = styled(Button)`
const StyledButtonContainer = styled.div`
align-self: flex-start;
`;
@@ -80,21 +80,27 @@ export const SettingsCustomDomain = () => {
)}
/>
{currentWorkspace?.customDomain && (
<StyledButtonGroup>
<StyledButton
isLoading={isLoading}
Icon={IconReload}
title={t`Reload`}
variant="primary"
onClick={checkCustomDomainRecords}
type="button"
/>
<StyledButton
Icon={IconTrash}
variant="primary"
onClick={deleteCustomDomain}
/>
</StyledButtonGroup>
<StyledButtonGroupContainer>
<ButtonGroup>
<StyledButtonContainer>
<Button
isLoading={isLoading}
Icon={IconReload}
title={t`Reload`}
variant="primary"
onClick={checkCustomDomainRecords}
type="button"
/>
</StyledButtonContainer>
<StyledButtonContainer>
<Button
Icon={IconTrash}
variant="primary"
onClick={deleteCustomDomain}
/>
</StyledButtonContainer>
</ButtonGroup>
</StyledButtonGroupContainer>
)}
</StyledDomainFormWrapper>
{currentWorkspace?.customDomain && (
@@ -6,23 +6,10 @@ import { StyledTableRow } from '@/settings/logic-functions/components/SettingsLo
import { useContext } from 'react';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledNameTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[2]};
`;
const StyledRuntimeTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.secondary};
gap: ${themeCssVariables.spacing[2]};
`;
const StyledIconTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${themeCssVariables.spacing[1]};
`;
const StyledIconChevronRight = styled(IconChevronRight)`
const StyledIconChevronRightContainer = styled.span`
align-items: center;
color: ${themeCssVariables.font.color.tertiary};
display: flex;
`;
export const SettingsLogicFunctionsFieldItemTableRow = ({
@@ -35,15 +22,33 @@ export const SettingsLogicFunctionsFieldItemTableRow = ({
const { theme } = useContext(ThemeContext);
return (
<StyledTableRow to={to}>
<StyledNameTableCell>{logicFunction.name}</StyledNameTableCell>
<StyledNameTableCell></StyledNameTableCell>
<StyledRuntimeTableCell>{logicFunction.runtime}</StyledRuntimeTableCell>
<StyledIconTableCell>
<StyledIconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
</StyledIconTableCell>
<TableCell
color={themeCssVariables.font.color.primary}
gap={themeCssVariables.spacing[2]}
>
{logicFunction.name}
</TableCell>
<TableCell
color={themeCssVariables.font.color.primary}
gap={themeCssVariables.spacing[2]}
></TableCell>
<TableCell
color={themeCssVariables.font.color.secondary}
gap={themeCssVariables.spacing[2]}
>
{logicFunction.runtime}
</TableCell>
<TableCell
align="center"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
>
<StyledIconChevronRightContainer>
<IconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
</StyledIconChevronRightContainer>
</TableCell>
</StyledTableRow>
);
};
@@ -8,14 +8,21 @@ import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { type LogicFunction } from '~/generated-metadata/graphql';
import { useLingui } from '@lingui/react/macro';
import React from 'react';
import { useParams } from 'react-router-dom';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const StyledTableRow = styled(TableRow)`
grid-template-columns: 164px 1fr 96px 32px;
`;
export const StyledTableRow = (
props: React.ComponentProps<typeof TableRow>,
) => (
<TableRow
gridTemplateColumns="164px 1fr 96px 32px"
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
);
const StyledTableBody = styled(TableBody)`
const StyledTableBodyContainer = styled.div`
border-bottom: 1px solid ${themeCssVariables.border.color.light};
`;
@@ -40,18 +47,20 @@ export const SettingsLogicFunctionsTable = ({
<TableHeader>{t`Runtime`}</TableHeader>
<TableHeader></TableHeader>
</StyledTableRow>
<StyledTableBody>
{logicFunctions.map((logicFunction: LogicFunction) => (
<SettingsLogicFunctionsFieldItemTableRow
key={logicFunction.id}
logicFunction={logicFunction}
to={getSettingsPath(SettingsPath.ApplicationLogicFunctionDetail, {
applicationId,
logicFunctionId: logicFunction.id,
})}
/>
))}
</StyledTableBody>
<StyledTableBodyContainer>
<TableBody>
{logicFunctions.map((logicFunction: LogicFunction) => (
<SettingsLogicFunctionsFieldItemTableRow
key={logicFunction.id}
logicFunction={logicFunction}
to={getSettingsPath(SettingsPath.ApplicationLogicFunctionDetail, {
applicationId,
logicFunctionId: logicFunction.id,
})}
/>
))}
</TableBody>
</StyledTableBodyContainer>
</Table>
);
};
@@ -12,8 +12,10 @@ import { H2Title, IconPlayerPlay } from 'twenty-ui/display';
import { Button, CoreEditorHeader } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
const StyledTabList = styled(TabList)`
border-bottom: none;
const StyledTabListContainer = styled.div`
> * {
border-bottom: none;
}
`;
export const SettingsLogicFunctionCodeEditorTab = ({
@@ -44,12 +46,14 @@ export const SettingsLogicFunctionCodeEditorTab = ({
);
const HeaderTabList = (
<StyledTabList
tabs={files.map((file) => {
return { id: file.path, title: file.path.split('/').at(-1) || '' };
})}
componentInstanceId={SETTINGS_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID}
/>
<StyledTabListContainer>
<TabList
tabs={files.map((file) => {
return { id: file.path, title: file.path.split('/').at(-1) || '' };
})}
componentInstanceId={SETTINGS_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID}
/>
</StyledTabListContainer>
);
return (
@@ -5,9 +5,8 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import React, { useState } from 'react';
import {
IconCheck,
IconDotsVertical,
@@ -20,13 +19,23 @@ import { LightIconButton } from 'twenty-ui/input';
import { MenuItem } from 'twenty-ui/navigation';
import type { ApplicationVariable } from '~/generated-metadata/graphql';
const StyledEditModeTableRow = styled(TableRow)`
grid-template-columns: 180px auto 56px;
`;
const StyledEditModeTableRow = (
props: React.ComponentProps<typeof TableRow>,
) => (
<TableRow
gridTemplateColumns="180px auto 56px"
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
);
const StyledTableRow = styled(TableRow)`
grid-template-columns: 180px 300px 32px;
`;
const StyledTableRow = (props: React.ComponentProps<typeof TableRow>) => (
<TableRow
gridTemplateColumns="180px 300px 32px"
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
);
export const SettingsLogicFunctionTabEnvironmentVariableTableRow = ({
envVariable,
@@ -14,18 +14,9 @@ import { Tag } from 'twenty-ui/components';
import { type LogicFunction } from '~/generated-metadata/graphql';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const StyledRouteTriggerTableRow = styled(TableRow)`
grid-template-columns: 1fr 120px 120px;
`;
const ROUTE_TRIGGER_GRID_TEMPLATE_COLUMNS = '1fr 120px 120px';
const StyledTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.tertiary};
gap: ${themeCssVariables.spacing[2]};
min-width: 0;
overflow: hidden;
`;
const StyledRouteTriggerTableHeaderRow = styled(StyledRouteTriggerTableRow)`
const StyledRouteTriggerTableHeaderRowWrapper = styled.div`
margin-bottom: ${themeCssVariables.spacing[2]};
`;
@@ -119,26 +110,44 @@ export const SettingsLogicFunctionTriggersTab = ({
description={t`Triggers the function with Http request`}
/>
<Table>
<StyledRouteTriggerTableHeaderRow>
<TableHeader>{t`Path`}</TableHeader>
<TableHeader>{t`Method`}</TableHeader>
<TableHeader>{t`Auth Required`}</TableHeader>
</StyledRouteTriggerTableHeaderRow>
<StyledRouteTriggerTableRow>
<StyledTableCell>
<StyledRouteTriggerTableHeaderRowWrapper>
<TableRow
gridTemplateColumns={ROUTE_TRIGGER_GRID_TEMPLATE_COLUMNS}
>
<TableHeader>{t`Path`}</TableHeader>
<TableHeader>{t`Method`}</TableHeader>
<TableHeader>{t`Auth Required`}</TableHeader>
</TableRow>
</StyledRouteTriggerTableHeaderRowWrapper>
<TableRow gridTemplateColumns={ROUTE_TRIGGER_GRID_TEMPLATE_COLUMNS}>
<TableCell
color={themeCssVariables.font.color.tertiary}
gap={themeCssVariables.spacing[2]}
overflow="hidden"
>
<OverflowingTextWithTooltip
text={`${REACT_APP_SERVER_BASE_URL}/s${routeTrigger.path}`}
/>
</StyledTableCell>
<StyledTableCell>{routeTrigger.httpMethod}</StyledTableCell>
<StyledTableCell>
</TableCell>
<TableCell
color={themeCssVariables.font.color.tertiary}
gap={themeCssVariables.spacing[2]}
overflow="hidden"
>
{routeTrigger.httpMethod}
</TableCell>
<TableCell
color={themeCssVariables.font.color.tertiary}
gap={themeCssVariables.spacing[2]}
overflow="hidden"
>
<Tag
text={routeTrigger.isAuthRequired ? t`True` : t`False`}
color={routeTrigger.isAuthRequired ? 'green' : 'orange'}
weight="medium"
/>
</StyledTableCell>
</StyledRouteTriggerTableRow>
</TableCell>
</TableRow>
</Table>
</Section>
)}
@@ -3,9 +3,9 @@ import { type ReactNode, useContext } from 'react';
import DarkCoverImage from '@/settings/playground/assets/cover-dark.png';
import LightCoverImage from '@/settings/playground/assets/cover-light.png';
import { Card } from 'twenty-ui/layout';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledCard = styled(Card)`
const StyledCoverContainer = styled.div`
align-items: center;
background-size: cover;
border-radius: ${themeCssVariables.border.radius.md};
@@ -34,11 +34,11 @@ export const StyledSettingsApiPlaygroundCoverImage = ({
? LightCoverImage.toString()
: DarkCoverImage.toString();
return (
<StyledCard
<StyledCoverContainer
className={className}
style={{ backgroundImage: `url('${coverImage}')` }}
>
{children}
</StyledCard>
</StyledCoverContainer>
);
};
@@ -31,7 +31,7 @@ const StyledActionWrapper = styled.div`
}
`;
const StyledActionButton = styled(Button)`
const StyledActionButtonContainer = styled.div`
height: 100%;
display: inline-flex;
align-items: center;
@@ -96,34 +96,40 @@ export const EmailField = () => {
/>
{isEditing ? (
<StyledActionWrapper key="editing">
<StyledActionButton
Icon={IconCheck}
variant="secondary"
position="left"
size="small"
onClick={handleSave}
disabled={isSaveDisabled}
type="button"
/>
<StyledActionButton
Icon={IconX}
variant="secondary"
position="right"
size="small"
onClick={handleCancelEditing}
type="button"
/>
<StyledActionButtonContainer>
<Button
Icon={IconCheck}
variant="secondary"
position="left"
size="small"
onClick={handleSave}
disabled={isSaveDisabled}
type="button"
/>
</StyledActionButtonContainer>
<StyledActionButtonContainer>
<Button
Icon={IconX}
variant="secondary"
position="right"
size="small"
onClick={handleCancelEditing}
type="button"
/>
</StyledActionButtonContainer>
</StyledActionWrapper>
) : (
<StyledActionWrapper key="view">
<StyledActionButton
Icon={IconPencil}
variant="secondary"
size="small"
onClick={handleStartEditing}
disabled={!canEdit}
type="button"
/>
<StyledActionButtonContainer>
<Button
Icon={IconPencil}
variant="secondary"
size="small"
onClick={handleStartEditing}
disabled={!canEdit}
type="button"
/>
</StyledActionButtonContainer>
</StyledActionWrapper>
)}
</StyledFieldRow>
@@ -27,12 +27,14 @@ import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { sortByAscString } from '~/utils/array/sortByAscString';
const StyledCreateRoleSection = styled(Section)`
border-top: 1px solid ${themeCssVariables.border.color.light};
display: flex;
justify-content: flex-end;
padding-top: ${themeCssVariables.spacing[2]};
padding-bottom: ${themeCssVariables.spacing[2]};
const StyledCreateRoleSectionContainer = styled.div`
> * {
border-top: 1px solid ${themeCssVariables.border.color.light};
display: flex;
justify-content: flex-end;
padding-top: ${themeCssVariables.spacing[2]};
padding-bottom: ${themeCssVariables.spacing[2]};
}
`;
const StyledTableRows = styled.div`
@@ -40,10 +42,6 @@ const StyledTableRows = styled.div`
padding-top: ${themeCssVariables.spacing[2]};
`;
const StyledNoRoles = styled(TableCell)`
color: ${themeCssVariables.font.color.tertiary};
`;
const StyledSearchAndFilterContainer = styled.div`
display: flex;
gap: ${themeCssVariables.spacing[2]};
@@ -51,7 +49,7 @@ const StyledSearchAndFilterContainer = styled.div`
width: 100%;
`;
const StyledSearchInput = styled(SettingsTextInput)`
const StyledSearchInputContainer = styled.div`
flex: 1;
`;
@@ -88,13 +86,15 @@ export const SettingsRolesList = () => {
/>
<StyledSearchAndFilterContainer>
<StyledSearchInput
instanceId="settings-roles-search"
LeftIcon={IconSearch}
placeholder={t`Search a role...`}
value={searchTerm}
onChange={setSearchTerm}
/>
<StyledSearchInputContainer>
<SettingsTextInput
instanceId="settings-roles-search"
LeftIcon={IconSearch}
placeholder={t`Search a role...`}
value={searchTerm}
onChange={setSearchTerm}
/>
</StyledSearchInputContainer>
<Dropdown
dropdownId="settings-roles-filter-dropdown"
dropdownPlacement="bottom-end"
@@ -135,7 +135,9 @@ export const SettingsRolesList = () => {
<SettingsRolesTableHeader />
<StyledTableRows>
{filteredRoles.length === 0 ? (
<StyledNoRoles>{t`No roles found`}</StyledNoRoles>
<TableCell color={themeCssVariables.font.color.tertiary}>
{t`No roles found`}
</TableCell>
) : (
filteredRoles.map((role) => (
<SettingsRolesTableRow key={role.id} role={role} />
@@ -143,15 +145,17 @@ export const SettingsRolesList = () => {
)}
</StyledTableRows>
</Table>
<StyledCreateRoleSection>
<Button
Icon={IconPlus}
title={t`Create Role`}
variant="secondary"
size="small"
onClick={() => navigateSettings(SettingsPath.RoleCreate)}
/>
</StyledCreateRoleSection>
<StyledCreateRoleSectionContainer>
<Section>
<Button
Icon={IconPlus}
title={t`Create Role`}
variant="secondary"
size="small"
onClick={() => navigateSettings(SettingsPath.RoleCreate)}
/>
</Section>
</StyledCreateRoleSectionContainer>
</Section>
);
};
@@ -48,10 +48,12 @@ const StyledIconLockContainer = styled.div`
justify-content: flex-end;
`;
const StyledTableRow = styled(TableRow)`
&:hover {
background: ${themeCssVariables.background.transparent.light};
cursor: pointer;
const StyledTableRowContainer = styled.div`
> * {
&:hover {
background: ${themeCssVariables.background.transparent.light};
cursor: pointer;
}
}
`;
@@ -77,57 +79,61 @@ export const SettingsRolesTableRow = ({ role }: SettingsRolesTableRowProps) => {
.filter(isDefined);
return (
<StyledTableRow
key={role.id}
gridAutoColumns="332px 3fr 2fr 1fr"
to={getSettingsPath(SettingsPath.RoleDetail, { roleId: role.id })}
>
<TableCell>
<StyledNameCell>
<Icon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
{role.label}
{!role.isEditable && (
<StyledIconLockContainer>
<IconLock
color={theme.font.color.light}
stroke={theme.icon.stroke.sm}
size={theme.icon.size.sm}
/>
</StyledIconLockContainer>
)}
</StyledNameCell>
</TableCell>
<TableCell align="right">
<StyledAvatarGroup>
{enrichedWorkspaceMembers.slice(0, 5).map((workspaceMember) => (
<React.Fragment key={workspaceMember.id}>
<div id={`avatar-${workspaceMember.id}`}>
<Avatar
avatarUrl={workspaceMember.avatarUrl}
placeholderColorSeed={workspaceMember.id}
placeholder={workspaceMember.name.firstName ?? ''}
type="rounded"
size="md"
<StyledTableRowContainer>
<TableRow
key={role.id}
gridAutoColumns="332px 3fr 2fr 1fr"
to={getSettingsPath(SettingsPath.RoleDetail, { roleId: role.id })}
>
<TableCell>
<StyledNameCell>
<Icon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
{role.label}
{!role.isEditable && (
<StyledIconLockContainer>
<IconLock
color={theme.font.color.light}
stroke={theme.icon.stroke.sm}
size={theme.icon.size.sm}
/>
</div>
<AppTooltip
anchorSelect={`#avatar-${workspaceMember.id}`}
content={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
noArrow
place="top"
positionStrategy="fixed"
delay={TooltipDelay.shortDelay}
/>
</React.Fragment>
))}
</StyledAvatarGroup>
</TableCell>
<TableCell align="left">
<StyledAssignedText>{role.workspaceMembers.length}</StyledAssignedText>
</TableCell>
<TableCell align="right" color={theme.font.color.tertiary}>
<IconChevronRight size={theme.icon.size.md} />
</TableCell>
</StyledTableRow>
</StyledIconLockContainer>
)}
</StyledNameCell>
</TableCell>
<TableCell align="right">
<StyledAvatarGroup>
{enrichedWorkspaceMembers.slice(0, 5).map((workspaceMember) => (
<React.Fragment key={workspaceMember.id}>
<div id={`avatar-${workspaceMember.id}`}>
<Avatar
avatarUrl={workspaceMember.avatarUrl}
placeholderColorSeed={workspaceMember.id}
placeholder={workspaceMember.name.firstName ?? ''}
type="rounded"
size="md"
/>
</div>
<AppTooltip
anchorSelect={`#avatar-${workspaceMember.id}`}
content={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
noArrow
place="top"
positionStrategy="fixed"
delay={TooltipDelay.shortDelay}
/>
</React.Fragment>
))}
</StyledAvatarGroup>
</TableCell>
<TableCell align="left">
<StyledAssignedText>
{role.workspaceMembers.length}
</StyledAssignedText>
</TableCell>
<TableCell align="right" color={theme.font.color.tertiary}>
<IconChevronRight size={theme.icon.size.md} />
</TableCell>
</TableRow>
</StyledTableRowContainer>
);
};
@@ -27,16 +27,12 @@ const StyledTableRows = styled.div`
padding-block: ${themeCssVariables.spacing[2]};
`;
const StyledEmptyState = styled(TableCell)`
color: ${themeCssVariables.font.color.tertiary};
`;
const StyledSearchContainer = styled.div`
padding-bottom: ${themeCssVariables.spacing[2]};
`;
const StyledSearchInput = styled(SettingsTextInput)`
input {
const StyledSearchInputContainer = styled.div`
> * input {
background: ${themeCssVariables.background.transparent.lighter};
border: 1px solid ${themeCssVariables.border.color.medium};
}
@@ -148,15 +144,17 @@ export const SettingsRoleAssignmentTable = <T extends RoleTargetType>({
description={t`This role is assigned to these ${roleTargetDisplayName}.`}
/>
<StyledSearchContainer>
<StyledSearchInput
instanceId={`role-assignment-${roleTargetType}-search`}
value={searchFilter}
onChange={handleSearchChange}
placeholder={t`Search an assigned ${roleTargetDisplayName}...`}
fullWidth
LeftIcon={IconSearch}
sizeVariant="lg"
/>
<StyledSearchInputContainer>
<SettingsTextInput
instanceId={`role-assignment-${roleTargetType}-search`}
value={searchFilter}
onChange={handleSearchChange}
placeholder={t`Search an assigned ${roleTargetDisplayName}...`}
fullWidth
LeftIcon={IconSearch}
sizeVariant="lg"
/>
</StyledSearchInputContainer>
</StyledSearchContainer>
<StyledTable>
<TableRow gridAutoColumns="2fr 4fr">
@@ -173,9 +171,9 @@ export const SettingsRoleAssignmentTable = <T extends RoleTargetType>({
))}
{filteredRoleTargets.length === 0 && (
<StyledEmptyState>
<TableCell color={themeCssVariables.font.color.tertiary}>
{tableConfig[roleTargetType].emptyStateText}
</StyledEmptyState>
</TableCell>
)}
</StyledTableRows>
</StyledTable>
@@ -38,10 +38,6 @@ const StyledNameContainer = styled.div`
width: 100%;
`;
const StyledTableCell = styled(TableCell)`
overflow: hidden;
`;
export type RoleTarget =
| { type: 'member'; data: PartialWorkspaceMember }
| { type: 'agent'; data: Agent }
@@ -119,17 +115,17 @@ export const SettingsRoleAssignmentTableRow = ({
return (
<TableRow gridAutoColumns="2fr 4fr">
<StyledTableCell>
<TableCell overflow="hidden">
<StyledNameContainer>
<StyledIconWrapper>{renderIcon()}</StyledIconWrapper>
<StyledNameCell>
<OverflowingTextWithTooltip text={renderName()} />
</StyledNameCell>
</StyledNameContainer>
</StyledTableCell>
<StyledTableCell>
</TableCell>
<TableCell overflow="hidden">
<OverflowingTextWithTooltip text={renderSecondaryInfo()} />
</StyledTableCell>
</TableCell>
</TableRow>
);
};
@@ -39,8 +39,8 @@ const StyledSearchContainer = styled.div`
padding-bottom: ${themeCssVariables.spacing[2]};
`;
const StyledSearchInput = styled(SettingsTextInput)`
input {
const StyledSearchInputContainer = styled.div`
> * input {
background: ${themeCssVariables.background.transparent.lighter};
border: 1px solid ${themeCssVariables.border.color.medium};
}
@@ -104,15 +104,17 @@ export const SettingsRolePermissionsObjectLevelObjectPicker = ({
<StyledTypeSelectContainer>
<Section>
<StyledSearchContainer>
<StyledSearchInput
instanceId="role-permissions-object-search"
value={searchFilter}
onChange={handleSearchChange}
placeholder={t`Search an object`}
fullWidth
LeftIcon={IconSearch}
sizeVariant="lg"
/>
<StyledSearchInputContainer>
<SettingsTextInput
instanceId="role-permissions-object-search"
value={searchFilter}
onChange={handleSearchChange}
placeholder={t`Search an object`}
fullWidth
LeftIcon={IconSearch}
sizeVariant="lg"
/>
</StyledSearchInputContainer>
</StyledSearchContainer>
</Section>
@@ -18,12 +18,14 @@ import { Section } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
const StyledCreateObjectOverrideSection = styled(Section)`
border-top: 1px solid ${themeCssVariables.border.color.light};
display: flex;
justify-content: flex-end;
padding-top: ${themeCssVariables.spacing[2]};
padding-bottom: ${themeCssVariables.spacing[2]};
const StyledCreateObjectOverrideSectionContainer = styled.div`
> * {
border-top: 1px solid ${themeCssVariables.border.color.light};
display: flex;
justify-content: flex-end;
padding-top: ${themeCssVariables.spacing[2]};
padding-bottom: ${themeCssVariables.spacing[2]};
}
`;
const StyledTableRows = styled.div`
@@ -38,10 +40,6 @@ type SettingsRolePermissionsObjectLevelSectionProps = {
objectMetadataItemsFromMarketplaceApp?: ObjectMetadataItem[];
};
const StyledNoOverride = styled(TableCell)`
color: ${themeCssVariables.font.color.tertiary};
`;
export const SettingsRolePermissionsObjectLevelSection = ({
roleId,
fromAgentId,
@@ -130,25 +128,27 @@ export const SettingsRolePermissionsObjectLevelSection = ({
),
)
) : (
<StyledNoOverride>
<TableCell color={themeCssVariables.font.color.tertiary}>
{t`No permissions have been set for individual objects.`}
</StyledNoOverride>
</TableCell>
)}
</StyledTableRows>
</Table>
{isEditable && (
<StyledCreateObjectOverrideSection>
<Button
Icon={IconPlus}
title={t`Add rule`}
variant="secondary"
size="small"
disabled={
!settingsDraftRole.isEditable || allObjectsHaveSetPermission
}
onClick={handleAddRule}
/>
</StyledCreateObjectOverrideSection>
<StyledCreateObjectOverrideSectionContainer>
<Section>
<Button
Icon={IconPlus}
title={t`Add rule`}
variant="secondary"
size="small"
disabled={
!settingsDraftRole.isEditable || allObjectsHaveSetPermission
}
onClick={handleAddRule}
/>
</Section>
</StyledCreateObjectOverrideSectionContainer>
)}
</Section>
);
@@ -13,22 +13,12 @@ import { OverflowingTextWithTooltip, useIcons } from 'twenty-ui/display';
import { useContext } from 'react';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledNameTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[1]};
`;
const StyledNameLabel = styled.div`
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
`;
const StyledOptionsTableCell = styled(TableCell)`
justify-content: flex-end;
padding-right: ${themeCssVariables.spacing[1]};
`;
type SettingsRolePermissionsObjectLevelTableRowProps = {
objectMetadataItem: ObjectMetadataItem;
roleId: string;
@@ -62,7 +52,10 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
to={isEditable ? navigationUrl : undefined}
gridAutoColumns={OBJECT_LEVEL_PERMISSION_TABLE_GRID_AUTO_COLUMNS}
>
<StyledNameTableCell>
<TableCell
color={themeCssVariables.font.color.primary}
gap={themeCssVariables.spacing[1]}
>
{!!Icon && (
<Icon
style={{
@@ -75,7 +68,7 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
<StyledNameLabel title={objectLabelPlural}>
<OverflowingTextWithTooltip text={objectLabelPlural} />
</StyledNameLabel>
</StyledNameTableCell>
</TableCell>
<TableCell>
<SettingsRolePermissionsObjectLevelOverrideCellContainer
objectMetadataItem={objectMetadataItem}
@@ -96,14 +89,17 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
/>
</TableCell>
<TableCell></TableCell>
<StyledOptionsTableCell>
<TableCell
align="right"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
>
<SettingsRolePermissionsObjectLevelTableRowOptionsDropdown
roleId={roleId}
objectMetadataId={objectMetadataItem.id}
objectPermissionDetailUrl={navigationUrl}
isEditable={isEditable}
/>
</StyledOptionsTableCell>
</TableCell>
</TableRow>
);
};
@@ -1,9 +1,10 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { filterUserFacingFieldMetadataItems } from '@/object-metadata/utils/filterUserFacingFieldMetadataItems';
import { SettingsRolePermissionsObjectLevelObjectFieldPermissionTableAllHeaderRow } from '@/settings/roles/role-permissions/object-level-permissions/field-permissions/components/SettingsRolePermissionsObjectLevelObjectFieldPermissionTableAllHeaderRow';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import {
FIELD_PERMISSION_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
SettingsRolePermissionsObjectLevelObjectFieldPermissionTableRow,
StyledObjectFieldTableRow,
} from '@/settings/roles/role-permissions/object-level-permissions/field-permissions/components/SettingsRolePermissionsObjectLevelObjectFieldPermissionTableRow';
import { useObjectPermissionDerivedStates } from '@/settings/roles/role-permissions/object-level-permissions/field-permissions/hooks/useObjectPermissionDerivedStates';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
@@ -28,7 +29,7 @@ import { turnOrderByIntoSort } from '~/utils/turnOrderByIntoSort';
export const SETTINGS_ROLE_PERMISSION_OBJECT_LEVEL_FIELD_PERMISSION_TABLE_ID =
'settings-role-permissions-object-level-object-field-permission';
const StyledSearchInput = styled(SettingsTextInput)`
const StyledSearchInputContainer = styled.div`
padding-bottom: ${themeCssVariables.spacing[2]};
width: 100%;
`;
@@ -99,15 +100,19 @@ export const SettingsRolePermissionsObjectLevelObjectFieldPermissionTable = ({
title={t`Fields Permissions`}
description={t`Ability to interact with this object's fields.`}
/>
<StyledSearchInput
instanceId="object-field-table-search"
LeftIcon={IconSearch}
placeholder={t`Search a field...`}
value={searchTerm}
onChange={setSearchTerm}
/>
<StyledSearchInputContainer>
<SettingsTextInput
instanceId="object-field-table-search"
LeftIcon={IconSearch}
placeholder={t`Search a field...`}
value={searchTerm}
onChange={setSearchTerm}
/>
</StyledSearchInputContainer>
<Table>
<StyledObjectFieldTableRow>
<TableRow
gridTemplateColumns={FIELD_PERMISSION_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
>
<SortableTableHeader
fieldName="label"
label={t`Name`}
@@ -132,7 +137,7 @@ export const SettingsRolePermissionsObjectLevelObjectFieldPermissionTable = ({
</TableHeader>
)}
</>
</StyledObjectFieldTableRow>
</TableRow>
<SettingsRolePermissionsObjectLevelObjectFieldPermissionTableAllHeaderRow
roleId={roleId}
objectMetadataItem={objectMetadataItem}
@@ -20,14 +20,8 @@ import {
RelationType,
} from '~/generated-metadata/graphql';
export const StyledObjectFieldTableRow = styled(TableRow)`
grid-template-columns: 180px minmax(0, 1fr) 60px 60px;
`;
const StyledNameTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[2]};
`;
export const FIELD_PERMISSION_TABLE_ROW_GRID_TEMPLATE_COLUMNS =
'180px minmax(0, 1fr) 60px 60px';
const StyledNameLabel = styled.div`
white-space: nowrap;
@@ -156,8 +150,13 @@ export const SettingsRolePermissionsObjectLevelObjectFieldPermissionTableRow =
const shouldShowEmptyTableHeader = cannotAllowFieldUpdateRestrict;
return (
<StyledObjectFieldTableRow>
<StyledNameTableCell>
<TableRow
gridTemplateColumns={FIELD_PERMISSION_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
>
<TableCell
color={themeCssVariables.font.color.primary}
gap={themeCssVariables.spacing[2]}
>
{!!Icon && (
<Icon
style={{
@@ -170,7 +169,7 @@ export const SettingsRolePermissionsObjectLevelObjectFieldPermissionTableRow =
<StyledNameLabel title={fieldMetadataItem.label}>
{fieldMetadataItem.label}
</StyledNameLabel>
</StyledNameTableCell>
</TableCell>
<TableCell>
<SettingsObjectFieldDataType
Icon={RelationIcon}
@@ -212,6 +211,6 @@ export const SettingsRolePermissionsObjectLevelObjectFieldPermissionTableRow =
</TableCell>
)}
</>
</StyledObjectFieldTableRow>
</TableRow>
);
};
@@ -15,20 +15,6 @@ import { isDefined } from 'twenty-shared/utils';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { type ObjectPermission, type Role } from '~/generated-metadata/graphql';
const StyledTableRow = styled(TableRow)<{ isDisabled: boolean }>`
align-items: center;
display: flex;
cursor: ${({ isDisabled }) => (isDisabled ? 'default' : 'pointer')};
`;
const StyledPermissionCell = styled(TableCell)`
align-items: center;
display: flex;
flex: 1;
gap: ${themeCssVariables.spacing[1]};
padding-left: ${themeCssVariables.spacing[2]};
`;
const StyledPermissionContent = styled.div`
align-items: center;
display: flex;
@@ -46,13 +32,6 @@ const StyledOverrideInfo = styled.div`
gap: ${themeCssVariables.spacing[1]};
`;
const StyledCheckboxCell = styled(TableCell)`
align-items: center;
display: flex;
justify-content: flex-end;
padding-right: ${themeCssVariables.spacing[1]};
`;
type OverridableCheckboxType = 'no_cta' | 'default' | 'override';
type SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps = {
@@ -146,8 +125,11 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
);
return (
<StyledTableRow onClick={handleCheckboxChange} isDisabled={!isEditable}>
<StyledPermissionCell>
<TableRow
onClick={handleCheckboxChange}
cursor={!isEditable ? 'default' : 'pointer'}
>
<TableCell gap={themeCssVariables.spacing[1]}>
<StyledPermissionContent>
<PermissionIcon
permission={permission.key as SettingsRoleObjectPermissionKey}
@@ -173,15 +155,19 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
</>
) : null}
</StyledOverrideInfo>
</StyledPermissionCell>
<StyledCheckboxCell onClick={(e) => e.stopPropagation()}>
</TableCell>
<TableCell
align="right"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
onClick={(e) => e.stopPropagation()}
>
<OverridableCheckbox
onChange={handleCheckboxChange}
disabled={!isEditable}
type={checkboxType}
checked={isChecked}
/>
</StyledCheckboxCell>
</StyledTableRow>
</TableCell>
</TableRow>
);
};
@@ -2,7 +2,6 @@
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { Pill } from 'twenty-ui/components';
import { H2Title, IconArrowUp, IconLock } from 'twenty-ui/display';
import { Card, Section } from 'twenty-ui/layout';
@@ -21,17 +20,20 @@ const StyledContent = styled.div`
padding-top: ${themeCssVariables.spacing[4]};
`;
const StyledCard = styled(Card)`
const StyledCardContainer = styled.div`
margin-top: ${themeCssVariables.spacing[4]};
overflow: hidden;
`;
const StyledPill = styled(Pill)`
border-radius: 40px;
border: 1px solid ${themeCssVariables.border.color.light};
const StyledPillContainer = styled.span`
align-items: center;
background: ${themeCssVariables.background.secondary};
border: 1px solid ${themeCssVariables.border.color.light};
border-radius: 40px;
color: ${themeCssVariables.font.color.tertiary};
display: inline-flex;
font-weight: ${themeCssVariables.font.weight.medium};
gap: ${themeCssVariables.spacing[1]};
padding: ${themeCssVariables.spacing[1]} ${themeCssVariables.spacing[2]};
`;
@@ -56,27 +58,34 @@ export const SettingsRolePermissionsObjectLevelRecordLevelSection = ({
<H2Title
title={t`Record-level`}
description={t`Ability to filter the records a user can interact with`}
adornment={<StyledPill label={t`Organization`} Icon={IconLock} />}
adornment={
<StyledPillContainer>
<IconLock size={12} />
{t`Organization`}
</StyledPillContainer>
}
/>
<StyledCard rounded>
<SettingsOptionCardContentButton
Icon={IconLock}
title={t`Upgrade to access`}
description={t`This feature is part of the Organization Plan`}
Button={
isBillingEnabled && (
<Button
title={t`Upgrade`}
variant="primary"
accent="blue"
size="small"
Icon={IconArrowUp}
onClick={() => navigateSettings(SettingsPath.Billing)}
/>
)
}
/>
</StyledCard>
<StyledCardContainer>
<Card rounded>
<SettingsOptionCardContentButton
Icon={IconLock}
title={t`Upgrade to access`}
description={t`This feature is part of the Organization Plan`}
Button={
isBillingEnabled && (
<Button
title={t`Upgrade`}
variant="primary"
accent="blue"
size="small"
Icon={IconArrowUp}
onClick={() => navigateSettings(SettingsPath.Billing)}
/>
)
}
/>
</Card>
</StyledCardContainer>
</Section>
);
}
@@ -4,22 +4,10 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { Checkbox } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledNameHeader = styled(TableHeader)`
flex: 1;
`;
const StyledActionsHeader = styled(TableHeader)`
align-items: center;
display: flex;
justify-content: flex-end;
padding-right: ${themeCssVariables.spacing[1]};
`;
type SettingsRolePermissionsObjectsTableHeaderProps = {
roleId: string;
objectPermissionsConfig: SettingsRolePermissionsObjectPermission[];
@@ -50,8 +38,12 @@ export const SettingsRolePermissionsObjectsTableHeader = ({
return (
<TableRow>
<StyledNameHeader>{t`All Objects`}</StyledNameHeader>
<StyledActionsHeader aria-label={t`Actions`}>
<TableHeader>{t`All Objects`}</TableHeader>
<TableHeader
align="right"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
aria-label={t`Actions`}
>
<Checkbox
checked={allPermissionsEnabled}
indeterminate={somePermissionsEnabled && !allPermissionsEnabled}
@@ -69,7 +61,7 @@ export const SettingsRolePermissionsObjectsTableHeader = ({
});
}}
/>
</StyledActionsHeader>
</TableHeader>
</TableRow>
);
};
@@ -8,14 +8,6 @@ import { plural } from '@lingui/core/macro';
import { Checkbox, CheckboxAccent } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledPermissionCell = styled(TableCell)`
align-items: center;
display: flex;
flex: 1;
gap: ${themeCssVariables.spacing[1]};
padding-left: ${themeCssVariables.spacing[2]};
`;
const StyledPermissionContent = styled.div`
align-items: center;
display: flex;
@@ -32,18 +24,6 @@ const StyledOverrideInfo = styled.div`
display: flex;
gap: ${themeCssVariables.spacing[1]};
`;
const StyledCheckboxCell = styled(TableCell)`
align-items: center;
display: flex;
justify-content: flex-end;
padding-right: ${themeCssVariables.spacing[1]};
`;
const StyledTableRow = styled(TableRow)<{ isDisabled: boolean }>`
align-items: center;
display: flex;
cursor: ${({ isDisabled }) => (isDisabled ? 'default' : 'pointer')};
`;
type SettingsRolePermissionsObjectsTableRowProps = {
permission: SettingsRolePermissionsObjectPermission;
@@ -68,8 +48,11 @@ export const SettingsRolePermissionsObjectsTableRow = ({
};
return (
<StyledTableRow onClick={handleRowClick} isDisabled={isDisabled}>
<StyledPermissionCell>
<TableRow
onClick={handleRowClick}
cursor={isDisabled ? 'default' : 'pointer'}
>
<TableCell gap={themeCssVariables.spacing[1]}>
<StyledPermissionContent>
<PermissionIcon
permission={permission.key as SettingsRoleObjectPermissionKey}
@@ -96,15 +79,19 @@ export const SettingsRolePermissionsObjectsTableRow = ({
</>
) : null}
</StyledOverrideInfo>
</StyledPermissionCell>
<StyledCheckboxCell onClick={(e) => e.stopPropagation()}>
</TableCell>
<TableCell
align="right"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
onClick={(e) => e.stopPropagation()}
>
<Checkbox
checked={permission.value ?? false}
onChange={() => permission.setValue(!permission.value)}
disabled={isDisabled}
accent={isRevoked ? CheckboxAccent.Orange : CheckboxAccent.Blue}
/>
</StyledCheckboxCell>
</StyledTableRow>
</TableCell>
</TableRow>
);
};
@@ -20,7 +20,7 @@ const StyledTableRows = styled.div`
padding-top: ${themeCssVariables.spacing[2]};
`;
const StyledCard = styled(Card)`
const StyledCardContainer = styled.div`
margin-bottom: ${themeCssVariables.spacing[4]};
`;
@@ -58,21 +58,23 @@ export const SettingsRolePermissionsSettingsSection = ({
<Section>
<H2Title title={t`Settings`} description={t`Settings permissions`} />
{shouldShowAllAccessToggle && (
<StyledCard rounded>
<SettingsOptionCardContentToggle
Icon={IconSettings}
title={t`Settings All Access`}
description={t`Ability to edit all settings`}
checked={settingsDraftRole.canUpdateAllSettings}
disabled={!isEditable}
onChange={() => {
setSettingsDraftRole({
...settingsDraftRole,
canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings,
});
}}
/>
</StyledCard>
<StyledCardContainer>
<Card rounded>
<SettingsOptionCardContentToggle
Icon={IconSettings}
title={t`Settings All Access`}
description={t`Ability to edit all settings`}
checked={settingsDraftRole.canUpdateAllSettings}
disabled={!isEditable}
onChange={() => {
setSettingsDraftRole({
...settingsDraftRole,
canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings,
});
}}
/>
</Card>
</StyledCardContainer>
)}
<AnimatedExpandableContainer
isExpanded={
@@ -1,6 +1,5 @@
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { Checkbox } from 'twenty-ui/input';
@@ -17,13 +16,6 @@ type SettingsRolePermissionsSettingsTableHeaderProps = {
isEditable: boolean;
};
const StyledActionsHeader = styled(TableHeader)`
align-items: center;
display: flex;
justify-content: flex-end;
padding-right: ${themeCssVariables.spacing[1]};
`;
export const SettingsRolePermissionsSettingsTableHeader = ({
roleId,
settingsPermissionsConfig,
@@ -55,7 +47,11 @@ export const SettingsRolePermissionsSettingsTableHeader = ({
<TableRow gridAutoColumns="3fr 4fr 24px">
<TableHeader>{t`Name`}</TableHeader>
<TableHeader>{t`Description`}</TableHeader>
<StyledActionsHeader aria-label={t`Actions`}>
<TableHeader
align="right"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
aria-label={t`Actions`}
>
<Checkbox
checked={allSettingsPermissionsEnabled}
indeterminate={
@@ -78,7 +74,7 @@ export const SettingsRolePermissionsSettingsTableHeader = ({
});
}}
/>
</StyledActionsHeader>
</TableHeader>
</TableRow>
);
};
@@ -10,32 +10,14 @@ import { useContext } from 'react';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { v4 } from 'uuid';
const StyledTableRow = styled(TableRow)<{ isDisabled: boolean }>`
cursor: ${({ isDisabled }) => (isDisabled ? 'default' : 'pointer')};
`;
const StyledName = styled.span`
color: ${themeCssVariables.font.color.primary};
`;
const StyledDescription = styled(StyledName)`
const StyledDescription = styled.span`
color: ${themeCssVariables.font.color.secondary};
`;
const StyledPermissionCell = styled(TableCell)`
align-items: center;
display: flex;
flex: 1;
gap: ${themeCssVariables.spacing[2]};
`;
const StyledCheckboxCell = styled(TableCell)`
align-items: center;
display: flex;
justify-content: flex-end;
padding-right: ${themeCssVariables.spacing[1]};
`;
const StyledIconContainer = styled.div`
display: flex;
align-items: center;
@@ -112,13 +94,13 @@ export const SettingsRolePermissionsSettingsTableRow = ({
};
return (
<StyledTableRow
<TableRow
key={permission.key}
gridAutoColumns="3fr 4fr 24px"
onClick={handleRowClick}
isDisabled={isDisabled}
cursor={isDisabled ? 'default' : 'pointer'}
>
<StyledPermissionCell>
<TableCell gap={themeCssVariables.spacing[2]}>
<StyledIconContainer>
<permission.Icon
size={theme.icon.size.md}
@@ -127,17 +109,21 @@ export const SettingsRolePermissionsSettingsTableRow = ({
/>
</StyledIconContainer>
<StyledName>{permission.name}</StyledName>
</StyledPermissionCell>
<StyledPermissionCell>
</TableCell>
<TableCell gap={themeCssVariables.spacing[2]}>
<StyledDescription>{permission.description}</StyledDescription>
</StyledPermissionCell>
<StyledCheckboxCell onClick={(e) => e.stopPropagation()}>
</TableCell>
<TableCell
align="right"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
onClick={(e) => e.stopPropagation()}
>
<Checkbox
checked={isChecked}
disabled={isDisabled}
onChange={(event) => handleChange(event.target.checked)}
/>
</StyledCheckboxCell>
</StyledTableRow>
</TableCell>
</TableRow>
);
};
@@ -21,7 +21,7 @@ const StyledTableRows = styled.div`
padding-top: ${themeCssVariables.spacing[2]};
`;
const StyledCard = styled(Card)`
const StyledCardContainer = styled.div`
margin-bottom: ${themeCssVariables.spacing[4]};
`;
@@ -59,21 +59,23 @@ export const SettingsRolePermissionsToolSection = ({
<Section>
<H2Title title={t`Actions`} description={t`Actions permissions`} />
{shouldShowAllAccessToggle && (
<StyledCard rounded>
<SettingsOptionCardContentToggle
Icon={IconTool}
title={t`All Actions Access`}
description={t`Grants permission to perform all available actions without restriction`}
checked={settingsDraftRole.canAccessAllTools}
disabled={!isEditable}
onChange={() => {
setSettingsDraftRole({
...settingsDraftRole,
canAccessAllTools: !settingsDraftRole.canAccessAllTools,
});
}}
/>
</StyledCard>
<StyledCardContainer>
<Card rounded>
<SettingsOptionCardContentToggle
Icon={IconTool}
title={t`All Actions Access`}
description={t`Grants permission to perform all available actions without restriction`}
checked={settingsDraftRole.canAccessAllTools}
disabled={!isEditable}
onChange={() => {
setSettingsDraftRole({
...settingsDraftRole,
canAccessAllTools: !settingsDraftRole.canAccessAllTools,
});
}}
/>
</Card>
</StyledCardContainer>
)}
<AnimatedExpandableContainer
isExpanded={
@@ -18,9 +18,12 @@ import { getSettingsPath } from 'twenty-shared/utils';
import { IconKey } from 'twenty-ui/display';
import { useGetSsoIdentityProvidersQuery } from '~/generated-metadata/graphql';
const StyledLink = styled(Link)<{ isDisabled: boolean }>`
const StyledLinkContainer = styled.div<{ isDisabled: boolean }>`
pointer-events: ${({ isDisabled }) => (isDisabled ? 'none' : 'auto')};
text-decoration: none;
> a {
text-decoration: none;
}
`;
export const SettingsSSOIdentitiesProvidersListCard = () => {
@@ -48,16 +51,17 @@ export const SettingsSSOIdentitiesProvidersListCard = () => {
});
return loading || !SSOIdentitiesProviders.length ? (
<StyledLink
to={getSettingsPath(SettingsPath.NewSSOIdentityProvider)}
<StyledLinkContainer
isDisabled={currentWorkspace?.hasValidEnterpriseKey !== true}
>
<SettingsCard
title={t`Add SSO Identity Provider`}
disabled={currentWorkspace?.hasValidEnterpriseKey !== true}
Icon={<IconKey />}
/>
</StyledLink>
<Link to={getSettingsPath(SettingsPath.NewSSOIdentityProvider)}>
<SettingsCard
title={t`Add SSO Identity Provider`}
disabled={currentWorkspace?.hasValidEnterpriseKey !== true}
Icon={<IconKey />}
/>
</Link>
</StyledLinkContainer>
) : (
<SettingsSSOIdentitiesProvidersListCardWrapper />
);
@@ -19,8 +19,10 @@ import { useGetApprovedAccessDomainsQuery } from '~/generated-metadata/graphql';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
const StyledLink = styled(Link)`
text-decoration: none;
const StyledLinkContainer = styled.div`
> a {
text-decoration: none;
}
`;
export const SettingsApprovedAccessDomainsListCard = () => {
@@ -54,12 +56,14 @@ export const SettingsApprovedAccessDomainsListCard = () => {
};
return loading || !approvedAccessDomains.length ? (
<StyledLink to={getSettingsPath(SettingsPath.NewApprovedAccessDomain)}>
<SettingsCard
title={t`Add Approved Access Domain`}
Icon={<IconMailCog />}
/>
</StyledLink>
<StyledLinkContainer>
<Link to={getSettingsPath(SettingsPath.NewApprovedAccessDomain)}>
<SettingsCard
title={t`Add Approved Access Domain`}
Icon={<IconMailCog />}
/>
</Link>
</StyledLinkContainer>
) : (
<>
<SettingsSecurityApprovedAccessDomainValidationEffect />