From eda905f271d72e984186509869caa7e0de243657 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Wed, 4 Mar 2026 17:04:16 +0100 Subject: [PATCH] [DevXP] Improve Linaria pre-build speed (#18382) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR improves Linaria/WYW pre-build speed and continues the migration of `twenty-ui` components away from runtime `ThemeContext` reads toward static CSS variables and theme constants. ### Linaria/WYW profiling plugin improvements (`twenty-shared`) - **Babel JIT warmup**: added a `buildStart` warmup step that triggers WYW's Babel JIT compilation before the real build starts, so the first real file doesn't pay the cold-start penalty - **`configResolved` hook**: detects dev vs prod mode and resolves the correct warmup file path relative to `config.root` - **Dev-only per-file logging**: slow file warnings are now gated behind `isDevMode`, keeping production/CI build output clean - **`closeBundle` summary**: moved the final top-slow-files report to `closeBundle` for accurate end-of-build reporting - **Removed noisy progress interval logging** in favor of the warmup log + final summary ### Migration from `ThemeContext` to static CSS variables / constants Across `twenty-ui`, replaced runtime `useTheme()` reads with: - `themeCssVariables` CSS custom properties (colors, spacing) - Hard-coded design-system constants (`ICON.size.md` → `16`, `ICON.stroke.sm` → `1.6`) so components no longer need a React context at render time — enabling Linaria static extraction **Components migrated:** - `Button`, `AnimatedButton`, `LightButton`, `LightIconButton`, `AnimatedLightIconButton`, `ButtonIcon`, `ButtonSoon` - `ProgressBar` (Framer Motion width animation → CSS `transition`) - `Info`, `HorizontalSeparator`, `LinkChip` - `MenuPicker`, `MenuItemLeftContent`, `MenuItemIconWithGripSwap`, `NavigationBarItem` - `JsonArrow`, `JsonNestedNode` - `ModalHeader` ### Other - Added `aria-valuenow` to `ProgressBar` for accessibility - `VisibilityHidden` component updated to inline accessibility styles --- .../components/AppRootErrorFallback.tsx | 15 +-- .../components/FavoriteFolderPickerList.tsx | 6 +- .../components/RecordCalendarCardBody.tsx | 9 +- .../components/RecordCalendarCardHeader.tsx | 13 +-- .../components/RecordCardBodyContainer.tsx | 8 +- .../components/RecordCardHeaderContainer.tsx | 9 +- .../components/FormSingleRecordFieldChip.tsx | 20 ++-- .../MultipleRecordPickerMenuItem.tsx | 8 -- .../MultipleRecordPickerMenuItemContent.tsx | 10 +- .../components/SingleRecordPickerMenuItem.tsx | 10 +- .../HorizontalScrollBoxShadowCSS.ts | 21 ++++ .../components/RecordTableStyleWrapper.tsx | 42 +------ .../components/VerticalScrollBoxShadowCSS.ts | 20 ++++ ...cordTableBodyVirtualizedDraggableClone.tsx | 17 +-- .../components/SettingsAdminTableCard.tsx | 76 ++++--------- .../SettingsAdminQueueJobsTable.tsx | 63 ++++------- .../SettingsDataModelFieldPreview.tsx | 14 +-- .../ui/layout/table/components/TableCell.tsx | 20 +++- .../layout/table/components/TableHeader.tsx | 3 +- .../ui/layout/table/components/TableRow.tsx | 36 +++++- .../WorkflowRunStepJsonContainer.tsx | 23 ++-- .../components/WorkflowStepBody.tsx | 45 ++++++-- .../components/WorkflowEditActionAiAgent.tsx | 9 +- .../WorkflowEditActionFilterBody.tsx | 8 +- .../WorkflowEditActionFormBuilder.tsx | 106 ++++++++++-------- .../WorkflowEditActionIfElseBody.tsx | 8 +- .../ai/components/SettingsAIModelsTab.tsx | 7 +- .../ai/components/SettingsSkillTableRow.tsx | 36 +++--- .../ai/components/SettingsSkillsTable.tsx | 74 ++++++------ .../src/application/fieldManifestType.ts | 10 +- .../src/vite/createWywProfilingPlugin.ts | 83 ++++++++++---- .../scripts/generateThemeConstants.ts | 6 + .../components/VisibilityHidden.tsx | 11 +- .../src/components/chip/LinkChip.tsx | 63 ++++++----- .../src/display/info/components/Info.tsx | 38 ++++--- .../text/components/HorizontalSeparator.tsx | 24 ++-- .../progress-bar/components/ProgressBar.tsx | 39 +++---- .../button/components/AnimatedButton.tsx | 31 +++-- .../components/AnimatedLightIconButton.tsx | 11 +- .../input/button/components/Button/Button.tsx | 17 ++- .../components/Button/internal/ButtonIcon.tsx | 35 +++--- .../components/Button/internal/ButtonSoon.tsx | 7 +- .../input/button/components/LightButton.tsx | 9 +- .../button/components/LightIconButton.tsx | 11 +- .../components/JsonNestedNode.tsx | 66 +++++------ .../components/internal/JsonArrow.tsx | 31 +++-- .../src/layout/card/components/Card.tsx | 6 + .../layout/modal/components/ModalHeader.tsx | 3 - .../navigation/menu/components/MenuPicker.tsx | 12 +- .../components/MenuItemIconWithGripSwap.tsx | 17 ++- .../components/MenuItemLeftContent.tsx | 24 ++-- .../components/StyledMenuItemBase.tsx | 16 ++- .../components/NavigationBarItem.tsx | 19 +--- .../generated/themeLightCssVariableEntries.ts | 14 +++ .../twenty-ui/src/theme-constants/index.ts | 2 + 55 files changed, 691 insertions(+), 650 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-table/components/HorizontalScrollBoxShadowCSS.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/components/VerticalScrollBoxShadowCSS.ts diff --git a/packages/twenty-front/src/modules/error-handler/components/AppRootErrorFallback.tsx b/packages/twenty-front/src/modules/error-handler/components/AppRootErrorFallback.tsx index df077c1b8f..2240825f54 100644 --- a/packages/twenty-front/src/modules/error-handler/components/AppRootErrorFallback.tsx +++ b/packages/twenty-front/src/modules/error-handler/components/AppRootErrorFallback.tsx @@ -1,15 +1,13 @@ import { type AppErrorDisplayProps } from '@/error-handler/types/AppErrorDisplayProps'; import { styled } from '@linaria/react'; import { t } from '@lingui/core/macro'; -import { motion } from 'framer-motion'; import { IconReload } from 'twenty-ui/display'; -import { THEME_DARK } from 'twenty-ui/theme'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { ICON_SIZES, themeCssVariables } from 'twenty-ui/theme-constants'; type AppRootErrorFallbackProps = AppErrorDisplayProps; const StyledContainer = styled.div` - background: ${THEME_DARK.background.noisy}; + background: ${themeCssVariables.background.noisy}; box-sizing: border-box; display: flex; height: 100vh; @@ -27,7 +25,7 @@ const StyledPanel = styled.div` width: 100%; `; -const StyledEmptyContainer = styled(motion.div)` +const StyledEmptyContainer = styled.div` align-items: center; width: 100%; height: 100%; @@ -94,8 +92,9 @@ const StyledButton = styled.button` padding: 8px; `; -const StyledIcon = styled(IconReload)` +const StyledIconContainer = styled.span` color: ${themeCssVariables.grayScale.gray12}; + display: inline-flex; margin-right: 8px; `; @@ -124,7 +123,9 @@ export const AppRootErrorFallback = ({ - + + + {t`Reload`} diff --git a/packages/twenty-front/src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx b/packages/twenty-front/src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx index c168c59793..8aee475a5e 100644 --- a/packages/twenty-front/src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx +++ b/packages/twenty-front/src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx @@ -12,7 +12,7 @@ const StyledItemsContainer = styled.div` width: 100%; `; -const StyledDropdownMenuSeparator = styled(DropdownMenuSeparator)` +const StyledSeparatorContainer = styled.div` margin-bottom: ${themeCssVariables.spacing[1]}; margin-top: ${themeCssVariables.spacing[1]}; `; @@ -59,7 +59,9 @@ export const FavoriteFolderPickerList = ({ /> )} {showNoFolderOption && filteredFolders.length > 0 && ( - + + + )} {filteredFolders.length > 0 ? filteredFolders.map((folder) => ( diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardBody.tsx b/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardBody.tsx index 368655b073..a3e0a81738 100644 --- a/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardBody.tsx +++ b/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardBody.tsx @@ -17,13 +17,8 @@ import { RecordInlineCell } from '@/object-record/record-inline-cell/components/ import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; -import { styled } from '@linaria/react'; import { themeCssVariables } from 'twenty-ui/theme-constants'; -const StyledRecordCardBodyContainer = styled(RecordCardBodyContainer)` - padding: ${themeCssVariables.spacing[1]}; -`; - type RecordCalendarCardBodyProps = { recordId: string; isRecordReadOnly: boolean; @@ -73,7 +68,7 @@ export const RecordCalendarCardBody = ({ }; return ( - + {visibleRecordFieldsExceptLabelIdentifier.map((recordField, index) => { const correspondingFieldDefinition = fieldDefinitionByFieldMetadataItemId[recordField.fieldMetadataItemId]; @@ -120,6 +115,6 @@ export const RecordCalendarCardBody = ({ ); })} - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardHeader.tsx b/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardHeader.tsx index 6567c577e9..23b83f78fa 100644 --- a/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardHeader.tsx @@ -9,12 +9,12 @@ import { useAtomComponentFamilyState } from '@/ui/utilities/state/jotai/hooks/us import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { styled } from '@linaria/react'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; import { isDefined } from 'twenty-shared/utils'; import { ChipVariant } from 'twenty-ui/components'; import { Checkbox, CheckboxVariant } from 'twenty-ui/input'; import { isRecordCalendarCardSelectedComponentFamilyState } from '@/object-record/record-calendar/record-calendar-card/states/isRecordCalendarCardSelectedComponentFamilyState'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; const StyledCheckboxContainer = styled.div` margin-left: auto; @@ -27,10 +27,6 @@ const StyledRecordChipContainer = styled.div` padding: ${themeCssVariables.spacing[1]}; `; -const StyledRecordCardHeaderContainer = styled(RecordCardHeaderContainer)` - padding: ${themeCssVariables.spacing[1]}; -`; - type RecordCalendarCardHeaderProps = { recordId: string; }; @@ -68,7 +64,10 @@ export const RecordCalendarCardHeader = ({ } return ( - + - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-card/components/RecordCardBodyContainer.tsx b/packages/twenty-front/src/modules/object-record/record-card/components/RecordCardBodyContainer.tsx index bb7bcfacec..7a6a4d375e 100644 --- a/packages/twenty-front/src/modules/object-record/record-card/components/RecordCardBodyContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-card/components/RecordCardBodyContainer.tsx @@ -1,13 +1,13 @@ import { styled } from '@linaria/react'; import { themeCssVariables } from 'twenty-ui/theme-constants'; -const StyledCardBodyContainer = styled.div` +const StyledCardBodyContainer = styled.div<{ padding?: string }>` display: flex; flex-direction: column; gap: ${themeCssVariables.spacing['0.5']}; - padding-bottom: ${themeCssVariables.spacing[2]}; - padding-left: 10px; - padding-right: ${themeCssVariables.spacing[2]}; + padding: ${({ padding }) => + padding ?? + `0 ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]} 10px`}; span { align-items: center; display: flex; diff --git a/packages/twenty-front/src/modules/object-record/record-card/components/RecordCardHeaderContainer.tsx b/packages/twenty-front/src/modules/object-record/record-card/components/RecordCardHeaderContainer.tsx index 292d31922d..58d26ba674 100644 --- a/packages/twenty-front/src/modules/object-record/record-card/components/RecordCardHeaderContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-card/components/RecordCardHeaderContainer.tsx @@ -3,6 +3,7 @@ import { themeCssVariables } from 'twenty-ui/theme-constants'; export const StyledBoardCardHeaderContainer = styled.div<{ isCompact: boolean; + padding?: string; }>` align-items: center; display: flex; @@ -10,11 +11,9 @@ export const StyledBoardCardHeaderContainer = styled.div<{ justify-content: space-between; font-weight: ${themeCssVariables.font.weight.medium}; height: 24px; - padding-bottom: ${({ isCompact }) => - isCompact ? themeCssVariables.spacing[2] : themeCssVariables.spacing[1]}; - padding-left: ${themeCssVariables.spacing[2]}; - padding-right: ${themeCssVariables.spacing[2]}; - padding-top: ${themeCssVariables.spacing[2]}; + padding: ${({ padding, isCompact }) => + padding ?? + `${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]} ${isCompact ? themeCssVariables.spacing[2] : themeCssVariables.spacing[1]} ${themeCssVariables.spacing[2]}`}; transition: padding ease-in-out 160ms; img { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormSingleRecordFieldChip.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormSingleRecordFieldChip.tsx index 6e88154453..e63d9123d5 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormSingleRecordFieldChip.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormSingleRecordFieldChip.tsx @@ -11,11 +11,11 @@ import { styled } from '@linaria/react'; import { t } from '@lingui/core/macro'; import { themeCssVariables } from 'twenty-ui/theme-constants'; -const StyledRecordChip = styled(RecordChip)` +const StyledRecordChipContainer = styled.div` margin: ${themeCssVariables.spacing[2]}; `; -const StyledPlaceholder = styled(FormFieldPlaceholder)` +const StyledPlaceholderContainer = styled.div` margin: ${themeCssVariables.spacing[2]}; `; @@ -58,12 +58,18 @@ export const FormSingleRecordFieldChip = ({ if (!!draftValue && draftValue.type === 'static' && !!selectedRecord) { return ( - + + + ); } - return {t`Select`}; + return ( + + {t`Select`} + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItem.tsx b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItem.tsx index 837ae06ba6..5514cc3264 100644 --- a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItem.tsx +++ b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItem.tsx @@ -1,16 +1,8 @@ -import { styled } from '@linaria/react'; - import { useRecordPickerGetSearchRecordAndObjectMetadataItemFromRecordId } from '@/object-record/record-picker/hooks/useRecordPickerGetSearchRecordAndObjectMetadataItemFromRecordId'; import { MultipleRecordPickerMenuItemContent } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItemContent'; import { type RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem'; -import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { isDefined } from 'twenty-shared/utils'; -export const StyledSelectableItem = styled(SelectableListItem)` - height: 100%; - width: 100%; -`; - type MultipleRecordPickerMenuItemProps = { recordId: string; onChange: (morphItem: RecordPickerPickableMorphItem) => void; diff --git a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItemContent.tsx b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItemContent.tsx index 8a97314b2a..da9e6a91fc 100644 --- a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItemContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItemContent.tsx @@ -1,5 +1,4 @@ import { t } from '@lingui/core/macro'; -import { styled } from '@linaria/react'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { getAvatarType } from '@/object-metadata/utils/getAvatarType'; @@ -20,11 +19,6 @@ import { MenuItemMultiSelectAvatar } from 'twenty-ui/navigation'; import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState'; import { type SearchRecord } from '~/generated/graphql'; -export const StyledSelectableItem = styled(SelectableListItem)` - height: 100%; - width: 100%; -`; - type MultipleRecordPickerMenuItemContentProps = { searchRecord: SearchRecord; objectMetadataItem: ObjectMetadataItem; @@ -78,7 +72,7 @@ export const MultipleRecordPickerMenuItemContent = ({ multipleRecordPickerSearchableObjectMetadataItems.length > 1; return ( - handleSelectChange(!isRecordSelectedWithObjectItem)} @@ -103,6 +97,6 @@ export const MultipleRecordPickerMenuItemContent = ({ : undefined } /> - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItem.tsx b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItem.tsx index a127079af8..cb2f074b10 100644 --- a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItem.tsx +++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItem.tsx @@ -1,5 +1,3 @@ -import { styled } from '@linaria/react'; - import { getAvatarType } from '@/object-metadata/utils/getAvatarType'; import { searchRecordStoreFamilyState } from '@/object-record/record-picker/multiple-record-picker/states/searchRecordStoreComponentFamilyState'; import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext'; @@ -22,10 +20,6 @@ type SingleRecordPickerMenuItemProps = { isRecordSelected: boolean; }; -const StyledSelectableItem = styled(SelectableListItem)` - width: 100%; -`; - export const SingleRecordPickerMenuItem = ({ morphItem, onMorphItemSelected, @@ -64,7 +58,7 @@ export const SingleRecordPickerMenuItem = ({ singleRecordPickerSearchableObjectMetadataItems.length > 1; return ( - { @@ -94,6 +88,6 @@ export const SingleRecordPickerMenuItem = ({ : undefined } /> - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/HorizontalScrollBoxShadowCSS.ts b/packages/twenty-front/src/modules/object-record/record-table/components/HorizontalScrollBoxShadowCSS.ts new file mode 100644 index 0000000000..e19de57414 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/components/HorizontalScrollBoxShadowCSS.ts @@ -0,0 +1,21 @@ +import { RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableHorizontalScrollShadowVisibilityCssVariableName'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +export const HorizontalScrollBoxShadowCSS = ` + &::after { + content: ''; + position: absolute; + top: -1px; + height: calc(100% + 2px); + width: 4px; + right: -1px; + box-shadow: + 2px 0px 4px 0px ${themeCssVariables.boxShadow.color}, + 0px 0px 4px 0px ${themeCssVariables.boxShadow.color}; + clip-path: inset(0px -4px 0px 0px); + visibility: var( + ${RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME}, + hidden + ); + } +`; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStyleWrapper.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStyleWrapper.tsx index a11d0237a7..c0f4b0e69d 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStyleWrapper.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStyleWrapper.tsx @@ -9,51 +9,15 @@ import { RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_CLASS_NAME } from '@/object import { RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableColumnLastEmptyColumnWidthVariableName'; import { RECORD_TABLE_COLUMN_WITH_GROUP_LAST_EMPTY_COLUMN_WIDTH_CLASS_NAME } from '@/object-record/record-table/constants/RecordTableColumnWithGroupLastEmptyColumnWidthClassName'; import { RECORD_TABLE_COLUMN_WITH_GROUP_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableColumnWithGroupLastEmptyColumnWidthVariableName'; -import { RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableHorizontalScrollShadowVisibilityCssVariableName'; -import { RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableVerticalScrollShadowVisibilityCssVariableName'; - import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex'; +import { HorizontalScrollBoxShadowCSS } from '@/object-record/record-table/components/HorizontalScrollBoxShadowCSS'; +import { VerticalScrollBoxShadowCSS } from '@/object-record/record-table/components/VerticalScrollBoxShadowCSS'; import { getRecordTableColumnFieldWidthClassName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthClassName'; import { getRecordTableColumnFieldWidthCSSVariableName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthCSSVariableName'; import { styled } from '@linaria/react'; import { themeCssVariables } from 'twenty-ui/theme-constants'; -export const VerticalScrollBoxShadowCSS = ` - &::before { - bottom: -1px; - box-shadow: - 0px 2px 4px 0px ${themeCssVariables.boxShadow.color}, - 0px 0px 4px 0px ${themeCssVariables.boxShadow.color}; - clip-path: inset(0px 0px -4px 0px); - content: ''; - height: 4px; - position: absolute; - visibility: var( - ${RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME}, - hidden - ); - width: 100%; - } -`; - -export const HorizontalScrollBoxShadowCSS = ` - &::after { - content: ''; - position: absolute; - top: -1px; - height: calc(100% + 2px); - width: 4px; - right: -1px; - box-shadow: - 2px 0px 4px 0px ${themeCssVariables.boxShadow.color}, - 0px 0px 4px 0px ${themeCssVariables.boxShadow.color}; - clip-path: inset(0px -4px 0px 0px); - visibility: var( - ${RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME}, - hidden - ); - } -`; +export { HorizontalScrollBoxShadowCSS, VerticalScrollBoxShadowCSS }; const MAX_COLUMNS = 100; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/VerticalScrollBoxShadowCSS.ts b/packages/twenty-front/src/modules/object-record/record-table/components/VerticalScrollBoxShadowCSS.ts new file mode 100644 index 0000000000..16c29dfcc8 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/components/VerticalScrollBoxShadowCSS.ts @@ -0,0 +1,20 @@ +import { RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableVerticalScrollShadowVisibilityCssVariableName'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +export const VerticalScrollBoxShadowCSS = ` + &::before { + bottom: -1px; + box-shadow: + 0px 2px 4px 0px ${themeCssVariables.boxShadow.color}, + 0px 0px 4px 0px ${themeCssVariables.boxShadow.color}; + clip-path: inset(0px 0px -4px 0px); + content: ''; + height: 4px; + position: absolute; + visibility: var( + ${RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME}, + hidden + ); + width: 100%; + } +`; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyVirtualizedDraggableClone.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyVirtualizedDraggableClone.tsx index 525794c2bc..ed668b5a87 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyVirtualizedDraggableClone.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyVirtualizedDraggableClone.tsx @@ -1,7 +1,5 @@ -import { - getRecordTableColumnWidthInlineStyles, - HorizontalScrollBoxShadowCSS, -} from '@/object-record/record-table/components/RecordTableStyleWrapper'; +import { getRecordTableColumnWidthInlineStyles } from '@/object-record/record-table/components/RecordTableStyleWrapper'; +import { HorizontalScrollBoxShadowCSS } from '@/object-record/record-table/components/HorizontalScrollBoxShadowCSS'; import { RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnAddColumnButtonWidth'; import { RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH_CLASS_NAME } from '@/object-record/record-table/constants/RecordTableColumnAddColumnButtonWidthClassName'; import { RECORD_TABLE_COLUMN_CHECKBOX_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnCheckboxWidth'; @@ -34,10 +32,9 @@ import { type DraggableRubric, type DraggableStateSnapshot, } from '@hello-pangea/dnd'; -import { useContext, useMemo } from 'react'; +import { useMemo } from 'react'; import { isDefined } from 'twenty-shared/utils'; -import { ThemeContext } from 'twenty-ui/theme'; -import { MOBILE_VIEWPORT } from 'twenty-ui/theme-constants'; +import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants'; const MAX_COLUMNS = 100; @@ -139,8 +136,6 @@ export const RecordTableBodyVirtualizedDraggableClone = ({ }) => { const realIndex = rubric.source.index; - const { theme } = useContext(ThemeContext); - const recordId = useAtomComponentFamilySelectorValue( recordIdByRealIndexComponentFamilySelector, realIndex, @@ -178,10 +173,10 @@ export const RecordTableBodyVirtualizedDraggableClone = ({ style={{ ...draggableProvided.draggableProps.style, background: draggableSnapshot.isDragging - ? theme.background.transparent.light + ? themeCssVariables.background.transparent.light : undefined, borderColor: draggableSnapshot.isDragging - ? `${theme.border.color.medium}` + ? themeCssVariables.border.color.medium : 'transparent', opacity: isSecondaryDragged ? 0.3 : undefined, }} diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTableCard.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTableCard.tsx index de62a1a47f..e32099ac9f 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTableCard.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTableCard.tsx @@ -2,52 +2,10 @@ import { Table } from '@/ui/layout/table/components/Table'; import { TableBody } from '@/ui/layout/table/components/TableBody'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; -import { styled } from '@linaria/react'; -import { useContext } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { type IconComponent } from 'twenty-ui/display'; import { Card } from 'twenty-ui/layout'; -import { ThemeContext } from 'twenty-ui/theme'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; - -const StyledCard = styled(Card)` - background-color: ${themeCssVariables.background.secondary}; - border: 1px solid ${themeCssVariables.border.color.medium}; -`; - -const StyledTableRow = styled(TableRow)` - height: ${themeCssVariables.spacing[6]}; -`; - -const StyledTableCellLabel = styled(TableCell)<{ - align?: 'left' | 'center' | 'right'; -}>` - color: ${themeCssVariables.font.color.tertiary}; - height: ${themeCssVariables.spacing[6]}; - display: flex; - gap: ${themeCssVariables.spacing[2]}; - justify-content: ${({ align }) => - align === 'right' - ? 'flex-end' - : align === 'center' - ? 'center' - : 'flex-start'}; -`; - -const StyledTableCellValue = styled(TableCell)<{ - align?: 'left' | 'center' | 'right'; - clickable?: boolean; -}>` - color: ${themeCssVariables.font.color.primary}; - cursor: ${({ clickable }) => (clickable ? 'pointer' : 'default')}; - height: ${themeCssVariables.spacing[6]}; - justify-content: ${({ align }) => - align === 'left' - ? 'flex-start' - : align === 'center' - ? 'center' - : 'flex-end'}; -`; +import { ICON_SIZES, themeCssVariables } from 'twenty-ui/theme-constants'; type TableItem = { Icon?: IconComponent; @@ -73,32 +31,42 @@ export const SettingsAdminTableCard = ({ valueAlign = 'left', className, }: SettingsAdminTableCardProps) => { - const { theme } = useContext(ThemeContext); - return ( - + {items.map((item, index) => ( - - - {item.Icon && } + + {item.Icon && } {item.label} - - + {item.value} - - + + ))}
-
+ ); }; diff --git a/packages/twenty-front/src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable.tsx b/packages/twenty-front/src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable.tsx index ac890916e5..dec51d123e 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable.tsx @@ -61,42 +61,10 @@ const StyledPaginationContainer = styled.div` padding: ${themeCssVariables.spacing[2]}; `; -const StyledTableCell = styled(TableCell)` - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -`; - -const StyledExpandableTableRow = styled(TableRow)<{ isExpanded: boolean }>` - cursor: pointer; - background-color: ${({ isExpanded }) => - isExpanded - ? themeCssVariables.background.transparent.light - : 'transparent'}; - - &:hover { - background-color: ${themeCssVariables.background.transparent.light}; - } -`; - const StyledJobRowWrapper = styled.div` display: contents; `; -const StyledCheckboxCell = styled(TableCell)` - justify-content: center; - padding: 0; - padding-left: ${themeCssVariables.spacing[1]}; -`; - -const StyledHeaderCheckboxCell = styled(TableHeader)` - align-items: center; - display: flex; - justify-content: center; - padding-right: ${themeCssVariables.spacing[1]}; -`; - const StyledButtonGroup = styled.div` display: flex; gap: ${themeCssVariables.spacing[2]}; @@ -299,7 +267,10 @@ export const SettingsAdminQueueJobsTable = ({ <> - + {jobs.length > 0 && ( )} - + {t`Job Name`} {t`State`} {t`Timestamp`} @@ -320,22 +291,34 @@ export const SettingsAdminQueueJobsTable = ({ return ( - handleRowClick(job.id)} isExpanded={isExpanded} + cursor="pointer" + hoverBackgroundColor={ + themeCssVariables.background.transparent.light + } > - { e.stopPropagation(); handleToggleJob(e, job.id); }} > - - + + {job.name} - + handleDeleteOne(job.id)} /> - + { - const { theme } = useContext(ThemeContext); const { labelIdentifierFieldMetadataItem } = useLabelIdentifierFieldMetadataItem({ objectNameSingular: objectNameSingular, @@ -114,10 +115,7 @@ export const SettingsDataModelFieldPreview = ({ {!!withFieldLabel && ( - + {fieldMetadataItem.label}: )} diff --git a/packages/twenty-front/src/modules/ui/layout/table/components/TableCell.tsx b/packages/twenty-front/src/modules/ui/layout/table/components/TableCell.tsx index 17391ce758..b48dee0bb4 100644 --- a/packages/twenty-front/src/modules/ui/layout/table/components/TableCell.tsx +++ b/packages/twenty-front/src/modules/ui/layout/table/components/TableCell.tsx @@ -4,21 +4,37 @@ import { themeCssVariables } from 'twenty-ui/theme-constants'; type TableCellProps = { align?: 'left' | 'center' | 'right'; color?: string; + gap?: string; + height?: string; + maxWidth?: string; + minWidth?: string; + overflow?: string; + padding?: string; + textOverflow?: string; + whiteSpace?: string; + clickable?: boolean; }; const StyledTableCell = styled.div` align-items: center; color: ${({ color }) => color || themeCssVariables.font.color.secondary}; + cursor: ${({ clickable }) => (clickable === true ? 'pointer' : 'default')}; display: flex; - height: ${themeCssVariables.spacing[8]}; + gap: ${({ gap }) => gap ?? 'normal'}; + height: ${({ height }) => height ?? themeCssVariables.spacing[8]}; justify-content: ${({ align }) => align === 'right' ? 'flex-end' : align === 'center' ? 'center' : 'flex-start'}; - padding: 0 ${themeCssVariables.spacing[2]}; + max-width: ${({ maxWidth }) => maxWidth ?? 'none'}; + min-width: ${({ minWidth }) => minWidth ?? 'auto'}; + overflow: ${({ overflow }) => overflow ?? 'visible'}; + padding: ${({ padding }) => padding ?? `0 ${themeCssVariables.spacing[2]}`}; text-align: ${({ align }) => align ?? 'left'}; + text-overflow: ${({ textOverflow }) => textOverflow ?? 'clip'}; + white-space: ${({ whiteSpace }) => whiteSpace ?? 'normal'}; `; export { StyledTableCell as TableCell }; diff --git a/packages/twenty-front/src/modules/ui/layout/table/components/TableHeader.tsx b/packages/twenty-front/src/modules/ui/layout/table/components/TableHeader.tsx index 7d9f130179..bd09ba93de 100644 --- a/packages/twenty-front/src/modules/ui/layout/table/components/TableHeader.tsx +++ b/packages/twenty-front/src/modules/ui/layout/table/components/TableHeader.tsx @@ -4,6 +4,7 @@ import { themeCssVariables } from 'twenty-ui/theme-constants'; const StyledTableHeader = styled.div<{ align?: 'left' | 'center' | 'right'; onClick?: () => void; + padding?: string; }>` gap: ${themeCssVariables.spacing[1]}; align-items: center; @@ -18,7 +19,7 @@ const StyledTableHeader = styled.div<{ : align === 'center' ? 'center' : 'flex-start'}; - padding: 0 ${themeCssVariables.spacing[2]}; + padding: ${({ padding }) => padding ?? `0 ${themeCssVariables.spacing[2]}`}; text-align: ${({ align }) => align ?? 'left'}; cursor: ${({ onClick }) => (onClick ? 'pointer' : 'default')}; `; diff --git a/packages/twenty-front/src/modules/ui/layout/table/components/TableRow.tsx b/packages/twenty-front/src/modules/ui/layout/table/components/TableRow.tsx index 813e7085db..6f9fa259c7 100644 --- a/packages/twenty-front/src/modules/ui/layout/table/components/TableRow.tsx +++ b/packages/twenty-front/src/modules/ui/layout/table/components/TableRow.tsx @@ -4,15 +4,24 @@ import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants'; const StyledTableRow = styled.div<{ isSelected?: boolean; + isExpanded?: boolean; onClick?: () => void; to?: string; gridAutoColumns?: string; gridTemplateColumns?: string; mobileGridAutoColumns?: string; + height?: string; + cursor?: string; + hoverBackgroundColor?: string; }>` - background-color: ${({ isSelected }) => - isSelected ? themeCssVariables.accent.quaternary : 'transparent'}; + background-color: ${({ isSelected, isExpanded }) => + isSelected + ? themeCssVariables.accent.quaternary + : isExpanded === true + ? themeCssVariables.background.transparent.light + : 'transparent'}; border-radius: ${themeCssVariables.border.radius.sm}; + cursor: ${({ cursor }) => cursor ?? 'default'}; display: grid; grid-auto-columns: ${({ gridAutoColumns }) => gridAutoColumns ?? '1fr'}; grid-template-columns: ${({ gridTemplateColumns }) => @@ -23,6 +32,7 @@ const StyledTableRow = styled.div<{ mobileGridAutoColumns ?? gridAutoColumns ?? '1fr'}; } + height: ${({ height }) => height ?? 'auto'}; grid-auto-flow: column; transition: background-color calc(${themeCssVariables.animation.duration.normal} * 1s); @@ -30,11 +40,13 @@ const StyledTableRow = styled.div<{ text-decoration: none; &:hover { - background-color: ${({ onClick, to }) => - onClick || to + background-color: ${({ onClick, to, hoverBackgroundColor }) => + hoverBackgroundColor ?? + (onClick || to ? themeCssVariables.background.transparent.light - : 'transparent'}; - cursor: ${({ onClick, to }) => (onClick || to ? 'pointer' : 'default')}; + : 'transparent')}; + cursor: ${({ onClick, to, cursor }) => + cursor ?? (onClick || to ? 'pointer' : 'default')}; } &[data-clickable='true'] { @@ -44,6 +56,7 @@ const StyledTableRow = styled.div<{ type TableRowProps = { isSelected?: boolean; + isExpanded?: boolean; isClickable?: boolean; onClick?: () => void; to?: string; @@ -52,10 +65,14 @@ type TableRowProps = { gridAutoColumns?: string; gridTemplateColumns?: string; mobileGridAutoColumns?: string; + height?: string; + cursor?: string; + hoverBackgroundColor?: string; }; export const TableRow = ({ isSelected, + isExpanded, isClickable, onClick, to, @@ -65,9 +82,13 @@ export const TableRow = ({ gridAutoColumns, gridTemplateColumns, mobileGridAutoColumns, + height, + cursor, + hoverBackgroundColor, }: React.PropsWithChildren) => ( diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepJsonContainer.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepJsonContainer.tsx index 91069ae443..551bf916fd 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepJsonContainer.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepJsonContainer.tsx @@ -1,11 +1,18 @@ import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; -import { styled } from '@linaria/react'; -const StyledWorkflowRunStepJsonContainer = styled(WorkflowStepBody)` - grid-template-rows: max-content; - gap: 0; - display: grid; - overflow: auto; -`; +const WorkflowRunStepJsonContainerInner = ({ + children, +}: { + children: React.ReactNode; +}) => ( + + {children} + +); -export { StyledWorkflowRunStepJsonContainer as WorkflowRunStepJsonContainer }; +export { WorkflowRunStepJsonContainerInner as WorkflowRunStepJsonContainer }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepBody.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepBody.tsx index 04093415b5..77dd58f878 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepBody.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepBody.tsx @@ -4,27 +4,54 @@ import { type AppErrorDisplayProps } from '@/error-handler/types/AppErrorDisplay import { styled } from '@linaria/react'; import { themeCssVariables } from 'twenty-ui/theme-constants'; -const StyledWorkflowStepBody = styled.div` +const StyledWorkflowStepBody = styled.div<{ + rowGap?: string; + display?: string; + overflow?: string; + paddingBlock?: string; + paddingInline?: string; + gridTemplateRows?: string; +}>` background: ${themeCssVariables.background.primary}; - display: flex; + display: ${({ display }) => display ?? 'flex'}; flex: 1 1 auto; flex-direction: column; height: 100%; - overflow-y: scroll; - padding-block: ${themeCssVariables.spacing[4]}; - padding-inline: ${themeCssVariables.spacing[3]}; - row-gap: ${themeCssVariables.spacing[4]}; + overflow: ${({ overflow }) => overflow ?? 'hidden scroll'}; + padding-block: ${({ paddingBlock }) => + paddingBlock ?? themeCssVariables.spacing[4]}; + padding-inline: ${({ paddingInline }) => + paddingInline ?? themeCssVariables.spacing[3]}; + row-gap: ${({ rowGap }) => rowGap ?? themeCssVariables.spacing[4]}; + grid-template-rows: ${({ gridTemplateRows }) => gridTemplateRows ?? 'none'}; `; export const WorkflowStepBody = ({ children, - className, + rowGap, + display, + overflow, + paddingBlock, + paddingInline, + gridTemplateRows, }: { children: React.ReactNode; - className?: string; + rowGap?: string; + display?: string; + overflow?: string; + paddingBlock?: string; + paddingInline?: string; + gridTemplateRows?: string; }) => { return ( - + {currentTabId === WORKFLOW_AI_AGENT_TABS.PERMISSIONS ? ( - + - + ) : ( - + {t`Conditions`} {isDefined(rootStepFilterGroup) ? ( @@ -119,7 +115,7 @@ export const WorkflowEditActionFilterBody = ({ ) : ( )} - + ); }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx index 20be88f7d1..8645fd3eac 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx @@ -18,7 +18,7 @@ import { styled } from '@linaria/react'; import { type OnDragEndResponder } from '@hello-pangea/dnd'; import { useLingui } from '@lingui/react/macro'; import { isNonEmptyString } from '@sniptt/guards'; -import { useContext, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { @@ -32,8 +32,7 @@ import { import { LightIconButton } from 'twenty-ui/input'; import { useDebouncedCallback } from 'use-debounce'; import { v4 } from 'uuid'; -import { ThemeContext } from 'twenty-ui/theme'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { ICON_SIZES, themeCssVariables } from 'twenty-ui/theme-constants'; export type WorkflowEditActionFormBuilderProps = { triggerType: WorkflowTriggerType | undefined; @@ -50,12 +49,6 @@ export type WorkflowEditActionFormBuilderProps = { type FormData = WorkflowFormActionField[]; -const StyledWorkflowStepBody = styled(WorkflowStepBody)` - display: block; - padding-left: ${themeCssVariables.spacing[2]}; - padding-right: ${themeCssVariables.spacing[2]}; -`; - const StyledFormFieldContainer = styled.div` align-items: flex-end; column-gap: ${themeCssVariables.spacing[1]}; @@ -74,17 +67,21 @@ const StyledDraggingIndicator = styled.div` background-color: ${themeCssVariables.background.transparent.light}; `; -const StyledLightGripIconButton = styled(LightIconButton)` +const StyledGripButtonContainer = styled.div` + align-items: flex-end; + display: flex; grid-area: grip; margin-bottom: ${themeCssVariables.spacing[1]}; `; -const StyledLightTrashIconButton = styled(LightIconButton)` +const StyledTrashButtonContainer = styled.div` + align-items: flex-end; + display: flex; grid-area: delete; margin-bottom: ${themeCssVariables.spacing[1]}; `; -const StyledFormFieldInputContainer = styled(FormFieldInputContainer)` +const StyledFormFieldInputContainerWrapper = styled.div` grid-area: input; `; @@ -115,7 +112,7 @@ const StyledFieldContainer = styled.div<{ } `; -const StyledPlaceholder = styled(FormFieldPlaceholder)` +const StyledPlaceholderContainer = styled.div` width: 100%; `; @@ -151,7 +148,6 @@ export const WorkflowEditActionFormBuilder = ({ action, actionOptions, }: WorkflowEditActionFormBuilderProps) => { - const { theme } = useContext(ThemeContext); const { t } = useLingui(); const [formData, setFormData] = useState(action.settings.input); @@ -233,7 +229,10 @@ export const WorkflowEditActionFormBuilder = ({ return ( <> - + {triggerType && triggerType !== 'MANUAL' && isCalloutVisible && ( } {showButtons && ( - + + + )} - + {field.label || ''} @@ -316,44 +317,51 @@ export const WorkflowEditActionFormBuilder = ({ - - {isDefined(field.placeholder) && - isNonEmptyString(field.placeholder) - ? field.placeholder - : getDefaultFormFieldSettings(field.type) - .placeholder} - + + + {isDefined(field.placeholder) && + isNonEmptyString(field.placeholder) + ? field.placeholder + : getDefaultFormFieldSettings(field.type) + .placeholder} + + {field.type === 'RECORD' && ( )} - + {showButtons && ( - { - const updatedFormData = formData.filter( - (currentField) => currentField.id !== field.id, - ); + + { + const updatedFormData = formData.filter( + (currentField) => + currentField.id !== field.id, + ); - setFormData(updatedFormData); + setFormData(updatedFormData); - actionOptions.onActionUpdate({ - ...action, - settings: { - ...action.settings, - input: updatedFormData, - }, - }); - }} - /> + actionOptions.onActionUpdate({ + ...action, + settings: { + ...action.settings, + input: updatedFormData, + }, + }); + }} + /> + )} {isFieldSelected(field.id) && ( @@ -410,7 +418,7 @@ export const WorkflowEditActionFormBuilder = ({ > - + {t`Add Field`} @@ -419,7 +427,7 @@ export const WorkflowEditActionFormBuilder = ({ )} - + {!actionOptions.readonly && } ); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/if-else-action/components/WorkflowEditActionIfElseBody.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/if-else-action/components/WorkflowEditActionIfElseBody.tsx index 5758c39faf..6402a0a825 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/if-else-action/components/WorkflowEditActionIfElseBody.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/if-else-action/components/WorkflowEditActionIfElseBody.tsx @@ -38,10 +38,6 @@ const StyledContainer = styled.div` gap: ${themeCssVariables.spacing[4]}; `; -const StyledBodyContainer = styled(WorkflowStepBody)` - gap: ${themeCssVariables.spacing[2]}; -`; - type WorkflowEditActionIfElseBodyProps = { action: WorkflowIfElseAction; actionOptions: @@ -250,7 +246,7 @@ export const WorkflowEditActionIfElseBody = ({ }; return ( - + {t`Conditions`} {branches.map((branch, branchIndex) => { @@ -303,6 +299,6 @@ export const WorkflowEditActionIfElseBody = ({ ); })} - + ); }; diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx index d8d68593cc..cfe4a35d1e 100644 --- a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx @@ -32,10 +32,6 @@ const StyledSearchContainer = styled.div` padding-bottom: ${themeCssVariables.spacing[2]}; `; -const StyledSearchInput = styled(SettingsTextInput)` - width: 100%; -`; - export const SettingsAIModelsTab = () => { const { enqueueErrorSnackBar } = useSnackBar(); const [currentWorkspace, setCurrentWorkspace] = useAtomState( @@ -358,12 +354,13 @@ export const SettingsAIModelsTab = () => { /> - diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsSkillTableRow.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsSkillTableRow.tsx index c4629bd908..7df14e443d 100644 --- a/packages/twenty-front/src/pages/settings/ai/components/SettingsSkillTableRow.tsx +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsSkillTableRow.tsx @@ -5,7 +5,7 @@ import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; import { useIcons, OverflowingTextWithTooltip } from 'twenty-ui/display'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { ICON_SIZES, themeCssVariables } from 'twenty-ui/theme-constants'; import { type Skill } from '~/generated-metadata/graphql'; @@ -15,28 +15,12 @@ export type SettingsSkillTableRowProps = { link?: string; }; -export const StyledSkillTableRow = styled(TableRow)` - grid-template-columns: 1fr 120px 36px; -`; - -const StyledNameTableCell = styled(TableCell)` - color: ${themeCssVariables.font.color.primary}; - gap: ${themeCssVariables.spacing[2]}; - min-width: 0; - overflow: hidden; -`; - const StyledIconContainer = styled.div` align-items: center; display: flex; flex-shrink: 0; `; -const StyledActionTableCell = styled(TableCell)` - justify-content: flex-end; - padding-right: ${themeCssVariables.spacing[2]}; -`; - export const SettingsSkillTableRow = ({ skill, action, @@ -46,21 +30,27 @@ export const SettingsSkillTableRow = ({ const Icon = getIcon(skill.icon ?? 'IconSparkles'); return ( - - + - + - + - {action} - + {action} + ); }; diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsSkillsTable.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsSkillsTable.tsx index 6138864930..5cd243967a 100644 --- a/packages/twenty-front/src/pages/settings/ai/components/SettingsSkillsTable.tsx +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsSkillsTable.tsx @@ -1,6 +1,6 @@ import { styled } from '@linaria/react'; import { useLingui } from '@lingui/react/macro'; -import { useContext, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import Skeleton from 'react-loading-skeleton'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; @@ -11,6 +11,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader'; 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 { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; @@ -23,8 +24,11 @@ import { } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { MenuItemToggle, UndecoratedLink } from 'twenty-ui/navigation'; -import { ThemeContext } from 'twenty-ui/theme'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { + ICON_SIZES, + ICON_STROKES, + themeCssVariables, +} from 'twenty-ui/theme-constants'; import { useActivateSkillMutation, @@ -34,10 +38,7 @@ import { import { SettingsSkillInactiveMenuDropDown } from '~/pages/settings/ai/components/SettingsSkillInactiveMenuDropDown'; import { SETTINGS_SKILL_TABLE_METADATA } from '~/pages/settings/ai/constants/SettingsSkillTableMetadata'; import { normalizeSearchText } from '~/utils/normalizeSearchText'; -import { - SettingsSkillTableRow, - StyledSkillTableRow, -} from './SettingsSkillTableRow'; +import { SettingsSkillTableRow } from './SettingsSkillTableRow'; const StyledSearchAndFilterContainer = styled.div` display: flex; @@ -46,12 +47,11 @@ const StyledSearchAndFilterContainer = styled.div` padding-bottom: ${themeCssVariables.spacing[2]}; `; -const StyledSearchInput = styled(SettingsTextInput)` +const StyledSearchInputWrapper = styled.div` flex: 1; - width: 100%; `; -const StyledTableHeaderRow = styled(StyledSkillTableRow)` +const StyledTableHeaderRowContainer = styled.div` margin-bottom: ${themeCssVariables.spacing[2]}; `; @@ -68,7 +68,6 @@ export const SettingsSkillsTable = () => { const [deleteSkill] = useDeleteSkillMutation(); const { t } = useLingui(); - const { theme } = useContext(ThemeContext); const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar(); const [searchTerm, setSearchTerm] = useState(''); const [showDeactivated, setShowDeactivated] = useState(true); @@ -123,13 +122,16 @@ export const SettingsSkillsTable = () => { return ( <> - + + + {
- - {SETTINGS_SKILL_TABLE_METADATA.fields.map( - (settingsSkillTableMetadataField) => ( - - ), - )} - - + + + {SETTINGS_SKILL_TABLE_METADATA.fields.map( + (settingsSkillTableMetadataField) => ( + + ), + )} + + + {showSkeleton ? Array.from({ length: 3 }).map((_, index) => ( @@ -186,8 +190,8 @@ export const SettingsSkillsTable = () => { action={ skill.isActive ? ( ) : ( { - const slowThresholdMs = options?.slowThresholdMs ?? 50; + const devSlowThresholdMs = options?.devSlowThresholdMs ?? 200; const topSlowFilesCount = options?.topSlowFilesCount ?? 10; - const progressIntervalFiles = options?.progressIntervalFiles ?? 50; + const warmupThresholdMs = options?.warmupThresholdMs ?? 500; let totalMs = 0; let fileCount = 0; let skippedCount = 0; - const slowFiles: { id: string; ms: number }[] = []; + let isDevMode = false; + let warmupId = `${process.cwd()}/src/__wyw_warmup__.tsx`; + const allTransforms: { id: string; ms: number }[] = []; const originalTransform = wywPlugin.transform; - console.log( - `[linaria/wyw] CSS pre-build profiling enabled (slow threshold: ${slowThresholdMs}ms)`, - ); - return { ...wywPlugin, enforce: 'pre' as const, + configResolved(config) { + isDevMode = config.command === 'serve'; + warmupId = `${config.root}/src/__wyw_warmup__.tsx`; + if (typeof wywPlugin.configResolved === 'function') { + (wywPlugin.configResolved as Function).call(this, config); + } + }, + async buildStart() { + console.log(`[linaria/wyw] Starting CSS pre-build`); + + const warmupStart = performance.now(); + try { + const warmupResult = (originalTransform as Function).call( + this, + WARMUP_CODE, + warmupId, + ); + if ( + warmupResult !== null && + typeof warmupResult === 'object' && + 'then' in warmupResult + ) { + await warmupResult; + } + } catch { + // Expected: fake file path causes module resolution errors, but + // Babel's JIT compilation is already triggered — that's all we need. + } + + const warmupMs = performance.now() - warmupStart; + const warmupWarning = warmupMs > warmupThresholdMs ? ' ⚠️ slow' : ''; + console.log( + `[linaria/wyw] Pre-warm: ${warmupMs.toFixed(0)}ms${warmupWarning}`, + ); + }, transform(code: string, id: string, ...rest: unknown[]) { if (!LINARIA_IMPORT_RE.test(code)) { skippedCount++; @@ -47,14 +89,11 @@ export const createWywProfilingPlugin = ( const handleTiming = (elapsed: number) => { totalMs += elapsed; fileCount++; + allTransforms.push({ id, ms: elapsed }); - if (elapsed > slowThresholdMs) { - slowFiles.push({ id, ms: elapsed }); - } - - if (fileCount % progressIntervalFiles === 0) { + if (isDevMode && elapsed > devSlowThresholdMs) { console.log( - `[linaria/wyw] CSS pre-build progress: ${fileCount} transformed, ${skippedCount} skipped, ${totalMs.toFixed(0)}ms total`, + `[linaria/wyw] slow: ${id.replace(process.cwd(), '')} ${elapsed.toFixed(0)}ms`, ); } }; @@ -69,18 +108,21 @@ export const createWywProfilingPlugin = ( handleTiming(performance.now() - start); return result; }, - buildEnd() { - console.log('\n[linaria/wyw] ===== CSS PRE-BUILD TIMING SUMMARY ====='); + closeBundle: () => { + const avg = fileCount > 0 ? totalMs / fileCount : 0; + const dynamicThreshold = Math.round(10 * avg); + const slowFiles = allTransforms.filter((f) => f.ms > dynamicThreshold); + + console.log('\n[linaria/wyw] ===== CSS PRE-BUILD SUMMARY ====='); console.log(`[linaria/wyw] Files transformed: ${fileCount}`); console.log(`[linaria/wyw] Files skipped (no @linaria): ${skippedCount}`); console.log(`[linaria/wyw] Transform time: ${totalMs.toFixed(0)}ms`); console.log( - `[linaria/wyw] Avg per transformed file: ${fileCount > 0 ? (totalMs / fileCount).toFixed(1) : 0}ms`, + `[linaria/wyw] Avg per transformed file: ${avg.toFixed(1)}ms`, ); - if (slowFiles.length > 0) { console.log( - `[linaria/wyw] Slow CSS pre-build files (>${slowThresholdMs}ms):`, + `[linaria/wyw] Slow files (>10x avg = ${dynamicThreshold}ms):`, ); slowFiles .sort((a, b) => b.ms - a.ms) @@ -91,7 +133,6 @@ export const createWywProfilingPlugin = ( ), ); } - console.log('[linaria/wyw] ==========================================\n'); }, }; diff --git a/packages/twenty-ui/scripts/generateThemeConstants.ts b/packages/twenty-ui/scripts/generateThemeConstants.ts index 3e34080d45..fce2256e3b 100644 --- a/packages/twenty-ui/scripts/generateThemeConstants.ts +++ b/packages/twenty-ui/scripts/generateThemeConstants.ts @@ -20,6 +20,7 @@ const { MOBILE_VIEWPORT, THEME_LIGHT, THEME_DARK, + ICON, prepareThemeForRootCssVariableInjection, } = require('../dist/theme.cjs'); @@ -106,6 +107,11 @@ writeFileSync( // must be a static number rather than a var(--...) reference. export const MOBILE_VIEWPORT = ${MOBILE_VIEWPORT}; +// Numeric icon size/stroke constants for components that require pixel values +// (e.g. icon size props) rather than CSS variable strings. +export const ICON_SIZES = ${serializeObject(ICON.size)} as const; +export const ICON_STROKES = ${serializeObject(ICON.stroke)} as const; + export const THEME_LIGHT_CSS_VARIABLE_ENTRIES: [string, string][] = ${serializeTupleArray(lightEntries)}; `, 'utf-8', diff --git a/packages/twenty-ui/src/accessibility/components/VisibilityHidden.tsx b/packages/twenty-ui/src/accessibility/components/VisibilityHidden.tsx index 0aae6f4a3f..50b576f86e 100644 --- a/packages/twenty-ui/src/accessibility/components/VisibilityHidden.tsx +++ b/packages/twenty-ui/src/accessibility/components/VisibilityHidden.tsx @@ -1,8 +1,15 @@ import { styled } from '@linaria/react'; -import { VISIBILITY_HIDDEN } from '@ui/accessibility/utils/visibility-hidden'; const StyledSpan = styled.span` - ${VISIBILITY_HIDDEN} + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; `; export const VisibilityHidden = ({ diff --git a/packages/twenty-ui/src/components/chip/LinkChip.tsx b/packages/twenty-ui/src/components/chip/LinkChip.tsx index e39b344619..3b9b232388 100644 --- a/packages/twenty-ui/src/components/chip/LinkChip.tsx +++ b/packages/twenty-ui/src/components/chip/LinkChip.tsx @@ -22,11 +22,14 @@ export type LinkChipProps = Omit< target?: '_blank' | '_self'; }; -const StyledLink = styled(Link)` +const StyledLinkContainer = styled.span` display: inline-flex; - text-decoration: none; min-width: 0; vertical-align: middle; + + & > a { + text-decoration: none; + } `; export const LinkChip = ({ @@ -55,32 +58,34 @@ export const LinkChip = ({ }); return ( - { - event.stopPropagation(); - onClickHandler(event); - }} - onMouseDown={onMouseDownHandler} - data-click-outside-id={LINK_CHIP_CLICK_OUTSIDE_ID} - target={target} - rel={target === '_blank' ? 'noopener noreferrer' : undefined} - > - - + + { + event.stopPropagation(); + onClickHandler(event); + }} + onMouseDown={onMouseDownHandler} + data-click-outside-id={LINK_CHIP_CLICK_OUTSIDE_ID} + target={target} + rel={target === '_blank' ? 'noopener noreferrer' : undefined} + > + + + ); }; diff --git a/packages/twenty-ui/src/display/info/components/Info.tsx b/packages/twenty-ui/src/display/info/components/Info.tsx index 60a7e43c61..f2a28215ea 100644 --- a/packages/twenty-ui/src/display/info/components/Info.tsx +++ b/packages/twenty-ui/src/display/info/components/Info.tsx @@ -1,10 +1,9 @@ import { styled } from '@linaria/react'; import { IconInfoCircle } from '@ui/display/icon/components/TablerIcons'; -import { ThemeContext } from '@ui/theme'; -import { themeCssVariables } from '@ui/theme-constants'; +import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants'; import { Button } from '@ui/input/button/components/Button/Button'; -import React, { useContext } from 'react'; +import React from 'react'; import { Link } from 'react-router-dom'; export type InfoAccent = 'blue' | 'danger'; @@ -20,10 +19,10 @@ const StyledTextContainer = styled.div` align-items: center; display: flex; gap: ${themeCssVariables.spacing[2]}; -`; -const StyledIconInfoCircle = styled(IconInfoCircle)` - flex-shrink: 0; + & > svg { + flex-shrink: 0; + } `; const StyledInfo = styled.div>` @@ -57,8 +56,10 @@ const StyledInfo = styled.div>` }}; `; -const StyledLink = styled(Link)` - text-decoration: none; +const StyledLinkContainer = styled.span` + & > a { + text-decoration: none; + } `; export const Info = ({ @@ -68,22 +69,23 @@ export const Info = ({ onClick, to, }: InfoProps) => { - const { theme } = useContext(ThemeContext); return ( - + {text} {buttonTitle && to && ( - -