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:
Charles Bochet
2026-03-04 13:22:31 +01:00
committed by GitHub
parent 995793c0ac
commit 3b2bf39565
99 changed files with 4283 additions and 3688 deletions
@@ -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>
);
};