Permalink
Please sign in to comment.
Browse files
Examples basic form (#59)
* initial commit * add type checking for session * add dummy session data * add register component * create design system * add nwb as dependency for web rendering * add web entry point * add button component * add strength meter component * add border-box to button styles * add prop-types package * add a few more states to sessions * add default font family to design system and components * unifying styling between sketch and web * add spacing component * layout set of components in sketch and web * add password state to web component * all textbox primitives to render child components * remove render targets from the Register component into their corresponding entry points * remove unnecessary dependencies * remove destructing of styles * remove magic numbers from text box styling * remove whitespace * add some basic instructions * add border to textbox * remove magic numbers from strength meter * fix margin bug on web version * update heading * add screenshot of example * update skpm dependency * ensure button has props * ensure strength meter has props * remove explicit importing of TextBox web and sketch components * update password checker lengths * avoid duplicate defining of register component * add proptypes * add default state to web * add props * detach strength meter from TextBox and render it directly from form * ensure props where required * improve contrast of yellow * use flexbox when rendering to sketch only * add default props to Register * fix linting issues * update readme * rename to form-validation * nwb seems to need explicit extract-text-webpack-plugin
- Loading branch information...
Showing
with
515 additions
and 0 deletions.
- BIN docs/assets/examples-basic-form.png
- +31 −0 examples/form-validation/README.md
- +30 −0 examples/form-validation/package.json
- +29 −0 examples/form-validation/src/components/Button.js
- +63 −0 examples/form-validation/src/components/Register.js
- +22 −0 examples/form-validation/src/components/Space.js
- +94 −0 examples/form-validation/src/components/StrengthMeter.js
- +52 −0 examples/form-validation/src/components/TextBox/index.js
- +20 −0 examples/form-validation/src/components/TextBox/index.sketch.js
- +25 −0 examples/form-validation/src/components/TextBox/style.js
- +22 −0 examples/form-validation/src/data.js
- +45 −0 examples/form-validation/src/designSystem.js
- +32 −0 examples/form-validation/src/main.js
- +17 −0 examples/form-validation/src/manifest.json
- +5 −0 examples/form-validation/src/types.js
- +28 −0 examples/form-validation/src/web.js
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
| @@ -0,0 +1,31 @@ | ||
| +# Form Validation | ||
| + | ||
| +## How to use | ||
| +Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): | ||
| +``` | ||
| +curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/form-validation | ||
| +cd form-validation | ||
| +``` | ||
| + | ||
| +Install the dependencies | ||
| +``` | ||
| +npm install | ||
| +``` | ||
| + | ||
| +Run with live reloading in Sketch | ||
| +``` | ||
| +npm run render | ||
| +``` | ||
| + | ||
| +Or, to install as a Sketch plugin: | ||
| +``` | ||
| +npm run build | ||
| +npm run link-plugin | ||
| +``` | ||
| +Then, open Sketch and navigate to `Plugins → react-sketchapp: Basic Form` | ||
| + | ||
| +## The idea behind the example | ||
| + | ||
| +`react-sketchapp` makes it simple to render all potential states of a web component to sketch. | ||
| + | ||
| + |
| @@ -0,0 +1,30 @@ | ||
| +{ | ||
| + "name": "form-validation", | ||
| + "private": true, | ||
| + "main": "form-validation.sketchplugin", | ||
| + "manifest": "src/manifest.json", | ||
| + "scripts": { | ||
| + "build": "skpm build", | ||
| + "watch": "skpm build --watch", | ||
| + "render": "skpm build --watch --run", | ||
| + "render:once": "skpm build --run", | ||
| + "link-plugin": "skpm link", | ||
| + "web": "react run src/web.js" | ||
| + }, | ||
| + "author": "Lloyd Wheeler <lloyd@lloydwheeler.co.uk>", | ||
| + "license": "MIT", | ||
| + "dependencies": { | ||
| + "prop-types": "^15.5.8", | ||
| + "react": "^15.4.2", | ||
| + "react-dom": "^15.4.2", | ||
| + "react-native": "^0.42.3", | ||
| + "react-primitives": "^0.3.4", | ||
| + "react-sketchapp": "^0.10.0", | ||
| + "react-test-renderer": "^15.4.1" | ||
| + }, | ||
| + "devDependencies": { | ||
| + "extract-text-webpack-plugin": "^2.1.0", | ||
| + "nwb": "^0.15.6", | ||
| + "skpm": "^0.9.0" | ||
| + } | ||
| +} |
| @@ -0,0 +1,29 @@ | ||
| +/* @flow */ | ||
| +import React from 'react'; | ||
| +import { Text } from 'react-primitives'; | ||
| +import { spacing, colors, fontFamily } from '../designSystem'; | ||
| + | ||
| +type Props = { | ||
| + label: string, | ||
| + backgroundColor: string, | ||
| +}; | ||
| + | ||
| +const buttonStyle = { | ||
| + borderRadius: 3, | ||
| + boxSizing: 'border-box', | ||
| + color: colors.White, | ||
| + cursor: 'pointer', | ||
| + fontFamily, | ||
| + fontWeight: 'bold', | ||
| + padding: spacing.Medium, | ||
| + textAlign: 'center', | ||
| + width: 300, | ||
| +}; | ||
| + | ||
| +const Button = ({ label, backgroundColor }: Props) => ( | ||
| + <Text style={{ ...buttonStyle, backgroundColor }}> | ||
| + {label} | ||
| + </Text> | ||
| +); | ||
| + | ||
| +export default Button; |
| @@ -0,0 +1,63 @@ | ||
| +/* @flow */ | ||
| +import React from 'react'; | ||
| +import { View, Text, StyleSheet } from 'react-primitives'; | ||
| +import { spacing, colors, typeRamp, fontFamily } from '../designSystem'; | ||
| +import type { Session } from '../types'; | ||
| +import TextBox from './TextBox'; | ||
| +import StrengthMeter from './StrengthMeter'; | ||
| +import Button from './Button'; | ||
| + | ||
| +type Props = { | ||
| + session: Session, | ||
| +}; | ||
| + | ||
| +const styles = StyleSheet.create({ | ||
| + register: { | ||
| + backgroundColor: colors.LightGrey, | ||
| + padding: spacing.Large, | ||
| + boxSizing: 'border-box', | ||
| + }, | ||
| + heading: { | ||
| + color: colors.Purple, | ||
| + fontSize: typeRamp.Medium, | ||
| + fontFamily, | ||
| + fontWeight: 'bold', | ||
| + textAlign: 'center', | ||
| + marginBottom: spacing.Medium, | ||
| + width: 300, | ||
| + }, | ||
| +}); | ||
| + | ||
| +const Register = ({ session }: Props) => ( | ||
| + <View style={styles.register}> | ||
| + <Text style={styles.heading}>Register an Account</Text> | ||
| + <TextBox | ||
| + label={'Email'} | ||
| + value={session.email} | ||
| + type={'email'} | ||
| + /> | ||
| + <TextBox | ||
| + label={'Password'} | ||
| + value={session.password} | ||
| + type={'password'} | ||
| + > | ||
| + <StrengthMeter | ||
| + password={session.password} | ||
| + /> | ||
| + </TextBox> | ||
| + <Button | ||
| + label={'Register'} | ||
| + backgroundColor={colors.Purple} | ||
| + /> | ||
| + </View> | ||
| +); | ||
| + | ||
| +Register.defaultProps = { | ||
| + session: { | ||
| + email: '', | ||
| + password: '', | ||
| + }, | ||
| +}; | ||
| + | ||
| +export default Register; | ||
| + |
| @@ -0,0 +1,22 @@ | ||
| +/* @flow */ | ||
| +import React from 'react'; | ||
| +import { View } from 'react-primitives'; | ||
| + | ||
| +type Props = { | ||
| + h?: number, | ||
| + v?: number, | ||
| + children?: React$Element<any>, | ||
| +}; | ||
| + | ||
| +const Space = ({ h, v, children }: Props): React$Element<any> => ( | ||
| + <View | ||
| + style={{ | ||
| + paddingHorizontal: h, | ||
| + paddingVertical: v, | ||
| + }} | ||
| + > | ||
| + {children} | ||
| + </View> | ||
| +); | ||
| + | ||
| +export default Space; |
| @@ -0,0 +1,94 @@ | ||
| +/* @flow */ | ||
| +import React from 'react'; | ||
| +import { View, Text } from 'react-primitives'; | ||
| +import { colors, fontFamily, spacing, typeRamp } from '../designSystem'; | ||
| + | ||
| +type Props = { | ||
| + password: string, | ||
| +}; | ||
| + | ||
| +const strengths = { | ||
| + short: { | ||
| + width: 75, | ||
| + label: 'Too short', | ||
| + backgroundColor: colors.Rose, | ||
| + }, | ||
| + fair: { | ||
| + width: 150, | ||
| + label: 'Fair', | ||
| + backgroundColor: colors.Yellow, | ||
| + }, | ||
| + good: { | ||
| + width: 225, | ||
| + label: 'Good', | ||
| + backgroundColor: colors.Yellow, | ||
| + }, | ||
| + strong: { | ||
| + width: 300, | ||
| + label: 'Strong', | ||
| + backgroundColor: colors.Green, | ||
| + }, | ||
| +}; | ||
| + | ||
| +const styles = { | ||
| + meter: { | ||
| + boxSizing: 'border-box', | ||
| + height: 5, | ||
| + width: 300, | ||
| + backgroundColor: '#ddd', | ||
| + marginTop: spacing.Medium, | ||
| + marginBottom: spacing.Large, | ||
| + borderRadius: 5, | ||
| + }, | ||
| + innerMeter: { | ||
| + boxSizing: 'border-box', | ||
| + height: 5, | ||
| + borderRadius: 5, | ||
| + }, | ||
| + meterLabel: { | ||
| + fontFamily, | ||
| + textAlign: 'right', | ||
| + width: 300, | ||
| + fontSize: typeRamp.Small, | ||
| + marginTop: 5, | ||
| + }, | ||
| +}; | ||
| + | ||
| +const passwordStrength = (password) => { | ||
| + // Faux password checking | ||
| + if (password.length <= 6) { | ||
| + return 'short'; | ||
| + } else if (password.length <= 9) { | ||
| + return 'fair'; | ||
| + } else if (password.length <= 12) { | ||
| + return 'good'; | ||
| + } | ||
| + | ||
| + return 'strong'; | ||
| +}; | ||
| + | ||
| +const StrengthMeter = ({ password }: Props) => ( | ||
| + <View> | ||
| + {password.length > 0 && | ||
| + <View style={styles.meter}> | ||
| + <View | ||
| + style={{ | ||
| + ...styles.innerMeter, | ||
| + width: strengths[passwordStrength(password)].width, | ||
| + backgroundColor: strengths[passwordStrength(password)].backgroundColor, | ||
| + }} | ||
| + /> | ||
| + <Text | ||
| + style={{ | ||
| + ...styles.meterLabel, | ||
| + color: strengths[passwordStrength(password)].backgroundColor, | ||
| + }} | ||
| + > | ||
| + {strengths[passwordStrength(password)].label} | ||
| + </Text> | ||
| + </View> | ||
| + } | ||
| + </View> | ||
| +); | ||
| + | ||
| +export default StrengthMeter; |
| @@ -0,0 +1,52 @@ | ||
| +/* @flow */ | ||
| +import React, { Component } from 'react'; | ||
| +import styles from './style'; | ||
| + | ||
| +class TextBox extends Component { | ||
| + | ||
| + constructor(props) { | ||
| + super(props); | ||
| + | ||
| + this.handleChange = this.handleChange.bind(this); | ||
| + | ||
| + this.state = { | ||
| + value: this.props.value, | ||
| + }; | ||
| + } | ||
| + | ||
| + props: { | ||
| + label: string, | ||
| + type: string, | ||
| + value: string, | ||
| + children?: React$Element<any>, | ||
| + }; | ||
| + | ||
| + handleChange(event) { | ||
| + this.setState({ value: event.target.value }); | ||
| + } | ||
| + | ||
| + render() { | ||
| + return ( | ||
| + <div style={styles.formElement}> | ||
| + <label | ||
| + style={styles.label} | ||
| + htmlFor={this.props.type} | ||
| + > | ||
| + {this.props.label} | ||
| + </label> | ||
| + <input | ||
| + id={this.props.type} | ||
| + style={{ ...styles.textbox, lineHeight: '100%' }} | ||
| + type={this.props.type} | ||
| + value={this.state.value} | ||
| + onChange={this.handleChange} | ||
| + /> | ||
| + {this.props.children && | ||
| + React.cloneElement(this.props.children, { password: this.state.value }) | ||
| + } | ||
| + </div> | ||
| + ); | ||
| + } | ||
| +} | ||
| + | ||
| +export default TextBox; |
| @@ -0,0 +1,20 @@ | ||
| +/* @flow */ | ||
| +import React from 'react'; | ||
| +import { View, Text } from 'react-primitives'; | ||
| +import styles from './style'; | ||
| + | ||
| +type Props = { | ||
| + label: string, | ||
| + value: string, | ||
| + children?: React$Element<any>, | ||
| +}; | ||
| + | ||
| +const TextBox = ({ label, value, children }: Props) => ( | ||
| + <View style={styles.formElement}> | ||
| + <Text style={styles.label}>{label}</Text> | ||
| + <View style={styles.textbox}>{value}</View> | ||
| + {children} | ||
| + </View> | ||
| +); | ||
| + | ||
| +export default TextBox; |
| @@ -0,0 +1,25 @@ | ||
| +import { colors, spacing, fontFamily, typeRamp } from '../../designSystem'; | ||
| + | ||
| +export default { | ||
| + formElement: { | ||
| + marginBottom: spacing.Medium, | ||
| + }, | ||
| + label: { | ||
| + display: 'block', | ||
| + fontFamily, | ||
| + marginBottom: spacing.Small, | ||
| + fontSize: typeRamp.Medium - 2, | ||
| + }, | ||
| + textbox: { | ||
| + boxSizing: 'border-box', | ||
| + borderWidth: 1, | ||
| + borderStyle: 'solid', | ||
| + borderColor: colors.Grey, | ||
| + backgroundColor: colors.White, | ||
| + fontFamily, | ||
| + fontSize: typeRamp.Medium, | ||
| + lineHeight: typeRamp.Medium, | ||
| + padding: spacing.Medium, | ||
| + width: 300, | ||
| + }, | ||
| +}; |
| @@ -0,0 +1,22 @@ | ||
| +export default [ | ||
| + { | ||
| + email: 'john.hornsby@example.com', | ||
| + password: '', | ||
| + }, | ||
| + { | ||
| + email: 'john.hornsby@example.com', | ||
| + password: 'hello', | ||
| + }, | ||
| + { | ||
| + email: 'john.hornsby@example.com', | ||
| + password: '!H3ll0!', | ||
| + }, | ||
| + { | ||
| + email: 'john.hornsby@example.com', | ||
| + password: 'IL0v3ToasT!', | ||
| + }, | ||
| + { | ||
| + email: 'john.hornsby@example.com', | ||
| + password: 'IRea11yL0v3ToasT!', | ||
| + }, | ||
| +]; |
Oops, something went wrong.
0 comments on commit
c46abc0