Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EPMRPP-89804 || Update Modal component #28

Merged
merged 5 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/modal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ Default width - 480px, height 100%
### Props :

- **title**: _string_, optional, default = ""
- **headerNode**: _node_, optional, default = null
- **children**: _node_, optional, default = null
- **footerNode**: _node_, optional, default = null
- **okButton**: _object_, optional, default = null
- **cancelButton**: _object_, optional, default = null
- **className**: _string_, optional, default = ""
- **size**: _string_, optional, default = "default"
- **onClose**: _function_, optional, default = () => {}
- **description**: _node_, optional, default = null

### Variants

Expand Down
15 changes: 14 additions & 1 deletion src/components/modal/modal.module.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
@import 'src/assets/styles/variables/typography';
@import 'src/assets/styles/mixins/font-scale';

$WIDTH-SMALL: 320px;
$WIDTH-DEFAULT: 480px;
$WIDTH-LARGE: 720px;
Expand Down Expand Up @@ -45,7 +48,7 @@ $WIDTH-LARGE: 720px;
transform: translate(-50%);
display: inline-block;
margin-bottom: 10px;
padding: 32px 48px;
padding: 32px 40px;
box-sizing: border-box;
background-color: var(--rp-ui-base-bg-100);
text-align: left;
Expand All @@ -56,6 +59,16 @@ $WIDTH-LARGE: 720px;
max-height: 90%;
}

.description {
display: inline-block;
width: 100%;
padding-bottom: 24px;
font-family: var(--rp-ui-base-font-family);
font-weight: $fw-regular;
@include font-scale();
color: var(--rp-ui-base-almost-black);
}

.size-default {
@include modalWidth($WIDTH-DEFAULT);
}
Expand Down
82 changes: 81 additions & 1 deletion src/components/modal/modal.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Modal } from './modal';
import { FC, useState } from 'react';
import { Button } from '@components/button';

const meta: Meta<typeof Modal> = {
title: 'Modal',
Expand Down Expand Up @@ -32,6 +34,49 @@ export const Default: Story = {
args: {},
};

export const WithoutFooter: Story = {
args: {
withoutFooter: true,
description: 'title description',
},
};

export const WithSteps: Story = {
render: (args) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [step, setStep] = useState<number>(1);

const CustomFooter: FC<{ closeHandler: () => void }> = ({ closeHandler }) => {
return (
<div
style={{
display: 'flex',
flexDirection: 'row',
gap: 18,
}}
>
<Button variant={'ghost'} onClick={() => setStep(1)}>
Go to 1 step
</Button>
<Button variant={'ghost'} onClick={() => setStep(2)}>
Go to 2 step
</Button>
<Button onClick={() => console.log('done')}>OK</Button>
<Button variant={'danger'} onClick={() => closeHandler()}>
Cancel
</Button>
</div>
);
};

return (
<Modal {...args} title={'Modal with steps'} CustomFooter={CustomFooter}>
content for step {step}
</Modal>
);
},
};

