Skip to content
Permalink
Browse files

feat(Flex): add Flex component package (#204)

* feat(flex): checkpoint

* fix: remove ignored props

* fix: add back in missing props

* fix: simplify flex api props, simple story

* fix: box typescript error

* fix: export types, add stretch, around, and between

* fix: adjust column, clean up getFlexStyles

* fix: adjust flex functions, add more stories

* feat: more flex stories

* feat: stories and separate align functions

* fix: small ts tweaked fix

* fix: horizontal align ts fix

* chore: fix lint error

* feat: add flex tests

* chore: feedback changes and add basis tests

* chore: remove unused code

* fix: account for grow, shrink, vertical, wrap boolean arrays

* chore: update stories and tests

* chore: exporting functions and unit tests

* fix: add react useMemo to style functions

* fix: proptype fixes, tests, and shapshots
  • Loading branch information
richbachman committed Dec 13, 2019
1 parent a308215 commit 74877c4b0b260c5c6451bf9eb95c2baf3b1b7751

Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -0,0 +1,48 @@
{
"name": "@twilio-paste/flex",
"version": "0.0.0",
"category": "layout",
"status": "alpha",
"description": "",
"author": "Twilio Inc.",
"license": "MIT",
"main:dev": "src/index.tsx",
"main": "dist/index.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
"sideEffects": false,
"publishConfig": {
"access": "public"
},
"files": [
"dist"
],
"scripts": {
"build": "yarn clean && yarn compile",
"build:dev": "yarn clean && yarn compile:dev",
"clean": "rm -rf ./dist && rm -rf tsconfig.build.tsbuildinfo && rm -rf .rpt2_cache",
"compile": "rollup -c --environment NODE_ENV:production",
"compile:dev": "rollup -c --environment NODE_ENV:development",
"prepublishOnly": "yarn build",
"type-check": "tsc --noEmit"
},
"peerDependencies": {
"@twilio-paste/box": "^1.1.0",
"@twilio-paste/types": "^2.1.1",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"styled-system": "^5.1.2"
},
"devDependencies": {
"@twilio-paste/box": "^1.1.0",
"@twilio-paste/types": "^2.1.1",
"csstype": "^2.6.6",
"rollup": "^1.16.2",
"rollup-plugin-babel": "^4.3.3",
"rollup-plugin-commonjs": "^10.0.1",
"rollup-plugin-node-resolve": "^5.1.0",
"rollup-plugin-terser": "^5.0.0",
"rollup-plugin-typescript2": "^0.21.2",
"typescript": "^3.5.2"
}
}
@@ -0,0 +1,34 @@
import typescript from 'rollup-plugin-typescript2';
import babel from 'rollup-plugin-babel';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import {terser} from 'rollup-plugin-terser';
import pkg from './package.json';

export default {
input: pkg['main:dev'],
output: [
{
file: pkg.main,
format: 'cjs',
},
{
file: pkg.module,
format: 'esm',
},
],
external: [...Object.keys(pkg.peerDependencies || {})],
plugins: [
resolve(),
commonjs(),
typescript({
clean: true,
typescript: require('typescript'),
tsconfig: './tsconfig.build.json',
}),
babel({
exclude: 'node_modules/**',
}),
process.env.NODE_ENV === 'production' ? terser() : null,
],
};
@@ -0,0 +1,239 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import {ResponsiveValue} from 'styled-system';
import {Box} from '@twilio-paste/box';
import {FlexboxProps} from '@twilio-paste/types';

type DisplayOptions = 'flex' | 'inline-flex';
export type Display = ResponsiveValue<DisplayOptions>;
type VerticalAlignOptions = 'top' | 'center' | 'bottom' | 'stretch';
export type VerticalAlign = ResponsiveValue<VerticalAlignOptions>;
type HorizontalAlignOptions = 'left' | 'center' | 'right' | 'around' | 'between';
export type HorizontalAlign = ResponsiveValue<HorizontalAlignOptions>;
type VerticalOptions = boolean;
export type Vertical = ResponsiveValue<VerticalOptions>;
type GrowOptions = boolean | number;
export type Grow = ResponsiveValue<GrowOptions>;
type ShrinkOptions = boolean | number;
export type Shrink = ResponsiveValue<ShrinkOptions>;
type BasisOptions = string | number;
export type Basis = ResponsiveValue<BasisOptions>;
type WrapOptions = boolean;
export type Wrap = ResponsiveValue<WrapOptions>;

export interface FlexProps {
display?: Display;
vertical?: Vertical;
vAlignContent?: VerticalAlign;
hAlignContent?: HorizontalAlign;
grow?: Grow;
shrink?: Shrink;
basis?: Basis;
wrap?: Wrap;
}

export const getGrow = ({grow}: FlexProps): {} => {
if (Array.isArray(grow)) {
return (grow as GrowOptions[]).map((value: GrowOptions) => {
return Number(value);
});
}

if (typeof grow === 'number') {
return grow;
}

if (grow) {
return 1;
}

return 0;
};

export const getShrink = ({shrink, basis}: FlexProps): {} => {
if (Array.isArray(shrink)) {
return (shrink as ShrinkOptions[]).map((value: ShrinkOptions) => {
return Number(value);
});
}

if (typeof shrink === 'number') {
return shrink;
}

if (typeof shrink === 'boolean') {
return shrink ? 1 : 0;
}

if (basis && basis !== 'auto') {
return 0;
}

return 1;
};

export const getSuffix = (item: Basis): {} => {
const suffix = typeof item === 'number' || String(parseInt(item as string, 10)) === item ? 'px' : '';
return item + suffix;
};

export const getBasis = ({basis}: FlexProps): {} => {
if (Array.isArray(basis)) {
return (basis as BasisOptions[]).map((value: BasisOptions) => {
return getSuffix(value);
});
}

if (basis) {
return getSuffix(basis);
}

return 'auto';
};

export const getVertical = ({vertical}: FlexProps): {} => {
if (Array.isArray(vertical)) {
return (vertical as VerticalOptions[]).map((value: VerticalOptions) => {
if (typeof value === 'boolean') {
return value === true ? 'column' : 'row';
}
return value;
});
}

if (vertical) {
return 'column';
}

return 'row';
};

export const getWrap = ({wrap}: FlexProps): {} => {
if (Array.isArray(wrap)) {
return (wrap as WrapOptions[]).map((value: WrapOptions) => {
if (typeof value === 'boolean') {
return value === true ? 'wrap' : 'nowrap';
}
return value;
});
}

if (wrap) {
return 'wrap';
}

return 'nowrap';
};

const RemapedVerticalAlignments = {
top: 'flex-start',
center: 'center',
bottom: 'flex-end',
stretch: 'stretch',
};

export const vAlignToProps = ({vAlignContent}: FlexProps): {} => {
if (Array.isArray(vAlignContent)) {
return (vAlignContent as VerticalAlignOptions[]).map(value => RemapedVerticalAlignments[value]);
}

if (vAlignContent) {
return RemapedVerticalAlignments[vAlignContent as VerticalAlignOptions];
}

return 'flex-start';
};

const RemapedHorizontalAlignments = {
left: 'flex-start',
center: 'center',
right: 'flex-end',
around: 'space-around',
between: 'space-between',
};

export const hAlignToProps = ({hAlignContent}: FlexProps): {} => {
if (Array.isArray(hAlignContent)) {
return (hAlignContent as HorizontalAlignOptions[]).map(value => RemapedHorizontalAlignments[value]);
}

if (hAlignContent) {
return RemapedHorizontalAlignments[hAlignContent as HorizontalAlignOptions];
}

return 'flex-start';
};

const getFlexStyles = (props: FlexProps): FlexboxProps => {
const styles: FlexboxProps = {
justifyContent: props.vertical ? vAlignToProps(props) : hAlignToProps(props),
alignItems: props.vertical ? hAlignToProps(props) : vAlignToProps(props),
};

if (props.grow || props.shrink || props.basis) {
styles.flexGrow = getGrow(props);
styles.flexShrink = getShrink(props);
styles.flexBasis = getBasis(props);
}

if (props.vertical) {
styles.flexDirection = getVertical(props);
}

if (props.wrap) {
styles.flexWrap = getWrap(props);
}

return styles;
};

const Flex: React.FC<FlexProps> = props => {
const FlexStyles = React.useMemo(() => getFlexStyles(props), [props]);
return (
<Box {...FlexStyles} display={props.display}>
{props.children}
</Box>
);
};

Flex.propTypes = {
display: PropTypes.oneOfType([
PropTypes.oneOf(['flex', 'inline-flex']),
PropTypes.arrayOf(PropTypes.oneOf(['flex', 'inline-flex'])),
] as any),
vertical: PropTypes.oneOfType([
PropTypes.oneOfType([PropTypes.bool]),
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.bool])),
] as any),
vAlignContent: PropTypes.oneOfType([
PropTypes.oneOf(['top', 'center', 'bottom', 'stretch']),
PropTypes.arrayOf(PropTypes.oneOf(['top', 'center', 'bottom', 'stretch'])),
] as any),
hAlignContent: PropTypes.oneOfType([
PropTypes.oneOf(['left', 'center', 'right', 'around', 'between']),
PropTypes.arrayOf(PropTypes.oneOf(['left', 'center', 'right', 'around', 'between'])),
] as any),
grow: PropTypes.oneOfType([
PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.bool, PropTypes.number])),
] as any),
shrink: PropTypes.oneOfType([
PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.bool, PropTypes.number])),
] as any),
basis: PropTypes.oneOfType([
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
] as any),
wrap: PropTypes.oneOfType([
PropTypes.oneOfType([PropTypes.bool]),
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.bool])),
] as any),
};

Flex.defaultProps = {
display: 'flex',
};

Flex.displayName = 'Flex';
export {Flex};

1 comment on commit 74877c4

@now

This comment has been minimized.

Please sign in to comment.
You can’t perform that action at this time.