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:
+17
-7
@@ -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>
|
||||
}
|
||||
|
||||
+21
-11
@@ -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>
|
||||
);
|
||||
|
||||
+10
-8
@@ -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"
|
||||
|
||||
+11
-9
@@ -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>
|
||||
|
||||
+220
-199
@@ -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>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
+31
-18
@@ -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 : (
|
||||
|
||||
+35
-27
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+30
-21
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+20
-16
@@ -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} />
|
||||
|
||||
+17
-23
@@ -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
-3
@@ -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
|
||||
|
||||
+24
-24
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+8
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
-21
@@ -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]};
|
||||
`;
|
||||
+29
@@ -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}
|
||||
/>
|
||||
);
|
||||
+51
-42
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+16
-11
@@ -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}
|
||||
|
||||
+19
-15
@@ -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}
|
||||
|
||||
+2
-2
@@ -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};
|
||||
|
||||
+24
-20
@@ -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>
|
||||
)}
|
||||
{[
|
||||
|
||||
+40
-27
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user