├── .babelrc ├── .gitignore ├── LICENSE.md ├── README.md ├── demo.gif ├── dev-server.js ├── keymaps └── atom-react-preview.cson ├── lib ├── atom-react-preview-view.coffee ├── atom-react-preview.coffee ├── bootstrap.js ├── compiler.js ├── find_component_proptypes.js └── renderer.js ├── menus └── atom-react-preview.cson ├── package.json ├── public └── index.html ├── styles └── atom-react-preview.less ├── test-styles.js ├── test-styles.scss └── test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "stage-0", "es2015"], 3 | "env": { 4 | "development": { 5 | "plugins": [ 6 | ["react-transform", { 7 | "transforms": [{ 8 | "transform": "react-transform-hmr", 9 | "imports": ["react"], 10 | "locals": ["module"] 11 | }] 12 | }] 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Victor Bjelkholm 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Atom React Preview - [Github](https://github.com/VictorBjelkholm/atom-react-preview) - [Atom.io](https://atom.io/packages/atom-react-preview) 2 | 3 | [![deprecated](http://badges.github.io/stability-badges/dist/deprecated.svg)](http://github.com/badges/stability-badges) 4 | 5 | Preview your React components directly in Atom. Works best for stateless components. 6 | 7 | ![Atom React Preview](demo.gif) 8 | 9 | ## Features: 10 | 11 | * Reload your component on save 12 | * Editing the props in the preview area 13 | * Saving the edits for when you reload your component again 14 | * Supports LESS currently (feel free to submit PR with LESS/PostCSS support) 15 | 16 | Like hot reloading, but within Atom! 17 | 18 | ## Known issues: 19 | 20 | * The codebase is a mess, based on atom-html-preview and only got time for minor refactoring atm, will get to that ([Issue #4](https://github.com/VictorBjelkholm/atom-react-preview/issues/4)) 21 | 22 | ## Install: 23 | 24 | ### apm 25 | 26 | ```bash 27 | apm install atom-react-preview 28 | ``` 29 | 30 | ### Inside Atom: 31 | 32 | Go to "Install Packages" and search for atom-react-preview 33 | 34 | ## Toggle React Preview ## 35 | 36 | Press `CTRL-SHIFT-M` in the editor to open the preview pane. 37 | 38 | ## Thanks 39 | 40 | Thanks to https://github.com/webBoxio/atom-html-preview which 90% of the code is coming from. It got me to understand how Atom packages works and served as a base for this package. Thanks @webBoxio! 41 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorb/atom-react-preview/f321ed10d004a1186b847e98572c619fc985d106/demo.gif -------------------------------------------------------------------------------- /dev-server.js: -------------------------------------------------------------------------------- 1 | // Dev-server that is accepts component to wrap as the third argument. 2 | // Starts a webpack dev server on port 3000 3 | var component_to_render = process.argv[2] 4 | if (component_to_render === undefined) { 5 | throw new Error('You need to specify which component to render!') 6 | } 7 | var WebpackDevServer = require('webpack-dev-server') 8 | var compiler = require('./lib/compiler.js')(component_to_render) 9 | 10 | var port = process.env.PORT || 3000 11 | 12 | new WebpackDevServer(compiler, { 13 | contentBase: __dirname + '/public', 14 | publicPath: '/', 15 | hot: true, 16 | historyApiFallback: true, 17 | stats: { 18 | colors: true 19 | } 20 | }).listen(port, '0.0.0.0', function (err) { 21 | if (err) { 22 | console.log(err) 23 | } 24 | console.log(`Listening at http://localhost:${port}`) 25 | }) 26 | -------------------------------------------------------------------------------- /keymaps/atom-react-preview.cson: -------------------------------------------------------------------------------- 1 | 'atom-text-editor[data-grammar~=js]': 2 | 'ctrl-shift-m': 'atom-react-preview:toggle' 3 | -------------------------------------------------------------------------------- /lib/atom-react-preview-view.coffee: -------------------------------------------------------------------------------- 1 | # TODO Fix this file to be A LOT simpler. There is things here that should be nowhere really 2 | fs = require 'fs' 3 | fse = require 'fs-extra' 4 | {CompositeDisposable, Disposable} = require 'atom' 5 | {$, $$$, ScrollView} = require 'atom-space-pen-views' 6 | path = require 'path' 7 | os = require 'os' 8 | child_process = require 'child_process' 9 | spawn = child_process.spawn 10 | 11 | did_bootstrap = false 12 | 13 | module.exports = 14 | class AtomReactPreviewView extends ScrollView 15 | atom.deserializers.add(this) 16 | 17 | editorSub : null 18 | onDidChangeTitle : -> new Disposable() 19 | # onDidChangeModified : -> new Disposable() 20 | componentState : {} 21 | webpackProcess: null 22 | 23 | @deserialize: (state) -> 24 | new AtomReactPreviewView(state) 25 | 26 | @content: -> 27 | @div class: 'atom-react-preview native-key-bindings', tabindex: -1 28 | 29 | constructor: ({@editorId, filePath}) -> 30 | super 31 | 32 | if @editorId? 33 | @resolveEditor(@editorId) 34 | @tmpPath = @getPath() # after resolveEditor 35 | else 36 | if atom.workspace? 37 | @subscribeToFilePath(filePath) 38 | else 39 | atom.packages.onDidActivatePackage => 40 | @subscribeToFilePath(filePath) 41 | 42 | serialize: -> 43 | deserializer : 'AtomReactPreviewView' 44 | filePath : @getPath() 45 | editorId : @editorId 46 | 47 | destroy: -> 48 | @editorSub.dispose() 49 | @webpackProcess.kill 'SIGQUIT' 50 | did_bootstrap = false 51 | 52 | resolveEditor: (editorId) -> 53 | resolve = => 54 | @editor = @editorForId(editorId) 55 | 56 | if @editor? 57 | @trigger 'title-changed' if @editor? 58 | @handleEvents() 59 | else 60 | # The editor this preview was created for has been closed so close 61 | # this preview since a preview cannot be rendered without an editor 62 | atom.workspace?.paneForItem(this)?.destroyItem(this) 63 | 64 | if atom.workspace? 65 | resolve() 66 | else 67 | atom.packages.onDidActivatePackage => 68 | resolve() 69 | @renderHTML() 70 | 71 | editorForId: (editorId) -> 72 | for editor in atom.workspace.getTextEditors() 73 | return editor if editor.id?.toString() is editorId.toString() 74 | null 75 | 76 | handleEvents: => 77 | 78 | @editorSub = new CompositeDisposable 79 | 80 | if @editor? 81 | @editorSub.add @editor.onDidChangePath => @trigger 'title-changed' 82 | 83 | renderHTML: -> 84 | @showLoading() 85 | if @editor? 86 | @renderHTMLCode() 87 | 88 | updateComponents: (new_props) -> 89 | @componentState = new_props 90 | @renderHTMLCode() 91 | 92 | renderHTMLCode: () -> 93 | path = @editor.getPath() 94 | @webpackProcess = spawn('node', ['dev-server.js', path]) 95 | @webpackProcess.on('close', (code) => 96 | console.log('webpack child process exited with code ' + code) 97 | ) 98 | @webpackProcess.stdout.on('data', (data) => 99 | # console.log(data.toString()) 100 | if data.toString().indexOf('bundle is now VALID.') != -1 101 | if did_bootstrap is false 102 | did_bootstrap = true 103 | iframe = document.createElement("iframe") 104 | iframe.setAttribute("src", "http://localhost:3000") 105 | @html $ iframe 106 | ) 107 | 108 | getTitle: -> 109 | if @editor? 110 | "#{@editor.getTitle()} Preview" 111 | else 112 | "React Preview" 113 | 114 | getURI: -> 115 | "react-preview://editor/#{@editorId}" 116 | 117 | getPath: -> 118 | if @editor? 119 | @editor.getPath() 120 | 121 | showError: (result) -> 122 | failureMessage = result?.message 123 | 124 | @html $$$ -> 125 | @h2 'Previewing React Component Failed' 126 | @h3 failureMessage if failureMessage? 127 | 128 | showLoading: -> 129 | @html $$$ -> 130 | @div class: 'atom-react-spinner', 'Loading React Preview\u2026' 131 | -------------------------------------------------------------------------------- /lib/atom-react-preview.coffee: -------------------------------------------------------------------------------- 1 | # TODO Fix this file to be A LOT simpler. There is things here that should be nowhere really 2 | url = require 'url' 3 | {CompositeDisposable} = require 'atom' 4 | 5 | ReactPreviewView = require './atom-react-preview-view' 6 | 7 | module.exports = 8 | config: 9 | triggerOnSave: 10 | type : 'boolean' 11 | description : 'Watch will trigger on save.' 12 | default : false 13 | 14 | reactPreviewView: null 15 | 16 | activate: (state) -> 17 | # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable 18 | @subscriptions = new CompositeDisposable 19 | 20 | # Register command that toggles this view 21 | @subscriptions.add atom.commands.add 'atom-workspace', 'atom-react-preview:toggle': => @toggle() 22 | 23 | atom.workspace.addOpener (uriToOpen) -> 24 | try 25 | {protocol, host, pathname} = url.parse(uriToOpen) 26 | catch error 27 | return 28 | 29 | return unless protocol is 'react-preview:' 30 | 31 | try 32 | pathname = decodeURI(pathname) if pathname 33 | catch error 34 | return 35 | 36 | console.log('Opener') 37 | if host is 'editor' 38 | new ReactPreviewView(editorId: pathname.substring(1)) 39 | else 40 | new ReactPreviewView(filePath: pathname) 41 | 42 | toggle: -> 43 | editor = atom.workspace.getActiveTextEditor() 44 | return unless editor? 45 | 46 | uri = "react-preview://editor/#{editor.id}" 47 | 48 | previewPane = atom.workspace.paneForURI(uri) 49 | if previewPane 50 | previewPane.destroyItem(previewPane.itemForURI(uri)) 51 | return 52 | 53 | previousActivePane = atom.workspace.getActivePane() 54 | atom.workspace.open(uri, split: 'right', searchAllPanes: true).done (reactPreviewView) -> 55 | if reactPreviewView instanceof ReactPreviewView 56 | reactPreviewView.renderHTML() 57 | previousActivePane.activate() 58 | 59 | deactivate: -> 60 | @subscriptions.dispose() 61 | -------------------------------------------------------------------------------- /lib/bootstrap.js: -------------------------------------------------------------------------------- 1 | /* global __COMPONENT_TO_RENDER */ 2 | // This guy is responsible for settings up the application renderer with the props 3 | import ReactDOM from 'react-dom' 4 | import React from 'react' 5 | 6 | import Renderer from './renderer' 7 | 8 | var ComponentToRender = require(__COMPONENT_TO_RENDER) 9 | 10 | if (ComponentToRender.default !== undefined) { 11 | ComponentToRender = ComponentToRender.default 12 | } 13 | 14 | const render = (props) => { 15 | ReactDOM.render( { 18 | render(new_props) 19 | }} 20 | component={ComponentToRender} 21 | />, document.getElementById('root')) 22 | } 23 | 24 | document.addEventListener('DOMContentLoaded', () => { 25 | render({}) 26 | }) 27 | -------------------------------------------------------------------------------- /lib/compiler.js: -------------------------------------------------------------------------------- 1 | // This file contains the webpack config and the component to render. 2 | // TODO figure out a way to load 'loaders' from projects webpack config 3 | var webpack = require('webpack') 4 | var path = require('path') 5 | 6 | module.exports = function (component_to_render) { 7 | return webpack({ 8 | devtool: 'eval', 9 | entry: [ 10 | 'webpack-dev-server/client?http://0.0.0.0:3000', 11 | 'webpack/hot/only-dev-server', 12 | './bootstrap.js' 13 | ], 14 | context: __dirname, 15 | output: { 16 | path: __dirname + '../public', 17 | filename: 'bundle.js', 18 | publicPath: '/' 19 | }, 20 | plugins: [ 21 | new webpack.HotModuleReplacementPlugin(), 22 | new webpack.DefinePlugin({ 23 | __COMPONENT_TO_RENDER: JSON.stringify(component_to_render) 24 | }) 25 | ], 26 | module: { 27 | loaders: [ 28 | { 29 | test: /\.js$/, 30 | loaders: ['react-hot', 'babel-loader'], 31 | exclude: /node_modules/, 32 | include: path.join(__dirname, '../') 33 | }, 34 | { 35 | test: /\.scss$/, 36 | loader: 'style!css!sass' 37 | } 38 | ] 39 | } 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /lib/find_component_proptypes.js: -------------------------------------------------------------------------------- 1 | // This handy utility figures out the default values and prop types for a React 2 | // component 3 | 4 | // TODO Make less messy and start testing 5 | var React = require('react') 6 | 7 | const get_default_value = (props, key, default_val) => { 8 | if (props !== undefined) { 9 | return props[key] 10 | } 11 | return default_val 12 | } 13 | 14 | module.exports = (component, default_values) => { 15 | var mapping = {} 16 | const defaultProps = component.defaultProps 17 | for (var key in component.propTypes) { 18 | switch (component.propTypes[key]) { 19 | case React.PropTypes.array: 20 | mapping[key] = { type: 'array', required: false } 21 | mapping[key].defaultValue = get_default_value(defaultProps, key, []) 22 | break 23 | case React.PropTypes.array.isRequired: 24 | mapping[key] = { type: 'array', required: true } 25 | mapping[key].defaultValue = get_default_value(defaultProps, key, []) 26 | break 27 | case React.PropTypes.bool: 28 | mapping[key] = { type: 'bool', required: false } 29 | mapping[key].defaultValue = get_default_value(defaultProps, key, false) 30 | break 31 | case React.PropTypes.bool.isRequired: 32 | mapping[key] = { type: 'bool', required: true } 33 | mapping[key].defaultValue = get_default_value(defaultProps, key, false) 34 | break 35 | case React.PropTypes.func: 36 | mapping[key] = { type: 'func', required: false } 37 | mapping[key].defaultValue = get_default_value(defaultProps, key, () => {}) 38 | break 39 | case React.PropTypes.func.isRequired: 40 | mapping[key] = { type: 'func', required: true } 41 | mapping[key].defaultValue = get_default_value(defaultProps, key, () => {}) 42 | break 43 | case React.PropTypes.number: 44 | mapping[key] = { type: 'number', required: false } 45 | mapping[key].defaultValue = get_default_value(defaultProps, key, 0) 46 | break 47 | case React.PropTypes.number.isRequired: 48 | mapping[key] = { type: 'number', required: true } 49 | mapping[key].defaultValue = get_default_value(defaultProps, key, 0) 50 | break 51 | case React.PropTypes.object: 52 | mapping[key] = { type: 'object', required: false } 53 | mapping[key].defaultValue = get_default_value(defaultProps, key, {}) 54 | break 55 | case React.PropTypes.object.isRequired: 56 | mapping[key] = { type: 'object', required: true } 57 | mapping[key].defaultValue = get_default_value(defaultProps, key, {}) 58 | break 59 | case React.PropTypes.string: 60 | mapping[key] = { type: 'string', required: false } 61 | mapping[key].defaultValue = get_default_value(defaultProps, key, 'World') 62 | break 63 | case React.PropTypes.string.isRequired: 64 | mapping[key] = { type: 'string', required: true } 65 | mapping[key].defaultValue = get_default_value(defaultProps, key, 'World') 66 | break 67 | // TODO figure out how to handles element and nodes types 68 | // case React.PropTypes.element: 69 | // mapping[key] = { type: 'element', required: false } 70 | // mapping[key].defaultValue = get_default_value(defaultProps, key) 71 | // break 72 | // case React.PropTypes.element.isRequired: 73 | // mapping[key] = { type: 'element', required: true } 74 | // mapping[key].defaultValue = get_default_value(defaultProps, key) 75 | // break 76 | default: 77 | mapping[key] = 'unknown' 78 | mapping[key].defaultValue = 'unknown' 79 | break 80 | } 81 | if (default_values[key] !== undefined) { 82 | mapping[key].defaultValue = default_values[key] 83 | } 84 | } 85 | return mapping 86 | } 87 | -------------------------------------------------------------------------------- /lib/renderer.js: -------------------------------------------------------------------------------- 1 | // Here be dragons 2 | // This file contains logic for the PropEditor and the Renderer itself 3 | // TODO split this into many smaller files and refactor a bit. 4 | import React, { Component, PropTypes } from 'react' 5 | import getPropTypes from './find_component_proptypes' 6 | 7 | const prop_style = { 8 | padding: '5px', 9 | backgroundColor: '#343d46', 10 | margin: '5px', 11 | width: '130px', 12 | color: 'white', 13 | fontFamily: 'Georgia', 14 | float: 'left', 15 | borderLeft: '5px solid #e67e22' 16 | } 17 | 18 | const prop_type_style = { 19 | marginTop: '5px', 20 | opacity: '0.7' 21 | } 22 | 23 | class StringProps extends Component { 24 | render () { 25 | const required_render = this.props.isRequired ? '*' : '' 26 | return
27 | {this.props.label}{required_render}
28 | { 32 | this.props.onChange(this.props.label, ev.target.value) 33 | }} 34 | onKeyUp={(ev) => { 35 | if (ev.keyCode === 13) { 36 | this.props.onChange(this.props.label, ev.target.value, true) 37 | } 38 | }} 39 | > 40 |
(string)
41 |
42 | } 43 | } 44 | StringProps.propTypes = { 45 | label: PropTypes.string.isRequired, 46 | defaultValue: PropTypes.string.isRequired, 47 | isRequired: PropTypes.bool.isRequired, 48 | onChange: PropTypes.func.isRequired 49 | } 50 | class ArrayProps extends Component { 51 | render () { 52 | const required_render = this.props.isRequired ? '*' : '' 53 | return
54 | {this.props.label}{required_render}
55 | { 59 | try { 60 | this.props.onChange(this.props.label, JSON.parse(ev.target.value)) 61 | } catch (err) { 62 | console.log('Couldnt parse ev.target.value') 63 | } 64 | }} 65 | onKeyUp={(ev) => { 66 | if (ev.keyCode === 13) { 67 | try { 68 | this.props.onChange(this.props.label, JSON.parse(ev.target.value), true) 69 | } catch (err) { 70 | console.log('Couldnt parse ev.target.value') 71 | } 72 | } 73 | }} 74 | > 75 |
(array)
76 |
77 | } 78 | } 79 | ArrayProps.propTypes = { 80 | label: PropTypes.string.isRequired, 81 | defaultValue: PropTypes.array.isRequired, 82 | isRequired: PropTypes.bool.isRequired, 83 | onChange: PropTypes.func.isRequired 84 | } 85 | class NumberProps extends Component { 86 | render () { 87 | const required_render = this.props.isRequired ? '*' : '' 88 | return
89 | {this.props.label}{required_render}
90 | { 94 | this.props.onChange(this.props.label, parseInt(ev.target.value, 10)) 95 | }} 96 | onKeyUp={(ev) => { 97 | if (ev.keyCode === 13) { 98 | this.props.onChange(this.props.label, parseInt(ev.target.value, 10), true) 99 | } 100 | }} 101 | > 102 |
(integer)
103 |
104 | } 105 | } 106 | NumberProps.propTypes = { 107 | label: PropTypes.string.isRequired, 108 | defaultValue: PropTypes.number.isRequired, 109 | isRequired: PropTypes.bool.isRequired, 110 | onChange: PropTypes.func.isRequired 111 | } 112 | class BoolProps extends Component { 113 | render () { 114 | const required_render = this.props.isRequired ? '*' : '' 115 | return
116 | {this.props.label}{required_render}
117 | { 121 | this.props.onChange(this.props.label, ev.target.checked, true) 122 | }} 123 | > 124 |
(boolean)
125 |
126 | } 127 | } 128 | BoolProps.propTypes = { 129 | label: PropTypes.string.isRequired, 130 | defaultValue: PropTypes.bool.isRequired, 131 | isRequired: PropTypes.bool.isRequired, 132 | onChange: PropTypes.func.isRequired 133 | } 134 | 135 | const props_map = { 136 | 'string': StringProps, 137 | 'array': ArrayProps, 138 | 'number': NumberProps, 139 | 'bool': BoolProps 140 | } 141 | 142 | class PropsEditor extends Component { 143 | render () { 144 | const props_to_edit = Object.keys(this.props.props).map((key) => { 145 | const defVal = this.props.props[key].defaultValue 146 | const required = this.props.props[key].required 147 | const type = this.props.props[key].type 148 | const type_props = props_map[type] 149 | return React.createElement(type_props, { 150 | key: key, 151 | label: key, 152 | isRequired: required, 153 | defaultValue: defVal, 154 | onChange: this.props.onChange 155 | }) 156 | }) 157 | const style = { 158 | padding: '10px' 159 | } 160 | return
161 | {props_to_edit} 162 |
163 | } 164 | } 165 | PropsEditor.propTypes = { 166 | props: PropTypes.object.isRequired, 167 | onChange: PropTypes.func.isRequired 168 | } 169 | 170 | export default class Renderer extends Component { 171 | constructor (props) { 172 | super(props) 173 | this.state = this.props.component_props 174 | this.state.hovering_button = false 175 | } 176 | handleOnChange (label, value, should_trigger_update) { 177 | this.setState({ 178 | [label]: value 179 | }, () => { 180 | if (should_trigger_update) { 181 | this.props.dispatch(this.state) 182 | } 183 | }) 184 | } 185 | handleOnHover () { 186 | this.setState({ 187 | hovering_button: !this.state.hovering_button 188 | }) 189 | } 190 | render () { 191 | const component_to_render = React.createElement(this.props.component, this.props.component_props) 192 | const children_props = getPropTypes(component_to_render.type, this.props.component_props) 193 | const render_style = { 194 | position: 'absolute', 195 | top: 0, 196 | right: 0, 197 | bottom: 0, 198 | left: 0, 199 | overflow: 'hidden' 200 | } 201 | const title_style = { 202 | fontFamily: 'Verdana', 203 | backgroundColor: '#343D46', 204 | color: 'white', 205 | padding: '10px', 206 | position: 'fixed', 207 | zIndex: '100', 208 | borderLeft: '10px solid #A3BE8C', 209 | width: '100%' 210 | } 211 | const component_style = { 212 | padding: '10px', 213 | maxHeight: '54vh', 214 | overflowY: 'scroll' 215 | } 216 | const props_editor_style = { 217 | bottom: 0, 218 | left: 0, 219 | height: '200px', 220 | overflowY: 'scroll', 221 | position: 'absolute', 222 | right: 0, 223 | backgroundColor: 'white' 224 | } 225 | const right_title_style = { 226 | float: 'right', 227 | marginRight: '50px' 228 | } 229 | 230 | const hovering = this.state.hovering_button 231 | const button_style = { 232 | border: 'none', 233 | backgroundColor: hovering ? '#2ecc71' : 'transparent', 234 | padding: '5px', 235 | color: 'white', 236 | fontFamily: 'Verdana', 237 | textDecoration: 'underline', 238 | cursor: 'pointer', 239 | lineHeight: '15px' 240 | } 241 | 242 | const ren_comp_title_style = Object.assign({}, 243 | title_style, { position: 'relative' }) 244 | 245 | return
246 |
247 |
248 | Rendered Component 249 |
250 |
251 |
252 |
253 | {component_to_render} 254 |
255 |
256 |
257 |
258 | 262 | Props Editor 263 | 264 |
265 | 273 |
274 |
275 |
276 | 277 |
278 |
279 |
280 | } 281 | } 282 | Renderer.propTypes = { 283 | component: PropTypes.func.isRequired, 284 | dispatch: PropTypes.func.isRequired, 285 | component_props: PropTypes.object.isRequired 286 | } 287 | -------------------------------------------------------------------------------- /menus/atom-react-preview.cson: -------------------------------------------------------------------------------- 1 | 'context-menu': 2 | 'atom-text-editor': [ 3 | {label: 'Preview React Component', command: 'atom-react-preview:toggle'} 4 | ] 5 | 6 | 'menu': [ 7 | { 8 | 'label': 'Packages' 9 | 'submenu': [ 10 | 'label': 'Preview React Component' 11 | 'submenu': [ 12 | { 'label': 'Toggle React Component Preview', 'command': 'atom-react-preview:toggle' } 13 | ] 14 | ] 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atom-react-preview", 3 | "main": "./lib/atom-react-preview", 4 | "version": "2.0.0", 5 | "description": "Preview your React components directly in Atom", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/VictorBjelkholm/atom-react-preview" 9 | }, 10 | "scripts": { 11 | "start": "node dev-server.js" 12 | }, 13 | "license": "MIT", 14 | "engines": { 15 | "atom": ">=0.174.0 <2.0.0" 16 | }, 17 | "dependencies": { 18 | "atom-space-pen-views": "^2.0.5", 19 | "babel-core": "^6.3.15", 20 | "babel-plugin-react-transform": "^2.0.0", 21 | "babel-preset-es2015": "^6.3.13", 22 | "babel-preset-react": "^6.3.13", 23 | "babel-preset-react-hmre": "^1.0.1", 24 | "babel-preset-stage-0": "^6.3.13", 25 | "css-loader": "^0.23.1", 26 | "fs-extra": "^0.26.2", 27 | "node-sass": "^3.4.2", 28 | "react": "^0.14.3", 29 | "react-dom": "^0.14.3", 30 | "react-hot-loader": "^1.3.0", 31 | "react-transform-catch-errors": "^1.0.1", 32 | "react-transform-hmr": "^1.0.1", 33 | "sass-loader": "^3.1.2", 34 | "style-loader": "^0.13.0", 35 | "webpack-dev-middleware": "^1.5.1", 36 | "webpack-hot-middleware": "^2.6.1" 37 | }, 38 | "readmeFilename": "README.md", 39 | "bugs": { 40 | "url": "https://github.com/Victorbjelkholm/atom-react-preview/issues" 41 | }, 42 | "homepage": "https://github.com/VictorBjelkholm/atom-react-preview", 43 | "_id": "atom-react-preview@0.1.3", 44 | "_shasum": "1b6a3d9636c2b2411e84e1c95ab2121ea54e153e", 45 | "_resolved": "file:../d-115111-11388-1t9q7wq/package.tgz", 46 | "_from": "../d-115111-11388-1t9q7wq/package.tgz", 47 | "_atomModuleCache": { 48 | "version": 1, 49 | "dependencies": [], 50 | "extensions": { 51 | ".coffee": [ 52 | "lib/atom-react-preview-view.coffee", 53 | "lib/atom-react-preview.coffee" 54 | ], 55 | ".json": [ 56 | "package.json" 57 | ] 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | atom-react-preview 6 | 7 | 8 |
9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /styles/atom-react-preview.less: -------------------------------------------------------------------------------- 1 | // The ui-variables file is provided by base themes provided by Atom. 2 | // 3 | // See https://github.com/atom/atom-dark-ui/blob/master/stylesheets/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | html, body { 7 | padding: 0px; 8 | margin: 0px; 9 | } 10 | .atom-react-preview { 11 | padding: 0px; 12 | margin: 0px; 13 | 14 | box-sizing: border-box; 15 | background-color: white; 16 | 17 | iframe { 18 | width: 100%; 19 | height: 100%; 20 | padding: 0px; 21 | margin: 0px; 22 | border: 0; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test-styles.js: -------------------------------------------------------------------------------- 1 | // This is just a test-file with import of SASS stuff 2 | import React, { Component, PropTypes } from 'react' 3 | import classNames from 'classnames' 4 | import './test-styles.scss' 5 | 6 | class Button extends Component { 7 | render () { 8 | const buttonClass = classNames({ 9 | 'button': true, 10 | 'button--hidden': this.props.hidden, 11 | 'button--disabled': this.props.disabled 12 | }) 13 | 14 | return ( 15 | 18 | ) 19 | } 20 | } 21 | 22 | Button.propTypes = { 23 | hidden: PropTypes.bool, 24 | disabled: PropTypes.bool, 25 | text: PropTypes.string 26 | } 27 | 28 | Button.defaultProps = { 29 | hidden: false, 30 | disabled: false, 31 | text: 'Submit' 32 | } 33 | 34 | export default Button 35 | -------------------------------------------------------------------------------- /test-styles.scss: -------------------------------------------------------------------------------- 1 | // SASS style testing, belongs with test-styles.js 2 | .button { 3 | display: inline-block; 4 | border-radius: 3px; 5 | border-style: solid; 6 | border-width: 1px; 7 | background-color: #34495e; 8 | color: #ecf0f1; 9 | padding: 0.7em 1em; 10 | } 11 | 12 | .button--disabled { 13 | opacity: 0.3; 14 | } 15 | 16 | .button--hidden { 17 | opacity: 0; 18 | } 19 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 'heyo5' 3 | 4 | export default class Name extends Component { 5 | printTired () { 6 | return this.props.is_tired ? 'You are tired' : 'You are not tired...' 7 | } 8 | render () { 9 | const style = { 10 | color: 'white', 11 | backgroundColor: 'grey', 12 | padding: '50px' 13 | } 14 | const shared_style = { 15 | margin: '15px', 16 | color: '#9f9' 17 | } 18 | let nums = [] 19 | for (var i = 1; i < this.props.number + 1; i++) { 20 | nums.push(i) 21 | } 22 | 23 | let warning = null 24 | if (this.props.show_warning) { 25 | const warning_style = { 26 | padding: '10px', 27 | color: '#f99' 28 | } 29 | warning =
WARNING! Content here might or might not be true...
30 | } 31 | let pet_or_pets = 'pets' 32 | if (this.props.pets.length === 1) { 33 | pet_or_pets = 'pet' 34 | } 35 | return
36 | {warning} 37 |
38 | Hello, {this.props.name} 39 |
40 |
41 | You are {this.props.age} years old. 42 |
43 |
44 | You have {this.props.pets.length} {pet_or_pets}... 45 | {this.props.pets.map((pet) => { 46 | return
One is {pet}
47 | })} 48 |
49 |
50 | { this.printTired.bind(this)() } 51 |
52 | {(function () { 53 | return nums.map((i) => { 54 | return
{i}
55 | }) 56 | })()} 57 |
58 | } 59 | } 60 | Name.propTypes = { 61 | name: PropTypes.string, 62 | number: PropTypes.number, 63 | pets: PropTypes.array, 64 | age: PropTypes.number, 65 | is_tired: PropTypes.bool, 66 | show_warning: PropTypes.bool 67 | } 68 | Name.defaultProps = { 69 | name: 'Victor', 70 | number: 5, 71 | pets: ['Gaia'], 72 | age: 18, 73 | is_tired: false, 74 | show_warning: false 75 | } 76 | --------------------------------------------------------------------------------