@gravity-ui/uikit
Version:
Gravity UI base styling and components
348 lines (259 loc) • 9.97 kB
Markdown
# Layout components and spacings
This guide covers UIKit's layout foundations: a shared **spacing** scale (`--g-spacing-*`, used
everywhere via tokens and props) and a responsive **grid** (`Container`/`Row`/`Col`), plus the
flexbox-based `Flex`/`Box` primitives built on top of them. Compose pages from these instead of
raw `div`s and inline styles.
## Spacing
Spacing in UIKit is a **scale**, not free-form pixels. You reference a step (`1`, `2`, … `10`)
and the design system turns it into a concrete size. Sticking to the scale is what keeps rhythm
consistent across the whole app, and lets you rescale everything from a single variable.
### The scale
Every step is a multiple of a base unit (`--g-spacing-base`, `4px` by default), so `step × 4px`:
| Step | CSS variable | Size |
| ----- | ------------------ | ---- |
| `0` | `--g-spacing-0` | 0 |
| `0.5` | `--g-spacing-half` | 2px |
| `1` | `--g-spacing-1` | 4px |
| `2` | `--g-spacing-2` | 8px |
| `3` | `--g-spacing-3` | 12px |
| `4` | `--g-spacing-4` | 16px |
| `5` | `--g-spacing-5` | 20px |
| `6` | `--g-spacing-6` | 24px |
| `7` | `--g-spacing-7` | 28px |
| `8` | `--g-spacing-8` | 32px |
| `9` | `--g-spacing-9` | 36px |
| `10` | `--g-spacing-10` | 40px |
Because every step is derived from `--g-spacing-base`, changing that one value rescales the
entire spacing system proportionally (see [Customization](#customization)).
### Ways to apply spacing
There are three ways to consume the scale — pick by context:
**1. Component props** — spacing **between** children of `Flex`/`Box`, via the `gap` prop:
```tsx
import {Flex} from '@gravity-ui/uikit';
<Flex gap={5}>
<Button />
<Button />
</Flex>; // 20px between children
```
**2. CSS custom properties** — the same steps as `--g-spacing-{step}` variables, for use in your
own styles (e.g. `--g-spacing-half` for the `0.5` step):
```css
.example-class {
margin-right: var(--g-spacing-5); /* 20px */
padding: var(--g-spacing-2) var(--g-spacing-4);
}
```
**3. The `spacing()` utility** — for one-off margins/paddings on any element without hand-writing
class names. It returns a generated class name string:
```tsx
import {spacing} from '@gravity-ui/uikit';
<>
<Button className={spacing({mr: 5})}>button 1</Button>
<Button className={spacing({mt: 2, px: 4})}>button 2</Button>
</>;
```
`sp` is a shorter alias: `import {sp} from '@gravity-ui/uikit'` → `sp({mr: 5})`.
Supported keys (each takes a scale step):
| Key | Property |
| ------------------- | --------------------------------- |
| `m` | `margin` |
| `mt` `mr` `mb` `ml` | `margin-top/right/bottom/left` |
| `mx` | horizontal margin (left + right) |
| `my` | vertical margin (top + bottom) |
| `p` | `padding` |
| `pt` `pr` `pb` `pl` | `padding-top/right/bottom/left` |
| `px` | horizontal padding (left + right) |
| `py` | vertical padding (top + bottom) |
You can pass a second argument to merge extra class names: `spacing({mr: 5}, myClassName)`.
> **Rule of thumb:** `gap` for spacing between siblings in a `Flex`/`Box`; the `spacing()`/`sp()`
> utility for one-off offsets on an element; raw `--g-spacing-*` variables inside your own CSS.
> Always use scale steps, never hard-coded pixels.
### Customization
Override the base unit to rescale the whole system. Do it via CSS at the project level:
```css
:root {
--g-spacing-base: 5px; /* now step 5 = 25px, etc. */
}
```
Or through the layout theme, which keeps the JS `Space` values and CSS variables in sync:
```tsx
import {ThemeProvider, LayoutTheme} from '@gravity-ui/uikit';
const config: LayoutTheme = {
spaceBaseSize: 5,
};
export const App = () => {
return (
<ThemeProvider layout={{config, fixBreakpoints: true}}>
{...}
</ThemeProvider>
);
};
```
## Screen sizes:
We use **mobile-first** approach. It means that you should adapt your app for desktop after completing development of mobile version.
The default breakpoints are:
- `xs` - < 576px
- `s` - ≥ 576px;
- `m` - ≥ 768px;
- `l` - ≥ 1080px;
- `xl` - ≥ 1200px;
- `xxl` - ≥ 1400px;
- `xxxl` - ≥ 1920px;
To override a breakpoint use the `breakpoints` property in the layout config:
```tsx
const APP_LAYOUT_THEME: LayoutTheme = {
spaceBaseSize: 4,
components: {
container: {
gutters: 3,
media: {
l: {
gutters: 5,
},
},
},
},
breakpoints: {
s: 320,
l: 980,
},
};
<ThemeProvider layout={{config: APP_LAYOUT_THEME}}>
{...}
</ThemeProvider>;
```
## Box
The `Box` component is a developer friend and basic block to build other components. Aware about spacing, its own sizes and most commonly used CSS properties.
Use it to declaratively describe elements with a fixed height/width. It also has built-in support for the most commonly used properties, such as `overflow`.
It is mainly used as a base unit for other components such as `Flex` and `Card`.
It is also well suited for use as a base for data loading containers, for example:
```tsx
import React, {Suspense} from 'react';
import {Flex, Loader} from '@gravity-ui/uikit';
// `Flex` extended from `Box` component and enriched flexbox model properties
<Flex centerContent width="100%" height="100%">
<Suspense fallback={<Loader size="m" />}>
<LazyLoadedComponent />
</Suspense>
</Flex>;
```
## Layout Grid
Main components to describe 12-th column grid layout for your app.
Supports nested grids. This should be used when you have mobile and desktop app versions.
```tsx
import {Row, Col} from '@gravity-ui/uikit';
<Row space="5">
<Col size="4">...</Col>
<Col size="4">...</Col>
<Col size="4">...</Col>
</Row>;
```
### Row
**Props**
- `space` - specify horizontal spacing between child `Col` components;
- `spaceRow` - specify vertical spacing between child `Col` components. By default, it takes values from the `space` prop.
### Col
How many columns of your 12-th column layout will take content.
Must be used as a child of `Row` component.
**Props**
- `size` - column width in column number (if omitted the column will occupy all free space in the row)
```tsx
import {Row, Col} from '@gravity-ui/uikit';
<Row
/**
* In this example we override default theme behavior.
*
* space={{s: '1', xl:'5'}}
*/
space="5"
>
<Col
// Will be:
// 12 for "xs" and "s"
// 6 for "m" and "l"
// 4 for "xl" and "xxl"
size={[12, {m: 6, xl: 4}]}
/>
</Row>;
```
> Grid system use negative margins under the hood. So you can specify background-color css property directly in `Col` components. Use wrapper components in such cases.
## Container
Center you content. Almost always it should be one per page. Manage max width corresponding to current screen size
**Props**
- `gutters` - left and right content padding. Needed when you content width equals screen width;
- `maxWidth` - limits screen width for specific screen size;
- `spaceRow` - ability to specify spacing between children `Row` components
## Flex
CSS `Flexbox` model representation in `jsx` world. Has built-in `spacing` to manage space between children. All flex properties are available in props.
For most used properties it supports object syntax config to override behavior in different screen sizes.
#### Examples
_Space between children components in row direction_
```jsx
import {Flex, TextInput, Button} from '@gravity-ui/uikit';
<Flex space="5">
<TextInput />
<Button />
</Flex>;
```
_Nested `Flex` example_
```jsx
import {Flex, TextInput, Button, Table} from '@gravity-ui/uikit';
<Flex direction="column" space="5">
<Flex space="5">
<TextInput />
<Button />
</Flex>
<Table />
</Flex>;
```
_Responsible example_
```jsx
import {Flex, TextInput, Button} from '@gravity-ui/uikit';
<Flex
// direction: column will be applied to l, xl, xxl, xxxl screen sizes here
direction={{l: 'column'}}
space={{s: '5', m: '3'}}
>
<TextInput />
<Button />
</Flex>;
```
## Hooks
### useLayoutContext
Hook `useLayoutContext` provide ability to use `LayoutTheme` and helper functions to work with media queries.
It returns the following methods and objects:
- `theme` - `LayoutTheme` object;
- `activeMediaQuery` - returns current [Screen sizes](#screen-sizes) keys.
```tsx
import {useLayoutContext} from '@gravity-ui/uikit';
const Component = () => {
const {activeMediaQuery} = useLayoutContext();
return (
<>{activeMediaQuery === 'l' ? <Text>I render only on screen resolution "l"</Text> : null}</>
);
};
```
- `isMediaActive` - returns `true` if passed value is equal to or greater than the current active media. It is necessary to implement logic of adaptive elements for **mobile-first** approach.
```tsx
import {useLayoutContext} from '@gravity-ui/uikit';
// this example will be shown on xl, xxl and xxxl screen sizes
const Component = () => {
const {isMediaActive} = useLayoutContext();
return (
<>{isMediaActive('xl') ? <Text>I render on "xl", "xxl" and "xxxl" screen sizes</Text> : null}</>
);
};
```
- `getClosestMediaProps` - it works in a similar way as `isMediaActive`, but takes a map with screen media as an argument. Returns the nearest available value in the map taking into account the **mobile-first** approach.
```tsx
import {useLayoutContext} from '@gravity-ui/uikit';
const mapOfPropsByScreen = {
s: "i'm will be shown on 's' and 'n' screen size",
l: "i'm will be shown on 'l' and 'xl' screen size",
xxl: "i'm will be shown on 'xxl' and 'xxxl' screen size",
};
const Component = () => {
const {getClosestMediaProps} = useLayoutContext();
return <Text>{getClosestMediaProps(mapOfPropsByScreen)}</Text>;
};
```