App :: keyboard navigation added

left arrow — hide sidebar
right arrow — show sidebar
up arrow — previous block
down arrow — next block
This commit is contained in:
mertJF
2020-05-29 00:46:45 +03:00
parent 2a0c4204a8
commit 2c321175c6
20 changed files with 237 additions and 45 deletions

View File

@@ -6,6 +6,12 @@ import getBlock from './blocks';
import getIcons from './icons';
const iconList = getIcons();
const blockListArr = [];
Object.entries(iconList).forEach(([type, icons]) => {
Object.keys(icons).map(name => blockListArr.push(`${name},${type}`));
});
const themeList = ["indigo", "orange", "teal", "red", "purple", "pink", "blue", "green"];
const desktopIcon = (
@@ -88,6 +94,7 @@ class App extends Component {
copied: false,
sidebar: true,
codeView: false,
currentKeyCode: null,
view: 'desktop',
theme: 'indigo',
blockType: 'Blog',
@@ -103,6 +110,7 @@ class App extends Component {
this.toggleSidebar = this.toggleSidebar.bind(this);
this.toggleView = this.toggleView.bind(this);
this.copyToClipboard = this.copyToClipboard.bind(this);
this.keyboardNavigation = this.keyboardNavigation.bind(this);
this.markupRef = React.createRef();
this.textareaRef = React.createRef();
this.sidebarRef = React.createRef();
@@ -110,21 +118,12 @@ class App extends Component {
}
componentDidMount() {
window.focus();
const iframe = document.querySelector('iframe');
document.addEventListener('keydown', this.keyboardNavigation);
}
hideSidebar() {
const sidebar = this.sidebarRef.current;
const opener = this.openerRef.current;
let iframeMouseOver = false;
window.focus();
window.addEventListener('blur', () => {
if (iframeMouseOver) {
this.setState({ sidebar: false });
}
});
iframe.addEventListener('mouseover', () => iframeMouseOver = true);
iframe.addEventListener('mouseout', () => iframeMouseOver = false);
document.addEventListener('click', (e) => {
if (e.target === opener) {
@@ -137,11 +136,77 @@ class App extends Component {
});
}
keyboardNavigation(e) {
const { blockType, blockName } = this.state;
const blockStringFormat = `${blockName},${blockType}`;
const keyCode = e.which || e.keyCode;
switch (keyCode) {
case 40: // Down
e.preventDefault();
blockListArr.forEach((block, index) => {
if (block === blockStringFormat) {
const newActiveBlock = index + 1 <= blockListArr.length - 1 ? blockListArr[index + 1].split(',') : blockListArr[0].split(',');
const newBlockName = newActiveBlock[0];
const newBlockType = newActiveBlock[1];
const newBlockNode = document.querySelector(`.block-item[block-name="${newBlockName}"]`);
if (newBlockNode) newBlockNode.focus();
this.setState({
blockType: newBlockType,
blockName: newBlockName,
codeView: false,
currentKeyCode: 40
});
}
});
break;
case 37: // Left
e.preventDefault();
this.setState({ sidebar: false, currentKeyCode: 37 });
break;
case 39: // Right
e.preventDefault();
this.setState({ sidebar: true, currentKeyCode: 39 });
break;
case 38: // Up
e.preventDefault();
blockListArr.forEach((block, index) => {
if (block === blockStringFormat) {
const newActiveBlock = index - 1 >= 0 ? blockListArr[index - 1].split(',') : blockListArr[blockListArr.length - 1].split(',');
const newBlockName = newActiveBlock[0];
const newBlockType = newActiveBlock[1];
const newBlockNode = document.querySelector(`.block-item[block-name="${newBlockName}"]`);
if (newBlockNode) newBlockNode.focus();
this.setState({
blockType: newBlockType,
blockName: newBlockName,
codeView: false,
currentKeyCode: 38
});
}
});
break;
default:
return;
}
setTimeout(() => {
if (keyCode === 37 || keyCode === 38 || keyCode === 39 || keyCode === 40) {
this.setState({ currentKeyCode: null })
}
}, 200);
}
changeMode() {
this.setState({ darkMode: !this.state.darkMode })
}
handleContentDidMount() {
const iframe = document.querySelector('iframe');
iframe.contentWindow.document.addEventListener('keydown', this.keyboardNavigation);
iframe.contentWindow.document.addEventListener('click', () => this.setState({ sidebar: false }));
setTimeout(() => {
this.setState({
ready: true,
@@ -187,7 +252,6 @@ class App extends Component {
blockType, blockName,
codeView: false
});
}
changeTheme(e) {
@@ -209,7 +273,7 @@ class App extends Component {
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>
<button key={k} data-theme={t} onKeyDown={this.keyboardNavigation} className={`theme-button bg-${t}-500${theme === t ? ' is-active' : ''}`} onClick={this.changeTheme}></button>
)
}
@@ -219,7 +283,7 @@ class App extends Component {
<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>)}
{Object.entries(icons).map(icon => <button key={icon[0]} tabIndex="0" onClick={this.changeBlock} className={`block-item${icon[0] === blockName ? ' is-active': ''}`} block-type={type} block-name={icon[0]}>{icon[1]}</button>)}
</div>
</div>
);
@@ -251,7 +315,7 @@ class App extends Component {
}
render() {
const { darkMode, theme, blockName, blockType, sidebar, view, copied } = this.state;
const { darkMode, theme, blockName, blockType, sidebar, view, copied, currentKeyCode } = this.state;
return (
<div className={`app${darkMode ? ' dark-mode' : ''}${sidebar ? ' has-sidebar' : ''} ${theme} ${view}`}>
<textarea className="copy-textarea" ref={this.textareaRef} />
@@ -335,9 +399,33 @@ class App extends Component {
</svg>
GitHub
</a>
<div className="keyboard-nav">
<div className={`k-up keyboard-button${currentKeyCode === 38 ? ' is-active' : ''}`} data-info="Previous block">
<svg stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" viewBox="0 0 24 24">
<path d="M12 19V5M5 12l7-7 7 7"/>
</svg>
</div>
<div className="keyboard-nav-row">
<div className={`k-left keyboard-button${currentKeyCode === 37 ? ' is-active' : ''}`} data-info="Hide sidebar">
<svg stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" viewBox="0 0 24 24">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</svg>
</div>
<div className={`k-down keyboard-button${currentKeyCode === 40 ? ' is-active' : ''}`} data-info="Next block">
<svg stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" viewBox="0 0 24 24">
<path d="M12 5v14M19 12l-7 7-7-7"/>
</svg>
</div>
<div className={`k-right keyboard-button${currentKeyCode === 39 ? ' is-active' : ''}`} data-info="Show sidebar">
<svg stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" viewBox="0 0 24 24">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</div>
</div>
</div>
</div>
);
}
}
export default App;
export default App;

View File

@@ -19,7 +19,7 @@ function DarkFooterA(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span className="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<p className="mt-2 text-sm text-gray-700">
Air plant banjo lyft occupy retro adaptogen indego
@@ -107,7 +107,7 @@ function DarkFooterA(props) {
<div className="bg-gray-800">
<div className="container mx-auto py-4 px-5 flex flex-wrap flex-col sm:flex-row">
<p className="text-gray-600 text-sm text-center sm:text-left">
© 2020 madde
© 2020 tailblocks
<a href="https://twitter.com/knyttneve" rel="noopener noreferrer" className="text-gray-500 ml-1" target="_blank">
@knyttneve
</a>

View File

@@ -19,7 +19,7 @@ function DarkFooterB(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span className="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<p className="mt-2 text-sm text-gray-700">
Air plant banjo lyft occupy retro adaptogen indego
@@ -107,7 +107,7 @@ function DarkFooterB(props) {
<div className="bg-gray-800">
<div className="container mx-auto py-4 px-5 flex flex-wrap flex-col sm:flex-row">
<p className="text-gray-600 text-sm text-center sm:text-left">
© 2020 madde
© 2020 tailblocks
<a href="https://twitter.com/knyttneve" className="text-gray-500 ml-1" rel="noopener noreferrer" target="_blank">
@knyttneve
</a>

View File

@@ -200,7 +200,7 @@ function DarkFooterC(props) {
<div className="bg-gray-800">
<div className="container mx-auto py-4 px-5 flex flex-wrap flex-col sm:flex-row">
<p className="text-gray-600 text-sm text-center sm:text-left">
© 2020 madde
© 2020 tailblocks
<a href="https://twitter.com/knyttneve" className="text-gray-500 ml-1" target="_blank" rel="noopener noreferrer">
@knyttneve
</a>

View File

@@ -18,10 +18,10 @@ function DarkFooterD(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span class="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<p className="text-sm text-gray-600 sm:ml-4 sm:pl-4 sm:border-l-2 sm:border-gray-800 sm:py-2 sm:mt-0 mt-4">
© 2020 madde
© 2020 tailblocks
<a href="https://twitter.com/knyttneve" className="text-gray-500 ml-1" target="_blank" rel="noopener noreferrer">
@knyttneve
</a>

View File

@@ -99,10 +99,10 @@ function DarkFooterE(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span class="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<p className="text-sm text-gray-600 sm:ml-6 sm:mt-0 mt-4">
© 2020 madde
© 2020 tailblocks
<a href="https://twitter.com/knyttneve" className="text-gray-500 ml-1" target="_blank" rel="noopener noreferrer">
@knyttneve
</a>

View File

@@ -19,7 +19,7 @@ function LightFooterA(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span className="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<p className="mt-2 text-sm text-gray-500">
Air plant banjo lyft occupy retro adaptogen indego
@@ -107,7 +107,7 @@ function LightFooterA(props) {
<div className="bg-gray-200">
<div className="container mx-auto py-4 px-5 flex flex-wrap flex-col sm:flex-row">
<p className="text-gray-500 text-sm text-center sm:text-left">
© 2020 madde
© 2020 tailblocks
<a href="https://twitter.com/knyttneve" rel="noopener noreferrer" className="text-gray-600 ml-1" target="_blank">
@knyttneve
</a>

View File

@@ -19,7 +19,7 @@ function LightFooterB(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span className="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<p className="mt-2 text-sm text-gray-500">
Air plant banjo lyft occupy retro adaptogen indego
@@ -107,7 +107,7 @@ function LightFooterB(props) {
<div className="bg-gray-200">
<div className="container mx-auto py-4 px-5 flex flex-wrap flex-col sm:flex-row">
<p className="text-gray-500 text-sm text-center sm:text-left">
© 2020 madde
© 2020 tailblocks
<a href="https://twitter.com/knyttneve" rel="noopener noreferrer" className="text-gray-600 ml-1" target="_blank">
@knyttneve
</a>

View File

@@ -200,7 +200,7 @@ function LightFooterC(props) {
<div className="bg-gray-200">
<div className="container mx-auto py-4 px-5 flex flex-wrap flex-col sm:flex-row">
<p className="text-gray-500 text-sm text-center sm:text-left">
© 2020 madde
© 2020 tailblocks
<a href="https://twitter.com/knyttneve" className="text-gray-600 ml-1" target="_blank" rel="noopener noreferrer">
@knyttneve
</a>

View File

@@ -18,10 +18,10 @@ function LightFooterD(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span class="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<p className="text-sm text-gray-500 sm:ml-4 sm:pl-4 sm:border-l-2 sm:border-gray-200 sm:py-2 sm:mt-0 mt-4">
© 2020 madde
© 2020 tailblocks
<a href="https://twitter.com/knyttneve" className="text-gray-600 ml-1" rel="noopener noreferrer" target="_blank">
@knyttneve
</a>

View File

@@ -99,10 +99,10 @@ function LightFooterE(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span class="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<p className="text-sm text-gray-500 sm:ml-6 sm:mt-0 mt-4">
© 2020 madde
© 2020 tailblocks
<a href="https://twitter.com/knyttneve" rel="noopener noreferrer" className="text-gray-600 ml-1" target="_blank">
@knyttneve
</a>

View File

@@ -18,7 +18,7 @@ function DarkHeaderA(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span className="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<nav className="md:ml-auto flex flex-wrap items-center text-base justify-center">
<a href className="mr-5 hover:text-white">First Link</a>

View File

@@ -18,7 +18,7 @@ function DarkHeaderB(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span className="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<nav className="md:mr-auto md:ml-4 md:py-1 md:pl-4 md:border-l md:border-gray-700 flex flex-wrap items-center text-base justify-center">
<a href className="mr-5 hover:text-white">First Link</a>

View File

@@ -24,7 +24,7 @@ function DarkHeaderC(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span className="ml-3 text-xl xl:block lg:hidden">madde</span>
<span className="ml-3 text-xl xl:block lg:hidden">tailblocks</span>
</a>
<div className="lg:w-2/5 inline-flex lg:justify-end ml-5 lg:ml-0">
<button className="inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0">

View File

@@ -18,7 +18,7 @@ function DarkHeaderD(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span class="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<nav className="md:ml-auto md:mr-auto flex flex-wrap items-center text-base justify-center">
<a href className="mr-5 hover:text-white">First Link</a>

View File

@@ -18,7 +18,7 @@ function LightHeaderA(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span class="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<nav className="md:ml-auto flex flex-wrap items-center text-base justify-center">
<a href className="mr-5 hover:text-gray-900">First Link</a>

View File

@@ -18,7 +18,7 @@ function LightHeaderB(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span class="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<nav className="md:mr-auto md:ml-4 md:py-1 md:pl-4 md:border-l md:border-gray-400 flex flex-wrap items-center text-base justify-center">
<a href className="mr-5 hover:text-gray-900">First Link</a>

View File

@@ -24,7 +24,7 @@ function LightHeaderC(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span class="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<div className="lg:w-2/5 inline-flex lg:justify-end ml-5 lg:ml-0">
<button className="inline-flex items-center bg-gray-200 border-0 py-1 px-3 focus:outline-none hover:bg-gray-300 rounded text-base mt-4 md:mt-0">

View File

@@ -18,7 +18,7 @@ function LightHeaderD(props) {
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span className="ml-3 text-xl">madde</span>
<span className="ml-3 text-xl">tailblocks</span>
</a>
<nav className="md:ml-auto md:mr-auto flex flex-wrap items-center text-base justify-center">
<a href className="mr-5 hover:text-gray-900">First Link</a>

View File

@@ -617,12 +617,116 @@ pre, code {
}
}
.keyboard-nav {
position: fixed;
right: 158px;
bottom: 18px;
display: flex;
flex-direction: column;
align-items: center;
}
.keyboard-nav-row {
display: flex;
}
.keyboard-button {
width: 20px;
height: 20px;
opacity: 1;
border-radius: 4px;
flex-shrink: 0;
margin: 1px;
opacity: 0.5;
background-color: var(--base-300);
color: var(--solid-900);
padding: 3px;
position: relative;
}
.keyboard-nav:hover .keyboard-button:not(:hover) {
opacity: .2;
}
.keyboard-button:hover {
opacity: 1;
}
.keyboard-button:before {
content: attr(data-info);
position: absolute;
color: var(--solid-900);
pointer-events: none;
font-size: 8px;
text-transform: uppercase;
transition: opacity .3s;
text-align: center;
white-space: nowrap;
padding: 1px 4px;
background-color: var(--base-200);
border-radius: 4px;
opacity: 0;
pointer-events: none;
z-index: -1;
}
.keyboard-button.is-active {
animation-name: keyboard;
animation-duration: .2s;
animation-fill-mode: forwards;
}
@keyframes keyboard {
0% {
box-shadow: 0;
}
50% {
box-shadow: 0 0 0 5px var(--main-500);
z-index: 1;
}
}
.keyboard-button:hover:before {
opacity: 1;
z-index: 1;
}
.k-up:before {
left: 50%;
transform: translateX(-50%);
top: 24px;
}
.k-down:before {
left: 50%;
transform: translateX(-50%);
top: -18px;
}
.k-left:before {
right: -70px;
top: 4px;
}
.k-right:before {
left: -76px;
top: 4px;
}
.keyboard-button:hover:before {
opacity: 1;
}
@media (max-width: 960px) {
.copy-to-clipboard {
display: none;
}
}
.keyboard-nav{
display: none;
}
}
@media (max-width: 760px) {
.copy-the-block {