Skip to content

Commit

Permalink
concept: new ModalPage
Browse files Browse the repository at this point in the history
  • Loading branch information
SevereCloud committed May 18, 2023
1 parent 1cdad2d commit d0d0404
Show file tree
Hide file tree
Showing 6 changed files with 500 additions and 0 deletions.
73 changes: 73 additions & 0 deletions packages/vkui/src/components/ModalPageNew/ModalPageNew.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
.ModalPageNew__container {
top: 0;
right: 0;
bottom: 0;
left: 0;
position: fixed;
overflow-x: hidden;
overflow-y: auto;
z-index: var(--vkui--z_index_modal);
-webkit-overflow-scrolling: touch;

/**
* Для удаление скролла в Firefox.
* В версии ниже 64 будет виден скролл, но это не ломает функциональность.
*/
scrollbar-width: none;

/**
* В старых браузерах не работает, но это не ломает функциональность.
*/
scroll-snap-type: y mandatory;
}

/* stylelint-disable-next-line @project-tools/stylelint-atomic */
.ModalPageNew__container > * {
scroll-snap-align: start;
}

.ModalPageNew__container::-webkit-scrollbar {
display: none;
}

.ModalPageNew__contentIn {
background: var(--vkui--color_background_modal);
border-radius: var(--vkui--size_border_radius_paper--regular)
var(--vkui--size_border_radius_paper--regular) 0 0;
margin-left: auto;
margin-right: auto;
max-width: var(--vkui--size_popup_small--regular);
}

.ModalPageNew__mask {
z-index: var(--vkui--z_index_modal);
position: fixed;
background: var(--vkui--color_overlay_primary);
top: 0;
right: 0;
bottom: 0;
left: 0;
opacity: 0;
}

/**
* В старых браузерах sticky не работает, но это не ломает функциональность.
*/
.ModalPageNew__header {
z-index: 1;
position: sticky;
top: 0;
border-radius: inherit;
background: var(--vkui--color_background_modal);
}

.ModalPageNew__headerFixed {
border-radius: 0;
}

.ModalPageNew__footer {
z-index: 2;
position: sticky;
bottom: 0;
background: var(--vkui--color_background_modal);
}
130 changes: 130 additions & 0 deletions packages/vkui/src/components/ModalPageNew/ModalPageNew.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import React from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { withSinglePanel, withVKUILayout } from '../../storybook/VKUIDecorators';
import { CanvasFullLayout, DisableCartesianParam } from '../../storybook/constants';
import { Avatar } from '../Avatar/Avatar';
import { Button } from '../Button/Button';
import { Card } from '../Card/Card';
import { CardScroll } from '../CardScroll/CardScroll';
import { Div } from '../Div/Div';
import { FormItem } from '../FormItem/FormItem';
import { Group } from '../Group/Group';
import { Header } from '../Header/Header';
import { HorizontalCell } from '../HorizontalCell/HorizontalCell';
import { HorizontalScroll } from '../HorizontalScroll/HorizontalScroll';
import { Input } from '../Input/Input';
import { ModalPageHeader } from '../ModalPageHeader/ModalPageHeader';
import { Panel } from '../Panel/Panel';
import { PanelHeader } from '../PanelHeader/PanelHeader';
import { SimpleCell } from '../SimpleCell/SimpleCell';
import { Textarea } from '../Textarea/Textarea';
import { ModalPageNew, ModalPageNewProps } from './ModalPageNew';

const story: Meta<ModalPageNewProps> = {
title: 'Modals/ModalPageNew',
component: ModalPageNew,
parameters: { ...CanvasFullLayout, ...DisableCartesianParam },
decorators: [withSinglePanel, withVKUILayout],
};

export default story;

type Story = StoryObj<ModalPageNewProps>;

