├── .gitattributes ├── .gitignore ├── .npmignore ├── .npmrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── doczrc.js ├── package.json ├── public └── index.html ├── scripts └── ghpages.js ├── src ├── base │ ├── base.tsx │ └── normalize.ts ├── common.tsx ├── component │ └── component.tsx ├── components │ ├── containers.tsx │ ├── grid.tsx │ ├── horizontal.tsx │ ├── responsive.tsx │ ├── spacers.tsx │ └── vertical.tsx ├── demos │ └── index.tsx ├── docs │ ├── base │ │ └── base.mdx │ ├── common │ │ ├── commonTypes.mdx │ │ └── defaults.mdx │ ├── components │ │ ├── component.mdx │ │ ├── containers.mdx │ │ ├── grid.mdx │ │ ├── horizontal.mdx │ │ ├── responsive.mdx │ │ ├── spacers.mdx │ │ └── vertical.mdx │ ├── guidance │ │ ├── components.mdx │ │ ├── guidance.mdx │ │ └── layouts.mdx │ ├── images │ │ ├── borderbox.png │ │ ├── margincollapse.png │ │ ├── margincollapse.xml │ │ ├── margindependency.png │ │ ├── margindependency.xml │ │ ├── marginfree.png │ │ ├── marginfree.xml │ │ ├── marginplacement.png │ │ └── marginplacement.xml │ ├── index.mdx │ └── principles │ │ ├── scrolling.mdx │ │ ├── sizing.mdx │ │ └── spacing.mdx ├── index.tsx ├── internal │ └── utils.tsx └── styles │ ├── flex.ts │ ├── scroll.ts │ └── spacing.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Fix `gh-pages` langauge detection 2 | *.js linguist-language=TypeScript 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | node_modules 3 | 4 | # Output 5 | lib 6 | 7 | # IDE 8 | .vscode 9 | 10 | # Docz 11 | .docz 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Dev only assets 2 | lib/demos 3 | 4 | # Documentation website 5 | .docz 6 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | # 3.0.1 4 | * Wraping container children in a `span` breaks in uncontrollable ways. So reverted that. Added explicit `Content` prop `wrapInSpan` and guidance. 5 | 6 | ## 3.0.0 7 | * *Breaking*: Containers now wrap their children in a `span` ensuring a single flex child. 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | ``` 3 | git clone 4 | npm install 5 | ``` 6 | 7 | # Dev 8 | ``` 9 | npm start 10 | ``` 11 | 12 | > Note: `docz` will generate docs on `http://127.0.0.1:3000/gls/`. 13 | 14 | # Releasing 15 | * Think if your change is `major (breaking api) / minor (potentially breaking but you tried your best not to) / patch (safe)`. 16 | * See current version in `package.json` and update `CHANGELOG.md` adding the *planned release version* notes. 17 | * Commit all your changes (including changelog) 18 | * Run `npm version major|minor|patch`. It will automatically push to github, run `npm publish` on your behalf. 19 | 20 | # Component guide 21 | Each component should do 5 things: 22 | * take all the props supported on a `div` i.e. `React.HTMLProps` : 23 | ```ts 24 | export interface GridProps extends React.HTMLProps { 25 | spacing?: number 26 | } 27 | ``` 28 | * isolate `className`: 29 | ```ts 30 | const { className, ...otherProps } = props; 31 | ``` 32 | * append to `className` adding its own props as a className generated from typestyle: 33 | ```ts 34 | const klass = classes( 35 | className, 36 | typestyle.style(/*stuff*/), 37 | ); 38 | ``` 39 | * render with all props + customized class + `data-comment`: 40 | ```ts 41 |
42 | ``` 43 | * have a `displayName`: 44 | ```ts 45 | WrappingGrid.displayName = 'WrappingGrid'; 46 | ``` 47 | 48 | # BaseProps 49 | The props we want to be supported by a potential wysiwyg editor get added to `BaseProps`. 50 | 51 | > Potentially all of these can be done by the user in `style` Or `className`, but its easier this way as we get explicit properties to test on the component (instead of trying to do code flow analysis to determine the calculated styles). 52 | 53 | # Docs Images 54 | 55 | Drawn using [draw.io](https://draw.io). Just open the xml files from the images directory. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gls 2 | 3 | > General Layout System for React. 4 | 5 | [![Build Status][travis-image]][travis-url] 6 | [![NPM version][npm-image]][npm-url] 7 | 8 | # Installation 9 | 10 | Install `gls` along with its peer dependencies: `react` (for html) and `typestyle` (for css): 11 | 12 | ```sh 13 | npm i gls react typestyle 14 | ``` 15 | 16 | # Get Started 17 | [Docs and demos are available online 🌹](https://basarat.com/gls) 18 | 19 | [travis-image]:https://travis-ci.org/basarat/gls.svg?branch=master 20 | [travis-url]:https://travis-ci.org/basarat/gls 21 | [npm-image]:https://img.shields.io/npm/v/gls.svg?style=flat 22 | [npm-url]:https://npmjs.org/package/gls 23 | -------------------------------------------------------------------------------- /doczrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'General Layout System', 3 | base: '/gls/', 4 | menu: [ 5 | 'General Layout System', 6 | 'Spacing principle', 7 | 'Sizing principle', 8 | 'Scrolling principle', 9 | 'Base', 10 | 'Common Types and Props', 11 | 'Defaults', 12 | 'Vertical', 13 | 'Horizontal', 14 | 'Responsive', 15 | 'Grid', 16 | 'Spacers', 17 | 'Containers', 18 | 'Component', 19 | 'Further Guidance 🌹', 20 | 'Layout Examples', 21 | 'Component Guidance', 22 | ], 23 | 24 | // Some cleanups 25 | codeSandbox: false, 26 | typescript: true, 27 | indexHtml: 'public/index.html', 28 | themeConfig: { 29 | footerText: 'white', 30 | styles: { 31 | playground: ` 32 | padding: 0px; 33 | border: 8px solid #eeee; 34 | ` 35 | } 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gls", 3 | "version": "3.0.1", 4 | "description": "General Layout System for React", 5 | "main": "lib/index.js", 6 | "types": "lib", 7 | "scripts": { 8 | "clean": "rm -rf ./node_modules & npm i", 9 | "compile": "tsc -p .", 10 | "compile:live": "npm run compile -- -w --preserveWatchOutput", 11 | "docs": "docz build && cp ./.docz/dist/index.html ./.docz/dist/404.html", 12 | "docs:live": "docz dev", 13 | "ghpages": "node ./scripts/ghpages.js", 14 | "start": "concurrently \"npm run compile:live\" \"npm run docs:live\"", 15 | "test": "npm run compile && npm run docs", 16 | "preversion": "npm run test", 17 | "postversion": "git push --follow-tags && npm publish" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/basarat/gls.git" 22 | }, 23 | "keywords": [ 24 | "layout", 25 | "react", 26 | "typescript" 27 | ], 28 | "author": "basaratali@gmail.com", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/basarat/gls/issues" 32 | }, 33 | "peerDependencies": { 34 | "react": "*", 35 | "typestyle": "*" 36 | }, 37 | "homepage": "https://github.com/basarat/gls#readme", 38 | "devDependencies": { 39 | "@types/react": "18.2.14", 40 | "@types/react-dom": "18.2.6", 41 | "concurrently": "8.2.0", 42 | "docz": "2.4.0", 43 | "gh-pages": "5.0.0", 44 | "react": "17.0.2", 45 | "react-dom": "17.0.2", 46 | "typescript": "5.1.3", 47 | "typestyle": "2.4.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 18 | {{ title }} 19 | {{ head }} 20 | 21 | 22 |
23 | {{ footer }} 24 | 25 | -------------------------------------------------------------------------------- /scripts/ghpages.js: -------------------------------------------------------------------------------- 1 | const ghpages = require('gh-pages'); 2 | const path = require('path'); 3 | const date = new Date(); 4 | 5 | /** Directory */ 6 | const directory = '.docz/dist'; 7 | 8 | /** Repo */ 9 | const repo = 'basarat/gls'; 10 | 11 | /** Branch */ 12 | const branch = 'gh-pages'; 13 | 14 | 15 | ghpages.publish(path.resolve(__dirname, '..', directory), { 16 | message: `[ci skip] deployment (${date.getUTCFullYear()}-${date.getUTCMonth() + 1}-${date.getUTCDate()}-${date.getUTCHours()}-${date.getUTCMinutes()})`, 17 | branch: branch, 18 | repo: 'https://' + process.env.GH_TOKEN + '@github.com/' + repo + '.git', 19 | user: { 20 | name: 'basarat', 21 | email: 'basaratali@gmail.com' 22 | } 23 | }, (err) => { 24 | if (err) { 25 | console.log('--publish failed!--', err) 26 | } else { 27 | console.log('--publish done--'); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /src/base/base.tsx: -------------------------------------------------------------------------------- 1 | import { cssRule } from "typestyle"; 2 | import { normalize } from "./normalize"; 3 | 4 | /** 5 | * Setups: 6 | * - normalize 7 | * - border box 8 | * - fill body into page 9 | * - fill root-selector into body 10 | */ 11 | export function base(rootSelector?: string) { 12 | /** normalize */ 13 | normalize(); 14 | 15 | /** Use border box */ 16 | cssRule('html', { 17 | '-moz-box-sizing': 'border-box', 18 | '-webkit-box-sizing': 'border-box', 19 | boxSizing: 'border-box' 20 | }); 21 | cssRule('*,*:before,*:after', { 22 | boxSizing: 'inherit', 23 | }); 24 | 25 | /** Use full window size for application */ 26 | cssRule('html, body', { 27 | height: '100%', 28 | width: '100%', 29 | padding: 0, 30 | margin: 0 31 | }); 32 | 33 | if (rootSelector) { 34 | /** Root should fill parent and start a vertical flex layout */ 35 | cssRule(rootSelector, { 36 | width: '100%', 37 | height: '100%', 38 | display: [ 39 | '-ms-flexbox', 40 | '-webkit-flex', 41 | 'flex', 42 | ], 43 | '-ms-flex-direction': 'column', 44 | '-webkit-flex-direction': 'column', 45 | flexDirection: 'column' 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/base/normalize.ts: -------------------------------------------------------------------------------- 1 | import { cssRaw } from 'typestyle'; 2 | 3 | /** 4 | * Adds https://github.com/sindresorhus/modern-normalize 5 | */ 6 | export function normalize() { 7 | /** 8 | * To update: 9 | * - https://cdn.jsdelivr.net/npm/modern-normalize/modern-normalize.min.css 10 | * - then copy paste raw below 11 | * - remove: 12 | * - the heading comment 13 | * - the sourcemap at the end of the file 14 | * 15 | * Release 16 | * - All changes to normalize will be considered minor 17 | **/ 18 | cssRaw( 19 | `*,::after,::before{box-sizing:border-box}html{font-family:system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji';line-height:1.15;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4}body{margin:0}hr{height:0;color:inherit}abbr[title]{text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}:-moz-focusring{outline:1px dotted ButtonText}:-moz-ui-invalid{box-shadow:none}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}` 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/common.tsx: -------------------------------------------------------------------------------- 1 | import { types } from "typestyle"; 2 | import React from 'react'; 3 | 4 | /** 5 | * Default values for components 6 | */ 7 | export const GLSDefaults = React.createContext<{ 8 | verticalSpacing?: CSSLength, 9 | horizontalSpacing?: CSSLength, 10 | breakpoint?: number 11 | }>({ 12 | verticalSpacing: 24, 13 | horizontalSpacing: 24, 14 | breakpoint: 480, 15 | }); 16 | 17 | /** 18 | * Example: 19 | * - 5 (implies 5px) 20 | * - '5rem' 21 | */ 22 | export type CSSLength = number | string; 23 | 24 | /** 25 | * Various versions of providing common shorthand properties 26 | */ 27 | export type CSSBox = 28 | /** top,right,left,bottom */ 29 | | CSSLength 30 | /** [top & bottom, left & right] */ 31 | | [CSSLength, CSSLength] 32 | /** [top,right,bottom,left] */ 33 | | [CSSLength, CSSLength, CSSLength, CSSLength] 34 | /** Individual */ 35 | | { top?: CSSLength, right?: CSSLength, bottom?: CSSLength, left?: CSSLength } 36 | 37 | export type Sizing = 38 | | 'content' /** default */ 39 | | 'stretch' /** Same as `1` */ 40 | | number /** A flex ratio */ 41 | 42 | /** 43 | * Specifies sizing interaction 44 | */ 45 | export interface SizingProp { 46 | /** 47 | * Specifies `sizing` interaction in the main axis 48 | */ 49 | sizing?: Sizing, 50 | } 51 | 52 | export interface CrossAxisAlignProp { 53 | /** 54 | * Allows an item to override its alignment in the parents cross axis 55 | * e.g. 56 | * - allows an item to stretch horizontally in a Vertical(horizontalAlign='left') 57 | * - allows an item to stretch vertically in a Horizontal(verticalAlign='top') 58 | */ 59 | crossAxisAlign?: 'start' | 'center' | 'end' | 'stretch'; 60 | } 61 | 62 | export interface SpacingProp { 63 | /** Spacing between each child */ 64 | spacing?: CSSLength, 65 | } 66 | 67 | export interface VerticalsAlignProps { 68 | /** Child alignment in vertical axis */ 69 | verticalAlign?: 'top' /** default */ | 'center' | 'bottom', 70 | /** Child alignment in horizontal axis */ 71 | horizontalAlign?: 'stretch' /** default */ | 'left' | 'center' | 'right', 72 | } 73 | 74 | export interface HorizontalsAlignProps { 75 | /** Child alignment in vertical axis */ 76 | verticalAlign?: 'stretch' /** default */ | 'top' | 'center' | 'bottom' | 'baseline', 77 | /** Child alignment in horizontal axis */ 78 | horizontalAlign?: 'left' /** default if `reverse = false` */ | 'center' | 'right' /** default if `reverse = true` */, 79 | } 80 | 81 | /** 82 | * Props accepted by all our container components 83 | */ 84 | export interface BaseProps extends 85 | ScrollProp, 86 | PaddingProp, 87 | SizeProps, 88 | StylesProp, 89 | StyleProp, 90 | ClassNameProp, 91 | TagProps { 92 | } 93 | 94 | /** 95 | * Add support for padding 96 | */ 97 | export interface PaddingProp { 98 | padding?: CSSBox, 99 | } 100 | 101 | 102 | /** 103 | * Add support for explicit size 104 | */ 105 | export interface SizeProps { 106 | height?: CSSLength, 107 | minHeight?: CSSLength, 108 | maxHeight?: CSSLength, 109 | 110 | width?: CSSLength, 111 | minWidth?: CSSLength, 112 | maxWidth?: CSSLength, 113 | } 114 | 115 | export type Scroll = 116 | | 'overflow' /** default */ 117 | | 'both' 118 | | 'vertical' 119 | | 'horizontal' 120 | | 'hidden'; 121 | 122 | /** 123 | * Add on anything to add support for controlling the scroll 124 | */ 125 | export interface ScrollProp { 126 | scroll?: Scroll; 127 | } 128 | 129 | /** 130 | * Support for mixing in TypeStyle NestedCSSProperties (style function arguments) 131 | */ 132 | export interface StylesProp { 133 | styles?: (types.NestedCSSProperties | null | false)[]; 134 | } 135 | 136 | /** 137 | * Support for React `style` property 138 | */ 139 | export interface StyleProp { 140 | style?: React.CSSProperties 141 | } 142 | 143 | /** 144 | * Support for React `className` property 145 | */ 146 | export interface ClassNameProp { 147 | className?: string 148 | } 149 | 150 | /** 151 | * Props supported by the underlying tag 152 | */ 153 | export interface TagProps extends Omit, 'className' | 'style'> { 154 | tag?: string, 155 | } 156 | -------------------------------------------------------------------------------- /src/component/component.tsx: -------------------------------------------------------------------------------- 1 | import { PaddingProp, ClassNameProp, ScrollProp, SizeProps, StylesProp, SizingProp, CrossAxisAlignProp } from "../common"; 2 | import * as typestyle from "typestyle"; 3 | import { _processPadding, _processScroll, cssLengthToString, _processSizing, _processCrossAxisAlign } from "../internal/utils"; 4 | 5 | /** 6 | * A set of props that can help you build layout-customizable components 7 | */ 8 | export interface ComponentProps extends 9 | ClassNameProp, 10 | ScrollProp, 11 | PaddingProp, 12 | SizingProp, 13 | CrossAxisAlignProp, 14 | SizeProps, 15 | StylesProp { 16 | } 17 | 18 | /** 19 | * Takes a set of component props 20 | * - actions certain props to update the `props` className 21 | * @returns `props` with actioned members omitted and className updated 22 | */ 23 | export function component(props: T, defaults: ComponentProps = {}) 24 | : Omit< 25 | T, 26 | | 'padding' 27 | | 'scroll' 28 | | 'sizing' 29 | | 'crossAxisAlign' 30 | 31 | | 'height' 32 | | 'minHeight' 33 | | 'maxHeight' 34 | | 'width' 35 | | 'minWidth' 36 | | 'maxWidth' 37 | 38 | | 'styles' 39 | > { 40 | let stylesToProcess: (typestyle.types.NestedCSSProperties | false | null)[] = []; 41 | 42 | const { 43 | padding = defaults.padding, 44 | scroll = defaults.scroll, 45 | sizing = defaults.sizing, 46 | crossAxisAlign = defaults.crossAxisAlign, 47 | 48 | height = defaults.height, 49 | minHeight = defaults.minHeight, 50 | maxHeight = defaults.maxHeight, 51 | width = defaults.width, 52 | minWidth = defaults.minWidth, 53 | maxWidth = defaults.maxWidth, 54 | 55 | styles = defaults.styles, 56 | 57 | ...result 58 | } = props; 59 | 60 | if (padding != null) stylesToProcess.push(_processPadding(padding)); 61 | if (scroll != null) stylesToProcess.push(_processScroll(scroll)); 62 | /** Always size by content by default */ 63 | stylesToProcess.push(_processSizing(sizing)); 64 | if (crossAxisAlign != null) stylesToProcess.push(_processCrossAxisAlign(crossAxisAlign)); 65 | 66 | if (height != null) stylesToProcess.push({ height: cssLengthToString(height) }); 67 | if (minHeight != null) stylesToProcess.push({ minHeight: cssLengthToString(minHeight) }); 68 | if (maxHeight != null) stylesToProcess.push({ maxHeight: cssLengthToString(maxHeight) }); 69 | if (width != null) stylesToProcess.push({ width: cssLengthToString(width) }); 70 | if (minWidth != null) stylesToProcess.push({ minWidth: cssLengthToString(minWidth) }); 71 | if (maxWidth != null) stylesToProcess.push({ maxWidth: cssLengthToString(maxWidth) }); 72 | 73 | if (styles != null) stylesToProcess = stylesToProcess.concat(styles); 74 | 75 | result.className = typestyle.classes( 76 | props.className, 77 | typestyle.style(...stylesToProcess), 78 | ); 79 | 80 | return result; 81 | } 82 | -------------------------------------------------------------------------------- /src/components/containers.tsx: -------------------------------------------------------------------------------- 1 | import * as typestyle from 'typestyle'; 2 | import * as React from 'react'; 3 | import { BaseProps, SizingProp, VerticalsAlignProps, CrossAxisAlignProp } from '../common'; 4 | import { createBaseTag, _processSizing, _processCrossAxisAlign } from '../internal/utils'; 5 | import { content, stretch, centerJustified, endJustified, center, end, vertical, start } from '../styles/flex'; 6 | 7 | export interface Stretch extends BaseProps, VerticalsAlignProps, CrossAxisAlignProp { 8 | sizing?: number; 9 | } 10 | 11 | /** 12 | * For providing a *as much as available* amount of space for an item 13 | */ 14 | export const Stretch = React.forwardRef((props: Stretch, ref: React.LegacyRef) => { 15 | const { 16 | sizing, 17 | crossAxisAlign, 18 | 19 | verticalAlign = 'top', 20 | horizontalAlign = 'left', 21 | 22 | ...otherProps 23 | } = props; 24 | const klass = typestyle.style( 25 | stretch(sizing), 26 | crossAxisAlign != null && _processCrossAxisAlign(crossAxisAlign), 27 | vertical, 28 | verticalAlign == 'center' && centerJustified, 29 | verticalAlign == 'bottom' && endJustified, 30 | horizontalAlign == 'left' && start, 31 | horizontalAlign == 'center' && center, 32 | horizontalAlign == 'right' && end, 33 | ); 34 | return createBaseTag(otherProps, klass, 'Stretch', ref); 35 | }); 36 | Stretch.displayName = 'Stretch'; 37 | 38 | export interface ContentProps extends BaseProps, VerticalsAlignProps, CrossAxisAlignProp { 39 | wrapInSpan?: boolean 40 | } 41 | 42 | /** 43 | * For providing a *as much as needed* amount of space for an item 44 | */ 45 | export const Content = React.forwardRef((props: ContentProps, ref: React.LegacyRef) => { 46 | const { 47 | crossAxisAlign, 48 | verticalAlign = 'top', 49 | horizontalAlign = 'left', 50 | wrapInSpan, 51 | 52 | ...otherProps 53 | } = props; 54 | /** 55 | * Ensure single child. Without it the following would become weird 56 | * `Hello is it me you are looking for` 57 | */ 58 | if (wrapInSpan) { 59 | otherProps.children = {otherProps.children}; 60 | } 61 | const klass = typestyle.style( 62 | content, 63 | crossAxisAlign != null && _processCrossAxisAlign(crossAxisAlign), 64 | vertical, 65 | verticalAlign == 'center' && centerJustified, 66 | verticalAlign == 'bottom' && endJustified, 67 | horizontalAlign == 'left' && start, 68 | horizontalAlign == 'center' && center, 69 | horizontalAlign == 'right' && end, 70 | ); 71 | return createBaseTag(otherProps, klass, 'Content', ref); 72 | }); 73 | Content.displayName = 'Content'; 74 | 75 | export interface BoxProps extends BaseProps, VerticalsAlignProps, SizingProp, CrossAxisAlignProp { 76 | } 77 | 78 | /** 79 | * A general purpose single item container 80 | */ 81 | export const Box = React.forwardRef((props: BoxProps, ref: React.LegacyRef) => { 82 | const { 83 | sizing, 84 | crossAxisAlign, 85 | verticalAlign = 'top', 86 | horizontalAlign = 'left', 87 | 88 | ...otherProps 89 | } = props; 90 | const klass = typestyle.style( 91 | _processSizing(sizing), 92 | crossAxisAlign != null && _processCrossAxisAlign(crossAxisAlign), 93 | vertical, 94 | verticalAlign == 'center' && centerJustified, 95 | verticalAlign == 'bottom' && endJustified, 96 | horizontalAlign == 'left' && start, 97 | horizontalAlign == 'center' && center, 98 | horizontalAlign == 'right' && end, 99 | ); 100 | return createBaseTag(otherProps, klass, 'Box', ref); 101 | }); 102 | Box.displayName = 'Box'; 103 | -------------------------------------------------------------------------------- /src/components/grid.tsx: -------------------------------------------------------------------------------- 1 | import * as typestyle from 'typestyle'; 2 | import * as React from 'react'; 3 | import { CSSLength, BaseProps, SizingProp, CrossAxisAlignProp } from '../common'; 4 | import { createBaseTag, useGLSDefaults, _processSizing, _processCrossAxisAlign } from '../internal/utils'; 5 | import { horizontal, wrap, endJustified, centerJustified, betweenJustified, aroundJustified } from '../styles/flex'; 6 | import { gridSpaced } from '../styles/spacing'; 7 | 8 | export interface GridProps extends BaseProps, SizingProp, CrossAxisAlignProp { 9 | /** 10 | * Minimum spacing between children 11 | */ 12 | spacing?: 13 | | CSSLength 14 | | [/** Vertical */ CSSLength, /** Horizontal */ CSSLength] 15 | 16 | /** 17 | * Controls how the extra space around the children is handled 18 | */ 19 | justify?: 20 | /** Controls by content */ 21 | | 'left' /** default */ 22 | | 'center' 23 | | 'right' 24 | /** Controls by space */ 25 | | 'space-between' 26 | | 'space-around' 27 | } 28 | 29 | /** 30 | * Lays out children with a margin between them (horizontal and vertical) 31 | */ 32 | export const Grid = React.forwardRef((props: GridProps, ref: React.LegacyRef) => { 33 | const { 34 | sizing, 35 | crossAxisAlign, 36 | spacing, 37 | justify, 38 | ...otherProps } = props; 39 | 40 | /** 41 | * Figure out the spacing requested 42 | */ 43 | let { verticalSpacing, horizontalSpacing } = useGLSDefaults(); 44 | if (spacing != null) { 45 | if (typeof spacing == 'number' || typeof spacing == 'string') { 46 | horizontalSpacing = spacing; 47 | verticalSpacing = horizontalSpacing; 48 | } else { 49 | [verticalSpacing, horizontalSpacing] = spacing; 50 | } 51 | } 52 | 53 | const klass = typestyle.style( 54 | _processSizing(sizing), 55 | crossAxisAlign != null && _processCrossAxisAlign(crossAxisAlign), 56 | horizontal, wrap, 57 | gridSpaced(verticalSpacing, horizontalSpacing), 58 | justify == 'center' && centerJustified, 59 | justify == 'right' && endJustified, 60 | justify == 'space-between' && betweenJustified, 61 | justify == 'space-around' && aroundJustified, 62 | ); 63 | return (<> 64 | 65 | {createBaseTag(otherProps, klass, 'Grid', ref)} 66 | ); 67 | }); 68 | Grid.displayName = 'Grid'; 69 | -------------------------------------------------------------------------------- /src/components/horizontal.tsx: -------------------------------------------------------------------------------- 1 | import * as typestyle from 'typestyle'; 2 | import * as React from 'react'; 3 | import { BaseProps, SizingProp, SpacingProp, HorizontalsAlignProps, CrossAxisAlignProp } from '../common'; 4 | import { createBaseTag, _processSizing, useGLSDefaults, _processCrossAxisAlign } from '../internal/utils'; 5 | import { horizontal, endJustified, centerJustified, center, end, start, baseline, horizontalReverse, startJustified } from '../styles/flex'; 6 | import { horizontallySpaced, horizontallyReverseSpaced } from '../styles/spacing'; 7 | 8 | export interface HorizontalProps extends BaseProps, SizingProp, SpacingProp, HorizontalsAlignProps, CrossAxisAlignProp { 9 | reverse?: boolean 10 | } 11 | 12 | /** 13 | * Layout out children horizontally with a margin between them 14 | */ 15 | export const Horizontal = React.forwardRef((props: HorizontalProps, ref: React.LegacyRef) => { 16 | const { horizontalSpacing } = useGLSDefaults(); 17 | const { 18 | sizing, 19 | crossAxisAlign, 20 | 21 | spacing = horizontalSpacing, 22 | verticalAlign, 23 | horizontalAlign, 24 | reverse, 25 | 26 | ...otherProps } = props; 27 | 28 | const klass = typestyle.style( 29 | _processSizing(sizing), 30 | crossAxisAlign != null && _processCrossAxisAlign(crossAxisAlign), 31 | reverse === true ? horizontalReverse : horizontal, 32 | spacing !== 0 && (reverse === true ? horizontallyReverseSpaced(spacing) : horizontallySpaced(spacing)), 33 | horizontalAlign == 'left' && (reverse === true ? endJustified : startJustified), 34 | horizontalAlign == 'center' && centerJustified, 35 | horizontalAlign == 'right' && (reverse === true ? startJustified : endJustified), 36 | verticalAlign == 'top' && start, 37 | verticalAlign == 'center' && center, 38 | verticalAlign == 'bottom' && end, 39 | verticalAlign == 'baseline' && baseline, 40 | ); 41 | return createBaseTag(otherProps, klass, 'Horizontal', ref); 42 | }); 43 | Horizontal.displayName = 'Horizontal'; 44 | -------------------------------------------------------------------------------- /src/components/responsive.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as typestyle from 'typestyle'; 3 | import { SizingProp, StylesProp, SpacingProp, VerticalsAlignProps, HorizontalsAlignProps, PaddingProp, SizeProps, ScrollProp, StyleProp, ClassNameProp, TagProps, CrossAxisAlignProp } from '../common'; 4 | import { createBaseTag, useGLSDefaults, _processSizing, _processPadding, cssLengthToString, _processScroll, _processCrossAxisAlign } from '../internal/utils'; 5 | import { vertical, horizontal, centerJustified, endJustified, end, center, start, baseline } from '../styles/flex'; 6 | import { verticallySpaced, horizontallySpaced } from '../styles/spacing'; 7 | 8 | /** 9 | * Props that can be specified at 10 | * - root of `Responsive` 11 | * - and overridden for `vertical`/`horizontal` modes 12 | */ 13 | export interface ResponsiveOverridableProps extends 14 | ScrollProp, 15 | PaddingProp, 16 | SizeProps, 17 | SizingProp, 18 | SpacingProp, 19 | CrossAxisAlignProp { 20 | } 21 | 22 | export interface BreakpointProp { 23 | /** 24 | * windowWidth <= breakpoint : it is vertical (mobile) 25 | * else : it is horizontal (desktop) 26 | **/ 27 | breakpoint?: number; 28 | } 29 | 30 | /** 31 | * Props for Vertical mode 32 | */ 33 | export interface ResponsiveVerticalModeProps extends 34 | VerticalsAlignProps, 35 | StylesProp, 36 | ResponsiveOverridableProps { 37 | } 38 | 39 | /** 40 | * Props for Horizontal mode 41 | */ 42 | export interface ResponsiveHorizontalModeProps extends 43 | HorizontalsAlignProps, 44 | StylesProp, 45 | ResponsiveOverridableProps { 46 | } 47 | 48 | export interface ResponsiveModesProps { 49 | /** Vertical mode configuration */ 50 | vertical?: ResponsiveVerticalModeProps; 51 | /** Horizontal mode configuration */ 52 | horizontal?: ResponsiveHorizontalModeProps; 53 | } 54 | 55 | /** 56 | * Props that can only be specified at the root of the `Responsive` 57 | */ 58 | export interface ResponsiveRootOnlyProps extends 59 | StylesProp, 60 | StyleProp, 61 | ClassNameProp, 62 | TagProps, 63 | BreakpointProp { 64 | } 65 | 66 | /** 67 | * Props for the Responsive component 68 | */ 69 | export interface ResponsiveProps extends 70 | ResponsiveRootOnlyProps, 71 | ResponsiveModesProps, 72 | ResponsiveOverridableProps { 73 | } 74 | 75 | /** 76 | * Layout out children 77 | * - vertically till breakpoint 78 | * - horizontally above breakpoint 79 | */ 80 | export const Responsive = React.forwardRef((props: ResponsiveProps, ref: React.LegacyRef) => { 81 | const { 82 | verticalSpacing: defaultVerticalSpacing, 83 | horizontalSpacing: defaultHorizontalSpacing, 84 | breakpoint: bp 85 | } = useGLSDefaults(); 86 | 87 | const { 88 | breakpoint = bp, 89 | vertical: verticalOptions, 90 | horizontal: horizontalOptions, 91 | 92 | /** Overridable */ 93 | scroll, 94 | padding, 95 | height, 96 | minHeight, 97 | maxHeight, 98 | width, 99 | minWidth, 100 | maxWidth, 101 | sizing, 102 | crossAxisAlign, 103 | spacing, 104 | 105 | ...otherProps 106 | } = props; 107 | 108 | /** Determine scroll */ 109 | const verticalScroll = (verticalOptions && verticalOptions.scroll != null) ? verticalOptions.scroll 110 | : scroll; 111 | const horizontalScroll = (horizontalOptions && horizontalOptions.scroll != null) ? horizontalOptions.scroll 112 | : scroll; 113 | 114 | /** Determine paddings */ 115 | const verticalPadding = (verticalOptions && verticalOptions.padding != null) ? verticalOptions.padding 116 | : padding; 117 | const horizontalPadding = (horizontalOptions && horizontalOptions.padding != null) ? horizontalOptions.padding 118 | : padding; 119 | 120 | /** Determine size */ 121 | const verticalHeight = (verticalOptions && verticalOptions.height != null) ? verticalOptions.height 122 | : height; 123 | const horizontalHeight = (horizontalOptions && horizontalOptions.height != null) ? horizontalOptions.height 124 | : height; 125 | const verticalMinHeight = (verticalOptions && verticalOptions.minHeight != null) ? verticalOptions.minHeight 126 | : minHeight; 127 | const horizontalMinHeight = (horizontalOptions && horizontalOptions.minHeight != null) ? horizontalOptions.minHeight 128 | : minHeight; 129 | const verticalMaxHeight = (verticalOptions && verticalOptions.maxHeight != null) ? verticalOptions.maxHeight 130 | : maxHeight; 131 | const horizontalMaxHeight = (horizontalOptions && horizontalOptions.maxHeight != null) ? horizontalOptions.maxHeight 132 | : maxHeight; 133 | const verticalWidth = (verticalOptions && verticalOptions.width != null) ? verticalOptions.width 134 | : width; 135 | const horizontalWidth = (horizontalOptions && horizontalOptions.width != null) ? horizontalOptions.width 136 | : width; 137 | const verticalMinWidth = (verticalOptions && verticalOptions.minWidth != null) ? verticalOptions.minWidth 138 | : minWidth; 139 | const horizontalMinWidth = (horizontalOptions && horizontalOptions.minWidth != null) ? horizontalOptions.minWidth 140 | : minWidth; 141 | const verticalMaxWidth = (verticalOptions && verticalOptions.maxWidth != null) ? verticalOptions.maxWidth 142 | : maxWidth; 143 | const horizontalMaxWidth = (horizontalOptions && horizontalOptions.maxWidth != null) ? horizontalOptions.maxWidth 144 | : maxWidth; 145 | 146 | /** Determine sizings */ 147 | const verticalSizing = (verticalOptions && verticalOptions.sizing != null) 148 | ? verticalOptions.sizing 149 | : sizing; 150 | const horizontalSizing = (horizontalOptions && horizontalOptions.sizing != null) 151 | ? horizontalOptions.sizing 152 | : sizing; 153 | 154 | const verticalCrossAxisAlign = (verticalOptions && verticalOptions.crossAxisAlign != null) 155 | ? verticalOptions.crossAxisAlign 156 | : crossAxisAlign; 157 | const horizontalCrossAxisAlign = (horizontalOptions && horizontalOptions.crossAxisAlign != null) 158 | ? horizontalOptions.crossAxisAlign 159 | : crossAxisAlign; 160 | 161 | /** Determine spacings */ 162 | const verticalSpacing = (verticalOptions && verticalOptions.spacing != null) ? verticalOptions.spacing 163 | : spacing != null ? spacing 164 | : defaultVerticalSpacing; 165 | const horizontalSpacing = (horizontalOptions && horizontalOptions.spacing != null) ? horizontalOptions.spacing 166 | : spacing != null ? spacing 167 | : defaultHorizontalSpacing; 168 | 169 | /** Determine alignments */ 170 | const verticalModeVerticalAlign = 171 | (verticalOptions && verticalOptions.verticalAlign != null) 172 | ? verticalOptions.verticalAlign 173 | : null; 174 | const verticalModeHorizontalAlign = 175 | (verticalOptions && verticalOptions.horizontalAlign != null) 176 | ? verticalOptions.horizontalAlign 177 | : null; 178 | const horizontalModeVerticalAlign = 179 | (horizontalOptions && horizontalOptions.verticalAlign != null) 180 | ? horizontalOptions.verticalAlign 181 | : null; 182 | const horizontalModeHorizontalAlign = 183 | (horizontalOptions && horizontalOptions.horizontalAlign != null) 184 | ? horizontalOptions.horizontalAlign 185 | : null; 186 | 187 | const klass = typestyle.style( 188 | /** Till breakpoint: Vertical */ 189 | typestyle.media({ minWidth: 0, maxWidth: breakpoint }, 190 | _processSizing(verticalSizing), 191 | verticalCrossAxisAlign != null && _processCrossAxisAlign(verticalCrossAxisAlign), 192 | verticalPadding != null && _processPadding(verticalPadding), 193 | vertical, 194 | verticalSpacing !== 0 && verticallySpaced(verticalSpacing), 195 | verticalModeVerticalAlign == 'center' && centerJustified, 196 | verticalModeVerticalAlign == 'bottom' && endJustified, 197 | verticalModeHorizontalAlign == 'left' && start, 198 | verticalModeHorizontalAlign == 'right' && end, 199 | verticalModeHorizontalAlign == 'center' && center, 200 | verticalHeight != null && { height: cssLengthToString(verticalHeight) }, 201 | verticalMinHeight != null && { minHeight: cssLengthToString(verticalMinHeight) }, 202 | verticalMaxHeight != null && { maxHeight: cssLengthToString(verticalMaxHeight) }, 203 | verticalWidth != null && { width: cssLengthToString(verticalWidth) }, 204 | verticalMinWidth != null && { minWidth: cssLengthToString(verticalMinWidth) }, 205 | verticalMaxWidth != null && { maxWidth: cssLengthToString(verticalMaxWidth) }, 206 | verticalScroll != null && _processScroll(verticalScroll), 207 | ), 208 | verticalOptions && verticalOptions.styles && typestyle.media({ minWidth: 0, maxWidth: breakpoint }, 209 | ...verticalOptions.styles), 210 | 211 | /** Bigger than breakpoint: Horizontal */ 212 | typestyle.media({ minWidth: breakpoint + 1 }, 213 | _processSizing(horizontalSizing), 214 | horizontalCrossAxisAlign != null && _processCrossAxisAlign(horizontalCrossAxisAlign), 215 | horizontalPadding != null && _processPadding(horizontalPadding), 216 | horizontal, 217 | horizontalSpacing !== 0 && horizontallySpaced(horizontalSpacing), 218 | horizontalModeHorizontalAlign == 'right' && endJustified, 219 | horizontalModeHorizontalAlign == 'center' && centerJustified, 220 | horizontalModeVerticalAlign == 'top' && start, 221 | horizontalModeVerticalAlign == 'center' && center, 222 | horizontalModeVerticalAlign == 'bottom' && end, 223 | horizontalModeVerticalAlign == 'baseline' && baseline, 224 | horizontalHeight != null && { height: cssLengthToString(horizontalHeight) }, 225 | horizontalMinHeight != null && { minHeight: cssLengthToString(horizontalMinHeight) }, 226 | horizontalMaxHeight != null && { maxHeight: cssLengthToString(horizontalMaxHeight) }, 227 | horizontalWidth != null && { width: cssLengthToString(horizontalWidth) }, 228 | horizontalMinWidth != null && { minWidth: cssLengthToString(horizontalMinWidth) }, 229 | horizontalMaxWidth != null && { maxWidth: cssLengthToString(horizontalMaxWidth) }, 230 | horizontalScroll != null && _processScroll(horizontalScroll), 231 | ), 232 | horizontalOptions && horizontalOptions.styles && typestyle.media({ minWidth: breakpoint + 1 }, 233 | ...horizontalOptions.styles), 234 | ); 235 | return createBaseTag(otherProps, klass, 'Responsive', ref); 236 | }); 237 | Responsive.displayName = 'Responsive'; 238 | -------------------------------------------------------------------------------- /src/components/spacers.tsx: -------------------------------------------------------------------------------- 1 | import * as typestyle from 'typestyle'; 2 | import * as React from 'react'; 3 | import { CSSLength } from '../common'; 4 | import { cssLengthToString, useGLSDefaults } from '../internal/utils'; 5 | import { stretch } from '../styles/flex'; 6 | 7 | export interface StretchSpacerProps extends React.HTMLProps { 8 | sizing?: number; 9 | } 10 | 11 | /** 12 | * Flexes into any available space 13 | */ 14 | export const StretchSpacer: React.FC = (props) => { 15 | const { className, sizing, ...otherProps } = props; 16 | const klass = typestyle.classes( 17 | className, 18 | typestyle.style(stretch(sizing)) 19 | ); 20 | return ( 21 |
22 | ); 23 | }; 24 | StretchSpacer.displayName = 'StretchSpacer'; 25 | 26 | /** 27 | * Common interface for space props 28 | */ 29 | export interface SpacerProps extends React.HTMLProps { 30 | space?: CSSLength; 31 | } 32 | 33 | /** 34 | * Takes a fixed amount of horizontal space 35 | */ 36 | export const HorizontalSpacer: React.FC = (props) => { 37 | const { style, ...otherProps } = props; 38 | const { horizontalSpacing } = useGLSDefaults(); 39 | const width = cssLengthToString(props.space == null ? horizontalSpacing : props.space); 40 | const styles: React.CSSProperties = { 41 | display: 'inline-block', 42 | width: width, 43 | ...style 44 | }; 45 | return
; 46 | }; 47 | HorizontalSpacer.displayName = 'HorizontalSpacer'; 48 | 49 | 50 | /** 51 | * Takes a fixed amount of vertical space 52 | */ 53 | export const VerticalSpacer: React.FC = (props) => { 54 | const { style, ...otherProps } = props; 55 | const { verticalSpacing } = useGLSDefaults(); 56 | const height = cssLengthToString(props.space == null ? verticalSpacing : props.space); 57 | const styles: React.CSSProperties = { 58 | height: height, 59 | ...style 60 | }; 61 | return
; 62 | }; 63 | VerticalSpacer.displayName = 'VerticalSpacer'; 64 | -------------------------------------------------------------------------------- /src/components/vertical.tsx: -------------------------------------------------------------------------------- 1 | import * as typestyle from 'typestyle'; 2 | import * as React from 'react'; 3 | import { BaseProps, SizingProp, SpacingProp, VerticalsAlignProps, CrossAxisAlignProp } from '../common'; 4 | import { createBaseTag, _processSizing, useGLSDefaults, _processCrossAxisAlign } from '../internal/utils'; 5 | import { vertical, centerJustified, endJustified, end, center, start } from '../styles/flex'; 6 | import { verticallySpaced } from '../styles/spacing'; 7 | 8 | export interface VerticalProps extends BaseProps, SizingProp, SpacingProp, VerticalsAlignProps, CrossAxisAlignProp { 9 | } 10 | 11 | /** 12 | * Layout out children vertically with a margin between them 13 | */ 14 | export const Vertical = React.forwardRef((props: VerticalProps, ref: React.LegacyRef) => { 15 | const { verticalSpacing } = useGLSDefaults(); 16 | const { 17 | sizing, 18 | crossAxisAlign, 19 | 20 | spacing = verticalSpacing, 21 | verticalAlign, 22 | horizontalAlign, 23 | 24 | ...otherProps 25 | } = props; 26 | 27 | const klass = typestyle.style( 28 | _processSizing(sizing), 29 | crossAxisAlign != null && _processCrossAxisAlign(crossAxisAlign), 30 | vertical, 31 | spacing !== 0 && verticallySpaced(spacing), 32 | verticalAlign == 'center' && centerJustified, 33 | verticalAlign == 'bottom' && endJustified, 34 | horizontalAlign == 'left' && start, 35 | horizontalAlign == 'center' && center, 36 | horizontalAlign == 'right' && end, 37 | ); 38 | return createBaseTag(otherProps, klass, 'Vertical', ref); 39 | }); 40 | Vertical.displayName = 'Vertical'; 41 | -------------------------------------------------------------------------------- /src/demos/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Demo components for docs 3 | */ 4 | 5 | import React from "react"; 6 | import * as typestyle from "typestyle"; 7 | import * as gls from ".."; 8 | 9 | export const Star: React.FC = () => { 10 | const link = "https://ghbtns.com/github-btn.html?user=basarat&repo=gls&type=star&count=true" 11 | return ( 12 |
13 | Powered by your ⭐s 17 |