Skip to content

Commit

Permalink
feat: Add shortcuts component to show a help of hotkeys
Browse files Browse the repository at this point in the history
  • Loading branch information
cyaiox committed Jan 30, 2024
1 parent d767a49 commit 4277b9e
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
.Shortcuts {
--shortcuts-bottom: 8px;
--shortcuts-right: 8px;
--shortcuts-button-height: 30px;
--shortcuts-button-width: 30px;

position: absolute;
bottom: 8px;
right: 8px;
z-index: 1;
}

.Shortcuts .Buttons {
display: flex;
flex-direction: row;
gap: 8px;
}

.Shortcuts .Buttons .Button {
display: flex;
align-items: center;
justify-content: center;
height: 30px;
width: 30px;
padding: 5px;
border-radius: 4px;
background-color: var(--base-16);
}

.Shortcuts .Buttons .Button:hover {
background-color: var(--base-17);
}

.Shortcuts .Buttons .Button.Active {
background-color: var(--base-02);
}

.Shortcuts .Buttons .Button.Active svg {
color: var(--base-21);
}

.Shortcuts .Buttons .Button svg {
color: var(--base-02);
}

.Shortcuts .Buttons .ZoomButtons {
display: flex;
}

.Shortcuts .Buttons .ZoomButtons .Button:first-of-type {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: 1.5px solid var(--base-19);
}

.Shortcuts .Buttons .ZoomButtons .Button:last-of-type {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: 1.5px solid var(--base-19);
}

.Shortcuts > .Overlay {
display: flex;
flex-direction: column;
position: absolute;
width: 298px;
overflow-y: auto;
right: 0;
bottom: calc(var(--shortcuts-bottom) + var(--shortcuts-button-height) + 8px);
background-color: var(--base-19);
padding: 13px 12px;
border-radius: 4px;
gap: 16px;
}

.Shortcuts > .Overlay h2.Header {
font-size: 14px;
font-weight: 500;
line-height: 17px;
color: var(--base-01);
margin-bottom: 0;
}

.Shortcuts > .Overlay h5.SubHeader,
.Shortcuts > .Overlay .Item .Title,
.Shortcuts > .Overlay .Item .Description,
.Shortcuts > .Overlay .Item .Description .Key {
font-size: 11px;
font-weight: 500;
color: var(--base-01);
margin-bottom: 0;
}

.Shortcuts > .Overlay h5.SubHeader {
color: var(--base-09);
text-transform: uppercase;
}

.Shortcuts > .Overlay .Items {
display: flex;
flex-direction: column;
gap: 8px;
}

.Shortcuts > .Overlay .Items .Item {
display: flex;
gap: 4px;
padding: 8px 0;
border-bottom: 1px solid var(--base-18);
}

.Shortcuts > .Overlay .Items .Item:last-of-type {
border-bottom: none;
}

.Shortcuts > .Overlay .Item .Title {
display: flex;
flex: 1;
align-items: center;
}

.Shortcuts > .Overlay .Item .Description {
display: flex;
flex: 1;
align-items: center;
gap: 4px;
line-height: 14px;
}

