├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── src ├── fields.js ├── forms.jsx ├── index.js ├── tags.jsx └── utils.js ├── webpack.config.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "prettier/prettier": ["error", { "trailingComma": "es5", "semi": false }], 5 | "react/jsx-uses-vars": "error", 6 | "react/jsx-uses-react": "error", 7 | }, 8 | "plugins": [ 9 | "prettier", 10 | "react", 11 | ], 12 | "extends": [ 13 | "eslint:recommended", 14 | "prettier", 15 | "prettier/react", 16 | ], 17 | "globals": { 18 | "document": true, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | lib 3 | node_modules 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Dan Ott 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Rails Form Helpers 2 | 3 | ## Installation 4 | 5 | Using via npm 6 | 7 | ``` 8 | npm install --save react-rails-form-helpers 9 | ``` 10 | 11 | Usage via vendoring 12 | 13 | You can grab the latest UMD build from https://unpkg.com/react-rails-form-helpers@latest/dist/react-rails-form-helpers.js 14 | 15 | ## About 16 | 17 | This package provides components for composing a form targeted at Rails. 18 | The main intent of this package is for communicating the intent of the form via named components. 19 | If you're scared by *Rails Magic* you'll be happy to know that these components are mostly dumb. 20 | The only magical parts are generating the attribute names and ensuring that the fields Rails expects exist on a form. 21 | 22 | There are two varieties of components. 23 | 24 | ### "FieldTag" components 25 | 26 | The components with a `Tag` suffix are not magical at all. 27 | They exist to mirror the [Rails form helpers][] for expressing the intent. 28 | They are tiny wrappers around the built in ReactDOM `` and ` 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | Remove from order 119 | 120 | )} 121 | 122 | })} 123 | 124 | 125 | Add a burger 126 | 127 | 128 | 203 | 204 | 205 | 206 | ``` 207 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-rails-form-helpers", 3 | "version": "0.1.0-beta.19", 4 | "description": "Build forms that will be submitted to Rails in React.", 5 | "main": "lib/index.js", 6 | "repository": "danott/react-rails-form-helpers", 7 | "keywords": [ 8 | "react", 9 | "rails", 10 | "forms" 11 | ], 12 | "scripts": { 13 | "build": "npm run build:lib && npm run build:dist", 14 | "build:dist": "NODE_ENV=production webpack", 15 | "build:lib": "babel ./src --out-dir ./lib --stage 0", 16 | "dev:dist": "NODE_ENV=production webpack --watch", 17 | "dev:lib": "babel ./src --out-dir ./lib --stage 0 --watch", 18 | "test": "echo \"Error: no test specified\" && exit 1", 19 | "eslint-check": "eslint --print-config .eslintrc.js | eslint-config-prettier-check", 20 | "lint": "eslint --ext=js,jsx --fix src", 21 | "prepublish": "npm run build" 22 | }, 23 | "files": [ 24 | "dist", 25 | "lib", 26 | "src" 27 | ], 28 | "author": "Dan Ott (https://danott.co)", 29 | "license": "MIT", 30 | "devDependencies": { 31 | "babel": "^6.5.2", 32 | "babel-cli": "^6.7.7", 33 | "babel-eslint": "^7.2.3", 34 | "babel-loader": "^6.2.4", 35 | "babel-plugin-transform-object-assign": "^6.8.0", 36 | "babel-preset-es2015": "^6.6.0", 37 | "babel-preset-react": "^6.5.0", 38 | "babel-preset-stage-2": "^6.5.0", 39 | "eslint": "^4.5.0", 40 | "eslint-config-rackt": "^1.1.1", 41 | "eslint-plugin-prettier": "^2.2.0", 42 | "eslint-plugin-react": "^7.3.0", 43 | "prettier": "^1.6.1", 44 | "react": "^15.0.1", 45 | "webpack": "^1.13.0" 46 | }, 47 | "babel": { 48 | "presets": [ 49 | "es2015", 50 | "react", 51 | "stage-2" 52 | ], 53 | "plugins": [ 54 | "transform-object-assign" 55 | ] 56 | }, 57 | "dependencies": { 58 | "eslint-config-prettier": "^2.3.0", 59 | "prop-types": "^15.5.10" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/fields.js: -------------------------------------------------------------------------------- 1 | import * as Tags from "./tags" 2 | import { nameWithContext } from "./utils" 3 | 4 | export const CheckBox = nameWithContext(Tags.CheckBoxTag) 5 | export const ColorField = nameWithContext(Tags.ColorFieldTag) 6 | export const DateField = nameWithContext(Tags.DateFieldTag) 7 | export const DatetimeField = nameWithContext(Tags.DatetimeFieldTag) 8 | export const DatetimeLocalField = nameWithContext(Tags.DatetimeLocalFieldTag) 9 | export const EmailField = nameWithContext(Tags.EmailFieldTag) 10 | export const HiddenField = nameWithContext(Tags.HiddenFieldTag) 11 | export const Label = nameWithContext(Tags.LabelTag, "htmlFor") 12 | export const MonthField = nameWithContext(Tags.MonthFieldTag) 13 | export const NumberField = nameWithContext(Tags.NumberFieldTag) 14 | export const PasswordField = nameWithContext(Tags.PasswordFieldTag) 15 | export const Radio = nameWithContext(Tags.RadioTag) 16 | export const RangeField = nameWithContext(Tags.RangeFieldTag) 17 | export const SearchField = nameWithContext(Tags.SearchFieldTag) 18 | export const Select = nameWithContext(Tags.SelectTag) 19 | export const Submit = nameWithContext(Tags.SubmitTag) 20 | export const TelephoneField = nameWithContext(Tags.TelephoneFieldTag) 21 | export const TextArea = nameWithContext(Tags.TextAreaTag) 22 | export const TextField = nameWithContext(Tags.TextFieldTag) 23 | export const TimeField = nameWithContext(Tags.TimeFieldTag) 24 | export const UrlField = nameWithContext(Tags.UrlFieldTag) 25 | export const WeekField = nameWithContext(Tags.WeekFieldTag) 26 | export const DestroyField = () => 27 | -------------------------------------------------------------------------------- /src/forms.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import PropTypes from "prop-types" 3 | import { HiddenFieldTag } from "./tags" 4 | import { whitelistProps } from "./utils" 5 | 6 | export class FormTag extends React.Component { 7 | static propTypes = { 8 | url: PropTypes.string.isRequired, 9 | method: PropTypes.oneOf(["get", "post", "put", "patch", "delete"]), 10 | csrfToken: PropTypes.string, 11 | children: PropTypes.node, 12 | } 13 | 14 | static defaultProps = { 15 | method: "post", 16 | } 17 | 18 | render() { 19 | let browserHTTPMethod = "post" 20 | let fakedHTTPMethod = null 21 | 22 | if (this.props.method === "get") { 23 | browserHTTPMethod = "get" 24 | } else if (this.props.method !== "post") { 25 | fakedHTTPMethod = this.props.method 26 | } 27 | 28 | const csrfToken = 29 | this.props.csrfToken || 30 | document.querySelector("head meta[name='csrf-token']").content 31 | 32 | return ( 33 |
39 | {fakedHTTPMethod && ( 40 | 41 | )} 42 | {csrfToken && ( 43 | 44 | )} 45 | 46 | {this.props.children} 47 | 48 | ) 49 | } 50 | } 51 | 52 | export class FormFor extends React.Component { 53 | static propTypes = { 54 | name: PropTypes.string, 55 | } 56 | 57 | static defaultProps = { 58 | name: null, 59 | } 60 | 61 | static childContextTypes = { 62 | railsFormNamespaces: PropTypes.arrayOf(PropTypes.string), 63 | } 64 | 65 | getChildContext() { 66 | return { 67 | railsFormNamespaces: this.props.name ? [this.props.name] : [], 68 | } 69 | } 70 | 71 | render() { 72 | return {this.props.children} 73 | } 74 | } 75 | 76 | export class FieldsFor extends React.Component { 77 | static propTypes = { 78 | name: PropTypes.string.isRequired, 79 | } 80 | 81 | static contextTypes = { 82 | railsFormNamespaces: PropTypes.arrayOf(PropTypes.string), 83 | } 84 | 85 | static childContextTypes = { 86 | railsFormNamespaces: PropTypes.arrayOf(PropTypes.string), 87 | } 88 | 89 | static defaultProps = { 90 | name: "", 91 | } 92 | 93 | getChildContext() { 94 | return { 95 | railsFormNamespaces: [ 96 | ...(this.context.railsFormNamespaces || []), 97 | this.props.name, 98 | ], 99 | } 100 | } 101 | 102 | render() { 103 | return {this.props.children} 104 | } 105 | } 106 | 107 | export const ArrayFields = FieldsFor 108 | export const HashFields = FieldsFor 109 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export * from "./forms" 2 | export * from "./fields" 3 | export * from "./tags" 4 | -------------------------------------------------------------------------------- /src/tags.jsx: -------------------------------------------------------------------------------- 1 | import { whitelistProps } from "./utils" 2 | import React from "react" 3 | 4 | export const CheckBoxTag = ({ 5 | checkedValue = 1, 6 | uncheckedValue = 0, 7 | ...props 8 | }) => { 9 | return ( 10 | 11 | 12 | 13 | 14 | ) 15 | } 16 | 17 | export const ColorFieldTag = props => 18 | 19 | export const DateFieldTag = props => 20 | 21 | export const DatetimeFieldTag = props => 22 | 23 | export const DatetimeLocalFieldTag = props => ( 24 | 25 | ) 26 | 27 | export const EmailFieldTag = props => 28 | 29 | export const HiddenFieldTag = props => ( 30 | 31 | ) 32 | 33 | export const LabelTag = props =>