export const Small: Story = {
args: {
size: 'small',
Expand All @@ -53,9 +98,44 @@ export const OverlayLightCyan: Story = {
export const Scrollable: Story = {
args: {
scrollable: true,
description: 'title description',
children: (
<>
<p style={{ marginBlockEnd: 0, marginBlockStart: 0 }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing
elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
</p>
</>
),
},
};

export const ScrollableWithoutFooter: Story = {
args: {
scrollable: true,
withoutFooter: true,
children: (
<>
<p>
<p style={{ marginBlockEnd: 0, marginBlockStart: 0 }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
Expand Down
64 changes: 45 additions & 19 deletions src/components/modal/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
const cx = classNames.bind(styles);

const MODAL_MAX_RATIO = 0.9;
const MODAL_HEADER_AND_FOOTER_HEIGHT = 176;
const MODAL_HEADER_HEIGHT = 32 + 24;
const MODAL_HEADER_WITH_DESCRIPTION_HEIGHT = 32 + 8;
const MODAL_FOOTER_HEIGHT = 36 + 16;
const MODAL_LAYOUT_PADDING = 32 * 2;

type ModalOverlay = 'default' | 'light-cyan';

interface ModalProps {
onClose: () => void;
onClose?: () => void;
title?: ReactNode;
headerNode?: ReactNode;
children?: ReactNode;
footerNode?: ReactNode;
className?: string;
Expand All @@ -31,12 +33,14 @@
okButton?: ButtonProps;
cancelButton?: ButtonProps;
scrollable?: boolean;
withoutFooter?: boolean;
CustomFooter?: FC<{ closeHandler: () => void }>;
description?: ReactNode;
}

// TODO: Fix issue with modal positioning
export const Modal: FC<ModalProps> = ({
title,
headerNode,
children,
footerNode,
okButton,
Expand All @@ -48,6 +52,9 @@
zIndex = 2,
allowCloseOutside = true,
scrollable = false,
withoutFooter = false,
CustomFooter = null,
description = null,
}) => {
const [isShown, setShown] = useState(false);
const [modalHeight, setModalHeight] = useState(0);
Expand All @@ -57,7 +64,20 @@
const windowHeight = windowSize.height;
const modalMaxHeight = windowHeight * MODAL_MAX_RATIO;
const modalMargin = (windowHeight - modalHeight) / 2;
const contentMaxHeight = modalMaxHeight - MODAL_HEADER_AND_FOOTER_HEIGHT;
const getContentMaxHeight = () => {
let contentMaxHeight = modalMaxHeight - MODAL_LAYOUT_PADDING;
if (!withoutFooter) {
contentMaxHeight = contentMaxHeight - MODAL_FOOTER_HEIGHT;
}

if (description) {
contentMaxHeight = contentMaxHeight - MODAL_HEADER_WITH_DESCRIPTION_HEIGHT;
} else {
contentMaxHeight = contentMaxHeight - MODAL_HEADER_HEIGHT;
}

return contentMaxHeight;
};

const closeModal = () => {
setShown(false);
Expand All @@ -81,14 +101,10 @@
useEffect(() => {
setShown(true);

if (modalRef && modalRef.current) {
modalRef.current.focus();
}

document.addEventListener('keydown', onKeydown, false);

return () => document.removeEventListener('keydown', onKeydown, false);
}, []);

Check warning on line 107 in src/components/modal/modal.tsx

View workflow job for this annotation

GitHub Actions / build

React Hook useEffect has a missing dependency: 'onKeydown'. Either include it or remove the dependency array

useOnClickOutside(modalRef, allowCloseOutside ? closeModal : undefined);

Expand All @@ -105,22 +121,32 @@
animate={{ opacity: 1, marginTop: modalMargin }}
exit={{ opacity: 0, marginTop: -modalMargin }}
transition={{ duration: 0.3 }}
onAnimationStart={() => modalRef.current?.focus()}
>
<ModalHeader title={title} headerNode={headerNode} onClose={closeModal} />
<ModalHeader title={title} onClose={closeModal} withDescription={!!description} />
{scrollable ? (
<Scrollbars autoHeight autoHeightMax={contentMaxHeight} hideTracksWhenNotNeeded>
<Scrollbars autoHeight autoHeightMax={getContentMaxHeight()} hideTracksWhenNotNeeded>
{description && <span className={cx('description')}>{description}</span>}
<ModalContent>{children}</ModalContent>
</Scrollbars>
) : (
<ModalContent>{children}</ModalContent>
<>
{description && <span className={cx('description')}>{description}</span>}
<ModalContent>{children}</ModalContent>
</>
)}
<ModalFooter
size={size}
footerNode={footerNode}
okButton={okButton}
cancelButton={cancelButton}
closeHandler={closeModal}
/>
{!withoutFooter &&
Vadim73i marked this conversation as resolved.
Show resolved Hide resolved
(CustomFooter ? (
<CustomFooter closeHandler={closeModal} />
) : (
<ModalFooter
size={size}
footerNode={footerNode}
okButton={okButton}
cancelButton={cancelButton}
closeHandler={closeModal}
/>
))}
</motion.div>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

.modal-content {
width: calc(100% - 34px);
margin-top: 13px;
font-family: var(--rp-ui-base-font-family);
font-weight: $fw-regular;
@include font-scale();
color: var(--rp-ui-color-text);
white-space: pre-line;
word-break: break-word;
padding: 0 0 16px;
}
7 changes: 4 additions & 3 deletions src/components/modal/modalHeader/modalHeader.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
@include font-scale(x4-medium);
color: var(--rp-ui-color-text);
padding-bottom: 24px;

&.width-description {
padding-bottom: 8px;
}
}

.modal-header-content {
display: flex;
justify-content: stretch;
align-items: center;
width: 100%;
padding-right: 30px;
box-sizing: border-box;
Expand Down
7 changes: 3 additions & 4 deletions src/components/modal/modalHeader/modalHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ const cx = classNames.bind(styles);

interface ModalHeaderProps {
title?: ReactNode;
headerNode?: ReactNode;
onClose: () => void;
withDescription?: boolean;
}

export const ModalHeader: FC<ModalHeaderProps> = ({ title, onClose, headerNode }) => (
<div className={cx('modal-header')}>
export const ModalHeader: FC<ModalHeaderProps> = ({ title, onClose, withDescription = false }) => (
<div className={cx('modal-header', { 'width-description': withDescription })}>
<div className={cx('modal-header-content')}>
{title && <span className={cx('modal-title')}>{title}</span>}
{headerNode && headerNode}
</div>
<BaseIconButton className={cx('close-modal-icon')} onClick={onClose}>
<CloseIcon />
Expand Down
Loading