├── .gitignore ├── .npmignore ├── .babelrc ├── docs ├── entry.js ├── index.html └── App.js ├── webpack.config.js ├── README.md ├── package.json └── src └── CSSEditor.js /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "stage-0", 5 | "react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /docs/entry.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { render } = require('react-dom') 3 | const App = require('./App') 4 | 5 | render(, app) 6 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | const webpack = require('webpack') 3 | const path = require('path') 4 | 5 | module.exports = { 6 | entry: './docs/entry.js', 7 | 8 | output: { 9 | path: path.join(__dirname, 'docs'), 10 | filename: 'bundle.js' 11 | }, 12 | 13 | node: { 14 | fs: 'empty' 15 | }, 16 | 17 | resolve: { 18 | alias: { 19 | 'react-css-editor': path.join(__dirname, 'src/CSSEditor') 20 | } 21 | }, 22 | 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.js$/, 27 | exclude: /node_modules/, 28 | use: 'babel-loader' 29 | } 30 | ] 31 | }, 32 | 33 | plugins: [ 34 | new webpack.DefinePlugin({ 35 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 36 | }) 37 | ], 38 | 39 | devServer: { 40 | contentBase: 'docs/', 41 | historyApiFallback: true 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # React CSS Editor 3 | 4 | React textarea component for editing style objects as CSS strings 5 | 6 | Demo: http://jxnblk.com/react-css-editor 7 | 8 | ```sh 9 | npm i react-css-editor 10 | ``` 11 | 12 | ```jsx 13 | import React from 'react' 14 | import CSSEditor from 'react-css-editor' 15 | 16 | class App extends React.Component { 17 | state = { 18 | style: { 19 | color: 'tomato' 20 | padding: 16 21 | } 22 | } 23 | 24 | update = fn => { 25 | this.setState(fn) 26 | } 27 | 28 | render () { 29 | const { style } = this.state 30 | 31 | return ( 32 |
33 | { 36 | this.update(state => ({ style: val })) 37 | }) 38 | /> 39 |
41 | ) 42 | } 43 | } 44 | ``` 45 | 46 | MIT License 47 | 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-css-editor", 3 | "version": "1.0.1", 4 | "description": "React textarea component for editing style objects as CSS strings", 5 | "main": "dist/CSSEditor.js", 6 | "scripts": { 7 | "dev": "webpack-dev-server", 8 | "docs": "NODE_ENV=production webpack", 9 | "prepublish": "babel src --out-dir dist", 10 | "test": "ava" 11 | }, 12 | "keywords": [], 13 | "author": "Brent Jackson", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "ava": "^0.19.1", 17 | "axs": "^1.0.6", 18 | "babel-cli": "^6.24.1", 19 | "babel-core": "^6.24.1", 20 | "babel-loader": "^7.0.0", 21 | "babel-preset-env": "^1.4.0", 22 | "babel-preset-react": "^6.24.1", 23 | "babel-preset-stage-0": "^6.24.1", 24 | "babel-register": "^6.24.1", 25 | "react": "^15.5.4", 26 | "react-dom": "^15.5.4", 27 | "react-test-renderer": "^15.5.4", 28 | "webpack": "^2.5.0", 29 | "webpack-dev-server": "^2.4.5" 30 | }, 31 | "dependencies": { 32 | "css-to-object": "^1.0.0", 33 | "objss": "^1.0.2", 34 | "prop-types": "^15.5.8" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/CSSEditor.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const PropTypes = require('prop-types') 3 | const objss = require('objss') 4 | const cssobj = require('css-to-object') 5 | 6 | class CSSEditor extends React.Component { 7 | state = { 8 | css: objss(this.props.value, { 9 | newline: true 10 | }) 11 | } 12 | 13 | onChange = e => { 14 | const { value } = e.target 15 | this.setState({ css: value }) 16 | try { 17 | const obj = cssobj(value) 18 | console.log('obj', obj) 19 | this.props.onChange(obj) 20 | } catch (e) { 21 | console.error(e) 22 | } 23 | } 24 | 25 | onKeyDown = e => { 26 | if (e.key === 'Tab') { 27 | e.preventDefault() 28 | const { css } = this.state 29 | const { selectionStart, selectionEnd } = this.root 30 | const next = css.slice(0, selectionStart) + ' ' + css.slice(selectionEnd) 31 | const position = selectionStart + 2 32 | this.setState({ css: next }, () => { 33 | this.root.selectionStart = position 34 | this.root.selectionEnd = position 35 | }) 36 | } 37 | } 38 | 39 | render () { 40 | const { 41 | ...rest 42 | } = this.props 43 | const { css } = this.state 44 | 45 | return ( 46 |