.Shortcuts > .Overlay .Item .Description .Key {
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
height: 18px;
border-radius: 4px;
background-color: var(--base-13);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import React, { useCallback, useMemo } from 'react'
import cx from 'classnames'
import {
MdOutlineZoomIn as ZoomInIcon,
MdOutlineZoomOut as ZoomOutIcon,
MdKeyboard as KeyboardIcon
} from 'react-icons/md'
import { HiOutlineViewfinderCircle as ResetCameraIcon } from 'react-icons/hi2'

import { useContainerSize } from '../../../hooks/useContainerSize'
import { useOutsideClick } from '../../../hooks/useOutsideClick'
import { Button } from '../../Button'
import { InfoTooltip } from '../../ui'
import { Props } from './types'

import './Shortcuts.css'

const ICON_SIZE = 18

const Shortcuts: React.FC<Props> = ({ canvas, onResetCamera, onZoomIn, onZoomOut }) => {
const [showShortcuts, setShowShortcuts] = React.useState(false)
const { height } = useContainerSize(canvas)

const maxOverlayHeight = useMemo(() => {
return (height ?? 600) - 60
}, [height])

const handleToggleShortcutsOverlay = useCallback(
(e: React.MouseEvent<HTMLButtonElement> | MouseEvent) => {
e.preventDefault()
e.stopPropagation()
setShowShortcuts((value) => !value)
},
[showShortcuts, setShowShortcuts]
)

const overlayRef = useOutsideClick(handleToggleShortcutsOverlay)

return (
<div className="Shortcuts">
<div className="Buttons">
<Button onClick={onResetCamera}>
<ResetCameraIcon size={ICON_SIZE} />
</Button>
<div className="ZoomButtons">
<Button onClick={onZoomIn}>
<ZoomInIcon size={ICON_SIZE} />
</Button>
<Button onClick={onZoomOut}>
<ZoomOutIcon size={ICON_SIZE} />
</Button>
</div>
<InfoTooltip
text="View Shortcuts"
trigger={
<Button className={cx({ Active: showShortcuts })} onClick={handleToggleShortcutsOverlay}>
<KeyboardIcon size={ICON_SIZE} />
</Button>
}
openOnTriggerMouseEnter={!showShortcuts}
closeOnTriggerClick={true}
position="top center"
/>
</div>
{showShortcuts && (
<div ref={overlayRef} className="Overlay" style={{ maxHeight: maxOverlayHeight }}>
<h2 className="Header">Shortcuts</h2>
<div className="Items">
<h5 className="SubHeader">General</h5>
<div className="Item">
<div className="Title">Pan Camera</div>
<div className="Description">
<span className="Key">W</span>
<span className="Key">A</span>
<span className="Key">S</span>
<span className="Key">D</span>
</div>
</div>
<div className="Item">
<div className="Title">Select Multiple Items</div>
<div className="Description">
Hold<span className="Key">ctrl</span>and click
</div>
</div>
<div className="Item">
<div className="Title">Save</div>
<div className="Description">
<span className="Key">ctrl</span>+<span className="Key">S</span>
</div>
</div>
<div className="Item">
<div className="Title">Undo</div>
<div className="Description">
<span className="Key">ctrl</span>+<span className="Key">Z</span>
</div>
</div>
<div className="Item">
<div className="Title">Redo</div>
<div className="Description">
<span className="Key">ctrl</span>+<span className="Key">Y</span>
</div>
</div>
<div className="Item">
<div className="Title">Copy</div>
<div className="Description">
<span className="Key">ctrl</span>+<span className="Key">C</span>
</div>
</div>
<div className="Item">
<div className="Title">Paste</div>
<div className="Description">
<span className="Key">ctrl</span>+<span className="Key">V</span>
</div>
</div>
<div className="Item">
<div className="Title">Reset Camera</div>
<div className="Description">
<span className="Key">space</span>
</div>
</div>
</div>
<div className="Items">
<h5 className="SubHeader">Item Selected</h5>
<div className="Item">
<div className="Title">Snap to Grid</div>
<div className="Description">
Hold<span className="Key">shift</span>
</div>
</div>
<div className="Item">
<div className="Title">Toggle Positioning</div>
<div className="Description">
<span className="Key">M</span>
</div>
</div>
<div className="Item">
<div className="Title">Toggle Rotating</div>
<div className="Description">
<span className="Key">R</span>
</div>
</div>
<div className="Item">
<div className="Title">Toggle Scaling</div>
<div className="Description">
<span className="Key">X</span>
</div>
</div>
<div className="Item">
<div className="Title">Duplicate</div>
<div className="Description">
<span className="Key">ctrl</span>+<span className="Key">D</span>
</div>
</div>
<div className="Item">
<div className="Title">Delete</div>
<div className="Description">
<span className="Key">del</span>or<span className="Key">backspace</span>
</div>
</div>
</div>
</div>
)}
</div>
)
}

export default React.memo(Shortcuts)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Shortcuts from './Shortcuts'
export { Shortcuts }
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Props {
canvas: React.RefObject<HTMLCanvasElement>
onResetCamera: () => void
onZoomIn: () => void
onZoomOut: () => void
}

0 comments on commit 4277b9e

Please sign in to comment.