From b864df9393afa95db38a7c486c7a9504df1c8bdc Mon Sep 17 00:00:00 2001 From: Kata Date: Mon, 20 May 2019 00:57:55 -0400 Subject: [PATCH] [design]: initialize design system --- packages/roleypoly-design/.gitignore | 3 + .../roleypoly-design/.storybook/addons.js | 2 + .../roleypoly-design/.storybook/config.js | 8 + .../.storybook/webpack.config.js | 16 ++ packages/roleypoly-design/.stylelintrc | 9 ++ packages/roleypoly-design/package.json | 42 +++++ .../src/button/Button.story.tsx | 12 ++ .../roleypoly-design/src/button/Button.tsx | 10 ++ packages/roleypoly-design/src/button/index.ts | 5 + .../src/button/styled-components.tsx | 58 +++++++ .../src/helpers/deep-merge.ts | 34 ++++ .../roleypoly-design/src/helpers/overrides.ts | 151 ++++++++++++++++++ packages/roleypoly-design/tsconfig.json | 38 +++++ packages/roleypoly-design/tslint.json | 3 + 14 files changed, 391 insertions(+) create mode 100644 packages/roleypoly-design/.gitignore create mode 100644 packages/roleypoly-design/.storybook/addons.js create mode 100644 packages/roleypoly-design/.storybook/config.js create mode 100644 packages/roleypoly-design/.storybook/webpack.config.js create mode 100644 packages/roleypoly-design/.stylelintrc create mode 100644 packages/roleypoly-design/package.json create mode 100644 packages/roleypoly-design/src/button/Button.story.tsx create mode 100644 packages/roleypoly-design/src/button/Button.tsx create mode 100644 packages/roleypoly-design/src/button/index.ts create mode 100644 packages/roleypoly-design/src/button/styled-components.tsx create mode 100644 packages/roleypoly-design/src/helpers/deep-merge.ts create mode 100644 packages/roleypoly-design/src/helpers/overrides.ts create mode 100644 packages/roleypoly-design/tsconfig.json create mode 100644 packages/roleypoly-design/tslint.json diff --git a/packages/roleypoly-design/.gitignore b/packages/roleypoly-design/.gitignore new file mode 100644 index 0000000..1eaff4a --- /dev/null +++ b/packages/roleypoly-design/.gitignore @@ -0,0 +1,3 @@ +lib +node_modules +*.log \ No newline at end of file diff --git a/packages/roleypoly-design/.storybook/addons.js b/packages/roleypoly-design/.storybook/addons.js new file mode 100644 index 0000000..6aed412 --- /dev/null +++ b/packages/roleypoly-design/.storybook/addons.js @@ -0,0 +1,2 @@ +import '@storybook/addon-actions/register'; +import '@storybook/addon-links/register'; diff --git a/packages/roleypoly-design/.storybook/config.js b/packages/roleypoly-design/.storybook/config.js new file mode 100644 index 0000000..1f94f9f --- /dev/null +++ b/packages/roleypoly-design/.storybook/config.js @@ -0,0 +1,8 @@ +import { configure } from '@storybook/react' +const req = require.context('../src', true, /\.stor\bies|y\b\.[tj]sx?$/) + +function loadStories() { + req.keys().forEach(req) +} + +configure(loadStories, module) diff --git a/packages/roleypoly-design/.storybook/webpack.config.js b/packages/roleypoly-design/.storybook/webpack.config.js new file mode 100644 index 0000000..1609a56 --- /dev/null +++ b/packages/roleypoly-design/.storybook/webpack.config.js @@ -0,0 +1,16 @@ +module.exports = ({ config }) => { + config.module.rules.push({ + test: /\.(ts|tsx)$/, + use: [ + { + loader: require.resolve('awesome-typescript-loader'), + }, + // Optional + { + loader: require.resolve('react-docgen-typescript-loader'), + }, + ], + }); + config.resolve.extensions.push('.ts', '.tsx'); + return config; +}; \ No newline at end of file diff --git a/packages/roleypoly-design/.stylelintrc b/packages/roleypoly-design/.stylelintrc new file mode 100644 index 0000000..7bde962 --- /dev/null +++ b/packages/roleypoly-design/.stylelintrc @@ -0,0 +1,9 @@ +{ + "processors": [ + "stylelint-processor-styled-components" + ], + "extends": [ + "stylelint-config-recommended", + "stylelint-config-styled-components" + ] +} \ No newline at end of file diff --git a/packages/roleypoly-design/package.json b/packages/roleypoly-design/package.json new file mode 100644 index 0000000..29fbe5a --- /dev/null +++ b/packages/roleypoly-design/package.json @@ -0,0 +1,42 @@ +{ + "private": true, + "name": "@roleypoly/design", + "version": "2.0.0", + "dependencies": { + "react": "^16.8.6", + "react-dom": "^16.8.6", + "styled-components": "^4.2.0" + }, + "files": [ + "lib" + ], + "devDependencies": { + "@babel/core": "^7.4.4", + "@storybook/addon-actions": "^5.0.11", + "@storybook/addon-info": "^5.0.11", + "@storybook/addon-knobs": "^5.0.11", + "@storybook/addon-links": "^5.0.11", + "@storybook/addons": "^5.0.11", + "@storybook/react": "^5.0.11", + "@types/jest": "^24.0.13", + "@types/node": "^12.0.2", + "@types/react": "^16.8.17", + "@types/react-dom": "^16.8.4", + "@types/storybook__addon-actions": "^3.4.2", + "@types/storybook__addon-knobs": "^5.0.0", + "@types/storybook__addon-links": "^3.3.4", + "@types/storybook__react": "^4.0.1", + "@types/styled-components": "4.1.8", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.6", + "react-docgen-typescript-loader": "^3.1.0", + "stylelint": "^10.0.1", + "tslint": "^5.16.0", + "tslint-config-standard": "^8.0.1", + "typescript": "^3.4.5" + }, + "scripts": { + "storybook": "start-storybook -p 6006", + "build-storybook": "build-storybook" + } +} \ No newline at end of file diff --git a/packages/roleypoly-design/src/button/Button.story.tsx b/packages/roleypoly-design/src/button/Button.story.tsx new file mode 100644 index 0000000..eb9bd8d --- /dev/null +++ b/packages/roleypoly-design/src/button/Button.story.tsx @@ -0,0 +1,12 @@ +import * as React from 'react' +import { storiesOf } from '@storybook/react' + +import Button from './Button' + +const s = storiesOf('Button', module) + +s.add('Default', () => ) +s.add('Disabled', () => ) +s.add('Primary', () => ) +s.add('Secondary', () => ) +s.add('Loading', () => ) \ No newline at end of file diff --git a/packages/roleypoly-design/src/button/Button.tsx b/packages/roleypoly-design/src/button/Button.tsx new file mode 100644 index 0000000..4362022 --- /dev/null +++ b/packages/roleypoly-design/src/button/Button.tsx @@ -0,0 +1,10 @@ +import * as React from 'react' +import { + StyledButton +} from './styled-components' + +const Button = ({ children, ...rest }: { children: React.ReactChild | React.ReactChild[] }) => + {children} + + +export default Button diff --git a/packages/roleypoly-design/src/button/index.ts b/packages/roleypoly-design/src/button/index.ts new file mode 100644 index 0000000..fd8e2db --- /dev/null +++ b/packages/roleypoly-design/src/button/index.ts @@ -0,0 +1,5 @@ +export { default as default } from './Button' + +export { + StyledButton +} from './styled-components' diff --git a/packages/roleypoly-design/src/button/styled-components.tsx b/packages/roleypoly-design/src/button/styled-components.tsx new file mode 100644 index 0000000..814a08d --- /dev/null +++ b/packages/roleypoly-design/src/button/styled-components.tsx @@ -0,0 +1,58 @@ +// import * as React from 'react' +import styled from 'styled-components' + +export const StyledButton = styled.button` + /* reset some styles */ + box-shadow: none; + appearance: none; + outline: none; + cursor: pointer; + background-color: rgba(0,0,0,0.1); + + /* real styles */ + position: relative; + transition: all 0.05s ease-in-out; + padding: 1em 1.4em; + font-weight: bold; + border: 1px solid rgba(0,0,0,0.1); + + &::after { + content: ""; + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + + /* transparent by default */ + background-color: transparent; + + transition: background-color 0.05s ease-in-out; + + /* put the overlay behind the text */ + z-index: -1; + } + + /* on hover, raise, brighten and shadow */ + &:hover { + transform: translateY(-1px); + box-shadow: 0 1px 2px rgba(0,0,0,0.05); + + &::after { + background-color: rgba(255, 255, 255, 0.3); + } + } + + /* on click, lower and darken */ + &:active { + outline: none; + box-shadow: none; + border-color: rgba(0,0,0,0.2); + + transform: translateY(0); + + &::after { + background-color: rgba(0, 0, 0, 0.1); + } + } +` diff --git a/packages/roleypoly-design/src/helpers/deep-merge.ts b/packages/roleypoly-design/src/helpers/deep-merge.ts new file mode 100644 index 0000000..002c3b7 --- /dev/null +++ b/packages/roleypoly-design/src/helpers/deep-merge.ts @@ -0,0 +1,34 @@ +/* +Copyright (c) 2018-2019 Uber Technologies, Inc. +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ + +export default function deepMerge ( + target?: {}, + ...sources: Array +): {} { + target = target || {} + const len = sources.length + let obj + let value + for (let i = 0; i < len; i++) { + obj = sources[i] || {} + for (let key in obj) { + if (obj.hasOwnProperty(key)) { + value = obj[key] + if (isCloneable(value)) { + target[key] = deepMerge(target[key] || {}, value) + } else { + target[key] = value + } + } + } + } + return target +} + +/* eslint-disable-next-line flowtype/no-weak-types */ +function isCloneable(obj: Object) { + return Array.isArray(obj) || {}.toString.call(obj) === '[object Object]' +} diff --git a/packages/roleypoly-design/src/helpers/overrides.ts b/packages/roleypoly-design/src/helpers/overrides.ts new file mode 100644 index 0000000..e0e67f7 --- /dev/null +++ b/packages/roleypoly-design/src/helpers/overrides.ts @@ -0,0 +1,151 @@ +/* +Copyright (c) 2018-2019 Uber Technologies, Inc. +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +import * as React from 'react' +import deepMerge from './deep-merge' + +export type StyleOverrideT = {} | (({ }) => {} | undefined) + +export type OverrideObjectT = { + component?: React.ComponentType, + props?: {}, + style?: StyleOverrideT +} + +export type OverrideT = OverrideObjectT | React.ComponentType + +export type OverridesT = { + [x: string]: OverrideT +} + +/** + * Given an override argument, returns the component implementation override if it exists + */ +// eslint-disable-next-line flowtype/no-weak-types +export function getOverride (override: any): any { + // Check if override is OverrideObjectT + if (override && typeof override === 'object') { + // Remove this 'any' once this flow issue is fixed: + // https://github.com/facebook/flow/issues/6666 + // eslint-disable-next-line flowtype/no-weak-types + return override.component + } + // Otherwise it must be a component type (function or class) or null/undefined + return override +} + +/** + * Given an override argument, returns the override props that should be passed + * to the component when rendering it. + */ +export function getOverrideProps (override?: OverrideT) { + if (override && typeof override === 'object') { + return { + // $FlowFixMe + ...override.props, + // $FlowFixMe + $style: override.style + } + } + return {} +} + +/** + * Coerces an override argument into an override object + * (sometimes it is just an override component) + */ +export function toObjectOverride ( + override: OverrideT +): OverrideObjectT { + if (typeof override === 'function') { + return { + component: (override) + } + } + // Flow can't figure out that typeof 'function' above will + // catch React.StatelessFunctionalComponent + // (probably related to https://github.com/facebook/flow/issues/6666) + // eslint-disable-next-line flowtype/no-weak-types + return (((override || {}) as any) as OverrideObjectT) +} + +/** + * Get a convenient override array that will always have [component, props] + */ +/* eslint-disable flowtype/no-weak-types */ +export function getOverrides ( + override: any, + defaultComponent: React.ComponentType +): [React.ComponentType, {}] { + const component = getOverride(override) || defaultComponent + const props = getOverrideProps(override) + return [component, props] +} +/* eslint-enable flowtype/no-weak-types */ + +/** + * Merges two overrides objects – this is useful if you want to inject your own + * overrides into a child component, but also accept further overrides from + * from upstream. See `mergeOverride` below. + */ +export function mergeOverrides ( + target: OverridesT | undefined = {}, + source: OverridesT | undefined = {} +): OverridesT { + const allIdentifiers = Object.keys({ ...target, ...source }) + return allIdentifiers.reduce((acc, name) => { + acc[name] = mergeOverride( + toObjectOverride(target[name]), + toObjectOverride(source[name]) + ) + return acc + }, {}) +} + +/** + * Merges two override objects using the following behavior: + * - Component implementation from the source (parent) replaces target + * - Props and styles are both deep merged + */ +export function mergeOverride ( + target: OverrideObjectT, + source: OverrideObjectT +): OverrideObjectT { + // Shallow merge should handle `component` + const merged = { ...target, ...source } + // Props just use deep merge + if (target.props && source.props) { + merged.props = deepMerge({}, target.props, source.props) + } + // Style overrides need special merging since they may be functions + if (target.style && source.style) { + merged.style = mergeStyleOverrides(target.style, source.style) + } + return merged +} + +/** + * Since style overrides can be an object *or* a function, we need to handle + * the case that one of them is a function. We do this by returning a new + * function that deep merges the result of each style override + */ +export function mergeStyleOverrides ( + target: StyleOverrideT, + source: StyleOverrideT +): StyleOverrideT { + // Simple case of both objects + if (typeof target === 'object' && typeof source === 'object') { + return deepMerge({}, target, source) + } + + // At least one is a function, return a new composite function + return (...args) => { + return deepMerge( + {}, + typeof target === 'function' ? target(...args) : target, + typeof source === 'function' ? source(...args) : source + ) + } +} diff --git a/packages/roleypoly-design/tsconfig.json b/packages/roleypoly-design/tsconfig.json new file mode 100644 index 0000000..69d92b9 --- /dev/null +++ b/packages/roleypoly-design/tsconfig.json @@ -0,0 +1,38 @@ +{ + "compilerOptions": { + "outDir": "./lib", + "module": "commonjs", + "target": "es2018", + "lib": [ + "es5", + "es6", + "es7", + "es2017", + "dom" + ], + "sourceMap": true, + "allowJs": false, + "jsx": "react", + "moduleResolution": "node", + "rootDirs": [ + "src" + ], + "baseUrl": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": true, + "declaration": true, + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "exclude": [ + "node_modules", + "build", + "scripts" + ] +} \ No newline at end of file diff --git a/packages/roleypoly-design/tslint.json b/packages/roleypoly-design/tslint.json new file mode 100644 index 0000000..7967051 --- /dev/null +++ b/packages/roleypoly-design/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "tslint-config-standard" +} \ No newline at end of file