├── src
├── components
│ ├── Spacer
│ │ ├── Spacer.module.css
│ │ ├── index.js
│ │ ├── Spacer.stories.jsx
│ │ └── Spacer.jsx
│ ├── Grid
│ │ ├── index.js
│ │ ├── Grid.module.css
│ │ ├── Grid.jsx
│ │ └── Grid.stories.jsx
│ ├── Flex
│ │ ├── index.js
│ │ ├── Flex.module.css
│ │ ├── Flex.stories.jsx
│ │ └── Flex.jsx
│ ├── Frame
│ │ ├── index.js
│ │ ├── Frame.stories.jsx
│ │ ├── Frame.jsx
│ │ └── Frame.module.css
│ ├── View
│ │ ├── index.js
│ │ ├── View.stories.jsx
│ │ ├── View.module.css
│ │ └── View.jsx
│ ├── Center
│ │ ├── index.js
│ │ ├── Center.module.css
│ │ ├── Center.jsx
│ │ └── Center.stories.jsx
│ ├── Cluster
│ │ ├── index.js
│ │ ├── Cluster.module.css
│ │ ├── Cluster.jsx
│ │ └── Cluster.stories.jsx
│ ├── VStack
│ │ ├── index.js
│ │ ├── VStack.module.css
│ │ ├── VStack.jsx
│ │ └── VStack.stories.jsx
│ ├── ZStack
│ │ ├── index.js
│ │ ├── ZStack.module.css
│ │ ├── ZStack.stories.jsx
│ │ └── ZStack.jsx
│ └── HStack
│ │ ├── index.js
│ │ ├── HStack.module.css
│ │ ├── HStack.jsx
│ │ └── HStack.stories.jsx
└── index.js
├── .gitignore
├── .storybook
├── preview-head.html
├── preview.js
└── main.js
├── LICENSE
├── package.json
├── CHANGELOG.md
└── README.md
/src/components/Spacer/Spacer.module.css:
--------------------------------------------------------------------------------
1 | .spacer {
2 | flex: 1 1 0%;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Grid/index.js:
--------------------------------------------------------------------------------
1 | import { Grid } from './Grid'
2 |
3 | export default Grid
--------------------------------------------------------------------------------
/src/components/Flex/index.js:
--------------------------------------------------------------------------------
1 | import { Flex } from './Flex'
2 |
3 | export default Flex
4 |
--------------------------------------------------------------------------------
/src/components/Frame/index.js:
--------------------------------------------------------------------------------
1 | import { Frame } from './Frame'
2 |
3 | export default Frame
--------------------------------------------------------------------------------
/src/components/View/index.js:
--------------------------------------------------------------------------------
1 | import { View } from './View'
2 |
3 | export default View
4 |
--------------------------------------------------------------------------------
/src/components/Center/index.js:
--------------------------------------------------------------------------------
1 | import { Center } from './Center'
2 |
3 | export default Center
--------------------------------------------------------------------------------
/src/components/Cluster/index.js:
--------------------------------------------------------------------------------
1 | import { Cluster } from './Cluster'
2 |
3 | export default Cluster
--------------------------------------------------------------------------------
/src/components/Spacer/index.js:
--------------------------------------------------------------------------------
1 | import { Spacer } from './Spacer'
2 |
3 | export default Spacer
--------------------------------------------------------------------------------
/src/components/VStack/index.js:
--------------------------------------------------------------------------------
1 | import { VStack } from './VStack'
2 |
3 | export default VStack
--------------------------------------------------------------------------------
/src/components/ZStack/index.js:
--------------------------------------------------------------------------------
1 | import { ZStack } from './ZStack'
2 |
3 | export default ZStack
--------------------------------------------------------------------------------
/src/components/HStack/index.js:
--------------------------------------------------------------------------------
1 | import { HStack } from './HStack'
2 |
3 | export default HStack
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | .cache
5 | dist
6 | .next
7 | docs
8 | storybook-static
--------------------------------------------------------------------------------
/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/Cluster/Cluster.module.css:
--------------------------------------------------------------------------------
1 | .cluster {
2 | flex-wrap: wrap;
3 | gap: var(--layout-cluster-spacing, 1rem);
4 | }
5 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | export const parameters = {
2 | actions: { argTypesRegex: "^on[A-Z].*" },
3 | controls: {
4 | matchers: {
5 | color: /(background|color)$/i,
6 | date: /Date$/,
7 | },
8 | },
9 | }
--------------------------------------------------------------------------------
/src/components/Flex/Flex.module.css:
--------------------------------------------------------------------------------
1 | .flex {
2 | align-items: var(--layout-flex-align, 'center');
3 | display: flex;
4 | flex-direction: var(--layout-flex-direction, 'row');
5 | justify-content: var(--layout-flex-justify, 'center');
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/HStack/HStack.module.css:
--------------------------------------------------------------------------------
1 | .hstack {
2 | height: 100%;
3 | }
4 |
5 | .hstack > * {
6 | margin-left: 0;
7 | margin-right: 0;
8 | }
9 |
10 | .hstack > * + * {
11 | margin-left: var(--layout-hstack-spacing, 0);
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/VStack/VStack.module.css:
--------------------------------------------------------------------------------
1 | .vstack {
2 | height: 100%;
3 | }
4 |
5 | .vstack > * {
6 | margin-top: 0;
7 | margin-bottom: 0;
8 | }
9 |
10 | .vstack > * + * {
11 | margin-top: var(--layout-vstack-spacing, 0);
12 | }
13 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: ['../**/*.stories.mdx', '../**/*.stories.@(js|jsx|ts|tsx)'],
3 | addons: [
4 | '@storybook/addon-docs',
5 | '@storybook/addon-links',
6 | '@storybook/addon-essentials',
7 | 'storybook-css-modules-preset',
8 | ],
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/Center/Center.module.css:
--------------------------------------------------------------------------------
1 | .center {
2 | box-sizing: content-box;
3 | margin-left: auto;
4 | margin-right: auto;
5 | max-inline-size: var(--layout-center-max-width);
6 | height: 100%;
7 | }
8 |
9 | @supports not (max-inline-size: var(--layout-center-max-width)) {
10 | .center {
11 | max-width: var(--layout-center-max-width);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/Spacer/Spacer.stories.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { Spacer } from './Spacer'
4 |
5 | export default {
6 | title: 'Layout/Spacer',
7 | component: Spacer,
8 | argTypes: {}
9 | }
10 |
11 | const Template = (args) => (
12 |
13 | )
14 |
15 | export const Default = Template.bind({})
16 | Default.args = {}
17 |
18 |
--------------------------------------------------------------------------------
/src/components/Grid/Grid.module.css:
--------------------------------------------------------------------------------
1 | .grid {
2 | display: grid;
3 | gap: var(--layout-grid-spacing, 1rem);
4 | height: 100%;
5 | }
6 |
7 | @supports (width: min(var(--layout-grid-min-width, 250px), 100%)) {
8 | .grid {
9 | grid-template-columns: repeat(
10 | auto-fit,
11 | minmax(min(var(--layout-grid-min-width, 250px), 100%), 1fr)
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Spacer/Spacer.jsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import View from '../View'
3 | import styles from './Spacer.module.css'
4 |
5 | export const Spacer = React.forwardRef(
6 | ({ as: Tag = 'div', children, className = '', ...props }, ref) => {
7 | const sharedProps = {
8 | ...props,
9 | ref,
10 | as: Tag,
11 | className: `${styles.spacer} ${className}`.trim(),
12 | }
13 |
14 | return {children}
15 | }
16 | )
17 |
--------------------------------------------------------------------------------
/src/components/Frame/Frame.stories.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { Frame } from './Frame'
4 |
5 | export default {
6 | title: 'Layout/Frame',
7 | component: Frame,
8 | argTypes: {},
9 | }
10 |
11 | const Template = (args) => (
12 |
13 |
14 |
15 | )
16 |
17 | export const Default = Template.bind({})
18 | Default.args = {}
19 |
--------------------------------------------------------------------------------
/src/components/Flex/Flex.stories.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { Flex } from '../../../dist'
4 |
5 | export default {
6 | title: 'Layout/Flex',
7 | component: Flex,
8 | }
9 |
10 | const Template = args => (
11 |
12 | One
13 | Two
14 | Three
15 | Four
16 |
17 | )
18 |
19 | export const Row = Template.bind({})
20 | Row.args = {
21 | direction: 'row',
22 | }
23 |
24 | export const Column = Template.bind({})
25 | Column.args = {
26 | direction: 'column',
27 | }
28 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as Center } from './components/Center'
2 | export { default as Cluster } from './components/Cluster'
3 | export { default as Frame } from './components/Frame'
4 | export { default as Grid } from './components/Grid'
5 | export { default as VStack } from './components/VStack'
6 | export { default as HStack } from './components/HStack'
7 | export { default as ZStack } from './components/ZStack'
8 | export { default as Spacer } from './components/Spacer'
9 | export { default as Flex } from './components/Flex'
10 | export { default as View } from './components/View'
11 |
--------------------------------------------------------------------------------
/src/components/View/View.stories.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { View } from '../../../dist'
4 |
5 | export default {
6 | title: 'Layout/View',
7 | component: View,
8 | argTypes: {
9 | padding: { control: 'text' },
10 | color: { control: 'text' },
11 | backgroundColor: { control: 'text' },
12 | width: { control: 'text' },
13 | height: { control: 'text' },
14 | },
15 | }
16 |
17 | const Template = args => Some text
18 |
19 | export const Default = Template.bind({})
20 | export const WidthAndHeight = Template.bind({})
21 | WidthAndHeight.args = {
22 | width: '800px',
23 | height: '600px',
24 | padding: '4rem',
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/ZStack/ZStack.module.css:
--------------------------------------------------------------------------------
1 | .zstack {
2 | display: grid;
3 | grid-template-columns: 1fr;
4 | grid-template-rows: 1fr;
5 | grid-template-areas: 'zstack';
6 | justify-items: var(--layout-zstack-justify);
7 | align-items: var(--layout-zstack-align);
8 | }
9 |
10 | .zstack > * {
11 | grid-area: zstack;
12 | z-index: var(--layout-zstack-zIndex);
13 | margin-top: calc(var(--layout-zstack-marginTop) * var(--layout-zstack-layer));
14 | margin-right: calc(var(--layout-zstack-marginRight) * var(--layout-zstack-layer));
15 | margin-bottom: calc(var(--layout-zstack-marginBottom) * var(--layout-zstack-layer));
16 | margin-left: calc(var(--layout-zstack-marginLeft) * var(--layout-zstack-layer));
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/View/View.module.css:
--------------------------------------------------------------------------------
1 | .view {
2 | box-sizing: border-box;
3 | padding-block-start: var(--layout-padding-top, 0);
4 | padding-inline-end: var(--layout-padding-right, 0);
5 | padding-block-end: var(--layout-padding-bottom, 0);
6 | padding-inline-start: var(--layout-padding-left, 0);
7 | border: var(--layout-border-width) var(--layout-border-style)
8 | var(--layout-border-color);
9 | border-radius: var(layout-border-radius);
10 | box-shadow: var(layout-shadow);
11 | }
12 |
13 | @supports not (padding-block: var(--layout-padding-top, 0)) {
14 | .view {
15 | padding-top: var(--layout-padding-top, 0);
16 | padding-right: var(--layout-padding-right, 0);
17 | padding-bottom: var(--layout-padding-bottom, 0);
18 | padding-left: var(--layout-padding-left, 0);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Grid/Grid.jsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import View from '../View'
3 | import styles from './Grid.module.css'
4 |
5 | export const Grid = React.forwardRef(
6 | (
7 | {
8 | as: Tag = 'div',
9 | children,
10 | className = '',
11 | spacing = '1rem',
12 | min = '250px',
13 | style: passedInStyles,
14 | ...props
15 | },
16 | ref
17 | ) => {
18 | const style = {
19 | ...passedInStyles,
20 | '--layout-grid-spacing': spacing,
21 | '--layout-grid-min-width': min,
22 | }
23 |
24 | const sharedProps = {
25 | ...props,
26 | ref,
27 | as: Tag,
28 | className: `${styles.grid} ${className}`.trim(),
29 | style,
30 | }
31 |
32 | return {children}
33 | }
34 | )
35 |
--------------------------------------------------------------------------------
/src/components/Frame/Frame.jsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import View from '../View'
3 | import styles from './Frame.module.css'
4 |
5 | export const Frame = React.forwardRef(
6 | (
7 | {
8 | as: Tag = 'div',
9 | children,
10 | className = '',
11 | style: passedInStyles,
12 | ratio = '16:9',
13 | ...props
14 | },
15 | ref
16 | ) => {
17 | const [ratioWidth, ratioHeight] = ratio.split(/\/|:/)
18 |
19 | const style = {
20 | ...passedInStyles,
21 | '--layout-frame-ratio-width': ratioWidth,
22 | '--layout-frame-ratio-height': ratioHeight,
23 | }
24 |
25 | const sharedProps = {
26 | ...props,
27 | ref,
28 | as: Tag,
29 | className: `${styles.frame} ${className}`.trim(),
30 | style,
31 | }
32 |
33 | return {children}
34 | }
35 | )
36 |
--------------------------------------------------------------------------------
/src/components/Cluster/Cluster.jsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styles from './Cluster.module.css'
3 | import Flex from '../Flex'
4 |
5 | export const Cluster = React.forwardRef(
6 | (
7 | {
8 | align = 'center',
9 | justify = 'flex-start',
10 | as: Tag = 'div',
11 | children,
12 | className = '',
13 | spacing = '1rem',
14 | style: passedInStyles,
15 | ...props
16 | },
17 | ref
18 | ) => {
19 | const style = {
20 | ...passedInStyles,
21 | '--layout-cluster-spacing': spacing,
22 | }
23 |
24 | const sharedProps = {
25 | ...props,
26 | ref,
27 | as: Tag,
28 | className: `${styles.cluster} ${className}`.trim(),
29 | style,
30 | }
31 |
32 | return (
33 |
34 | {children}
35 |
36 | )
37 | }
38 | )
39 |
--------------------------------------------------------------------------------
/src/components/Frame/Frame.module.css:
--------------------------------------------------------------------------------
1 | .frame {
2 | position: relative;
3 | width: 100%;
4 | }
5 |
6 | .frame > * {
7 | overflow: hidden;
8 | position: absolute;
9 | top: 0;
10 | right: 0;
11 | bottom: 0;
12 | left: 0;
13 | display: flex;
14 | justify-content: center;
15 | align-items: center;
16 | }
17 |
18 | .frame > img,
19 | .frame > video {
20 | width: 100%;
21 | height: 100%;
22 | object-fit: cover;
23 | }
24 |
25 | @supports (
26 | aspect-ratio: var(--layout-frame-ratio-width) / var(--layout-frame-ratio-height)
27 | ) {
28 | .frame {
29 | aspect-ratio: var(--layout-frame-ratio-width) / var(--layout-frame-ratio-height);
30 | }
31 | }
32 |
33 | @supports not (
34 | aspect-ratio: var(--layout-frame-ratio-width) / var(--layout-frame-ratio-height)
35 | ) {
36 | .frame {
37 | padding-bottom: calc(
38 | var(--layout-frame-ratio-height) / var(--layout-frame-ratio-width) * 100%
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/Center/Center.jsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import View from '../View'
3 | import Flex from '../Flex'
4 | import styles from './Center.module.css'
5 |
6 | export const Center = React.forwardRef(
7 | (
8 | {
9 | as: Tag = 'div',
10 | children,
11 | max,
12 | gutter = 0,
13 | centerChildren = false,
14 | className = '',
15 | style: passedInStyles,
16 | ...props
17 | },
18 | ref
19 | ) => {
20 | const style = {
21 | ...passedInStyles,
22 | '--layout-center-max-width': max,
23 | }
24 |
25 | const sharedProps = {
26 | ...props,
27 | ref,
28 | as: Tag,
29 | className: `${styles.center} ${className}`.trim(),
30 | style,
31 | paddingInline: gutter,
32 | }
33 |
34 | return centerChildren ? (
35 |
36 | {children}
37 |
38 | ) : (
39 | {children}
40 | )
41 | }
42 | )
43 |
--------------------------------------------------------------------------------
/src/components/ZStack/ZStack.stories.jsx:
--------------------------------------------------------------------------------
1 | import { ZStack, View, HStack } from '../../../dist'
2 |
3 | export default {
4 | title: 'Layout/ZStack',
5 | component: ZStack,
6 | argTypes: {},
7 | }
8 |
9 | const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'purple']
10 |
11 | const CaptionTemplate = args => {
12 | return (
13 |
14 |
21 |
28 | A Caption
29 |
30 |
31 | )
32 | }
33 |
34 | export const Caption = CaptionTemplate.bind({})
35 | Caption.args = {}
36 |
--------------------------------------------------------------------------------
/src/components/Center/Center.stories.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { Center } from './Center'
4 |
5 | export default {
6 | title: 'Layout/Center',
7 | component: Center,
8 | argTypes: {},
9 | }
10 |
11 | const Template = (args) => (
12 |
13 |
19 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
20 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
21 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
22 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
23 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
24 | cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
25 | est laborum.
26 |
27 | Lorem ipsum dolor sit amet
28 |
29 | )
30 |
31 | export const Default = Template.bind({})
32 | Default.args = {}
33 |
--------------------------------------------------------------------------------
/src/components/HStack/HStack.jsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Flex from '../Flex'
3 | import styles from './HStack.module.css'
4 |
5 | export const supportedValues = {
6 | alignment: {
7 | top: 'flex-start',
8 | center: 'center',
9 | bottom: 'flex-end',
10 | },
11 | }
12 |
13 | export const HStack = React.forwardRef(
14 | (
15 | {
16 | alignment = 'center',
17 | as: Tag = 'div',
18 | children,
19 | className = '',
20 | spacing = '0',
21 | style: passedInStyles,
22 | ...props
23 | },
24 | ref
25 | ) => {
26 | const style = {
27 | ...passedInStyles,
28 | '--layout-hstack--spacing': spacing,
29 | }
30 |
31 | const sharedProps = {
32 | ...props,
33 | ref,
34 | as: Tag,
35 | className: `${styles.hstack} ${className}`.trim(),
36 | style,
37 | }
38 |
39 | return (
40 |
45 | {children}
46 |
47 | )
48 | }
49 | )
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Chad Donohue
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/src/components/VStack/VStack.jsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Flex from '../Flex'
3 | import styles from './VStack.module.css'
4 |
5 | export const supportedValues = {
6 | alignment: {
7 | leading: 'flex-start',
8 | center: 'center',
9 | trailing: 'flex-end',
10 | },
11 | }
12 |
13 | export const VStack = React.forwardRef(
14 | (
15 | {
16 | alignment = 'center',
17 | as: Tag = 'div',
18 | children,
19 | className = '',
20 | spacing = '0',
21 | style: passedInStyles,
22 | ...props
23 | },
24 | ref
25 | ) => {
26 | const style = {
27 | ...passedInStyles,
28 | '--layout-vstack-spacing': spacing,
29 | }
30 |
31 | const sharedProps = {
32 | ...props,
33 | ref,
34 | as: Tag,
35 | className: `${styles.vstack} ${className}`.trim(),
36 | style,
37 | }
38 |
39 | return (
40 |
46 | {children}
47 |
48 | )
49 | }
50 | )
51 |
--------------------------------------------------------------------------------
/src/components/Flex/Flex.jsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import View from '../View'
3 | import styles from './Flex.module.css'
4 |
5 | export const supportedValues = {
6 | align: ['flex-start', 'center', 'flex-end', 'stretch', 'baseline'],
7 | direction: ['column', 'row'],
8 | justify: [
9 | 'flex-start',
10 | 'center',
11 | 'flex-end',
12 | 'space-between',
13 | 'space-around',
14 | 'space-evenly',
15 | ],
16 | }
17 |
18 | export const Flex = React.forwardRef(
19 | (
20 | {
21 | align = 'center',
22 | as: Tag = 'div',
23 | children,
24 | className = '',
25 | direction = 'row',
26 | justify = 'center',
27 | style: passedInStyles,
28 | ...props
29 | },
30 | ref
31 | ) => {
32 | const style = {
33 | ...passedInStyles,
34 | '--layout-flex-align': align,
35 | '--layout-flex-direction': direction,
36 | '--layout-flex-justify': justify,
37 | }
38 |
39 | const sharedProps = {
40 | ...props,
41 | ref,
42 | as: Tag,
43 | className: `${styles.flex} ${className}`.trim(),
44 | style,
45 | }
46 |
47 | return {children}
48 | }
49 | )
50 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.2.0",
3 | "license": "MIT",
4 | "main": "dist/index.js",
5 | "files": [
6 | "dist"
7 | ],
8 | "scripts": {
9 | "storybook": "start-storybook -p 6006",
10 | "build-storybook": "build-storybook",
11 | "version": "auto-changelog -p --template keepachangelog && git add CHANGELOG.md"
12 | },
13 | "peerDependencies": {
14 | "react": ">=16.8"
15 | },
16 | "prettier": {
17 | "printWidth": 80,
18 | "semi": false,
19 | "singleQuote": true,
20 | "trailingComma": "es5"
21 | },
22 | "name": "layout-blocks",
23 | "author": "Chad Donohue",
24 | "keywords": [
25 | "react",
26 | "layout",
27 | "ui",
28 | "components"
29 | ],
30 | "module": "dist/layout-blocks.esm.js",
31 | "devDependencies": {
32 | "@babel/core": "^7.16.5",
33 | "@storybook/addon-actions": "^6.4.9",
34 | "@storybook/addon-docs": "^6.4.9",
35 | "@storybook/addon-essentials": "^6.4.9",
36 | "@storybook/addon-links": "^6.4.9",
37 | "@storybook/react": "^6.4.9",
38 | "auto-changelog": "^2.3.0",
39 | "babel-loader": "^8.2.3",
40 | "css-tree": "^2.0.3",
41 | "esbuild": "^0.14.5",
42 | "esbuild-css-modules-plugin": "^2.0.9",
43 | "np": "^7.6.0",
44 | "open-props": "^1.0.13",
45 | "react": "^17.0.2",
46 | "react-dom": "^17.0.2",
47 | "storybook-css-modules-preset": "^1.1.1"
48 | },
49 | "description": "Reusable layout components for your React project",
50 | "repository": "https://github.com/cdonohue/layout-blocks.git",
51 | "dependencies": {}
52 | }
53 |
--------------------------------------------------------------------------------
/src/components/Cluster/Cluster.stories.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { Cluster } from '../../../dist'
4 |
5 | export default {
6 | title: 'Layout/Cluster',
7 | component: Cluster,
8 | argTypes: {},
9 | }
10 |
11 | const Template = args => (
12 |
13 |
20 | One
21 |
22 |
29 | Two
30 |
31 |
38 | Three
39 |
40 |
41 | )
42 |
43 | const Link = ({ children }) => (
44 |
50 | {children}
51 |
52 | )
53 |
54 | const NavbarExample = args => (
55 |
56 |
62 |
63 | About
64 | Blog
65 | Contact
66 |
67 |
68 | )
69 |
70 | export const Default = Template.bind({})
71 | Default.args = {}
72 |
73 | export const Navbar = NavbarExample.bind({})
74 | Navbar.args = {}
75 |
--------------------------------------------------------------------------------
/src/components/HStack/HStack.stories.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { HStack } from '../../../dist'
4 | import View from '../View'
5 |
6 | export default {
7 | title: 'Layout/HStack',
8 | component: HStack,
9 | argTypes: Object.entries(supportedValues).reduce((memo, [key, options]) => {
10 | memo[key] = {
11 | options: Object.keys(options),
12 | }
13 |
14 | return memo
15 | }, {}),
16 | }
17 |
18 | const Template = args => (
19 |
20 |
21 | One
22 |
23 |
24 | A lot of text to show
25 |
26 |
27 | Slow-carb copper mug sartorial put a bird on it single-origin coffee
28 | austin pork belly etsy shoreditch tousled seitan. Waistcoat art party man
29 | bun intelligentsia banjo. Cold-pressed plaid hella leggings snackwave DIY
30 | echo park man braid synth palo santo tilde. Brunch literally green juice
31 | +1. Try-hard vexillologist etsy enamel pin.
32 |
33 |
34 | Four
35 |
36 |
37 | )
38 |
39 | export const Default = Template.bind({})
40 |
41 | export const WithSpacing = Template.bind({})
42 | WithSpacing.args = {
43 | spacing: '24px',
44 | }
45 |
46 | export const Top = Template.bind({})
47 | Top.args = {
48 | alignment: 'top',
49 | }
50 |
51 | export const Bottom = Template.bind({})
52 | Bottom.args = {
53 | alignment: 'bottom',
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/View/View.jsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styles from './View.module.css'
3 |
4 | export const View = React.forwardRef(
5 | (
6 | {
7 | as: Tag = 'div',
8 | children,
9 | className = '',
10 | padding = '',
11 | paddingInline = '',
12 | paddingBlock = '',
13 | paddingTop = '0',
14 | paddingRight = '0',
15 | paddingBottom = '0',
16 | paddingLeft = '0',
17 | borderRadius = '0',
18 | borderColor = 'black',
19 | borderStyle = 'solid',
20 | borderWidth = '0',
21 | shadow = 'none',
22 | style: passedInStyles,
23 | ...props
24 | },
25 | ref
26 | ) => {
27 | if (paddingInline) {
28 | paddingLeft = paddingInline
29 | paddingRight = paddingInline
30 | }
31 |
32 | if (paddingBlock) {
33 | paddingTop = paddingBlock
34 | paddingBottom = paddingBlock
35 | }
36 |
37 | if (padding) {
38 | paddingLeft = padding
39 | paddingRight = padding
40 | paddingTop = padding
41 | paddingBottom = padding
42 | }
43 |
44 | const style = {
45 | ...passedInStyles,
46 | '--layout-padding-top': paddingTop,
47 | '--layout-padding-right': paddingRight,
48 | '--layout-padding-bottom': paddingBottom,
49 | '--layout-padding-left': paddingLeft,
50 | '--layout-border-radius': borderRadius,
51 | '--layout-border-color': borderColor,
52 | '--layout-border-style': borderStyle,
53 | '--layout-border-width': borderWidth,
54 | '--layout-shadow': shadow,
55 | }
56 |
57 | const sharedProps = {
58 | ...props,
59 | ref,
60 | className: `${styles.view} ${className}`.trim(),
61 | style,
62 | }
63 |
64 | return {children}
65 | }
66 | )
67 |
--------------------------------------------------------------------------------
/src/components/VStack/VStack.stories.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { VStack, View, Frame } from '../../../dist'
4 |
5 | export default {
6 | title: 'Layout/VStack',
7 | component: VStack,
8 | argTypes: Object.entries(supportedValues).reduce((memo, [key, options]) => {
9 | memo[key] = {
10 | options: Object.keys(options),
11 | }
12 |
13 | return memo
14 | }, {}),
15 | }
16 |
17 | const Template = args => (
18 |
24 |
25 |
26 |
27 |
28 |
29 | )
30 |
31 | const Card = args => (
32 |
33 | Card Heading
34 |
35 |
36 |
37 |
38 | Slow-carb copper mug sartorial put a bird on it single-origin coffee
39 | austin pork belly etsy shoreditch tousled seitan. Waistcoat art party man
40 | bun intelligentsia banjo. Cold-pressed plaid hella leggings snackwave DIY
41 | echo park man braid synth palo santo tilde. Brunch literally green juice
42 | +1. Try-hard vexillologist etsy enamel pin.
43 |
44 |
45 | )
46 |
47 | export const Default = Template.bind({})
48 |
49 | export const Leading = Template.bind({})
50 | Leading.args = {
51 | alignment: 'leading',
52 | }
53 |
54 | export const Trailing = Template.bind({})
55 | Trailing.args = {
56 | alignment: 'trailing',
57 | }
58 |
59 | export const CardLayout = Card.bind({})
60 | CardLayout.args = {
61 | alignment: 'leading',
62 | }
63 |
--------------------------------------------------------------------------------
/src/components/Grid/Grid.stories.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { Grid } from './Grid'
4 | import Frame from '../Frame'
5 | import VStack from '../VStack'
6 | import Cluster from '../Cluster'
7 | import Center from '../Center'
8 | import Flex from '../Flex'
9 |
10 | export default {
11 | title: 'Layout/Grid',
12 | component: Grid,
13 | argTypes: {},
14 | }
15 |
16 | const Template = (args) => (
17 |
18 |
19 | Some words
20 |
21 |
22 | Some words
23 |
24 |
25 | Some words
26 |
27 |
28 | Some words
29 |
30 |
31 | Some words
32 |
33 |
34 | )
35 |
36 | const Button = ({ primary = false, children }) => (
37 |
49 | {children}
50 |
51 | )
52 |
53 | const Hero = () => (
54 |
55 |
56 |
57 |
63 | Hero layout with
64 | Open Props
65 |
66 |
67 | Lorem ipsum dolor sit amet consectetu adipisicing elit. Nemo in
68 | doloremque quam, voluptatibus eum voluptatum.
69 |
70 |
71 | Get started
72 | Live demo
73 |
74 |
75 |
76 |
77 |
78 | )
79 |
80 | export const Default = Template.bind({})
81 | Default.args = {}
82 |
83 | export const HeroLayout = Hero.bind({})
84 | HeroLayout.args = {}
85 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
9 |
10 | ## [v1.2.0](https://github.com/cdonohue/layout-blocks/compare/v1.1.1...v1.2.0)
11 |
12 | ### Commits
13 |
14 | - Add back storybook [`c7576f2`](https://github.com/cdonohue/layout-blocks/commit/c7576f23de86dd70adf14922b0271be7051dce92)
15 |
16 | ## [v1.1.1](https://github.com/cdonohue/layout-blocks/compare/v1.1.0...v1.1.1) - 2021-12-15
17 |
18 | ### Commits
19 |
20 | - Add Flex to exports [`58c53fb`](https://github.com/cdonohue/layout-blocks/commit/58c53fbe79e895a1872067cfc770502ca77b3e52)
21 |
22 | ## [v1.1.0](https://github.com/cdonohue/layout-blocks/compare/v1.0.0...v1.1.0) - 2021-12-15
23 |
24 | ### Commits
25 |
26 | - Update package [`26ae712`](https://github.com/cdonohue/layout-blocks/commit/26ae7121b5f714a88c56c3550d022bdeb85cc24b)
27 | - Refactor and use CSS modules for styling [`cc2e685`](https://github.com/cdonohue/layout-blocks/commit/cc2e685d324b108a4c5e87ec375c17afc502ae62)
28 | - Updates [`d90b7aa`](https://github.com/cdonohue/layout-blocks/commit/d90b7aa1d2b0d2b6a497222d33ee28405e64dc02)
29 |
30 | ## [v1.0.0](https://github.com/cdonohue/layout-blocks/compare/v0.3.0...v1.0.0) - 2020-07-29
31 |
32 | ### Merged
33 |
34 | - Bump websocket-extensions from 0.1.3 to 0.1.4 [`#6`](https://github.com/cdonohue/layout-blocks/pull/6)
35 |
36 | ### Commits
37 |
38 | - Refactor a bunch and start fresh [`6473e4b`](https://github.com/cdonohue/layout-blocks/commit/6473e4b12055fda117c334bceab8b4357bba2916)
39 | - Add version and publish scripts [`63572b1`](https://github.com/cdonohue/layout-blocks/commit/63572b1de2ca045eca562501b97388287abbaae5)
40 | - Update readme and various tweaks [`5e35186`](https://github.com/cdonohue/layout-blocks/commit/5e351864dd41cfe1d8ea91df085f099e54c42f22)
41 |
42 | ## [v0.3.0](https://github.com/cdonohue/layout-blocks/compare/v0.2.1...v0.3.0) - 2020-05-11
43 |
44 | ## [v0.2.1](https://github.com/cdonohue/layout-blocks/compare/v0.2.0...v0.2.1) - 2020-05-11
45 |
46 | ### Merged
47 |
48 | - CodeSandbox Story [`#5`](https://github.com/cdonohue/layout-blocks/pull/5)
49 | - Add view component and stories [`#4`](https://github.com/cdonohue/layout-blocks/pull/4)
50 | - Adds storybook [`#3`](https://github.com/cdonohue/layout-blocks/pull/3)
51 |
52 | ### Commits
53 |
54 | - Cleanup packages [`871573b`](https://github.com/cdonohue/layout-blocks/commit/871573b28bdfb35560c82573f526c9b62e9b8824)
55 | - Rewrite all components to use a styled wrapper [`41f4db8`](https://github.com/cdonohue/layout-blocks/commit/41f4db8bbc36e81bb470100925325884dfcfb145)
56 | - Extend everything from Box and update tailwindUI example [`c7b5da2`](https://github.com/cdonohue/layout-blocks/commit/c7b5da2da1d390c006b451e4667667cd52c1ad1e)
57 |
58 | ## v0.2.0 - 2020-03-08
59 |
60 | ### Commits
61 |
62 | - Initial commit [`f0ee7f0`](https://github.com/cdonohue/layout-blocks/commit/f0ee7f05a704acbfd45f5cae5a09325e94ef8532)
63 | - Update repo in package [`9d7f86f`](https://github.com/cdonohue/layout-blocks/commit/9d7f86f25eb6d6e539ae36e2272fcfacf6692877)
64 |
--------------------------------------------------------------------------------
/src/components/ZStack/ZStack.jsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import View from '../View'
4 | import styles from './ZStack.module.css'
5 |
6 | const alignmentTypes = {
7 | topLeading: 'topLeading',
8 | top: 'top',
9 | topTrailing: 'topTrailing',
10 | leading: 'leading',
11 | center: 'center',
12 | trailing: 'trailing',
13 | bottomLeading: 'bottomLeading',
14 | bottom: 'bottom',
15 | bottomTrailing: 'bottomTrailing',
16 | }
17 |
18 | const alignments = {
19 | [alignmentTypes.topLeading]: ['start', 'start'],
20 | [alignmentTypes.top]: ['start', 'center'],
21 | [alignmentTypes.topTrailing]: ['start', 'end'],
22 | [alignmentTypes.leading]: ['center', 'start'],
23 | [alignmentTypes.center]: ['center', 'center'],
24 | [alignmentTypes.trailing]: ['center', 'end'],
25 | [alignmentTypes.bottomLeading]: ['end', 'start'],
26 | [alignmentTypes.bottom]: ['end', 'center'],
27 | [alignmentTypes.bottomTrailing]: ['end', 'end'],
28 | }
29 |
30 | function getOffsetProperties(alignment) {
31 | switch (alignment) {
32 | case alignmentTypes.topLeading:
33 | return ['--layout-zstack-marginTop', '--layout-zstack-marginLeft']
34 | case alignmentTypes.top:
35 | return ['--layout-zstack-marginTop']
36 | case alignmentTypes.topTrailing:
37 | return ['--layout-zstack-marginTop', '--layout-zstack-marginRight']
38 | case alignmentTypes.leading:
39 | return ['--layout-zstack-marginLeft']
40 | case alignmentTypes.center:
41 | return []
42 | case alignmentTypes.trailing:
43 | return ['--layout-zstack-marginRight']
44 | case alignmentTypes.bottomLeading:
45 | return ['--layout-zstack-marginBottom', '--layout-zstack-marginLeft']
46 | case alignmentTypes.bottom:
47 | return ['--layout-zstack-marginBottom']
48 | case alignmentTypes.bottomTrailing:
49 | return ['--layout-zstack-marginBottom', '--layout-zstack-layout-zstack-marginRight']
50 | }
51 | }
52 |
53 | export const ZStack = React.forwardRef(
54 | (
55 | {
56 | alignment = 'center',
57 | as: Tag = 'div',
58 | offset = '0',
59 | reverse = false,
60 | children,
61 | className = '',
62 | style: passedInStyles,
63 | ...props
64 | },
65 | ref
66 | ) => {
67 | const [align, justify] = alignments[alignment]
68 |
69 | const childrenCount = React.Children.count(children)
70 |
71 | const style = {
72 | ...passedInStyles,
73 | '--layout-zstack-align': align,
74 | '--layout-zstack-justify': justify,
75 | }
76 |
77 | const sharedProps = {
78 | ...props,
79 | ref,
80 | as: Tag,
81 | className: `${styles.zstack} ${className}`.trim(),
82 | style,
83 | }
84 |
85 | return (
86 |
87 | {React.Children.toArray(children).map((view, index) => {
88 | const zIndex = reverse ? childrenCount - index - 1 : index
89 | const layer = index
90 | const { props } = view
91 |
92 | const offsetProperties = getOffsetProperties(alignment).reduce(
93 | (memo, prop) => {
94 | memo[prop] = offset
95 | return memo
96 | },
97 | {}
98 | )
99 |
100 | const viewProps = {
101 | ...props,
102 | style: {
103 | ...props.style,
104 | ...offsetProperties,
105 | '--layout-zstack-layer': layer,
106 | '--layout-zstack-zIndex': props.zIndex || zIndex,
107 | },
108 | }
109 | return React.cloneElement(view, viewProps)
110 | })}
111 |
112 | )
113 | }
114 | )
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Layout Blocks
2 |
3 | Reusable layout components for your React project
4 |
5 | ```
6 | npm i layout-blocks
7 | ```
8 |
9 | ## Components
10 |
11 | All layout components support an `as` prop to define the html element you want the block to render as (defaults to `div`).
12 |
13 | ### `Center`
14 |
15 | Horizontally centers children up to a `max` width. Can also define side gutters for padding.
16 |
17 | ```
18 | import { Center } from 'layout-blocks'
19 | ```
20 |
21 | | prop | values | description | default |
22 | | -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
23 | | max | string | Unit of space used to define the max width of the container. The container will have auto inline margin applied when this width is reached. | `0` |
24 | | gutter | string | Controls the inline padding of the container | `0` |
25 | | centerChildren | boolean | Specifies if the container should also center children if they do not meet the `max` width | `false` |
26 |
27 | ### `Cluster`
28 |
29 | Flex row container that wraps to flow items to the next line. Useful for button groups and pill boxes.
30 |
31 | ```
32 | import { Cluster } from 'layout-blocks'
33 | ```
34 |
35 | | prop | values | description | default |
36 | | ------- | ------------------------------------------------------------------------------ | -------------------------------------------- | ------------ |
37 | | align | `flex-start` `center` `flex-end` | Controls vertical axis alignment | `center` |
38 | | justify | `flex-start` `center` `flex-end` `space-around` `space-between` `space-evenly` | Controls horizontal axis alignment | `flex-start` |
39 | | spacing | string | Unit of space used to separate cluster items | `1rem` |
40 |
41 | ### `VStack`
42 |
43 | Renders children in a vertical stack with a prop to control horizontal alignment.
44 |
45 | ```
46 | import { VStack } from 'layout-blocks'
47 | ```
48 |
49 | | prop | values | description | default |
50 | | --------- | ----------------------------- | --------------------------------------------- | -------- |
51 | | spacing | string | Unit of space used to separate stack items | `0` |
52 | | alignment | `leading` `center` `trailing` | Controls the horizontal alignment of children | `center` |
53 |
54 | ### `HStack`
55 |
56 | Renders children in a horizontal stack with a prop to control vertical alignment.
57 |
58 | ```
59 | import { HStack } from 'layout-blocks'
60 | ```
61 |
62 | | prop | values | description | default |
63 | | --------- | ----------------------- | ------------------------------------------- | -------- |
64 | | spacing | string | Unit of space used to separate stack items | `0` |
65 | | alignment | `top` `center` `bottom` | Controls the vertical alignment of children | `center` |
66 |
67 | ### `Flex`
68 |
69 | Flex container abstraction used by both `VStack` and `HStack`
70 |
71 | ```
72 | import { Flex } from 'layout-blocks'
73 | ```
74 |
75 | | prop | value | description | default |
76 | | --------- | ------------------------------------------------------------------------------ | ----------------------------- | -------- |
77 | | align | `flex-start` `center` `flex-end` | Controls cross axis alignment | `center` |
78 | | justify | `flex-start` `center` `flex-end` `space-around` `space-between` `space-evenly` | Controls main axis alignment | `center` |
79 | | direction | `row` `column` | Direction flow children | `row` |
80 |
81 | ### `Spacer`
82 |
83 | Useful to insert space within stacks to push surrounding content away.
84 |
85 | ```
86 | import { Spacer } from 'layout-blocks'
87 | ```
88 |
89 | ### `Grid`
90 |
91 | Renders children in a grid with a prop to control the minimum width before rendering each child in a row of it's own.
92 |
93 | ```
94 | import { Grid } from 'layout-blocks'
95 | ```
96 |
97 | | prop | value | description | default |
98 | | ------- | ------ | ------------------------------------------------------------ | ------- |
99 | | spacing | string | Unit of space used to separate grid items | `1rem` |
100 | | min | string | Minimum width of child before collapsing to one item per row | `250px` |
101 |
--------------------------------------------------------------------------------