mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-01 10:07:35 +00:00
Fix export; iterate on README
This commit is contained in:
@@ -1,57 +1,34 @@
|
|||||||
# react-web-sdk
|
# react-web-sdk
|
||||||
|
|
||||||
**Experimental / API proof of concept**
|
**Experimental / Proof of concept**
|
||||||
|
|
||||||
Components for building web applications and SDKs. Based on `react-native`'s
|
A component-based React SDK for building and styling web applications and SDKs.
|
||||||
components.
|
Inspired by `react-native`.
|
||||||
|
|
||||||
1. Styles are plain JavaScript objects.
|
This library provides:
|
||||||
2. Styles are passed to a component's `style` prop.
|
|
||||||
3. Non-dynamic styles rely on static CSS classes.
|
|
||||||
4. Dynamic styles are written as inline styles on the DOM node.
|
|
||||||
|
|
||||||
Each Component defines `StylePropTypes` and filters out any style properties
|
* An initial implementation of the `react-native` components `Image`, `Text`,
|
||||||
that are not part of its style API. For example, `View` does not support any
|
and `View`.
|
||||||
typographic properties. You must use `Text` to wrap and style any text strings.
|
* An initial implementation of defining explicit and default `style` propTypes.
|
||||||
|
* A proof of concept implementation for converting inline `style` definitions
|
||||||
|
to CSS classes.
|
||||||
|
|
||||||
### Implementation notes
|
This proof of concept uses a ~3KB (gzipped) precomputed CSS bundle; a complete
|
||||||
|
implementation is likely to produce a slightly larger CSS file and fewer inline
|
||||||
The current implementation uses a prebuilt CSS library – 200+ single-purpose,
|
|
||||||
obfuscated selectors. It provides a large number of common, knowable styles
|
|
||||||
needed to build apps. This makes the foundational CSS fixed (~5KB gzipped) and
|
|
||||||
highly cachable. Inline styles are used for anything the CSS library doesn't
|
|
||||||
provide, but their use is significantly reduced.
|
|
||||||
|
|
||||||
A better implementation would generate the CSS library from the declarations
|
|
||||||
defined in the source code, replace static variables (use webpack's
|
|
||||||
`DefinePlugin`?), and only use inline-styles for dynamic, instance-specific
|
|
||||||
styles.
|
styles.
|
||||||
|
|
||||||
What about media queries? Perhaps rely on `mediaMatch` and re-render when the
|
Other React styling strategies:
|
||||||
`style` prop needs to change; works better with DOM-related changes that need
|
[react-native](https://facebook.github.io/react-native/),
|
||||||
to happen at breakpoints. And that would avoid the need for any additional
|
[react-style](https://github.com/js-next/react-style),
|
||||||
CSS. Alternatively, rely on something similar to `react-style`'s approach and
|
[jsxstyle](https://github.com/petehunt/jsxstyle),
|
||||||
duplicate the single-purpose classes within static CSS media queries.
|
[react-inline](https://github.com/martinandert/react-inline), and
|
||||||
|
[react-free-style](https://github.com/blakeembrey/react-free-style/)
|
||||||
|
|
||||||
### Example
|
### 1. Write styles using plain JavaScript objects
|
||||||
|
|
||||||
|
Use JavaScript to write style definitions in React components:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import React from 'react';
|
|
||||||
import {Image, Text, View} from 'react-web-sdk';
|
|
||||||
|
|
||||||
class Example extends React.Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<View element="main" style={{ ...style.common, ...style.root }}>
|
|
||||||
<Image alt="accessibility text" src="/image.png" style={style.image} />
|
|
||||||
<Text style={{ ...style.common, ...(Math.random() > 0.5 && style.text) }}>
|
|
||||||
Example component
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
common: {
|
common: {
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
@@ -68,10 +45,131 @@ const style = {
|
|||||||
fontWeight: '300'
|
fontWeight: '300'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Example;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 2. Set styles using the `style` attribute
|
||||||
|
|
||||||
|
The `style` attribute is a simple API for creating element-scoped styles.
|
||||||
|
|
||||||
|
```js
|
||||||
|
<View style={style.root}>...</View>
|
||||||
|
|
||||||
|
<View style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between'
|
||||||
|
}}>
|
||||||
|
...
|
||||||
|
</View
|
||||||
|
```
|
||||||
|
|
||||||
|
It's easy to combine and override style objects:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import baseStyle from './baseStyle';
|
||||||
|
|
||||||
|
const buttonStyle = {
|
||||||
|
...baseStyle,
|
||||||
|
backgroundColor: '#333',
|
||||||
|
color: '#fff'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
So far, this is identical to using inline styles in a React component.
|
||||||
|
|
||||||
|
### 3. Map inline styles to static CSS rules
|
||||||
|
|
||||||
|
Using the `style` attribute would normally produce inline styles. Libraries
|
||||||
|
like [react-style](https://github.com/js-next/react-style) implement a
|
||||||
|
`Stylesheet.create` API and provide a means to extract styles to CSS at build
|
||||||
|
time. The extraction maps whole style objects to multi-declaration CSS rules.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// relies on a new construct
|
||||||
|
Stylesheet.create({
|
||||||
|
root: {
|
||||||
|
background: 'transparent',
|
||||||
|
display: 'flex',
|
||||||
|
flexGrow: 1,
|
||||||
|
justifyContent: 'center'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* produces new css for every new style rule */
|
||||||
|
.foo {
|
||||||
|
background: transparent;
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each component adds new rules to the stylesheet. This can lead the extracted
|
||||||
|
stylesheet to become increasingly large and prone to change.
|
||||||
|
|
||||||
|
Libraries like [Atomic CSS](http://acss.io/),
|
||||||
|
[Basscss](http://www.basscss.com/), [SUIT CSS](https://suitcss.github.io/), and
|
||||||
|
[tachyons](http://tachyons.io/) are attempts to limit style scope and limit
|
||||||
|
stylesheet growth. But they are CSS utility libraries, each with a particular
|
||||||
|
set of classes and features to learn. All of them require developers to
|
||||||
|
manually connect CSS classes for given styles.
|
||||||
|
|
||||||
|
The `react-web-sdk` library's implementation is a proof-of-concept for the
|
||||||
|
strategy of automatically mapping style `key`-`value` pairs to
|
||||||
|
single-declaration CSS rules.
|
||||||
|
|
||||||
|
```css
|
||||||
|
.background-transparent { background: transparent }
|
||||||
|
.display-block { display: block }
|
||||||
|
.flexGrow-1 { flex-grow: 1 }
|
||||||
|
.justifyContent-center { justify-content: center }
|
||||||
|
```
|
||||||
|
|
||||||
|
Mapping declarations to single-purpose classes greatly reduces the total amount
|
||||||
|
of CSS needed. It avoids the repetition of declarations between rules for
|
||||||
|
unrelated elements, and prevents style collisions. A build step hashes the class names.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="_jsy9e _fidnk _jn78h _qqqnk">...</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
The current implementation uses a precomputed CSS library of known and common
|
||||||
|
declarations – 200+ single-declaration rules, with obfuscated selectors. This
|
||||||
|
handles a signficant portion of possible declarations. But it falls through to
|
||||||
|
inline styles significantly more often than an better implementation using
|
||||||
|
static analysis to generate the CSS library at build time.
|
||||||
|
|
||||||
|
### 4. Use inline styles for dynamic values
|
||||||
|
|
||||||
|
Inline styles are used for values that cannot be resolved at build time.
|
||||||
|
|
||||||
|
```js
|
||||||
|
<Text style={{ color: (Math.random() > 0.5 ? 'red' : 'black')}}>...</Text>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Add `style` prop types
|
||||||
|
|
||||||
|
Each Component defines `StylePropTypes` and filters out any style properties
|
||||||
|
that are not part of its style API. For example, `View` does not support any
|
||||||
|
typographic properties.
|
||||||
|
|
||||||
|
See the section below on `StylePropTypes`.
|
||||||
|
|
||||||
|
### Media Queries?
|
||||||
|
|
||||||
|
We can use `mediaMatch` to orchestrate both style and DOM changes. The
|
||||||
|
component can be re-rendered when the styles change. This also avoids the need
|
||||||
|
for additional CSS. Adding media queries as keys on the `style` object (like
|
||||||
|
`react-style` does) might be a good, or it might not be. I'd prefer a solution
|
||||||
|
that accomodates both style and DOM changes, rather than one or the other.
|
||||||
|
Perhaps `this.context` or a higher-order component that passes viewport data to
|
||||||
|
components via `props`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Components
|
## Components
|
||||||
@@ -93,37 +191,26 @@ All other props are transferred directly to the `element`.
|
|||||||
#### Examples
|
#### Examples
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import {Component, getOtherProps, pickProps} from 'react-web-sdk';
|
import {Component, pickProps} from 'react-web-sdk';
|
||||||
import React, {PropTypes} from 'react';
|
import React, {PropTypes} from 'react';
|
||||||
|
|
||||||
const ExampleStylePropTypes = {
|
const ExampleStylePropTypes = { opacity: PropTypes.number };
|
||||||
opacity: PropTypes.number
|
const ExampleStyleDefaultProps = { opacity: 1 };
|
||||||
};
|
|
||||||
|
|
||||||
const ExampleStyleDefaultProps = {
|
|
||||||
opacity: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
class Example extends React.Component {
|
class Example extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
anExampleProp: PropTypes.number,
|
|
||||||
someExampleProp: PropTypes.string,
|
|
||||||
style: PropTypes.shape(ExampleStylePropTypes)
|
style: PropTypes.shape(ExampleStylePropTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
style: ExampleStyleDefaultProps
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const other = getOtherProps(this);
|
// only apply supported styles
|
||||||
const supportedStyle = pickProps(this.props.style, ExampleStylePropTypes);
|
const supportedStyle = pickProps(this.props.style, ExampleStylePropTypes);
|
||||||
|
// merge with default styles
|
||||||
const style = { ...ExampleStyleDefaultProps, ...supportedStyle }
|
const style = { ...ExampleStyleDefaultProps, ...supportedStyle }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component
|
<Component
|
||||||
{...other}
|
children={this.props.children}
|
||||||
className={`Example`}
|
|
||||||
element="main"
|
element="main"
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
@@ -141,10 +228,10 @@ TODO
|
|||||||
|
|
||||||
All other props are transferred directly to the `element`.
|
All other props are transferred directly to the `element`.
|
||||||
|
|
||||||
+ `alt`: `string`
|
+ `accessibilityLabel`: `string`
|
||||||
+ `async`: `bool` (TODO)
|
+ `async`: `bool` (TODO)
|
||||||
+ `className`: `string`
|
+ `className`: `string`
|
||||||
+ `src`: `string`
|
+ `source`: `string`
|
||||||
+ `style`: `ImageStylePropTypes`
|
+ `style`: `ImageStylePropTypes`
|
||||||
|
|
||||||
#### ImageStylePropTypes
|
#### ImageStylePropTypes
|
||||||
@@ -154,6 +241,47 @@ All other props are transferred directly to the `element`.
|
|||||||
+ `LayoutPropTypes`
|
+ `LayoutPropTypes`
|
||||||
+ `opacity`: `string`
|
+ `opacity`: `string`
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```js
|
||||||
|
import {Image, StylePropTypes} from 'react-web-sdk';
|
||||||
|
import React, {PropTypes} from 'react';
|
||||||
|
|
||||||
|
const AvatarStylePropTypes = {
|
||||||
|
...StylePropTypes.BorderThemePropTypes
|
||||||
|
};
|
||||||
|
|
||||||
|
const AvatarStyleDefaultProps = {
|
||||||
|
borderColor: 'white',
|
||||||
|
borderWidth: '5px'
|
||||||
|
};
|
||||||
|
|
||||||
|
class Avatar extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
size: PropTypes.oneOf(['small', 'normal', 'large']),
|
||||||
|
style: PropTypes.shape(AvatarStylePropTypes),
|
||||||
|
user: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
size: 'normal'
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const supportedStyle = pickProps(this.props.style, AvatarStylePropTypes);
|
||||||
|
const style = { ...AvatarStyleDefaultProps, ...supportedStyle }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
accessibilityLabel=""
|
||||||
|
source={user.avatarUrl}
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### `Text`
|
### `Text`
|
||||||
|
|
||||||
@@ -172,6 +300,61 @@ All other props are transferred directly to the `element`.
|
|||||||
+ ViewStylePropTypes
|
+ ViewStylePropTypes
|
||||||
+ TypographicPropTypes
|
+ TypographicPropTypes
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```js
|
||||||
|
import {StylePropTypes, Text} from 'react-web-sdk';
|
||||||
|
import React, {PropTypes} from 'react';
|
||||||
|
|
||||||
|
class PrettyText extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
color: PropTypes.oneOf(['white', 'gray', 'red']),
|
||||||
|
size: PropTypes.oneOf(['small', 'normal', 'large']),
|
||||||
|
weight: PropTypes.oneOf(['light', 'normal', 'bold'])
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
color: 'gray',
|
||||||
|
size: 'normal',
|
||||||
|
weight: 'normal'
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { color, size, style, weight, ...other } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
...other
|
||||||
|
style={{
|
||||||
|
...style,
|
||||||
|
...localStyle[color],
|
||||||
|
...localStyle[size],
|
||||||
|
...localStyle[weight]
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const localStyle = {
|
||||||
|
color: {
|
||||||
|
white: { color: 'white' },
|
||||||
|
gray: { color: 'gray' },
|
||||||
|
red: { color: 'red' }
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
small: { fontSize: '0.85rem', padding: '0.5rem' },
|
||||||
|
normal: { fontSize: '1rem', padding: '0.75rem' },
|
||||||
|
large: { fontSize: '1.5rem', padding: '1rem' }
|
||||||
|
},
|
||||||
|
weight: {
|
||||||
|
light: { fontWeight: '300' },
|
||||||
|
normal: { fontWeight: '400' },
|
||||||
|
bold: { fontWeight: '700' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### `View`
|
### `View`
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {getOtherProps, omitProps, pickProps} from './lib/filterObjectProps';
|
import {getOtherProps, omitProps, pickProps} from './lib/filterObjectProps';
|
||||||
|
import StylePropTypes from './lib/StylePropTypes';
|
||||||
import Component from './lib/components/Component';
|
import Component from './lib/components/Component';
|
||||||
import Image from './lib/components/Image';
|
import Image from './lib/components/Image';
|
||||||
import Text from './lib/components/Text';
|
import Text from './lib/components/Text';
|
||||||
@@ -7,6 +8,7 @@ import View from './lib/components/View';
|
|||||||
export default {
|
export default {
|
||||||
getOtherProps,
|
getOtherProps,
|
||||||
pickProps,
|
pickProps,
|
||||||
|
StylePropTypes,
|
||||||
Component,
|
Component,
|
||||||
Image,
|
Image,
|
||||||
Text,
|
Text,
|
||||||
|
|||||||
Reference in New Issue
Block a user