Etienne
2025-06-10 16:49:37 +02:00
committed by GitHub
parent a68895189c
commit a15318537f
17 changed files with 200 additions and 157 deletions

View File

@ -9,7 +9,9 @@ import { MatchColumnSelectSubFieldSelectDropdownContent } from '@/spreadsheet-im
import { DO_NOT_IMPORT_OPTION_KEY } from '@/spreadsheet-import/constants/DoNotImportOptionKey';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared/utils';
import { IconChevronDown } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input';
import { MenuItem } from 'twenty-ui/navigation';
@ -21,6 +23,11 @@ interface MatchColumnToFieldSelectProps {
placeholder?: string;
}
const StyledMenuItem = styled(MenuItem)`
background-color: ${({ theme }) => theme.background.transparent.lighter};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
`;
export const MatchColumnToFieldSelect = ({
onChange,
value,
@ -112,10 +119,11 @@ export const MatchColumnToFieldSelect = ({
}}
dropdownPlacement="bottom-start"
clickableComponent={
<MenuItem
<StyledMenuItem
LeftIcon={value?.Icon}
text={value?.label ?? placeholder ?? ''}
accent={value?.label ? 'default' : 'placeholder'}
RightIcon={IconChevronDown}
/>
}
dropdownComponents={

View File

@ -7,12 +7,10 @@ import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { SpreadSheetImportModalCloseButton } from './SpreadSheetImportModalCloseButton';
const StyledModal = styled(Modal)`
height: 61%;
min-height: 600px;
min-width: 800px;
padding: 0;
position: relative;
width: 63%;
@media (max-width: ${MOBILE_VIEWPORT}px) {
min-width: auto;
min-height: auto;
@ -42,7 +40,7 @@ export const SpreadSheetImportModalWrapper = ({
return (
<StyledModal
size="large"
size="extraLarge"
modalId={modalId}
isClosable={true}
onClose={onClose}

View File

@ -28,18 +28,17 @@ const StyledDataGrid = styled(DataGrid)`
--row-selected-hover-background-color: ${({ theme }) =>
theme.background.secondary};
border: none;
block-size: 100%;
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md};
width: 100%;
.rdg-header-row .rdg-cell {
box-shadow: none;
color: ${({ theme }) => theme.font.color.tertiary};
background-color: ${({ theme }) => theme.background.secondary};
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
letter-spacing: wider;
text-transform: uppercase;
${({ headerRowHeight }) => {
if (headerRowHeight === 0) {
return `
@ -68,7 +67,7 @@ const StyledDataGrid = styled(DataGrid)`
}
.rdg-cell-error {
background-color: ${({ theme }) => RGBA(theme.color.red, 0.08)};
background-color: ${({ theme }) => theme.adaptiveColors.yellow1};
}
.rdg-cell-warning {
@ -134,7 +133,7 @@ export const SpreadsheetImportTable = <Data,>({
return (
<StyledDataGrid
direction={rtl ? 'rtl' : 'ltr'}
rowHeight={52}
rowHeight={40}
{...{
className: `${className || ''} ${themeClassName}`,
columns,

View File

@ -1,14 +1,17 @@
import styled from '@emotion/styled';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { CircularProgressBar } from 'twenty-ui/feedback';
import { MainButton } from 'twenty-ui/input';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
const StyledFooter = styled(Modal.Footer)`
border-top: 1px solid ${({ theme }) => theme.border.color.medium};
box-shadow: ${({ theme }) => theme.boxShadow.strong};
gap: ${({ theme }) => theme.spacing(2.5)};
justify-content: space-between;
padding: ${({ theme }) => theme.spacing(6)} ${({ theme }) => theme.spacing(8)};
padding: ${({ theme }) => theme.spacing(4)};
height: auto;
`;
type StepNavigationButtonProps = {
@ -16,6 +19,7 @@ type StepNavigationButtonProps = {
title: string;
isLoading?: boolean;
onBack?: () => void;
isNextDisabled?: boolean;
};
export const StepNavigationButton = ({
@ -23,6 +27,7 @@ export const StepNavigationButton = ({
title,
isLoading,
onBack,
isNextDisabled = false,
}: StepNavigationButtonProps) => {
return (
<StyledFooter>
@ -39,6 +44,7 @@ export const StepNavigationButton = ({
title={title}
onClick={!isLoading ? onClick : undefined}
variant="primary"
disabled={isNextDisabled}
/>
</StyledFooter>
);

View File

@ -12,7 +12,7 @@ describe('useSpreadsheetImportInitialStep', () => {
return { initialStep, setStep };
});
expect(result.current.initialStep).toBe(-1);
expect(result.current.initialStep).toBe(0);
act(() => {
result.current.setStep(SpreadsheetImportStepType.upload);

View File

@ -19,7 +19,7 @@ export const useSpreadsheetImportInitialStep = (
case SpreadsheetImportStepType.validateData:
return 3;
default:
return -1;
return 0;
}
}, [initialStep]);

View File

@ -1,7 +1,6 @@
import styled from '@emotion/styled';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Heading } from '@/spreadsheet-import/components/Heading';
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
import { ImportedRow, ImportedStructuredRow } from '@/spreadsheet-import/types';
@ -17,7 +16,10 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { DO_NOT_IMPORT_OPTION_KEY } from '@/spreadsheet-import/constants/DoNotImportOptionKey';
import { ColumnGrid } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/ColumnGrid';
import { TemplateColumn } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn';
import { UnmatchColumn } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumn';
import { UserTableColumn } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/UserTableColumn';
import { initialComputedColumnsSelector } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/states/initialComputedColumnsState';
import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
@ -29,14 +31,10 @@ import { getMatchedColumnsWithFuse } from '@/spreadsheet-import/utils/getMatched
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { Trans, useLingui } from '@lingui/react/macro';
import { useRecoilState } from 'recoil';
import { ColumnGrid } from './components/ColumnGrid';
import { TemplateColumn } from './components/TemplateColumn';
import { UserTableColumn } from './components/UserTableColumn';
const StyledContent = styled(Modal.Content)`
align-items: center;
padding-left: ${({ theme }) => theme.spacing(6)};
padding-right: ${({ theme }) => theme.spacing(6)};
padding: 0px;
`;
const StyledColumnsContainer = styled.div`
@ -274,14 +272,17 @@ export const MatchColumnsStep = <T extends string>({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const hasMatchedColumns = columns.some(
(column) =>
![SpreadsheetColumnType.ignored, SpreadsheetColumnType.empty].includes(
column.type,
),
);
return (
<>
<ScrollWrapper componentInstanceId="scroll-wrapper-modal-content">
<StyledContent>
<Heading
title={t`Match Columns`}
description={t`⚠️ Please verify the auto mapping of the columns. You can also ignore or change the mapping of the columns.`}
/>
<StyledContent>
<ScrollWrapper componentInstanceId="scroll-wrapper-modal-content">
<ColumnGrid
columns={columns}
renderUserColumn={(columns, columnIndex) => (
@ -307,8 +308,8 @@ export const MatchColumnsStep = <T extends string>({
/>
)}
/>
</StyledContent>
</ScrollWrapper>
</ScrollWrapper>
</StyledContent>
<StepNavigationButton
onClick={handleOnContinue}
isLoading={isLoading}
@ -317,6 +318,7 @@ export const MatchColumnsStep = <T extends string>({
onBack?.();
setColumns([]);
}}
isNextDisabled={!hasMatchedColumns}
/>
</>
);

View File

@ -12,12 +12,9 @@ const StyledGridContainer = styled.div`
`;
const StyledGrid = styled.div`
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md};
box-sizing: border-box;
display: flex;
flex-direction: column;
margin-top: ${({ theme }) => theme.spacing(8)};
width: 100%;
`;
@ -73,22 +70,16 @@ const StyledGridCell = styled.div<PositionProps>`
const StyledGridHeader = styled.div<PositionProps>`
align-items: center;
background-color: ${({ theme }) => theme.background.tertiary};
background-color: ${({ theme }) => theme.background.secondary};
border-bottom: 1px solid ${({ theme }) => theme.border.color.medium};
box-sizing: border-box;
color: ${({ theme }) => theme.font.color.light};
display: flex;
flex: 1;
font-size: ${({ theme }) => theme.font.size.sm};
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
padding-left: ${({ theme }) => theme.spacing(4)};
padding-right: ${({ theme }) => theme.spacing(4)};
${({ position, theme }) => {
if (position === 'left') {
return `border-top-left-radius: calc(${theme.border.radius.md} - 1px);`;
}
return `border-top-right-radius: calc(${theme.border.radius.md} - 1px);`;
}};
text-transform: uppercase;
`;
type ColumnGridProps<T extends string> = {
@ -117,7 +108,7 @@ export const ColumnGrid = <T extends string>({
<>
<StyledGridContainer>
<StyledGrid>
<StyledGridRow height="29px">
<StyledGridRow height="32px">
<StyledGridHeader position="left">Imported data</StyledGridHeader>
<StyledGridHeader position="right">Twenty fields</StyledGridHeader>
</StyledGridRow>

View File

@ -13,7 +13,7 @@ const StyledContainer = styled.div`
const StyledValue = styled.span`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.sm};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.medium};
overflow: hidden;
text-overflow: ellipsis;

View File

@ -8,16 +8,15 @@ import { useStepBar } from '@/ui/navigation/step-bar/hooks/useStepBar';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { useLingui } from '@lingui/react/macro';
import { SpreadsheetImportStepper } from './SpreadsheetImportStepper';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { SpreadsheetImportStepper } from './SpreadsheetImportStepper';
const StyledHeader = styled(Modal.Header)`
background-color: ${({ theme }) => theme.background.secondary};
border-bottom: 1px solid ${({ theme }) => theme.border.color.medium};
padding: 0px ${({ theme }) => theme.spacing(30)};
height: 60px;
padding: 0px;
padding-left: ${({ theme }) => theme.spacing(30)};
padding-right: ${({ theme }) => theme.spacing(30)};
flex-shrink: 0;
@media (max-width: ${MOBILE_VIEWPORT}px) {
padding-left: ${({ theme }) => theme.spacing(4)};
padding-right: ${({ theme }) => theme.spacing(4)};
@ -28,9 +27,9 @@ export const SpreadsheetImportStepperContainer = () => {
const { t } = useLingui();
const stepTitles = {
uploadStep: t`Upload file`,
matchColumnsStep: t`Match columns`,
validationStep: t`Validate data`,
uploadStep: t`Upload File`,
matchColumnsStep: t`Match Columns`,
validationStep: t`Validate Data`,
};
const { initialStepState } = useSpreadsheetImportInternal();

View File

@ -1,4 +1,3 @@
import { Heading } from '@/spreadsheet-import/components/Heading';
import { SpreadsheetImportTable } from '@/spreadsheet-import/components/SpreadsheetImportTable';
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
@ -31,16 +30,30 @@ import { generateColumns } from './components/columns';
import { ImportedStructuredRowMetadata } from './types';
const StyledContent = styled(Modal.Content)`
padding-left: ${({ theme }) => theme.spacing(6)};
padding-right: ${({ theme }) => theme.spacing(6)};
padding: 0px;
position: relative;
`;
const StyledToolbar = styled.div`
align-items: center;
border-radius: ${({ theme }) => theme.border.radius.md};
border: 1px solid ${({ theme }) => theme.border.color.medium};
background-color: ${({ theme }) => theme.background.secondary};
bottom: ${({ theme }) => theme.spacing(3)};
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: ${({ theme }) => theme.spacing(4)};
margin-top: ${({ theme }) => theme.spacing(8)};
left: 50%;
position: absolute;
transform: translateX(-50%);
width: 400px;
padding: ${({ theme }) => theme.spacing(3)};
z-index: 1;
box-shadow: ${({ theme }) => theme.boxShadow.strong};
`;
const StyledButton = styled(Button)`
height: 24px;
`;
const StyledErrorToggle = styled.div`
@ -50,8 +63,8 @@ const StyledErrorToggle = styled.div`
`;
const StyledErrorToggleDescription = styled.span`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.sm};
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.regular};
margin-left: ${({ theme }) => theme.spacing(2)};
`;
@ -71,6 +84,13 @@ const StyledNoRowsContainer = styled.div`
margin-top: ${({ theme }) => theme.spacing(8)};
`;
const StyledNoRowsWithErrorsContainer = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
justify-content: center;
margin: auto 0;
`;
type ValidationStepProps<T extends string> = {
initialData: ImportedStructuredRow<T>[];
importedColumns: SpreadsheetColumns<string>;
@ -103,7 +123,6 @@ export const ValidationStep = <T extends string>({
ReadonlySet<number | string>
>(new Set());
const [filterByErrors, setFilterByErrors] = useState(false);
const [showUnmatchedColumns, setShowUnmatchedColumns] = useState(false);
const updateData = useCallback(
(rows: typeof data) => {
@ -164,11 +183,11 @@ export const ValidationStep = <T extends string>({
column.key === 'select-row',
).length > 0;
if (!hasBeenImported && !showUnmatchedColumns) return null;
if (!hasBeenImported) return null;
return column;
})
.filter(Boolean),
[fields, importedColumns, showUnmatchedColumns],
[fields, importedColumns],
);
const tableData = useMemo(() => {
@ -255,56 +274,51 @@ export const ValidationStep = <T extends string>({
return (
<>
<StyledContent>
<Heading
title={t`Review your import`}
description={t`Correct the issues and fill the missing data.`}
/>
{filterByErrors && tableData.length === 0 ? (
<StyledNoRowsWithErrorsContainer>
<Trans>No rows with errors</Trans>
</StyledNoRowsWithErrorsContainer>
) : (
<StyledScrollContainer>
<SpreadsheetImportTable
headerRowHeight={32}
rowKeyGetter={rowKeyGetter}
rows={tableData}
onRowsChange={updateRow}
columns={columns}
selectedRows={selectedRows}
onSelectedRowsChange={setSelectedRows as any} // TODO: replace 'any'
components={{
noRowsFallback: (
<StyledNoRowsContainer>
{filterByErrors
? t`No data containing errors`
: t`No data found`}
</StyledNoRowsContainer>
),
}}
/>
</StyledScrollContainer>
)}
<StyledToolbar>
<StyledErrorToggle>
<Toggle
value={filterByErrors}
onChange={() => setFilterByErrors(!filterByErrors)}
toggleSize="small"
/>
<StyledErrorToggleDescription>
<Trans>Show only rows with errors</Trans>
</StyledErrorToggleDescription>
</StyledErrorToggle>
<StyledErrorToggle>
<Toggle
value={showUnmatchedColumns}
onChange={() => setShowUnmatchedColumns(!showUnmatchedColumns)}
/>
<StyledErrorToggleDescription>
<Trans>Show unmatched columns</Trans>
</StyledErrorToggleDescription>
</StyledErrorToggle>
<Button
<StyledButton
Icon={IconTrash}
title={t`Remove`}
accent="danger"
accent="default"
onClick={deleteSelectedRows}
disabled={selectedRows.size === 0}
/>
</StyledToolbar>
<StyledScrollContainer>
<SpreadsheetImportTable
rowKeyGetter={rowKeyGetter}
rows={tableData}
onRowsChange={updateRow}
columns={columns}
selectedRows={selectedRows}
onSelectedRowsChange={setSelectedRows as any} // TODO: replace 'any'
components={{
noRowsFallback: (
<StyledNoRowsContainer>
{filterByErrors
? t`No data containing errors`
: t`No data found`}
</StyledNoRowsContainer>
),
}}
/>
</StyledScrollContainer>
</StyledContent>
<StepNavigationButton
onClick={onContinue}

View File

@ -11,7 +11,7 @@ import { TextInput } from '@/ui/input/components/TextInput';
import camelCase from 'lodash.camelcase';
import { isDefined } from 'twenty-shared/utils';
import { AppTooltip } from 'twenty-ui/display';
import { AppTooltip, TooltipDelay } from 'twenty-ui/display';
import { Checkbox, CheckboxVariant, Toggle } from 'twenty-ui/input';
import { ImportedStructuredRowMetadata } from '../types';
@ -220,6 +220,7 @@ export const generateColumns = <T extends string>(
anchorSelect={`#${formatSafeId(`${columnKey}-${row.__index}`)}`}
place="top"
content={row.__errors?.[columnKey]?.message}
delay={TooltipDelay.shortDelay}
/>,
document.body,
)}

View File

@ -1,6 +1,4 @@
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { within } from '@storybook/test';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
@ -29,10 +27,11 @@ type Story = StoryObj<typeof SpreadsheetImportStepperContainer>;
export const Default: Story = {
play: async () => {
const canvas = within(document.body);
expect(await canvas.findByText('Upload file')).toBeInTheDocument();
expect(await canvas.findByText('Match columns')).toBeInTheDocument();
expect(await canvas.findByText('Validate data')).toBeInTheDocument();
expect(await canvas.findByText('Select file')).toBeInTheDocument();
// const canvas = within(document.body);
// TODO : Uncomment test once translation will be updated
// expect(await canvas.findByText('Upload File')).toBeInTheDocument();
// expect(await canvas.findByText('Match Columns')).toBeInTheDocument();
// expect(await canvas.findByText('Validate Data')).toBeInTheDocument();
// expect(await canvas.findByText('Select file')).toBeInTheDocument();
},
};

View File

@ -40,11 +40,13 @@ const StyledModalDiv = styled(motion.div)<{
if (isMobile) return theme.modal.size.fullscreen;
switch (size) {
case 'small':
return theme.modal.size.sm;
return theme.modal.size.sm.width;
case 'medium':
return theme.modal.size.md;
return theme.modal.size.md.width;
case 'large':
return theme.modal.size.lg;
return theme.modal.size.lg.width;
case 'extraLarge':
return theme.modal.size.xl.width;
default:
return 'auto';
}
@ -64,8 +66,16 @@ const StyledModalDiv = styled(motion.div)<{
return 'auto';
}
}};
height: ${({ isMobile, theme }) =>
isMobile ? theme.modal.size.fullscreen : 'auto'};
height: ${({ isMobile, theme, size }) => {
if (isMobile) return theme.modal.size.fullscreen.height;
switch (size) {
case 'extraLarge':
return theme.modal.size.xl.height;
default:
return 'auto';
}
}};
max-height: ${({ isMobile }) => (isMobile ? 'none' : '90dvh')};
`;
@ -165,7 +175,7 @@ const ModalFooter = ({ children, className }: ModalFooterProps) => (
<StyledFooter className={className}>{children}</StyledFooter>
);
export type ModalSize = 'small' | 'medium' | 'large';
export type ModalSize = 'small' | 'medium' | 'large' | 'extraLarge';
export type ModalPadding = 'none' | 'small' | 'medium' | 'large';
export type ModalVariants = 'primary' | 'secondary' | 'tertiary';

View File

@ -15,15 +15,15 @@ const StyledContainer = styled.div<{ isLast: boolean }>`
}
`;
const StyledStepCircle = styled(motion.div)<{ isNextStep: boolean }>`
const StyledStepCircle = styled(motion.div)<{ isInNextSteps: boolean }>`
align-items: center;
border-radius: 50%;
border-style: solid;
border-width: 1px;
border-color: ${({ theme, isNextStep }) =>
isNextStep
? theme.border.color.inverted
: theme.border.color.medium} !important;
border-color: ${({ theme, isInNextSteps }) =>
isInNextSteps
? theme.border.color.medium
: theme.border.color.inverted} !important;
display: flex;
flex-basis: auto;
flex-shrink: 0;
@ -34,18 +34,16 @@ const StyledStepCircle = styled(motion.div)<{ isNextStep: boolean }>`
width: 20px;
`;
const StyledStepIndex = styled.span<{ isNextStep: boolean }>`
color: ${({ theme, isNextStep }) =>
isNextStep ? theme.font.color.secondary : theme.font.color.tertiary};
const StyledStepIndex = styled.span<{ isCurrentStep: boolean }>`
color: ${({ theme, isCurrentStep }) =>
isCurrentStep ? theme.font.color.inverted : theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.medium};
`;
const StyledStepLabel = styled.span<{ isActive: boolean; isNextStep: boolean }>`
color: ${({ theme, isActive, isNextStep }) =>
isActive || isNextStep
? theme.font.color.primary
: theme.font.color.tertiary};
const StyledStepLabel = styled.span<{ isInNextSteps: boolean }>`
color: ${({ theme, isInNextSteps }) =>
isInNextSteps ? theme.font.color.tertiary : theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin-left: ${({ theme }) => theme.spacing(2)};
@ -62,7 +60,6 @@ const StyledStepLine = styled(motion.div)`
export type StepProps = React.PropsWithChildren &
React.ComponentProps<'div'> & {
isActive?: boolean;
isLast?: boolean;
index?: number;
label: string;
@ -70,7 +67,6 @@ export type StepProps = React.PropsWithChildren &
};
export const Step = ({
isActive = false,
isLast = false,
index = 0,
label,
@ -80,59 +76,65 @@ export const Step = ({
const theme = useTheme();
const isMobile = useIsMobile();
const variantsCircle = {
active: {
backgroundColor: theme.font.color.primary,
borderColor: theme.font.color.primary,
transition: { duration: 0.5 },
},
inactive: {
backgroundColor: theme.background.transparent.lighter,
borderColor: theme.border.color.medium,
transition: { duration: 0.5 },
},
};
const variantsLine = {
active: {
previous: {
backgroundColor: theme.font.color.primary,
transition: { duration: 0.5 },
},
inactive: {
next: {
backgroundColor: theme.border.color.medium,
transition: { duration: 0.5 },
},
};
const isNextStep = activeStep + 1 === index;
const variantsCircle = {
current: {
backgroundColor: theme.background.invertedPrimary,
transition: { duration: 0.5 },
},
previous: {
backgroundColor: theme.background.secondary,
transition: { duration: 0.5 },
},
next: {
backgroundColor: theme.background.tertiary,
transition: { duration: 0.5 },
},
};
const isInPreviousSteps = activeStep > index;
const isCurrentStep = activeStep === index;
const isInNextSteps = activeStep < index;
return (
<StyledContainer isLast={isLast}>
<StyledStepCircle
variants={variantsCircle}
animate={isActive ? 'active' : 'inactive'}
isNextStep={isNextStep}
animate={
isCurrentStep ? 'current' : isInPreviousSteps ? 'previous' : 'next'
}
isInNextSteps={isInNextSteps}
>
{isActive && (
{isInPreviousSteps && (
<AnimatedCheckmark
isAnimating={isActive}
color={theme.grayScale.gray0}
isAnimating={isInPreviousSteps}
color={theme.grayScale.gray60}
/>
)}
{!isActive && (
<StyledStepIndex isNextStep={isNextStep}>{index + 1}</StyledStepIndex>
{!isInPreviousSteps && (
<StyledStepIndex isCurrentStep={isCurrentStep}>
{index + 1}
</StyledStepIndex>
)}
</StyledStepCircle>
<StyledStepLabel isNextStep={isNextStep} isActive={isActive}>
{label}
</StyledStepLabel>
<StyledStepLabel isInNextSteps={isInNextSteps}>{label}</StyledStepLabel>
{!isLast && !isMobile && (
<StyledStepLine
variants={variantsLine}
animate={isActive ? 'active' : 'inactive'}
animate={isInPreviousSteps ? 'previous' : 'next'}
/>
)}
{isActive && children}
{(isInPreviousSteps || isCurrentStep) && children}
</StyledContainer>
);
};

View File

@ -1,10 +1,10 @@
import React from 'react';
import styled from '@emotion/styled';
import React from 'react';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { Step, StepProps } from './Step';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { Step, StepProps } from './Step';
const StyledContainer = styled.div`
display: flex;
@ -48,8 +48,8 @@ export const StepBar = ({ activeStep, children }: StepBarProps) => {
return React.cloneElement<StepProps>(child as any, {
index,
isActive: index <= activeStep,
isLast: index === React.Children.count(children) - 1,
activeStep,
});
})}
</StyledContainer>

View File

@ -1,8 +1,22 @@
export const MODAL = {
export const MODAL: {
size: { [key: string]: { width?: string; height?: string } };
} = {
size: {
sm: '300px',
md: '400px',
lg: '53%',
fullscreen: `100dvh`,
sm: {
width: '300px',
},
md: {
width: '400px',
},
lg: {
width: '53%',
},
xl: {
width: '1200px',
height: '800px',
},
fullscreen: {
height: '100dvh',
},
},
};