Created
February 14, 2016 18:50
-
-
Save jaredpalmer/4d8042fb5d4dcd77a0f1 to your computer and use it in GitHub Desktop.
React Text Editor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component, PropTypes } from 'react'; | |
import { Block, Flex } from 'jsxstyle'; | |
import L from '../LayoutConstants'; | |
import TextArea from 'react-textarea-autosize'; | |
class Input extends Component { | |
constructor() { | |
super(); | |
this.state = { | |
focused: false, | |
}; | |
} | |
handleOnFocus() { | |
this.setState({ focused: true }); | |
} | |
handleOnBlur() { | |
this.setState({ focused: false }); | |
} | |
render() { | |
const { style, rows, ...rest } = this.props; | |
const border = this.state.focused ? '1px solid #aaa' : '1px solid #c4c4c4'; | |
const defaults = { | |
boxSizing: 'border-box', | |
fontFamily: L.sans, | |
transition: '.2s border', | |
border: border, | |
fontSize: '1rem', | |
outline: 'none !important', | |
padding: '8px 10px', | |
borderRadius:'2px', | |
WebkitAppearance: 'none', | |
display: 'inline-block', | |
}; | |
const inputStyle = Object.assign({}, defaults, style); | |
return ( | |
rows ? <TextArea | |
useCacheForDOMMeasurements | |
rows={rows} | |
onFocus={this.handleOnFocus.bind(this)} | |
onBlur={this.handleOnBlur.bind(this)} | |
style={inputStyle} | |
{...rest}/> : <input | |
onFocus={this.handleOnFocus.bind(this)} | |
onBlur={this.handleOnBlur.bind(this)} | |
style={inputStyle} | |
{...rest}/> | |
); | |
} | |
} | |
export default Input; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Color = require('jsxstyle/lib/Color'); | |
const primaryColor = Color.rgb(10, 0, 0); | |
const secondaryColor = Color.alpha(primaryColor, .8); | |
// AirBnb Colors | |
const turq = Color.rgb(0, 209, 193); | |
const green = Color.rgb(63, 179, 79); | |
const orange = Color.rgb(255, 180, 0); | |
const pink = Color.rgb(255, 90, 95); | |
const darkBlue = Color.rgb(56, 59, 60); | |
// Email colors | |
const white = Color.rgb(255, 255, 255); | |
const black = Color.rgb(20, 24, 35); | |
const gray = Color.rgb(89, 95, 108); | |
const blue = Color.rgb(0, 112, 255); | |
const LayoutConstants = { | |
white, | |
black, | |
primaryColor, | |
secondaryColor, | |
turq, | |
green, | |
orange, | |
pink, | |
darkBlue, | |
gridUnit: 8, | |
u: 8, | |
sans: "-apple-system,BlinkMacSystemFont,\"Helvetica Neue\",Helvetica,Arial,sans-serif", | |
}; | |
module.exports = LayoutConstants; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { curry, Inline} from 'jsxstyle'; | |
import L from '../LayoutConstants'; | |
export const PrimaryText = curry(Inline, { fontSize: '2rem', fontWeight: 'bold', color: L.white }); | |
export const SecondaryText = curry(Inline, { fontSize: '1.5rem', fontWeight: 'bold', color: L.black }); | |
export const TertiaryText = curry(Inline, { fontSize: '1.25rem', fontWeight: 'bold', color: L.black }); | |
export const SmallText = curry(Inline, { fontSize: '.9rem', fontWeight: 'bold', color: L.gray }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component, PropTypes } from 'react'; | |
import ReactDOM from 'react-dom'; | |
import TextArea from 'react-textarea-autosize'; | |
import { Block, Row, Col } from 'jsxstyle'; | |
import marked from 'marked'; | |
import {SmallText} from '../Text'; | |
import Input from '../Input'; | |
const Button = ({ onClick, children, ...other }) => | |
<button | |
style={{ | |
background: 'transparent', | |
outline: 0, | |
border: 'none', | |
fontWeight: 'bold', | |
fontSize: 16, | |
cursor: 'pointer !important', | |
}} | |
onClick={onClick}> | |
{children} | |
</button>; | |
class TextArea extends React.Component { | |
constructor() { | |
super(); | |
this.state = { | |
val: '', | |
selectStart: null, | |
selectEnd: null, | |
preview: false, | |
}; | |
} | |
handleChange(e) { | |
this.setState({ | |
val: e.target.value, | |
html: marked(e.target.value, { gfm: true, sanitize: false }), | |
preview: false, | |
}); | |
} | |
handleSelect(e) { | |
this.setState({ | |
selectStart: e.target.selectionStart, | |
selectEnd: e.target.selectionEnd, | |
}); | |
} | |
insertEl(el) { | |
const { val, selectStart, selectEnd } = this.state; | |
const text = val; | |
const len = el.length; | |
const temp = text.slice(0, selectEnd) + `</${el}>` + text.slice(selectEnd); | |
const result = temp.slice(0, selectStart) + `<${el}>` + temp.slice(selectStart); | |
this.setState({ val: result }, () => { | |
ReactDOM.findDOMNode(this.refs.mInput).focus(); | |
if (selectStart === selectEnd) { | |
ReactDOM.findDOMNode(this.refs.mInput).setSelectionRange(selectStart + len + 2, selectEnd + len + 2); | |
} else { | |
ReactDOM.findDOMNode(this.refs.mInput).setSelectionRange(selectStart, selectEnd + 5 + len * 2); | |
} | |
}); | |
} | |
insertSingle(el) { | |
const { val, selectStart, selectEnd } = this.state; | |
const text = val; | |
if (selectStart !== selectEnd && selectStart !== 0 || | |
selectStart === selectEnd && selectStart !== 0) { | |
el = '\n' + el + ' '; | |
} | |
const len = el.length; | |
const result = text.slice(0, selectStart) + el + text.slice(selectStart); | |
this.setState({ val: result }, () => { | |
ReactDOM.findDOMNode(this.refs.mInput).focus(); | |
if (selectStart === selectEnd) { | |
ReactDOM.findDOMNode(this.refs.mInput).setSelectionRange(selectStart + len, selectEnd + len); | |
} else { | |
ReactDOM.findDOMNode(this.refs.mInput).setSelectionRange(selectStart, selectEnd + len); | |
} | |
}); | |
} | |
render() { | |
return ( | |
<Block position="relative"> | |
<Block alignItems="center" | |
position="absolute" | |
top="0" | |
left="-40px" | |
justifyContent="space-between" | |
background="#f5f5f5" | |
borderRadius="2px" | |
boxShadow="0px 1px 3px rgba(0,0,0,.5)" | |
> | |
<Block> | |
{ !this.state.preview && | |
<Col> | |
<Button onClick={this.insertEl.bind(this, 'i')}><i className="material-icons">format_italic</i></Button> | |
<Button onClick={this.insertEl.bind(this, 'b')}><i className="material-icons">format_bold</i></Button> | |
<Button onClick={this.insertSingle.bind(this, '* ')}><i className="material-icons">format_list_bulleted</i></Button> | |
<Button onClick={this.insertSingle.bind(this, '1. ')}><i className="material-icons">format_list_numbered</i></Button> | |
</Col> | |
} | |
</Block> | |
</Block> | |
{ !this.state.preview && <Input | |
ref={'mInput'} | |
value={this.state.val} | |
onSelect={this.handleSelect.bind(this)} | |
onChange={this.handleChange.bind(this)} | |
rows={5} | |
placeholder={'Start typing... and then press the bold button to insert a \<strong\> tag.\n\n Why doesn\'t setSelectionRange work inside of insertBold\(\)??'} | |
style={{ display: 'block', width: '100%', marginTop: 10, padding: 5, border: '1px solid #c4c4c4', fontSize: 16 }} | |
/> } | |
{this.state.preview && | |
<div style={{ | |
display: 'block', | |
background: 'white', | |
border: '1px solid #f5f5f5', | |
width: '100%', | |
minHeight: 100, | |
marginTop: 10, | |
padding: 5, | |
}} | |
dangerouslySetInnerHTML={{ __html: this.state.html }} | |
/> | |
} | |
{!this.state.preview && | |
<Block marginTop="1rem" textAlign="center"> | |
<a href="https://guides.github.com/features/mastering-markdown/" target="_blank" data-ga-click="Markdown Toolbar, click, help"> | |
<svg style={{ verticalAlign: 'bottom', marginRight: 2, fill: '#999999' }} aria-hidden="true" className="octicon octicon-markdown" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M14.85 3H1.15C0.52 3 0 3.52 0 4.15v7.69C0 12.48 0.52 13 1.15 13h13.69c0.64 0 1.15-0.52 1.15-1.15V4.15C16 3.52 15.48 3 14.85 3zM9 11L7 11V8l-1.5 1.92L4 8v3H2V5h2l1.5 2 1.5-2 2 0V11zM11.99 11.5L9.5 8h1.5V5h2v3h1.5L11.99 11.5z"></path></svg> | |
<SmallText color="#999999" | |
fontWeight="normal"> | |
Styling with Markdown is supported | |
</SmallText> | |
</a> | |
</Block> | |
} | |
<button onClick={() => this.setState({ preview: !this.state.preview })}>Preview</button> | |
</Block> | |
); | |
} | |
} | |
export default TextArea; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment