Skip to content

Instantly share code, notes, and snippets.

@jaredpalmer
Created February 14, 2016 18:50
Show Gist options
  • Save jaredpalmer/4d8042fb5d4dcd77a0f1 to your computer and use it in GitHub Desktop.
Save jaredpalmer/4d8042fb5d4dcd77a0f1 to your computer and use it in GitHub Desktop.
React Text Editor
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;
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;
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 });
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