├── .eslintignore
├── .eslintrc
├── .flowconfig
├── .gitignore
├── .npmignore
├── .vscode
└── settings.json
├── CHANGELOG.md
├── README.md
├── babel.config.js
├── examples
└── sketch
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── components
│ │ ├── AppBar.jsx
│ │ ├── BarChart.jsx
│ │ ├── InputField.jsx
│ │ ├── Select.jsx
│ │ └── TextInput.jsx
│ ├── index.js
│ ├── manifest.json
│ ├── screens
│ │ ├── Home.jsx
│ │ ├── Signup.jsx
│ │ └── Visual.jsx
│ └── styles
│ │ └── theme.js
│ ├── static-blog.sketchplugin
│ └── Contents
│ │ └── Sketch
│ │ ├── index.js
│ │ ├── index.js.map
│ │ └── manifest.json
│ └── webpack.skpm.config.js
├── index.js
├── index.native.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── sketch.js
├── src
├── @types
│ ├── react-primitives-svg
│ │ └── index.d.ts
│ ├── react-primitives
│ │ └── index.d.ts
│ └── styled-system
│ │ └── index.d.ts
├── LayoutProvider.tsx
├── ThemeProvider.tsx
├── atoms
│ ├── Box
│ │ ├── Box.tsx
│ │ └── index.ts
│ ├── Button
│ │ ├── Button.tsx
│ │ └── index.ts
│ ├── Circle
│ │ ├── Circle.tsx
│ │ └── index.ts
│ ├── Image
│ │ ├── Image.tsx
│ │ └── index.ts
│ ├── Line
│ │ ├── Line.tsx
│ │ └── index.ts
│ ├── Rectangle
│ │ ├── Rectangle.tsx
│ │ └── index.ts
│ ├── Text
│ │ ├── Text.tsx
│ │ └── index.ts
│ └── index.ts
├── context.ts
├── hooks
│ ├── index.ts
│ ├── use-color-scheme
│ │ ├── index.figma.ts
│ │ ├── index.native.ts
│ │ ├── index.sketch.ts
│ │ └── index.web.ts
│ ├── use-dimensions
│ │ ├── index.figma.ts
│ │ ├── index.native.ts
│ │ ├── index.sketch.ts
│ │ └── index.web.ts
│ ├── use-hover.js
│ ├── use-style-state.tsx
│ └── use-viewport
│ │ └── index.web.ts
├── index.ts
├── molecules
│ ├── Form
│ │ ├── Input.tsx
│ │ ├── TextInput
│ │ │ ├── TextInput.figma.tsx
│ │ │ ├── TextInput.native.tsx
│ │ │ ├── TextInput.sketch.tsx
│ │ │ ├── TextInput.web.tsx
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── Row
│ │ ├── Row.tsx
│ │ └── index.ts
│ ├── Typography
│ │ ├── Headline.tsx
│ │ └── index.ts
│ └── index.ts
├── styled.ts
└── utils
│ ├── extend.tsx
│ ├── index.ts
│ ├── shadow.ts
│ └── styles.ts
├── tsconfig.json
└── tsconfig.module.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "airbnb",
4 | "plugin:flowtype/recommended"
5 | ],
6 | "plugins": [
7 | "flowtype"
8 | ],
9 | "rules": {
10 | "import/prefer-default-export": 0,
11 | "import/no-named-as-default": 0,
12 | "flowtype/semi": "error",
13 | "react/default-props-match-prop-types": 0,
14 | "flowtype/delimiter-dangle": [
15 | 2,
16 | "always-multiline"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [libs]
2 | ./node_modules/fbjs/flow/lib
3 |
4 | [options]
5 | esproposal.class_static_fields=enable
6 | esproposal.class_instance_fields=enable
7 |
8 | module.name_mapper='^\(.*\)\.css$' -> 'react-scripts/config/flow/css'
9 | module.name_mapper='^\(.*\)\.\(jpg\|png\|gif\|eot\|svg\|ttf\|woff\|woff2\|mp4\|webm\)$' -> 'react-scripts/config/flow/file'
10 |
11 | suppress_type=$FlowIssue
12 | suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | .DS_Store
64 |
65 | # TODO: fix up examples
66 | examples
67 |
68 | lib/
69 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.log*
3 | node_modules
4 | _book
5 | examples
6 | docs
7 | .vscode
8 | yarn.lock
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | ## Unreleased Changes
6 |
7 | -
8 | -
9 | -
10 |
11 | ## Versions
12 |
13 | ## 0.3.3
14 |
15 | - Create web umd/esm build
16 |
17 |
18 | ## 0.3.2
19 |
20 | - Fix
21 |
22 | ## 0.3.1
23 |
24 | - Forward ref for
25 | - Pass `src` prop for `` on web
26 | - Concat `px` to `` `lineHeight` prop transformation
27 |
28 | ## 0.3.0
29 |
30 | - Add React Native support
31 | - Add `useWindowDimensions` hook (cross-platform primitive)
32 |
33 | ### 0.2.0
34 |
35 | - Add cross-platform shadow resolving support (`boxShadow` interface)
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Elemental React
2 |
3 | > Build UI components once, render to any platform using `react-primitives`. This library abstracts away common UI patterns for you.
4 |
5 | [](https://www.npmjs.com/package/elemental-react)
6 | [](https://www.npmjs.com/package/elemental-react)
7 |
8 |
9 | Abstraction for app presentation to speed up cross-platform UI design and development with code using React/Sketch as a design function. This is an underlying cross-platform abstraction wrapper that allows you to build your own design language
10 |
11 | > Based off [`styled-system`]() and [`styled-components`](). API is similar to [`rebass`](https://github.com/rebassjs/rebass), but using React Native style components.
12 |
13 | This is an **alpha/preview** release. Please **test** comprehensively before using in **production**.
14 |
15 | **Supported React Renderers:**
16 |
17 | - `react` - React web
18 | - `react-native` - React Native (WIP)
19 | - `react-sketchapp` - React Sketch.app
20 | - **more** - Post an issue to suggest more! Ideally an API should exist that lets you override the primitives
21 |
22 | ## Getting Started
23 |
24 | ```sh
25 | npm install elemental-react
26 | ```
27 |
28 | ```jsx
29 | import React from 'react';
30 | import {
31 | Box, Text, Button,
32 | } from 'elemental-react';
33 |
34 | // ...
35 | return (
36 |
37 |
38 | Hello World
39 |
40 |
41 | );
42 | ```
43 |
44 | ## Example UI
45 |
46 | Quick example of a design created by a coder (me :slightly_smiling_face:), developed with live rendering to `react-sketchapp`:
47 | 
48 |
49 | ## Related Reading
50 |
51 | - https://daneden.me/2018/01/05/subatomic-design-systems/
52 | - https://medium.com/styled-components/build-better-component-libraries-with-styled-system-4951653d54ee
53 | - https://medium.com/@_alanbsmith/layered-components-6f18996073a8
54 | - https://medium.com/@_alanbsmith/component-api-design-3ff378458511
55 |
56 | ## Design Properties
57 |
58 | ### Line
59 | Themed colour (primary)
60 | - Weight
61 | - Color
62 | - Texture
63 | - Style
64 |
65 |
66 | ### Shape
67 | Foundational element.
68 | - Depth
69 | - Light, shadow and depth (illusion)
70 |
71 | ### Texture
72 | Physical quality of a surface.
73 |
74 | ### Balance
75 | Equal distribution of visual weight – spacing.
76 | - Symmetry (each side is the same)
77 | - Asymmetry – evenly distribute weight
78 | - Rule of thirds – grid divided into thirds
79 |
80 |
81 | ### Color
82 |
83 | **Properties**
84 | - Hue
85 | - Saturation
86 | - Monochromatic
87 | - Value
88 |
89 | **Analagous Colour Scheme**
90 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable quote-props, quotes, comma-dangle */
2 |
3 | module.exports = {
4 | "presets": [
5 | "@babel/preset-env",
6 | "@babel/preset-react",
7 | "@babel/preset-typescript"
8 | ],
9 | "plugins": [
10 | "@babel/plugin-proposal-class-properties"
11 | // "babel-plugin-styled-components"
12 | ]
13 | };
14 |
--------------------------------------------------------------------------------
/examples/sketch/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "static-blog",
3 | "version": "0.0.1",
4 | "description": "",
5 | "skpm": {
6 | "main": "static-blog.sketchplugin",
7 | "manifest": "src/manifest.json"
8 | },
9 | "scripts": {
10 | "build": "skpm-build",
11 | "watch": "skpm-build --watch",
12 | "render": "skpm-build --watch --run",
13 | "render:once": "skpm-build --run",
14 | "postinstall": "npm run build && skpm-link"
15 | },
16 | "author": "Macintosh Helper ",
17 | "license": "MIT",
18 | "devDependencies": {
19 | "@skpm/builder": "^0.5.15"
20 | },
21 | "dependencies": {
22 | "@vx/gradient": "0.0.183",
23 | "@vx/group": "0.0.183",
24 | "@vx/mock-data": "0.0.185",
25 | "@vx/scale": "0.0.182",
26 | "@vx/shape": "0.0.184",
27 | "chroma-js": "^1.2.2",
28 | "d3-scale": "^3.0.0",
29 | "elemental-react": "^0.1.1",
30 | "prop-types": "^15.5.8",
31 | "react": "^16.13.1",
32 | "react-primitives": "^0.8.1",
33 | "react-primitives-svg": "0.0.3",
34 | "react-sketchapp": "^3.1.1",
35 | "react-test-renderer": "^16.13.1",
36 | "styled-components": "^5.1.0",
37 | "styled-system": "^5.1.5",
38 | "yoga-layout-prebuilt": "^1.9.5"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/examples/sketch/src/components/AppBar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Box, Line, Button } from 'elemental-react';
3 |
4 |
5 |
6 | export const MenuIcon = () => (
7 |
8 |
9 |
10 |
11 |
12 | );
13 |
14 | export const ActionButton = ({ children, ...props }) => (
15 |
18 | )
19 |
20 | const AppBar = ({ children, ...props }) => (
21 |
22 | {children}
23 |
24 | );
25 |
26 | AppBar.MenuIcon = MenuIcon;
27 | AppBar.ActionButton = ActionButton;
28 |
29 | export default AppBar;
30 |
--------------------------------------------------------------------------------
/examples/sketch/src/components/BarChart.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Bar } from '@vx/shape';
3 | import { Group } from '@vx/group';
4 | import { GradientTealBlue } from '@vx/gradient';
5 | import { letterFrequency } from '@vx/mock-data';
6 | import { scaleBand, scaleLinear } from '@vx/scale';
7 |
8 | import { Svg, Rect } from 'react-primitives-svg';
9 |
10 |
11 | const data = letterFrequency.slice(5);
12 |
13 |
14 | // accessors
15 | const x = d => d.letter;
16 | const y = d => +d.frequency * 100;
17 |
18 | export default ({ width, height }) => {
19 | // bounds
20 | const xMax = width;
21 | const yMax = height - 120;
22 |
23 | // scales
24 | const xScale = scaleBand({
25 | rangeRound: [0, xMax],
26 | domain: data.map(x),
27 | padding: 0.4
28 | });
29 | const yScale = scaleLinear({
30 | rangeRound: [yMax, 0],
31 | domain: [0, Math.max(...data.map(y))]
32 | });
33 |
34 | return (
35 |
58 | );
59 | };
60 |
--------------------------------------------------------------------------------
/examples/sketch/src/components/InputField.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Box, Text } from 'elemental-react';
3 |
4 | const InputField = ({ label, placeholder, error, value = '', children, ...props }) => (
5 |
6 | 0 ? 1 : 0}>
7 |
8 | {label && value.length > 0 ? label : ''}
9 |
10 |
11 |
12 | {children({ label, placeholder, error, value })}
13 |
14 |
15 | {error && (
16 |
17 | {error}
18 |
19 | )}
20 |
21 |
22 | );
23 | export default InputField;
24 |
--------------------------------------------------------------------------------
/examples/sketch/src/components/Select.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Box, Line, Text } from 'elemental-react';
3 |
4 | const Select = ({ label, placeholder, error, value = '', ...props }) => (
5 |
6 |
7 |
8 | {value || placeholder || label}
9 |
10 |
11 |
12 |
13 |
14 | );
15 | export default Select;
16 |
--------------------------------------------------------------------------------
/examples/sketch/src/components/TextInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Line, TextInput } from 'elemental-react';
3 |
4 | const InputField = ({ label, placeholder, error, value = '', children, ...props }) => (
5 | <>
6 |
7 |
8 | >
9 | );
10 | export default InputField;
11 |
--------------------------------------------------------------------------------
/examples/sketch/src/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-filename-extension */
2 | /* globals context */
3 | import React, { Component } from 'react';
4 | import * as PropTypes from 'prop-types';
5 | import {
6 | width as styledWidth, position, space, height as styledHeight,
7 | } from 'styled-system';
8 | import {
9 | render, Document, Page, Artboard, RedBox,
10 | } from 'react-sketchapp';
11 | import chroma from 'chroma-js';
12 |
13 | import {
14 | styled, ThemeProvider, Box, Text,
15 | } from 'elemental-react';
16 |
17 | import theme from './styles/theme';
18 |
19 | import HomeScreen from './screens/Home';
20 | import SignupScreen from './screens/Signup';
21 |
22 | const StyledText = styled(Text)`
23 | color: blue;
24 | `;
25 |
26 | const Screen = styled(Artboard)`
27 | ${styledWidth}
28 | ${position}
29 | ${space}
30 | ${styledHeight}
31 | `;
32 |
33 | Screen.defaultProps = {
34 | width: 360,
35 | position: 'relative',
36 | ml: 0,
37 | };
38 |
39 | class ErrorBoundary extends Component {
40 | constructor(props) {
41 | super(props);
42 | this.state = { hasError: false };
43 | }
44 |
45 | // static getDerivedStateFromError(error) {
46 | // // Update state so the next render will show the fallback UI.
47 | // return { hasError: true, error };
48 | // }
49 |
50 | componentDidCatch(error, info) {
51 | this.setState({
52 | error,
53 | hasError: true,
54 | });
55 | // You can also log the error to an error reporting service
56 | console.log(`
57 | ${JSON.stringify(error, null, 2)}\n
58 | ${JSON.stringify(info, null, 2)}
59 | `);
60 | }
61 |
62 | render() {
63 | // eslint-disable-next-line react/prop-types
64 | const { children } = this.props;
65 | const { error, hasError } = this.state;
66 |
67 | if (hasError) {
68 | // You can render any custom fallback UI
69 | return ;
70 | }
71 |
72 | return children;
73 | }
74 | }
75 |
76 | // take a hex and give us a nice text color to put over it
77 | const textColor = (hex) => {
78 | const vsWhite = chroma.contrast(hex, 'white');
79 | if (vsWhite > 4) {
80 | return '#FFF';
81 | }
82 | return chroma(hex)
83 | .darken(3)
84 | .hex();
85 | };
86 |
87 | const SwatchTile = styled.View`
88 | height: 250px;
89 | width: 250px;
90 | border-radius: 125px;
91 | margin: 4px;
92 | background-color: ${props => props.hex};
93 | justify-content: center;
94 | align-items: center;
95 | `;
96 |
97 | const SwatchName = styled.Text`
98 | color: ${props => textColor(props.hex)};
99 | font-size: 32px;
100 | font-weight: bold;
101 | `;
102 |
103 | const Ampersand = styled.Text`
104 | color: ${props => textColor(props.hex)};
105 | font-size: 120px;
106 | font-family: Himalaya;
107 | line-height: 144px;
108 | `;
109 |
110 | const Swatch = ({ name, hex }) => (
111 |
112 |
113 | &
114 |
115 |
116 |
117 | {name}
118 |
119 |
120 | {hex}
121 |
122 |
123 |
124 | );
125 |
126 | const Color = {
127 | hex: PropTypes.string.isRequired,
128 | name: PropTypes.string.isRequired,
129 | };
130 |
131 | Swatch.propTypes = Color;
132 |
133 | const screens = [{
134 | name: 'Android', width: 360, height: 640,
135 | }, {
136 | name: 'Tablet', width: 1024, height: 768,
137 | }, {
138 | name: 'Desktop', width: 1280, height: 720,
139 | }];
140 |
141 | const routes = [{
142 | name: 'Home',
143 | component: HomeScreen,
144 | }, {
145 | name: 'Signup',
146 | component: SignupScreen,
147 | }];
148 |
149 | const DocumentContainer = ({ colors }) => (
150 |
151 |
152 |
153 | <>
154 |
155 | {routes.map(({ name: routeName, component: Comp }) => (
156 |
157 | {screens.map(({ name, height, width }) => (
158 |
159 |
160 |
161 | ))}
162 |
163 | ))}
164 |
165 |
166 |
167 |
168 | Colours
169 | {Object.keys(colors).map(color => (
170 | typeof colors[color] === 'string' ? (
171 |
172 | ) : (
173 |
174 | {colors[color].map((shade, i) => (
175 |
176 | ))}
177 |
178 | )
179 | ))}
180 |
181 |
182 |
183 | >
184 |
185 |
186 |
187 | );
188 |
189 | Document.propTypes = {
190 | colors: PropTypes.objectOf(PropTypes.string).isRequired,
191 | };
192 |
193 | export default () => {
194 | const data = context.document.documentData();
195 | const pages = context.document.pages();
196 | render();
197 | data.setCurrentPage(pages.firstObject());
198 | };
199 |
--------------------------------------------------------------------------------
/examples/sketch/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "compatibleVersion": 3,
3 | "bundleVersion": 1,
4 | "commands": [
5 | {
6 | "name": "static-blog: Blog",
7 | "identifier": "main",
8 | "script": "./index.js"
9 | }
10 | ],
11 | "menu": {
12 | "isRoot": true,
13 | "items": [
14 | "main"
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/sketch/src/screens/Home.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Box, Text, Button, Headline } from 'elemental-react';
3 |
4 | import AppBar from '../components/AppBar';
5 | import InputField from '../components/InputField';
6 | import TextInput from '../components/TextInput';
7 |
8 | const Home = () => (
9 | <>
10 |
11 |
12 |
13 |
14 | SIGN IN
15 |
16 |
17 |
18 |
19 | The Blog
20 |
21 |
22 | We solve problems
23 |
24 |
25 |
26 |
27 | {({ label, value }) => }
28 |
29 |
34 |
35 |
36 |
37 |
38 | RECENT POSTS
39 |
40 | {[{
41 | title: 'An important announcement',
42 | category: 'News',
43 | description: 'Some important information about the incorporation of this blog.',
44 | }, {
45 | title: 'Looking back',
46 | category: 'History',
47 | description: 'While checking our archives, we found some interesting data.',
48 | }, {
49 | title: 'You won\'t believe what we found',
50 | category: 'Trends',
51 | description: 'Clickbait at its finest. Please don\'t click through as this is a dummy site and it won\'t work',
52 | }].map(({ title, category, description }) => (
53 |
54 | {category}
55 | {title}
56 | {description}
57 |
58 | Written by John Smith
59 |
60 |
61 | ))}
62 |
63 | >
64 | );
65 |
66 | export default Home;
67 |
--------------------------------------------------------------------------------
/examples/sketch/src/screens/Signup.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { Rectangle, Box, Circle, Line, Text, Image, Button, Headline } from 'elemental-react';
3 |
4 | import AppBar from '../components/AppBar';
5 | import InputField from '../components/InputField';
6 | import TextInput from '../components/TextInput';
7 | import Select from '../components/Select';
8 |
9 |
10 | const Home = () => (
11 |
12 |
13 |
14 |
15 |
16 | The Blog
17 |
18 |
19 |
20 |
21 |
22 |
23 | {({ label, value }) => }
24 |
25 |
26 | {({ label, value }) => }
27 |
28 |
29 | {({ label, value }) => (
30 |
31 |
32 |
33 |
34 |
35 | )}
36 |
37 |
38 |
39 |
44 |
45 |
46 |
47 | );
48 |
49 | export default Home;
50 |
--------------------------------------------------------------------------------
/examples/sketch/src/screens/Visual.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { Rectangle, Box, Circle, Line, Text, Image, Button, Headline } from 'elemental-react';
3 |
4 | import AppBar, { MenuIcon, ActionButton } from '../components/AppBar';
5 | import InputField from '../components/InputField';
6 | import TextInput from '../components/TextInput';
7 | import Select from '../components/Select';
8 |
9 | import BarChart from '../components/BarChart';
10 |
11 |
12 | const Home = () => (
13 |
14 |
15 |
16 |
17 |
18 | The Blog
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 |
30 | export default Home;
31 |
--------------------------------------------------------------------------------
/examples/sketch/src/styles/theme.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-destructuring */
2 |
3 | const breakpoints = [
4 | '601px',
5 | '769px',
6 | '1025px',
7 | ];
8 |
9 | // aliases
10 | breakpoints.mobile = breakpoints[0];
11 | breakpoints.tablet = breakpoints[1];
12 | breakpoints.desktop = breakpoints[2];
13 | breakpoints.m = breakpoints[0];
14 | breakpoints.t = breakpoints[1];
15 | breakpoints.d = breakpoints[2];
16 |
17 | const colors = {
18 | black: '#000',
19 | greys: [
20 | '#FAFAFA',
21 | '#F5F5F5',
22 | '#EEEEEE',
23 | '#E0E0E0',
24 | '#BDBDBD',
25 | '#9E9E9E',
26 | '#757575',
27 | '#616161',
28 | '#424242',
29 | '#212121',
30 | ],
31 | white: '#fff',
32 | blue: '#2196F3',
33 | blues: [
34 | '#E3F2FD',
35 | '#BBDEFB',
36 | '#90CAF9',
37 | '#64B5F6',
38 | '#42A5F5',
39 | '#2196F3',
40 | '#1E88E5',
41 | '#1976D2',
42 | '#1565C0',
43 | '#0D47A1',
44 | ],
45 | green: '#43A047',
46 | greens: [
47 | '#E8F5E9',
48 | '#C8E6C9',
49 | '#A5D6A7',
50 | '#81C784',
51 | '#66BB6A',
52 | '#4CAF50',
53 | '#43A047',
54 | '#388E3C',
55 | '#2E7D32',
56 | '#1B5E20',
57 | ],
58 | navy: '#004175',
59 | red: '#D32F2F',
60 | reds: [
61 | '#FFEBEE',
62 | '#FFCDD2',
63 | '#EF9A9A',
64 | '#E57373',
65 | '#EF5350',
66 | '#F44336',
67 | '#E53935',
68 | '#D32F2F',
69 | '#C62828',
70 | '#B71C1C',
71 | ],
72 | };
73 |
74 | const space = [
75 | 0, 4, 8, 16, 32, 64, 128, 256, 512,
76 | ];
77 |
78 | const fontSizes = [96, 60, 48, 34, 24, 20, 16, 14];
79 |
80 | fontSizes.h1 = fontSizes[0];
81 | fontSizes.h2 = fontSizes[1];
82 | fontSizes.h3 = fontSizes[2];
83 | fontSizes.h4 = fontSizes[3];
84 | fontSizes.h5 = fontSizes[4];
85 | fontSizes.h6 = fontSizes[5];
86 |
87 | const textStyles = {
88 | h1: {
89 | fontFamily: 'Roboto',
90 | marginBottom: 16,
91 | },
92 | h2: {
93 | fontFamily: 'Roboto',
94 | marginBottom: 16,
95 | },
96 | h3: {
97 | fontFamily: 'Roboto',
98 | marginBottom: 16,
99 | },
100 | h4: {
101 | fontFamily: 'Roboto',
102 | marginBottom: 16,
103 | },
104 | h5: {
105 | fontFamily: 'Roboto',
106 | marginBottom: 16,
107 | },
108 | h6: {
109 | fontFamily: 'Roboto',
110 | marginBottom: 16,
111 | },
112 | button: {
113 | fontFamily: 'Roboto',
114 | },
115 | };
116 |
117 | const fonts = {
118 | primary: 'Roboto',
119 | secondary: 'Open Sans',
120 | };
121 |
122 | const lineHeights = {
123 | ...fontSizes,
124 | };
125 |
126 | export default {
127 | fonts,
128 | space,
129 | colors,
130 | fontSizes,
131 | textStyles,
132 | breakpoints,
133 | lineHeights,
134 | };
135 |
--------------------------------------------------------------------------------
/examples/sketch/static-blog.sketchplugin/Contents/Sketch/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "compatibleVersion": 3,
3 | "bundleVersion": 1,
4 | "commands": [
5 | {
6 | "name": "static-blog: Blog",
7 | "identifier": "main",
8 | "script": "index.js"
9 | }
10 | ],
11 | "menu": {
12 | "isRoot": true,
13 | "items": [
14 | "main"
15 | ]
16 | },
17 | "version": "0.0.1",
18 | "description": "",
19 | "name": "static-blog",
20 | "identifier": "static-blog",
21 | "disableCocoaScriptPreprocessor": true,
22 | "appcast": "https://raw.githubusercontent.com//master/.appcast.xml",
23 | "author": "Macintosh Helper",
24 | "authorEmail": "noreply@macintoshhelper.com"
25 | }
--------------------------------------------------------------------------------
/examples/sketch/webpack.skpm.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | const path = require('path');
3 |
4 | // eslint-disable-next-line spaced-comment
5 | /**
6 | * Function that mutates original webpack config.
7 | * Supports asynchronous changes when promise is returned.
8 | *
9 | * @param {object} config - original webpack config.
10 | * @param {boolean} isPluginCommand - wether the config is for a plugin command or a resource
11 | **/
12 | module.exports = (config) => {
13 | /* you can change config here */
14 |
15 | config.resolve = {
16 | ...config.resolve,
17 | extensions: [...config.resolve.extensions, '.jsx'],
18 | alias: {
19 | ...config.resolve.alias,
20 | 'react': path.resolve(__dirname, './node_modules/react/'),
21 | 'elemental-react': path.resolve(__dirname, '../../'),
22 | 'react-sketchapp': path.resolve(__dirname, './node_modules/react-sketchapp/'),
23 | 'styled-components': path.resolve(__dirname, './node_modules/styled-components'),
24 | 'react-primitives-svg': path.resolve(__dirname, './node_modules/react-primitives-svg'),
25 | },
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | export * from './lib/main.web.esm';
2 |
--------------------------------------------------------------------------------
/index.native.js:
--------------------------------------------------------------------------------
1 | export * from './lib/main.native';
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "elemental-react",
3 | "version": "0.4.0-beta.0",
4 | "description": "Cross-platform React design elements to let you build your own design language.",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/elemental-design/elemental-react.git"
8 | },
9 | "figma": "lib/module/index.js",
10 | "react-native": "lib/module/index.js",
11 | "main": "lib/index.js",
12 | "sketch": "lib/module/index.js",
13 | "module": "lib/module/index.js",
14 | "types": "lib/index.d.ts",
15 | "scripts": {
16 | "build:js": "npm run build:main && npm run build:module && npm run build:types",
17 | "build:main": "tsc -p tsconfig.json",
18 | "build:module": "tsc -p tsconfig.module.json",
19 | "build:types": "tsc -d -p tsconfig.json",
20 | "build": "npm run clean && npm run build:js && npm run build:types",
21 | "clean": "rm -rf lib/",
22 | "dev": "rollup -c -w",
23 | "lint": "eslint",
24 | "test": "echo \"Error: no test specified\" && exit 1"
25 | },
26 | "keywords": [],
27 | "author": "macintoshhelper",
28 | "license": "ISC",
29 | "dependencies": {
30 | "@styled-system/theme-get": "^5.0.18",
31 | "deepmerge": "^4.2.2",
32 | "lodash.get": "^4.4.2",
33 | "lodash.merge": "^4.6.2"
34 | },
35 | "devDependencies": {
36 | "@babel/cli": "^7.8.4",
37 | "@babel/core": "^7.3.4",
38 | "@babel/plugin-proposal-class-properties": "^7.3.4",
39 | "@babel/plugin-proposal-optional-chaining": "^7.16.7",
40 | "@babel/preset-env": "^7.3.4",
41 | "@babel/preset-react": "^7.0.0",
42 | "@babel/preset-typescript": "^7.8.3",
43 | "@react-platform/native": "0.0.3-beta.3",
44 | "@rollup/plugin-commonjs": "^11.0.2",
45 | "@rollup/plugin-json": "^4.0.2",
46 | "@rollup/plugin-node-resolve": "^7.1.1",
47 | "@types/react": "^16.14.23",
48 | "@types/react-dom": "^16.9.14",
49 | "@types/styled-components": "^5.0.1",
50 | "@types/styled-system": "^5.1.9",
51 | "@types/styled-system__theme-get": "^5.0.0",
52 | "babel-eslint": "^10.0.1",
53 | "babel-plugin-styled-components": "^1.10.0",
54 | "eslint": "^5.16.0",
55 | "eslint-config-airbnb": "^17.1.0",
56 | "eslint-plugin-import": "^2.16.0",
57 | "eslint-plugin-jsx-a11y": "^6.2.1",
58 | "eslint-plugin-react": "^7.12.4",
59 | "rollup": "^2.0.2",
60 | "rollup-plugin-alias": "^1.5.2",
61 | "rollup-plugin-babel": "^4.4.0",
62 | "styled-components": "4.2.0",
63 | "typescript": "^4.6.2"
64 | },
65 | "peerDependencies": {
66 | "@react-platform/native": ">= 0.0.0"
67 | },
68 | "optionalDependencies": {
69 | "react": ">= 16.8.0",
70 | "react-dom": ">= 16.8.0",
71 | "react-primitives-svg": ">= 0.0.2",
72 | "styled-components": ">= 4.0.0",
73 | "styled-system": ">= 5.0.0"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import json from '@rollup/plugin-json';
2 | import resolve from '@rollup/plugin-node-resolve';
3 | import babel from 'rollup-plugin-babel';
4 | import commonjs from '@rollup/plugin-commonjs';
5 | // import alias from 'rollup-plugin-alias';
6 |
7 | import packageJson from './package.json';
8 |
9 | const getPlugins = (platform) => [
10 | json(),
11 | babel({
12 | extensions: ['.ts', '.tsx', '.js', '.jsx', '.es6', '.es', '.mjs'],
13 | exclude: 'node_modules/**',
14 | }),
15 | resolve({
16 | extensions: ['.mjs', `.${platform}.js`, `.${platform}.jsx`, `.${platform}.ts`, `.${platform}.tsx`, '.js', '.ts', '.jsx', '.tsx', '.json'],
17 | }),
18 | commonjs(),
19 | ];
20 |
21 | const output = {
22 | // file: 'dist/bundle.esm.js',
23 | file: packageJson.module,
24 | format: 'esm',
25 | name: packageJson.name,
26 | globals: {
27 | react: 'React',
28 | 'react-dom': 'ReactDOM',
29 | 'styled-components': 'styled',
30 | 'styled-components/primitives': 'styledP',
31 | 'react-primitives': 'reactPrimitives',
32 | 'styled-system': 'styledSystem',
33 | 'react-primitives-svg': 'reactPrimitivesSvg',
34 | },
35 | // plugins: getPlugins('web'),
36 | };
37 |
38 | export default [
39 | // Web export
40 | {
41 | input: 'src/index.ts',
42 | output: [
43 | {
44 | ...output,
45 | file: packageJson.main,
46 | format: 'umd',
47 | },
48 | output
49 | ],
50 | external: ['react', 'react-native-web', 'react-dom', 'react-is', 'styled-components', 'styled-system', 'react-primitives', 'react-primitives-svg'],
51 | plugins: getPlugins('web')
52 | },
53 | // Sketch export
54 | {
55 | input: 'src/index.ts',
56 | output: [
57 | {
58 | ...output,
59 | file: packageJson.sketch,
60 | // plugins: getPlugins('sketch'),
61 | }
62 | ],
63 | external: ['react', 'react-native-web', 'react-dom', 'react-is', 'styled-components', 'styled-components/primitives', 'styled-system', 'react-primitives', 'react-primitives-svg'],
64 | plugins: getPlugins('sketch')
65 | },
66 | // React Native export
67 | {
68 | input: 'src/index.ts',
69 | output: [
70 | {
71 | ...output,
72 | file: packageJson['react-native'],
73 | // plugins: getPlugins('sketch'),
74 | }
75 | ],
76 | external: ['react', 'react-native-web', 'react-native', 'react-dom', 'react-is', 'styled-components', 'styled-components/primitives', 'styled-system', 'react-primitives', 'react-primitives-svg'],
77 | plugins: getPlugins('native')
78 | },
79 | // Figma export
80 | {
81 | input: 'src/index.ts',
82 | output: [
83 | {
84 | ...output,
85 | format: 'umd', // Figma doesn't support ES imports
86 | file: packageJson.figma,
87 | // plugins: getPlugins('sketch'),
88 | }
89 | ],
90 | external: ['react', 'react-native-web', 'react-dom', 'react-is', 'styled-components', 'styled-components/primitives', 'styled-system', 'react-primitives', 'react-primitives-svg'],
91 | plugins: getPlugins('figma')
92 | },
93 | ]
94 |
--------------------------------------------------------------------------------
/sketch.js:
--------------------------------------------------------------------------------
1 | export { default } from './lib/sketchBundle.js';
2 |
--------------------------------------------------------------------------------
/src/@types/react-primitives-svg/index.d.ts:
--------------------------------------------------------------------------------
1 | // Credit: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-sketchapp
2 |
3 | type NumberProp = string | number;
4 | type NumberArrayProp = [NumberProp] | NumberProp;
5 |
6 | interface FillProps {
7 | fill?: string;
8 | fillOpacity?: NumberProp;
9 | fillRule?: 'evenodd' | 'nonzero';
10 | }
11 |
12 | interface ClipProps {
13 | clipRule?: 'evenodd' | 'nonzero';
14 | clipPath?: string;
15 | }
16 |
17 | interface DefinationProps {
18 | name?: string;
19 | }
20 |
21 | interface StrokeProps {
22 | stroke?: string;
23 | strokeWidth?: NumberProp;
24 | strokeOpacity?: NumberProp;
25 | strokeDasharray?: NumberArrayProp;
26 | strokeDashoffset?: NumberProp;
27 | strokeLinecap?: 'butt' | 'square' | 'round';
28 | strokeLinejoin?: 'miter' | 'bevel' | 'round';
29 | strokeMiterlimit?: NumberProp;
30 | }
31 |
32 | interface TransformProps {
33 | scale?: NumberProp;
34 | scaleX?: NumberProp;
35 | scaleY?: NumberProp;
36 | rotate?: NumberProp;
37 | rotation?: NumberProp;
38 | translate?: NumberProp;
39 | translateX?: NumberProp;
40 | translateY?: NumberProp;
41 | x?: NumberProp;
42 | y?: NumberProp;
43 | origin?: NumberProp;
44 | originX?: NumberProp;
45 | originY?: NumberProp;
46 | skew?: NumberProp;
47 | skewX?: NumberProp;
48 | skewY?: NumberProp;
49 | transform?: object | string;
50 | }
51 |
52 | interface PathProps
53 | extends FillProps,
54 | StrokeProps,
55 | ClipProps,
56 | TransformProps,
57 | DefinationProps { }
58 |
59 | // normal | italic | oblique | inherit
60 | // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-style
61 | type FontStyle = 'normal' | 'italic' | 'oblique';
62 |
63 | // normal | small-caps | inherit
64 | // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-variant
65 | type FontVariant = 'normal' | 'small-caps';
66 |
67 | // normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
68 | // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-weight
69 | type FontWeight =
70 | | 'normal'
71 | | 'bold'
72 | | 'bolder'
73 | | 'lighter'
74 | | '100'
75 | | '200'
76 | | '300'
77 | | '400'
78 | | '500'
79 | | '600'
80 | | '700'
81 | | '800'
82 | | '900';
83 |
84 | // normal | wider | narrower | ultra-condensed | extra-condensed |
85 | // condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit
86 | // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-stretch
87 | type FontStretch =
88 | | 'normal'
89 | | 'wider'
90 | | 'narrower'
91 | | 'ultra-condensed'
92 | | 'extra-condensed'
93 | | 'condensed'
94 | | 'semi-condensed'
95 | | 'semi-expanded'
96 | | 'expanded'
97 | | 'extra-expanded'
98 | | 'ultra-expanded';
99 |
100 | // | | | | inherit
101 | // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size
102 | type fontSize = NumberProp;
103 |
104 | // [[ | ],]* [ | ] | inherit
105 | // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-family
106 | type FontFamily = string;
107 |
108 | /*
109 | font syntax [ [ <'font-style'> || ||
110 | <'font-weight'> || <'font-stretch'> ]? <'font-size'> [ / <'line-height'> ]? <'font-family'> ] |
111 | caption | icon | menu | message-box | small-caption | status-bar
112 | where = [ normal | small-caps ]
113 | Shorthand property for setting ‘font-style’, ‘font-variant’,
114 | ‘font-weight’, ‘font-size’, ‘line-height’ and ‘font-family’.
115 | The ‘line-height’ property has no effect on text layout in SVG.
116 | Note: for the purposes of processing the ‘font’ property in SVG,
117 | 'line-height' is assumed to be equal the value for property ‘font-size’
118 | https://www.w3.org/TR/SVG11/text.html#FontProperty
119 | https://developer.mozilla.org/en-US/docs/Web/CSS/font
120 | https://drafts.csswg.org/css-fonts-3/#font-prop
121 | https://www.w3.org/TR/CSS2/fonts.html#font-shorthand
122 | https://www.w3.org/TR/CSS1/#font
123 | */
124 | type Font = object;
125 |
126 | // start | middle | end | inherit
127 | // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor
128 | type TextAnchor = 'start' | 'middle' | 'end';
129 |
130 | // none | underline | overline | line-through | blink | inherit
131 | // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-decoration
132 | type TextDecoration =
133 | | 'none'
134 | | 'underline'
135 | | 'overline'
136 | | 'line-through'
137 | | 'blink';
138 |
139 | // normal | | inherit
140 | // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/letter-spacing
141 | type LetterSpacing = NumberProp;
142 |
143 | // normal | | inherit
144 | // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/word-spacing
145 | type WordSpacing = NumberProp;
146 |
147 | // auto | | inherit
148 | // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/kerning
149 | type Kerning = NumberProp;
150 |
151 | /*
152 | Name: font-variant-ligatures
153 | Value: normal | none | [ || ||
154 | || ]
155 | Initial: normal
156 | Applies to: all elements
157 | Inherited: yes
158 | Percentages: N/A
159 | Media: visual
160 | Computed value: as specified
161 | Animatable: no
162 | Ligatures and contextual forms are ways of combining glyphs to produce more harmonized forms.
163 | = [ common-ligatures | no-common-ligatures ]
164 | = [ discretionary-ligatures | no-discretionary-ligatures ]
165 | = [ historical-ligatures | no-historical-ligatures ]
166 | = [ contextual | no-contextual ]
167 | https://developer.mozilla.org/en/docs/Web/CSS/font-variant-ligatures
168 | https://www.w3.org/TR/css-fonts-3/#font-variant-ligatures-prop
169 | */
170 | type FontVariantLigatures = 'normal' | 'none';
171 |
172 | interface FontProps {
173 | fontStyle?: FontStyle;
174 | fontVariant?: FontVariant;
175 | fontWeight?: FontWeight;
176 | fontStretch?: FontStretch;
177 | fontSize?: fontSize;
178 | fontFamily?: FontFamily;
179 | textAnchor?: TextAnchor;
180 | textDecoration?: TextDecoration;
181 | letterSpacing?: LetterSpacing;
182 | wordSpacing?: WordSpacing;
183 | kerning?: Kerning;
184 | fontVariantLigatures?: FontVariantLigatures;
185 | font?: Font;
186 | }
187 |
188 | /*
189 | Name Value Initial value Animatable
190 | lengthAdjust spacing | spacingAndGlyphs spacing yes
191 | https://svgwg.org/svg2-draft/text.html#TextElementLengthAdjustAttribute
192 | https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/lengthAdjust
193 | */
194 | type LengthAdjust = 'spacing' | 'spacingAndGlyphs';
195 |
196 | /*
197 | Name Value Initial value Animatable
198 | textLength | | See below yes
199 | https://svgwg.org/svg2-draft/text.html#TextElementTextLengthAttribute
200 | https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/textLength
201 | */
202 | type TextLength = NumberProp;
203 |
204 | /*
205 | 2.2. Transverse Box Alignment: the vertical-align property
206 | Name: vertical-align
207 | Value: <‘baseline-shift’> || <‘alignment-baseline’>
208 | Initial: baseline
209 | Applies to: inline-level boxes
210 | Inherited: no
211 | Percentages: N/A
212 | Media: visual
213 | Computed value: as specified
214 | Canonical order: per grammar
215 | Animation type: discrete
216 | This shorthand property specifies how an inline-level box is aligned within the line.
217 | Values are the same as for its longhand properties, see below.
218 | Authors should use this property (vertical-align) instead of its longhands.
219 | https://www.w3.org/TR/css-inline-3/#transverse-alignment
220 | https://drafts.csswg.org/css-inline/#propdef-vertical-align
221 | */
222 | type VerticalAlign = NumberProp;
223 |
224 | /*
225 | Name: alignment-baseline
226 | 1.1 Value: auto | baseline | before-edge | text-before-edge | middle | central |
227 | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | inherit
228 | 2.0 Value: baseline | text-bottom | alphabetic | ideographic | middle | central |
229 | mathematical | text-top | bottom | center | top
230 | Initial: baseline
231 | Applies to: inline-level boxes, flex items, grid items, table cells
232 | Inherited: no
233 | Percentages: N/A
234 | Media: visual
235 | Computed value: as specified
236 | Canonical order: per grammar
237 | Animation type: discrete
238 | https://drafts.csswg.org/css-inline/#propdef-alignment-baseline
239 | https://www.w3.org/TR/SVG11/text.html#AlignmentBaselineProperty
240 | https://svgwg.org/svg2-draft/text.html#AlignmentBaselineProperty
241 | https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline
242 | */
243 | type AlignmentBaseline =
244 | | 'baseline'
245 | | 'text-bottom'
246 | | 'alphabetic'
247 | | 'ideographic'
248 | | 'middle'
249 | | 'central'
250 | | 'mathematical'
251 | | 'text-top'
252 | | 'bottom'
253 | | 'center'
254 | | 'top'
255 | | 'text-before-edge'
256 | | 'text-after-edge'
257 | | 'before-edge'
258 | | 'after-edge'
259 | | 'hanging';
260 |
261 | /*
262 | 2.2.2. Alignment Shift: baseline-shift longhand
263 | Name: baseline-shift
264 | Value: | | sub | super
265 | Initial: 0
266 | Applies to: inline-level boxes
267 | Inherited: no
268 | Percentages: refer to the used value of line-height
269 | Media: visual
270 | Computed value: absolute length, percentage, or keyword specified
271 | Animation type: discrete
272 | This property specifies by how much the box is shifted up from its alignment point.
273 | It does not apply when alignment-baseline is top or bottom.
274 | https://www.w3.org/TR/css-inline-3/#propdef-baseline-shift
275 | */
276 | type BaselineShift =
277 | | 'sub'
278 | | 'super'
279 | | 'baseline'
280 | | [NumberProp]
281 | | NumberProp;
282 |
283 | /*
284 | 6.12. Low-level font feature settings control: the font-feature-settings property
285 | Name: font-feature-settings
286 | Value: normal | #
287 | Initial: normal
288 | Applies to: all elements
289 | Inherited: yes
290 | Percentages: N/A
291 | Media: visual
292 | Computed value: as specified
293 | Animatable: no
294 | This property provides low-level control over OpenType font features.
295 | It is intended as a way of providing access to font features
296 | that are not widely used but are needed for a particular use case.
297 | Authors should generally use ‘font-variant’ and its related subproperties
298 | whenever possible and only use this property for special cases where its use
299 | is the only way of accessing a particular infrequently used font feature.
300 | enable small caps and use second swash alternate
301 | font-feature-settings: "smcp", "swsh" 2;
302 | A value of ‘normal’ means that no change in glyph selection or positioning
303 | occurs due to this property.
304 | Feature tag values have the following syntax:
305 | = [ | on | off ]?
306 | The is a case-sensitive OpenType feature tag. As specified in the
307 | OpenType specification, feature tags contain four ASCII characters.
308 | Tag strings longer or shorter than four characters,
309 | or containing characters outside the U+20–7E codepoint range are invalid.
310 | Feature tags need only match a feature tag defined in the font,
311 | so they are not limited to explicitly registered OpenType features.
312 | Fonts defining custom feature tags should follow the tag name rules
313 | defined in the OpenType specification [OPENTYPE-FEATURES].
314 | Feature tags not present in the font are ignored;
315 | a user agent must not attempt to synthesize fallback behavior based on these feature tags.
316 | The one exception is that user agents may synthetically support the kern feature with fonts
317 | that contain kerning data in the form of a ‘kern’ table but lack kern feature
318 | support in the ‘GPOS’ table.
319 | In general, authors should use the ‘font-kerning’ property to explicitly
320 | enable or disable kerning
321 | since this property always affects fonts with either type of kerning data.
322 | If present, a value indicates an index used for glyph selection.
323 | An value must be 0 or greater.
324 | A value of 0 indicates that the feature is disabled.
325 | For boolean features, a value of 1 enables the feature.
326 | For non-boolean features, a value of 1 or greater enables the
327 | feature and indicates the feature selection index.
328 | A value of ‘on’ is synonymous with 1 and ‘off’ is synonymous with 0.
329 | If the value is omitted, a value of 1 is assumed.
330 | font-feature-settings: "dlig" 1; /* dlig=1 enable discretionary ligatures * /
331 | font-feature-settings: "smcp" on; /* smcp=1 enable small caps * /
332 | font-feature-settings: 'c2sc'; /* c2sc=1 enable caps to small caps * /
333 | font-feature-settings: "liga" off; /* liga=0 no common ligatures * /
334 | font-feature-settings: "tnum", 'hist'; /* tnum=1, hist=1 enable tabular numbers
335 | and historical forms * /
336 | font-feature-settings: "tnum" "hist"; /* invalid, need a comma-delimited list * /
337 | font-feature-settings: "silly" off; /* invalid, tag too long * /
338 | font-feature-settings: "PKRN"; /* PKRN=1 enable custom feature * /
339 | font-feature-settings: dlig; /* invalid, tag must be a string * /
340 | When values greater than the range supported by the font are specified,
341 | the behavior is explicitly undefined.
342 | For boolean features, in general these will enable the feature.
343 | For non-boolean features, out of range values will in general be equivalent to a 0 value.
344 | However, in both cases the exact behavior will depend upon the way the font is designed
345 | (specifically, which type of lookup is used to define the feature).
346 | Although specifically defined for OpenType feature tags,
347 | feature tags for other modern font formats that support font features
348 | may be added in the future.
349 | Where possible, features defined for other font formats
350 | should attempt to follow the pattern of registered OpenType tags.
351 | The Japanese text below will be rendered with half-width kana characters:
352 | body { font-feature-settings: "hwid"; /* Half-width OpenType feature * / }
353 | 毎日カレー食べてるのに、飽きない
354 | https://drafts.csswg.org/css-fonts-3/#propdef-font-feature-settings
355 | https://developer.mozilla.org/en/docs/Web/CSS/font-feature-settings
356 | */
357 | type FontFeatureSettings = string;
358 |
359 | interface TextSpecificProps extends PathProps, FontProps {
360 | alignmentBaseline?: AlignmentBaseline;
361 | baselineShift?: BaselineShift;
362 | verticalAlign?: VerticalAlign;
363 | lengthAdjust?: LengthAdjust;
364 | textLength?: TextLength;
365 | fontData?: object;
366 | fontFeatureSettings?: FontFeatureSettings;
367 | }
368 |
369 | // https://svgwg.org/svg2-draft/text.html#TSpanAttributes
370 | interface TextProps extends TextSpecificProps {
371 | dx?: NumberArrayProp;
372 | dy?: NumberArrayProp;
373 | }
374 |
375 | /*
376 | Name
377 | side
378 | Value
379 | left | right
380 | initial value
381 | left
382 | Animatable
383 | yes
384 | https://svgwg.org/svg2-draft/text.html#TextPathElementSideAttribute
385 | */
386 | type Side = 'left' | 'right';
387 |
388 | /*
389 | Name
390 | startOffset
391 | Value
392 | | |
393 | initial value
394 | 0
395 | Animatable
396 | yes
397 | https://svgwg.org/svg2-draft/text.html#TextPathElementStartOffsetAttribute
398 | https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath
399 | */
400 | type StartOffset = NumberProp;
401 |
402 | /*
403 | Name
404 | method
405 | Value
406 | align | stretch
407 | initial value
408 | align
409 | Animatable
410 | yes
411 | https://svgwg.org/svg2-draft/text.html#TextPathElementMethodAttribute
412 | https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath
413 | */
414 | type Method = 'align' | 'stretch';
415 |
416 | /*
417 | Name
418 | spacing
419 | Value
420 | auto | exact
421 | initial value
422 | exact
423 | Animatable
424 | yes
425 | https://svgwg.org/svg2-draft/text.html#TextPathElementSpacingAttribute
426 | https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath
427 | */
428 | type Spacing = 'auto' | 'exact';
429 |
430 | /*
431 | Name
432 | mid-line
433 | Value
434 | sharp | smooth
435 | initial value
436 | smooth
437 | Animatable
438 | yes
439 | */
440 | type MidLine = 'sharp' | 'smooth';
441 |
442 | // https://svgwg.org/svg2-draft/text.html#TextPathAttributes
443 | // https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath
444 | interface TextPathProps extends TextSpecificProps {
445 | href: string;
446 | startOffset?: StartOffset;
447 | method?: Method;
448 | spacing?: Spacing;
449 | side?: Side;
450 | midLine?: MidLine;
451 | }
452 | type Color = string | number;
453 |
454 | interface Style {
455 | shadowColor?: Color;
456 | shadowOffset?: { width?: number; height?: number };
457 | shadowOpacity?: number;
458 | shadowRadius?: number;
459 | width?: number;
460 | height?: number;
461 | top?: number;
462 | left?: number;
463 | right?: number;
464 | bottom?: number;
465 | minWidth?: number;
466 | maxWidth?: number;
467 | minHeight?: number;
468 | maxHeight?: number;
469 | margin?: number;
470 | marginVertical?: number;
471 | marginHorizontal?: number;
472 | marginTop?: number;
473 | marginBottom?: number;
474 | marginLeft?: number;
475 | marginRight?: number;
476 | padding?: number;
477 | paddingVertical?: number;
478 | paddingHorizontal?: number;
479 | paddingTop?: number;
480 | paddingBottom?: number;
481 | paddingLeft?: number;
482 | paddingRight?: number;
483 | borderWidth?: number;
484 | borderTopWidth?: number;
485 | borderRightWidth?: number;
486 | borderBottomWidth?: number;
487 | borderLeftWidth?: number;
488 | position?: 'absolute' | 'relative';
489 | flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse';
490 | flexWrap?: 'wrap' | 'nowrap';
491 | justifyContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around';
492 | alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch';
493 | alignSelf?: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch';
494 | overflow?: 'visible' | 'hidden' | 'scroll';
495 | flex?: number;
496 | flexGrow?: number;
497 | flexShrink?: number;
498 | flexBasis?: number;
499 | aspectRatio?: number;
500 | zIndex?: number;
501 | backfaceVisibility?: 'visible' | 'hidden';
502 | backgroundColor?: Color;
503 | borderColor?: Color;
504 | borderTopColor?: Color;
505 | borderRightColor?: Color;
506 | borderBottomColor?: Color;
507 | borderLeftColor?: Color;
508 | borderRadius?: number;
509 | borderTopLeftRadius?: number;
510 | borderTopRightRadius?: number;
511 | borderBottomLeftRadius?: number;
512 | borderBottomRightRadius?: number;
513 | borderStyle?: 'solid' | 'dotted' | 'dashed';
514 | opacity?: number;
515 | }
516 | type StyleReference = number;
517 | interface ResizeConstraints {
518 | top: boolean;
519 | right: boolean;
520 | bottom: boolean;
521 | left: boolean;
522 | fixedHeight: boolean;
523 | fixedWidth: boolean;
524 | }
525 | interface SketchShadow {
526 | shadowColor: Color;
527 | shadowOffset: { width: number; height: number };
528 | shadowSpread: number;
529 | shadowOpacity: number;
530 | shadowRadius: number;
531 | shadowInner: boolean;
532 | }
533 |
534 | interface ViewProps {
535 | name?: string;
536 | children?: React.ReactNode[] | React.ReactNode;
537 | style?: Style | StyleReference;
538 | resizingConstraint?: ResizeConstraints;
539 | shadows?: SketchShadow[];
540 | }
541 |
542 | declare module 'react-primitives-svg' {
543 | export interface CircleProps extends PathProps {
544 | cx: NumberProp;
545 | cy: NumberProp;
546 | r: NumberProp;
547 | }
548 | export interface EllipseProps extends PathProps {
549 | cx: NumberProp;
550 | cy: NumberProp;
551 | rx: NumberProp;
552 | ry: NumberProp;
553 | }
554 | export interface GProps extends PathProps, FontProps { }
555 |
556 | export interface LinearGradientProps {
557 | x1: NumberProp;
558 | x2: NumberProp;
559 | y1: NumberProp;
560 | y2: NumberProp;
561 | gradientUnits?: 'objectBoundingBox' | 'userSpaceOnUse';
562 | id?: string;
563 | children?: React.ReactNode[] | React.ReactNode;
564 | }
565 | export interface RadialGradientProps {
566 | fx: NumberProp;
567 | fy: NumberProp;
568 | rx?: NumberProp;
569 | ry?: NumberProp;
570 | cx: NumberProp;
571 | cy: NumberProp;
572 | r?: NumberProp;
573 | gradientUnits?: 'objectBoundingBox' | 'userSpaceOnUse';
574 | id: string;
575 | children?: React.ReactNode[] | React.ReactNode;
576 | }
577 | export interface LineProps extends PathProps {
578 | x1: NumberProp;
579 | x2: NumberProp;
580 | y1: NumberProp;
581 | y2: NumberProp;
582 | }
583 |
584 | export interface PathPathProps extends PathProps {
585 | d: string;
586 | }
587 |
588 | export interface PolygonProps extends PathProps {
589 | points: string;
590 | }
591 |
592 | export interface PolylineProps extends PathProps {
593 | points: string;
594 | }
595 |
596 | export interface RectProps extends PathProps {
597 | x: NumberProp;
598 | y: NumberProp;
599 | width: NumberProp;
600 | height: NumberProp;
601 | rx?: NumberProp;
602 | ry?: NumberProp;
603 | }
604 |
605 | export interface SymbolProps {
606 | id: string;
607 | viewBox?: string;
608 | preserveAspectRatio?: string;
609 | children: React.ReactNode[] | React.ReactNode;
610 | }
611 |
612 | export class Text extends React.Component {
613 | getChildContext(): { isInAParentText: boolean };
614 | }
615 |
616 | export interface UseProps extends PathProps {
617 | href: string;
618 | width?: NumberProp; // Just for reusing `Symbol`
619 | height?: NumberProp; // Just for reusing `Symbol`
620 | }
621 |
622 | export interface DefsProps {
623 | children: React.ReactNode[] | React.ReactNode;
624 | }
625 |
626 | export interface StopProps {
627 | stopColor?: string;
628 | stopOpacity?: NumberProp;
629 | children?: React.ReactNode[] | React.ReactNode;
630 | }
631 |
632 | export interface SvgProps extends ViewProps {
633 | opacity?: string | number;
634 | width?: string | number;
635 | height?: string | number;
636 | // more detail https://svgwg.org/svg2-draft/coords.html#ViewBoxAttribute
637 | viewBox?: string;
638 | preserveAspectRatio?: string;
639 | }
640 |
641 | export interface ClipPathProps {
642 | id: string;
643 | children?: React.ReactNode[] | React.ReactNode;
644 | }
645 | export interface ImageProps {
646 | x?: NumberProp;
647 | y?: NumberProp;
648 | width: NumberProp;
649 | height: NumberProp;
650 | href: string;
651 | preserveAspectRatio?: string;
652 | children?: React.ReactNode[] | React.ReactNode;
653 | }
654 | export interface PatternProps {
655 | x1?: NumberProp;
656 | x2?: NumberProp;
657 | y1?: NumberProp;
658 | y2?: NumberProp;
659 | patternTransform?: string;
660 | patternUnits?: 'userSpaceOnUse' | 'objectBoundingBox';
661 | patternContentUnits?: 'userSpaceOnUse' | 'objectBoundingBox';
662 | children?: React.ReactNode[] | React.ReactNode;
663 | }
664 | type TSpanProps = TextProps & {};
665 |
666 |
667 |
668 |
669 | export class Svg extends React.Component {
670 | static Circle: new (props: CircleProps) => Circle;
671 | static ClipPath: new (props: ClipPathProps) => ClipPath;
672 | static Defs: new (props: DefsProps) => Defs;
673 | static Ellipse: new (props: EllipseProps) => Ellipse;
674 | static G: new (props: GProps) => G;
675 | static Image: new (props: ImageProps) => Image;
676 | static Line: new (props: LineProps) => Line;
677 | static LinearGradient: new (props: LinearGradientProps) => LinearGradient;
678 | static Path: new (props: PathProps) => Path;
679 | static Pattern: new (props: PatternProps) => Pattern;
680 | static Polygon: new (props: PolygonProps) => Polygon;
681 | static Polyline: new (props: PolylineProps) => Polyline;
682 | static RadialGradient: new (props: RadialGradientProps) => RadialGradient;
683 | static Rect: new (props: RectProps) => Rect;
684 | static Stop: new (props: StopProps) => Stop;
685 | static Symbol: new (props: SymbolProps) => Symbol;
686 | static Text: new (props: TextProps) => Text;
687 | static TextPath: new (props: TextPathProps) => TextPath;
688 | static TSpan: new (props: TSpanProps) => TSpan;
689 | static Use: new (props: UseProps) => Use;
690 | }
691 | export class TSpan extends React.Component {
692 | getChildContext(): { isInAParentText: boolean };
693 | }
694 | export class TextPath extends React.Component { }
695 | export class Pattern extends React.Component { }
696 | export class Image extends React.Component { }
697 | export class ClipPath extends React.Component { }
698 | export class Stop extends React.Component { }
699 | export class Defs extends React.Component { }
700 | export class Use extends React.Component { }
701 | export class Symbol extends React.Component { }
702 | export class Rect extends React.Component { }
703 | export class Polyline extends React.Component { }
704 | export class Polygon extends React.Component { }
705 | export class Path extends React.Component { }
706 | export class Line extends React.Component { }
707 | export class RadialGradient extends React.Component { }
708 | export class LinearGradient extends React.Component { }
709 | export class G extends React.Component { }
710 | export class Ellipse extends React.Component { }
711 | export class Circle extends React.Component { }
712 |
713 | }
714 |
--------------------------------------------------------------------------------
/src/@types/react-primitives/index.d.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | declare module 'react-primitives' {
7 | import { ComponentType } from 'react';
8 |
9 | interface TouchableWithoutFeedbackPropsIOS {
10 | /**
11 | * *(Apple TV only)* TV preferred focus (see documentation for the View component).
12 | *
13 | * @platform ios
14 | */
15 | hasTVPreferredFocus?: boolean;
16 |
17 | /**
18 | * *(Apple TV only)* Object with properties to control Apple TV parallax effects.
19 | *
20 | * enabled: If true, parallax effects are enabled. Defaults to true.
21 | * shiftDistanceX: Defaults to 2.0.
22 | * shiftDistanceY: Defaults to 2.0.
23 | * tiltAngle: Defaults to 0.05.
24 | * magnification: Defaults to 1.0.
25 | * pressMagnification: Defaults to 1.0.
26 | * pressDuration: Defaults to 0.3.
27 | * pressDelay: Defaults to 0.0.
28 | *
29 | * @platform ios
30 | */
31 | tvParallaxProperties?: any;
32 | }
33 |
34 | interface AccessibilityProps {
35 | /**
36 | * When true, indicates that the view is an accessibility element.
37 | * By default, all the touchable elements are accessible.
38 | */
39 | accessible?: boolean;
40 |
41 | /**
42 | * Provides an array of custom actions available for accessibility.
43 | */
44 | accessibilityActions?: ReadonlyArray;
45 |
46 | /**
47 | * Overrides the text that's read by the screen reader when the user interacts with the element. By default, the
48 | * label is constructed by traversing all the children and accumulating all the Text nodes separated by space.
49 | */
50 | accessibilityLabel?: string;
51 |
52 | /**
53 | * Accessibility Role tells a person using either VoiceOver on iOS or TalkBack on Android the type of element that is focused on.
54 | */
55 | accessibilityRole?: any;
56 | /**
57 | * Accessibility State tells a person using either VoiceOver on iOS or TalkBack on Android the state of the element currently focused on.
58 | * @deprecated: accessibilityState available in 0.60+
59 | */
60 | accessibilityStates?: any[];
61 | /**
62 | * Accessibility State tells a person using either VoiceOver on iOS or TalkBack on Android the state of the element currently focused on.
63 | */
64 | accessibilityState?: any;
65 | /**
66 | * An accessibility hint helps users understand what will happen when they perform an action on the accessibility element when that result is not obvious from the accessibility label.
67 | */
68 | accessibilityHint?: string;
69 |
70 | /**
71 | * When `accessible` is true, the system will try to invoke this function when the user performs an accessibility custom action.
72 | */
73 | onAccessibilityAction?: (event: any) => void;
74 | }
75 |
76 | export interface TouchableWithoutFeedbackProps extends TouchableWithoutFeedbackPropsIOS, AccessibilityProps {
77 | /**
78 | * Delay in ms, from onPressIn, before onLongPress is called.
79 | */
80 | delayLongPress?: number;
81 |
82 | /**
83 | * Delay in ms, from the start of the touch, before onPressIn is called.
84 | */
85 | delayPressIn?: number;
86 |
87 | /**
88 | * Delay in ms, from the release of the touch, before onPressOut is called.
89 | */
90 | delayPressOut?: number;
91 |
92 | /**
93 | * If true, disable all interactions for this component.
94 | */
95 | disabled?: boolean;
96 |
97 | /**
98 | * This defines how far your touch can start away from the button.
99 | * This is added to pressRetentionOffset when moving off of the button.
100 | * NOTE The touch area never extends past the parent view bounds and
101 | * the Z-index of sibling views always takes precedence if a touch hits
102 | * two overlapping views.
103 | */
104 | hitSlop?: any;
105 |
106 | /**
107 | * When `accessible` is true (which is the default) this may be called when
108 | * the OS-specific concept of "blur" occurs, meaning the element lost focus.
109 | * Some platforms may not have the concept of blur.
110 | */
111 | onBlur?: (e: any) => void;
112 |
113 | /**
114 | * When `accessible` is true (which is the default) this may be called when
115 | * the OS-specific concept of "focus" occurs. Some platforms may not have
116 | * the concept of focus.
117 | */
118 | onFocus?: (e: any) => void;
119 |
120 | /**
121 | * Invoked on mount and layout changes with
122 | * {nativeEvent: {layout: {x, y, width, height}}}
123 | */
124 | onLayout?: (event: any) => void;
125 |
126 | onLongPress?: (event: any) => void;
127 |
128 | /**
129 | * Called when the touch is released,
130 | * but not if cancelled (e.g. by a scroll that steals the responder lock).
131 | */
132 | onPress?: (event: any) => void;
133 |
134 | onPressIn?: (event: any) => void;
135 |
136 | onPressOut?: (event: any) => void;
137 |
138 | /**
139 | * //FIXME: not in doc but available in examples
140 | */
141 | style?: Object;
142 |
143 | /**
144 | * When the scroll view is disabled, this defines how far your
145 | * touch may move off of the button, before deactivating the button.
146 | * Once deactivated, try moving it back and you'll see that the button
147 | * is once again reactivated! Move it back and forth several times
148 | * while the scroll view is disabled. Ensure you pass in a constant
149 | * to reduce memory allocations.
150 | */
151 | pressRetentionOffset?: any;
152 |
153 | /**
154 | * Used to locate this view in end-to-end tests.
155 | */
156 | testID?: string;
157 | }
158 |
159 | interface TouchableOpacityProps extends TouchableWithoutFeedbackProps {
160 | /**
161 | * Determines what the opacity of the wrapped view should be when touch is active.
162 | * Defaults to 0.2
163 | */
164 | activeOpacity?: number;
165 | }
166 |
167 |
168 |
169 | export const Touchable: ComponentType;
170 |
171 | export type PlatformOSType = 'ios' | 'android' | 'web' | 'sketch' | 'vr' | 'figma';
172 | export interface PlatformStatic {
173 | OS: PlatformOSType;
174 | Version: number | string;
175 | select(specifics: { [platform in PlatformOSType | 'default']?: T }): T;
176 | }
177 | export const Platform: PlatformStatic;
178 | }
179 |
--------------------------------------------------------------------------------
/src/@types/styled-system/index.d.ts:
--------------------------------------------------------------------------------
1 |
2 | declare module '@styled-system/core' {
3 | import * as CSS from 'csstype';
4 |
5 | export type ObjectOrArray = T[] | Record | T[]>;
6 |
7 | export type Scale = ObjectOrArray;
8 | export interface styleFn {
9 | (...args: any[]): any;
10 |
11 | config?: object;
12 | propNames?: string[];
13 | cache?: object;
14 | }
15 |
16 | export interface ConfigStyle {
17 | /** The CSS property to use in the returned style object (overridden by `properties` if present). */
18 | property?: keyof CSS.Properties;
19 | /**
20 | * An array of multiple properties (e.g. `['marginLeft', 'marginRight']`) to which this style's value will be
21 | * assigned (overrides `property` when present).
22 | */
23 | properties?: Array;
24 | /** A string referencing a key in the `theme` object. */
25 | scale?: string;
26 | /** A fallback scale object for when there isn't one defined in the `theme` object. */
27 | defaultScale?: Scale;
28 | /** A function to transform the raw value based on the scale. */
29 | transform?: (value: any, scale?: Scale) => any;
30 | }
31 |
32 | export interface Config {
33 | /** Property name exposed for use in components */
34 | [customStyleName: string]: ConfigStyle | boolean;
35 | }
36 |
37 | export function system(styleDefinitions: Config): styleFn;
38 | export function get(obj: any, ...paths: Array): any;
39 | }
40 |
--------------------------------------------------------------------------------
/src/LayoutProvider.tsx:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, {
3 | createContext,
4 | ReactNode,
5 | ComponentType,
6 | } from 'react';
7 | // import Platform from 'react-primitives';
8 |
9 | export interface State {
10 | breakpoint: number,
11 | };
12 |
13 | export interface Value {
14 | state: State,
15 | dispatch: ({ }: { type: string, payload: Object }) => void,
16 | };
17 |
18 | const initialState = {
19 | state: {
20 | breakpoint: 0, // 0 is mobile, 1 is tablet, 2 is desktop
21 | },
22 | dispatch: () => {},
23 | };
24 |
25 | const LayoutContext = createContext(initialState);
26 |
27 | // const reducer = (state, action) => {
28 | // const { type, payload } = action;
29 |
30 | // switch (type) {
31 | // case 'resize': {
32 | // return {
33 | // ...state,
34 | // };
35 | // }
36 | // default: {
37 | // return state;
38 | // }
39 | // }
40 | // };
41 |
42 | const LayoutProvider = ({ breakpoint, children }: {
43 | breakpoint: number,
44 | children: ReactNode,
45 | }) => {
46 | // const [state, dispatch] = Platform.OS === 'sketch' ? [{
47 | // ...initialState,
48 | // breakpoint,
49 | // }, () => {}] : useReducer(reducer, initialState);
50 | const state = { breakpoint };
51 | const dispatch = () => {};
52 | const value = { state, dispatch };
53 |
54 | return (
55 |
56 | {children}
57 |
58 | );
59 | };
60 |
61 | function withContext(Component: ComponentType) {
62 | return React.forwardRef((props: Omit, ref) => (
63 |
64 | {value => }
65 |
66 | ));
67 | };
68 |
69 | const LayoutContextConsumer = LayoutContext.Consumer;
70 |
71 | export { LayoutProvider, LayoutContext, LayoutContextConsumer, withContext };
72 |
--------------------------------------------------------------------------------
/src/ThemeProvider.tsx:
--------------------------------------------------------------------------------
1 | // Color scheme theming code used from https://github.com/primer/react/blob/006cc80bd8fa2f31947e17e0683880e0b8cdc400/src/ThemeProvider.tsx (MIT License)
2 |
3 | import React, { ReactNode, createContext, useContext, ComponentType, Dispatch, SetStateAction } from 'react';
4 | import deepmerge from 'deepmerge';
5 |
6 | // @ts-ignore
7 | import useColorScheme from './hooks/use-color-scheme';
8 | // import { Platform } from 'react-primitives';
9 | import { ThemeProvider } from './styled';
10 | // import { ThemeProvider as ThemeProviderPrimitives } from 'styled-components/primitives';
11 |
12 | // const ThemeProvider = Platform.OS === 'web' ? ThemeProviderWeb : ThemeProviderPrimitives;
13 |
14 | const baseTheme = {
15 | fonts: {},
16 | space: [
17 | 0, 4, 8, 16, 32, 64, 128, 256, 512,
18 | ],
19 | colors: {
20 | black: '#000000',
21 | white: '#ffffff',
22 | },
23 | fontSizes: [96, 60, 48, 34, 24, 20, 16, 14],
24 | textStyles: {},
25 | breakpoints: [
26 | '601px',
27 | '769px',
28 | '1025px',
29 | ],
30 | lineHeights: {},
31 | colorSchemes: {},
32 | };
33 |
34 | const defaultColorMode = 'day';
35 | const defaultDayScheme = 'light';
36 | const defaultNightScheme = 'dark';
37 |
38 | type Theme = {[key: string]: any};
39 | export type ColorMode = 'day' | 'night';
40 | type ColorModeWithAuto = ColorMode | 'auto';
41 |
42 | const ThemeContext = React.createContext<{
43 | theme?: Theme
44 | colorScheme?: string
45 | colorMode?: ColorModeWithAuto
46 | resolvedColorMode?: ColorMode
47 | resolvedColorScheme?: string
48 | dayScheme?: string
49 | nightScheme?: string
50 | setColorMode: Dispatch>,
51 | setDayScheme: Dispatch>
52 | setNightScheme: Dispatch>
53 | }>({
54 | setColorMode: () => null,
55 | setDayScheme: () => null,
56 | setNightScheme: () => null
57 | });
58 |
59 | function resolveColorMode(colorMode: ColorModeWithAuto, systemColorMode: ColorMode) {
60 | switch (colorMode) {
61 | case 'auto':
62 | return systemColorMode
63 | default:
64 | return colorMode
65 | }
66 | }
67 |
68 | function chooseColorScheme(colorMode: ColorMode, dayScheme: string, nightScheme: string) {
69 | switch (colorMode) {
70 | case 'day':
71 | return dayScheme
72 | case 'night':
73 | return nightScheme
74 | }
75 | }
76 |
77 |
78 | export const useTheme = () => {
79 | return React.useContext(ThemeContext);
80 | }
81 |
82 | const DesignContext = createContext<{
83 | state: any,
84 | dispatch?: any,
85 | }>({
86 | state: {}
87 | });
88 |
89 | export const useDesign = (name: string) => {
90 | const { state } = useContext(DesignContext);
91 | const { design } = state || {};
92 |
93 | return design[name] || {};
94 | };
95 |
96 | interface WithStylesProps { [key: string]: any };
97 |
98 | export function withStyles(Component: ComponentType, styles: any): ComponentType {
99 | return (props: T) => (
100 |
101 | );
102 | };
103 |
104 | export function withDesign(Component: ComponentType): ComponentType {
105 | return (props: T) => (
106 |
107 | {(value) => {
108 | const { state } = value;
109 | const { design = {} } = state || {};
110 | return (
111 |
115 | );
116 | }}
117 |
118 | );
119 | };
120 |
121 | const DesignProvider = ({ design, children }: {
122 | design: { [key: string]: any },
123 | children: ReactNode,
124 | }) => {
125 | const state = { design };
126 | const dispatch = () => {};
127 | const value = { state, dispatch };
128 |
129 | return (
130 |
131 | {children}
132 |
133 | );
134 | };
135 |
136 | const useSystemColorMode = () => {
137 | const systemColorScheme = useColorScheme();
138 |
139 | if (systemColorScheme === 'light') {
140 | return 'day';
141 | }
142 | if (systemColorScheme === 'dark') {
143 | return 'night';
144 | }
145 |
146 | return defaultColorMode;
147 | }
148 |
149 | function applyColorScheme(
150 | theme: Theme,
151 | colorScheme: string
152 | ): {resolvedTheme: Theme; resolvedColorScheme: string | undefined} {
153 | if (!theme.colorSchemes) {
154 | return {
155 | resolvedTheme: theme,
156 | resolvedColorScheme: undefined
157 | }
158 | }
159 |
160 | if (!theme.colorSchemes[colorScheme]) {
161 | // eslint-disable-next-line no-console
162 | console.error(`\`${colorScheme}\` scheme not defined in \`theme.colorSchemes\``)
163 |
164 | // Apply the first defined color scheme
165 | const defaultColorScheme = Object.keys(theme.colorSchemes)[0]
166 | return {
167 | resolvedTheme: deepmerge(theme, theme.colorSchemes[defaultColorScheme]),
168 | resolvedColorScheme: defaultColorScheme
169 | }
170 | }
171 |
172 | return {
173 | resolvedTheme: deepmerge(theme, theme.colorSchemes[colorScheme]),
174 | resolvedColorScheme: colorScheme
175 | }
176 | }
177 |
178 | const ComponentLibThemeProvider = ({
179 | design = {}, children, ...props
180 | }: {
181 | design: { [key: string]: any },
182 | theme: typeof baseTheme,
183 | colorMode?: ColorMode,
184 | dayScheme?: string,
185 | nightScheme?: string,
186 | children: ReactNode,
187 | }) => {
188 | const {
189 | theme: fallbackTheme,
190 | colorMode: fallbackColorMode,
191 | dayScheme: fallbackDayScheme,
192 | nightScheme: fallbackNightScheme
193 | } = useTheme();
194 |
195 | const theme = props.theme || fallbackTheme || baseTheme;
196 |
197 | const [colorMode, setColorMode] = React.useState(props.colorMode || fallbackColorMode || defaultColorMode)
198 | const [dayScheme, setDayScheme] = React.useState(props.dayScheme || fallbackDayScheme || defaultDayScheme)
199 | const [nightScheme, setNightScheme] = React.useState(props.nightScheme || fallbackNightScheme || defaultNightScheme)
200 | const systemColorMode = useSystemColorMode()
201 | const resolvedColorMode = resolveColorMode(colorMode, systemColorMode)
202 | const colorScheme = chooseColorScheme(resolvedColorMode, dayScheme, nightScheme)
203 | const { resolvedTheme, resolvedColorScheme } = React.useMemo(
204 | () => applyColorScheme(theme, colorScheme),
205 | [theme, colorScheme]
206 | );
207 |
208 | // Update state if props change
209 | React.useEffect(() => {
210 | setColorMode(props.colorMode || fallbackColorMode || defaultColorMode);
211 | }, [props.colorMode, fallbackColorMode]);
212 |
213 | React.useEffect(() => {
214 | setDayScheme(props.dayScheme || fallbackDayScheme || defaultDayScheme);
215 | }, [props.dayScheme, fallbackDayScheme]);
216 |
217 | React.useEffect(() => {
218 | setNightScheme(props.nightScheme || fallbackNightScheme || defaultNightScheme);
219 | }, [props.nightScheme, fallbackNightScheme]);
220 |
221 | return (
222 |
236 |
237 |
238 | {children}
239 |
240 |
241 |
242 | );
243 | };
244 |
245 | export default ComponentLibThemeProvider;
246 |
--------------------------------------------------------------------------------
/src/atoms/Box/Box.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-nested-ternary */
2 | // @flow
3 | import React, { ComponentProps, ComponentType, ReactNode, useState } from 'react';
4 | import { Platform, Touchable } from 'react-primitives';
5 |
6 | const RN: any = Platform.select({
7 | ios: () => require('react-native'),
8 | android: () => require('react-native'),
9 | default: () => { return {}; },
10 | })();
11 |
12 | const { TouchableOpacity, TouchableHighlight, TouchableNativeFeedback } = RN;
13 |
14 | import styled from '../../styled';
15 |
16 | import Rectangle, { rectangleStylePropNames } from '../Rectangle';
17 | import { parseAttributes } from '../../utils';
18 | import { makeShadow } from '../../utils/shadow';
19 | import { withContext } from '../../LayoutProvider';
20 | // import useHover from '../../hooks/use-hover';
21 |
22 | type BoxProps = ComponentProps & {
23 | onClick?: (...args: any[]) => any,
24 | onPress?: (...args: any[]) => any,
25 | ref?: () => any,
26 | as?: string | ReactNode,
27 | };
28 |
29 | const Box: ComponentType = styled(Rectangle)``;
30 |
31 | Box.defaultProps = {
32 | display: 'flex',
33 | flexDirection: 'column',
34 | ...((Platform.OS === 'web' || Platform.OS === 'sketch') && { borderColor: 'none' }), // FIXME: ...
35 | borderStyle: 'solid',
36 | borderWidth: 0,
37 | };
38 |
39 | type Styles = { size: number, [key: string]: any };
40 |
41 | const getSize = (args: Styles, breakpoint: number) => {
42 | const { size, ...styles } = args;
43 |
44 | if (size) {
45 | styles.width = size;
46 | styles.height = size;
47 | }
48 |
49 | const res = Object.keys(styles).reduce((acc: { [key: string]: any }, arg) => {
50 | if (styles[arg]) {
51 | const value = styles[arg];
52 |
53 | acc[arg] = Array.isArray(value)
54 | ? breakpoint > (value.length - 1)
55 | ? value[value.length - 1]
56 | : value[breakpoint]
57 | : value;
58 | }
59 | return acc;
60 | }, {});
61 |
62 | return res;
63 | };
64 |
65 | export type InteractiveState = 'idle' | 'hover' | 'focus' | undefined;
66 | type StyleKey = 'hover' | 'focus' | 'disabled';
67 |
68 | type Props = BoxProps & {
69 | styles?: { [K in StyleKey]?: Object }
70 | pseudoState?: InteractiveState,
71 | disabled?: boolean,
72 | onClick?: (...args: any[]) => void,
73 | onPress?: (...args: any[]) => void,
74 | onMouseEnter: Function,
75 | onMouseLeave: Function,
76 | touchable?: 'opacity' | 'highlight' | 'default' | 'material' | 'native',
77 | size?: number,
78 | name?: string, // react-sketchapp
79 | center?: boolean,
80 | p?: number,
81 | boxShadow?: (_: {}) => string | string,
82 | children?: ReactNode,
83 | forwardedRef?: () => any,
84 | };
85 |
86 | const useMouseEventState = (initialPseudoState: InteractiveState) => {
87 | const [hover, setHover] = useState(false);
88 | const [pseudoState, setPseudoState]: [InteractiveState, Function] = useState('idle');
89 |
90 | if (Platform.OS === 'sketch') {
91 | return { events: {}, pseudoState: initialPseudoState || 'idle' };
92 | }
93 |
94 | const onPress = () => {
95 | setPseudoState('focus');
96 |
97 | setTimeout(() => {
98 | setPseudoState(initialPseudoState || 'idle');
99 | }, 200);
100 | }
101 |
102 | const onMouseEnter = () => {
103 | setHover(true);
104 | setPseudoState('hover');
105 | };
106 | const onMouseLeave = () => {
107 | if (hover) {
108 | setHover(false);
109 | setPseudoState(initialPseudoState || 'idle');
110 | }
111 | };
112 | return {
113 | events: { onMouseEnter, onMouseLeave, onPress },
114 | pseudoState: initialPseudoState || pseudoState,
115 | }
116 | }
117 |
118 | // FIXME: Move pseudoStyles into a hook and context that can be accessed by parent components like