mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-23 14:03:35 +03:00
Fixed SignInUp Modal misalignment for devices smaller than 400px width (#6386)
Hi @Bonapara, Issue #6385 I encountered an issue with the Modal component where its width was fixed at 400px. While the container housing the Modal adjusted its size based on the screen width, the Modal itself remained at 400px regardless of the screen size. I have implemented a change to address this problem. Could you please review the changes and let me know your thoughts? Thank you! https://github.com/user-attachments/assets/8358aacb-d6c3-440e-895e-7abc4f8a3534 --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
parent
5b7933a6ab
commit
c836bbbfc2
@ -1,17 +1,16 @@
|
|||||||
import React from 'react';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { ModalLayout } from '@/ui/layout/modal/components/ModalLayout';
|
const StyledContent = styled(Modal.Content)`
|
||||||
|
|
||||||
const StyledContent = styled(ModalLayout.Content)`
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: calc(400px - ${({ theme }) => theme.spacing(10 * 2)});
|
justify-content: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type AuthModalProps = { children: React.ReactNode };
|
type AuthModalProps = { children: React.ReactNode };
|
||||||
|
|
||||||
export const AuthModal = ({ children }: AuthModalProps) => (
|
export const AuthModal = ({ children }: AuthModalProps) => (
|
||||||
<ModalLayout padding={'none'}>
|
<Modal padding={'none'} modalVariant="primary">
|
||||||
<StyledContent>{children}</StyledContent>
|
<StyledContent>{children}</StyledContent>
|
||||||
</ModalLayout>
|
</Modal>
|
||||||
);
|
);
|
@ -2,8 +2,8 @@ import styled from '@emotion/styled';
|
|||||||
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
||||||
|
|
||||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
|
||||||
|
|
||||||
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
import { ModalCloseButton } from './ModalCloseButton';
|
import { ModalCloseButton } from './ModalCloseButton';
|
||||||
|
|
||||||
const StyledModal = styled(Modal)`
|
const StyledModal = styled(Modal)`
|
||||||
@ -16,7 +16,7 @@ const StyledModal = styled(Modal)`
|
|||||||
min-width: auto;
|
min-width: auto;
|
||||||
min-height: auto;
|
min-height: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 80%;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -40,11 +40,15 @@ export const ModalWrapper = ({
|
|||||||
const { rtl } = useSpreadsheetImportInternal();
|
const { rtl } = useSpreadsheetImportInternal();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledModal isOpen={isOpen} size="large">
|
<>
|
||||||
<StyledRtlLtr dir={rtl ? 'rtl' : 'ltr'}>
|
{isOpen && (
|
||||||
<ModalCloseButton onClose={onClose} />
|
<StyledModal size="large" onClose={onClose} isClosable={true}>
|
||||||
{children}
|
<StyledRtlLtr dir={rtl ? 'rtl' : 'ltr'}>
|
||||||
</StyledRtlLtr>
|
<ModalCloseButton onClose={onClose} />
|
||||||
</StyledModal>
|
{children}
|
||||||
|
</StyledRtlLtr>
|
||||||
|
</StyledModal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
import { CircularProgressBar } from '@/ui/feedback/progress-bar/components/CircularProgressBar';
|
import { CircularProgressBar } from '@/ui/feedback/progress-bar/components/CircularProgressBar';
|
||||||
import { MainButton } from '@/ui/input/button/components/MainButton';
|
import { MainButton } from '@/ui/input/button/components/MainButton';
|
||||||
|
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
|
|
||||||
|
@ -14,8 +14,8 @@ import { setSubColumn } from '@/spreadsheet-import/utils/setSubColumn';
|
|||||||
import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager';
|
import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
|
||||||
|
|
||||||
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
import { ColumnGrid } from './components/ColumnGrid';
|
import { ColumnGrid } from './components/ColumnGrid';
|
||||||
import { TemplateColumn } from './components/TemplateColumn';
|
import { TemplateColumn } from './components/TemplateColumn';
|
||||||
import { UserTableColumn } from './components/UserTableColumn';
|
import { UserTableColumn } from './components/UserTableColumn';
|
||||||
|
@ -4,8 +4,8 @@ import { useCallback, useState } from 'react';
|
|||||||
import { Heading } from '@/spreadsheet-import/components/Heading';
|
import { Heading } from '@/spreadsheet-import/components/Heading';
|
||||||
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
|
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
|
||||||
import { ImportedRow } from '@/spreadsheet-import/types';
|
import { ImportedRow } from '@/spreadsheet-import/types';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
|
||||||
|
|
||||||
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
import { SelectHeaderTable } from './components/SelectHeaderTable';
|
import { SelectHeaderTable } from './components/SelectHeaderTable';
|
||||||
|
|
||||||
const StyledHeading = styled(Heading)`
|
const StyledHeading = styled(Heading)`
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { useCallback, useState } from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { Heading } from '@/spreadsheet-import/components/Heading';
|
import { Heading } from '@/spreadsheet-import/components/Heading';
|
||||||
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
|
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
|
||||||
import { Radio } from '@/ui/input/components/Radio';
|
import { Radio } from '@/ui/input/components/Radio';
|
||||||
import { RadioGroup } from '@/ui/input/components/RadioGroup';
|
import { RadioGroup } from '@/ui/input/components/RadioGroup';
|
||||||
|
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
|
|
||||||
const StyledContent = styled(Modal.Content)`
|
const StyledContent = styled(Modal.Content)`
|
||||||
|
@ -3,10 +3,11 @@ import { MOBILE_VIEWPORT } from 'twenty-ui';
|
|||||||
|
|
||||||
import { useSpreadsheetImportInitialStep } from '@/spreadsheet-import/hooks/useSpreadsheetImportInitialStep';
|
import { useSpreadsheetImportInitialStep } from '@/spreadsheet-import/hooks/useSpreadsheetImportInitialStep';
|
||||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
|
||||||
import { StepBar } from '@/ui/navigation/step-bar/components/StepBar';
|
import { StepBar } from '@/ui/navigation/step-bar/components/StepBar';
|
||||||
import { useStepBar } from '@/ui/navigation/step-bar/hooks/useStepBar';
|
import { useStepBar } from '@/ui/navigation/step-bar/hooks/useStepBar';
|
||||||
|
|
||||||
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
import { UploadFlow } from './UploadFlow';
|
import { UploadFlow } from './UploadFlow';
|
||||||
|
|
||||||
const StyledHeader = styled(Modal.Header)`
|
const StyledHeader = styled(Modal.Header)`
|
||||||
|
@ -10,8 +10,8 @@ import { mapWorkbook } from '@/spreadsheet-import/utils/mapWorkbook';
|
|||||||
import { CircularProgressBar } from '@/ui/feedback/progress-bar/components/CircularProgressBar';
|
import { CircularProgressBar } from '@/ui/feedback/progress-bar/components/CircularProgressBar';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
|
||||||
|
|
||||||
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
import { Columns, MatchColumnsStep } from './MatchColumnsStep/MatchColumnsStep';
|
import { Columns, MatchColumnsStep } from './MatchColumnsStep/MatchColumnsStep';
|
||||||
import { SelectHeaderStep } from './SelectHeaderStep/SelectHeaderStep';
|
import { SelectHeaderStep } from './SelectHeaderStep/SelectHeaderStep';
|
||||||
import { SelectSheetStep } from './SelectSheetStep/SelectSheetStep';
|
import { SelectSheetStep } from './SelectSheetStep/SelectSheetStep';
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { useCallback, useState } from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
import { WorkBook } from 'xlsx-ugnis';
|
import { WorkBook } from 'xlsx-ugnis';
|
||||||
|
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
|
|
||||||
import { DropZone } from './components/DropZone';
|
import { DropZone } from './components/DropZone';
|
||||||
|
|
||||||
const StyledContent = styled(Modal.Content)`
|
const StyledContent = styled(Modal.Content)`
|
||||||
|
@ -20,9 +20,9 @@ import { addErrorsAndRunHooks } from '@/spreadsheet-import/utils/dataMutations';
|
|||||||
import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager';
|
import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager';
|
||||||
import { Button } from '@/ui/input/button/components/Button';
|
import { Button } from '@/ui/input/button/components/Button';
|
||||||
import { Toggle } from '@/ui/input/components/Toggle';
|
import { Toggle } from '@/ui/input/components/Toggle';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
import { generateColumns } from './components/columns';
|
import { generateColumns } from './components/columns';
|
||||||
import { ImportedStructuredRowMetadata } from './types';
|
import { ImportedStructuredRowMetadata } from './types';
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import { DialogHotkeyScope } from '../types/DialogHotkeyScope';
|
|||||||
|
|
||||||
const StyledDialogOverlay = styled(motion.div)`
|
const StyledDialogOverlay = styled(motion.div)`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: ${({ theme }) => theme.background.overlay};
|
background: ${({ theme }) => theme.background.overlayPrimary};
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100dvh;
|
height: 100dvh;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -6,6 +6,7 @@ import { useDebouncedCallback } from 'use-debounce';
|
|||||||
|
|
||||||
import { Button, ButtonAccent } from '@/ui/input/button/components/Button';
|
import { Button, ButtonAccent } from '@/ui/input/button/components/Button';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
|
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
import {
|
import {
|
||||||
Section,
|
Section,
|
||||||
@ -27,8 +28,8 @@ export type ConfirmationModalProps = {
|
|||||||
|
|
||||||
const StyledConfirmationModal = styled(Modal)`
|
const StyledConfirmationModal = styled(Modal)`
|
||||||
border-radius: ${({ theme }) => theme.spacing(1)};
|
border-radius: ${({ theme }) => theme.spacing(1)};
|
||||||
padding: ${({ theme }) => theme.spacing(6)};
|
|
||||||
width: calc(400px - ${({ theme }) => theme.spacing(32)});
|
width: calc(400px - ${({ theme }) => theme.spacing(32)});
|
||||||
|
height: auto;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledCenteredButton = styled(Button)`
|
const StyledCenteredButton = styled(Button)`
|
||||||
@ -85,54 +86,57 @@ export const ConfirmationModal = ({
|
|||||||
return (
|
return (
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
<LayoutGroup>
|
<LayoutGroup>
|
||||||
<StyledConfirmationModal
|
{isOpen && (
|
||||||
isOpen={isOpen}
|
<StyledConfirmationModal
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}
|
}
|
||||||
}}
|
|
||||||
onEnter={onConfirmClick}
|
|
||||||
>
|
|
||||||
<StyledCenteredTitle>
|
|
||||||
<H1Title title={title} fontColor={H1TitleFontColor.Primary} />
|
|
||||||
</StyledCenteredTitle>
|
|
||||||
<StyledSection
|
|
||||||
alignment={SectionAlignment.Center}
|
|
||||||
fontColor={SectionFontColor.Primary}
|
|
||||||
>
|
|
||||||
{subtitle}
|
|
||||||
</StyledSection>
|
|
||||||
{confirmationValue && (
|
|
||||||
<Section>
|
|
||||||
<TextInput
|
|
||||||
value={inputConfirmationValue}
|
|
||||||
onChange={handleInputConfimrationValueChange}
|
|
||||||
placeholder={confirmationPlaceholder}
|
|
||||||
fullWidth
|
|
||||||
key={'input-' + confirmationValue}
|
|
||||||
/>
|
|
||||||
</Section>
|
|
||||||
)}
|
|
||||||
<StyledCenteredButton
|
|
||||||
onClick={() => setIsOpen(false)}
|
|
||||||
variant="secondary"
|
|
||||||
title="Cancel"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<StyledCenteredButton
|
|
||||||
onClick={async () => {
|
|
||||||
await onConfirmClick();
|
|
||||||
setIsOpen(false);
|
|
||||||
}}
|
}}
|
||||||
variant="secondary"
|
onEnter={onConfirmClick}
|
||||||
accent={confirmButtonAccent}
|
isClosable={true}
|
||||||
title={deleteButtonText}
|
padding="large"
|
||||||
disabled={!isValidValue}
|
>
|
||||||
fullWidth
|
<StyledCenteredTitle>
|
||||||
dataTestId="confirmation-modal-confirm-button"
|
<H1Title title={title} fontColor={H1TitleFontColor.Primary} />
|
||||||
/>
|
</StyledCenteredTitle>
|
||||||
</StyledConfirmationModal>
|
<StyledSection
|
||||||
|
alignment={SectionAlignment.Center}
|
||||||
|
fontColor={SectionFontColor.Primary}
|
||||||
|
>
|
||||||
|
{subtitle}
|
||||||
|
</StyledSection>
|
||||||
|
{confirmationValue && (
|
||||||
|
<Section>
|
||||||
|
<TextInput
|
||||||
|
value={inputConfirmationValue}
|
||||||
|
onChange={handleInputConfimrationValueChange}
|
||||||
|
placeholder={confirmationPlaceholder}
|
||||||
|
fullWidth
|
||||||
|
key={'input-' + confirmationValue}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
<StyledCenteredButton
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
variant="secondary"
|
||||||
|
title="Cancel"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<StyledCenteredButton
|
||||||
|
onClick={async () => {
|
||||||
|
await onConfirmClick();
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
variant="secondary"
|
||||||
|
accent={confirmButtonAccent}
|
||||||
|
title={deleteButtonText}
|
||||||
|
disabled={!isValidValue}
|
||||||
|
fullWidth
|
||||||
|
dataTestId="confirmation-modal-confirm-button"
|
||||||
|
/>
|
||||||
|
</StyledConfirmationModal>
|
||||||
|
)}
|
||||||
</LayoutGroup>
|
</LayoutGroup>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
);
|
);
|
||||||
|
@ -1,46 +1,183 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
|
||||||
import { Key } from 'ts-key-enum';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ModalLayout,
|
|
||||||
ModalLayoutProps,
|
|
||||||
} from '@/ui/layout/modal/components/ModalLayout';
|
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
|
||||||
import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2';
|
import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2';
|
||||||
import { ModalHotkeyScope } from './types/ModalHotkeyScope';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
type ModalProps = ModalLayoutProps & {
|
const StyledModalDiv = styled(motion.div)<{
|
||||||
isOpen?: boolean;
|
size?: ModalSize;
|
||||||
|
padding?: ModalPadding;
|
||||||
|
isMobile: boolean;
|
||||||
|
}>`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: ${({ theme }) => theme.background.primary};
|
||||||
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
|
border-radius: ${({ theme, isMobile }) => {
|
||||||
|
if (isMobile) return `0`;
|
||||||
|
return theme.border.radius.md;
|
||||||
|
}};
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 10000; // should be higher than Backdrop's z-index
|
||||||
|
|
||||||
|
width: ${({ isMobile, size, theme }) => {
|
||||||
|
if (isMobile) return theme.modal.size.fullscreen;
|
||||||
|
switch (size) {
|
||||||
|
case 'small':
|
||||||
|
return theme.modal.size.sm;
|
||||||
|
case 'medium':
|
||||||
|
return theme.modal.size.md;
|
||||||
|
case 'large':
|
||||||
|
return theme.modal.size.lg;
|
||||||
|
default:
|
||||||
|
return 'auto';
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
padding: ${({ padding, theme }) => {
|
||||||
|
switch (padding) {
|
||||||
|
case 'none':
|
||||||
|
return theme.spacing(0);
|
||||||
|
case 'small':
|
||||||
|
return theme.spacing(2);
|
||||||
|
case 'medium':
|
||||||
|
return theme.spacing(4);
|
||||||
|
case 'large':
|
||||||
|
return theme.spacing(6);
|
||||||
|
default:
|
||||||
|
return 'auto';
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
height: ${({ isMobile, theme }) =>
|
||||||
|
isMobile ? theme.modal.size.fullscreen : 'auto'};
|
||||||
|
max-height: ${({ isMobile }) => (isMobile ? 'none' : '90dvh')};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledHeader = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 60px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: ${({ theme }) => theme.spacing(5)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledContent = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: ${({ theme }) => theme.spacing(10)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledFooter = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 60px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: ${({ theme }) => theme.spacing(5)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledBackDrop = styled(motion.div)<{
|
||||||
|
modalVariant: ModalVariants;
|
||||||
|
}>`
|
||||||
|
align-items: center;
|
||||||
|
background: ${({ theme, modalVariant }) =>
|
||||||
|
modalVariant === 'primary'
|
||||||
|
? theme.background.overlayPrimary
|
||||||
|
: theme.background.overlaySecondary};
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
left: 0;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 9999;
|
||||||
|
user-select: none;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type ModalHeaderProps = React.PropsWithChildren & {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModalHeader = ({ children, className }: ModalHeaderProps) => (
|
||||||
|
<StyledHeader className={className}>{children}</StyledHeader>
|
||||||
|
);
|
||||||
|
|
||||||
|
type ModalContentProps = React.PropsWithChildren & {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModalContent = ({ children, className }: ModalContentProps) => (
|
||||||
|
<StyledContent className={className}>{children}</StyledContent>
|
||||||
|
);
|
||||||
|
|
||||||
|
type ModalFooterProps = React.PropsWithChildren & {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModalFooter = ({ children, className }: ModalFooterProps) => (
|
||||||
|
<StyledFooter className={className}>{children}</StyledFooter>
|
||||||
|
);
|
||||||
|
|
||||||
|
export type ModalSize = 'small' | 'medium' | 'large';
|
||||||
|
export type ModalPadding = 'none' | 'small' | 'medium' | 'large';
|
||||||
|
export type ModalVariants = 'primary' | 'secondary';
|
||||||
|
|
||||||
|
export type ModalProps = React.PropsWithChildren & {
|
||||||
|
size?: ModalSize;
|
||||||
|
padding?: ModalPadding;
|
||||||
|
className?: string;
|
||||||
hotkeyScope?: ModalHotkeyScope;
|
hotkeyScope?: ModalHotkeyScope;
|
||||||
onClose?: () => void;
|
|
||||||
onEnter?: () => void;
|
onEnter?: () => void;
|
||||||
|
modalVariant?: ModalVariants;
|
||||||
|
} & (
|
||||||
|
| { isClosable: true; onClose: () => void }
|
||||||
|
| { isClosable?: false; onClose?: never }
|
||||||
|
);
|
||||||
|
|
||||||
|
const modalAnimation = {
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
visible: { opacity: 1 },
|
||||||
|
exit: { opacity: 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Modal = ({
|
export const Modal = ({
|
||||||
isOpen = false,
|
|
||||||
children,
|
children,
|
||||||
onClose,
|
|
||||||
hotkeyScope = ModalHotkeyScope.Default,
|
|
||||||
onEnter,
|
|
||||||
size = 'medium',
|
size = 'medium',
|
||||||
padding = 'medium',
|
padding = 'medium',
|
||||||
className,
|
className,
|
||||||
|
hotkeyScope = ModalHotkeyScope.Default,
|
||||||
|
onEnter,
|
||||||
|
isClosable = false,
|
||||||
|
onClose,
|
||||||
|
modalVariant = 'primary',
|
||||||
}: ModalProps) => {
|
}: ModalProps) => {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
goBackToPreviousHotkeyScope,
|
goBackToPreviousHotkeyScope,
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
} = usePreviousHotkeyScope();
|
} = usePreviousHotkeyScope();
|
||||||
|
|
||||||
useScopedHotkeys(
|
useEffect(() => {
|
||||||
[Key.Escape],
|
setHotkeyScopeAndMemorizePreviousScope(hotkeyScope);
|
||||||
() => {
|
return () => {
|
||||||
onClose?.();
|
goBackToPreviousHotkeyScope();
|
||||||
},
|
};
|
||||||
|
}, [
|
||||||
hotkeyScope,
|
hotkeyScope,
|
||||||
[onClose],
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
);
|
goBackToPreviousHotkeyScope,
|
||||||
|
]);
|
||||||
|
|
||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
[Key.Enter],
|
[Key.Enter],
|
||||||
@ -50,41 +187,53 @@ export const Modal = ({
|
|||||||
hotkeyScope,
|
hotkeyScope,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useScopedHotkeys(
|
||||||
if (isOpen) {
|
[Key.Escape],
|
||||||
setHotkeyScopeAndMemorizePreviousScope(hotkeyScope);
|
() => {
|
||||||
} else {
|
if (isClosable && onClose !== undefined) {
|
||||||
goBackToPreviousHotkeyScope();
|
onClose();
|
||||||
}
|
}
|
||||||
}, [
|
},
|
||||||
goBackToPreviousHotkeyScope,
|
|
||||||
hotkeyScope,
|
hotkeyScope,
|
||||||
isOpen,
|
);
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useListenClickOutsideV2({
|
useListenClickOutsideV2({
|
||||||
refs: [modalRef],
|
refs: [modalRef],
|
||||||
listenerId: 'MODAL_CLICK_OUTSIDE_LISTENER_ID',
|
listenerId: 'MODAL_CLICK_OUTSIDE_LISTENER_ID',
|
||||||
callback: () => onClose?.(),
|
callback: () => {
|
||||||
|
if (isClosable && onClose !== undefined) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return isOpen ? (
|
const stopEventPropagation = (e: React.MouseEvent) => {
|
||||||
<ModalLayout
|
e.stopPropagation();
|
||||||
className={className}
|
};
|
||||||
modalRef={modalRef}
|
|
||||||
size={size}
|
return (
|
||||||
padding={padding}
|
<StyledBackDrop
|
||||||
|
onMouseDown={stopEventPropagation}
|
||||||
|
modalVariant={modalVariant}
|
||||||
>
|
>
|
||||||
{children}
|
<StyledModalDiv
|
||||||
</ModalLayout>
|
ref={modalRef}
|
||||||
) : (
|
size={size}
|
||||||
<></>
|
padding={padding}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
exit="exit"
|
||||||
|
layout
|
||||||
|
variants={modalAnimation}
|
||||||
|
className={className}
|
||||||
|
isMobile={isMobile}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</StyledModalDiv>
|
||||||
|
</StyledBackDrop>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Modal.Header = ModalLayout.Header;
|
Modal.Header = ModalHeader;
|
||||||
Modal.Content = ModalLayout.Content;
|
Modal.Content = ModalContent;
|
||||||
Modal.Footer = ModalLayout.Footer;
|
Modal.Footer = ModalFooter;
|
||||||
|
@ -1,173 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
|
|
||||||
const StyledModalDiv = styled(motion.div)<{
|
|
||||||
size?: ModalSize;
|
|
||||||
padding?: ModalPadding;
|
|
||||||
}>`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: ${({ theme }) => theme.background.primary};
|
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
|
||||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
|
||||||
overflow: hidden;
|
|
||||||
max-height: 90vh;
|
|
||||||
z-index: 10000; // should be higher than Backdrop's z-index
|
|
||||||
|
|
||||||
width: ${({ size, theme }) => {
|
|
||||||
switch (size) {
|
|
||||||
case 'small':
|
|
||||||
return theme.modal.size.sm;
|
|
||||||
case 'medium':
|
|
||||||
return theme.modal.size.md;
|
|
||||||
case 'large':
|
|
||||||
return theme.modal.size.lg;
|
|
||||||
default:
|
|
||||||
return 'auto';
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
|
|
||||||
padding: ${({ padding, theme }) => {
|
|
||||||
switch (padding) {
|
|
||||||
case 'none':
|
|
||||||
return theme.spacing(0);
|
|
||||||
case 'small':
|
|
||||||
return theme.spacing(2);
|
|
||||||
case 'medium':
|
|
||||||
return theme.spacing(4);
|
|
||||||
case 'large':
|
|
||||||
return theme.spacing(6);
|
|
||||||
default:
|
|
||||||
return 'auto';
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledHeader = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
height: 60px;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: ${({ theme }) => theme.spacing(5)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledContent = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex: 1 1 0%;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: ${({ theme }) => theme.spacing(10)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledFooter = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
height: 60px;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: ${({ theme }) => theme.spacing(5)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledBackDrop = styled(motion.div)`
|
|
||||||
align-items: center;
|
|
||||||
background: ${({ theme }) => theme.background.overlay};
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
left: 0;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 9999;
|
|
||||||
user-select: none;
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modal components
|
|
||||||
*/
|
|
||||||
type ModalLayoutHeaderProps = React.PropsWithChildren & {
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ModalLayoutHeader = ({ children, className }: ModalLayoutHeaderProps) => (
|
|
||||||
<StyledHeader className={className}>{children}</StyledHeader>
|
|
||||||
);
|
|
||||||
|
|
||||||
type ModalLayoutContentProps = React.PropsWithChildren & {
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ModalLayoutContent = ({
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
}: ModalLayoutContentProps) => (
|
|
||||||
<StyledContent className={className}>{children}</StyledContent>
|
|
||||||
);
|
|
||||||
|
|
||||||
type ModalLayoutFooterProps = React.PropsWithChildren & {
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ModalLayoutFooter = ({ children, className }: ModalLayoutFooterProps) => (
|
|
||||||
<StyledFooter className={className}>{children}</StyledFooter>
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modal
|
|
||||||
*/
|
|
||||||
export type ModalSize = 'small' | 'medium' | 'large';
|
|
||||||
export type ModalPadding = 'none' | 'small' | 'medium' | 'large';
|
|
||||||
|
|
||||||
export type ModalLayoutProps = React.PropsWithChildren & {
|
|
||||||
size?: ModalSize;
|
|
||||||
padding?: ModalPadding;
|
|
||||||
className?: string;
|
|
||||||
modalRef?: React.RefObject<HTMLElement>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const modalVariants = {
|
|
||||||
hidden: { opacity: 0 },
|
|
||||||
visible: { opacity: 1 },
|
|
||||||
exit: { opacity: 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
// This component should be used over Modal when seeking a modal feel without modal state (hotkeyScope etc)
|
|
||||||
export const ModalLayout = ({
|
|
||||||
children,
|
|
||||||
size = 'medium',
|
|
||||||
padding = 'medium',
|
|
||||||
modalRef,
|
|
||||||
className,
|
|
||||||
}: ModalLayoutProps) => {
|
|
||||||
const stopEventPropagation = (e: React.MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledBackDrop onMouseDown={stopEventPropagation}>
|
|
||||||
<StyledModalDiv
|
|
||||||
// framer-motion seems to have typing problems with refs
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
ref={modalRef}
|
|
||||||
size={size}
|
|
||||||
padding={padding}
|
|
||||||
initial="hidden"
|
|
||||||
animate="visible"
|
|
||||||
exit="exit"
|
|
||||||
layout
|
|
||||||
variants={modalVariants}
|
|
||||||
className={className}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</StyledModalDiv>
|
|
||||||
</StyledBackDrop>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ModalLayout.Header = ModalLayoutHeader;
|
|
||||||
ModalLayout.Content = ModalLayoutContent;
|
|
||||||
ModalLayout.Footer = ModalLayoutFooter;
|
|
@ -2,7 +2,6 @@ import { Meta, StoryObj } from '@storybook/react';
|
|||||||
import { ComponentDecorator } from 'twenty-ui';
|
import { ComponentDecorator } from 'twenty-ui';
|
||||||
|
|
||||||
import { Modal } from '../Modal';
|
import { Modal } from '../Modal';
|
||||||
import { ModalHotkeyScope } from '../types/ModalHotkeyScope';
|
|
||||||
|
|
||||||
const meta: Meta<typeof Modal> = {
|
const meta: Meta<typeof Modal> = {
|
||||||
title: 'UI/Layout/Modal/Modal',
|
title: 'UI/Layout/Modal/Modal',
|
||||||
@ -14,10 +13,8 @@ type Story = StoryObj<typeof Modal>;
|
|||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
isOpen: true,
|
|
||||||
size: 'medium',
|
size: 'medium',
|
||||||
padding: 'medium',
|
padding: 'medium',
|
||||||
hotkeyScope: ModalHotkeyScope.Default,
|
|
||||||
children: (
|
children: (
|
||||||
<>
|
<>
|
||||||
<Modal.Header>Stay in touch</Modal.Header>
|
<Modal.Header>Stay in touch</Modal.Header>
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
|
||||||
import { ComponentDecorator } from 'twenty-ui';
|
|
||||||
|
|
||||||
import { ModalLayout } from '@/ui/layout/modal/components/ModalLayout';
|
|
||||||
|
|
||||||
const meta: Meta<typeof ModalLayout> = {
|
|
||||||
title: 'UI/Layout/Modal/ModalLayout',
|
|
||||||
component: ModalLayout,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof ModalLayout>;
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
args: {
|
|
||||||
size: 'medium',
|
|
||||||
padding: 'medium',
|
|
||||||
children: (
|
|
||||||
<>
|
|
||||||
<ModalLayout.Header>Stay in touch</ModalLayout.Header>
|
|
||||||
<ModalLayout.Content>
|
|
||||||
This is a dummy newletter form so don't bother trying to test it. Not
|
|
||||||
that I expect you to, anyways. :)
|
|
||||||
</ModalLayout.Content>
|
|
||||||
<ModalLayout.Footer>
|
|
||||||
By using Twenty, you're opting for the finest CRM experience you'll
|
|
||||||
ever encounter.
|
|
||||||
</ModalLayout.Footer>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
decorators: [ComponentDecorator],
|
|
||||||
argTypes: {
|
|
||||||
children: { control: false },
|
|
||||||
},
|
|
||||||
};
|
|
@ -2,8 +2,6 @@ import { css, Global, useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { AnimatePresence, LayoutGroup, motion } from 'framer-motion';
|
import { AnimatePresence, LayoutGroup, motion } from 'framer-motion';
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
import { AuthModal } from '@/auth/components/Modal';
|
|
||||||
import { CommandMenu } from '@/command-menu/components/CommandMenu';
|
import { CommandMenu } from '@/command-menu/components/CommandMenu';
|
||||||
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
||||||
import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu';
|
import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu';
|
||||||
@ -16,6 +14,7 @@ import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
|
|||||||
import { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths';
|
import { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { useScreenSize } from '@/ui/utilities/screen-size/hooks/useScreenSize';
|
import { useScreenSize } from '@/ui/utilities/screen-size/hooks/useScreenSize';
|
||||||
|
import { AuthModal } from '@/auth/components/AuthModal';
|
||||||
|
|
||||||
const StyledLayout = styled.div`
|
const StyledLayout = styled.div`
|
||||||
background: ${({ theme }) => theme.background.noisy};
|
background: ${({ theme }) => theme.background.noisy};
|
||||||
|
@ -23,7 +23,8 @@ export const BACKGROUND_DARK = {
|
|||||||
lighter: RGBA(GRAY_SCALE.gray0, 0.03),
|
lighter: RGBA(GRAY_SCALE.gray0, 0.03),
|
||||||
danger: RGBA(COLOR.red, 0.08),
|
danger: RGBA(COLOR.red, 0.08),
|
||||||
},
|
},
|
||||||
overlay: RGBA(GRAY_SCALE.gray80, 0.8),
|
overlayPrimary: RGBA(GRAY_SCALE.gray80, 0.8),
|
||||||
|
overlaySecondary: RGBA(GRAY_SCALE.gray80, 0.4),
|
||||||
radialGradient: `radial-gradient(50% 62.62% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
|
radialGradient: `radial-gradient(50% 62.62% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
|
||||||
radialGradientHover: `radial-gradient(76.32% 95.59% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
|
radialGradientHover: `radial-gradient(76.32% 95.59% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
|
||||||
primaryInverted: GRAY_SCALE.gray20,
|
primaryInverted: GRAY_SCALE.gray20,
|
||||||
|
@ -23,7 +23,8 @@ export const BACKGROUND_LIGHT = {
|
|||||||
lighter: RGBA(GRAY_SCALE.gray100, 0.02),
|
lighter: RGBA(GRAY_SCALE.gray100, 0.02),
|
||||||
danger: RGBA(COLOR.red, 0.08),
|
danger: RGBA(COLOR.red, 0.08),
|
||||||
},
|
},
|
||||||
overlay: RGBA(GRAY_SCALE.gray80, 0.8),
|
overlayPrimary: RGBA(GRAY_SCALE.gray80, 0.8),
|
||||||
|
overlaySecondary: RGBA(GRAY_SCALE.gray80, 0.4),
|
||||||
radialGradient: `radial-gradient(50% 62.62% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
|
radialGradient: `radial-gradient(50% 62.62% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
|
||||||
radialGradientHover: `radial-gradient(76.32% 95.59% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
|
radialGradientHover: `radial-gradient(76.32% 95.59% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
|
||||||
primaryInverted: GRAY_SCALE.gray60,
|
primaryInverted: GRAY_SCALE.gray60,
|
||||||
|
@ -3,5 +3,6 @@ export const MODAL = {
|
|||||||
sm: '300px',
|
sm: '300px',
|
||||||
md: '400px',
|
md: '400px',
|
||||||
lg: '53%',
|
lg: '53%',
|
||||||
|
fullscreen: `100dvh`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user