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:
+23
-20
@@ -4,17 +4,22 @@ import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
|
||||
import { Modal, type ModalVariants } from '@/ui/layout/modal/components/Modal';
|
||||
import { ModalStatefulWrapper } from '@/ui/layout/modal/components/ModalStatefulWrapper';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { H1Title, H1TitleFontColor } from 'twenty-ui/display';
|
||||
import { Button, type ButtonAccent } from 'twenty-ui/input';
|
||||
import { Section, SectionAlignment, SectionFontColor } from 'twenty-ui/layout';
|
||||
import {
|
||||
Section,
|
||||
SectionAlignment,
|
||||
SectionFontColor,
|
||||
type ModalOverlay,
|
||||
} from 'twenty-ui/layout';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
export type ConfirmationModalProps = {
|
||||
modalId: string;
|
||||
modalInstanceId: string;
|
||||
title: string;
|
||||
loading?: boolean;
|
||||
subtitle: ReactNode;
|
||||
@@ -25,18 +30,11 @@ export type ConfirmationModalProps = {
|
||||
confirmationValue?: string;
|
||||
confirmButtonAccent?: ButtonAccent;
|
||||
AdditionalButtons?: React.ReactNode;
|
||||
modalVariant?: ModalVariants;
|
||||
overlay?: ModalOverlay;
|
||||
};
|
||||
|
||||
const StyledConfirmationModal = styled(Modal)`
|
||||
border-radius: ${themeCssVariables.spacing[1]};
|
||||
width: calc(400px - ${themeCssVariables.spacing[32]});
|
||||
height: auto;
|
||||
`;
|
||||
|
||||
export const StyledCenteredButton = styled(Button)`
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
margin-top: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
@@ -62,7 +60,7 @@ export const StyledConfirmationButton = styled(StyledCenteredButton)`
|
||||
const defaultConfirmButtonText = msg`Confirm`;
|
||||
|
||||
export const ConfirmationModal = ({
|
||||
modalId,
|
||||
modalInstanceId,
|
||||
title,
|
||||
loading,
|
||||
subtitle,
|
||||
@@ -73,7 +71,7 @@ export const ConfirmationModal = ({
|
||||
confirmationPlaceholder,
|
||||
confirmButtonAccent = 'danger',
|
||||
AdditionalButtons,
|
||||
modalVariant = 'primary',
|
||||
overlay = 'dark',
|
||||
}: ConfirmationModalProps) => {
|
||||
const { i18n, t } = useLingui();
|
||||
const translatedConfirmButtonText =
|
||||
@@ -97,12 +95,12 @@ export const ConfirmationModal = ({
|
||||
const { closeModal } = useModal();
|
||||
|
||||
const handleConfirmClick = () => {
|
||||
closeModal(modalId);
|
||||
closeModal(modalInstanceId);
|
||||
onConfirmClick();
|
||||
};
|
||||
|
||||
const handleCancelClick = () => {
|
||||
closeModal(modalId);
|
||||
closeModal(modalInstanceId);
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
@@ -113,17 +111,20 @@ export const ConfirmationModal = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledConfirmationModal
|
||||
modalId={modalId}
|
||||
<ModalStatefulWrapper
|
||||
modalInstanceId={modalInstanceId}
|
||||
onClose={() => {
|
||||
onClose?.();
|
||||
}}
|
||||
onEnter={handleEnter}
|
||||
isClosable={true}
|
||||
padding="large"
|
||||
modalVariant={modalVariant}
|
||||
overlay={overlay}
|
||||
dataGloballyPreventClickOutside
|
||||
ignoreContainer
|
||||
renderInDocumentBody
|
||||
smallBorderRadius
|
||||
narrowWidth
|
||||
autoHeight
|
||||
>
|
||||
<StyledCenteredTitle>
|
||||
<H1Title title={title} fontColor={H1TitleFontColor.Primary} />
|
||||
@@ -153,6 +154,7 @@ export const ConfirmationModal = ({
|
||||
variant="secondary"
|
||||
title={t`Cancel`}
|
||||
fullWidth
|
||||
justify="center"
|
||||
dataTestId="confirmation-modal-cancel-button"
|
||||
/>
|
||||
|
||||
@@ -165,8 +167,9 @@ export const ConfirmationModal = ({
|
||||
title={translatedConfirmButtonText}
|
||||
disabled={!isValidValue || loading}
|
||||
fullWidth
|
||||
justify="center"
|
||||
dataTestId="confirmation-modal-confirm-button"
|
||||
/>
|
||||
</StyledConfirmationModal>
|
||||
</ModalStatefulWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user