├── 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 | 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 | 72 | 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 | --------------------------------------------------------------------------------