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,12 +13,14 @@ type CalendarDayCardContentProps = {
divider?: boolean;
};
const StyledCardContent = styled(CardContent)`
align-items: flex-start;
display: flex;
flex-direction: row;
gap: ${themeCssVariables.spacing[3]};
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[3]};
const StyledCardContentContainer = styled.div`
> * {
align-items: flex-start;
display: flex;
flex-direction: row;
gap: ${themeCssVariables.spacing[3]};
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[3]};
}
`;
const StyledDayContainer = styled.div`
@@ -44,7 +46,7 @@ const StyledEvents = styled.div`
gap: ${themeCssVariables.spacing[3]};
`;
const StyledEventRow = styled(CalendarEventRow)`
const StyledEventRowContainer = styled.div`
flex: 1 0 auto;
`;
@@ -67,28 +69,29 @@ export const CalendarDayCardContent = ({
};
return (
<StyledCardContent
divider={divider}
initial="upcoming"
animate="ended"
variants={upcomingDayCardContentVariants}
transition={{
delay: Math.max(0, dayEndsIn),
duration: theme.animation.duration.fast,
}}
>
<StyledDayContainer>
<StyledWeekDay>{weekDayLabel}</StyledWeekDay>
<StyledMonthDay>{monthDayLabel}</StyledMonthDay>
</StyledDayContainer>
<StyledEvents>
{calendarEvents.map((calendarEvent) => (
<StyledEventRow
key={calendarEvent.id}
calendarEvent={calendarEvent}
/>
))}
</StyledEvents>
</StyledCardContent>
<StyledCardContentContainer>
<CardContent
divider={divider}
initial="upcoming"
animate="ended"
variants={upcomingDayCardContentVariants}
transition={{
delay: Math.max(0, dayEndsIn),
duration: theme.animation.duration.fast,
}}
>
<StyledDayContainer>
<StyledWeekDay>{weekDayLabel}</StyledWeekDay>
<StyledMonthDay>{monthDayLabel}</StyledMonthDay>
</StyledDayContainer>
<StyledEvents>
{calendarEvents.map((calendarEvent) => (
<StyledEventRowContainer key={calendarEvent.id}>
<CalendarEventRow calendarEvent={calendarEvent} />
</StyledEventRowContainer>
))}
</StyledEvents>
</CardContent>
</StyledCardContentContainer>
);
};
@@ -85,7 +85,7 @@ const StyledFields = styled.div`
width: 100%;
`;
const StyledPropertyBox = styled(PropertyBox)`
const StyledPropertyBoxContainer = styled.div`
height: ${themeCssVariables.spacing[6]};
width: 100%;
`;
@@ -165,35 +165,37 @@ export const CalendarEventDetails = ({
});
return (
<StyledPropertyBox key={fieldMetadataItem.id}>
<FieldContext.Provider
value={{
recordId: calendarEvent.id,
isLabelIdentifier: false,
fieldDefinition: formatFieldMetadataItemAsFieldDefinition({
field: fieldMetadataItem,
objectMetadataItem,
showLabel: true,
labelWidth: 72,
}),
useUpdateRecord: useUpdateOneCalendarEventRecordMutation,
maxWidth: 300,
isRecordFieldReadOnly: isReadOnly,
}}
>
<RecordFieldComponentInstanceContext.Provider
<StyledPropertyBoxContainer key={fieldMetadataItem.id}>
<PropertyBox>
<FieldContext.Provider
value={{
instanceId: getRecordFieldInputInstanceId({
recordId: calendarEvent.id,
fieldName: fieldMetadataItem.name,
prefix: INPUT_ID_PREFIX,
recordId: calendarEvent.id,
isLabelIdentifier: false,
fieldDefinition: formatFieldMetadataItemAsFieldDefinition({
field: fieldMetadataItem,
objectMetadataItem,
showLabel: true,
labelWidth: 72,
}),
useUpdateRecord: useUpdateOneCalendarEventRecordMutation,
maxWidth: 300,
isRecordFieldReadOnly: isReadOnly,
}}
>
<RecordInlineCell />
</RecordFieldComponentInstanceContext.Provider>
</FieldContext.Provider>
</StyledPropertyBox>
<RecordFieldComponentInstanceContext.Provider
value={{
instanceId: getRecordFieldInputInstanceId({
recordId: calendarEvent.id,
fieldName: fieldMetadataItem.name,
prefix: INPUT_ID_PREFIX,
}),
}}
>
<RecordInlineCell />
</RecordFieldComponentInstanceContext.Provider>
</FieldContext.Provider>
</PropertyBox>
</StyledPropertyBoxContainer>
);
};
@@ -5,15 +5,18 @@ import { IconLock } from 'twenty-ui/display';
import { Card, CardContent } from 'twenty-ui/layout';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledVisibilityCard = styled(Card)`
const StyledVisibilityCardContainer = styled.div`
color: ${themeCssVariables.font.color.light};
flex: 1;
border-color: ${themeCssVariables.border.color.light};
transition: color ${themeCssVariables.animation.duration.normal} ease;
width: 100%;
> * {
border-color: ${themeCssVariables.border.color.light};
}
`;
const StyledVisibilityCardContent = styled(CardContent)`
const StyledVisibilityCardContentContainer = styled.div`
align-items: center;
background-color: ${themeCssVariables.background.transparent.lighter};
box-sizing: border-box;
@@ -29,11 +32,15 @@ export const CalendarEventNotSharedContent = () => {
const { theme } = useContext(ThemeContext);
return (
<StyledVisibilityCard>
<StyledVisibilityCardContent>
<IconLock size={theme.icon.size.sm} />
<Trans>Not shared</Trans>
</StyledVisibilityCardContent>
</StyledVisibilityCard>
<StyledVisibilityCardContainer>
<Card>
<StyledVisibilityCardContentContainer>
<CardContent>
<IconLock size={theme.icon.size.sm} />
<Trans>Not shared</Trans>
</CardContent>
</StyledVisibilityCardContentContainer>
</Card>
</StyledVisibilityCardContainer>
);
};
@@ -21,7 +21,7 @@ const StyledInlineCellBaseContainer = styled.div`
user-select: none;
`;
const StyledPropertyBox = styled(PropertyBox)`
const StyledPropertyBoxContainer = styled.div`
height: ${themeCssVariables.spacing[6]};
width: 100%;
`;
@@ -87,19 +87,21 @@ export const CalendarEventParticipantsResponseStatusField = ({
));
return (
<StyledPropertyBox>
<StyledInlineCellBaseContainer>
<StyledLabelAndIconContainer>
<StyledIconContainer>{Icon}</StyledIconContainer>
<StyledPropertyBoxContainer>
<PropertyBox>
<StyledInlineCellBaseContainer>
<StyledLabelAndIconContainer>
<StyledIconContainer>{Icon}</StyledIconContainer>
<StyledLabelContainer width={72}>
<EllipsisDisplay>{responseStatus}</EllipsisDisplay>
</StyledLabelContainer>
</StyledLabelAndIconContainer>
<StyledDiv ref={participantsContainerRef}>
<ExpandableList isChipCountDisplayed>{styledChips}</ExpandableList>
</StyledDiv>
</StyledInlineCellBaseContainer>
</StyledPropertyBox>
<StyledLabelContainer width={72}>
<EllipsisDisplay>{responseStatus}</EllipsisDisplay>
</StyledLabelContainer>
</StyledLabelAndIconContainer>
<StyledDiv ref={participantsContainerRef}>
<ExpandableList isChipCountDisplayed>{styledChips}</ExpandableList>
</StyledDiv>
</StyledInlineCellBaseContainer>
</PropertyBox>
</StyledPropertyBoxContainer>
);
};
@@ -2,16 +2,20 @@ import { styled } from '@linaria/react';
import { Card } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledList = styled(Card)`
& > :not(:last-child) {
border-bottom: 1px solid ${themeCssVariables.border.color.light};
const StyledListContainer = styled.div`
> * {
& > :not(:last-child) {
border-bottom: 1px solid ${themeCssVariables.border.color.light};
}
overflow: auto;
width: calc(100% - 2px);
}
width: calc(100% - 2px);
overflow: auto;
`;
export const ActivityList = ({ children }: React.PropsWithChildren) => {
return <StyledList>{children}</StyledList>;
return (
<StyledListContainer>
<Card>{children}</Card>
</StyledListContainer>
);
};
@@ -3,14 +3,14 @@ import React from 'react';
import { CardContent } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledRowContent = styled(CardContent)`
const StyledRowContentContainer = styled.div`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[2]};
height: ${themeCssVariables.spacing[12]};
padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[4]};
&[data-clickable='false'] {
> *[data-clickable='false'] {
cursor: default;
}
`;
@@ -30,8 +30,10 @@ export const ActivityRow = ({
};
return (
<StyledRowContent onClick={handleClick} isClickable={disabled !== true}>
{children}
</StyledRowContent>
<StyledRowContentContainer>
<CardContent onClick={handleClick} isClickable={disabled !== true}>
{children}
</CardContent>
</StyledRowContentContainer>
);
};
@@ -7,7 +7,7 @@ import { RecordChip } from '@/object-record/components/RecordChip';
import { Avatar } from 'twenty-ui/display';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledAvatar = styled(Avatar)`
const StyledAvatarContainer = styled.span`
margin-right: ${themeCssVariables.spacing[1]};
`;
@@ -67,12 +67,14 @@ export const ParticipantChip = ({
/>
) : (
<StyledChip>
<StyledAvatar
avatarUrl={avatarUrl}
type="rounded"
placeholder={displayName}
size="sm"
/>
<StyledAvatarContainer>
<Avatar
avatarUrl={avatarUrl}
type="rounded"
placeholder={displayName}
size="sm"
/>
</StyledAvatarContainer>
<StyledSenderName variant={variant}>{displayName}</StyledSenderName>
</StyledChip>
)}
@@ -38,9 +38,11 @@ const StyledContainer = styled.div`
overflow: auto;
`;
const StyledH1Title = styled(H1Title)`
display: flex;
gap: ${themeCssVariables.spacing[2]};
const StyledH1TitleWrapper = styled.div`
> h2 {
display: flex;
gap: ${themeCssVariables.spacing[2]};
}
`;
const StyledEmailCount = styled.span`
@@ -107,15 +109,17 @@ export const EmailsCard = () => {
return (
<StyledContainer>
<Section>
<StyledH1Title
title={
<>
<Trans>Inbox</Trans>{' '}
<StyledEmailCount>{totalNumberOfThreads}</StyledEmailCount>
</>
}
fontColor={H1TitleFontColor.Primary}
/>
<StyledH1TitleWrapper>
<H1Title
title={
<>
<Trans>Inbox</Trans>{' '}
<StyledEmailCount>{totalNumberOfThreads}</StyledEmailCount>
</>
}
fontColor={H1TitleFontColor.Primary}
/>
</StyledH1TitleWrapper>
{!firstQueryLoading && (
<ActivityList>
{timelineThreads?.map((thread: TimelineThread) => (
@@ -34,8 +34,9 @@ const StyledUploadDragSubTitle = styled.div`
line-height: ${themeCssVariables.text.lineHeight.md};
`;
const StyledUploadIcon = styled(IconUpload)`
const StyledUploadIconContainer = styled.div`
color: ${themeCssVariables.font.color.tertiary};
display: flex;
margin-bottom: ${themeCssVariables.spacing[3]};
`;
@@ -83,10 +84,12 @@ export const DropZone = ({
// eslint-disable-next-line react/jsx-props-no-spreading
{...getInputProps()}
/>
<StyledUploadIcon
stroke={theme.icon.stroke.sm}
size={theme.icon.size.lg}
/>
<StyledUploadIconContainer>
<IconUpload
stroke={theme.icon.stroke.sm}
size={theme.icon.size.lg}
/>
</StyledUploadIconContainer>
<StyledUploadDragTitle>{t`Upload files`}</StyledUploadDragTitle>
<StyledUploadDragSubTitle>
{t`Drag and Drop Here`}
@@ -38,11 +38,11 @@ const StyledMainContainer = styled.div`
}
`;
const StyledSidePanelAnimatedPlaceholderEmptyContainer = styled(
AnimatedPlaceholderEmptyContainer,
)`
height: auto;
padding-top: ${themeCssVariables.spacing[8]};
const StyledSidePanelPlaceholderWrapper = styled.div`
> * {
height: auto;
padding-top: ${themeCssVariables.spacing[8]};
}
`;
export const TimelineCard = () => {
@@ -59,12 +59,8 @@ export const TimelineCard = () => {
}
if (isTimelineActivitiesEmpty) {
const EmptyContainer = isInSidePanel
? StyledSidePanelAnimatedPlaceholderEmptyContainer
: AnimatedPlaceholderEmptyContainer;
return (
<EmptyContainer
const placeholderContent = (
<AnimatedPlaceholderEmptyContainer
// eslint-disable-next-line react/jsx-props-no-spreading
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
>
@@ -77,7 +73,15 @@ export const TimelineCard = () => {
{t`There is no activity associated with this record.`}
</AnimatedPlaceholderEmptySubTitle>
</AnimatedPlaceholderEmptyTextContainer>
</EmptyContainer>
</AnimatedPlaceholderEmptyContainer>
);
return isInSidePanel ? (
<StyledSidePanelPlaceholderWrapper>
{placeholderContent}
</StyledSidePanelPlaceholderWrapper>
) : (
placeholderContent
);
}
@@ -1,5 +1,4 @@
import { styled } from '@linaria/react';
import { Card } from 'twenty-ui/layout';
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
type EventCardProps = {
@@ -22,7 +21,7 @@ const StyledCardContainer = styled.div`
}
`;
const StyledCard = styled(Card)`
const StyledCardInnerContainer = styled.div`
background: ${themeCssVariables.background.secondary};
border: 1px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.md};
@@ -40,7 +39,7 @@ export const EventCard = ({ children, isOpen }: EventCardProps) => {
return (
isOpen && (
<StyledCardContainer>
<StyledCard fullWidth>{children}</StyledCard>
<StyledCardInnerContainer>{children}</StyledCardInnerContainer>
</StyledCardContainer>
)
);
@@ -26,8 +26,12 @@ export const StyledEventRowItemColumn = styled.div`
gap: ${themeCssVariables.spacing[1]};
`;
export const StyledEventRowItemAction = styled(StyledEventRowItemColumn)`
export const StyledEventRowItemAction = styled.div`
align-items: center;
color: ${themeCssVariables.font.color.secondary};
display: flex;
flex-direction: row;
gap: ${themeCssVariables.spacing[1]};
`;
export const EventRowDynamicComponent = ({
@@ -13,7 +13,7 @@ type BubbleMenuIconButtonProps = {
isActive?: boolean;
};
const StyledBubbleMenuIconButton = styled(FloatingIconButton)`
const StyledBubbleMenuIconButtonContainer = styled.div`
border: none;
border-radius: ${themeCssVariables.spacing[1.5]};
width: ${themeCssVariables.spacing[6]};
@@ -29,17 +29,18 @@ export const BubbleMenuIconButton = ({
isActive,
}: BubbleMenuIconButtonProps) => {
return (
<StyledBubbleMenuIconButton
className={className}
Icon={Icon}
disabled={disabled}
focus={focus}
onClick={onClick}
isActive={isActive}
applyShadow={false}
applyBlur={false}
size="medium"
position="standalone"
/>
<StyledBubbleMenuIconButtonContainer className={className}>
<FloatingIconButton
Icon={Icon}
disabled={disabled}
focus={focus}
onClick={onClick}
isActive={isActive}
applyShadow={false}
applyBlur={false}
size="medium"
position="standalone"
/>
</StyledBubbleMenuIconButtonContainer>
);
};
@@ -27,8 +27,9 @@ const StyledLoadingIconContainer = styled.div`
padding-inline: ${themeCssVariables.spacing[1]};
`;
const StyledLoadingIcon = styled(IconDotsVertical)`
const StyledLoadingIconWrapper = styled.span`
color: ${themeCssVariables.font.color.light};
display: flex;
transform: rotate(90deg);
`;
@@ -37,7 +38,9 @@ const InitialLoadingIndicator = () => {
return (
<StyledLoadingIconContainer>
<StyledLoadingIcon size={theme.icon.size.xl} />
<StyledLoadingIconWrapper>
<IconDotsVertical size={theme.icon.size.xl} />
</StyledLoadingIconWrapper>
</StyledLoadingIconContainer>
);
};
@@ -115,12 +115,14 @@ const StyledRightButtonsContainer = styled.div`
gap: ${themeCssVariables.spacing[1]};
`;
const StyledReadOnlyModelButton = styled(LightButton)`
cursor: default;
const StyledReadOnlyModelButtonContainer = styled.div`
> * {
cursor: default;
&:hover,
&:active {
background: transparent;
&:hover,
&:active {
background: transparent;
}
}
`;
@@ -163,10 +165,9 @@ export const AIChatTab = () => {
<AIChatContextUsageButton />
</StyledLeftButtonsContainer>
<StyledRightButtonsContainer>
<StyledReadOnlyModelButton
accent="tertiary"
title={smartModelLabel}
/>
<StyledReadOnlyModelButtonContainer>
<LightButton accent="tertiary" title={smartModelLabel} />
</StyledReadOnlyModelButtonContainer>
<SendMessageButton onSend={handleSendAndClear} />
</StyledRightButtonsContainer>
</StyledButtonsContainer>
@@ -8,7 +8,7 @@ import { styled } from '@linaria/react';
import { isNonEmptyArray } from '@sniptt/guards';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledScrollWrapper = styled(ScrollWrapper)`
const StyledScrollWrapperContainer = styled.div`
display: flex;
flex: 1;
flex-direction: column;
@@ -30,11 +30,13 @@ export const AIChatTabMessageList = () => {
}
return (
<StyledScrollWrapper componentInstanceId={AI_CHAT_SCROLL_WRAPPER_ID}>
{agentChatMessageIdsComponent.map((messageId) => {
return <AIChatMessage messageId={messageId} key={messageId} />;
})}
<AIChatErrorUnderMessageList />
</StyledScrollWrapper>
<StyledScrollWrapperContainer>
<ScrollWrapper componentInstanceId={AI_CHAT_SCROLL_WRAPPER_ID}>
{agentChatMessageIdsComponent.map((messageId) => {
return <AIChatMessage messageId={messageId} key={messageId} />;
})}
<AIChatErrorUnderMessageList />
</ScrollWrapper>
</StyledScrollWrapperContainer>
);
};
@@ -140,8 +140,9 @@ const StyledReasoningText = styled.p`
white-space: pre-wrap;
`;
const StyledOrbitLoaderIcon = styled(ThinkingOrbitLoaderIcon)`
const StyledOrbitLoaderIconContainer = styled.span`
color: ${themeCssVariables.font.color.tertiary};
display: flex;
`;
const StyledIconContainer = styled.div`
@@ -214,7 +215,7 @@ const StyledToolDetailsContainer = styled.div`
overflow: hidden;
`;
const StyledToolTabList = styled(TabList)`
const StyledToolTabListContainer = styled.div`
background-color: ${themeCssVariables.background.secondary};
padding-left: ${themeCssVariables.spacing[1]};
`;
@@ -334,11 +335,13 @@ const ThinkingToolStepRow = ({
<StyledToolErrorText>{part.errorText}</StyledToolErrorText>
) : (
<StyledToolDetailsContent>
<StyledToolTabList
tabs={toolTabs}
behaveAsLinks={false}
componentInstanceId={toolTabListComponentInstanceId}
/>
<StyledToolTabListContainer>
<TabList
tabs={toolTabs}
behaveAsLinks={false}
componentInstanceId={toolTabListComponentInstanceId}
/>
</StyledToolTabListContainer>
<StyledToolJsonContent>
<StyledJsonTreeContainer>
<JsonTree
@@ -388,7 +391,13 @@ const ThinkingStepRow = ({
return (
<StyledRow>
<StyledIconContainer>
{isActive ? <StyledOrbitLoaderIcon /> : <IconCpu size={14} />}
{isActive ? (
<StyledOrbitLoaderIconContainer>
<ThinkingOrbitLoaderIcon />
</StyledOrbitLoaderIconContainer>
) : (
<IconCpu size={14} />
)}
</StyledIconContainer>
<StyledRowLabelContainer>
<StyledRowLabel>{isActive ? t`Thinking` : t`Thought`}</StyledRowLabel>
@@ -29,7 +29,7 @@ const StyledTitle = styled.div`
padding: 0 ${themeCssVariables.spacing[2]};
`;
const StyledSuggestedPromptButton = styled(LightButton)`
const StyledSuggestedPromptButtonContainer = styled.div`
align-self: flex-start;
`;
@@ -62,13 +62,14 @@ export const AIChatSuggestedPrompts = ({
<StyledContainer>
<StyledTitle>{t`What can I help you with?`}</StyledTitle>
{DEFAULT_SUGGESTED_PROMPTS.map((prompt) => (
<StyledSuggestedPromptButton
key={prompt.id}
Icon={prompt.Icon}
title={resolveMessage(prompt.label)}
accent="secondary"
onClick={() => handleClick(prompt)}
/>
<StyledSuggestedPromptButtonContainer key={prompt.id}>
<LightButton
Icon={prompt.Icon}
title={resolveMessage(prompt.label)}
accent="secondary"
onClick={() => handleClick(prompt)}
/>
</StyledSuggestedPromptButtonContainer>
))}
</StyledContainer>
);
@@ -31,11 +31,11 @@ const StyledRow = styled.div`
gap: ${themeCssVariables.spacing[2]};
`;
const StyledSelect = styled(Select<string>)`
const StyledSelectContainer = styled.div`
flex: 1 1;
`;
const StyledButton = styled(Button)`
const StyledButtonContainer = styled.div`
flex: 0 0 auto;
`;
@@ -160,26 +160,30 @@ export const MeteredPriceSelector = ({
description={t`Number of new credits allocated every ${recurringInterval}`}
/>
<StyledRow>
<StyledSelect
dropdownId="settings_billing-metered-price"
options={options}
value={selectedPriceId ?? currentMeteredPrice.stripePriceId}
onChange={handleChange}
disabled={isUpdating || isTrialing}
description={
isTrialing ? t`Please start your subscription first` : undefined
}
fullWidth
/>
{isChanged && (
<StyledButton
title={isUpgrade() ? t`Upgrade` : t`Downgrade`}
onClick={handleOpenConfirm}
variant="primary"
isLoading={isUpdating}
disabled={!isChanged}
accent={isUpgrade() ? 'blue' : 'danger'}
<StyledSelectContainer>
<Select
dropdownId="settings_billing-metered-price"
options={options}
value={selectedPriceId ?? currentMeteredPrice.stripePriceId}
onChange={handleChange}
disabled={isUpdating || isTrialing}
description={
isTrialing ? t`Please start your subscription first` : undefined
}
fullWidth
/>
</StyledSelectContainer>
{isChanged && (
<StyledButtonContainer>
<Button
title={isUpgrade() ? t`Upgrade` : t`Downgrade`}
onClick={handleOpenConfirm}
variant="primary"
isLoading={isUpdating}
disabled={!isChanged}
accent={isUpgrade() ? 'blue' : 'danger'}
/>
</StyledButtonContainer>
)}
</StyledRow>
<ConfirmationModal
@@ -11,13 +11,15 @@ import { Chip, ChipVariant } from 'twenty-ui/components';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
const StyledRecordChip = styled(RecordChip)`
const StyledRecordChipContainer = styled.div`
display: inline;
height: auto;
margin: 0;
padding: 0 ${themeCssVariables.spacing[1]};
`;
const StyledInlineMentionRecordChip = styled(MentionRecordChip)`
const StyledInlineMentionRecordChipContainer = styled.div`
display: inline;
height: auto;
margin: 0;
padding: 0 ${themeCssVariables.spacing[1]};
@@ -69,11 +71,13 @@ const LegacyMentionRenderer = ({
}
return (
<StyledRecordChip
objectNameSingular={objectMetadataItem.nameSingular}
record={record}
forceDisableClick={false}
/>
<StyledRecordChipContainer>
<RecordChip
objectNameSingular={objectMetadataItem.nameSingular}
record={record}
forceDisableClick={false}
/>
</StyledRecordChipContainer>
);
};
@@ -112,12 +116,14 @@ export const MentionInlineContent = createReactInlineContentSpec(
// New notes store objectNameSingular + label + imageUrl directly
if (isNonEmptyString(objectNameSingular) && isNonEmptyString(label)) {
return (
<StyledInlineMentionRecordChip
recordId={recordId}
objectNameSingular={objectNameSingular}
label={label}
imageUrl={imageUrl}
/>
<StyledInlineMentionRecordChipContainer>
<MentionRecordChip
recordId={recordId}
objectNameSingular={objectNameSingular}
label={label}
imageUrl={imageUrl}
/>
</StyledInlineMentionRecordChipContainer>
);
}
@@ -25,8 +25,8 @@ type CommandMenuItemNumberInputProps = {
placeholder?: string;
};
const StyledRightAlignedTextInput = styled(TextInput)`
input {
const StyledRightAlignedTextInputContainer = styled.div`
& input {
text-align: right;
}
`;
@@ -132,18 +132,20 @@ export const CommandMenuItemNumberInput = ({
Icon={Icon}
onClick={focusInput}
RightComponent={
<StyledRightAlignedTextInput
ref={inputRef}
value={draftValue}
sizeVariant="sm"
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder={placeholder}
error={hasError ? ' ' : undefined}
noErrorHelper
textClickOutsideId={focusId}
/>
<StyledRightAlignedTextInputContainer>
<TextInput
ref={inputRef}
value={draftValue}
sizeVariant="sm"
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder={placeholder}
error={hasError ? ' ' : undefined}
noErrorHelper
textClickOutsideId={focusId}
/>
</StyledRightAlignedTextInputContainer>
}
/>
);
@@ -19,8 +19,8 @@ type CommandMenuItemTextInputProps = {
placeholder?: string;
};
const StyledRightAlignedTextInput = styled(TextInput)`
input {
const StyledRightAlignedTextInputContainer = styled.div`
& input {
text-align: right;
}
`;
@@ -96,16 +96,18 @@ export const CommandMenuItemTextInput = ({
Icon={Icon}
onClick={focusInput}
RightComponent={
<StyledRightAlignedTextInput
ref={inputRef}
value={draftValue}
sizeVariant="sm"
onChange={setDraftValue}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder={placeholder}
textClickOutsideId={focusId}
/>
<StyledRightAlignedTextInputContainer>
<TextInput
ref={inputRef}
value={draftValue}
sizeVariant="sm"
onChange={setDraftValue}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder={placeholder}
textClickOutsideId={focusId}
/>
</StyledRightAlignedTextInputContainer>
}
/>
);
@@ -18,7 +18,7 @@ const StyledText = styled.div`
white-space: nowrap;
`;
const StyledCloseButton = styled(IconButton)`
const StyledCloseButtonContainer = styled.div`
color: ${themeCssVariables.grayScale.gray1};
display: flex;
`;
@@ -79,13 +79,15 @@ export const InformationBanner = ({
)}
</StyledContent>
{onClose && (
<StyledCloseButton
Icon={IconX}
size="small"
variant="tertiary"
onClick={onClose}
ariaLabel={t`Close banner`}
/>
<StyledCloseButtonContainer>
<IconButton
Icon={IconX}
size="small"
variant="tertiary"
onClick={onClose}
ariaLabel={t`Close banner`}
/>
</StyledCloseButtonContainer>
)}
</Banner>
)}
@@ -19,12 +19,15 @@ const StyledMainContainerLayoutForDesktop = styled.div`
padding-right: ${themeCssVariables.spacing[3]};
`;
const StyledPageBodyForDesktop = styled(PageBody)`
const StyledPageBodyForDesktopContainer = styled.div`
flex: 1 1 0;
min-width: 0;
width: 0;
padding-bottom: 0;
padding-right: 0;
> * {
padding-bottom: 0;
padding-right: 0;
}
`;
const StyledMainContainerLayoutForMobile = styled.div`
@@ -34,11 +37,12 @@ const StyledMainContainerLayoutForMobile = styled.div`
padding: 0;
`;
const StyledPageBodyForMobile = styled(PageBody)`
padding-bottom: 0;
padding-left: ${themeCssVariables.spacing[1]};
padding-right: ${themeCssVariables.spacing['1.5']};
const StyledPageBodyForMobileContainer = styled.div`
> * {
padding-bottom: 0;
padding-left: ${themeCssVariables.spacing[1]};
padding-right: ${themeCssVariables.spacing['1.5']};
}
`;
export const MainContainerLayoutWithSidePanel = ({
@@ -51,7 +55,9 @@ export const MainContainerLayoutWithSidePanel = ({
if (isMobile) {
return (
<StyledMainContainerLayoutForMobile>
<StyledPageBodyForMobile>{children}</StyledPageBodyForMobile>
<StyledPageBodyForMobileContainer>
<PageBody>{children}</PageBody>
</StyledPageBodyForMobileContainer>
<CommandMenuForMobile />
</StyledMainContainerLayoutForMobile>
);
@@ -59,7 +65,9 @@ export const MainContainerLayoutWithSidePanel = ({
return (
<StyledMainContainerLayoutForDesktop>
<StyledPageBodyForDesktop>{children}</StyledPageBodyForDesktop>
<StyledPageBodyForDesktopContainer>
<PageBody>{children}</PageBody>
</StyledPageBodyForDesktopContainer>
<SidePanelForDesktop />
</StyledMainContainerLayoutForDesktop>
);
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`generateDepthRecordGqlFieldsFromRecord should generate depth one record gql fields from empty record 1`] = `
{
@@ -3,7 +3,7 @@ import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/use
import { styled } from '@linaria/react';
import { NotificationCounter } from 'twenty-ui/navigation';
const StyledNotificationCounter = styled(NotificationCounter)`
const StyledNotificationCounterContainer = styled.div`
position: absolute;
top: -7px;
right: -7px;
@@ -23,5 +23,9 @@ export const RecordBoardCardMultiDragCounterChip = () => {
return null;
}
return <StyledNotificationCounter count={selectedCount} />;
return (
<StyledNotificationCounterContainer>
<NotificationCounter count={selectedCount} />
</StyledNotificationCounterContainer>
);
};
@@ -68,7 +68,7 @@ const StyledColumn = styled.div`
position: relative;
`;
const StyledTag = styled(Tag)`
const StyledTagContainer = styled.div`
max-width: 100%;
min-width: 0;
overflow: hidden;
@@ -139,24 +139,29 @@ export const RecordBoardColumnHeader = () => {
y: 10,
}}
clickableComponent={
<StyledTag
variant={
columnDefinition.type === RecordGroupDefinitionType.Value
? 'solid'
: 'outline'
}
color={
columnDefinition.type === RecordGroupDefinitionType.Value
? columnDefinition.color
: 'transparent'
}
text={columnDefinition.title}
weight={
columnDefinition.type === RecordGroupDefinitionType.Value
? 'regular'
: 'medium'
}
/>
<StyledTagContainer>
<Tag
variant={
columnDefinition.type ===
RecordGroupDefinitionType.Value
? 'solid'
: 'outline'
}
color={
columnDefinition.type ===
RecordGroupDefinitionType.Value
? columnDefinition.color
: 'transparent'
}
text={columnDefinition.title}
weight={
columnDefinition.type ===
RecordGroupDefinitionType.Value
? 'regular'
: 'medium'
}
/>
</StyledTagContainer>
}
dropdownComponents={<RecordBoardColumnDropdownMenu />}
/>
@@ -6,12 +6,14 @@ import { type Nullable } from 'twenty-shared/types';
import { Tag } from 'twenty-ui/components';
import { AppTooltip, TooltipDelay } from 'twenty-ui/display';
const StyledTag = styled(Tag)`
const StyledTagContainer = styled.div`
width: 100%;
`;
const StyledHeader = styled(StyledHeaderDropdownButton)`
padding: 0;
const StyledHeaderContainer = styled.div`
> * {
padding: 0;
}
`;
export const RecordBoardColumnHeaderAggregateDropdownButton = ({
@@ -29,24 +31,28 @@ export const RecordBoardColumnHeaderAggregateDropdownButton = ({
);
return (
<StyledHeader id={dropdownId} isUnfolded={isDropdownOpen}>
<>
<StyledTag
text={value ? value.toString() : '-'}
color="transparent"
weight="regular"
/>
{!isDropdownOpen && (
<AppTooltip
anchorSelect={`#${dropdownId}`}
content={tooltip ?? ''}
noArrow
place="right"
positionStrategy="fixed"
delay={TooltipDelay.mediumDelay}
/>
)}
</>
</StyledHeader>
<StyledHeaderContainer>
<StyledHeaderDropdownButton id={dropdownId} isUnfolded={isDropdownOpen}>
<>
<StyledTagContainer>
<Tag
text={value ? value.toString() : '-'}
color="transparent"
weight="regular"
/>
</StyledTagContainer>
{!isDropdownOpen && (
<AppTooltip
anchorSelect={`#${dropdownId}`}
content={tooltip ?? ''}
noArrow
place="right"
positionStrategy="fixed"
delay={TooltipDelay.mediumDelay}
/>
)}
</>
</StyledHeaderDropdownButton>
</StyledHeaderContainer>
);
};
@@ -14,7 +14,7 @@ import { IconPlus } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledButton = styled(Button)`
const StyledButtonContainer = styled.div`
padding: ${themeCssVariables.spacing['0.5']};
min-width: unset;
height: auto;
@@ -69,17 +69,19 @@ export const RecordCalendarAddNew = ({
}
return (
<StyledButton
onClick={async () => {
await createNewIndexRecord({
[calendarFieldMetadataItem.name]: cardDate
.toZonedDateTime(userTimezone)
.toInstant()
.toString(),
});
}}
variant="tertiary"
Icon={() => <IconPlus size={theme.icon.size.sm} />}
/>
<StyledButtonContainer>
<Button
onClick={async () => {
await createNewIndexRecord({
[calendarFieldMetadataItem.name]: cardDate
.toZonedDateTime(userTimezone)
.toInstant()
.toString(),
});
}}
variant="tertiary"
Icon={() => <IconPlus size={theme.icon.size.sm} />}
/>
</StyledButtonContainer>
);
};
@@ -30,7 +30,7 @@ const StyledContainer = styled.div`
width: 100%;
`;
const StyledNavigationButton = styled(Button)`
const StyledNavigationButtonContainer = styled.div`
padding: ${themeCssVariables.spacing[1]};
`;
@@ -120,24 +120,28 @@ export const RecordCalendarTopBar = () => {
</StyledLeftSection>
<StyledNavigationSection>
<StyledNavigationButton
size="small"
variant="tertiary"
Icon={IconChevronLeft}
onClick={handlePreviousMonth}
/>
<StyledNavigationButtonContainer>
<Button
size="small"
variant="tertiary"
Icon={IconChevronLeft}
onClick={handlePreviousMonth}
/>
</StyledNavigationButtonContainer>
<Button
size="small"
variant="tertiary"
title={t`Today`}
onClick={handleTodayClick}
/>
<StyledNavigationButton
size="small"
variant="tertiary"
Icon={IconChevronRight}
onClick={handleNextMonth}
/>
<StyledNavigationButtonContainer>
<Button
size="small"
variant="tertiary"
Icon={IconChevronRight}
onClick={handleNextMonth}
/>
</StyledNavigationButtonContainer>
</StyledNavigationSection>
</StyledContainer>
);
@@ -24,7 +24,7 @@ const StyledContainer = styled.div`
display: flex;
`;
const StyledRecordCard = styled(RecordCard)`
const StyledRecordCardContainer = styled.div`
width: calc(100% - 2px);
`;
@@ -84,18 +84,20 @@ export const RecordCalendarCard = ({ recordId }: RecordCalendarCardProps) => {
value={{ scopeInstanceId: RECORD_CALENDAR_CARD_INPUT_ID_PREFIX }}
>
<StyledContainer onContextMenu={handleContextMenuOpen}>
<StyledRecordCard
data-selected={isRecordCalendarCardSelected}
data-click-outside-id={RECORD_CALENDAR_CARD_CLICK_OUTSIDE_ID}
>
<RecordCalendarCardHeader recordId={recordId} />
<AnimatedEaseInOut isOpen={!isCompactModeActive} initial={false}>
<RecordCalendarCardBody
recordId={recordId}
isRecordReadOnly={false}
/>
</AnimatedEaseInOut>
</StyledRecordCard>
<StyledRecordCardContainer>
<RecordCard
data-selected={isRecordCalendarCardSelected}
data-click-outside-id={RECORD_CALENDAR_CARD_CLICK_OUTSIDE_ID}
>
<RecordCalendarCardHeader recordId={recordId} />
<AnimatedEaseInOut isOpen={!isCompactModeActive} initial={false}>
<RecordCalendarCardBody
recordId={recordId}
isRecordReadOnly={false}
/>
</AnimatedEaseInOut>
</RecordCard>
</StyledRecordCardContainer>
<RecordCalendarCardCellHoveredPortal recordId={recordId} />
<RecordCalendarCardCellEditModePortal recordId={recordId} />
</StyledContainer>
@@ -4,7 +4,7 @@ import { Link } from 'react-router-dom';
import { Section } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledRecordDetailSectionContainer = styled(Section)`
const StyledRecordDetailSectionWrapper = styled.div`
border-top: 1px solid ${themeCssVariables.border.color.light};
padding-top: ${themeCssVariables.spacing[3]};
padding-bottom: ${themeCssVariables.spacing[3]};
@@ -36,13 +36,15 @@ const StyledTitleLabel = styled.div`
font-weight: ${themeCssVariables.font.weight.medium};
`;
const StyledLink = styled(Link)`
color: ${themeCssVariables.font.color.light};
text-decoration: none;
font-size: ${themeCssVariables.font.size.sm};
const StyledLinkContainer = styled.span`
& > a {
color: ${themeCssVariables.font.color.light};
font-size: ${themeCssVariables.font.size.sm};
text-decoration: none;
:hover {
color: ${themeCssVariables.font.color.secondary};
&:hover {
color: ${themeCssVariables.font.color.secondary};
}
}
`;
@@ -67,22 +69,28 @@ export const RecordDetailSectionContainer = ({
}: RecordDetailSectionContainerProps) => {
const [isHovered, setIsHovered] = useState(false);
return (
<StyledRecordDetailSectionContainer>
<StyledHeader
areRecordsAvailable={areRecordsAvailable}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
data-testid={dataTestId}
>
<StyledTitle>
<StyledTitleLabel>{title}</StyledTitleLabel>
{link && <StyledLink to={link.to}>{link.label}</StyledLink>}
</StyledTitle>
{hideRightAdornmentOnMouseLeave && !isHovered && areRecordsAvailable
? null
: rightAdornment}
</StyledHeader>
{children}
</StyledRecordDetailSectionContainer>
<StyledRecordDetailSectionWrapper>
<Section>
<StyledHeader
areRecordsAvailable={areRecordsAvailable}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
data-testid={dataTestId}
>
<StyledTitle>
<StyledTitleLabel>{title}</StyledTitleLabel>
{link && (
<StyledLinkContainer>
<Link to={link.to}>{link.label}</Link>
</StyledLinkContainer>
)}
</StyledTitle>
{hideRightAdornmentOnMouseLeave && !isHovered && areRecordsAvailable
? null
: rightAdornment}
</StyledHeader>
{children}
</Section>
</StyledRecordDetailSectionWrapper>
);
};
@@ -22,7 +22,7 @@ import { IconMaximize } from 'twenty-ui/display';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { useIsMobile } from 'twenty-ui/utilities';
const StyledAdvancedTextFieldContainer = styled(FormFieldInputContainer)`
const StyledAdvancedTextFieldContainerWrapper = styled.div`
flex-grow: 1;
`;
@@ -63,14 +63,16 @@ const StyledFullScreenEditorContainer = styled.div`
overflow-y: auto;
`;
const StyledFullScreenButtonContainer = styled(StyledDropdownButtonContainer)`
background-color: transparent;
const StyledFullScreenButtonContainerWrapper = styled.div`
> * {
background-color: transparent;
color: ${themeCssVariables.font.color.tertiary};
padding: ${themeCssVariables.spacing[2]};
color: ${themeCssVariables.font.color.tertiary};
padding: ${themeCssVariables.spacing[2]};
:hover {
cursor: pointer;
background-color: ${themeCssVariables.background.transparent.light};
:hover {
cursor: pointer;
background-color: ${themeCssVariables.background.transparent.light};
}
}
`;
@@ -209,46 +211,50 @@ export const FormAdvancedTextFieldInput = ({
return (
<>
<StyledAdvancedTextFieldContainer>
{label ? <InputLabel>{label}</InputLabel> : null}
<StyledAdvancedTextFieldContainerWrapper>
<FormFieldInputContainer>
{label ? <InputLabel>{label}</InputLabel> : null}
<StyledAdvancedTextFieldFieldContainer>
<StyledAdvancedTextFieldInnerContainer>
{!isFullScreen && (
<AdvancedTextEditor
editor={editor}
readonly={readonly}
minHeight={minHeight}
maxWidth={maxWidth}
/>
)}
<StyledAdvancedTextFieldFieldContainer>
<StyledAdvancedTextFieldInnerContainer>
{!isFullScreen && (
<AdvancedTextEditor
editor={editor}
readonly={readonly}
minHeight={minHeight}
maxWidth={maxWidth}
/>
)}
{enableFullScreen && (
<StyledEditorActionButtonContainer>
{!readonly && !isFullScreen && (
<StyledFullScreenButtonContainer
isUnfolded={false}
transparentBackground
onClick={handleEnterFullScreen}
>
<IconMaximize size={theme.icon.size.md} />
</StyledFullScreenButtonContainer>
)}
</StyledEditorActionButtonContainer>
)}
{enableFullScreen && (
<StyledEditorActionButtonContainer>
{!readonly && !isFullScreen && (
<StyledFullScreenButtonContainerWrapper>
<StyledDropdownButtonContainer
isUnfolded={false}
transparentBackground
onClick={handleEnterFullScreen}
>
<IconMaximize size={theme.icon.size.md} />
</StyledDropdownButtonContainer>
</StyledFullScreenButtonContainerWrapper>
)}
</StyledEditorActionButtonContainer>
)}
{VariablePicker && !readonly ? (
<VariablePicker
instanceId={instanceId}
multiline={true}
onVariableSelect={handleVariableTagInsert}
/>
) : null}
</StyledAdvancedTextFieldInnerContainer>
</StyledAdvancedTextFieldFieldContainer>
{hint && <InputHint>{hint}</InputHint>}
{error && <InputErrorHelper>{error}</InputErrorHelper>}
</StyledAdvancedTextFieldContainer>
{VariablePicker && !readonly ? (
<VariablePicker
instanceId={instanceId}
multiline={true}
onVariableSelect={handleVariableTagInsert}
/>
) : null}
</StyledAdvancedTextFieldInnerContainer>
</StyledAdvancedTextFieldFieldContainer>
{hint && <InputHint>{hint}</InputHint>}
{error && <InputErrorHelper>{error}</InputErrorHelper>}
</FormFieldInputContainer>
</StyledAdvancedTextFieldContainerWrapper>
{fullScreenOverlay}
</>
@@ -52,7 +52,14 @@ const StyledDisplayModeReadonlyContainer = styled.div`
width: 100%;
`;
const StyledDisplayModeContainer = styled(StyledDisplayModeReadonlyContainer)`
const StyledDisplayModeContainer = styled.div`
align-items: center;
background: transparent;
border: none;
display: flex;
font-family: inherit;
padding-inline: ${themeCssVariables.spacing[2]};
width: 100%;
height: 30px;
cursor: pointer;
box-sizing: border-box;
@@ -63,11 +70,11 @@ const StyledDisplayModeContainer = styled(StyledDisplayModeReadonlyContainer)`
}
`;
const StyledInput = styled(TextInput)`
const StyledInputContainer = styled.div`
padding: ${themeCssVariables.spacing[1]} ${themeCssVariables.spacing[2]};
`;
const StyledPlaceholder = styled(FormFieldPlaceholder)`
const StyledPlaceholderContainer = styled.div`
width: 100%;
`;
@@ -303,19 +310,23 @@ export const FormArrayFieldInput = ({
{draftValue.value.length > 0 ? (
<ArrayDisplay value={draftValue.value} />
) : (
<StyledPlaceholder />
<StyledPlaceholderContainer>
<FormFieldPlaceholder />
</StyledPlaceholderContainer>
)}
</StyledDisplayModeReadonlyContainer>
) : draftValue.value.length === 0 ? (
<StyledInput
instanceId={formFieldInputInstanceId}
placeholder={t`Enter an item`}
value={newItemDraftValue}
copyButton={false}
onChange={handleFirstItemInputChange}
onEnter={handleFirstItemInputEnter}
shouldTrim={false}
/>
<StyledInputContainer>
<TextInput
instanceId={formFieldInputInstanceId}
placeholder={t`Enter an item`}
value={newItemDraftValue}
copyButton={false}
onChange={handleFirstItemInputChange}
onEnter={handleFirstItemInputEnter}
shouldTrim={false}
/>
</StyledInputContainer>
) : (
<Dropdown
dropdownId={dropdownId}
@@ -33,7 +33,7 @@ import { themeCssVariables } from 'twenty-ui/theme-constants';
import { type Nullable } from 'twenty-ui/utilities';
import { getDateFormatStringForDatePickerInputMask } from '~/utils/date-utils';
const StyledInputContainer = styled(FormFieldInputInnerContainer)`
const StyledInputContainerWrapper = styled.div`
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr 0;
@@ -327,49 +327,50 @@ export const FormDateFieldInput = ({
{label ? <InputLabel>{label}</InputLabel> : null}
<FormFieldInputRowContainer>
<StyledInputContainer
formFieldInputInstanceId={instanceId}
ref={datePickerWrapperRef}
hasRightElement={isDefined(VariablePicker) && !readonly}
>
{draftValue.type === 'static' ? (
<>
<StyledDateInput
type="text"
placeholder={placeholderToDisplay}
value={inputDate}
onFocus={handleInputFocus}
onChange={handleInputChange}
onKeyDown={handleInputKeydown}
disabled={readonly}
/>
<StyledInputContainerWrapper ref={datePickerWrapperRef}>
<FormFieldInputInnerContainer
formFieldInputInstanceId={instanceId}
hasRightElement={isDefined(VariablePicker) && !readonly}
>
{draftValue.type === 'static' ? (
<>
<StyledDateInput
type="text"
placeholder={placeholderToDisplay}
value={inputDate}
onFocus={handleInputFocus}
onChange={handleInputChange}
onKeyDown={handleInputKeydown}
disabled={readonly}
/>
{draftValue.mode === 'edit' ? (
<StyledDateInputContainer>
<StyledDateInputAbsoluteContainer>
<OverlayContainer>
<DatePicker
instanceId={instanceId}
plainDateString={pickerDate}
onChange={handlePickerChange}
onClose={handlePickerMouseSelect}
onEnter={handlePickerEnter}
onEscape={handlePickerEscape}
onClear={handlePickerClear}
hideHeaderInput
/>
</OverlayContainer>
</StyledDateInputAbsoluteContainer>
</StyledDateInputContainer>
) : null}
</>
) : (
<VariableChipStandalone
rawVariableName={draftValue.value}
onRemove={readonly ? undefined : handleUnlinkVariable}
/>
)}
</StyledInputContainer>
{draftValue.mode === 'edit' ? (
<StyledDateInputContainer>
<StyledDateInputAbsoluteContainer>
<OverlayContainer>
<DatePicker
instanceId={instanceId}
plainDateString={pickerDate}
onChange={handlePickerChange}
onClose={handlePickerMouseSelect}
onEnter={handlePickerEnter}
onEscape={handlePickerEscape}
onClear={handlePickerClear}
hideHeaderInput
/>
</OverlayContainer>
</StyledDateInputAbsoluteContainer>
</StyledDateInputContainer>
) : null}
</>
) : (
<VariableChipStandalone
rawVariableName={draftValue.value}
onRemove={readonly ? undefined : handleUnlinkVariable}
/>
)}
</FormFieldInputInnerContainer>
</StyledInputContainerWrapper>
{VariablePicker && !readonly ? (
<VariablePicker
@@ -26,7 +26,7 @@ import { isDefined } from 'twenty-shared/utils';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { type Nullable } from 'twenty-ui/utilities';
const StyledInputContainer = styled(FormFieldInputInnerContainer)`
const StyledInputContainerWrapper = styled.div`
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr 0;
@@ -243,49 +243,50 @@ export const FormDateTimeFieldInput = ({
{label ? <InputLabel>{label}</InputLabel> : null}
<FormFieldInputRowContainer>
<StyledInputContainer
formFieldInputInstanceId={instanceId}
ref={datePickerWrapperRef}
hasRightElement={isDefined(VariablePicker) && !readonly}
>
{draftValue.type === 'static' ? (
<>
<StyledDateInputTextContainer>
<DateTimePickerInput
date={dateValue}
onChange={handleInputChange}
onFocus={handleInputFocus}
readonly={readonly}
timeZone={timeZone}
/>
</StyledDateInputTextContainer>
{draftValue.mode === 'edit' ? (
<StyledDateInputContainer>
<StyledDateInputAbsoluteContainer>
<OverlayContainer>
<DateTimePicker
instanceId={instanceId}
date={dateValue}
onChange={handlePickerChange}
onClose={handlePickerMouseSelect}
onEnter={handlePickerEnter}
onEscape={handlePickerEscape}
onClear={handlePickerClear}
hideHeaderInput
timeZone={timeZone}
/>
</OverlayContainer>
</StyledDateInputAbsoluteContainer>
</StyledDateInputContainer>
) : null}
</>
) : (
<VariableChipStandalone
rawVariableName={draftValue.value}
onRemove={readonly ? undefined : handleUnlinkVariable}
/>
)}
</StyledInputContainer>
<StyledInputContainerWrapper ref={datePickerWrapperRef}>
<FormFieldInputInnerContainer
formFieldInputInstanceId={instanceId}
hasRightElement={isDefined(VariablePicker) && !readonly}
>
{draftValue.type === 'static' ? (
<>
<StyledDateInputTextContainer>
<DateTimePickerInput
date={dateValue}
onChange={handleInputChange}
onFocus={handleInputFocus}
readonly={readonly}
timeZone={timeZone}
/>
</StyledDateInputTextContainer>
{draftValue.mode === 'edit' ? (
<StyledDateInputContainer>
<StyledDateInputAbsoluteContainer>
<OverlayContainer>
<DateTimePicker
instanceId={instanceId}
date={dateValue}
onChange={handlePickerChange}
onClose={handlePickerMouseSelect}
onEnter={handlePickerEnter}
onEscape={handlePickerEscape}
onClear={handlePickerClear}
hideHeaderInput
timeZone={timeZone}
/>
</OverlayContainer>
</StyledDateInputAbsoluteContainer>
</StyledDateInputContainer>
) : null}
</>
) : (
<VariableChipStandalone
rawVariableName={draftValue.value}
onRemove={readonly ? undefined : handleUnlinkVariable}
/>
)}
</FormFieldInputInnerContainer>
</StyledInputContainerWrapper>
{VariablePicker && !readonly ? (
<VariablePicker
instanceId={instanceId}
@@ -50,7 +50,14 @@ const StyledDisplayModeReadonlyContainer = styled.div`
width: 100%;
`;
const StyledDisplayModeContainer = styled(StyledDisplayModeReadonlyContainer)`
const StyledDisplayModeContainer = styled.div`
align-items: center;
background: transparent;
border: none;
display: flex;
font-family: inherit;
padding-inline: ${themeCssVariables.spacing[2]};
width: 100%;
cursor: pointer;
&:hover,
@@ -65,7 +72,7 @@ const StyledSelectInputContainer = styled.div`
top: ${themeCssVariables.spacing[9]};
`;
const StyledPlaceholder = styled(FormFieldPlaceholder)`
const StyledPlaceholderContainer = styled.div`
width: 100%;
`;
@@ -223,7 +230,9 @@ export const FormMultiSelectFieldInput = ({
options={selectedOptions}
/>
) : (
<StyledPlaceholder />
<StyledPlaceholderContainer>
<FormFieldPlaceholder />
</StyledPlaceholderContainer>
)}
<IconChevronDown
size={theme.icon.size.md}
@@ -243,7 +252,11 @@ export const FormMultiSelectFieldInput = ({
options={selectedOptions}
/>
) : (
<StyledPlaceholder>{placeholderText}</StyledPlaceholder>
<StyledPlaceholderContainer>
<FormFieldPlaceholder>
{placeholderText}
</FormFieldPlaceholder>
</StyledPlaceholderContainer>
)}
<IconChevronDown
size={theme.icon.size.md}
@@ -22,9 +22,7 @@ import { CustomError, isDefined, isValidUuid } from 'twenty-shared/utils';
import { IconChevronDown, IconForbid } from 'twenty-ui/display';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledFormSelectContainer = styled(FormFieldInputInnerContainer)<{
readonly?: boolean;
}>`
const StyledFormSelectContainerWrapper = styled.div<{ readonly?: boolean }>`
align-items: center;
height: 32px;
justify-content: space-between;
@@ -166,19 +164,20 @@ export const FormSingleRecordPicker = ({
{label ? <InputLabel>{label}</InputLabel> : null}
<FormFieldInputRowContainer>
{disabled ? (
<StyledFormSelectContainer
formFieldInputInstanceId={componentId}
hasRightElement={false}
readonly
>
<FormSingleRecordFieldChip
draftValue={draftValue}
selectedRecord={selectedRecord}
objectNameSingular={objectNameSingulars[0]}
onRemove={handleUnlinkVariable}
disabled={disabled}
/>
</StyledFormSelectContainer>
<StyledFormSelectContainerWrapper readonly>
<FormFieldInputInnerContainer
formFieldInputInstanceId={componentId}
hasRightElement={false}
>
<FormSingleRecordFieldChip
draftValue={draftValue}
selectedRecord={selectedRecord}
objectNameSingular={objectNameSingulars[0]}
onRemove={handleUnlinkVariable}
disabled={disabled}
/>
</FormFieldInputInnerContainer>
</StyledFormSelectContainerWrapper>
) : (
<Dropdown
dropdownId={dropdownId}
@@ -190,25 +189,27 @@ export const FormSingleRecordPicker = ({
y: parseInt(theme.spacing[1], 10),
}}
clickableComponent={
<StyledFormSelectContainer
formFieldInputInstanceId={componentId}
hasRightElement={isDefined(VariablePicker) && !disabled}
preventFocusStackUpdate={true}
>
<FormSingleRecordFieldChip
draftValue={draftValue}
selectedRecord={selectedRecord}
objectNameSingular={objectNameSingulars[0]}
onRemove={handleUnlinkVariable}
disabled={disabled}
/>
<StyledIconButton>
<IconChevronDown
size={theme.icon.size.md}
color={theme.font.color.light}
<StyledFormSelectContainerWrapper>
<FormFieldInputInnerContainer
formFieldInputInstanceId={componentId}
hasRightElement={isDefined(VariablePicker) && !disabled}
preventFocusStackUpdate={true}
>
<FormSingleRecordFieldChip
draftValue={draftValue}
selectedRecord={selectedRecord}
objectNameSingular={objectNameSingulars[0]}
onRemove={handleUnlinkVariable}
disabled={disabled}
/>
</StyledIconButton>
</StyledFormSelectContainer>
<StyledIconButton>
<IconChevronDown
size={theme.icon.size.md}
color={theme.font.color.light}
/>
</StyledIconButton>
</FormFieldInputInnerContainer>
</StyledFormSelectContainerWrapper>
}
dropdownComponents={
<SingleRecordPicker
@@ -43,23 +43,15 @@ const StyledCustomPhoneInputContainer = styled.div<{
height: ${({ hasItem }) => (hasItem ? '30px' : 'auto')};
`;
const StyledCustomPhoneInput = styled(ReactPhoneNumberInput)`
const StyledCustomPhoneInputWrapper = styled.div`
background-color: transparent;
border: none;
color: ${themeCssVariables.font.color.primary};
font-family: ${themeCssVariables.font.family};
font-size: inherit;
font-weight: inherit;
outline: none;
padding: 0;
&::placeholder,
&::-webkit-input-placeholder {
color: ${themeCssVariables.font.color.light};
font-family: ${themeCssVariables.font.family};
font-weight: ${themeCssVariables.font.weight.medium};
}
height: 100%;
width: calc(100% - ${themeCssVariables.spacing[8]});
.PhoneInputInput {
background: none;
@@ -83,7 +75,6 @@ const StyledCustomPhoneInput = styled(ReactPhoneNumberInput)`
border-radius: ${themeCssVariables.border.radius.xs};
height: 12px;
}
width: calc(100% - ${themeCssVariables.spacing[8]});
`;
export const PhonesFieldInput = () => {
@@ -219,16 +210,18 @@ export const PhonesFieldInput = () => {
hasItem={!!phones.length}
hasError={hasError}
>
<StyledCustomPhoneInput
autoFocus={autoFocus}
placeholder={placeholder}
value={value as E164Number}
onChange={onChange as unknown as (newValue: E164Number) => void}
international={true}
withCountryCallingCode={true}
countrySelectComponent={PhoneCountryPickerDropdownButton}
defaultCountry={defaultCountry}
/>
<StyledCustomPhoneInputWrapper>
<ReactPhoneNumberInput
autoFocus={autoFocus}
placeholder={placeholder}
value={value as E164Number}
onChange={onChange as unknown as (newValue: E164Number) => void}
international={true}
withCountryCallingCode={true}
countrySelectComponent={PhoneCountryPickerDropdownButton}
defaultCountry={defaultCountry}
/>
</StyledCustomPhoneInputWrapper>
</StyledCustomPhoneInputContainer>
);
}}
@@ -6,7 +6,7 @@ import { isDefined } from 'twenty-shared/utils';
import { MenuItem } from 'twenty-ui/navigation';
import { Tag } from 'twenty-ui/components';
const StyledMenuItem = styled(MenuItem)`
const StyledMenuItemContainer = styled.div`
width: calc(100% - 2 * var(--horizontal-padding));
`;
@@ -29,28 +29,30 @@ export const RecordIndexPageKanbanAddMenuItem = ({
}
return (
<StyledMenuItem
text={
<Tag
variant={
recordGroupDefinition.type === RecordGroupDefinitionType.Value
? 'solid'
: 'outline'
}
color={
recordGroupDefinition.type === RecordGroupDefinitionType.Value
? recordGroupDefinition.color
: 'transparent'
}
text={recordGroupDefinition.title}
weight={
recordGroupDefinition.type === RecordGroupDefinitionType.Value
? 'regular'
: 'medium'
}
/>
}
onClick={() => onItemClick(recordGroupDefinition)}
/>
<StyledMenuItemContainer>
<MenuItem
text={
<Tag
variant={
recordGroupDefinition.type === RecordGroupDefinitionType.Value
? 'solid'
: 'outline'
}
color={
recordGroupDefinition.type === RecordGroupDefinitionType.Value
? recordGroupDefinition.color
: 'transparent'
}
text={recordGroupDefinition.title}
weight={
recordGroupDefinition.type === RecordGroupDefinitionType.Value
? 'regular'
: 'medium'
}
/>
}
onClick={() => onItemClick(recordGroupDefinition)}
/>
</StyledMenuItemContainer>
);
};
@@ -27,7 +27,7 @@ const StyledShowPageRightContainer = styled.div`
overflow: auto;
`;
const StyledTabList = styled(TabList)`
const StyledTabListContainer = styled.div`
background-color: ${themeCssVariables.background.secondary};
padding-left: ${themeCssVariables.spacing[2]};
`;
@@ -65,11 +65,13 @@ export const MergeRecordsContainer = ({
<TabListComponentInstanceContext.Provider
value={{ instanceId: instanceId }}
>
<StyledTabList
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={instanceId}
/>
<StyledTabListContainer>
<TabList
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={instanceId}
/>
</StyledTabListContainer>
</TabListComponentInstanceContext.Provider>
<StyledContentContainer>
{activeTabId === MergeRecordsTabId.MERGE_PREVIEW && (
@@ -8,7 +8,7 @@ import { getPositionNumberIcon } from '@/object-record/record-merge/utils/getPos
import { getPositionWordLabel } from '@/object-record/record-merge/utils/getPositionWordLabel';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledSection = styled(Section)`
const StyledSectionContainer = styled.div`
margin: ${themeCssVariables.spacing[4]};
width: auto;
`;
@@ -37,14 +37,16 @@ export const MergeSettingsTab = () => {
}
return (
<StyledSection>
<Select
dropdownId="merge-settings-priority-select"
options={priorityOptions}
value={mergeSettings.conflictPriorityIndex}
onChange={handleSelectionChange}
label={t`Fields conflicts`}
/>
</StyledSection>
<StyledSectionContainer>
<Section>
<Select
dropdownId="merge-settings-priority-select"
options={priorityOptions}
value={mergeSettings.conflictPriorityIndex}
onChange={handleSelectionChange}
label={t`Fields conflicts`}
/>
</Section>
</StyledSectionContainer>
);
};
@@ -16,10 +16,10 @@ import {
type AnimatedPlaceholderType,
} from 'twenty-ui/layout';
const StyledEmptyPlaceholderOuterContainer = styled(
AnimatedPlaceholderEmptyContainer,
)`
align-items: flex-start;
const StyledEmptyPlaceholderOuterContainer = styled.div`
> * {
align-items: flex-start;
}
`;
type RecordTableEmptyStateDisplayButtonComponentProps = {
@@ -22,8 +22,7 @@ const StyledContainer = styled.div`
padding-right: ${themeCssVariables.spacing[1]};
`;
// TODO: refactor
const StyledRecordTableTd = styled(RecordTableCellStyleWrapper)`
const StyledRecordTableTdContainer = styled.div`
border-left: 1px solid transparent;
`;
@@ -43,14 +42,16 @@ export const RecordTableCellCheckbox = () => {
);
return (
<StyledRecordTableTd
isSelected={isSelected}
hasRightBorder={false}
widthClassName={RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME}
>
<StyledContainer onClick={handleClick} data-select-disable>
<Checkbox hoverable checked={isSelected} />
</StyledContainer>
</StyledRecordTableTd>
<StyledRecordTableTdContainer>
<RecordTableCellStyleWrapper
isSelected={isSelected}
hasRightBorder={false}
widthClassName={RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME}
>
<StyledContainer onClick={handleClick} data-select-disable>
<Checkbox hoverable checked={isSelected} />
</StyledContainer>
</RecordTableCellStyleWrapper>
</StyledRecordTableTdContainer>
);
};
@@ -19,8 +19,7 @@ const StyledContainer = styled.div`
padding-right: ${themeCssVariables.spacing[1]};
`;
// TODO: refactor
const StyledRecordTableTd = styled(RecordTableCellStyleWrapper)`
const StyledRecordTableTdContainer = styled.div`
border-left: 1px solid transparent;
`;
@@ -28,14 +27,16 @@ export const RecordTableCellCheckboxPlaceholder = () => {
const { hasUserSelectedAllRows } = useRecordTableBodyContextOrThrow();
return (
<StyledRecordTableTd
isSelected={hasUserSelectedAllRows}
hasRightBorder={false}
widthClassName={RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME}
>
<StyledContainer data-select-disable>
<Checkbox hoverable checked={hasUserSelectedAllRows === true} />
</StyledContainer>
</StyledRecordTableTd>
<StyledRecordTableTdContainer>
<RecordTableCellStyleWrapper
isSelected={hasUserSelectedAllRows}
hasRightBorder={false}
widthClassName={RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME}
>
<StyledContainer data-select-disable>
<Checkbox hoverable checked={hasUserSelectedAllRows === true} />
</StyledContainer>
</RecordTableCellStyleWrapper>
</StyledRecordTableTdContainer>
);
};
@@ -1,5 +1,4 @@
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
import { StyledCell } from '@/object-record/record-table/record-table-cell/components/RecordTableCellStyleWrapper';
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState';
@@ -12,7 +11,30 @@ import { cx } from '@linaria/core';
import { useContext, type ReactNode } from 'react';
import { ThemeContext } from 'twenty-ui/theme-constants';
const StyledRecordTableTd = styled(StyledCell)<{ zIndex: number }>`
const StyledRecordTableTd = styled.div<{
backgroundColor: string;
borderColor: string;
isDragging?: boolean;
fontColor: string;
hasRightBorder?: boolean;
hasBottomBorder?: boolean;
zIndex: number;
}>`
border-bottom: 1px solid
${({ borderColor, hasBottomBorder, isDragging }) =>
hasBottomBorder && !isDragging ? borderColor : 'transparent'};
color: ${({ fontColor }) => fontColor};
border-right: ${({ borderColor, hasRightBorder }) =>
hasRightBorder ? `1px solid ${borderColor}` : 'none'};
padding: 0;
text-align: left;
background: ${({ backgroundColor, isDragging }) =>
isDragging ? 'transparent' : backgroundColor};
z-index: ${({ zIndex }) => zIndex};
`;
@@ -20,7 +20,7 @@ const StyledText = styled.span`
z-index: 1;
`;
const StyledScrollableContainer = styled.div`
const StyledValueContainer = styled.div`
overflow-x: auto;
white-space: nowrap;
scrollbar-width: none;
@@ -29,9 +29,7 @@ const StyledScrollableContainer = styled.div`
&::-webkit-scrollbar {
display: none;
}
`;
const StyledValueContainer = styled(StyledScrollableContainer)`
align-items: center;
display: flex;
gap: 4px;
@@ -38,7 +38,7 @@ const StyledCell = styled.div<{ isUnfolded: boolean; isFirstCell: boolean }>`
: '0'};
`;
const StyledIcon = styled(IconChevronDown)`
const StyledIconContainer = styled.div`
align-items: center;
display: flex;
height: 20px;
@@ -94,7 +94,9 @@ export const RecordTableColumnAggregateFooterValueCell = ({
dropdownId={dropdownId}
/>
{!hasAggregateOperationForViewField && (
<StyledIcon fontWeight="light" size={theme.icon.size.sm} />
<StyledIconContainer>
<IconChevronDown fontWeight="light" size={theme.icon.size.sm} />
</StyledIconContainer>
)}
</>
) : (
@@ -30,7 +30,7 @@ export type RecordTableColumnHeadDropdownMenuProps = {
objectMetadataId: string;
};
const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
const StyledDropdownMenuItemsContainerWrapper = styled.div`
z-index: ${themeCssVariables.lastLayerZIndex};
`;
@@ -136,44 +136,46 @@ export const RecordTableColumnHeadDropdownMenu = ({
return (
<DropdownContent>
<StyledDropdownMenuItemsContainer>
{isFilterable && (
<MenuItem
LeftIcon={IconFilter}
onClick={handleFilterClick}
text={t`Filter`}
/>
)}
{isSortable && (
<MenuItem
LeftIcon={IconSortDescending}
onClick={handleSortClick}
text={t`Sort`}
/>
)}
{showSeparator && <DropdownMenuSeparator />}
{canMoveLeft && (
<MenuItem
LeftIcon={IconArrowLeft}
onClick={handleColumnMoveLeft}
text={t`Move left`}
/>
)}
{canMoveRight && (
<MenuItem
LeftIcon={IconArrowRight}
onClick={handleColumnMoveRight}
text={t`Move right`}
/>
)}
{canHide && (
<MenuItem
LeftIcon={IconEyeOff}
onClick={async () => await handleColumnVisibility()}
text={t`Hide`}
/>
)}
</StyledDropdownMenuItemsContainer>
<StyledDropdownMenuItemsContainerWrapper>
<DropdownMenuItemsContainer>
{isFilterable && (
<MenuItem
LeftIcon={IconFilter}
onClick={handleFilterClick}
text={t`Filter`}
/>
)}
{isSortable && (
<MenuItem
LeftIcon={IconSortDescending}
onClick={handleSortClick}
text={t`Sort`}
/>
)}
{showSeparator && <DropdownMenuSeparator />}
{canMoveLeft && (
<MenuItem
LeftIcon={IconArrowLeft}
onClick={handleColumnMoveLeft}
text={t`Move left`}
/>
)}
{canMoveRight && (
<MenuItem
LeftIcon={IconArrowRight}
onClick={handleColumnMoveRight}
text={t`Move right`}
/>
)}
{canHide && (
<MenuItem
LeftIcon={IconEyeOff}
onClick={async () => await handleColumnVisibility()}
text={t`Hide`}
/>
)}
</DropdownMenuItemsContainer>
</StyledDropdownMenuItemsContainerWrapper>
</DropdownContent>
);
};
@@ -20,13 +20,6 @@ import {
import { type IconComponent } from 'twenty-ui/display';
import { useIsMobile } from 'twenty-ui/utilities';
const StyledDragDropPlaceholderCell = styled(
RecordTableDragAndDropPlaceholderCell,
)`
left: 0;
position: sticky;
`;
const StyledFieldPlaceholderCell = styled.div<{ widthOfFields: number }>`
height: ${RECORD_TABLE_ROW_HEIGHT}px;
min-width: ${({ widthOfFields }) => widthOfFields}px;
@@ -137,7 +130,7 @@ export const RecordTableActionRow = ({
return (
<StyledRecordTableDraggableTr onClick={onClick}>
<StyledDragDropPlaceholderCell />
<RecordTableDragAndDropPlaceholderCell />
<StyledIconContainer>
<LeftIcon
stroke={theme.icon.stroke.sm}
@@ -3,7 +3,7 @@ import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/use
import { styled } from '@linaria/react';
import { NotificationCounter } from 'twenty-ui/navigation';
const StyledNotificationCounter = styled(NotificationCounter)`
const StyledNotificationCounterContainer = styled.div`
position: absolute;
top: -7px;
left: -7px;
@@ -23,5 +23,9 @@ export const RecordTableRowMultiDragCounterChip = () => {
return null;
}
return <StyledNotificationCounter count={selectedCount} />;
return (
<StyledNotificationCounterContainer>
<NotificationCounter count={selectedCount} />
</StyledNotificationCounterContainer>
);
};
@@ -64,7 +64,7 @@ const StyledChevronContainer = styled.div`
z-index: ${TABLE_Z_INDEX.groupSection.stickyCell};
`;
const StyledAnimatedLightIconButton = styled(AnimatedLightIconButton)`
const StyledAnimatedLightIconButtonContainer = styled.div`
display: block;
margin: auto;
@@ -88,7 +88,7 @@ const StyledRecordGroupSection = styled.div<{ width: number }>`
z-index: ${TABLE_Z_INDEX.groupSection.stickyCell};
`;
const StyledTag = styled(Tag)`
const StyledTagContainer = styled.div`
flex-shrink: 0;
`;
@@ -195,32 +195,36 @@ export const RecordTableRecordGroupSection = () => {
<StyledTrContainer onClick={handleDropdownToggle}>
<StyledRecordTableDragAndDropPlaceholderCell />
<StyledChevronContainer>
<StyledAnimatedLightIconButton
Icon={IconChevronDown}
size="small"
accent="secondary"
animate={{ rotate: !isRecordGroupTableSectionToggled ? -90 : 0 }}
transition={{ duration: theme.animation.duration.normal }}
/>
<StyledAnimatedLightIconButtonContainer>
<AnimatedLightIconButton
Icon={IconChevronDown}
size="small"
accent="secondary"
animate={{ rotate: !isRecordGroupTableSectionToggled ? -90 : 0 }}
transition={{ duration: theme.animation.duration.normal }}
/>
</StyledAnimatedLightIconButtonContainer>
</StyledChevronContainer>
<StyledRecordGroupSection
className="disable-shadow"
width={widthOfLabelIdentifierRecordField}
>
<StyledTag
variant={
recordGroupDefinition.type !== RecordGroupDefinitionType.NoValue
? 'solid'
: 'outline'
}
color={
recordGroupDefinition.type !== RecordGroupDefinitionType.NoValue
? recordGroupDefinition.color
: 'transparent'
}
text={recordGroupDefinition.title}
weight="medium"
/>
<StyledTagContainer>
<Tag
variant={
recordGroupDefinition.type !== RecordGroupDefinitionType.NoValue
? 'solid'
: 'outline'
}
color={
recordGroupDefinition.type !== RecordGroupDefinitionType.NoValue
? recordGroupDefinition.color
: 'transparent'
}
text={recordGroupDefinition.title}
weight="medium"
/>
</StyledTagContainer>
<RecordBoardColumnHeaderAggregateDropdown
aggregateValue={recordIndexAggregateDisplayValueForGroupValue}
dropdownId={`record-group-section-aggregate-dropdown-${currentRecordGroupId}`}
@@ -10,12 +10,14 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { Section } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledSection = styled(Section)`
display: flex;
flex-direction: column;
gap: ${themeCssVariables.spacing[6]};
padding: ${themeCssVariables.spacing[4]} ${themeCssVariables.spacing[3]};
width: auto;
const StyledSectionContainer = styled.div`
> * {
display: flex;
flex-direction: column;
gap: ${themeCssVariables.spacing[6]};
padding: ${themeCssVariables.spacing[4]} ${themeCssVariables.spacing[3]};
width: auto;
}
`;
export type UpdateMultipleRecordsFormProps = {
@@ -50,35 +52,37 @@ export const UpdateMultipleRecordsForm = ({
}));
return (
<StyledSection>
{fieldsWithDefinitions.map(({ fieldMetadataItem, fieldDefinition }) => {
const fieldName = fieldDefinition.metadata.fieldName;
const isRelation = isFieldRelation(fieldDefinition);
const fieldNameOrRelationIdName =
isRelation && fieldMetadataItem.type === FieldMetadataType.RELATION
? (fieldMetadataItem.settings?.joinColumnName as string)
: fieldName;
<StyledSectionContainer>
<Section>
{fieldsWithDefinitions.map(({ fieldMetadataItem, fieldDefinition }) => {
const fieldName = fieldDefinition.metadata.fieldName;
const isRelation = isFieldRelation(fieldDefinition);
const fieldNameOrRelationIdName =
isRelation && fieldMetadataItem.type === FieldMetadataType.RELATION
? (fieldMetadataItem.settings?.joinColumnName as string)
: fieldName;
const value = values[fieldNameOrRelationIdName];
const value = values[fieldNameOrRelationIdName];
const handleValueChange = (newValue: any) => {
if (isUpdateRecordValueEmpty(newValue)) {
onChange(fieldNameOrRelationIdName, undefined);
} else {
onChange(fieldNameOrRelationIdName, newValue);
}
};
const handleValueChange = (newValue: any) => {
if (isUpdateRecordValueEmpty(newValue)) {
onChange(fieldNameOrRelationIdName, undefined);
} else {
onChange(fieldNameOrRelationIdName, newValue);
}
};
return (
<FormFieldInput
key={fieldDefinition.metadata.fieldName}
readonly={disabled}
field={fieldDefinition}
defaultValue={value}
onChange={handleValueChange}
/>
);
})}
</StyledSection>
return (
<FormFieldInput
key={fieldDefinition.metadata.fieldName}
readonly={disabled}
field={fieldDefinition}
defaultValue={value}
onChange={handleValueChange}
/>
);
})}
</Section>
</StyledSectionContainer>
);
};
@@ -10,7 +10,7 @@ type OnboardingSyncEmailsSettingsCardProps = {
value?: MessageChannelVisibility;
};
const StyledCardMedia = styled(SettingsAccountsVisibilityIcon)`
const StyledCardMediaContainer = styled.div`
width: ${themeCssVariables.spacing[10]};
`;
@@ -21,11 +21,13 @@ export const OnboardingSyncEmailsSettingsCard = ({
const optionsWithCardMedia = ONBOARDING_SYNC_EMAILS_OPTIONS.map((option) => ({
...option,
cardMedia: (
<StyledCardMedia
metadata={option.cardMediaProps.metadata}
subject={option.cardMediaProps.subject}
body={option.cardMediaProps.body}
/>
<StyledCardMediaContainer>
<SettingsAccountsVisibilityIcon
metadata={option.cardMediaProps.metadata}
subject={option.cardMediaProps.subject}
body={option.cardMediaProps.body}
/>
</StyledCardMediaContainer>
),
}));
@@ -42,11 +42,11 @@ const StyledTabsAndDashboardContainer = styled.div`
overflow: hidden;
`;
const StyledPageLayoutTabList = styled(PageLayoutTabList)`
const StyledPageLayoutTabListContainer = styled.div`
padding-left: ${themeCssVariables.spacing[2]};
`;
const StyledScrollWrapper = styled(ScrollWrapper)`
const StyledScrollWrapperContainer = styled.div`
flex: 1;
`;
@@ -130,27 +130,31 @@ export const PageLayoutRendererContent = () => {
}
/>
{(sortedTabs.length > 1 || isPageLayoutInEditMode) && (
<StyledPageLayoutTabList
tabs={sortedTabs}
behaveAsLinks={!isInSidePanel && !isPageLayoutInEditMode}
componentInstanceId={tabListInstanceId}
onAddTab={handleAddTab}
isReorderEnabled={canEnableTabEditing}
onReorder={canEnableTabEditing ? reorderTabs : undefined}
pageLayoutType={currentPageLayout.type}
/>
<StyledPageLayoutTabListContainer>
<PageLayoutTabList
tabs={sortedTabs}
behaveAsLinks={!isInSidePanel && !isPageLayoutInEditMode}
componentInstanceId={tabListInstanceId}
onAddTab={handleAddTab}
isReorderEnabled={canEnableTabEditing}
onReorder={canEnableTabEditing ? reorderTabs : undefined}
pageLayoutType={currentPageLayout.type}
/>
</StyledPageLayoutTabListContainer>
)}
<StyledScrollWrapper
componentInstanceId={getScrollWrapperInstanceIdFromPageLayoutId(
currentPageLayout.id,
)}
defaultEnableXScroll={false}
>
{isDefined(activeTabId) && (
<PageLayoutMainContent tabId={activeTabId} />
)}
</StyledScrollWrapper>
<StyledScrollWrapperContainer>
<ScrollWrapper
componentInstanceId={getScrollWrapperInstanceIdFromPageLayoutId(
currentPageLayout.id,
)}
defaultEnableXScroll={false}
>
{isDefined(activeTabId) && (
<PageLayoutMainContent tabId={activeTabId} />
)}
</ScrollWrapper>
</StyledScrollWrapperContainer>
</StyledTabsAndDashboardContainer>
</StyledContainer>
);
@@ -26,9 +26,12 @@ import { LightIconButton } from 'twenty-ui/input';
import { RelationType } from '~/generated-metadata/graphql';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledLink = styled(Link)`
const StyledLinkContainer = styled.div`
display: flex;
text-decoration: none;
> * {
text-decoration: none;
}
`;
const StyledSeeAllButtonWrapper = styled.div<{ isMobile: boolean }>`
@@ -131,11 +134,13 @@ export const WidgetActionFieldSeeAll = () => {
return (
<>
<div id={tooltipId}>
<StyledLink to={filterLinkHref} data-testid="widget-see-all-link">
<StyledSeeAllButtonWrapper isMobile={isMobile}>
<LightIconButton Icon={IconArrowUpRight} accent="secondary" />
</StyledSeeAllButtonWrapper>
</StyledLink>
<StyledLinkContainer>
<Link to={filterLinkHref} data-testid="widget-see-all-link">
<StyledSeeAllButtonWrapper isMobile={isMobile}>
<LightIconButton Icon={IconArrowUpRight} accent="secondary" />
</StyledSeeAllButtonWrapper>
</Link>
</StyledLinkContainer>
</div>
<AppTooltip
anchorSelect={`#${tooltipId}`}
@@ -30,7 +30,8 @@ const StyledButton = styled.button`
}
`;
const StyledIcon = styled(IconChevronDown)`
const StyledIconContainer = styled.span`
display: flex;
height: 16px;
width: 16px;
`;
@@ -41,7 +42,9 @@ export const FieldWidgetShowMoreButton = ({
}: FieldWidgetShowMoreButtonProps) => {
return (
<StyledButton data-testid="field-widget-show-more-button" onClick={onClick}>
<StyledIcon />
<StyledIconContainer>
<IconChevronDown />
</StyledIconContainer>
{t`More (${remainingCount})`}
</StyledButton>
);
@@ -40,9 +40,14 @@ const StyledPropertyBox = styled.div`
padding-bottom: ${themeCssVariables.spacing[3]};
`;
const StyledInlineFieldsPropertyBox = styled(StyledPropertyBox)`
padding-bottom: 0;
const StyledInlineFieldsPropertyBox = styled.div`
align-self: stretch;
border-radius: ${themeCssVariables.border.radius.sm};
display: flex;
flex-direction: column;
gap: ${themeCssVariables.spacing[2]};
padding-top: 0;
padding-bottom: 0;
`;
type FieldsWidgetProps = {
@@ -37,9 +37,11 @@ const StyledTrendIconContainer = styled.div`
justify-content: center;
`;
const StyledH1Title = styled(H1Title)`
font-size: ${themeCssVariables.font.size.xxl};
margin: 0;
const StyledH1TitleWrapper = styled.div`
> h2 {
font-size: ${themeCssVariables.font.size.xxl};
margin: 0;
}
`;
export const GraphWidgetAggregateChart = ({
@@ -58,10 +60,9 @@ export const GraphWidgetAggregateChart = ({
return (
<StyledContainer>
<StyledH1Title
title={displayValue}
fontColor={H1TitleFontColor.Primary}
/>
<StyledH1TitleWrapper>
<H1Title title={displayValue} fontColor={H1TitleFontColor.Primary} />
</StyledH1TitleWrapper>
{isDefined(trendPercentage) && (
<StyledTrendIconContainer>
<StyledTrendPercentageValue>
@@ -51,7 +51,7 @@ const StyledChartContainer = styled.div<{ $isClickable?: boolean }>`
: ''}
`;
const StyledH1Title = styled(H1Title)`
const StyledH1TitleWrapper = styled.div`
left: 50%;
position: absolute;
top: 50%;
@@ -135,10 +135,12 @@ export const GraphWidgetGaugeChart = ({
layers={['bars', renderValueEndLine]}
/>
{showValue && (
<StyledH1Title
title={formattedValue}
fontColor={H1TitleFontColor.Primary}
/>
<StyledH1TitleWrapper>
<H1Title
title={formattedValue}
fontColor={H1TitleFontColor.Primary}
/>
</StyledH1TitleWrapper>
)}
</StyledChartContainer>
<GraphWidgetLegend
@@ -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>
);
};

Some files were not shown because too many files have changed in this diff Show More