[DevXP] Improve Linaria pre-build speed (#18382)
## 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
This commit is contained in:
@@ -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 = ({
|
||||
</StyledEmptySubTitle>
|
||||
</StyledEmptyTextContainer>
|
||||
<StyledButton onClick={resetErrorBoundary}>
|
||||
<StyledIcon size={16} />
|
||||
<StyledIconContainer>
|
||||
<IconReload size={ICON_SIZES.md} />
|
||||
</StyledIconContainer>
|
||||
{t`Reload`}
|
||||
</StyledButton>
|
||||
</StyledEmptyContainer>
|
||||
|
||||
+4
-2
@@ -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 && (
|
||||
<StyledDropdownMenuSeparator />
|
||||
<StyledSeparatorContainer>
|
||||
<DropdownMenuSeparator />
|
||||
</StyledSeparatorContainer>
|
||||
)}
|
||||
{filteredFolders.length > 0
|
||||
? filteredFolders.map((folder) => (
|
||||
|
||||
+2
-7
@@ -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 (
|
||||
<StyledRecordCardBodyContainer>
|
||||
<RecordCardBodyContainer padding={themeCssVariables.spacing[1]}>
|
||||
{visibleRecordFieldsExceptLabelIdentifier.map((recordField, index) => {
|
||||
const correspondingFieldDefinition =
|
||||
fieldDefinitionByFieldMetadataItemId[recordField.fieldMetadataItemId];
|
||||
@@ -120,6 +115,6 @@ export const RecordCalendarCardBody = ({
|
||||
</StopPropagationContainer>
|
||||
);
|
||||
})}
|
||||
</StyledRecordCardBodyContainer>
|
||||
</RecordCardBodyContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+6
-7
@@ -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 (
|
||||
<StyledRecordCardHeaderContainer isCompact={isCompactModeActive}>
|
||||
<RecordCardHeaderContainer
|
||||
isCompact={isCompactModeActive}
|
||||
padding={themeCssVariables.spacing[1]}
|
||||
>
|
||||
<StyledRecordChipContainer>
|
||||
<StopPropagationContainer>
|
||||
<RecordChip
|
||||
@@ -93,6 +92,6 @@ export const RecordCalendarCardHeader = ({
|
||||
/>
|
||||
</StopPropagationContainer>
|
||||
</StyledCheckboxContainer>
|
||||
</StyledRecordCardHeaderContainer>
|
||||
</RecordCardHeaderContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -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;
|
||||
|
||||
+4
-5
@@ -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 {
|
||||
|
||||
+13
-7
@@ -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 (
|
||||
<StyledRecordChip
|
||||
record={selectedRecord}
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
<StyledRecordChipContainer>
|
||||
<RecordChip
|
||||
record={selectedRecord}
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
</StyledRecordChipContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return <StyledPlaceholder>{t`Select`}</StyledPlaceholder>;
|
||||
return (
|
||||
<StyledPlaceholderContainer>
|
||||
<FormFieldPlaceholder>{t`Select`}</FormFieldPlaceholder>
|
||||
</StyledPlaceholderContainer>
|
||||
);
|
||||
};
|
||||
|
||||
-8
@@ -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;
|
||||
|
||||
+2
-8
@@ -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 (
|
||||
<StyledSelectableItem
|
||||
<SelectableListItem
|
||||
itemId={searchRecord.recordId}
|
||||
key={searchRecord.recordId}
|
||||
onEnter={() => handleSelectChange(!isRecordSelectedWithObjectItem)}
|
||||
@@ -103,6 +97,6 @@ export const MultipleRecordPickerMenuItemContent = ({
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</StyledSelectableItem>
|
||||
</SelectableListItem>
|
||||
);
|
||||
};
|
||||
|
||||
+2
-8
@@ -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 (
|
||||
<StyledSelectableItem
|
||||
<SelectableListItem
|
||||
itemId={morphItem.recordId}
|
||||
key={morphItem.recordId}
|
||||
onEnter={() => {
|
||||
@@ -94,6 +88,6 @@ export const SingleRecordPickerMenuItem = ({
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</StyledSelectableItem>
|
||||
</SelectableListItem>
|
||||
);
|
||||
};
|
||||
|
||||
+21
@@ -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
|
||||
);
|
||||
}
|
||||
`;
|
||||
+3
-39
@@ -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;
|
||||
|
||||
|
||||
+20
@@ -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%;
|
||||
}
|
||||
`;
|
||||
+6
-11
@@ -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,
|
||||
}}
|
||||
|
||||
+22
-54
@@ -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 (
|
||||
<StyledCard rounded={rounded} className={className}>
|
||||
<Card
|
||||
rounded={rounded}
|
||||
className={className}
|
||||
backgroundColor={themeCssVariables.background.secondary}
|
||||
>
|
||||
<Table>
|
||||
<TableBody>
|
||||
{items.map((item, index) => (
|
||||
<StyledTableRow
|
||||
<TableRow
|
||||
key={index + item.label}
|
||||
gridAutoColumns={gridAutoColumns}
|
||||
height={themeCssVariables.spacing[6]}
|
||||
>
|
||||
<StyledTableCellLabel align={labelAlign}>
|
||||
{item.Icon && <item.Icon size={theme.icon.size.md} />}
|
||||
<TableCell
|
||||
align={labelAlign}
|
||||
color={themeCssVariables.font.color.tertiary}
|
||||
height={themeCssVariables.spacing[6]}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
>
|
||||
{item.Icon && <item.Icon size={ICON_SIZES.md} />}
|
||||
<span>{item.label}</span>
|
||||
</StyledTableCellLabel>
|
||||
<StyledTableCellValue
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align={valueAlign}
|
||||
color={themeCssVariables.font.color.primary}
|
||||
height={themeCssVariables.spacing[6]}
|
||||
onClick={item.onClick}
|
||||
clickable={isDefined(item.onClick)}
|
||||
>
|
||||
{item.value}
|
||||
</StyledTableCellValue>
|
||||
</StyledTableRow>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</StyledCard>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
+23
-40
@@ -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 = ({
|
||||
<>
|
||||
<Table>
|
||||
<TableRow gridAutoColumns="32px 2fr 1fr 2fr 32px">
|
||||
<StyledHeaderCheckboxCell>
|
||||
<TableHeader
|
||||
align="center"
|
||||
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
>
|
||||
{jobs.length > 0 && (
|
||||
<Checkbox
|
||||
checked={allJobsSelected}
|
||||
@@ -307,7 +278,7 @@ export const SettingsAdminQueueJobsTable = ({
|
||||
onChange={handleToggleAll}
|
||||
/>
|
||||
)}
|
||||
</StyledHeaderCheckboxCell>
|
||||
</TableHeader>
|
||||
<TableHeader>{t`Job Name`}</TableHeader>
|
||||
<TableHeader>{t`State`}</TableHeader>
|
||||
<TableHeader align="right">{t`Timestamp`}</TableHeader>
|
||||
@@ -320,22 +291,34 @@ export const SettingsAdminQueueJobsTable = ({
|
||||
|
||||
return (
|
||||
<StyledJobRowWrapper key={job.id}>
|
||||
<StyledExpandableTableRow
|
||||
<TableRow
|
||||
gridAutoColumns="32px 2fr 1fr 2fr 32px"
|
||||
onClick={() => handleRowClick(job.id)}
|
||||
isExpanded={isExpanded}
|
||||
cursor="pointer"
|
||||
hoverBackgroundColor={
|
||||
themeCssVariables.background.transparent.light
|
||||
}
|
||||
>
|
||||
<StyledCheckboxCell
|
||||
<TableCell
|
||||
align="center"
|
||||
padding={`0 0 0 ${themeCssVariables.spacing[1]}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleToggleJob(e, job.id);
|
||||
}}
|
||||
>
|
||||
<Checkbox checked={isSelected} />
|
||||
</StyledCheckboxCell>
|
||||
<StyledTableCell title={job.name}>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
title={job.name}
|
||||
maxWidth="200px"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
whiteSpace="nowrap"
|
||||
>
|
||||
{job.name}
|
||||
</StyledTableCell>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SettingsAdminJobStateBadge
|
||||
state={job.state}
|
||||
@@ -364,7 +347,7 @@ export const SettingsAdminQueueJobsTable = ({
|
||||
onDelete={() => handleDeleteOne(job.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
</StyledExpandableTableRow>
|
||||
</TableRow>
|
||||
<SettingsAdminJobDetailsExpandable
|
||||
job={job}
|
||||
isExpanded={isExpanded}
|
||||
|
||||
+6
-8
@@ -1,4 +1,3 @@
|
||||
import { useContext } from 'react';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
||||
@@ -13,8 +12,11 @@ import { SettingsDataModelSetFieldValueEffect } from '@/settings/data-model/fiel
|
||||
import { SettingsDataModelSetLabelIdentifierRecordEffect } from '@/settings/data-model/fields/preview/components/SettingsDataModelSetLabelIdentifierRecordEffect';
|
||||
import { useFieldPreviewValue } from '@/settings/data-model/fields/preview/hooks/useFieldPreviewValue';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
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 { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
type SettingsDataModelFieldPreviewProps = {
|
||||
@@ -59,7 +61,6 @@ export const SettingsDataModelFieldPreview = ({
|
||||
shrink,
|
||||
withFieldLabel = true,
|
||||
}: SettingsDataModelFieldPreviewProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { labelIdentifierFieldMetadataItem } =
|
||||
useLabelIdentifierFieldMetadataItem({
|
||||
objectNameSingular: objectNameSingular,
|
||||
@@ -114,10 +115,7 @@ export const SettingsDataModelFieldPreview = ({
|
||||
<StyledFieldPreview shrink={shrink}>
|
||||
{!!withFieldLabel && (
|
||||
<StyledFieldLabel>
|
||||
<FieldIcon
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
<FieldIcon size={ICON_SIZES.md} stroke={ICON_STROKES.sm} />
|
||||
{fieldMetadataItem.label}:
|
||||
</StyledFieldLabel>
|
||||
)}
|
||||
|
||||
@@ -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<TableCellProps>`
|
||||
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 };
|
||||
|
||||
@@ -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')};
|
||||
`;
|
||||
|
||||
@@ -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<TableRowProps>) => (
|
||||
<StyledTableRow
|
||||
isSelected={isSelected}
|
||||
isExpanded={isExpanded}
|
||||
onClick={onClick}
|
||||
gridAutoColumns={gridAutoColumns}
|
||||
gridTemplateColumns={gridTemplateColumns}
|
||||
@@ -75,6 +96,9 @@ export const TableRow = ({
|
||||
style={style}
|
||||
data-clickable={isClickable}
|
||||
mobileGridAutoColumns={mobileGridAutoColumns}
|
||||
height={height}
|
||||
cursor={cursor}
|
||||
hoverBackgroundColor={hoverBackgroundColor}
|
||||
to={to}
|
||||
as={to ? Link : 'div'}
|
||||
>
|
||||
|
||||
+15
-8
@@ -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;
|
||||
}) => (
|
||||
<WorkflowStepBody
|
||||
display="grid"
|
||||
gridTemplateRows="max-content"
|
||||
rowGap="0"
|
||||
overflow="auto"
|
||||
>
|
||||
{children}
|
||||
</WorkflowStepBody>
|
||||
);
|
||||
|
||||
export { StyledWorkflowRunStepJsonContainer as WorkflowRunStepJsonContainer };
|
||||
export { WorkflowRunStepJsonContainerInner as WorkflowRunStepJsonContainer };
|
||||
|
||||
+36
-9
@@ -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 (
|
||||
<StyledWorkflowStepBody className={className}>
|
||||
<StyledWorkflowStepBody
|
||||
rowGap={rowGap}
|
||||
display={display}
|
||||
overflow={overflow}
|
||||
paddingBlock={paddingBlock}
|
||||
paddingInline={paddingInline}
|
||||
gridTemplateRows={gridTemplateRows}
|
||||
>
|
||||
<AppErrorBoundary
|
||||
resetOnLocationChange={true}
|
||||
FallbackComponent={({
|
||||
|
||||
+2
-7
@@ -55,11 +55,6 @@ const StyledTabList = styled(TabList)`
|
||||
padding-left: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledPermissionsStepBody = styled(WorkflowStepBody)`
|
||||
padding-block: 0;
|
||||
padding-inline: 0;
|
||||
`;
|
||||
|
||||
export const WorkflowEditActionAiAgent = ({
|
||||
action,
|
||||
actionOptions,
|
||||
@@ -281,14 +276,14 @@ export const WorkflowEditActionAiAgent = ({
|
||||
behaveAsLinks={false}
|
||||
/>
|
||||
{currentTabId === WORKFLOW_AI_AGENT_TABS.PERMISSIONS ? (
|
||||
<StyledPermissionsStepBody>
|
||||
<WorkflowStepBody paddingBlock="0" paddingInline="0">
|
||||
<WorkflowAiAgentPermissionsTab
|
||||
action={action}
|
||||
readonly={actionOptions.readonly === true}
|
||||
isAgentLoading={agentLoading}
|
||||
refetchAgent={refetchAgent}
|
||||
/>
|
||||
</StyledPermissionsStepBody>
|
||||
</WorkflowStepBody>
|
||||
) : (
|
||||
<WorkflowStepBody>
|
||||
<WorkflowAiAgentPromptTab
|
||||
|
||||
+2
-6
@@ -30,10 +30,6 @@ const StyledChildContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledFilterBodyContainer = styled(WorkflowStepBody)`
|
||||
gap: ${themeCssVariables.spacing[0]};
|
||||
`;
|
||||
|
||||
type WorkflowEditActionFilterBodyProps = {
|
||||
action: WorkflowFilterAction;
|
||||
actionOptions:
|
||||
@@ -84,7 +80,7 @@ export const WorkflowEditActionFilterBody = ({
|
||||
onFilterSettingsUpdate,
|
||||
}}
|
||||
>
|
||||
<StyledFilterBodyContainer>
|
||||
<WorkflowStepBody rowGap={themeCssVariables.spacing[0]}>
|
||||
<InputLabel>{t`Conditions`}</InputLabel>
|
||||
{isDefined(rootStepFilterGroup) ? (
|
||||
<StyledContainer>
|
||||
@@ -119,7 +115,7 @@ export const WorkflowEditActionFilterBody = ({
|
||||
) : (
|
||||
<WorkflowStepFilterAddRootStepFilterButton />
|
||||
)}
|
||||
</StyledFilterBodyContainer>
|
||||
</WorkflowStepBody>
|
||||
</WorkflowStepFilterContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
+57
-49
@@ -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<FormData>(action.settings.input);
|
||||
@@ -233,7 +229,10 @@ export const WorkflowEditActionFormBuilder = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledWorkflowStepBody>
|
||||
<WorkflowStepBody
|
||||
display="block"
|
||||
paddingInline={themeCssVariables.spacing[2]}
|
||||
>
|
||||
{triggerType && triggerType !== 'MANUAL' && isCalloutVisible && (
|
||||
<StyledCalloutContainer>
|
||||
<Callout
|
||||
@@ -296,13 +295,15 @@ export const WorkflowEditActionFormBuilder = ({
|
||||
{isDragging && <StyledDraggingIndicator />}
|
||||
|
||||
{showButtons && (
|
||||
<StyledLightGripIconButton
|
||||
Icon={IconGripVertical}
|
||||
aria-label={t`Reorder field`}
|
||||
/>
|
||||
<StyledGripButtonContainer>
|
||||
<LightIconButton
|
||||
Icon={IconGripVertical}
|
||||
aria-label={t`Reorder field`}
|
||||
/>
|
||||
</StyledGripButtonContainer>
|
||||
)}
|
||||
|
||||
<StyledFormFieldInputContainer>
|
||||
<StyledFormFieldInputContainerWrapper>
|
||||
<InputLabel>{field.label || ''}</InputLabel>
|
||||
|
||||
<FormFieldInputRowContainer>
|
||||
@@ -316,44 +317,51 @@ export const WorkflowEditActionFormBuilder = ({
|
||||
<StyledFieldContainer
|
||||
readonly={actionOptions.readonly}
|
||||
>
|
||||
<StyledPlaceholder>
|
||||
{isDefined(field.placeholder) &&
|
||||
isNonEmptyString(field.placeholder)
|
||||
? field.placeholder
|
||||
: getDefaultFormFieldSettings(field.type)
|
||||
.placeholder}
|
||||
</StyledPlaceholder>
|
||||
<StyledPlaceholderContainer>
|
||||
<FormFieldPlaceholder>
|
||||
{isDefined(field.placeholder) &&
|
||||
isNonEmptyString(field.placeholder)
|
||||
? field.placeholder
|
||||
: getDefaultFormFieldSettings(field.type)
|
||||
.placeholder}
|
||||
</FormFieldPlaceholder>
|
||||
</StyledPlaceholderContainer>
|
||||
{field.type === 'RECORD' && (
|
||||
<IconChevronDown
|
||||
size={theme.icon.size.md}
|
||||
color={theme.font.color.tertiary}
|
||||
size={ICON_SIZES.md}
|
||||
color={
|
||||
themeCssVariables.font.color.tertiary
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</StyledFieldContainer>
|
||||
</FormFieldInputInnerContainer>
|
||||
</FormFieldInputRowContainer>
|
||||
</StyledFormFieldInputContainer>
|
||||
</StyledFormFieldInputContainerWrapper>
|
||||
|
||||
{showButtons && (
|
||||
<StyledLightTrashIconButton
|
||||
Icon={IconTrash}
|
||||
aria-label={t`Delete field`}
|
||||
onClick={() => {
|
||||
const updatedFormData = formData.filter(
|
||||
(currentField) => currentField.id !== field.id,
|
||||
);
|
||||
<StyledTrashButtonContainer>
|
||||
<LightIconButton
|
||||
Icon={IconTrash}
|
||||
aria-label={t`Delete field`}
|
||||
onClick={() => {
|
||||
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,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</StyledTrashButtonContainer>
|
||||
)}
|
||||
|
||||
{isFieldSelected(field.id) && (
|
||||
@@ -410,7 +418,7 @@ export const WorkflowEditActionFormBuilder = ({
|
||||
>
|
||||
<StyledFieldContainer>
|
||||
<StyledAddFieldButtonContentContainer>
|
||||
<IconPlus size={theme.icon.size.sm} />
|
||||
<IconPlus size={ICON_SIZES.sm} />
|
||||
{t`Add Field`}
|
||||
</StyledAddFieldButtonContentContainer>
|
||||
</StyledFieldContainer>
|
||||
@@ -419,7 +427,7 @@ export const WorkflowEditActionFormBuilder = ({
|
||||
</FormFieldInputContainer>
|
||||
</StyledAddFieldButtonContainer>
|
||||
)}
|
||||
</StyledWorkflowStepBody>
|
||||
</WorkflowStepBody>
|
||||
{!actionOptions.readonly && <WorkflowStepFooter stepId={action.id} />}
|
||||
</>
|
||||
);
|
||||
|
||||
+2
-6
@@ -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 (
|
||||
<StyledBodyContainer>
|
||||
<WorkflowStepBody rowGap={themeCssVariables.spacing[2]}>
|
||||
<InputLabel>{t`Conditions`}</InputLabel>
|
||||
<StyledContainer>
|
||||
{branches.map((branch, branchIndex) => {
|
||||
@@ -303,6 +299,6 @@ export const WorkflowEditActionIfElseBody = ({
|
||||
);
|
||||
})}
|
||||
</StyledContainer>
|
||||
</StyledBodyContainer>
|
||||
</WorkflowStepBody>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 = () => {
|
||||
/>
|
||||
|
||||
<StyledSearchContainer>
|
||||
<StyledSearchInput
|
||||
<SettingsTextInput
|
||||
instanceId="model-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a model...`}
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledSearchContainer>
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<StyledSkillTableRow
|
||||
<TableRow
|
||||
key={skill.id}
|
||||
to={link}
|
||||
gridTemplateColumns="1fr 120px 36px"
|
||||
style={{ opacity: skill.isActive ? 1 : 0.5 }}
|
||||
>
|
||||
<StyledNameTableCell>
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.primary}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
minWidth="0"
|
||||
overflow="hidden"
|
||||
>
|
||||
<StyledIconContainer>
|
||||
<Icon size={16} />
|
||||
<Icon size={ICON_SIZES.md} />
|
||||
</StyledIconContainer>
|
||||
<OverflowingTextWithTooltip text={skill.label} />
|
||||
</StyledNameTableCell>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SettingsItemTypeTag item={skill} />
|
||||
</TableCell>
|
||||
<StyledActionTableCell>{action}</StyledActionTableCell>
|
||||
</StyledSkillTableRow>
|
||||
<TableCell align="right">{action}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<StyledSearchAndFilterContainer>
|
||||
<StyledSearchInput
|
||||
instanceId="skill-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a skill...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
<StyledSearchInputWrapper>
|
||||
<SettingsTextInput
|
||||
instanceId="skill-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a skill...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledSearchInputWrapper>
|
||||
<Dropdown
|
||||
dropdownId="settings-skills-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
@@ -160,21 +162,23 @@ export const SettingsSkillsTable = () => {
|
||||
</StyledSearchAndFilterContainer>
|
||||
|
||||
<Table>
|
||||
<StyledTableHeaderRow>
|
||||
{SETTINGS_SKILL_TABLE_METADATA.fields.map(
|
||||
(settingsSkillTableMetadataField) => (
|
||||
<SortableTableHeader
|
||||
key={settingsSkillTableMetadataField.fieldName}
|
||||
fieldName={settingsSkillTableMetadataField.fieldName}
|
||||
label={t(settingsSkillTableMetadataField.fieldLabel)}
|
||||
tableId={SETTINGS_SKILL_TABLE_METADATA.tableId}
|
||||
align={settingsSkillTableMetadataField.align}
|
||||
initialSort={SETTINGS_SKILL_TABLE_METADATA.initialSort}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<TableHeader />
|
||||
</StyledTableHeaderRow>
|
||||
<StyledTableHeaderRowContainer>
|
||||
<TableRow gridTemplateColumns="1fr 120px 36px">
|
||||
{SETTINGS_SKILL_TABLE_METADATA.fields.map(
|
||||
(settingsSkillTableMetadataField) => (
|
||||
<SortableTableHeader
|
||||
key={settingsSkillTableMetadataField.fieldName}
|
||||
fieldName={settingsSkillTableMetadataField.fieldName}
|
||||
label={t(settingsSkillTableMetadataField.fieldLabel)}
|
||||
tableId={SETTINGS_SKILL_TABLE_METADATA.tableId}
|
||||
align={settingsSkillTableMetadataField.align}
|
||||
initialSort={SETTINGS_SKILL_TABLE_METADATA.initialSort}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<TableHeader />
|
||||
</TableRow>
|
||||
</StyledTableHeaderRowContainer>
|
||||
{showSkeleton
|
||||
? Array.from({ length: 3 }).map((_, index) => (
|
||||
<Skeleton height={32} borderRadius={4} key={index} />
|
||||
@@ -186,8 +190,8 @@ export const SettingsSkillsTable = () => {
|
||||
action={
|
||||
skill.isActive ? (
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
size={ICON_SIZES.md}
|
||||
stroke={ICON_STROKES.sm}
|
||||
/>
|
||||
) : (
|
||||
<SettingsSkillInactiveMenuDropDown
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType';
|
||||
import {
|
||||
FieldMetadataDefaultValue,
|
||||
FieldMetadataOptions,
|
||||
FieldMetadataType,
|
||||
FieldMetadataUniversalSettings,
|
||||
RelationAndMorphRelationFieldMetadataType
|
||||
type FieldMetadataDefaultValue,
|
||||
type FieldMetadataOptions,
|
||||
type FieldMetadataType,
|
||||
type FieldMetadataUniversalSettings,
|
||||
type RelationAndMorphRelationFieldMetadataType,
|
||||
} from '@/types';
|
||||
|
||||
export type RegularFieldManifest<
|
||||
|
||||
@@ -3,33 +3,75 @@ import { type Plugin } from 'vite';
|
||||
|
||||
const LINARIA_IMPORT_RE = /@linaria/;
|
||||
|
||||
// Minimal Linaria code used to trigger WYW's Babel JIT compilation before
|
||||
// the real build starts, so the first real file doesn't pay the cold-start cost.
|
||||
// The ID must be inside the project root so WYW can resolve @linaria/react
|
||||
// from node_modules. It is set in configResolved once config.root is known.
|
||||
const WARMUP_CODE = `import { styled } from '@linaria/react';
|
||||
const StyledDiv = styled.div\`color: red;\`;
|
||||
`;
|
||||
|
||||
type WywProfilingOptions = {
|
||||
slowThresholdMs?: number;
|
||||
// Used only for dev-mode real-time slow-file alerts. Summary always uses 10x avg.
|
||||
devSlowThresholdMs?: number;
|
||||
topSlowFilesCount?: number;
|
||||
progressIntervalFiles?: number;
|
||||
warmupThresholdMs?: number;
|
||||
};
|
||||
|
||||
export const createWywProfilingPlugin = (
|
||||
wywPlugin: Plugin,
|
||||
options?: WywProfilingOptions,
|
||||
): Plugin => {
|
||||
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');
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 = ({
|
||||
|
||||
@@ -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 (
|
||||
<StyledLink
|
||||
to={to}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onClickHandler(event);
|
||||
}}
|
||||
onMouseDown={onMouseDownHandler}
|
||||
data-click-outside-id={LINK_CHIP_CLICK_OUTSIDE_ID}
|
||||
target={target}
|
||||
rel={target === '_blank' ? 'noopener noreferrer' : undefined}
|
||||
>
|
||||
<Chip
|
||||
size={size}
|
||||
label={label}
|
||||
isLabelHidden={isLabelHidden}
|
||||
isBold={isBold}
|
||||
clickable={true}
|
||||
variant={variant}
|
||||
leftComponent={leftComponent}
|
||||
rightComponent={rightComponent}
|
||||
rightComponentDivider={rightComponentDivider}
|
||||
accent={accent}
|
||||
className={className}
|
||||
maxWidth={maxWidth}
|
||||
emptyLabel={emptyLabel}
|
||||
/>
|
||||
</StyledLink>
|
||||
<StyledLinkContainer>
|
||||
<Link
|
||||
to={to}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onClickHandler(event);
|
||||
}}
|
||||
onMouseDown={onMouseDownHandler}
|
||||
data-click-outside-id={LINK_CHIP_CLICK_OUTSIDE_ID}
|
||||
target={target}
|
||||
rel={target === '_blank' ? 'noopener noreferrer' : undefined}
|
||||
>
|
||||
<Chip
|
||||
size={size}
|
||||
label={label}
|
||||
isLabelHidden={isLabelHidden}
|
||||
isBold={isBold}
|
||||
clickable={true}
|
||||
variant={variant}
|
||||
leftComponent={leftComponent}
|
||||
rightComponent={rightComponent}
|
||||
rightComponentDivider={rightComponentDivider}
|
||||
accent={accent}
|
||||
className={className}
|
||||
maxWidth={maxWidth}
|
||||
emptyLabel={emptyLabel}
|
||||
/>
|
||||
</Link>
|
||||
</StyledLinkContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<Pick<InfoProps, 'accent'>>`
|
||||
@@ -57,8 +56,10 @@ const StyledInfo = styled.div<Pick<InfoProps, 'accent'>>`
|
||||
}};
|
||||
`;
|
||||
|
||||
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 (
|
||||
<StyledInfo accent={accent}>
|
||||
<StyledTextContainer>
|
||||
<StyledIconInfoCircle size={theme.icon.size.md} />
|
||||
<IconInfoCircle size={ICON_SIZES.md} />
|
||||
{text}
|
||||
</StyledTextContainer>
|
||||
{buttonTitle && to && (
|
||||
<StyledLink to={to}>
|
||||
<Button
|
||||
title={buttonTitle}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
accent={accent}
|
||||
/>
|
||||
</StyledLink>
|
||||
<StyledLinkContainer>
|
||||
<Link to={to}>
|
||||
<Button
|
||||
title={buttonTitle}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
accent={accent}
|
||||
/>
|
||||
</Link>
|
||||
</StyledLinkContainer>
|
||||
)}
|
||||
{buttonTitle && onClick && !to && (
|
||||
<Button
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { type JSX, useContext } from 'react';
|
||||
import { type JSX } from 'react';
|
||||
import { Label } from '@ui/display';
|
||||
import { THEME_COMMON, ThemeContext } from '@ui/theme';
|
||||
|
||||
const spacing3 = THEME_COMMON.spacing(3);
|
||||
const spacing2 = THEME_COMMON.spacing(2);
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
|
||||
type HorizontalSeparatorProps = {
|
||||
visible?: boolean;
|
||||
@@ -21,16 +18,20 @@ const StyledSeparator = styled.div<{
|
||||
background-color: ${({ backgroundColor }) => backgroundColor};
|
||||
height: ${({ visible }) => (visible ? '1px' : '0')};
|
||||
flex-shrink: 0;
|
||||
margin-bottom: ${({ noMargin }) => (noMargin ? '0' : spacing3)};
|
||||
margin-top: ${({ noMargin }) => (noMargin ? '0' : spacing3)};
|
||||
margin-bottom: ${({ noMargin }) =>
|
||||
noMargin ? '0' : themeCssVariables.spacing[3]};
|
||||
margin-top: ${({ noMargin }) =>
|
||||
noMargin ? '0' : themeCssVariables.spacing[3]};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledSeparatorContainer = styled.div<{ noMargin: boolean }>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: ${({ noMargin }) => (noMargin ? '0' : spacing3)};
|
||||
margin-top: ${({ noMargin }) => (noMargin ? '0' : spacing3)};
|
||||
margin-bottom: ${({ noMargin }) =>
|
||||
noMargin ? '0' : themeCssVariables.spacing[3]};
|
||||
margin-top: ${({ noMargin }) =>
|
||||
noMargin ? '0' : themeCssVariables.spacing[3]};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@@ -44,7 +45,7 @@ const StyledLine = styled.div<{
|
||||
`;
|
||||
|
||||
const StyledText = styled.span`
|
||||
margin: 0 ${spacing2};
|
||||
margin: 0 ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
export const HorizontalSeparator = ({
|
||||
@@ -53,8 +54,7 @@ export const HorizontalSeparator = ({
|
||||
noMargin = false,
|
||||
color,
|
||||
}: HorizontalSeparatorProps): JSX.Element => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const borderColor = color ?? theme.border.color.medium;
|
||||
const borderColor = color ?? themeCssVariables.border.color.medium;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useState } from 'react';
|
||||
|
||||
export type ProgressBarProps = {
|
||||
value: number;
|
||||
@@ -26,43 +25,41 @@ const StyledBar = styled.div<StyledBarProps>`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledBarFillingBase = styled.div<{
|
||||
const StyledBarFilling = styled.div<{
|
||||
barColor?: string;
|
||||
withBorderRadius?: boolean;
|
||||
}>`
|
||||
background-color: ${({ barColor }) =>
|
||||
barColor ?? themeCssVariables.font.color.primary};
|
||||
height: 100%;
|
||||
border-radius: ${({ withBorderRadius }) =>
|
||||
withBorderRadius ? themeCssVariables.border.radius.md : '0'};
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledBarFilling = motion.create(StyledBarFillingBase);
|
||||
|
||||
export const ProgressBar = ({
|
||||
value,
|
||||
className,
|
||||
barColor,
|
||||
backgroundColor = 'none',
|
||||
withBorderRadius = false,
|
||||
}: ProgressBarProps) => {
|
||||
const [initialValue] = useState(value);
|
||||
|
||||
return (
|
||||
<StyledBar
|
||||
className={className}
|
||||
backgroundColor={backgroundColor}
|
||||
withBorderRadius={withBorderRadius}
|
||||
role="progressbar"
|
||||
aria-valuenow={Math.ceil(value)}
|
||||
}: ProgressBarProps) => (
|
||||
<StyledBar
|
||||
className={className}
|
||||
backgroundColor={backgroundColor}
|
||||
withBorderRadius={withBorderRadius}
|
||||
role="progressbar"
|
||||
aria-valuenow={Math.ceil(value)}
|
||||
>
|
||||
<motion.div
|
||||
style={{ height: '100%' }}
|
||||
animate={{ width: `${Math.ceil(value)}%` }}
|
||||
transition={{ duration: 0.3, ease: 'linear' }}
|
||||
>
|
||||
<StyledBarFilling
|
||||
initial={{ width: `${initialValue}%` }}
|
||||
animate={{ width: `${value}%` }}
|
||||
barColor={barColor}
|
||||
transition={{ ease: 'linear' }}
|
||||
withBorderRadius={withBorderRadius}
|
||||
/>
|
||||
</StyledBar>
|
||||
);
|
||||
};
|
||||
</motion.div>
|
||||
</StyledBar>
|
||||
);
|
||||
|
||||
@@ -2,12 +2,11 @@ import { styled } from '@linaria/react';
|
||||
import { useIsMobile } from '@ui/utilities';
|
||||
import { getOsShortcutSeparator } from '@ui/utilities/device/getOsShortcutSeparator';
|
||||
import { type MotionProps, motion } from 'framer-motion';
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Pill } from '@ui/components/Pill/Pill';
|
||||
import { ThemeContext } from '@ui/theme';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
|
||||
import {
|
||||
type ButtonAccent,
|
||||
type ButtonPosition,
|
||||
@@ -368,7 +367,8 @@ const StyledButton = styled.button<
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSoonPill = styled(Pill)`
|
||||
const StyledSoonPillContainer = styled.span`
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
@@ -413,9 +413,9 @@ const StyledShortcutLabel = styled.div<{
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
`;
|
||||
|
||||
const StyledIconContainer = styled(motion.div)`
|
||||
display: flex;
|
||||
const StyledIconContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
@@ -446,7 +446,6 @@ export const AnimatedButton = ({
|
||||
dataGloballyPreventClickOutside,
|
||||
soonLabel = 'Soon',
|
||||
}: AnimatedButtonProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const isMobile = useIsMobile();
|
||||
const isDisabled = soon || disabled;
|
||||
|
||||
@@ -491,13 +490,17 @@ export const AnimatedButton = ({
|
||||
data-globally-prevent-click-outside={dataGloballyPreventClickOutside}
|
||||
>
|
||||
{Icon && (
|
||||
<StyledIconContainer animate={animate} transition={transition}>
|
||||
<Icon size={theme.icon.size.sm} />
|
||||
<StyledIconContainer>
|
||||
<motion.div animate={animate} transition={transition}>
|
||||
<Icon size={ICON_SIZES.sm} />
|
||||
</motion.div>
|
||||
</StyledIconContainer>
|
||||
)}
|
||||
{animatedSvg && (
|
||||
<StyledIconContainer animate={animate} transition={transition}>
|
||||
{animatedSvg}
|
||||
<StyledIconContainer>
|
||||
<motion.div animate={animate} transition={transition}>
|
||||
{animatedSvg}
|
||||
</motion.div>
|
||||
</StyledIconContainer>
|
||||
)}
|
||||
{title}
|
||||
@@ -509,7 +512,11 @@ export const AnimatedButton = ({
|
||||
</StyledShortcutLabel>
|
||||
</>
|
||||
)}
|
||||
{soon && <StyledSoonPill label={soonLabel} />}
|
||||
{soon && (
|
||||
<StyledSoonPillContainer>
|
||||
<Pill label={soonLabel} />
|
||||
</StyledSoonPillContainer>
|
||||
)}
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,10 +4,9 @@ import {
|
||||
type LightIconButtonAccent,
|
||||
type LightIconButtonSize,
|
||||
} from '@ui/input/button/components/LightIconButton';
|
||||
import { ThemeContext } from '@ui/theme';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
|
||||
import { motion, type MotionProps } from 'framer-motion';
|
||||
import { type ComponentProps, type MouseEvent, useContext } from 'react';
|
||||
import { type ComponentProps, type MouseEvent } from 'react';
|
||||
|
||||
export type AnimatedLightIconButtonProps = {
|
||||
className?: string;
|
||||
@@ -108,8 +107,6 @@ export const AnimatedLightIconButton = ({
|
||||
onClick,
|
||||
title,
|
||||
}: AnimatedLightIconButtonProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
data-testid={testId}
|
||||
@@ -125,9 +122,7 @@ export const AnimatedLightIconButton = ({
|
||||
>
|
||||
<StyledIconContainer animate={animate} transition={transition}>
|
||||
{Icon && (
|
||||
<Icon
|
||||
size={size === 'medium' ? theme.icon.size.md : theme.icon.size.sm}
|
||||
/>
|
||||
<Icon size={size === 'medium' ? ICON_SIZES.md : ICON_SIZES.sm} />
|
||||
)}
|
||||
</StyledIconContainer>
|
||||
</StyledButton>
|
||||
|
||||
@@ -3,7 +3,6 @@ import { type IconComponent } from '@ui/display/icon/types/IconComponent';
|
||||
import { ButtonHotkeys } from '@ui/input/button/components/Button/internal/ButtonHotKeys';
|
||||
import { ButtonIcon } from '@ui/input/button/components/Button/internal/ButtonIcon';
|
||||
import { ButtonSoon } from '@ui/input/button/components/Button/internal/ButtonSoon';
|
||||
import { GRAY_SCALE_LIGHT } from '@ui/theme';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import { useIsMobile } from '@ui/utilities';
|
||||
import { type ClickOutsideAttributes } from '@ui/utilities/types/ClickOutsideAttributes';
|
||||
@@ -128,7 +127,7 @@ const computeButtonDynamicStyles = (
|
||||
}`
|
||||
: 'none';
|
||||
result.color = !inverted
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: themeCssVariables.color.blue;
|
||||
if (!disabled) {
|
||||
result.hoverBackground = !inverted
|
||||
@@ -191,10 +190,10 @@ const computeButtonDynamicStyles = (
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: themeCssVariables.background.transparent.primary
|
||||
: focus
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: 'transparent';
|
||||
result.borderWidthOverride = '1px 1px 1px 1px';
|
||||
result.boxShadow =
|
||||
@@ -232,10 +231,10 @@ const computeButtonDynamicStyles = (
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: themeCssVariables.background.transparent.primary
|
||||
: focus
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: 'transparent';
|
||||
result.borderWidthOverride = '1px 1px 1px 1px';
|
||||
result.boxShadow =
|
||||
@@ -273,10 +272,10 @@ const computeButtonDynamicStyles = (
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: themeCssVariables.background.transparent.primary
|
||||
: focus
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: 'transparent';
|
||||
result.borderWidthOverride = '1px 1px 1px 1px';
|
||||
result.boxShadow =
|
||||
@@ -416,7 +415,7 @@ const computeButtonWrapperColor = (
|
||||
: themeCssVariables.font.color.secondary;
|
||||
case 'blue':
|
||||
return !inverted
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: themeCssVariables.color.blue;
|
||||
case 'danger':
|
||||
return !inverted
|
||||
|
||||
@@ -2,9 +2,7 @@ import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import { Loader } from '@ui/feedback';
|
||||
import { baseTransitionTiming } from '@ui/input/button/components/Button/constant';
|
||||
import { ThemeContext } from '@ui/theme';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import { useContext } from 'react';
|
||||
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
|
||||
|
||||
const StyledIcon = styled.div<{
|
||||
isLoading: boolean;
|
||||
@@ -43,20 +41,17 @@ export const ButtonIcon = ({
|
||||
}: {
|
||||
Icon?: IconComponent;
|
||||
isLoading?: boolean;
|
||||
}) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return (
|
||||
<StyledIconWrapper>
|
||||
{isLoading && (
|
||||
<StyledLoader>
|
||||
<Loader />
|
||||
</StyledLoader>
|
||||
)}
|
||||
{Icon && (
|
||||
<StyledIcon isLoading={!!isLoading}>
|
||||
<Icon size={theme.icon.size.sm} />
|
||||
</StyledIcon>
|
||||
)}
|
||||
</StyledIconWrapper>
|
||||
);
|
||||
};
|
||||
}) => (
|
||||
<StyledIconWrapper>
|
||||
{isLoading && (
|
||||
<StyledLoader>
|
||||
<Loader />
|
||||
</StyledLoader>
|
||||
)}
|
||||
{Icon && (
|
||||
<StyledIcon isLoading={!!isLoading}>
|
||||
<Icon size={ICON_SIZES.sm} />
|
||||
</StyledIcon>
|
||||
)}
|
||||
</StyledIconWrapper>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { Pill } from '@ui/components';
|
||||
|
||||
const StyledSoonPill = styled(Pill)`
|
||||
const StyledSoonPillContainer = styled.span`
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
@@ -10,5 +11,7 @@ type ButtonSoonProps = {
|
||||
};
|
||||
|
||||
export const ButtonSoon = ({ label = 'Soon' }: ButtonSoonProps) => (
|
||||
<StyledSoonPill label={label} />
|
||||
<StyledSoonPillContainer>
|
||||
<Pill label={label} />
|
||||
</StyledSoonPillContainer>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import { ThemeContext } from '@ui/theme';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import { type MouseEvent, useContext } from 'react';
|
||||
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
|
||||
import { type MouseEvent } from 'react';
|
||||
|
||||
export type LightButtonAccent = 'secondary' | 'tertiary';
|
||||
|
||||
@@ -90,8 +89,6 @@ export const LightButton = ({
|
||||
type = 'button',
|
||||
onClick,
|
||||
}: LightButtonProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
onClick={onClick}
|
||||
@@ -102,7 +99,7 @@ export const LightButton = ({
|
||||
className={className}
|
||||
active={active}
|
||||
>
|
||||
{!!Icon && <Icon size={theme.icon.size.md} />}
|
||||
{!!Icon && <Icon size={ICON_SIZES.md} />}
|
||||
{title}
|
||||
</StyledButton>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import { ThemeContext } from '@ui/theme';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import { type ComponentProps, type MouseEvent, useContext } from 'react';
|
||||
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
|
||||
import { type ComponentProps, type MouseEvent } from 'react';
|
||||
|
||||
export type LightIconButtonAccent = 'secondary' | 'tertiary';
|
||||
export type LightIconButtonSize = 'small' | 'medium';
|
||||
@@ -106,8 +105,6 @@ export const LightIconButton = ({
|
||||
onClick,
|
||||
title,
|
||||
}: LightIconButtonProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
data-testid={testId}
|
||||
@@ -122,9 +119,7 @@ export const LightIconButton = ({
|
||||
title={title}
|
||||
>
|
||||
{Icon && (
|
||||
<Icon
|
||||
size={size === 'medium' ? theme.icon.size.md : theme.icon.size.sm}
|
||||
/>
|
||||
<Icon size={size === 'medium' ? ICON_SIZES.md : ICON_SIZES.sm} />
|
||||
)}
|
||||
</StyledButton>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,6 @@ import { JsonNodeValue } from '@ui/json-visualizer/components/internal/JsonNodeV
|
||||
import { JsonNode } from '@ui/json-visualizer/components/JsonNode';
|
||||
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
||||
import { type JsonNodeHighlighting } from '@ui/json-visualizer/types/JsonNodeHighlighting';
|
||||
import { ANIMATION } from '@ui/theme';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useState } from 'react';
|
||||
@@ -50,8 +49,6 @@ const StyledJsonListBase = styled.ul<{
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledJsonList = motion.create(StyledJsonListBase);
|
||||
|
||||
export const JsonNestedNode = ({
|
||||
label,
|
||||
Icon,
|
||||
@@ -80,45 +77,34 @@ export const JsonNestedNode = ({
|
||||
);
|
||||
|
||||
const renderedChildren = (
|
||||
<StyledJsonList
|
||||
initial={{
|
||||
height: 0,
|
||||
opacity: 0,
|
||||
overflowY: 'clip',
|
||||
}}
|
||||
animate={{
|
||||
height: 'auto',
|
||||
opacity: 1,
|
||||
overflowY: 'clip',
|
||||
}}
|
||||
exit={{
|
||||
height: 0,
|
||||
opacity: 0,
|
||||
overflowY: 'clip',
|
||||
}}
|
||||
transition={{ duration: ANIMATION.duration.normal }}
|
||||
depth={depth}
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0, overflow: 'clip' }}
|
||||
animate={{ height: 'auto', opacity: 1, overflow: 'clip' }}
|
||||
exit={{ height: 0, opacity: 0, overflow: 'clip' }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
{elements.length === 0 ? (
|
||||
<JsonNodeValue valueAsString={emptyElementsText} />
|
||||
) : (
|
||||
elements.map(({ id, label, value }) => {
|
||||
const nextKeyPath = isNonEmptyString(keyPath)
|
||||
? `${keyPath}.${id}`
|
||||
: String(id);
|
||||
<StyledJsonListBase depth={depth}>
|
||||
{elements.length === 0 ? (
|
||||
<JsonNodeValue valueAsString={emptyElementsText} />
|
||||
) : (
|
||||
elements.map(({ id, label, value }) => {
|
||||
const nextKeyPath = isNonEmptyString(keyPath)
|
||||
? `${keyPath}.${id}`
|
||||
: String(id);
|
||||
|
||||
return (
|
||||
<JsonNode
|
||||
key={id}
|
||||
label={label}
|
||||
value={value}
|
||||
depth={depth + 1}
|
||||
keyPath={nextKeyPath}
|
||||
/>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</StyledJsonList>
|
||||
return (
|
||||
<JsonNode
|
||||
key={id}
|
||||
label={label}
|
||||
value={value}
|
||||
depth={depth + 1}
|
||||
keyPath={nextKeyPath}
|
||||
/>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</StyledJsonListBase>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
const handleArrowClick = () => {
|
||||
|
||||
@@ -2,10 +2,8 @@ import { styled } from '@linaria/react';
|
||||
import { VisibilityHidden } from '@ui/accessibility';
|
||||
import { IconChevronDown } from '@ui/display';
|
||||
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
||||
import { ANIMATION, ThemeContext } from '@ui/theme';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useContext } from 'react';
|
||||
|
||||
const StyledButton = styled.button<{
|
||||
variant?: 'blue' | 'red';
|
||||
@@ -31,8 +29,6 @@ const StyledButton = styled.button<{
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const MotionIconChevronDown = motion.create(IconChevronDown);
|
||||
|
||||
export const JsonArrow = ({
|
||||
isOpen,
|
||||
onClick,
|
||||
@@ -42,30 +38,29 @@ export const JsonArrow = ({
|
||||
onClick: () => void;
|
||||
variant?: 'blue' | 'red';
|
||||
}) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
const { arrowButtonCollapsedLabel, arrowButtonExpandedLabel } =
|
||||
useJsonTreeContextOrThrow();
|
||||
|
||||
const iconColor =
|
||||
variant === 'blue'
|
||||
? themeCssVariables.color.blue
|
||||
: variant === 'red'
|
||||
? themeCssVariables.font.color.danger
|
||||
: themeCssVariables.font.color.secondary;
|
||||
|
||||
return (
|
||||
<StyledButton variant={variant} onClick={onClick}>
|
||||
<VisibilityHidden>
|
||||
{isOpen ? arrowButtonExpandedLabel : arrowButtonCollapsedLabel}
|
||||
</VisibilityHidden>
|
||||
|
||||
<MotionIconChevronDown
|
||||
size={theme.icon.size.md}
|
||||
color={
|
||||
variant === 'blue'
|
||||
? theme.color.blue
|
||||
: variant === 'red'
|
||||
? theme.font.color.danger
|
||||
: theme.font.color.secondary
|
||||
}
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{ rotate: isOpen ? 0 : -90 }}
|
||||
transition={{ duration: ANIMATION.duration.normal }}
|
||||
/>
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<IconChevronDown size={ICON_SIZES.md} color={iconColor} />
|
||||
</motion.div>
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,10 @@ import { type ComponentPropsWithoutRef } from 'react';
|
||||
const StyledCard = styled.div<{
|
||||
fullWidth?: boolean;
|
||||
rounded?: boolean;
|
||||
backgroundColor?: string;
|
||||
}>`
|
||||
background-color: ${({ backgroundColor }) =>
|
||||
backgroundColor ?? 'transparent'};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: ${({ rounded }) =>
|
||||
rounded
|
||||
@@ -19,6 +22,7 @@ const StyledCard = styled.div<{
|
||||
type CardProps = ComponentPropsWithoutRef<'div'> & {
|
||||
fullWidth?: boolean;
|
||||
rounded?: boolean;
|
||||
backgroundColor?: string;
|
||||
};
|
||||
|
||||
export const Card = ({
|
||||
@@ -26,6 +30,7 @@ export const Card = ({
|
||||
className,
|
||||
fullWidth,
|
||||
rounded,
|
||||
backgroundColor,
|
||||
...rest
|
||||
}: CardProps) => {
|
||||
return (
|
||||
@@ -33,6 +38,7 @@ export const Card = ({
|
||||
className={className}
|
||||
fullWidth={fullWidth}
|
||||
rounded={rounded}
|
||||
backgroundColor={backgroundColor}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...rest}
|
||||
>
|
||||
|
||||
@@ -40,7 +40,6 @@ export type ModalHeaderProps = React.PropsWithChildren & {
|
||||
hasBorderBottom?: boolean;
|
||||
paddingHorizontal?: number;
|
||||
backgroundColor?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const ModalHeader = ({
|
||||
@@ -50,7 +49,6 @@ export const ModalHeader = ({
|
||||
hasBorderBottom,
|
||||
paddingHorizontal,
|
||||
backgroundColor,
|
||||
className,
|
||||
}: ModalHeaderProps) => (
|
||||
<StyledHeader
|
||||
noPadding={noPadding}
|
||||
@@ -58,7 +56,6 @@ export const ModalHeader = ({
|
||||
hasBorderBottom={hasBorderBottom}
|
||||
paddingHorizontal={paddingHorizontal}
|
||||
backgroundColor={backgroundColor}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</StyledHeader>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useContext } from 'react';
|
||||
import {
|
||||
AppTooltip,
|
||||
TooltipDelay,
|
||||
TooltipPosition,
|
||||
type IconComponent,
|
||||
} from '@ui/display';
|
||||
import { ThemeContext } from '@ui/theme';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import {
|
||||
ICON_SIZES,
|
||||
ICON_STROKES,
|
||||
themeCssVariables,
|
||||
} from '@ui/theme-constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledMenuPicker = styled.button<{
|
||||
@@ -135,8 +137,6 @@ export const MenuPicker = ({
|
||||
tooltipDelay = TooltipDelay.noDelay,
|
||||
tooltipOffset = 5,
|
||||
}: MenuPickerProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledMenuPicker
|
||||
@@ -151,7 +151,7 @@ export const MenuPicker = ({
|
||||
aria-label={label}
|
||||
>
|
||||
<StyledIconContainer>
|
||||
<Icon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
|
||||
<Icon size={ICON_SIZES.md} stroke={ICON_STROKES.sm} />
|
||||
</StyledIconContainer>
|
||||
|
||||
{isDefined(label) && showLabel && (
|
||||
|
||||
+8
-9
@@ -1,9 +1,10 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent, IconGripVertical } from '@ui/display';
|
||||
import { ThemeContext } from '@ui/theme';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import {
|
||||
ICON_SIZES,
|
||||
ICON_STROKES,
|
||||
themeCssVariables,
|
||||
} from '@ui/theme-constants';
|
||||
import { MenuItemIconBoxContainer } from './MenuItemIconBoxContainer';
|
||||
|
||||
const StyledIconSwapContainer = styled.div`
|
||||
@@ -38,8 +39,6 @@ export const MenuItemIconWithGripSwap = ({
|
||||
withIconContainer = false,
|
||||
gripIconColor,
|
||||
}: MenuItemIconWithGripSwapProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
if (!LeftIcon) {
|
||||
return null;
|
||||
}
|
||||
@@ -47,12 +46,12 @@ export const MenuItemIconWithGripSwap = ({
|
||||
const iconContent = (
|
||||
<StyledIconSwapContainer>
|
||||
<StyledDefaultIcon className="grip-swap-default-icon">
|
||||
<LeftIcon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
|
||||
<LeftIcon size={ICON_SIZES.md} stroke={ICON_STROKES.sm} />
|
||||
</StyledDefaultIcon>
|
||||
<StyledHoverIcon className="grip-swap-hover-icon">
|
||||
<IconGripVertical
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
size={ICON_SIZES.md}
|
||||
stroke={ICON_STROKES.sm}
|
||||
color={gripIconColor}
|
||||
/>
|
||||
</StyledHoverIcon>
|
||||
|
||||
+13
-11
@@ -1,5 +1,5 @@
|
||||
import { isNonEmptyString, isString } from '@sniptt/guards';
|
||||
import { useContext, type ReactNode } from 'react';
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
import { styled } from '@linaria/react';
|
||||
import {
|
||||
@@ -7,7 +7,11 @@ import {
|
||||
IconGripVertical,
|
||||
OverflowingTextWithTooltip,
|
||||
} from '@ui/display';
|
||||
import { ThemeContext } from '@ui/theme';
|
||||
import {
|
||||
ICON_SIZES,
|
||||
ICON_STROKES,
|
||||
themeCssVariables,
|
||||
} from '@ui/theme-constants';
|
||||
import { type MenuItemDraggableGripMode } from '../../types/MenuItemDraggableGripMode';
|
||||
import { MenuItemIcon } from './MenuItemIcon';
|
||||
import { MenuItemIconBoxContainer } from './MenuItemIconBoxContainer';
|
||||
@@ -55,13 +59,11 @@ export const MenuItemLeftContent = ({
|
||||
gripMode = 'never',
|
||||
disabled = false,
|
||||
}: MenuItemLeftContentProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
const gripIconColor = withIconContainer
|
||||
? theme.font.color.tertiary
|
||||
? themeCssVariables.font.color.tertiary
|
||||
: disabled
|
||||
? theme.font.color.extraLight
|
||||
: theme.font.color.light;
|
||||
? themeCssVariables.font.color.extraLight
|
||||
: themeCssVariables.font.color.light;
|
||||
|
||||
return (
|
||||
<StyledMenuItemLeftContent className={className}>
|
||||
@@ -70,8 +72,8 @@ export const MenuItemLeftContent = ({
|
||||
<MenuItemIconBoxContainer>
|
||||
<StyledDraggableItem>
|
||||
<IconGripVertical
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
size={ICON_SIZES.md}
|
||||
stroke={ICON_STROKES.sm}
|
||||
color={gripIconColor}
|
||||
/>
|
||||
</StyledDraggableItem>
|
||||
@@ -79,8 +81,8 @@ export const MenuItemLeftContent = ({
|
||||
) : (
|
||||
<StyledDraggableItem>
|
||||
<IconGripVertical
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
size={ICON_SIZES.md}
|
||||
stroke={ICON_STROKES.sm}
|
||||
color={gripIconColor}
|
||||
/>
|
||||
</StyledDraggableItem>
|
||||
|
||||
+11
-5
@@ -1,7 +1,5 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
|
||||
import { IconCheck } from '@ui/display';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import { type MenuItemAccent } from '../../types/MenuItemAccent';
|
||||
@@ -45,7 +43,7 @@ export const StyledMenuItemBase = styled.div<MenuItemBaseProps>`
|
||||
disabled || isHoverBackgroundDisabled ? 'none' : 'background 0.1s ease'};
|
||||
|
||||
color: ${({ accent, disabled }) => {
|
||||
if (!isUndefined(disabled) && disabled !== false) {
|
||||
if (disabled !== undefined && disabled !== false) {
|
||||
return themeCssVariables.font.color.tertiary;
|
||||
}
|
||||
switch (accent) {
|
||||
@@ -168,7 +166,7 @@ export const StyledHoverableMenuItemBase = styled(
|
||||
}
|
||||
|
||||
cursor: ${({ cursor, disabled }) => {
|
||||
if (!isUndefined(disabled) && disabled !== false) {
|
||||
if (disabled !== undefined && disabled !== false) {
|
||||
return 'default';
|
||||
}
|
||||
|
||||
@@ -181,11 +179,19 @@ export const StyledHoverableMenuItemBase = styled(
|
||||
}};
|
||||
`;
|
||||
|
||||
export const StyledMenuItemIconCheck = styled(IconCheck)`
|
||||
const StyledMenuItemIconCheckContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
margin-right: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
export const StyledMenuItemIconCheck = ({ size }: { size?: number }) => (
|
||||
<StyledMenuItemIconCheckContainer>
|
||||
<IconCheck size={size} />
|
||||
</StyledMenuItemIconCheckContainer>
|
||||
);
|
||||
|
||||
export const StyledMenuItemContextualText = styled.div`
|
||||
color: ${themeCssVariables.font.color.light};
|
||||
font-family: inherit;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display/icon/types/IconComponent';
|
||||
import { ThemeContext } from '@ui/theme';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
|
||||
|
||||
const StyledIconButton = styled.div<{ isActive?: boolean }>`
|
||||
align-items: center;
|
||||
@@ -33,12 +30,8 @@ export const NavigationBarItem = ({
|
||||
Icon,
|
||||
isActive,
|
||||
onClick,
|
||||
}: NavigationBarItemProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledIconButton isActive={isActive} onClick={onClick}>
|
||||
<Icon color={theme.color.gray10} size={theme.icon.size.lg} />
|
||||
</StyledIconButton>
|
||||
);
|
||||
};
|
||||
}: NavigationBarItemProps) => (
|
||||
<StyledIconButton isActive={isActive} onClick={onClick}>
|
||||
<Icon color={themeCssVariables.grayScale.gray10} size={ICON_SIZES.lg} />
|
||||
</StyledIconButton>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,20 @@
|
||||
// must be a static number rather than a var(--...) reference.
|
||||
export const MOBILE_VIEWPORT = 768;
|
||||
|
||||
// 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 = {
|
||||
sm: 14,
|
||||
md: 16,
|
||||
lg: 20,
|
||||
xl: 24,
|
||||
} as const;
|
||||
export const ICON_STROKES = {
|
||||
sm: 1.6,
|
||||
md: 2,
|
||||
lg: 2.5,
|
||||
} as const;
|
||||
|
||||
export const THEME_LIGHT_CSS_VARIABLE_ENTRIES: [string, string][] = [
|
||||
['--t-icon-size-sm', '14'],
|
||||
['--t-icon-size-md', '16'],
|
||||
|
||||
@@ -11,5 +11,7 @@ export { themeCssVariables } from './generated/themeCssVariables';
|
||||
export { THEME_DARK_CSS_VARIABLE_ENTRIES } from './generated/themeDarkCssVariableEntries';
|
||||
export {
|
||||
MOBILE_VIEWPORT,
|
||||
ICON_SIZES,
|
||||
ICON_STROKES,
|
||||
THEME_LIGHT_CSS_VARIABLE_ENTRIES,
|
||||
} from './generated/themeLightCssVariableEntries';
|
||||
|
||||
Reference in New Issue
Block a user