mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-23 05:53:31 +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 React from 'react';
|
||||
|
||||
import { ModalLayout } from '@/ui/layout/modal/components/ModalLayout';
|
||||
|
||||
const StyledContent = styled(ModalLayout.Content)`
|
||||
const StyledContent = styled(Modal.Content)`
|
||||
align-items: center;
|
||||
width: calc(400px - ${({ theme }) => theme.spacing(10 * 2)});
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
type AuthModalProps = { children: React.ReactNode };
|
||||
|
||||
export const AuthModal = ({ children }: AuthModalProps) => (
|
||||
<ModalLayout padding={'none'}>
|
||||
<Modal padding={'none'} modalVariant="primary">
|
||||
<StyledContent>{children}</StyledContent>
|
||||
</ModalLayout>
|
||||
</Modal>
|
||||
);
|
@ -2,8 +2,8 @@ import styled from '@emotion/styled';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
||||
|
||||
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';
|
||||
|
||||
const StyledModal = styled(Modal)`
|
||||
@ -16,7 +16,7 @@ const StyledModal = styled(Modal)`
|
||||
min-width: auto;
|
||||
min-height: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 80%;
|
||||
}
|
||||
`;
|
||||
|
||||
@ -40,11 +40,15 @@ export const ModalWrapper = ({
|
||||
const { rtl } = useSpreadsheetImportInternal();
|
||||
|
||||
return (
|
||||
<StyledModal isOpen={isOpen} size="large">
|
||||
<StyledRtlLtr dir={rtl ? 'rtl' : 'ltr'}>
|
||||
<ModalCloseButton onClose={onClose} />
|
||||
{children}
|
||||
</StyledRtlLtr>
|
||||
</StyledModal>
|
||||
<>
|
||||
{isOpen && (
|
||||
<StyledModal size="large" onClose={onClose} isClosable={true}>
|
||||
<StyledRtlLtr dir={rtl ? 'rtl' : 'ltr'}>
|
||||
<ModalCloseButton onClose={onClose} />
|
||||
{children}
|
||||
</StyledRtlLtr>
|
||||
</StyledModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import styled from '@emotion/styled';
|
||||
|
||||
import { CircularProgressBar } from '@/ui/feedback/progress-bar/components/CircularProgressBar';
|
||||
import { MainButton } from '@/ui/input/button/components/MainButton';
|
||||
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
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 { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
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 { TemplateColumn } from './components/TemplateColumn';
|
||||
import { UserTableColumn } from './components/UserTableColumn';
|
||||
|
@ -4,8 +4,8 @@ import { useCallback, useState } from 'react';
|
||||
import { Heading } from '@/spreadsheet-import/components/Heading';
|
||||
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
|
||||
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';
|
||||
|
||||
const StyledHeading = styled(Heading)`
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { Heading } from '@/spreadsheet-import/components/Heading';
|
||||
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
|
||||
import { Radio } from '@/ui/input/components/Radio';
|
||||
import { RadioGroup } from '@/ui/input/components/RadioGroup';
|
||||
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
|
||||
const StyledContent = styled(Modal.Content)`
|
||||
|
@ -3,10 +3,11 @@ import { MOBILE_VIEWPORT } from 'twenty-ui';
|
||||
|
||||
import { useSpreadsheetImportInitialStep } from '@/spreadsheet-import/hooks/useSpreadsheetImportInitialStep';
|
||||
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 { useStepBar } from '@/ui/navigation/step-bar/hooks/useStepBar';
|
||||
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { UploadFlow } from './UploadFlow';
|
||||
|
||||
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 { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
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 { SelectHeaderStep } from './SelectHeaderStep/SelectHeaderStep';
|
||||
import { SelectSheetStep } from './SelectSheetStep/SelectSheetStep';
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { WorkBook } from 'xlsx-ugnis';
|
||||
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
|
||||
import { DropZone } from './components/DropZone';
|
||||
|
||||
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 { Button } from '@/ui/input/button/components/Button';
|
||||
import { Toggle } from '@/ui/input/components/Toggle';
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { generateColumns } from './components/columns';
|
||||
import { ImportedStructuredRowMetadata } from './types';
|
||||
|
||||
|
@ -11,7 +11,7 @@ import { DialogHotkeyScope } from '../types/DialogHotkeyScope';
|
||||
|
||||
const StyledDialogOverlay = styled(motion.div)`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.background.overlay};
|
||||
background: ${({ theme }) => theme.background.overlayPrimary};
|
||||
display: flex;
|
||||
height: 100dvh;
|
||||
justify-content: center;
|
||||
|
@ -6,6 +6,7 @@ import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { Button, ButtonAccent } from '@/ui/input/button/components/Button';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import {
|
||||
Section,
|
||||
@ -27,8 +28,8 @@ export type ConfirmationModalProps = {
|
||||
|
||||
const StyledConfirmationModal = styled(Modal)`
|
||||
border-radius: ${({ theme }) => theme.spacing(1)};
|
||||
padding: ${({ theme }) => theme.spacing(6)};
|
||||
width: calc(400px - ${({ theme }) => theme.spacing(32)});
|
||||
height: auto;
|
||||
`;
|
||||
|
||||
const StyledCenteredButton = styled(Button)`
|
||||
@ -85,54 +86,57 @@ export const ConfirmationModal = ({
|
||||
return (
|
||||
<AnimatePresence mode="wait">
|
||||
<LayoutGroup>
|
||||
<StyledConfirmationModal
|
||||
isOpen={isOpen}
|
||||
onClose={() => {
|
||||
if (isOpen) {
|
||||
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);
|
||||
{isOpen && (
|
||||
<StyledConfirmationModal
|
||||
onClose={() => {
|
||||
if (isOpen) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
}}
|
||||
variant="secondary"
|
||||
accent={confirmButtonAccent}
|
||||
title={deleteButtonText}
|
||||
disabled={!isValidValue}
|
||||
fullWidth
|
||||
dataTestId="confirmation-modal-confirm-button"
|
||||
/>
|
||||
</StyledConfirmationModal>
|
||||
onEnter={onConfirmClick}
|
||||
isClosable={true}
|
||||
padding="large"
|
||||
>
|
||||
<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"
|
||||
accent={confirmButtonAccent}
|
||||
title={deleteButtonText}
|
||||
disabled={!isValidValue}
|
||||
fullWidth
|
||||
dataTestId="confirmation-modal-confirm-button"
|
||||
/>
|
||||
</StyledConfirmationModal>
|
||||
)}
|
||||
</LayoutGroup>
|
||||
</AnimatePresence>
|
||||
);
|
||||
|
@ -1,46 +1,183 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import {
|
||||
ModalLayout,
|
||||
ModalLayoutProps,
|
||||
} from '@/ui/layout/modal/components/ModalLayout';
|
||||
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
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 & {
|
||||
isOpen?: boolean;
|
||||
const StyledModalDiv = styled(motion.div)<{
|
||||
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;
|
||||
onClose?: () => 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 = ({
|
||||
isOpen = false,
|
||||
children,
|
||||
onClose,
|
||||
hotkeyScope = ModalHotkeyScope.Default,
|
||||
onEnter,
|
||||
size = 'medium',
|
||||
padding = 'medium',
|
||||
className,
|
||||
hotkeyScope = ModalHotkeyScope.Default,
|
||||
onEnter,
|
||||
isClosable = false,
|
||||
onClose,
|
||||
modalVariant = 'primary',
|
||||
}: ModalProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const {
|
||||
goBackToPreviousHotkeyScope,
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
onClose?.();
|
||||
},
|
||||
useEffect(() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope(hotkeyScope);
|
||||
return () => {
|
||||
goBackToPreviousHotkeyScope();
|
||||
};
|
||||
}, [
|
||||
hotkeyScope,
|
||||
[onClose],
|
||||
);
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
]);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Enter],
|
||||
@ -50,41 +187,53 @@ export const Modal = ({
|
||||
hotkeyScope,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setHotkeyScopeAndMemorizePreviousScope(hotkeyScope);
|
||||
} else {
|
||||
goBackToPreviousHotkeyScope();
|
||||
}
|
||||
}, [
|
||||
goBackToPreviousHotkeyScope,
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
if (isClosable && onClose !== undefined) {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
hotkeyScope,
|
||||
isOpen,
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
]);
|
||||
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
);
|
||||
|
||||
useListenClickOutsideV2({
|
||||
refs: [modalRef],
|
||||
listenerId: 'MODAL_CLICK_OUTSIDE_LISTENER_ID',
|
||||
callback: () => onClose?.(),
|
||||
callback: () => {
|
||||
if (isClosable && onClose !== undefined) {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return isOpen ? (
|
||||
<ModalLayout
|
||||
className={className}
|
||||
modalRef={modalRef}
|
||||
size={size}
|
||||
padding={padding}
|
||||
const stopEventPropagation = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledBackDrop
|
||||
onMouseDown={stopEventPropagation}
|
||||
modalVariant={modalVariant}
|
||||
>
|
||||
{children}
|
||||
</ModalLayout>
|
||||
) : (
|
||||
<></>
|
||||
<StyledModalDiv
|
||||
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.Content = ModalLayout.Content;
|
||||
Modal.Footer = ModalLayout.Footer;
|
||||
Modal.Header = ModalHeader;
|
||||
Modal.Content = ModalContent;
|
||||
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 { Modal } from '../Modal';
|
||||
import { ModalHotkeyScope } from '../types/ModalHotkeyScope';
|
||||
|
||||
const meta: Meta<typeof Modal> = {
|
||||
title: 'UI/Layout/Modal/Modal',
|
||||
@ -14,10 +13,8 @@ type Story = StoryObj<typeof Modal>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
isOpen: true,
|
||||
size: 'medium',
|
||||
padding: 'medium',
|
||||
hotkeyScope: ModalHotkeyScope.Default,
|
||||
children: (
|
||||
<>
|
||||
<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 { AnimatePresence, LayoutGroup, motion } from 'framer-motion';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import { AuthModal } from '@/auth/components/Modal';
|
||||
import { CommandMenu } from '@/command-menu/components/CommandMenu';
|
||||
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
||||
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 { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { useScreenSize } from '@/ui/utilities/screen-size/hooks/useScreenSize';
|
||||
import { AuthModal } from '@/auth/components/AuthModal';
|
||||
|
||||
const StyledLayout = styled.div`
|
||||
background: ${({ theme }) => theme.background.noisy};
|
||||
|
@ -23,7 +23,8 @@ export const BACKGROUND_DARK = {
|
||||
lighter: RGBA(GRAY_SCALE.gray0, 0.03),
|
||||
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%)`,
|
||||
radialGradientHover: `radial-gradient(76.32% 95.59% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
|
||||
primaryInverted: GRAY_SCALE.gray20,
|
||||
|
@ -23,7 +23,8 @@ export const BACKGROUND_LIGHT = {
|
||||
lighter: RGBA(GRAY_SCALE.gray100, 0.02),
|
||||
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%)`,
|
||||
radialGradientHover: `radial-gradient(76.32% 95.59% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
|
||||
primaryInverted: GRAY_SCALE.gray60,
|
||||
|
@ -3,5 +3,6 @@ export const MODAL = {
|
||||
sm: '300px',
|
||||
md: '400px',
|
||||
lg: '53%',
|
||||
fullscreen: `100dvh`,
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user