diff --git a/src/components/Overlay/Overlay.scss b/src/components/Overlay/Overlay.scss new file mode 100644 index 0000000000..1898262208 --- /dev/null +++ b/src/components/Overlay/Overlay.scss @@ -0,0 +1,49 @@ +@use '../variables'; + +$block: '.#{variables.$ns}overlay'; + +#{$block} { + position: absolute; + inset: 0; + + display: flex; + visibility: hidden; + justify-content: center; + align-items: center; + + isolation: isolate; + + opacity: 0; + + transition: + visibility 0.1s, + opacity 0.1s linear; + + &_visible { + visibility: visible; + + opacity: 1; + } + + &__background { + position: absolute; + z-index: 0; + inset: 0; + + opacity: 0.8; + + &_style { + &_base { + background-color: var(--g-color-base-background); + } + + &_float { + background-color: var(--g-color-base-float); + } + } + } + + &__children { + z-index: 1; + } +} diff --git a/src/components/Overlay/Overlay.tsx b/src/components/Overlay/Overlay.tsx new file mode 100644 index 0000000000..89ea9fa25b --- /dev/null +++ b/src/components/Overlay/Overlay.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +import {block} from '../utils/cn'; + +import './Overlay.scss'; + +const b = block('overlay'); + +export type OverlayBackground = 'base' | 'float'; + +export interface OverlayProps { + className?: string; + background?: OverlayBackground; + visible?: boolean; + children?: React.ReactNode; +} + +export function Overlay({className, background = 'base', visible = false, children}: OverlayProps) { + return ( +
+
+ {children &&
{children}
} +
+ ); +} diff --git a/src/components/Overlay/README.md b/src/components/Overlay/README.md new file mode 100644 index 0000000000..b45a32f03c --- /dev/null +++ b/src/components/Overlay/README.md @@ -0,0 +1,48 @@ + + +# Overlay + + + +```tsx +import {Overlay} from '@gravity-ui/uikit'; +``` + +The `Overlay` component renders an overlay over the parent element with relative position, +i.e. parent element must have `position` set to `relative`. +For example, it can be used to preserve the desired layout while loading data. + +```jsx +import {Box, Overlay, Loader} from '@gravity-ui/uikit'; + + +
Some content to hide under overlay
+ + + +
; +``` + +## Appearance + +### Background + +You can use `base` or `float` background colors. + + + +```tsx + + +``` + + + +## Properties + +| Name | Description | Type | Default | +| :--------- | :---------------------------------- | :----------------: | :-----: | +| className | CSS class name of the root element | `string` | | +| visible | Overlay visibility state | `boolean` | `false` | +| background | Overlay background style | `"base"` `"float"` | `base` | +| children | Content, usually a Loader component | `React.ReactNode` | | diff --git a/src/components/Overlay/__stories__/Docs.mdx b/src/components/Overlay/__stories__/Docs.mdx new file mode 100644 index 0000000000..a099505e89 --- /dev/null +++ b/src/components/Overlay/__stories__/Docs.mdx @@ -0,0 +1,7 @@ +import {Meta, Markdown} from '@storybook/addon-docs'; +import * as Stories from './Overlay.stories'; +import Readme from '../README.md?raw'; + + + +{Readme} diff --git a/src/components/Overlay/__stories__/Overlay.stories.scss b/src/components/Overlay/__stories__/Overlay.stories.scss new file mode 100644 index 0000000000..93faf42c44 --- /dev/null +++ b/src/components/Overlay/__stories__/Overlay.stories.scss @@ -0,0 +1,22 @@ +@use '../../variables'; + +$block: '.#{variables.$ns}overlay-stories'; + +#{$block} { + &__content { + width: fit-content; + padding: var(--g-spacing-1); + } + + &__table table { + min-width: 763px; + } + + &__button { + margin-block-start: var(--g-spacing-1); + + & + & { + margin-inline-start: var(--g-spacing-1); + } + } +} diff --git a/src/components/Overlay/__stories__/Overlay.stories.tsx b/src/components/Overlay/__stories__/Overlay.stories.tsx new file mode 100644 index 0000000000..edfefbe88d --- /dev/null +++ b/src/components/Overlay/__stories__/Overlay.stories.tsx @@ -0,0 +1,128 @@ +import React from 'react'; + +import {ArrowsRotateRight} from '@gravity-ui/icons'; +import type {Meta, StoryObj} from '@storybook/react'; + +import {Showcase} from '../../../demo/Showcase'; +import {ShowcaseItem} from '../../../demo/ShowcaseItem'; +import {Button} from '../../Button'; +import {Icon} from '../../Icon'; +import {Loader} from '../../Loader'; +import {Table as TableComponent} from '../../Table'; +import {Box} from '../../layout'; +import {block} from '../../utils/cn'; +import {Overlay} from '../Overlay'; +import type {OverlayProps} from '../Overlay'; + +import {columns, data} from './data'; +import type {DataItem} from './data'; + +import './Overlay.stories.scss'; + +const b = block('overlay-stories'); + +type Story = StoryObj; + +export default { + title: 'Components/Utils/Overlay', + component: Overlay, +} as Meta; + +export const Default: Story = { + args: { + visible: true, + }, + render: (args) => { + return ( + + +
Example of overlay
+
with loader
+ + + +
+ +
Example of overlay
+
with text
+ Loading... +
+ +
Example of overlay
+
with icon
+ + + +
+ +
Example of overlay
+
without children
+ +
+
+ ); + }, +}; + +export const Background: Story = { + args: { + visible: true, + }, + render: (args) => { + return ( + + + +
I am an example
+
content
+ +
+
+ + +
I am an example
+
content
+ +
+
+
+ ); + }, +}; + +const TableView = (args: OverlayProps) => { + const [loading, setLoading] = React.useState(false); + const [loadedData, setData] = React.useState([]); + + return ( +
+ + + + + + + + +
+ ); +}; + +export const Table: Story = { + args: {}, + render: TableView, +}; diff --git a/src/components/Overlay/__stories__/data.tsx b/src/components/Overlay/__stories__/data.tsx new file mode 100644 index 0000000000..9254f6342f --- /dev/null +++ b/src/components/Overlay/__stories__/data.tsx @@ -0,0 +1,91 @@ +import React from 'react'; + +import type {TableColumnConfig} from '../../Table'; + +export interface DataItem { + name: string; + location?: {region: string; city?: string}; + phone: string; + count: number; + date: string; + disabled?: boolean; +} + +export const columns: TableColumnConfig[] = [ + { + id: 'name', + name: 'Name', + template(item, i) { + if (i % 2 === 0) { + return item.name; + } + const [name, surname] = item.name.split(' '); + return ( +
+ {name} +
+ {surname} +
+ ); + }, + }, + { + id: 'location.region', + name: 'Region', + }, + { + id: 'location.city', + name: 'City', + }, + { + id: 'phone', + name: 'Phone', + }, + { + id: 'count', + name: 'Count', + align: 'end', + }, + { + id: 'date', + name: 'Date created', + }, +]; + +export const data: DataItem[] = [ + { + name: 'Nomlanga Compton', + location: {region: 'Liguria', city: 'Erli'}, + phone: '+7 (923) 737-89-72', + count: 82, + date: '2019-03-15', + }, + { + name: 'Paul Hatfield', + location: {region: 'Trentino-Alto Adige/Südtirol', city: 'Campitello di Fassa'}, + phone: '+7 (900) 333-82-02', + count: 51, + date: '2019-11-23', + }, + { + name: 'Phelan Daniel', + location: {region: 'Piedmont', city: 'Meugliano'}, + phone: '+7 (925) 549-50-23', + count: 10, + date: '2019-05-14', + }, + { + name: 'Hiram Mayer', + phone: '+7 (950) 372-56-84', + location: {region: 'Calabria'}, + count: 54, + date: '2019-03-29', + }, + { + name: 'Madeline Puckett', + phone: '+7 (908) 582-05-91', + count: 75, + date: '2019-02-01', + disabled: true, + }, +]; diff --git a/src/components/Overlay/index.ts b/src/components/Overlay/index.ts new file mode 100644 index 0000000000..6dbcd51b81 --- /dev/null +++ b/src/components/Overlay/index.ts @@ -0,0 +1 @@ +export * from './Overlay'; diff --git a/src/components/index.ts b/src/components/index.ts index 56fbfb39e4..1f00c0fa38 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -25,6 +25,7 @@ export * from './List'; export * from './Loader'; export * from './Menu'; export * from './Modal'; +export * from './Overlay'; export * from './Pagination'; export * from './Palette'; export * from './UserLabel'; diff --git a/src/components/layout/Box/Box.tsx b/src/components/layout/Box/Box.tsx index 3e0d828494..2beafe2410 100644 --- a/src/components/layout/Box/Box.tsx +++ b/src/components/layout/Box/Box.tsx @@ -15,7 +15,13 @@ export interface BoxProps React.PropsWithChildren< Pick< React.CSSProperties, - 'width' | 'height' | 'maxHeight' | 'maxWidth' | 'minHeight' | 'minWidth' + | 'width' + | 'height' + | 'maxHeight' + | 'maxWidth' + | 'minHeight' + | 'minWidth' + | 'position' > > { as?: T; @@ -63,6 +69,7 @@ export const Box = React.forwardRef(function Box