mirror of
https://github.com/mertJF/tailblocks.git
synced 2025-12-08 17:03:24 +00:00
314 lines
10 KiB
JavaScript
314 lines
10 KiB
JavaScript
import React, { Component } from 'react';
|
||
import Frame from 'react-frame-component';
|
||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||
import { vs2015, docco } from 'react-syntax-highlighter/dist/esm/styles/hljs';
|
||
import getBlock from './blocks';
|
||
import getIcons from './icons';
|
||
|
||
const iconList = getIcons();
|
||
const themeList = ["indigo", "orange", "teal", "red", "purple", "pink", "blue", "green"];
|
||
|
||
const desktopIcon = (
|
||
<svg
|
||
stroke="currentColor"
|
||
strokeWidth={2}
|
||
fill="none"
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
viewBox="0 0 24 24"
|
||
>
|
||
<rect x={2} y={3} width={20} height={14} rx={2} ry={2} />
|
||
<path d="M8 21h8m-4-4v4" />
|
||
</svg>
|
||
);
|
||
|
||
const phoneIcon = (
|
||
<svg
|
||
viewBox="0 0 24 24"
|
||
stroke="currentColor"
|
||
strokeWidth={2}
|
||
fill="none"
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
>
|
||
<rect x={5} y={2} width={14} height={20} rx={2} ry={2} />
|
||
<path d="M12 18h.01" />
|
||
</svg>
|
||
);
|
||
|
||
const tabletIcon = (
|
||
<svg
|
||
viewBox="0 0 24 24"
|
||
stroke="currentColor"
|
||
strokeWidth={2}
|
||
fill="none"
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
>
|
||
<rect x={4} y={2} width={16} height={20} rx={2} ry={2} />
|
||
<path d="M12 18h.01" />
|
||
</svg>
|
||
);
|
||
|
||
const clipboardIcon = (
|
||
<svg
|
||
viewBox="0 0 25 24"
|
||
stroke="currentColor"
|
||
strokeWidth={2}
|
||
fill="none"
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
>
|
||
<path d="M19.914 1h-18v19"/>
|
||
<path d="M6 5v18h18V5z"/>
|
||
</svg>
|
||
);
|
||
|
||
const viewList = [
|
||
{
|
||
icon: desktopIcon,
|
||
name: 'desktop'
|
||
},
|
||
{
|
||
icon: tabletIcon,
|
||
name: 'tablet'
|
||
},
|
||
{
|
||
icon: phoneIcon,
|
||
name: 'phone'
|
||
}
|
||
]
|
||
|
||
class App extends Component {
|
||
constructor(props) {
|
||
super(props);
|
||
this.state = {
|
||
ready: false,
|
||
darkMode: false,
|
||
copied: false,
|
||
sidebar: true,
|
||
codeView: false,
|
||
view: 'desktop',
|
||
theme: 'indigo',
|
||
blockType: 'Blog',
|
||
blockName: 'BlogA',
|
||
markup: ''
|
||
}
|
||
|
||
this.changeMode = this.changeMode.bind(this);
|
||
this.changeTheme = this.changeTheme.bind(this);
|
||
this.changeBlock = this.changeBlock.bind(this);
|
||
this.handleContentDidMount = this.handleContentDidMount.bind(this);
|
||
this.changeView = this.changeView.bind(this);
|
||
this.toggleSidebar = this.toggleSidebar.bind(this);
|
||
this.toggleView = this.toggleView.bind(this);
|
||
this.copyToClipboard = this.copyToClipboard.bind(this);
|
||
this.markupRef = React.createRef();
|
||
this.textareaRef = React.createRef();
|
||
}
|
||
|
||
changeMode() {
|
||
this.setState({ darkMode: !this.state.darkMode })
|
||
}
|
||
|
||
handleContentDidMount() {
|
||
setTimeout(() => {
|
||
this.setState({
|
||
ready: true,
|
||
markup: this.markupRef.current.innerHTML
|
||
})
|
||
}, 400);
|
||
}
|
||
|
||
beautifyHTML(codeStr) {
|
||
const process = (str) => {
|
||
let div = document.createElement('div');
|
||
div.innerHTML = str.trim();
|
||
return format(div, 0).innerHTML.trim();
|
||
}
|
||
|
||
const format = (node, level) => {
|
||
let indentBefore = new Array(level++ + 1).join(' '),
|
||
indentAfter = new Array(level - 1).join(' '),
|
||
textNode;
|
||
|
||
for (let i = 0; i < node.children.length; i++) {
|
||
textNode = document.createTextNode('\n' + indentBefore);
|
||
node.insertBefore(textNode, node.children[i]);
|
||
|
||
format(node.children[i], level);
|
||
|
||
if (node.lastElementChild === node.children[i]) {
|
||
textNode = document.createTextNode('\n' + indentAfter);
|
||
node.appendChild(textNode);
|
||
}
|
||
}
|
||
|
||
return node;
|
||
}
|
||
return process(codeStr);
|
||
}
|
||
|
||
changeBlock(e) {
|
||
const { currentTarget } = e;
|
||
const blockType = currentTarget.getAttribute('block-type');
|
||
const blockName = currentTarget.getAttribute('block-name');
|
||
this.setState({
|
||
blockType, blockName,
|
||
codeView: false
|
||
});
|
||
|
||
}
|
||
|
||
changeTheme(e) {
|
||
const { currentTarget } = e;
|
||
const theme = currentTarget.getAttribute('data-theme');
|
||
this.setState({ theme });
|
||
}
|
||
|
||
changeView(e) {
|
||
const { currentTarget } = e;
|
||
const view = currentTarget.getAttribute('data-view');
|
||
this.setState({ view, codeView: false });
|
||
}
|
||
|
||
toggleView(e) {
|
||
this.setState({ codeView: !this.state.codeView, view: 'desktop', markup: this.markupRef.current.innerHTML })
|
||
}
|
||
|
||
themeListRenderer() {
|
||
const { theme } = this.state;
|
||
return themeList.map((t, k) =>
|
||
<button key={k} data-theme={t} className={`theme-button bg-${t}-500${theme === t ? ' is-active' : ''}`} onClick={this.changeTheme}></button>
|
||
)
|
||
}
|
||
|
||
listRenderer() {
|
||
const { blockName } = this.state;
|
||
return Object.entries(iconList).map(([type, icons]) =>
|
||
<div className="blocks" key={type}>
|
||
<div className="block-category">{type}</div>
|
||
<div className="block-list">
|
||
{Object.entries(icons).map(icon => <button key={icon[0]} onClick={this.changeBlock} className={`block-item${icon[0] === blockName ? ' is-active': ''}`} block-type={type} block-name={icon[0]}>{icon[1]}</button>)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
viewModeRenderer() {
|
||
const { view } = this.state;
|
||
return viewList.map((v, k) => <button key={k} className={`device${view === v.name ? ' is-active' : ''}`} data-view={v.name} onClick={this.changeView}>{v.icon}</button>);
|
||
}
|
||
|
||
toggleSidebar() {
|
||
this.setState({ sidebar: !this.state.sidebar });
|
||
}
|
||
|
||
copyToClipboard() {
|
||
const code = this.beautifyHTML(this.state.markup);
|
||
var input = document.createElement('textarea');
|
||
input.innerHTML = code;
|
||
document.body.appendChild(input);
|
||
input.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(input);
|
||
this.setState({copied: true});
|
||
setTimeout(() => {
|
||
this.setState({
|
||
copied: false
|
||
})
|
||
}, 2000);
|
||
}
|
||
|
||
render() {
|
||
const { darkMode, theme, blockName, blockType, sidebar, view, copied } = this.state;
|
||
return (
|
||
<div className={`app${darkMode ? ' dark-mode' : ''}${sidebar ? ' has-sidebar' : ''} ${theme} ${view}`}>
|
||
<textarea className="copy-textarea" ref={this.textareaRef} />
|
||
<aside className="sidebar">
|
||
{this.listRenderer()}
|
||
</aside>
|
||
<div className="toolbar">
|
||
<button className="opener" onClick={this.toggleSidebar}>TAILBLOCKS</button>
|
||
{this.state.codeView &&
|
||
<div className="clipboard-wrapper">
|
||
<button className="copy-the-block copy-to-clipboard" onClick={this.copyToClipboard}>
|
||
{clipboardIcon}
|
||
<span>COPY TO CLIPBOARD</span>
|
||
</button>
|
||
<span className={`clipboard-tooltip${copied ? ' is-copied ' : ''}`} >Copied!</span>
|
||
</div>
|
||
}
|
||
<button className="copy-the-block" onClick={this.toggleView}>
|
||
{!this.state.codeView ?
|
||
<svg
|
||
fill="none"
|
||
stroke="currentColor"
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
strokeWidth="2"
|
||
viewBox="0 0 24 24"
|
||
>
|
||
<path d="M16 18L22 12 16 6"></path>
|
||
<path d="M8 6L2 12 8 18"></path>
|
||
</svg>
|
||
:
|
||
<svg
|
||
fill="none"
|
||
stroke="currentColor"
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
strokeWidth="2"
|
||
className="css-i6dzq1"
|
||
viewBox="0 0 24 24"
|
||
>
|
||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||
<circle cx="12" cy="12" r="3"></circle>
|
||
</svg>
|
||
}
|
||
<span>{!this.state.codeView ? 'VIEW CODE': 'PREVIEW'}</span>
|
||
</button>
|
||
<div className="switcher">
|
||
{this.themeListRenderer()}
|
||
</div>
|
||
{this.viewModeRenderer()}
|
||
<button className="mode" onClick={this.changeMode}></button>
|
||
</div>
|
||
<div className="markup" ref={this.markupRef}>{getBlock({ theme, darkMode })[blockType][blockName]}</div>
|
||
<main className="main" style={{ opacity: this.state.ready ? '1' : '0' }}>
|
||
<div className={`view${this.state.codeView ? ' show-code' : ''}`}>
|
||
<Frame
|
||
contentDidMount={this.handleContentDidMount}
|
||
contentDidUpdate={this.handleContentDidUpdate}
|
||
head={
|
||
<>
|
||
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/1.4.6/tailwind.min.css" rel="stylesheet" />
|
||
{darkMode ? <style dangerouslySetInnerHTML={{__html:`img { filter: invert(1); mix-blend-mode: color-dodge }`}} /> : <style dangerouslySetInnerHTML={{__html:`img { filter: sepia(1) hue-rotate(180deg) opacity(.9) grayscale(.7) }`}} />}
|
||
</>
|
||
}
|
||
>
|
||
{getBlock({ theme, darkMode })[blockType][blockName]}
|
||
</Frame>
|
||
<div className="codes">
|
||
<SyntaxHighlighter language="html" style={darkMode ? vs2015 : docco} showLineNumbers>
|
||
{this.beautifyHTML(this.state.markup)}
|
||
</SyntaxHighlighter>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
<a href="https://github.com/mertJF/tailblocks" className="github" target="_blank" rel="noopener noreferrer">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||
<path
|
||
fill="currentColor"
|
||
d="M12 .5C5.37.5 0 5.78 0 12.292c0 5.211 3.438 9.63 8.205 11.188.6.111.82-.254.82-.567 0-.28-.01-1.022-.015-2.005-3.338.711-4.042-1.582-4.042-1.582-.546-1.361-1.335-1.725-1.335-1.725-1.087-.731.084-.716.084-.716 1.205.082 1.838 1.215 1.838 1.215 1.07 1.803 2.809 1.282 3.495.981.108-.763.417-1.282.76-1.577-2.665-.295-5.466-1.309-5.466-5.827 0-1.287.465-2.339 1.235-3.164-.135-.298-.54-1.497.105-3.121 0 0 1.005-.316 3.3 1.209.96-.262 1.98-.392 3-.398 1.02.006 2.04.136 3 .398 2.28-1.525 3.285-1.209 3.285-1.209.645 1.624.24 2.823.12 3.121.765.825 1.23 1.877 1.23 3.164 0 4.53-2.805 5.527-5.475 5.817.42.354.81 1.077.81 2.182 0 1.578-.015 2.846-.015 3.229 0 .309.21.678.825.56C20.565 21.917 24 17.495 24 12.292 24 5.78 18.627.5 12 .5z"
|
||
/>
|
||
</svg>
|
||
GitHub
|
||
</a>
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
|
||
export default App;
|