Refactor modal (#18377)
## Summary - Move Modal UI components (`Modal`, `ModalContent`, `ModalHeader`, `ModalFooter`, `ModalBackdrop`) from `twenty-front` to `twenty-ui` as stateless, reusable components - Create `ModalStatefulWrapper` in `twenty-front` that connects Jotai state (`isModalOpenedComponentState`) to the stateless `Modal` via an `isOpen` prop - Rename `modalVariant` prop to `overlay` with clearer values: `'dark'` (default), `'light'` (in-container), `'transparent'` (invisible panel). Remove unused `'medium'` overlay - Rename `modalId` to `modalInstanceId` across the entire modal zone (~30 consumer files) - Extract `ModalProps` to its own file in `twenty-ui/types/ModalProps.ts`; extract `ModalStatefulWrapperProps` to its own file using `Pick<ModalProps, ...>` for shared props - Extract `ModalBackdrop` to its own file and export from `twenty-ui`; use it in `UserOrMetadataLoader` instead of a local styled component - Use `ModalFooter` in `StepNavigationButton` and `ModalHeader` in `SpreadsheetImportStepperContainer` instead of duplicated `styled.div` definitions - Remove unused `onClose` prop from stateless `Modal`; fix `typeof document` guard in `ModalStatefulWrapper` - Split shared types into individual files: `ModalSize.ts`, `ModalPadding.ts`, `ModalOverlay.ts` - Extract wyw profiling instrumentation from `vite.config.ts` into reusable `createWywProfilingPlugin` with parametrized threshold and improved logging - Delete old `Modal.tsx`, `Modal.styles.ts`, `ModalContent.tsx`, `ModalHeader.tsx`, `ModalFooter.tsx` from `twenty-front` - Add comprehensive Storybook stories in `twenty-ui` covering Default, Confirmation, Small, ExtraLarge, Closed, and Interactive variants
This commit is contained in:
+3
-10
@@ -3,19 +3,12 @@ import { StepNavigationButton } from '@/spreadsheet-import/components/StepNaviga
|
||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||
import { spreadsheetImportCreatedRecordsProgressState } from '@/spreadsheet-import/states/spreadsheetImportCreatedRecordsProgressState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { ModalContent } from 'twenty-ui/layout';
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Loader } from 'twenty-ui/feedback';
|
||||
|
||||
const StyledContent = styled(Modal.Content)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0px;
|
||||
`;
|
||||
|
||||
const StyledHeader = styled.span`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
font-size: ${themeCssVariables.font.size.md};
|
||||
@@ -50,11 +43,11 @@ export const ImportDataStep = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContent>
|
||||
<ModalContent noPadding isVerticallyCentered isHorizontallyCentered>
|
||||
<StyledHeader>{t`Importing Data ...`}</StyledHeader>
|
||||
<StyledDescription>{t`${formattedCreatedRecordsProgress} out of ${formattedRecordsToImportCount} records imported.`}</StyledDescription>
|
||||
<Loader />
|
||||
</StyledContent>
|
||||
</ModalContent>
|
||||
<StepNavigationButton onBack={onClose} backTitle={t`Cancel`} />
|
||||
</>
|
||||
);
|
||||
|
||||
+3
-8
@@ -14,7 +14,7 @@ import { setIgnoreColumn } from '@/spreadsheet-import/utils/setIgnoreColumn';
|
||||
import { setSubColumn } from '@/spreadsheet-import/utils/setSubColumn';
|
||||
import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager';
|
||||
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { ModalContent } from 'twenty-ui/layout';
|
||||
|
||||
import { DO_NOT_IMPORT_OPTION_KEY } from '@/spreadsheet-import/constants/DoNotImportOptionKey';
|
||||
import { ColumnGrid } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/ColumnGrid';
|
||||
@@ -33,11 +33,6 @@ import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledContent = styled(Modal.Content)`
|
||||
align-items: center;
|
||||
padding: 0px;
|
||||
`;
|
||||
|
||||
const StyledColumnsContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@@ -276,7 +271,7 @@ export const MatchColumnsStep = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContent>
|
||||
<ModalContent noPadding isVerticallyCentered>
|
||||
<ScrollWrapper componentInstanceId="scroll-wrapper-modal-content">
|
||||
<ColumnGrid
|
||||
columns={columns}
|
||||
@@ -304,7 +299,7 @@ export const MatchColumnsStep = ({
|
||||
)}
|
||||
/>
|
||||
</ScrollWrapper>
|
||||
</StyledContent>
|
||||
</ModalContent>
|
||||
<StepNavigationButton
|
||||
onContinue={handleOnContinue}
|
||||
isLoading={isLoading}
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ import { Heading } from '@/spreadsheet-import/components/Heading';
|
||||
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
|
||||
import { type ImportedRow } from '@/spreadsheet-import/types';
|
||||
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { ModalContent } from 'twenty-ui/layout';
|
||||
|
||||
import { useComputeColumnSuggestionsAndAutoMatch } from '@/spreadsheet-import/hooks/useComputeColumnSuggestionsAndAutoMatch';
|
||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||
@@ -104,7 +104,7 @@ export const SelectHeaderStep = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Content>
|
||||
<ModalContent>
|
||||
<StyledHeading title={t`Select header row`} />
|
||||
<StyledTableContainer>
|
||||
<SelectHeaderTable
|
||||
@@ -113,7 +113,7 @@ export const SelectHeaderStep = ({
|
||||
setSelectedRowIndexes={setSelectedRowIndexes}
|
||||
/>
|
||||
</StyledTableContainer>
|
||||
</Modal.Content>
|
||||
</ModalContent>
|
||||
<StepNavigationButton
|
||||
onContinue={handleOnContinue}
|
||||
onBack={onBack}
|
||||
|
||||
+3
-11
@@ -9,20 +9,12 @@ import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/Spre
|
||||
import { exceedsMaxRecords } from '@/spreadsheet-import/utils/exceedsMaxRecords';
|
||||
import { mapWorkbook } from '@/spreadsheet-import/utils/mapWorkbook';
|
||||
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { ModalContent } from 'twenty-ui/layout';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Radio, RadioGroup } from 'twenty-ui/input';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { type WorkBook } from 'xlsx-ugnis';
|
||||
|
||||
const StyledContent = styled(Modal.Content)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: ${themeCssVariables.spacing[8]};
|
||||
`;
|
||||
|
||||
const StyledHeading = styled(Heading)`
|
||||
display: flex;
|
||||
`;
|
||||
@@ -111,7 +103,7 @@ export const SelectSheetStep = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContent>
|
||||
<ModalContent isVerticallyCentered isHorizontallyCentered gap={8}>
|
||||
<StyledHeading title={t`Select the sheet to use`} />
|
||||
<StyledRadioContainer>
|
||||
<RadioGroup onValueChange={(value) => setValue(value)} value={value}>
|
||||
@@ -124,7 +116,7 @@ export const SelectSheetStep = ({
|
||||
))}
|
||||
</RadioGroup>
|
||||
</StyledRadioContainer>
|
||||
</StyledContent>
|
||||
</ModalContent>
|
||||
<StepNavigationButton
|
||||
onContinue={() => handleOnContinue(value)}
|
||||
onBack={onBack}
|
||||
|
||||
+3
-10
@@ -1,10 +1,9 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { useCallback, useContext, useState } from 'react';
|
||||
import { ThemeContext } from 'twenty-ui/theme';
|
||||
|
||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { ModalContent } from 'twenty-ui/layout';
|
||||
|
||||
import { ImportDataStep } from '@/spreadsheet-import/steps/components/ImportDataStep';
|
||||
import { type SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
|
||||
@@ -16,12 +15,6 @@ import { SelectSheetStep } from './SelectSheetStep/SelectSheetStep';
|
||||
import { UploadStep } from './UploadStep/UploadStep';
|
||||
import { ValidationStep } from './ValidationStep/ValidationStep';
|
||||
|
||||
const StyledProgressBarContainer = styled(Modal.Content)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
type SpreadsheetImportStepperProps = {
|
||||
nextStep: () => void;
|
||||
prevStep: () => void;
|
||||
@@ -137,13 +130,13 @@ export const SpreadsheetImportStepper = ({
|
||||
case SpreadsheetImportStepType.loading:
|
||||
default:
|
||||
return (
|
||||
<StyledProgressBarContainer>
|
||||
<ModalContent isVerticallyCentered isHorizontallyCentered>
|
||||
<CircularProgressBar
|
||||
size={80}
|
||||
barWidth={8}
|
||||
barColor={theme.font.color.primary}
|
||||
/>
|
||||
</StyledProgressBarContainer>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
+8
-18
@@ -1,5 +1,3 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { useSpreadsheetImportInitialStep } from '@/spreadsheet-import/hooks/useSpreadsheetImportInitialStep';
|
||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||
|
||||
@@ -7,24 +5,12 @@ import { StepBar } from '@/ui/navigation/step-bar/components/StepBar';
|
||||
import { useStepBar } from '@/ui/navigation/step-bar/hooks/useStepBar';
|
||||
|
||||
import { spreadsheetImportDialogState } from '@/spreadsheet-import/states/spreadsheetImportDialogState';
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { ModalHeader } from 'twenty-ui/layout';
|
||||
import { SpreadsheetImportStepper } from './SpreadsheetImportStepper';
|
||||
|
||||
const StyledHeader = styled(Modal.Header)`
|
||||
background-color: ${themeCssVariables.background.secondary};
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.medium};
|
||||
padding: 0px ${themeCssVariables.spacing[30]};
|
||||
height: 60px;
|
||||
flex-shrink: 0;
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
padding-left: ${themeCssVariables.spacing[4]};
|
||||
padding-right: ${themeCssVariables.spacing[4]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SpreadsheetImportStepperContainer = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
@@ -50,7 +36,11 @@ export const SpreadsheetImportStepperContainer = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledHeader>
|
||||
<ModalHeader
|
||||
hasBorderBottom
|
||||
paddingHorizontal={30}
|
||||
backgroundColor={themeCssVariables.background.secondary}
|
||||
>
|
||||
{spreadsheetImportDialog.isStepBarVisible && (
|
||||
<StepBar activeStep={activeStep}>
|
||||
{steps.map((key) => (
|
||||
@@ -62,7 +52,7 @@ export const SpreadsheetImportStepperContainer = () => {
|
||||
))}
|
||||
</StepBar>
|
||||
)}
|
||||
</StyledHeader>
|
||||
</ModalHeader>
|
||||
<SpreadsheetImportStepper nextStep={nextStep} prevStep={prevStep} />
|
||||
</>
|
||||
);
|
||||
|
||||
+3
-9
@@ -1,10 +1,8 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { type WorkBook } from 'xlsx-ugnis';
|
||||
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { ModalContent } from 'twenty-ui/layout';
|
||||
|
||||
import { useComputeColumnSuggestionsAndAutoMatch } from '@/spreadsheet-import/hooks/useComputeColumnSuggestionsAndAutoMatch';
|
||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||
@@ -14,10 +12,6 @@ import { exceedsMaxRecords } from '@/spreadsheet-import/utils/exceedsMaxRecords'
|
||||
import { mapWorkbook } from '@/spreadsheet-import/utils/mapWorkbook';
|
||||
import { DropZone } from './components/DropZone';
|
||||
|
||||
const StyledContent = styled(Modal.Content)`
|
||||
padding: ${themeCssVariables.spacing[6]};
|
||||
`;
|
||||
|
||||
type UploadStepProps = {
|
||||
setUploadedFile: (file: File) => void;
|
||||
setCurrentStepState: (data: any) => void;
|
||||
@@ -118,8 +112,8 @@ export const UploadStep = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledContent>
|
||||
<ModalContent contentPadding={6}>
|
||||
<DropZone onContinue={handleOnContinue} isLoading={isLoading} />
|
||||
</StyledContent>
|
||||
</ModalContent>
|
||||
);
|
||||
};
|
||||
|
||||
+53
-49
@@ -12,7 +12,7 @@ import { type SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetC
|
||||
import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType';
|
||||
import { addErrorsAndRunHooks } from '@/spreadsheet-import/utils/dataMutations';
|
||||
import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager';
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { ModalContent } from 'twenty-ui/layout';
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
@@ -31,8 +31,10 @@ import { Button, Toggle } from 'twenty-ui/input';
|
||||
import { generateColumns } from './components/columns';
|
||||
import { type ImportedStructuredRowMetadata } from './types';
|
||||
|
||||
const StyledContent = styled(Modal.Content)`
|
||||
padding: 0px;
|
||||
const StyledContentWrapper = styled.div`
|
||||
display: flex;
|
||||
flex: 1 1 0%;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
@@ -282,53 +284,55 @@ export const ValidationStep = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContent>
|
||||
{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>
|
||||
),
|
||||
}}
|
||||
<ModalContent noPadding>
|
||||
<StyledContentWrapper>
|
||||
{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>
|
||||
<StyledButton
|
||||
Icon={IconTrash}
|
||||
title={t`Remove`}
|
||||
accent="default"
|
||||
onClick={deleteSelectedRows}
|
||||
disabled={selectedRows.size === 0}
|
||||
/>
|
||||
</StyledScrollContainer>
|
||||
)}
|
||||
<StyledToolbar>
|
||||
<StyledErrorToggle>
|
||||
<Toggle
|
||||
value={filterByErrors}
|
||||
onChange={() => setFilterByErrors(!filterByErrors)}
|
||||
toggleSize="small"
|
||||
/>
|
||||
<StyledErrorToggleDescription>
|
||||
<Trans>Show only rows with errors</Trans>
|
||||
</StyledErrorToggleDescription>
|
||||
</StyledErrorToggle>
|
||||
<StyledButton
|
||||
Icon={IconTrash}
|
||||
title={t`Remove`}
|
||||
accent="default"
|
||||
onClick={deleteSelectedRows}
|
||||
disabled={selectedRows.size === 0}
|
||||
/>
|
||||
</StyledToolbar>
|
||||
</StyledContent>
|
||||
</StyledToolbar>
|
||||
</StyledContentWrapper>
|
||||
</ModalContent>
|
||||
<StepNavigationButton
|
||||
onContinue={onContinue}
|
||||
onBack={onBack}
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ export const Default = () => (
|
||||
>
|
||||
<ReactSpreadsheetImportContextProvider values={mockRsiValues}>
|
||||
<SpreadSheetImportModalWrapper
|
||||
modalId="match-columns-step"
|
||||
modalInstanceId="match-columns-step"
|
||||
onClose={() => null}
|
||||
>
|
||||
<MatchColumnsStep
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ export const Default = () => (
|
||||
>
|
||||
<ReactSpreadsheetImportContextProvider values={mockRsiValues}>
|
||||
<SpreadSheetImportModalWrapper
|
||||
modalId="select-header-step"
|
||||
modalInstanceId="select-header-step"
|
||||
onClose={() => null}
|
||||
>
|
||||
<SelectHeaderStep
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ export const Default = () => (
|
||||
>
|
||||
<ReactSpreadsheetImportContextProvider values={mockRsiValues}>
|
||||
<SpreadSheetImportModalWrapper
|
||||
modalId="select-sheet-step"
|
||||
modalInstanceId="select-sheet-step"
|
||||
onClose={() => null}
|
||||
>
|
||||
<SelectSheetStep
|
||||
|
||||
+4
-1
@@ -46,7 +46,10 @@ export const Default = () => (
|
||||
value={{ instanceId: 'dialog-manager' }}
|
||||
>
|
||||
<ReactSpreadsheetImportContextProvider values={mockRsiValues}>
|
||||
<SpreadSheetImportModalWrapper modalId="upload-step" onClose={() => null}>
|
||||
<SpreadSheetImportModalWrapper
|
||||
modalInstanceId="upload-step"
|
||||
onClose={() => null}
|
||||
>
|
||||
<UploadStep
|
||||
setUploadedFile={() => null}
|
||||
setCurrentStepState={() => null}
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ export const Default = () => (
|
||||
>
|
||||
<ReactSpreadsheetImportContextProvider values={mockRsiValues}>
|
||||
<SpreadSheetImportModalWrapper
|
||||
modalId="validation-step"
|
||||
modalInstanceId="validation-step"
|
||||
onClose={() => null}
|
||||
>
|
||||
<ValidationStep
|
||||
|
||||
Reference in New Issue
Block a user