Drawer#
Drawer — это боковая панель для предоставления какой-либо информации, не требующей постоянного отображения на странице.
import { Drawer, DrawerHeader, DrawerBody, DrawerFooter } from '@v-uik/drawer'
№ |
Свойство |
Описание |
Значение по умолчанию |
---|---|---|---|
1 |
classes |
JSS-классы для стилизации |
- |
2 |
container |
HTML-элемент или функция, возвращающая HTML-элемент, в который отрисовываются дочерние элементы `HTMLElement |
(() => HTMLElement)` |
3 |
placement |
Расположение относительно границ экрана: bottom, top, left, right |
right |
4 |
open |
Показать/скрыть элемент (boolean) |
false |
5 |
height |
Высота элемента (string / number) |
|
6 |
width |
Ширина элемента (string / number) |
- |
7 |
contentProps |
Свойства HTML-элемента панели |
|
8 |
backdrop |
Показать/скрыть затемнение фона (boolean) |
true |
9 |
bodyScrollLock |
Флаг блокировки скролла страницы (boolean) |
true |
10 |
backdropProps |
HTML-аттрибуты элемента фона окна |
|
11 |
disableEscapePressHandler |
Отключить срабатывание обработчика onClose при нажатии клавиши Esc (boolean) |
|
12 |
disableBackdropClickHandler |
Отключить срабатывание обработчика onClose при клике за пределами панели (boolean) |
|
13 |
onClose |
Обработчик закрытия окна |
DrawerHeader#
Компонент DrawerHeader используется для отображения заголовка боковой панели.
№ |
Свойство |
Описание |
Значение по умолчанию |
---|---|---|---|
1 |
classes |
JSS-классы для стилизации |
- |
2 |
onClose |
Обработчик нажатия кнопки закрытия |
|
3 |
subtitle |
Подзаголовок модального окна (ReactNode) |
|
4 |
titleProps |
Свойства компонента заголовка (TextProps) |
|
5 |
subtitleProps |
Свойства компонента подзаголовка (TextProps) |
|
6 |
showCloseButton |
Отображать ли кнопку закрытия (boolean) |
true |
7 |
closeButtonProps |
Свойства кнопки закрытия (ButtonProps) |
|
8 |
dividerProps |
Свойства элемента-разделителя HTMLAttributes |
DrawerBody#
Компонент DrawerBody используется для отображения содержимого боковой панели.
DrawerFooter#
Компонент DrawerFooter используется для отображения футера боковой панели.
№ |
Свойство |
Описание |
Значение по умолчанию |
---|---|---|---|
1 |
dividerProps |
Свойства элемента-разделителя |
- |
Базовый пример#
import * as React from 'react'
import { Button } from '@v-uik/button'
import { Text } from '@v-uik/typography'
import { Drawer, DrawerHeader, DrawerBody, DrawerFooter } from '@v-uik/drawer'
export const BasicDrawer = (): JSX.Element => {
const [open, setOpen] = React.useState(false)
const handleClose = () => setOpen(false)
return (
<>
<Button onClick={() => setOpen(!open)}>показать drawer</Button>
<Drawer open={open} onClose={handleClose}>
<DrawerHeader
subtitle="Подзаголовок"
closeButtonProps={{
'aria-label': 'Close drawer',
}}
onClose={handleClose}
>
Заголовок
</DrawerHeader>
<DrawerBody>
<Text>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab animi
beatae consectetur dolore doloremque doloribus earum enim, ex
exercitationem facere natus nisi nostrum repellat repudiandae rerum,
sit tenetur velit voluptate.
</Text>
</DrawerBody>
<DrawerFooter>
<Button kind="outlined" onClick={handleClose}>
Закрыть
</Button>
</DrawerFooter>
</Drawer>
</>
)
}
Варианты расположения на странице#
С помощью свойства position можно выбрать, с какой стороны экрана будет появляться боковая панель: сверху, снизу, справа или слева.
import * as React from 'react'
import { Button } from '@v-uik/button'
import { Text } from '@v-uik/typography'
import {
Drawer,
DrawerPlacementType,
DrawerHeader,
DrawerBody,
DrawerFooter,
} from '@v-uik/drawer'
export const PositionsExample = (): JSX.Element => {
const [opened, setOpened] = React.useState<{
[key in DrawerPlacementType]: boolean
}>({
left: false,
right: false,
top: false,
bottom: false,
})
return (
<>
{(['left', 'right', 'top', 'bottom'] as const).map((placement) => {
const toggle = (val: boolean) =>
setOpened({
...opened,
[placement]: val,
})
const handleClose = () => toggle(false)
return (
<React.Fragment key={placement}>
<Button style={{ marginRight: 16 }} onClick={() => toggle(true)}>
{placement}
</Button>
<Drawer
open={opened[placement]}
placement={placement}
onClose={handleClose}
>
<DrawerHeader showCloseButton={false}>{placement}</DrawerHeader>
<DrawerBody>
<Text>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad
enim hic, inventore nihil quas soluta veritatis! Autem,
blanditiis, consectetur dolorum ea in laudantium non, omnis
perferendis porro praesentium quaerat sint.
</Text>
</DrawerBody>
<DrawerFooter>
<Button kind="outlined" onClick={handleClose}>
Закрыть
</Button>
<Button onClick={handleClose}>Подтвердить</Button>
</DrawerFooter>
</Drawer>
</React.Fragment>
)
})}
</>
)
}
Пример без блокировки страницы#
Установив свойству backdrop значение false, можно отключить затемнение фона.
При указании свойства bodyScrollLock = false
отключается блокировка скролла страницы.
Заметьте, что компонент будет помещен в текущее место в DOM-дереве,
тогда как вариант по умолчанию отрисовывает панель в конце документа.
import * as React from 'react'
import { Button } from '@v-uik/button'
import { Drawer, DrawerHeader, DrawerBody, DrawerFooter } from '@v-uik/drawer'
import { Text } from '@v-uik/typography'
export const NonModalExample = (): JSX.Element => {
const [open, setOpen] = React.useState(false)
const handleClose = () => setOpen(false)
return (
<>
<Button onClick={() => setOpen(!open)}>показать drawer</Button>
<Drawer
backdrop={false}
bodyScrollLock={false}
open={open}
onClose={handleClose}
>
<DrawerHeader onClose={handleClose}>Заголовок</DrawerHeader>
<DrawerBody>
<Text>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab animi
beatae consectetur dolore doloremque doloribus earum enim, ex
exercitationem facere natus nisi nostrum repellat repudiandae rerum,
sit tenetur velit voluptate.
</Text>
</DrawerBody>
<DrawerFooter>
<Button kind="outlined" onClick={handleClose}>
Закрыть
</Button>
</DrawerFooter>
</Drawer>
</>
)
}
Панель для пользовательского элемента#
С помощью свойства container можно отобразить боковую панель внутри любого элемента DOM-дерева.
import * as React from 'react'
import { Table, ColumnProps, RecordDataSource } from '@v-uik/table'
import { Drawer, DrawerHeader, DrawerBody } from '@v-uik/drawer'
import { Link } from '@v-uik/link'
import { Text } from '@v-uik/typography'
type DataSource = RecordDataSource<{
name: string
role: string
email: string
phone: string
city: string
}>
const dataSource: DataSource[] = [
{
key: 1,
name: 'Иван Иванов',
role: 'разработчик',
email: 'ivan@example.ru',
phone: '+79991110000',
city: 'Москва',
},
{
key: 2,
name: 'Петр Петров',
role: 'дизайнер',
email: 'petr@example.ru',
phone: '+79876543210',
city: 'Санкт-Петербург',
},
{
key: 3,
name: 'Николай Николаев',
role: 'менеджер',
email: 'nikolay@example.ru',
phone: '+79991234567',
city: 'Москва',
},
]
export const ContainerExample = (): JSX.Element => {
const [tableMounted, setTableMounted] = React.useState(false)
const tableRef = React.useRef<HTMLDivElement>(null)
const [currentProfile, setCurrentProfile] = React.useState<DataSource>()
React.useEffect(() => {
setTableMounted(true)
}, [setTableMounted])
const columns: ColumnProps<DataSource>[] = [
{
key: 'name',
dataIndex: 'name',
title: 'Имя',
},
{
key: 'role',
dataIndex: 'role',
title: 'Роль',
},
{
key: 'profile',
dataIndex: 'profile',
renderCellContent: ({ originClassName, row }) => {
return (
<div className={originClassName}>
<Link
tabIndex={0}
onKeyDown={(event: React.KeyboardEvent<HTMLAnchorElement>) => {
if (event.key === ' ' || event.key === 'Enter') {
event.preventDefault()
setCurrentProfile(row)
}
}}
onClick={(event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault()
setCurrentProfile(row)
}}
>
доп. информация
</Link>
</div>
)
},
},
]
const handleClose = () => setCurrentProfile(undefined)
const open = !!currentProfile
return (
<>
<Table
ref={tableRef}
style={{
position: 'relative',
overflowX: 'hidden',
}}
columns={columns}
dataSource={dataSource}
/>
{tableMounted && (
<Drawer
style={{
position: 'absolute',
}}
backdrop={false}
bodyScrollLock={false}
container={tableRef.current ?? undefined}
open={open}
onClose={handleClose}
>
<DrawerHeader
subtitle={currentProfile?.role}
dividerProps={{ style: { display: 'none' } }}
onClose={handleClose}
>
{currentProfile?.name}
</DrawerHeader>
<DrawerBody style={{ flexDirection: 'column' }}>
<Text kind="body1">{currentProfile?.email}</Text>
<Text kind="body1">{currentProfile?.phone}</Text>
</DrawerBody>
</Drawer>
)}
</>
)
}
Панель для пользовательского элемента со скроллом#
Для того чтобы использовать Drawer на скроллящемся элементе, рекомендуется обернуть его каким-либо элементом-контейнером, и уже его передавать свойству container.
import * as React from 'react'
import { Table, ColumnProps, RecordDataSource } from '@v-uik/table'
import { Drawer, DrawerHeader, DrawerBody } from '@v-uik/drawer'
import { Link } from '@v-uik/link'
import { Text } from '@v-uik/typography'
type DataSource = RecordDataSource<{
name: string
role: string
email: string
phone: string
city: string
}>
const dataSource: DataSource[] = [
{
key: 1,
name: 'Иван Иванов',
role: 'разработчик',
email: 'ivan@example.ru',
phone: '+79991110000',
city: 'Москва',
},
{
key: 2,
name: 'Петр Петров',
role: 'дизайнер',
email: 'petr@example.ru',
phone: '+79876543210',
city: 'Санкт-Петербург',
},
{
key: 3,
name: 'Николай Николаев',
role: 'менеджер',
email: 'nikolay@example.ru',
phone: '+79991234567',
city: 'Москва',
},
{
key: 4,
name: 'Федор Федоров',
role: 'разработчик',
email: 'fedor@example.ru',
phone: '+79999999999',
city: 'Казань',
},
{
key: 5,
name: 'Егор Егоров',
role: 'разработчик',
email: 'egor@example.ru',
phone: '+71234567890',
city: 'Нижний Новгород',
},
]
export const ContainerOverflowExample = (): JSX.Element => {
const [containerMounted, setContainerMounted] = React.useState(false)
const containerRef = React.useRef<HTMLDivElement>(null)
const [currentProfile, setCurrentProfile] = React.useState<DataSource>()
React.useEffect(() => {
setContainerMounted(true)
}, [setContainerMounted])
const columns: ColumnProps<DataSource>[] = [
{
key: 'name',
dataIndex: 'name',
title: 'Имя',
},
{
key: 'role',
dataIndex: 'role',
title: 'Роль',
},
{
key: 'profile',
dataIndex: 'profile',
renderCellContent: ({ originClassName, row }) => {
return (
<div className={originClassName}>
<Link
tabIndex={0}
onKeyDown={(event: React.KeyboardEvent<HTMLAnchorElement>) => {
if (event.key === ' ' || event.key === 'Enter') {
event.preventDefault()
setCurrentProfile(row)
}
}}
onClick={(event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault()
setCurrentProfile(row)
}}
>
доп. информация
</Link>
</div>
)
},
},
]
const handleClose = () => setCurrentProfile(undefined)
const open = !!currentProfile
return (
<div
ref={containerRef}
style={{
position: 'relative',
overflowX: 'hidden',
}}
>
<Table height={200} columns={columns} dataSource={dataSource} />
{containerMounted && (
<Drawer
style={{
position: 'absolute',
}}
backdrop={false}
bodyScrollLock={false}
container={containerRef.current ?? undefined}
open={open}
onClose={handleClose}
>
<DrawerHeader
subtitle={currentProfile?.role}
dividerProps={{ style: { display: 'none' } }}
onClose={handleClose}
>
{currentProfile?.name}
</DrawerHeader>
<DrawerBody style={{ flexDirection: 'column' }}>
<Text kind="body1">{currentProfile?.email}</Text>
<Text kind="body1">{currentProfile?.phone}</Text>
</DrawerBody>
</Drawer>
)}
</div>
)
}
Многоуровневая панель#
Немного стилизовав компоненты Drawer, можно добиться эффекта вложенности панелей.
import * as React from 'react'
import { Button } from '@v-uik/button'
import { Text } from '@v-uik/typography'
import { Drawer, DrawerHeader, DrawerBody, DrawerFooter } from '@v-uik/drawer'
const contentProps_1 = {
style: {
transition: 'transform 250ms ease-out',
transform: 'translateX(-24px)',
},
}
const contentProps_2 = {
style: {
transition: 'transform 250ms ease-out',
transform: 'translateX(-48px)',
},
}
const backdropProps = {
style: {
opacity: 0,
},
}
export const MultiLevelExample = (): JSX.Element => {
const [openedCount, setOpenedCount] = React.useState(0)
const handleClose = () => setOpenedCount(openedCount - 1)
const openNext = () => setOpenedCount(openedCount + 1)
return (
<>
<Button onClick={() => setOpenedCount(1)}>показать drawer</Button>
<Drawer
open={openedCount > 0}
backdropProps={openedCount > 1 ? backdropProps : undefined}
contentProps={
openedCount === 2
? contentProps_1
: openedCount === 3
? contentProps_2
: undefined
}
onClose={handleClose}
>
<DrawerHeader onClose={handleClose}>Первый</DrawerHeader>
<DrawerBody>
<Text>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab animi
beatae consectetur dolore doloremque doloribus earum enim, ex
exercitationem facere natus nisi nostrum repellat repudiandae rerum,
sit tenetur velit voluptate.
</Text>
</DrawerBody>
<DrawerFooter>
<Button kind="outlined" onClick={handleClose}>
Закрыть
</Button>
<Button onClick={openNext}>Далее</Button>
</DrawerFooter>
</Drawer>
<Drawer
open={openedCount > 1}
backdropProps={openedCount > 2 ? backdropProps : undefined}
contentProps={openedCount === 3 ? contentProps_1 : undefined}
onClose={handleClose}
>
<DrawerHeader onClose={handleClose}>Второй</DrawerHeader>
<DrawerBody>
<Text>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab animi
beatae consectetur dolore doloremque doloribus earum enim, ex
exercitationem facere natus nisi nostrum repellat repudiandae rerum,
sit tenetur velit voluptate.
</Text>
</DrawerBody>
<DrawerFooter>
<Button kind="outlined" onClick={handleClose}>
Закрыть
</Button>
<Button onClick={openNext}>Далее</Button>
</DrawerFooter>
</Drawer>
<Drawer open={openedCount > 2} onClose={handleClose}>
<DrawerHeader onClose={handleClose}>Третий</DrawerHeader>
<DrawerBody>
<Text>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab animi
beatae consectetur dolore doloremque doloribus earum enim, ex
exercitationem facere natus nisi nostrum repellat repudiandae rerum,
sit tenetur velit voluptate.
</Text>
</DrawerBody>
<DrawerFooter>
<Button kind="outlined" onClick={handleClose}>
Закрыть
</Button>
</DrawerFooter>
</Drawer>
</>
)
}
Доступность#
Компонент проставляет необходимые ARIA-атрибуты (role, aria-modal) по умолчанию. Для варианта с затемнением фона можно это изменить с помощью свойства contentProps. При открытии меню фокус выставляется на скрытый статичный элемент, от которого затем можно переходить к последующим фокусируемым элементам. На текущий момент нет фокуса для какого-либо видимого элемента, так как рекомендации по работе с фокусом сильно зависят от содержимого компонента (семантика, скролл), поэтому при необходимости установите фокус на нужном элементе самостоятельно.