React - Highlight Text Inside a Textarea

by codemeasandwich

HTML

<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="container">
    <!-- This element's contents will be replaced with your component. -->
</div>

CSS

@import url(https://fonts.googleapis.com/css?family=Open+Sans);

*, *::before, *::after {
  box-sizing: border-box;
}

body {
  margin: 30px;
  background-color: #f0f0f0;
}

.container, .backdrop, textarea {
  width: 460px;
  height: 180px;
}

.highlights, textarea {
  padding: 10px;
  font: 20px/28px 'Open Sans', sans-serif;
  letter-spacing: 1px;
}

.container {
  display: block;
  margin: 0 auto;
  transform: translateZ(0);
  -webkit-text-size-adjust: none;
}

.backdrop {
  position: absolute;
  z-index: 1;
  border: 2px solid #685972;
  background-color: #fff;
  overflow: auto;
  pointer-events: none;
  transition: transform 1s;
}

.highlights {
	white-space: pre-wrap;
	word-wrap: break-word;
	color: transparent;
}

textarea {
  display: block;
  position: absolute;
  z-index: 2;
  margin: 0;
  border: 2px solid #74637f;
  border-radius: 0;
  color: #444;
  background-color: transparent;
  overflow: auto;
  resize: none;
  transition: transform 1s;
}

mark {
  border-radius: 3px;
  color: transparent;
  background-color: #b1d5e5;
}

Babel + JSX

// yeah, browser sniffing sucks, but there are browser-specific quirks to handle that are not a matter of feature detection
var ua = window.navigator.userAgent.toLowerCase();
var isIE = !!ua.match(/msie|trident\/7|edge/);
var isWinPhone = ua.indexOf('windows phone') !== -1;
var isIOS = !isWinPhone && !!ua.match(/ipad|iphone|ipod/);

class Highlights extends React.Component {
  render() {
  
    let __html = this.props.children
    .replace(/\n$/g, '\n\n')
    .replace(/[A-Z].*?\b/g, '<mark>$&</mark>');

  if (isIE) {
    // IE wraps whitespace differently in a div vs textarea, this fixes it
    __html = __html.replace(/ /g, ' <wbr>');
  }

    return  <div  ref="elem" className="backdrop">
                <div className="highlights" dangerouslySetInnerHTML={{__html}} />
            </div>
  }
  componentDidUpdate() {
    this.refs.elem.scrollTop = this.props.scrollTop;
  }
}

class Textarea extends React.Component {

constructor(props) {
  super(props);
  this.listenScrollEvent = this.listenScrollEvent.bind(this)
  this.state = {
  	scrollTop:0,
    text: `This demo shows how to highlight bits of text within a textarea. Alright, that's a lie. You can't actually render markup inside a textarea. However, you can fake it by carefully positioning a div behind the textarea and adding your highlight markup there. JavaScript takes care of syncing the content and scroll position from the textarea to the div, so everything lines up nicely. Hit the toggle button to peek behind the curtain. And feel free to edit this text. All capitalized words will be highlighted.`
  };
}

componentDidMount() {
   this.refs.textarea.addEventListener('scroll', this.listenScrollEvent);
}

componentWillUnmount() {
   this.refs.textarea.removeEventListener('scroll', this.listenScrollEvent);
}

listenScrollEvent() {
    this.setState({scrollTop:this.refs.textarea.scrollTop})
}

  render() {
    return <div className="container">
        <Highlights scrollTop={this.state.scrollTop}>{
       ...