<div class="demo-wrapper">
<div class="grid-controls">
<section class="grid-controls__header">
<h3>Grid Container Controls</h3>
<button class="btn toggle-view">Switch Controls</button>
<button class="btn toggle-controls">Toggle View</button>
<button class="btn clear-fields">Clear Fields</button>
</section>
<section class="controls--wrapper">
<div class="grid-controls--container">
<!-- GRID TEMPLATE COLUMNS -->
<section class="grid-controls__body">
<div class="control--multiple-fields multiple-cols">
<p>Set values for the Grid container by individual fields. For more advanced controls, click 'Switch Controls'.</p>
<hr />
<p>Grid Template Columns:</p>
<div class="controls__row">
<div class="controls__row--title">
<label for="gtc">Column 1: </label>
</div>
<div class="controls__row--area">
<input type="text" id="col-input-1" placeholder="1" min="1"/>
<select name="" id="col-sel-1">
<option value="fr">fr</option>
<option value="px">px</option>
<option value="%">%</option>
<option value="auto">auto</option>
</select>
</div>
</div>
<div class="controls__row">
<div class="controls__row--title">
<label for="gtc2">Column 2: </label>
</div>
<div class="controls__row--area">
<input type="text" id="col-input-2" placeholder="150" min="1"/>
<select name="" id="col-sel-2">
<option value="fr">fr</option>
<option value="px" selected>px</option>
<option value="%">%</option>
<option value="auto">auto</option>
</select>
</div>
</div>
<div class="controls__row">
<div class="controls__row--title">
<label for="gtc">Column 3: </label>
</div>
<div class="controls__row--area">
<input type="text" id="col-input-3" name="gtc" placeholder="1" min="1"/>
<select name="" id="col-sel-3">
<option value="fr">fr</option>
<option value="px">px</option>
<option value="%">%</option>
<option value="auto">auto</option>
</select>
</div>
</div>
</div>
<!-- GRID CONTAINER SINGLE INPUTS VIEW -->
<div class="control--single-input single-input">
<p>Set values for the Grid container by property. <br />
To change grid-item values, click 'Toggle View'.</p>
<hr />
<p>Grid Container</p>
<div class="code-example">
<div class="codebox">
<span class="selector-name">.grid-container <span class="bracket">{</span></span><br />
<div class="single-input-wrapper">
<span class="prop-name">grid-template-columns:</span>
<span class="value">
<input class="grid-single-input" id="col-single-input"/>
</span>
</div>
<div class="single-input-wrapper">
<span class="prop-name">grid-auto-columns:</span>
<span class="value">
<input class="grid-single-input" id="auto-col-single-input"/>
</span>
</div>
<div class="single-input-wrapper">
<span class="prop-name">grid-template-rows:</span>
<span class="value">
<input class="grid-single-input" id="row-single-input"/>
</span>
</div>
<div class="single-input-wrapper">
<span class="prop-name">grid-auto-rows:</span>
<span class="value">
<input class="grid-single-input" id="auto-row-single-input"/>
</span>
</div>
<div class="single-input-wrapper">
<span class="prop-name">grid-auto-flow:</span>
<span class="value">
<input class="grid-single-input" id="auto-flow-single-input" />
</span>
</div>
<div class="single-input-wrapper">
<span class="prop-name">grid-gap:</span>
<span class="value">
<input class="grid-single-input" id="gap-single-input" />
</span>
</div>
<div class="single-input-wrapper">
<span class="prop-name">grid-template-areas:</span>
<span class="value">
<textarea class="grid-single-input" id="areas-single-input" rows='5'>
</textarea>
</span>
</div>
<span class="bracket bracket--close"> }</span>
<br />
</div>
</div>
</div>
</section>
<hr class='seperator' />
<!-- GRID TEMPLATE ROWS -->
<section class="grid-controls__body">
<div class="control--multiple-fields multiple-rows">
<p>Grid Template Rows:</p>
<div class="controls__row">
<div class="controls__row--title">
<label for="gtc">Row 1: </label>
</div>
<div class="controls__row--area">
<input type="text" id="row-input-1" placeholder="120" min="1"/>
<select name="" id="row-sel-1">
<option value="fr">fr</option>
<option value="px" selected>px</option>
<option value="%">%</option>
<option value="auto">auto</option>
</select>
</div>
</div>
<div class="controls__row">
<div class="controls__row--title">
<label for="gtc">Row 2: </label>
</div>
<div class="controls__row--area">
<input type="text" id="row-input-2" placeholder="100" min="1"/>
<select name="" id="row-sel-2">
<option value="fr">fr</option>
<option value="px" selected>px</option>
<option value="%">%</option>
<option value="auto">auto</option>
</select>
</div>
</div>
</div>
</section>
<hr class='seperator' />
<!-- GRID GAP -->
<section class="grid-controls__body">
<div class="control--multiple-fields multiple-gaps">
<div class="controls__row">
<div class="controls__row--title">
<label for="gtc">Grid Row Gap: </label>
</div>
<div class="controls__row--area">
<input type="text" id="gap-row-input" placeholder="5" min="0"/>
<select name="" id="gap-row-sel">
<option value="px" selected>px</option>
<option value="%">%</option>
</select>
</div>
</div>
<div class="controls__row">
<div class="controls__row--title">
<label for="gtc">Grid Column Gap: </label>
</div>
<div class="controls__row--area">
<input type="text" id="gap-col-input" placeholder="5" min="0"/>
<select name="" id="gap-col-sel">
<option value="px" selected>px</option>
<option value="%">%</option>
</select>
</div>
</div>
</div> <!-- End Multiple Fields -->
</section>
<hr class='seperator' />
<!-- JUSTIFY ITEMS -->
<section class="grid-controls__body">
<div class="controls__row">
<div class="controls__row--title">
<label>Justify Items:</label>
</div>
<div class="controls__row--area controls__row--stretch">
<select name="" id="justify-items-sel">
<option value="stretch" selected>stretch</option>
<option value="start">start</option>
<option value="center">center</option>
<option value="end">end</option>
</select>
</div>
</div>
<!-- ALIGN ITEMS -->
<div class="controls__row">
<div class="controls__row--title">
<label>Align Items:</label>
</div>
<div class="controls__row--area controls__row--stretch">
<select name="" id="align-items-sel">
<option value="stretch" selected>stretch</option>
<option value="start">start</option>
<option value="center">center</option>
<option value="end">end</option>
</select>
</div>
</div>
<!-- JUSTIFY CONTENT -->
<div class="controls__row">
<div class="controls__row--title">
<label>Justify Content:</label>
</div>
<div class="controls__row--area controls__row--stretch">
<select name="" id="justify-content-sel">
<option value="start">start</option>
<option value="center">center</option>
<option value="end">end</option>
<option value="stretch">stretch</option>
<option value="space-around">space-around</option>
<option value="space-between">space-between</option>
<option value="space-evenly">space-evenly</option>
</select>
</div>
</div>
<!-- ALIGN CONTENT -->
<div class="controls__row">
<div class="controls__row--title">
<label>Align Content:</label>
</div>
<div class="controls__row--area controls__row--stretch">
<select name="" id="align-content-sel">
<option value="start">start</option>
<option value="center">center</option>
<option value="end">end</option>
<option value="stretch">stretch</option>
<option value="space-around">space-around</option>
<option value="space-between">space-between</option>
<option value="space-evenly">space-evenly</option>
</select>
</div>
</div>
</section>
</div> <!-- End Grid Container Controls -->
<!-- GRID ITEM INPUTS VIEW -->
<div class="grid-controls--items">
<div class="control--single-item grid-items">
<p>Set values for the individual Grid Items. Click on an Item to target its values and the selector will update.</p>
<hr />
<p>Grid Items</p>
<div class="code-example">
<div class="codebox">
<span class="selector-name" id="grid-item--dynamic">.grid__item-1</span> <span class="bracket"> {</span><br />
<div class="single-input-wrapper">
<span class="prop-name">grid-column:</span>
<span class="value">
<input class="grid-item-input" id="column-item-input"/>
</span>
</div>
<div class="single-input-wrapper">
<span class="prop-name">grid-row:</span>
<span class="value">
<input class="grid-item-input" id="row-item-input"/>
</span>
</div>
<div class="single-input-wrapper">
<span class="prop-name">grid-area:</span>
<span class="value">
<input class="grid-item-input" id="grid-area-input"/>
</span>
</div>
<div class="single-input-wrapper">
<span class="prop-name">justify-self:</span>
<span class="value">
<select name="" class='grid-item-input' id="justify-self-sel">
<option value="stretch" selected>stretch</option>
<option value="start">start</option>
<option value="center">center</option>
<option value="end">end</option>
</select>
</span>
</div>
<div class="single-input-wrapper">
<span class="prop-name">align-self:</span>
<span class="value">
<select name="" class='grid-item-input' id="align-self-sel">
<option value="stretch" selected>stretch</option>
<option value="start">start</option>
<option value="center">center</option>
<option value="end">end</option>
</select>
</span>
</div>
<span class="bracket bracket--close"> }</span>
</div>
</div>
</section>
</div> <!-- End Grid Controls -->
<div class="grid-container">
<div class="grid__item" id="grid__item-1">1</div>
<div class="grid__item" id="grid__item-2">2</div>
<div class="grid__item" id="grid__item-3">3</div>
<div class="grid__item" id="grid__item-4">4</div>
<div class="grid__item" id="grid__item-5">5</div>
<div class="grid__item" id="grid__item-6">6</div>
<div class="grid__item" id="grid__item-7">7</div>
<div class="grid__item" id="grid__item-8">8</div>
<div class="grid__item" id="grid__item-9">9</div>
</div>
</div>
@import url('https://fonts.googleapis.com/css?family=Titillium+Web');
$bg: #d3e9ee;
$c1: #f74444; // btn
$c2: #44a4f7; // items
$c3: #3b99ab; // controls
$gap-size: 5px;
.demo-wrapper {
margin: 0 auto;
padding: 15px;
width: 100%;
height: 100vh;
overflow-y: auto;
max-width: 1400px;
display: flex;
align-items: stretch;
}
.grid-controls {
margin: 0 0 0 15px;
width: 35%;
flex: 0 1 auto;
background: $c3;
color: #fff;
font-size: 1.1rem;
border-radius: 3px;
overflow-y: auto;
overflow-x: hidden;
h3 {
margin: 0 auto 10px;
letter-spacing: 0.5px;
text-align: center;
}
p {
margin: 0 0 6px;
font-weight: bold;
}
&__header {
padding: 8px;
}
&__body {
display: grid;
}
}
.controls--wrapper {
height: auto;
width: 100%;
display: flex;
}
.grid-controls--container,
.grid-controls--items {
padding: 8px 14px 8px 8px;
flex: 0 0 100%;
transform: translateX(0);
transition: 0.3s ease-out;
&.slide {
transform: translateX(-100%);
}
}
.controls__row {
margin-bottom: 10px;
display: flex;
justify-content: space-between;
&--area {
display: flex;
}
&--stretch {
flex-direction: row-reverse;
flex: 1 1 auto;
select {
width: 100%;
max-width: 260px;
}
}
}
// --------------------------------
// Switch between states
.control--single-input,
.control--multiple-fields {
grid-area: 1 / 1;
}
.control--single-input {
height: 0;
visibility: hidden;
opacity: 0;
transition: all 0.2s;
&.visible {
height: auto;
visibility: visible;
opacity: 1;
transition: all 0.2s;
}
}
.control--multiple-fields {
height: auto;
visibility: visible;
opacity: 1;
transition: all 0.2s;
&.hidden {
height: 0;
visibility: hidden;
opacity: 0;
transition: all 0.2s;
}
}
// ----------------------------------------------
// Code example related:
.code-example {
margin-bottom: 15px;
background: #3f3f3f !important;
border-radius: 3px;
}
.single-input-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
}
.grid-single-input,
.grid-item-input {
width: 100%;
border-radius: 3px;
font-size: 0.935rem;
}
.codebox {
padding: 8px 0 12px;
font-size: 0.935rem;
span {
font-family: monospace;
&.selector-name {
display: inline-block;
padding-left: 6px;
margin-bottom: 6px;
color: #EC5f67;
}
&.prop-name {
padding-left: 20px;
color: #99C794;
}
&.value {
max-width: 215px;
flex: 1 1 auto;
padding: 2px 5px;
color: #C594C5;
}
&.bracket {
color: #ccc;
padding-left: 6px;
}
}
}
.grid-user-input {
width: 120px;
border-radius: 3px;
}
// Form elements
select,input {
font-family: 'Titillium Web';
font-size: 1rem;
background-color: #fff;
border: 1px solid #dbdbdb;
outline: 0;
height: 30px;
}
input {
padding-left: 6px;
border-radius: 3px 0 0 3px;
}
label {
margin-right: 10px;
}
select {
line-height: 1.25;
padding-right: .625em;
position: relative;
vertical-align: top;
color: #363636;
cursor: pointer;
display: block;
}
.btn {
padding: 2px 18px;
height: 30px;
font: {
family: inherit;
size: 0.938rem;
}
border: none;
outline: none;
cursor: pointer;
background: $c1;
color: #fff;
border-radius: 0 3px 3px 0;
transition: 0.2s;
&:hover {
background: darken($c1, 5%);
}
&--add-control-row {
height: 34px;
margin-top: 10px;
align-self: flex-start;
border-radius: 3px;
}
}
.grid-container {
margin: 0 15px;
padding: $gap-size;
overflow-x: hidden;
width: calc(65% - 15px);
flex: 1 1 auto;
border: 1px solid $c3;
display: grid;
border-radius: 3px;
}
.grid__item {
padding: 10px;
display: flex;
justify-content: center;
align-items: center;
background: $c2;
color: #fff;
font-size: 1.2rem;
border-radius: 3px;
cursor: pointer;
}
html {
box-sizing: border-box;
}
*, *::before, *::after {
box-sizing: inherit;
}
body {
margin: 0;
padding: 0;
height: 100vh;
background: $bg;
font: {
size: 16px;
family: 'Titillium Web';
}
}
.seperator {
display: block;
margin: 20px 0;
background: #fff;
height: 1px;
width: 95%;
&.hidden {
display: none;
}
}
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
border-radius: 2px;
background-color: #F5F5F5;
}
::-webkit-scrollbar {
width: 10px;
background-color: #F5F5F5;
}
::-webkit-scrollbar-thumb {
border-radius: 3px;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
background-color: #555;
}
const gridContainer = document.querySelector('.grid-container');
const gridControls = document.querySelector('.grid-controls');
const gridSingleInputs = Array.from(document.getElementsByClassName('grid-single-input'));
const gridItemSingleInputs = Array.from(document.getElementsByClassName('grid-item-input'));
const clearButton = document.querySelector('.clear-fields');
// Attach event listeners to single inputs and parent of multiple fields.
function addListeners() {
gridSingleInputs.forEach((input) => {
input.addEventListener('blur', handleSingleFieldChange);
});
gridItemSingleInputs.forEach((input) => {
if (input.id.includes('sel')) {
input.addEventListener('change', handleSingleItemChange);
} else {
input.addEventListener('blur', handleSingleItemChange);
};
});
gridControls.addEventListener('change', handleFieldsChange);
gridContainer.addEventListener('click', getGridItem);
clearButton.addEventListener('click', clearFields);
}
// Handle change on multiple fields view and call correct method to set vals.
function handleFieldsChange(e) {
const control = e.target.id;
if(control.startsWith('col')) {
setGridTemplateProp(e.target, 'column');
} else if (control.startsWith('row')) {
setGridTemplateProp(e.target, 'row');
} else if (control.startsWith('gap')) {
setGridGap(e.target);
} else if (control.startsWith('justify') || control.startsWith('align')) {
setItemAndContentAlignment(e.target);
}
}
// Container Single Field handler
function handleSingleFieldChange(e) {
const inputValue = e.target.value.toLowerCase();
const inputField = e.target.id;
let propName;
switch (inputField) {
case 'col-single-input':
propName = 'grid-template-columns';
break;
case 'auto-col-single-input':
propName = 'grid-auto-columns';
break;
case 'row-single-input':
propName = 'grid-template-rows';
break;
case 'auto-row-single-input':
propName = 'grid-auto-rows';
break;
case 'auto-flow-single-input':
propName = 'grid-auto-flow';
break;
case 'gap-single-input':
propName = 'grid-gap';
break;
case 'areas-single-input':
propName = 'grid-template-areas';
default:
break;
}
assignStyles(propName, inputValue);
}
// Single Item Event Handler
function handleSingleItemChange(e) {
// Select which grid-item to manipulate by users click//id title
const dynamicItemId = document.getElementById('grid-item--dynamic').textContent;
const stripId = dynamicItemId.slice(1);
const gridItem = document.getElementById(`${stripId}`);
// Get fields user clicked on.
const inputField = e.target.id;
const selectField = document.getElementById(inputField);
let inputValue;
let propName;
switch (inputField) {
case 'column-item-input':
inputValue = e.target.value.toLowerCase();
propName = 'grid-column';
break;
case 'row-item-input':
inputValue = e.target.value.toLowerCase();
propName = 'grid-row';
break;
case 'grid-area-input':
inputValue = e.target.value.toLowerCase();
propName = 'grid-area';
break;
case 'justify-self-sel':
inputValue = selectField.options[selectField.selectedIndex].value;
propName = 'justify-self';
break;
case 'align-self-sel':
inputValue = selectField.options[selectField.selectedIndex].value;
propName = 'align-self';
break;
default:
break;
}
assignStyles(propName, inputValue, gridItem);
}
function getGridItem(e) {
const itemId = e.target.id;
if (e.target && itemId.startsWith('grid__item')) {
const gridItem = document.getElementById(e.target.id);
const itemId = e.target.id;
const itemHolder = document.getElementById('grid-item--dynamic');
itemHolder.textContent = `.${itemId}`;
}
}
// Handles assigning the user's styles to the Grid element (container / items)
function assignStyles(propName, valueString, gridElement = gridContainer) {
Object.assign(gridElement.style, {
[propName]: valueString
});
}
// Set either grid-template-columns or grid-template-rows styles depending on argument passed.
function setGridTemplateProp(control, gridTemplateProp) {
// Set some variables determined by if user has changed a grid-template column or row property
const templateProp = (gridTemplateProp == 'column') ? 'col' : 'row';
const propVal = `${templateProp}Val`;
const propUnit = `${templateProp}Unit`;
// Get all the fields from either row or columns to be able to loop over each one.
const fieldsContainer = document.querySelector(`.multiple-${templateProp}s`);
const templateControls = Array.from(fieldsContainer.getElementsByClassName('controls__row'));
let propName = (gridTemplateProp == 'column') ? 'grid-template-columns' : 'grid-template-rows';
let userInputValues = {};
// Get input values from fields.
for (let i = 0, j = templateControls.length; i < j; i++) {
const selectOption = document.getElementById(`${templateProp}-sel-${i+1}`);
Object.assign(userInputValues, {
[propVal + (i+1)] : document.getElementById(`${templateProp}-input-${i+1}`).value,
[propUnit + (i+1)] : selectOption.options[selectOption.selectedIndex].value,
});
}
// If columnUnitN value is `auto`, set value to empty string
Object.keys(userInputValues).forEach((key, i) => {
if (userInputValues[key] == 'auto') {
// get number from this columns key name to be able to set prev keys value to '';
const controlId = `${propVal}${key.slice(key.length - 1)}`;
userInputValues[controlId] = '';
}
})
// Get current style of grid containers properties for grid template column/row.
const currentStyle = (gridTemplateProp == 'column') ? gridContainer.style.gridTemplateColumns : gridContainer.style.gridTemplateRows;
const currentValues = currentStyle.split(' ');
// Set each column as a value string consisting of it's value and unit.
let storeUserValues = {};
for (let i = 0, j = (Object.keys(userInputValues).length / 2); i < j; i++) {
storeUserValues[propVal + (i+1)] = `${userInputValues[propVal + (i+1)]}${userInputValues[propUnit + (i+1)]}`;
}
// get number off input id to determine which field user touched.
const controlId = control.id.slice(control.id.length -1);
// Update default value string definition to value user has supplied - index of column determined by controlId.
Object.keys(storeUserValues).forEach((col) => {
if (col.endsWith(controlId)) {
currentValues[controlId - 1] = storeUserValues[col];
}
});
// Use the updated values to pass on and update css property
const valueString = currentValues.join(' ');
assignStyles(propName, valueString);
}
function setGridGap(control) {
const fieldsContainer = document.querySelector('.multiple-gaps');
const templateControls = Array.from(fieldsContainer.getElementsByClassName('controls__row'));
const propName = 'grid-gap';
let userInputValues = {};
// Get input values from fields.
for (let i = 0, j = templateControls.length; i < j; i++) {
const gapRowSel = document.getElementById(`gap-row-sel`);
const gapColSel = document.getElementById(`gap-col-sel`);
Object.assign(userInputValues, {
[`rowGapVal`] : document.getElementById(`gap-row-input`).value,
[`rowGapUnit`] : gapRowSel.options[gapRowSel.selectedIndex].value,
[`colGapVal`] : document.getElementById(`gap-col-input`).value,
[`colGapUnit`] : gapColSel.options[gapColSel.selectedIndex].value,
});
}
// get default style of grid-gap (note: row comes before column for the shorthand).
const currentStyle = gridContainer.style.gridGap;
const currentValues = currentStyle.split(' ');
// If user has entered fields, use those for gap values, otherwise use defaults.
let rowGap = (userInputValues.rowGapVal !== '') ? userInputValues.rowGapVal : currentValues[0];
let colGap = (userInputValues.colGapVal !== '') ? userInputValues.colGapVal : currentValues[1];
// check if using the default values - remove unit abberviation at end
rowGap = rowGap.replace(/px|%/,'');
colGap = colGap.replace(/px|%/,'');
// Set value and unit together in object
const storeUserValues = {
gridRowGap: `${rowGap}${userInputValues.rowGapUnit}`,
gridColGap: `${colGap}${userInputValues.colGapUnit}`,
};
const valueString = `${storeUserValues.gridRowGap} ${storeUserValues.gridColGap}`;
assignStyles(propName, valueString);
}
// Set Justify Items / Align Items propertys
function setItemAndContentAlignment(control) {
const selectControl = document.getElementById(control.id);
const selectedOption = selectControl.options[selectControl.selectedIndex].value;
let propName;
switch (control.id) {
case 'justify-items-sel':
propName = 'justify-items';
break;
case 'align-items-sel':
propName = 'align-items';
break;
case 'justify-content-sel':
propName = 'justify-content';
break;
case 'align-content-sel':
propName = 'align-content';
break;
default:
break;
}
assignStyles(propName, selectedOption);
}
function setBasicDefaults() {
const gridDefaults = {
'grid-template-columns': '1fr 150px 1fr',
'grid-template-rows': '180px 100px',
'grid-auto-rows': '50px',
'grid-gap': '5px 5px',
};
Object.keys(gridDefaults).forEach((key) => {
const [ propName, valueString ] = [key, gridDefaults[key]]
assignStyles(propName, valueString)
});
}
function addControl() {
// if e.target.id.beginsWith col
// set control with ids on input and sel with that id.
// check length of current parent container to get numeric ID
// use this for add row and column.
// out same control__row
}
function clearFields() {
const gridItems = Array.from(document.getElementsByClassName('grid__item'));
gridContainer.setAttribute(`style`,
`grid-template-columns: initial;
grid-template-rows: initial;
grid-auto-columns: initial;
grid-auto-rows: initial;
grid-gap: initial;`
);
gridItems.forEach((gridItem) => {
gridItem.setAttribute(`style`,
`grid-area: initial;`
);
})
}
setBasicDefaults();
addListeners();
// --------------------------------------
// Toggle Views
const toggleControls = document.querySelector('.toggle-controls');
const containerControls = document.querySelector('.grid-controls--container');
const itemControls = document.querySelector('.grid-controls--items');
toggleControls.addEventListener('click', function(){
containerControls.classList.toggle('slide');
itemControls.classList.toggle('slide');
});
const toggleView = document.querySelector('.toggle-view');
const singleView = document.querySelector('.single-input');
const fieldsCol = document.querySelector('.multiple-cols');
const fieldsRow = document.querySelector('.multiple-rows');
const fieldsGap = document.querySelector('.multiple-gaps');
const allLines = Array.from(document.getElementsByClassName('seperator'));
toggleView.addEventListener('click', function(){
fieldsCol.classList.toggle('hidden');
fieldsRow.classList.toggle('hidden');
fieldsGap.classList.toggle('hidden');
singleView.classList.toggle('visible');
allLines.forEach((seperator) => {
seperator.classList.toggle('hidden');
})
});
// Reset values on user input whens hidden.
As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.
Also see: Tab Triggers