export const Example: Story = {
render: function Render() {
const [modal, setModal] = React.useState(true);

const openModal = () => setModal(true);
const closeModal = () => setModal(false);

return (
<Panel>
<PanelHeader>ANKI</PanelHeader>
<Group>
<FormItem>
<Input />
</FormItem>

<SimpleCell onClick={openModal}>open modal</SimpleCell>
</Group>

<Group>
{Array(50)
.fill(undefined)
.map((_, i) => (
<SimpleCell key={i} expandable>
SimpleCell
</SimpleCell>
))}
</Group>

{modal && (
<ModalPageNew
header={<ModalPageHeader>Заголовок</ModalPageHeader>}
footer={
<Div>
<Button size="l" stretched>
Button
</Button>
</Div>
}
onClosed={closeModal}
>
<Group>
<Header>https://github.com/VKCOM/VKUI/issues/335</Header>
<HorizontalScroll>
<div style={{ display: 'flex' }}>
{Array(50)
.fill(undefined)
.map((_, i) => (
<HorizontalCell key={i} header="title">
<Avatar />
</HorizontalCell>
))}
</div>
</HorizontalScroll>
</Group>

<Group>
<Header>https://github.com/VKCOM/VKUI/issues/338</Header>
<Header>https://github.com/VKCOM/VKUI/issues/599</Header>
<FormItem>
<Input />
</FormItem>
</Group>

<Group>
<Header>https://github.com/VKCOM/VKUI/issues/1071</Header>
<FormItem>
<Textarea />
</FormItem>
</Group>

<Group>
<Header>https://github.com/VKCOM/VKUI/issues/1494</Header>
<CardScroll size="s">
<div style={{ display: 'flex' }}>
{Array(50)
.fill(undefined)
.map((_, i) => (
<Card key={i}>
<div style={{ paddingBottom: '66%' }} />
</Card>
))}
</div>
</CardScroll>
</Group>

<Div>
https://github.com/VKCOM/VKUI/issues/604 https://github.com/VKCOM/VKUI/issues/741
https://github.com/VKCOM/VKUI/issues/876 https://github.com/VKCOM/VKUI/issues/1570
https://github.com/VKCOM/VKUI/issues/2008 https://github.com/VKCOM/VKUI/issues/2029
https://github.com/VKCOM/VKUI/issues/2030 https://github.com/VKCOM/VKUI/issues/2449
</Div>
</ModalPageNew>
)}
</Panel>
);
},
};
185 changes: 185 additions & 0 deletions packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import React from 'react';
import { classNames } from '@vkontakte/vkjs';
import { useEventListener } from '../../hooks/useEventListener';
import { useGlobalEventListener } from '../../hooks/useGlobalEventListener';
import { useDOM } from '../../lib/dom';
import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect';
import { useScrollLock } from '../AppRoot/ScrollContext';
import styles from './ModalPageNew.module.css';

// Прокрутка элемента на определенный процент
function useFirstOpen(container: React.RefObject<HTMLDivElement>, settlingHeight: number) {
useIsomorphicLayoutEffect(() => {
const el = container.current!;

Check warning on line 13 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L11-L13

Added lines #L11 - L13 were not covered by tests

el.scrollTop = 0;
el.scrollTo({

Check warning on line 16 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L15-L16

Added lines #L15 - L16 were not covered by tests
top: (el.clientHeight * settlingHeight) / 100,
behavior: 'smooth',
});
}, []);
}

// Отступы для модалки
function useIndents(settlingHeight: number) {
const { document, window } = useDOM();

Check warning on line 25 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L24-L25

Added lines #L24 - L25 were not covered by tests

const indent1Ref = React.useRef<HTMLDivElement>(null);
const indent2Ref = React.useRef<HTMLDivElement>(null);

Check warning on line 28 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L27-L28

Added lines #L27 - L28 were not covered by tests

const indentCalculate = () => {
const indent1Height = (document!.documentElement.clientHeight * settlingHeight) / 100;
const indent2Height = document!.documentElement.clientHeight - indent1Height;

Check warning on line 32 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L30-L32

Added lines #L30 - L32 were not covered by tests

indent1Ref.current!.style.height = `${indent1Height}px`;
indent2Ref.current!.style.height = `${indent2Height}px`;

Check warning on line 35 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L34-L35

Added lines #L34 - L35 were not covered by tests
};

useGlobalEventListener(window, 'resize', indentCalculate);

Check warning on line 38 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L38

Added line #L38 was not covered by tests

useIsomorphicLayoutEffect(() => {
indentCalculate();

Check warning on line 41 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L40-L41

Added lines #L40 - L41 were not covered by tests
}, [settlingHeight]);

return [indent1Ref, indent2Ref];

Check warning on line 44 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L44

Added line #L44 was not covered by tests
}

// Маска для модалки
function useMask(container: React.RefObject<HTMLDivElement>, settlingHeight: number) {
const maskRef = React.useRef<HTMLDivElement>(null);

Check warning on line 49 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L48-L49

Added lines #L48 - L49 were not covered by tests

const scroll = () => {
const el = container.current!;

Check warning on line 52 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L51-L52

Added lines #L51 - L52 were not covered by tests

const indent1 = (el.clientHeight * settlingHeight) / 100;

Check warning on line 54 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L54

Added line #L54 was not covered by tests

const opacity = Math.min(el.scrollTop / indent1, 1);

Check warning on line 56 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L56

Added line #L56 was not covered by tests

maskRef.current!.style.opacity = `${opacity}`;

Check warning on line 58 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L58

Added line #L58 was not covered by tests
};

const scrollListener = useEventListener('scroll', scroll);

Check warning on line 61 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L61

Added line #L61 was not covered by tests

useIsomorphicLayoutEffect(() => {
const el = container.current!;
scrollListener.add(el);

Check warning on line 65 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L63-L65

Added lines #L63 - L65 were not covered by tests
}, [settlingHeight]);

return maskRef;

Check warning on line 68 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L68

Added line #L68 was not covered by tests
}

