JSFiddle - React, Tailwind, and code Playground

by jeremyblalock

HTML

<div class='wrapper'>
    <div class='line-numbers'></div>
    <textarea class='textarea' rows="4">Some good text to start us off. It's much easier to imagine this if we start with something...

Another line of awesome content...</textarea>
    <div class='line-bg'></div>
</div>
<div class='indicator'></div>

CSS

* {
    box-sizing: border-box;
}

body {
    padding: 30px;
    background: #eee;
    font-size: 16px;
}

.wrapper {
    background: #fff;
    margin: bottom;
    position: relative;
    font-family: Monaco, fixed-width;
}
.textarea {
    width: 100%;
    font: inherit;
    font-size: 13px;
    outline: none !important;
    border: none;
    background: transparent;
    display: block;
    resize: none;
    line-height: 20px;
    position: relative;
    z-index: 2;
    padding-left: 55px;
}
.line-bg {
    background: #dfd;
    width: 100%;
    top: 0;
    height: 20px;
    left: 0;
    position: absolute;
    visibility: hidden;
    min-height: 20px;
}
.textarea:focus + .line-bg {
    visibility: visible;
}
.line-numbers {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 50px;
    background: rgba(0, 0, 0, 0.03);
    z-index: 1;
    color: #999;
}
.line-numbers span {
    position: absolute;
    line-height: 20px;
    left: 0;
    padding-left: 5px;
    font-size: 13px;
}

JavaScript

(function($, undefined) {  
    $.fn.getCursorPosition = function() {  
        var el = $(this).get(0);  
        var pos = el.selectionStart,
            end = el.selectionEnd;  
        return [pos, end];  
    }  
})(jQuery); 

var $shadow = $('<div></div>'),
    $ta = $('.textarea'),
    $lineBg = $('.line-bg'),
    $lineNumbers = $('.line-numbers'),
    lineHeight = 20;
$('body').append($shadow);

function setup() {
    $shadow.css({
        font: $ta.css('font'),
        width: $ta.width(),
        lineHeight: lineHeight + 'px',
        whiteSpace: 'pre-wrap',
        position: 'fixed',
        bottom: '100%'
    });
}

function computeRowCount(text) {
    $shadow.text(text);
    var height = $shadow.height();
    return height / lineHeight;
}

function getSelectionPosition(start, end) {
    var text = $ta.val(),
        $parent = $('<span></span>'),
        $startSpan = $('<span></span>'),
        $selectionSpan = $('<span></span>'),
        $endSpan = $('<span></span>');
    if (text.substring(text.length - 1).match(/[\r\n]/)) {
        text += '.';
    }
    $startSpan.text(text.substring(0, start));
    $selectionSpan.text(text.substring(start, end + 1));
    $endSpan.text(text.substring(end + 1));
    
    if (text.substring(start, end) == '') {
        $selectionSpan.css({
            //display: 'inline-block'
        });
    }

    $parent.append($startSpan);
    $parent.append($selectionSpan);
    $parent.append($endSpan);

    $shadow.html('');
    $shadow.append($parent);

    var y = $selectionSpan.position().top,
        h = $selectionSpan.outerHeight();
    return {top: y, height: h};
}

function setLineNumbers() {
    var text = $ta.val(),
        lines = text.split(/\n/),
        soFarText = '',
        $ln;
    $lineNumbers.html('');
    for (var i = 0; i < lines.length; i += 1) {
        $ln = $('<span></span>');
        $ln.text(i + 1);
        $ln.css({
            top: computeRowCount(soFarText) * 20
        });
       ...