From 95be4875cc2db2457be99d295bc1bdd8182a7d1c Mon Sep 17 00:00:00 2001 From: Daniil Suvorov Date: Thu, 18 May 2023 12:25:45 +0300 Subject: [PATCH 01/16] feat: add ModalSheet --- .../ModalSheet/ModalSheet.module.css | 209 +++++++++++ .../ModalSheet/ModalSheet.stories.tsx | 157 ++++++++ .../components/ModalSheet/ModalSheet.test.tsx | 17 + .../src/components/ModalSheet/ModalSheet.tsx | 339 ++++++++++++++++++ .../vkui/src/components/ModalSheet/Readme.md | 209 +++++++++++ .../ModalSheet/components/Content/Content.tsx | 10 + .../ModalSheet/components/Footer/Footer.tsx | 10 + .../ModalSheet/components/Header/Header.tsx | 10 + .../ModalSheet/components/Indent/Indent.tsx | 66 ++++ packages/vkui/src/hooks/useEventListener.ts | 4 +- .../vkui/src/hooks/useGlobalEventListener.ts | 6 +- packages/vkui/src/hooks/useKeyboard.ts | 34 ++ packages/vkui/src/index.ts | 3 + styleguide/config.js | 1 + 14 files changed, 1070 insertions(+), 5 deletions(-) create mode 100644 packages/vkui/src/components/ModalSheet/ModalSheet.module.css create mode 100644 packages/vkui/src/components/ModalSheet/ModalSheet.stories.tsx create mode 100644 packages/vkui/src/components/ModalSheet/ModalSheet.test.tsx create mode 100644 packages/vkui/src/components/ModalSheet/ModalSheet.tsx create mode 100644 packages/vkui/src/components/ModalSheet/Readme.md create mode 100644 packages/vkui/src/components/ModalSheet/components/Content/Content.tsx create mode 100644 packages/vkui/src/components/ModalSheet/components/Footer/Footer.tsx create mode 100644 packages/vkui/src/components/ModalSheet/components/Header/Header.tsx create mode 100644 packages/vkui/src/components/ModalSheet/components/Indent/Indent.tsx diff --git a/packages/vkui/src/components/ModalSheet/ModalSheet.module.css b/packages/vkui/src/components/ModalSheet/ModalSheet.module.css new file mode 100644 index 0000000000..e674047525 --- /dev/null +++ b/packages/vkui/src/components/ModalSheet/ModalSheet.module.css @@ -0,0 +1,209 @@ +.ModalSheet { + display: flex; + align-items: center; + justify-content: center; + inset: 0; + position: fixed; + z-index: var(--vkui--z_index_modal); +} + +.ModalSheet__container { + block-size: 100%; + position: relative; + overflow-x: hidden; + overflow-y: auto; + overscroll-behavior: none; + + /** + * Для удаление скролла в Firefox. + * В версии ниже 64 будет виден скролл, но это не ломает функциональность. + */ + scrollbar-width: none; + + /** + * В старых браузерах не работает, но это не ломает функциональность. + */ + scroll-snap-type: y mandatory; + inline-size: 100%; +} + +.ModalSheet__disableSnap { + scroll-snap-type: none; +} + +.ModalSheet--closes { + pointer-events: none; +} + +/* stylelint-disable-next-line selector-max-universal, @project-tools/stylelint-atomic */ +.ModalSheet__container > * { + scroll-snap-align: start; +} + +.ModalSheet__container::-webkit-scrollbar { + display: none; +} + +.ModalSheet__content { + display: flex; + flex-direction: column; + background: var(--vkui--color_background_modal); + border-start-start-radius: var(--vkui--size_border_radius_paper--regular); + border-start-end-radius: var(--vkui--size_border_radius_paper--regular); + border-end-end-radius: 0; + border-end-start-radius: 0; + margin-inline: auto; + transition: border-radius 0.1s var(--vkui--animation_easing_platform); + inline-size: 100%; +} + +.ModalSheet--size-s { + max-inline-size: var(--vkui--size_popup_small--regular); +} + +.ModalSheet--size-m { + max-inline-size: var(--vkui--size_popup_medium--regular); +} + +.ModalSheet--size-l { + max-inline-size: var(--vkui--size_popup_large--regular); +} + +.ModalSheet__fullOpen .ModalSheet__content { + transition: none; + border-radius: 0; +} + +.ModalSheet__overlay { + position: absolute; + background: var(--vkui--color_overlay_primary); + inset: 0; + opacity: 0; + cursor: pointer; +} + +.ModalSheet--sizeX-regular .ModalSheet__overlay { + opacity: 1; +} + +@media (--sizeX-regular) { + .ModalSheet--sizeX-none .ModalSheet__overlay { + opacity: 1; + } +} + +.ModalSheet__dismissButton { + display: none; +} + +.ModalSheet--sizeX-regular .ModalSheet__dismissButton { + display: block; +} + +@media (--sizeX-regular) { + .ModalSheet--sizeX-none .ModalSheet__dismissButton { + display: block; + } +} + +/** + * В старых браузерах sticky не работает, но это не ломает функциональность. + */ +.ModalSheet__header { + z-index: 1; + position: sticky; + inset-block-start: 0; + border-radius: inherit; + background: var(--vkui--color_background_modal); +} + +.ModalSheet__footer { + z-index: 2; + position: sticky; + inset-block-end: 0; + background: var(--vkui--color_background_modal); + border-end-start-radius: var(--vkui--size_border_radius_paper--regular); + border-end-end-radius: var(--vkui--size_border_radius_paper--regular); +} + +.ModalSheet--sizeX-regular .ModalSheet__content { + border-radius: var(--vkui--size_border_radius_paper--regular); + block-size: 100%; +} + +@media (--sizeX-regular) { + .ModalSheet--sizeX-none .ModalSheet__content { + border-radius: var(--vkui--size_border_radius_paper--regular); + block-size: 100%; + } +} + +.ModalSheet--sizeX-regular .ModalSheet__Content { + flex: 1; + overflow-y: auto; +} + +@media (--sizeX-regular) { + .ModalSheet--sizeX-none .ModalSheet__Content { + flex: 1; + overflow-y: auto; + } +} + +.ModalSheet--sizeX-regular .ModalSheet__indent { + display: none; +} + +@media (--sizeX-regular) { + .ModalSheet--sizeX-none .ModalSheet__indent { + display: none; + } +} + +.ModalSheet--sizeX-regular .ModalSheet__container { + max-block-size: calc(100% - 32px * 2); + margin-block: 32px; + margin-inline: 56px; + border-radius: var(--vkui--size_border_radius_paper--regular); + overflow: initial; +} + +@media (--sizeX-regular) { + .ModalSheet--sizeX-none .ModalSheet__container { + max-block-size: calc(100% - 32px * 2); + margin-block: 32px; + margin-inline: 56px; + border-radius: var(--vkui--size_border_radius_paper--regular); + overflow: initial; + } +} + +.ModalSheet--sizeX-regular.ModalSheet--opening { + animation: animation-fade-in 0.32s ease; +} + +@media (--sizeX-regular) { + .ModalSheet--sizeX-none.ModalSheet--opening { + animation: animation-fade-in 0.32s ease; + } +} + +.ModalSheet--sizeX-regular.ModalSheet--closing { + animation: animation-fade-in 0.32s ease reverse; +} + +@media (--sizeX-regular) { + .ModalSheet--sizeX-none.ModalSheet--closing { + animation: animation-fade-in 0.32s ease reverse; + } +} + +@keyframes animation-fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} diff --git a/packages/vkui/src/components/ModalSheet/ModalSheet.stories.tsx b/packages/vkui/src/components/ModalSheet/ModalSheet.stories.tsx new file mode 100644 index 0000000000..4d13196cd4 --- /dev/null +++ b/packages/vkui/src/components/ModalSheet/ModalSheet.stories.tsx @@ -0,0 +1,157 @@ +import * as 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 { ModalSheet, ModalSheetProps } from './ModalSheet'; + +const story: Meta = { + title: 'Modals/ModalSheet', + component: ModalSheet, + parameters: { ...CanvasFullLayout, ...DisableCartesianParam }, + decorators: [withSinglePanel, withVKUILayout], +}; + +export default story; + +type Story = StoryObj; + +export const Example: Story = { + render: function Render() { + const [modal, setModal] = React.useState(undefined); + + const closeModal = () => setModal(undefined); + + return ( + + ANKI + + + + + + setModal('1')}>open modal 1 + setModal('2')}>open modal 2 + + + + {Array(50) + .fill(undefined) + .map((_, i) => ( + + SimpleCell + + ))} + + + {modal === '1' && ( + + + Заголовок + + + + +
https://github.com/VKCOM/VKUI/issues/335
+ +
+ {Array(50) + .fill(undefined) + .map((_, i) => ( + + + + ))} +
+
+
+ + +
https://github.com/VKCOM/VKUI/issues/338
+
https://github.com/VKCOM/VKUI/issues/599
+ + + +
+ + +
https://github.com/VKCOM/VKUI/issues/1071
+ +