Skip to content

Commit 13a83e1

Browse files
components: Add ZStack (#31613)
* components: Add ZStack * components: Export ZStack * Use ReactNodeArray instead of duplicating it * Add explicit children prop * Update story and README * Improve story adding more knobs and colors * Rename offset to overlap * Update packages/components/src/z-stack/README.md Co-authored-by: Marco Ciampini <[email protected]> * New approach to offset * Fix ViewOwnProps import Co-authored-by: Marco Ciampini <[email protected]>
1 parent 2667ceb commit 13a83e1

File tree

9 files changed

+239
-2
lines changed

9 files changed

+239
-2
lines changed

docs/manifest.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,12 @@
12291229
"markdown_source": "../packages/components/src/visually-hidden/README.md",
12301230
"parent": "components"
12311231
},
1232+
{
1233+
"title": "ZStack",
1234+
"slug": "z-stack",
1235+
"markdown_source": "../packages/components/src/z-stack/README.md",
1236+
"parent": "components"
1237+
},
12321238
{
12331239
"title": "Package Reference",
12341240
"slug": "packages",

packages/components/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ export {
146146
useSlot as __experimentalUseSlot,
147147
} from './slot-fill';
148148
export { default as __experimentalStyleProvider } from './style-provider';
149+
export { ZStack as __experimentalZStack } from './z-stack';
149150

150151
// Higher-Order Components
151152
export {

packages/components/src/ui/utils/get-valid-children.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import { Children, isValidElement } from '@wordpress/element';
1212
/**
1313
* Gets a collection of available children elements from a React component's children prop.
1414
*
15-
* @param {import('react').ReactNode} children
15+
* @param children
1616
*
17-
* @return {import('react').ReactNodeArray} An array of available children.
17+
* @return An array of available children.
1818
*/
1919
export function getValidChildren( children: ReactNode ): ReactNodeArray {
2020
if ( typeof children === 'string' ) return [ children ];
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# ZStack
2+
3+
> **Experimental!**
4+
5+
## Usage
6+
7+
`ZStack` allows you to stack things along the Z-axis.
8+
9+
```jsx
10+
import { __experimentalZStack as ZStack } from '@wordpress/components';
11+
12+
function Example() {
13+
return (
14+
<ZStack offset={ 20 } isLayered>
15+
<ExampleImage />
16+
<ExampleImage />
17+
<ExampleImage />
18+
</ZStack>
19+
);
20+
}
21+
```
22+
23+
## Props
24+
25+
### `isLayered`: `boolean`
26+
27+
When `true`, the children are stacked on top of each other. When `false`, the children follow the normal flow of the layout. Defaults to `true`.
28+
29+
### `isReversed`: `boolean`
30+
31+
Reverse the layer ordering. When `true`, the first child has the lowest `z-index` and the last child has the highest `z-index`. When `false`, the first child has the highest `z-index` and the last child has the lowest `z-index`. Defaults to `false`.
32+
33+
### `offset`: `number`
34+
35+
The amount of space between each child element. Defaults to `0`.
36+
37+
### `children`: `ReactNode`
38+
39+
The children to stack.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import { css, cx } from 'emotion';
5+
// eslint-disable-next-line no-restricted-imports
6+
import type { Ref, ReactNode } from 'react';
7+
8+
/**
9+
* WordPress dependencies
10+
*/
11+
import { isValidElement } from '@wordpress/element';
12+
13+
/**
14+
* Internal dependencies
15+
*/
16+
import { getValidChildren } from '../ui/utils/get-valid-children';
17+
import { contextConnect, useContextSystem } from '../ui/context';
18+
// eslint-disable-next-line no-duplicate-imports
19+
import type { PolymorphicComponentProps } from '../ui/context';
20+
import { View } from '../view';
21+
import * as styles from './styles';
22+
const { ZStackView } = styles;
23+
24+
export interface ZStackProps {
25+
/**
26+
* Layers children elements on top of each other (first: highest z-index, last: lowest z-index).
27+
*
28+
* @default true
29+
*/
30+
isLayered?: boolean;
31+
/**
32+
* Reverse the layer ordering (first: lowest z-index, last: highest z-index).
33+
*
34+
* @default false
35+
*/
36+
isReversed?: boolean;
37+
/**
38+
* The amount of offset between each child element.
39+
*
40+
* @default 0
41+
*/
42+
offset?: number;
43+
/**
44+
* Child elements.
45+
*/
46+
children: ReactNode;
47+
}
48+
49+
function ZStack(
50+
props: PolymorphicComponentProps< ZStackProps, 'div' >,
51+
forwardedRef: Ref< any >
52+
) {
53+
const {
54+
children,
55+
className,
56+
isLayered = true,
57+
isReversed = false,
58+
offset = 0,
59+
...otherProps
60+
} = useContextSystem( props, 'ZStack' );
61+
62+
const validChildren = getValidChildren( children );
63+
const childrenLastIndex = validChildren.length - 1;
64+
65+
const clonedChildren = validChildren.map( ( child, index ) => {
66+
const zIndex = isReversed ? childrenLastIndex - index : index;
67+
const offsetAmount = offset * index;
68+
69+
const classes = cx(
70+
isLayered ? styles.positionAbsolute : styles.positionRelative,
71+
css( {
72+
...( isLayered
73+
? { marginLeft: offsetAmount }
74+
: { right: offsetAmount * -1 } ),
75+
} )
76+
);
77+
78+
const key = isValidElement( child ) ? child.key : index;
79+
80+
return (
81+
<View
82+
className={ classes }
83+
key={ key }
84+
style={ {
85+
zIndex,
86+
} }
87+
>
88+
{ child }
89+
</View>
90+
);
91+
} );
92+
93+
return (
94+
<ZStackView
95+
{ ...otherProps }
96+
className={ className }
97+
ref={ forwardedRef }
98+
>
99+
{ clonedChildren }
100+
</ZStackView>
101+
);
102+
}
103+
104+
export default contextConnect( ZStack, 'ZStack' );
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as ZStack } from './component';
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import { boolean, number } from '@storybook/addon-knobs';
5+
6+
/**
7+
* Internal dependencies
8+
*/
9+
import { Elevation } from '../../ui/elevation';
10+
import { HStack } from '../../h-stack';
11+
import { View } from '../../view';
12+
import { ZStack } from '..';
13+
14+
export default {
15+
component: ZStack,
16+
title: 'Components (Experimental)/ZStack',
17+
};
18+
19+
const Avatar = ( { backgroundColor } ) => {
20+
return (
21+
<View>
22+
<View
23+
style={ {
24+
border: '3px solid black',
25+
borderRadius: '9999px',
26+
height: '48px',
27+
width: '48px',
28+
backgroundColor,
29+
} }
30+
/>
31+
<Elevation
32+
borderRadius={ 9999 }
33+
isInteractive={ false }
34+
value={ 3 }
35+
/>
36+
</View>
37+
);
38+
};
39+
40+
const AnimatedAvatars = () => {
41+
const props = {
42+
offset: number( 'offset', 20 ),
43+
isLayered: boolean( 'isLayered', true ),
44+
isReversed: boolean( 'isReversed', false ),
45+
};
46+
47+
return (
48+
<HStack>
49+
<View>
50+
<ZStack { ...props }>
51+
<Avatar backgroundColor="#444" />
52+
<Avatar backgroundColor="#777" />
53+
<Avatar backgroundColor="#aaa" />
54+
<Avatar backgroundColor="#fff" />
55+
</ZStack>
56+
</View>
57+
</HStack>
58+
);
59+
};
60+
61+
export const _default = () => {
62+
return (
63+
<View>
64+
<AnimatedAvatars />
65+
</View>
66+
);
67+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import { css } from 'emotion';
5+
import styled from '@emotion/styled';
6+
7+
export const ZStackView = styled.div`
8+
display: flex;
9+
position: relative;
10+
`;
11+
12+
export const positionAbsolute = css`
13+
position: absolute;
14+
`;
15+
16+
export const positionRelative = css`
17+
position: relative;
18+
`;

packages/components/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"src/__next/**/*",
3737
"src/h-stack/**/*",
3838
"src/v-stack/**/*",
39+
"src/z-stack/**/*",
3940
"src/view/**/*"
4041
],
4142
"exclude": [

0 commit comments

Comments
 (0)