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

feat(Overlay): implement component #1474

Merged
merged 7 commits into from
Jun 25, 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
49 changes: 49 additions & 0 deletions src/components/Overlay/Overlay.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
25 changes: 25 additions & 0 deletions src/components/Overlay/Overlay.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={b({visible}, className)}>
<div className={b('background', {style: background})} />
{children && <div className={b('children')}>{children}</div>}
</div>
);
}
48 changes: 48 additions & 0 deletions src/components/Overlay/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!--GITHUB_BLOCK-->

# Overlay

<!--/GITHUB_BLOCK-->

```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';

<Box position="relative">
<div>Some content to hide under overlay</div>
<Overlay visible={loading}>
<Loader />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amje @korvin89 are you sure that this use case is really convenient? Do I transfer the same component to the loader every time? It looks like 90% of all uses here will be such a <Loader /> component and it should be made default by default

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I believe it's better to be that way, Overlay is a utility component, it shouldn't have "default" look. The look will depend on the usage context. Also, turning off the default behaviour would look a bit wierd:

<Overlay visible children={null} />

amje marked this conversation as resolved.
Show resolved Hide resolved
</Overlay>
</Box>;
```

## Appearance

### Background

You can use `base` or `float` background colors.

<!--GITHUB_BLOCK-->

```tsx
<Overlay background="base">
<Overlay background="float">
```

<!--/GITHUB_BLOCK-->

## 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` | |
7 changes: 7 additions & 0 deletions src/components/Overlay/__stories__/Docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {Meta, Markdown} from '@storybook/addon-docs';
import * as Stories from './Overlay.stories';
import Readme from '../README.md?raw';

<Meta of={Stories} />

<Markdown>{Readme}</Markdown>
22 changes: 22 additions & 0 deletions src/components/Overlay/__stories__/Overlay.stories.scss
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
128 changes: 128 additions & 0 deletions src/components/Overlay/__stories__/Overlay.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof Overlay>;

export default {
title: 'Components/Utils/Overlay',
component: Overlay,
} as Meta;

export const Default: Story = {
args: {
visible: true,
},
render: (args) => {
return (
<Showcase>
<Box position="relative" className={b('content')}>
<div>Example of overlay</div>
<div>with loader</div>
<Overlay {...args}>
<Loader size="m" />
</Overlay>
</Box>
<Box position="relative" className={b('content')}>
<div>Example of overlay</div>
<div>with text</div>
<Overlay {...args}>Loading...</Overlay>
</Box>
<Box position="relative" className={b('content')}>
<div>Example of overlay</div>
<div>with icon</div>
<Overlay {...args}>
<Icon data={ArrowsRotateRight} />
</Overlay>
</Box>
<Box position="relative" className={b('content')}>
<div>Example of overlay</div>
<div>without children</div>
<Overlay {...args} />
</Box>
</Showcase>
);
},
};

export const Background: Story = {
args: {
visible: true,
},
render: (args) => {
return (
<Showcase>
<ShowcaseItem title="base">
<Box position="relative" className={b('content')}>
<div>I am an example</div>
<div>content</div>
<Overlay {...args} background="base" />
</Box>
</ShowcaseItem>
<ShowcaseItem title="float">
<Box position="relative" className={b('content')}>
<div>I am an example</div>
<div>content</div>
<Overlay {...args} background="float" />
</Box>
</ShowcaseItem>
</Showcase>
);
},
};

const TableView = (args: OverlayProps) => {
const [loading, setLoading] = React.useState(false);
const [loadedData, setData] = React.useState<DataItem[]>([]);

return (
<div className={b()}>
<Box position="relative" className={b('content')}>
<TableComponent className={b('table')} columns={columns} data={loadedData} />
<Overlay {...args} visible={loading}>
<Loader size="m" />
</Overlay>
</Box>
<Button
className={b('button')}
disabled={loading}
onClick={() => {
setLoading(true);
setTimeout(() => {
setLoading(false);
setData(data);
}, 1000);
}}
>
Load data
</Button>
<Button className={b('button')} disabled={loading} onClick={() => setData([])}>
Clear data
</Button>
</div>
);
};

export const Table: Story = {
args: {},
render: TableView,
};
91 changes: 91 additions & 0 deletions src/components/Overlay/__stories__/data.tsx
Original file line number Diff line number Diff line change
@@ -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<DataItem>[] = [
{
id: 'name',
name: 'Name',
template(item, i) {
if (i % 2 === 0) {
return item.name;
}
const [name, surname] = item.name.split(' ');
return (
<div>
{name}
<br />
{surname}
</div>
);
},
},
{
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,
},
];
1 change: 1 addition & 0 deletions src/components/Overlay/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Overlay';
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Loading
Loading