function useCheckScroll(container: React.RefObject<HTMLDivElement>, closeCallback: () => void) {
useIsomorphicLayoutEffect(() => {
const el = container.current!;

Check warning on line 73 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L71-L73

Added lines #L71 - L73 were not covered by tests

const scroll = () => {
if (el.scrollTop === 0) {
closeCallback();

Check warning on line 77 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L75-L77

Added lines #L75 - L77 were not covered by tests
}
};

el.addEventListener('scroll', scroll);

Check warning on line 81 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L81

Added line #L81 was not covered by tests

return () => el.removeEventListener('scroll', scroll);

Check warning on line 83 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L83

Added line #L83 was not covered by tests
}, []);
}

function useFullOpen(container: React.RefObject<HTMLDivElement>) {
const [fullOpen, setFullOpen] = React.useState(false);

Check warning on line 88 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L87-L88

Added lines #L87 - L88 were not covered by tests

useIsomorphicLayoutEffect(() => {
const el = container.current!;

Check warning on line 91 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L90-L91

Added lines #L90 - L91 were not covered by tests

const scroll = () => {
if (el.scrollTop >= el.offsetHeight) {
!fullOpen && setFullOpen(true);
return;

Check warning on line 96 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L93-L96

Added lines #L93 - L96 were not covered by tests
}

fullOpen && setFullOpen(false);

Check warning on line 99 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L99

Added line #L99 was not covered by tests
};

el.addEventListener('scroll', scroll);

Check warning on line 102 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L102

Added line #L102 was not covered by tests

return () => el.removeEventListener('scroll', scroll);

Check warning on line 104 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L104

Added line #L104 was not covered by tests
}, [fullOpen]);

return fullOpen;

Check warning on line 107 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L107

Added line #L107 was not covered by tests
}

export interface ModalPageNewProps {
header?: React.ReactNode;
children?: React.ReactNode;
footer?: React.ReactNode;

onClose?: () => void;
onClosed?: () => void;

/**
* Процент, на который изначально будет открыта модальная страница.
* При settlingHeight={100} модальная страница раскрывается на всю высоту.
*/
settlingHeight?: number;
}

export const ModalPageNew = ({
header,
children,
footer,
onClose,
onClosed,
settlingHeight = 75,

Check warning on line 131 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L126-L131

Added lines #L126 - L131 were not covered by tests
...restProp
}: ModalPageNewProps) => {
const containerRef = React.useRef<HTMLDivElement>(null);

Check warning on line 134 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L133-L134

Added lines #L133 - L134 were not covered by tests

const [indent1Ref, indent2Ref] = useIndents(settlingHeight);
const maskRef = useMask(containerRef, settlingHeight);

Check warning on line 137 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L136-L137

Added lines #L136 - L137 were not covered by tests

useFirstOpen(containerRef, settlingHeight);
const fullOpen = useFullOpen(containerRef);
useCheckScroll(containerRef, () => {
console.log('Closed');
onClosed && onClosed();

Check warning on line 143 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L139-L143

Added lines #L139 - L143 were not covered by tests
});

useScrollLock();

Check warning on line 146 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L146

Added line #L146 was not covered by tests

const close = () => {
onClose && onClose();

Check warning on line 149 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L148-L149

Added lines #L148 - L149 were not covered by tests

containerRef.current?.scrollTo({

Check warning on line 151 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L151

Added line #L151 was not covered by tests
top: 0,
behavior: 'smooth',
});
};

return (

Check warning on line 157 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L157

Added line #L157 was not covered by tests
<>
<div className={styles['ModalPageNew__mask']} ref={maskRef} />
<div
className={styles['ModalPageNew__container']}
ref={containerRef}
{...restProp}
onClick={close}
>
<div style={{ height: `${settlingHeight}%` }} ref={indent1Ref} />
<div style={{ height: `${100 - settlingHeight}%` }} ref={indent2Ref} />
<div className={styles['ModalPageNew__contentIn']} onClick={(e) => e.stopPropagation()}>

Check warning on line 168 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L168

Added line #L168 was not covered by tests
<div
className={classNames(
styles['ModalPageNew__header'],
fullOpen && styles['ModalPageNew__headerFixed'],

Check warning on line 172 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L172

Added line #L172 was not covered by tests
)}
>
{header}
</div>

{children}

{footer && <div className={styles['ModalPageNew__footer']}>{footer}</div>}

Check warning on line 180 in packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ModalPageNew/ModalPageNew.tsx#L180

Added line #L180 was not covered by tests
</div>
</div>
</>
);
};
Loading

0 comments on commit d0d0404

Please sign in to comment.