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(Flex): add new Flex layout component #2748

Merged
merged 1 commit into from
Oct 13, 2023
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ComponentBox from '../../../../shared/tags/ComponentBox'
import { Card, Layout, P } from '@dnb/eufemia/src'
import { Card, Flex, P } from '@dnb/eufemia/src'
import { Field, Form } from '@dnb/eufemia/src/extensions/forms'

export const Default = () => {
Expand Down Expand Up @@ -34,10 +34,10 @@ export const VerticalFields = () => {
return (
<ComponentBox scope={{ Field }}>
<Card>
<Layout.Vertical>
<Flex.Vertical>
<Field.String label="Label" value="Value" />
<Field.String label="Label" value="Value" />
</Layout.Vertical>
</Flex.Vertical>
</Card>
</ComponentBox>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,26 @@ import * as Examples from './Examples'

`Card` is a block section element showing the white box with rounded gray borders, adding spacing automatically.

It uses [Layout.FlexItem](/uilib/components/layout/FlexItem) under the hood. When one of these props where given, `stack`, `direction` or `spacing` – the [Layout.FlexContainer](/uilib/components/layout/FlexContainer) will be used.
It uses [Flex.Item](/uilib/components/flex/item) under the hood. When one of these props where given, `stack`, `direction` or `spacing` – the [Flex.Container](/uilib/components/flex/container) will be used.

**BETA:** The design is not 100% finalised and may change to be officially approved by UX through an RFC.

```jsx
import { Card } from '@dnb/eufemia'
import { Form, Field } from '@dnb/eufemia/extensions/forms'

render(
<Form.Handler data={existingData} onSubmit={submitHandler}>
<Card>
<Field.Email path="/dataPath" />
<Form.ButtonRow>
<Form.SubmitButton />
</Form.ButtonRow>
</Card>
</Form.Handler>,
)
```

## Accessibility

It uses a `section` element. Which allows you to add an `aria-label` or `aria-labelledby` to provide screen readers with landmarks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ showTabs: true

## Properties

| Property | Type | Description |
| -------------------------------------------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------- |
| `stack` | `boolean` | _(optional)_ True to stack the sub components with lines between. The `spacing` will default to `medium`. |
| `direction` | `string` | _(optional)_ Defaults to `vertical`. |
| `alignSelf` | `string` | _(optional)_ Defaults to `stretch`. |
| `element` | `string` or `React.Element` | _(optional)_ Define the type of element. Defaults to `section`. |
| [Layout.FlexContainer](/uilib/components/layout/FlexContainer) | Various | _(optional)_ FlexContainer properties. |
| [Layout.FlexItem](/uilib/components/layout/FlexItem) | Various | _(optional)_ FlexItem properties. |
| [Space](/uilib/components/space/properties) | Various | _(optional)_ Spacing properties like `top` or `bottom` are supported. |
| Property | Type | Description |
| -------------------------------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------- |
| `stack` | `boolean` | _(optional)_ True to stack the sub components with lines between. The `spacing` will default to `medium`. |
| `direction` | `string` | _(optional)_ Defaults to `vertical`. |
| `alignSelf` | `string` | _(optional)_ Defaults to `stretch`. |
| `element` | `string` or `React.Element` | _(optional)_ Define the type of element. Defaults to `section`. |
| [Flex.Container](/uilib/components/flex/container) | Various | _(optional)_ Flex.Container properties. |
| [Flex.Item](/uilib/components/flex/item) | Various | _(optional)_ Flex.Item properties. |
| [Space](/uilib/components/space/properties) | Various | _(optional)_ Spacing properties like `top` or `bottom` are supported. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: 'Flex'
description: 'To make it easier to build application layout and form-views in line with defined design sketches, there are a number of components for layout.'
status: 'new'
theme: 'sbanken'
showTabs: true
tabs:
- title: Info
key: /info
- title: Demos
key: /demos
---

import Info from 'Docs/uilib/components/flex/info'
import Demos from 'Docs/uilib/components/flex/demos'

<Info />
<Demos />
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
import React from 'react'
import styled from '@emotion/styled'
import ComponentBox from '../../../../shared/tags/ComponentBox'
import MediaQuery from '@dnb/eufemia/src/shared/MediaQuery'
import { Slider, Code, Button, Card, Flex } from '@dnb/eufemia/src'
import {
TestElement,
Field,
FieldBlock,
Form,
} from '@dnb/eufemia/src/extensions/forms'
import { defaultBreakpoints } from '@dnb/eufemia/src/shared/MediaQueryUtils'
import { defaultQueries } from '@dnb/eufemia/src/shared/useMedia'
import { useMedia, useMediaQuery } from '@dnb/eufemia/src/shared'

export const LayoutComponents = () => {
return (
<ComponentBox
scope={{
Field,
Form,
}}
>
<Flex.Stack>
<Form.MainHeading>Profile</Form.MainHeading>

<Card stack>
<Form.SubHeading>Name</Form.SubHeading>

<Field.String label="Fornavn" value="John" />
<Field.String label="Etternavn" value="Smith" />
</Card>

<Card stack>
<Form.SubHeading>More information</Form.SubHeading>

<Field.NationalIdentityNumber value="20058512345" />
<Field.Email value="john@smith.email" />
<Field.PhoneNumber value="+47 98765432" />
</Card>
</Flex.Stack>
</ComponentBox>
)
}

export const colors = [
{ background: '#babeee' } as React.CSSProperties,
{ background: '#dfe0ee' } as React.CSSProperties,
{ background: '#90d2c3' } as React.CSSProperties,
{ background: '#ecf4be' } as React.CSSProperties,
]

export const HorizontalFlexItemResponsiveSize = () => {
return (
<ComponentBox
scope={{ colors, TestElement, Field }}
data-visual-test="flex-item-size"
>
<Flex.Container>
<Flex.Item size={8}>
<TestElement style={colors[0]}>FlexItem (8)</TestElement>
</Flex.Item>
<Flex.Item size={4}>
<TestElement style={colors[1]}>FlexItem (4)</TestElement>
</Flex.Item>
<Flex.Item size={{ small: 12, medium: 4 }}>
<TestElement style={colors[2]}>
FlexItem (small: 8, medium: 4)
</TestElement>
</Flex.Item>
<Flex.Item size={{ small: 12, medium: 8 }}>
<TestElement style={colors[3]}>
FlexItem (small: 4, medium: 8)
</TestElement>
</Flex.Item>
</Flex.Container>
</ComponentBox>
)
}

export const HorizontalFlexItemResponsiveSizeCustomColumns = () => {
return (
<ComponentBox
scope={{
colors,
TestElement,
Field,
defaultBreakpoints,
defaultQueries,
}}
data-visual-test="flex-item-custom-size"
>
{() => {
const breakpoints = {
...defaultBreakpoints,
xsmall: '30em',
}

const queries = {
...defaultQueries,
xsmall: { min: 0, max: 'xsmall' },
small: { min: 'xsmall', max: 'small' },
}

const CustomMediaQuery = styled.div`
display: flex;
flex-direction: column;
.dnb-flex-container[data-media-key='xsmall']
.dnb-flex-item--responsive {
--size: var(--xsmall);
}
`

return (
<CustomMediaQuery>
<Flex.Container
direction="horizontal"
sizeCount={4}
breakpoints={breakpoints}
queries={queries}
>
<Flex.Item size={{ small: 2, medium: 3, large: 1 }}>
<TestElement style={colors[0]}>FlexItem</TestElement>
</Flex.Item>
<Flex.Item size={{ small: 2, medium: 1, large: 2 }}>
<TestElement style={colors[1]}>FlexItem</TestElement>
</Flex.Item>
<Flex.Item
size={{ xsmall: 4, small: 2, medium: 1, large: 1 }}
>
<TestElement style={colors[2]}>FlexItem</TestElement>
</Flex.Item>
<Flex.Item
size={{ xsmall: 4, small: 2, medium: 3, large: 4 }}
>
<TestElement style={colors[3]}>FlexItem</TestElement>
</Flex.Item>
</Flex.Container>
</CustomMediaQuery>
)
}}
</ComponentBox>
)
}

export const HorizontalAutoSize = () => {
return (
<ComponentBox
scope={{
Field,
FieldBlock,
}}
hideCode
>
<FieldBlock label="Label">
<Flex.Container>
<Flex.Item size={{ small: 12, large: 'auto' }}>
<Field.String
path="/firstName"
label="First name"
width="medium"
minLength={2}
/>
</Flex.Item>
<Flex.Item size={{ small: 12, large: 'auto' }}>
<Field.String
path="/lastName"
label="Last name"
width="medium"
required
/>
</Flex.Item>
<Flex.Item size={{ small: 12, large: 'auto' }}>
<FieldBlock width="large">
<Slider
min={1900}
max={new Date().getFullYear()}
step={1}
value={2010}
label="Birth year"
label_direction="vertical"
tooltip
alwaysShowTooltip
/>
</FieldBlock>
</Flex.Item>
</Flex.Container>
</FieldBlock>
</ComponentBox>
)
}

const useWindowWidth = () => {
const [innerWidth, setWidth] = React.useState(
typeof window !== 'undefined' ? window.innerWidth : 0,
)

React.useEffect(() => {
const resizeHandler = () => {
setWidth(window.innerWidth)
}
window.addEventListener('resize', resizeHandler)
return () => window.removeEventListener('resize', resizeHandler)
}, [])

return { innerWidth }
}

export const MediaQueryUseMedia = () => (
<ComponentBox scope={{ useMedia, useWindowWidth }} hideCode>
{() => {
const Playground = () => {
const { isSmall, isMedium, isLarge, isSSR } = useMedia()
const { innerWidth } = useWindowWidth()

return (
<Code>
<pre>
{JSON.stringify(
{ isSmall, isMedium, isLarge, isSSR, innerWidth },
null,
2,
)}
</pre>
</Code>
)
}
return <Playground />
}}
</ComponentBox>
)

export const MediaQueryLiveExample = () => (
<ComponentBox scope={{ MediaQuery, useMediaQuery }} hideCode>
{() => {
const Playground = () => {
const [query, updateQuery] = React.useState({
screen: true,
not: true,
min: 'small',
max: 'large',
})

const match1 = useMediaQuery({
matchOnSSR: true,
when: query,
})
const match2 = useMediaQuery({
matchOnSSR: true,
not: true,
when: query,
})

React.useEffect(() => {
console.log('mediaQuery:', match1, match2)
}, [match1, match2])

return (
<>
<Button
onClick={() => {
updateQuery({
...query,
screen: !query.screen,
})
}}
right
>
Switch
</Button>
<MediaQuery when={query}>
<Code>when</Code>
</MediaQuery>
<MediaQuery not when={query}>
<Code>not when</Code>
</MediaQuery>
</>
)
}
return <Playground />
}}
</ComponentBox>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: 'Flex.Container'
description: '`Flex.Container` is a building block for CSS Grid based layouts.'
hideInMenu: true
showTabs: true
tabs:
- title: Info
key: '/info'
- title: Demos
key: '/demos'
- title: Properties
key: '/properties'
---

import Info from 'Docs/uilib/components/flex/container/info'
import Demos from 'Docs/uilib/components/flex/container/demos'

<Info />
<Demos />
Loading
Loading