Merge pull request #34 from tldraw/refactor-toolbar

Refactor toolbar
This commit is contained in:
Steve Ruiz 2021-07-10 13:21:53 +01:00 committed by GitHub
commit 83c6fce2bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 537 additions and 258 deletions

View file

@ -0,0 +1,4 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5 2.49538C12.2239 2.49538 12 2.71923 12 2.99538V5.49559H9.49979C9.22364 5.49559 8.99979 5.71945 8.99979 5.99559C8.99979 6.27173 9.22364 6.49559 9.49979 6.49559H12.5C12.7761 6.49559 13 6.27173 13 5.99559V2.99538C13 2.71923 12.7761 2.49538 12.5 2.49538Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.69698 2.04877C6.62345 1.89773 5.52991 2.09968 4.58113 2.62417C3.63236 3.14867 2.87973 3.9673 2.43667 4.95673C1.99361 5.94616 1.8841 7.05278 2.12465 8.10985C2.3652 9.16693 2.94278 10.1172 3.77036 10.8175C4.59794 11.5177 5.63069 11.9301 6.713 11.9924C7.79531 12.0547 8.86855 11.7635 9.77101 11.1628C10.6735 10.5621 11.3563 9.68441 11.7165 8.66191C11.8083 8.40146 11.6715 8.11593 11.4111 8.02417C11.1506 7.93241 10.8651 8.06916 10.7733 8.32961C10.4851 9.14762 9.93888 9.84981 9.21691 10.3304C8.49493 10.811 7.63632 11.0439 6.77046 10.994C5.9046 10.9442 5.07839 10.6143 4.41631 10.0541C3.75424 9.49386 3.29217 8.73363 3.09972 7.88796C2.90728 7.04229 2.99488 6.15698 3.34934 5.36542C3.7038 4.57387 4.30591 3.91895 5.06494 3.49935C5.82398 3.07974 6.69882 2.91819 7.55765 3.03902C8.41649 3.15985 9.21279 3.55653 9.82658 4.16928L9.83745 4.17981L12.1576 6.35996C12.3588 6.54906 12.6753 6.53921 12.8644 6.33797C13.0535 6.13673 13.0436 5.8203 12.8424 5.63121L10.5276 3.4561C9.76111 2.69329 8.76794 2.19945 7.69698 2.04877Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,6 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 4.65555C2 4.37941 2.22386 4.15555 2.5 4.15555H12.2C12.4761 4.15555 12.7 4.37941 12.7 4.65555C12.7 4.93169 12.4761 5.15555 12.2 5.15555H2.5C2.22386 5.15555 2 4.93169 2 4.65555Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.27208 3C6.11885 3 5.97189 3.06087 5.86353 3.16923C5.75518 3.27758 5.6943 3.42454 5.6943 3.57778V4.15556H9.00542V3.57778C9.00542 3.42454 8.94454 3.27758 8.83619 3.16923C8.72783 3.06087 8.58087 3 8.42764 3H6.27208ZM10.0054 4.15556V3.57778C10.0054 3.15933 9.83919 2.75801 9.54329 2.46212C9.2474 2.16623 8.84609 2 8.42764 2H6.27208C5.85363 2 5.45232 2.16623 5.15642 2.46212C4.86053 2.75801 4.6943 3.15933 4.6943 3.57778V4.15556H3.57764C3.30149 4.15556 3.07764 4.37941 3.07764 4.65556V12.2C3.07764 12.6185 3.24387 13.0198 3.53976 13.3157C3.83565 13.6115 4.23696 13.7778 4.65541 13.7778H10.0443C10.4628 13.7778 10.8641 13.6115 11.16 13.3157C11.4559 13.0198 11.6221 12.6185 11.6221 12.2V4.65556C11.6221 4.37941 11.3982 4.15556 11.1221 4.15556H10.0054ZM4.07764 5.15556V12.2C4.07764 12.3532 4.13851 12.5002 4.24686 12.6086C4.35522 12.7169 4.50218 12.7778 4.65541 12.7778H10.0443C10.1975 12.7778 10.3445 12.7169 10.4529 12.6086C10.5612 12.5002 10.6221 12.3532 10.6221 12.2V5.15556H4.07764Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.27246 6.85001C6.5486 6.85001 6.77246 7.07386 6.77246 7.35001V10.5833C6.77246 10.8595 6.5486 11.0833 6.27246 11.0833C5.99632 11.0833 5.77246 10.8595 5.77246 10.5833V7.35001C5.77246 7.07386 5.99632 6.85001 6.27246 6.85001Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.42773 6.85001C8.70388 6.85001 8.92773 7.07386 8.92773 7.35001V10.5833C8.92773 10.8595 8.70388 11.0833 8.42773 11.0833C8.15159 11.0833 7.92773 10.8595 7.92773 10.5833V7.35001C7.92773 7.07386 8.15159 6.85001 8.42773 6.85001Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,4 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 2.49538C2.77614 2.49538 3 2.71923 3 2.99538V5.49559H5.50021C5.77636 5.49559 6.00021 5.71945 6.00021 5.99559C6.00021 6.27173 5.77636 6.49559 5.50021 6.49559H2.5C2.22386 6.49559 2 6.27173 2 5.99559V2.99538C2 2.71923 2.22386 2.49538 2.5 2.49538Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.30302 2.04877C8.37655 1.89773 9.47009 2.09968 10.4189 2.62417C11.3676 3.14867 12.1203 3.9673 12.5633 4.95673C13.0064 5.94616 13.1159 7.05278 12.8753 8.10985C12.6348 9.16693 12.0572 10.1172 11.2296 10.8175C10.4021 11.5177 9.36931 11.9301 8.287 11.9924C7.20469 12.0547 6.13145 11.7635 5.22899 11.1628C4.32653 10.5621 3.64374 9.68441 3.2835 8.66191C3.19174 8.40146 3.32849 8.11593 3.58894 8.02417C3.84939 7.93241 4.13492 8.06916 4.22668 8.32961C4.51488 9.14762 5.06112 9.84981 5.78309 10.3304C6.50507 10.811 7.36368 11.0439 8.22954 10.994C9.0954 10.9442 9.92161 10.6143 10.5837 10.0541C11.2458 9.49386 11.7078 8.73363 11.9003 7.88796C12.0927 7.04229 12.0051 6.15698 11.6507 5.36542C11.2962 4.57387 10.6941 3.91895 9.93506 3.49935C9.17602 3.07974 8.30118 2.91819 7.44235 3.03902C6.58351 3.15985 5.78721 3.55653 5.17342 4.16928L5.16255 4.17981L2.84239 6.35996C2.64115 6.54906 2.32472 6.53921 2.13562 6.33797C1.94653 6.13673 1.95637 5.8203 2.15761 5.63121L4.47241 3.4561C5.23889 2.69329 6.23206 2.19945 7.30302 2.04877Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,3 @@
export { default as Redo } from './redo'
export { default as Trash } from './trash'
export { default as Undo } from './undo'

27
components/icons/redo.tsx Normal file
View file

@ -0,0 +1,27 @@
import * as React from 'react'
function SvgRedo(props: React.SVGProps<SVGSVGElement>): JSX.Element {
return (
<svg
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12.5 2.495a.5.5 0 00-.5.5v2.5H9.5a.5.5 0 100 1h3a.5.5 0 00.5-.5v-3a.5.5 0 00-.5-.5z"
fill="#000"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.697 2.049a5 5 0 104.02 6.613.5.5 0 10-.944-.332 4 4 0 11-.946-4.16l.01.01 2.32 2.18a.5.5 0 00.685-.729l-2.314-2.175A5 5 0 007.697 2.05z"
fill="#000"
/>
</svg>
)
}
export default SvgRedo

View file

@ -0,0 +1,33 @@
import * as React from 'react'
function SvgTrash(props: React.SVGProps<SVGSVGElement>): JSX.Element {
return (
<svg
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2 4.656a.5.5 0 01.5-.5h9.7a.5.5 0 010 1H2.5a.5.5 0 01-.5-.5z"
fill="#000"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6.272 3a.578.578 0 00-.578.578v.578h3.311v-.578A.578.578 0 008.428 3H6.272zm3.733 1.156v-.578A1.578 1.578 0 008.428 2H6.272a1.578 1.578 0 00-1.578 1.578v.578H3.578a.5.5 0 00-.5.5V12.2a1.578 1.578 0 001.577 1.578h5.39a1.578 1.578 0 001.577-1.578V4.656a.5.5 0 00-.5-.5h-1.117zm-5.927 1V12.2a.578.578 0 00.577.578h5.39a.578.578 0 00.577-.578V5.156H4.078z"
fill="#000"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6.272 6.85a.5.5 0 01.5.5v3.233a.5.5 0 11-1 0V7.35a.5.5 0 01.5-.5zM8.428 6.85a.5.5 0 01.5.5v3.233a.5.5 0 11-1 0V7.35a.5.5 0 01.5-.5z"
fill="#000"
/>
</svg>
)
}
export default SvgTrash

27
components/icons/undo.tsx Normal file
View file

@ -0,0 +1,27 @@
import * as React from 'react'
function SvgUndo(props: React.SVGProps<SVGSVGElement>): JSX.Element {
return (
<svg
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.5 2.495a.5.5 0 01.5.5v2.5h2.5a.5.5 0 110 1h-3a.5.5 0 01-.5-.5v-3a.5.5 0 01.5-.5z"
fill="#000"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.303 2.049a5 5 0 11-4.02 6.613.5.5 0 01.944-.332 4 4 0 10.946-4.16l-.01.01-2.32 2.18a.5.5 0 01-.685-.729l2.314-2.175A5 5 0 017.303 2.05z"
fill="#000"
/>
</svg>
)
}
export default SvgUndo

View file

@ -7,6 +7,7 @@ import { forwardRef } from 'react'
export const breakpoints: any = { '@initial': 'mobile', '@sm': 'small' }
export const IconButton = styled('button', {
position: 'relative',
height: '32px',
width: '32px',
backgroundColor: '$panel',
@ -19,6 +20,7 @@ export const IconButton = styled('button', {
outline: 'none',
border: 'none',
pointerEvents: 'all',
fontSize: '$0',
cursor: 'pointer',
'& > *': {
@ -48,7 +50,9 @@ export const IconButton = styled('button', {
},
size: {
small: {
'& svg': {
height: 32,
width: 32,
'& svg:nth-of-type(1)': {
height: '16px',
width: '16px',
},
@ -56,17 +60,17 @@ export const IconButton = styled('button', {
medium: {
height: 44,
width: 44,
'& svg': {
height: '20px',
width: '20px',
'& svg:nth-of-type(1)': {
height: '18px',
width: '18px',
},
},
large: {
height: 44,
width: 44,
'& svg': {
height: '24px',
width: '24px',
'& svg:nth-of-type(1)': {
height: '20px',
width: '20px',
},
},
},
@ -404,3 +408,10 @@ export const ButtonsRow = styled('div', {
justifyContent: 'flex-start',
padding: 0,
})
export const VerticalDivider = styled('hr', {
width: '1px',
margin: '-2px 3px',
border: 'none',
backgroundColor: '$brushFill',
})

View file

@ -123,6 +123,7 @@ const StylePanelRoot = styled(motion(Panel.Root), {
alignItems: 'center',
pointerEvents: 'all',
padding: 2,
zIndex: 300,
'& hr': {
marginTop: 2,

View file

@ -0,0 +1,249 @@
import Tooltip from 'components/tooltip'
import styled from 'styles'
export const ToolButton = styled('button', {
position: 'relative',
height: '32px',
width: '32px',
backgroundColor: '$panel',
borderRadius: '4px',
padding: '0',
margin: '0',
display: 'grid',
alignItems: 'center',
justifyContent: 'center',
outline: 'none',
border: 'none',
pointerEvents: 'all',
fontSize: '$0',
cursor: 'pointer',
'& > *': {
gridRow: 1,
gridColumn: 1,
},
'&:disabled': {
opacity: '0.5',
},
'& > span': {
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
},
})
export const PrimaryToolButton = styled(ToolButton, {
variants: {
bp: {
mobile: {
height: 44,
width: 44,
'& svg:nth-of-type(1)': {
height: '20px',
width: '20px',
},
},
small: {
'&:hover:not(:disabled)': {
backgroundColor: '$hover',
},
},
medium: {},
large: {},
},
isActive: {
true: {
color: '$selected',
},
},
},
})
export const SecondaryToolButton = styled(ToolButton, {
variants: {
bp: {
mobile: {
height: 44,
width: 44,
'& svg:nth-of-type(1)': {
height: '18px',
width: '18px',
},
},
small: {
'&:hover:not(:disabled)': {
backgroundColor: '$hover',
},
},
medium: {},
large: {},
},
isActive: {
true: {
color: '$selected',
},
},
},
})
export const TertiaryToolButton = styled(ToolButton, {
variants: {
bp: {
mobile: {
height: 32,
width: 44,
'& svg:nth-of-type(1)': {
height: '16px',
width: '16px',
},
},
small: {
height: 40,
width: 40,
'& svg:nth-of-type(1)': {
height: '18px',
width: '18px',
},
'&:hover:not(:disabled)': {
backgroundColor: '$hover',
},
},
medium: {},
large: {},
},
},
})
interface PrimaryToolButtonProps {
label: string
onClick: () => void
onDoubleClick?: () => void
isActive: boolean
children: React.ReactNode
}
export function PrimaryButton({
label,
onClick,
onDoubleClick,
isActive,
children,
}: PrimaryToolButtonProps): JSX.Element {
return (
<Tooltip label={label}>
<PrimaryToolButton
name={label}
bp={{
'@initial': 'mobile',
'@sm': 'small',
'@md': 'medium',
'@lg': 'large',
}}
onClick={onClick}
onDoubleClick={onDoubleClick}
isActive={isActive}
>
{children}
</PrimaryToolButton>
</Tooltip>
)
}
interface SecondaryToolButtonProps {
label: string
onClick: () => void
onDoubleClick?: () => void
isActive: boolean
children: React.ReactNode
}
export function SecondaryButton({
label,
onClick,
onDoubleClick,
isActive,
children,
}: SecondaryToolButtonProps): JSX.Element {
return (
<Tooltip label={label}>
<SecondaryToolButton
name={label}
bp={{
'@initial': 'mobile',
'@sm': 'small',
'@md': 'medium',
'@lg': 'large',
}}
onClick={onClick}
onDoubleClick={onDoubleClick}
isActive={isActive}
>
{children}
</SecondaryToolButton>
</Tooltip>
)
}
interface TertiaryToolProps {
label: string
onClick: () => void
onDoubleClick?: () => void
children: React.ReactNode
}
export function TertiaryButton({
label,
onClick,
onDoubleClick,
children,
}: TertiaryToolProps): JSX.Element {
return (
<Tooltip label={label}>
<TertiaryToolButton
name={label}
bp={{
'@initial': 'mobile',
'@sm': 'small',
'@md': 'medium',
'@lg': 'large',
}}
onClick={onClick}
onDoubleClick={onDoubleClick}
>
{children}
</TertiaryToolButton>
</Tooltip>
)
}
export const Container = styled('div', {
backgroundColor: '$panel',
border: '1px solid $panel',
borderRadius: '4px',
boxShadow: '0px 2px 4px rgba(0,0,0,.16)',
display: 'flex',
height: 'fit-content',
padding: 2,
pointerEvents: 'all',
position: 'relative',
userSelect: 'none',
zIndex: 200,
})
export const TertiaryButtonsContainer = styled(Container, {
variants: {
bp: {
mobile: {
alignItems: 'center',
flexDirection: 'column',
},
small: {
alignItems: 'center',
flexDirection: 'row',
},
},
},
})

View file

@ -5,18 +5,16 @@ import {
LockClosedIcon,
LockOpen1Icon,
Pencil1Icon,
Pencil2Icon,
SquareIcon,
TextIcon,
} from '@radix-ui/react-icons'
import { IconButton } from 'components/shared'
import { PrimaryButton, SecondaryButton, Container } from './shared'
import React from 'react'
import state, { useSelector } from 'state'
import styled from 'styles'
import { ShapeType } from 'types'
import UndoRedo from './undo-redo'
import Zoom from './zoom'
import Tooltip from '../tooltip'
const selectArrowTool = () => state.send('SELECTED_ARROW_TOOL')
const selectDrawTool = () => state.send('SELECTED_DRAW_TOOL')
@ -24,167 +22,154 @@ const selectEllipseTool = () => state.send('SELECTED_ELLIPSE_TOOL')
const selectTextTool = () => state.send('SELECTED_TEXT_TOOL')
const selectRectangleTool = () => state.send('SELECTED_RECTANGLE_TOOL')
const selectSelectTool = () => state.send('SELECTED_SELECT_TOOL')
const selectToolLock = () => state.send('TOGGLED_TOOL_LOCK')
const toggleToolLock = () => state.send('TOGGLED_TOOL_LOCK')
export default function ToolsPanel(): JSX.Element {
const activeTool = useSelector((s) => s.data.activeTool)
const isToolLocked = useSelector((s) => s.data.settings.isToolLocked)
const isPenLocked = useSelector((s) => s.data.settings.isPenLocked)
return (
<OuterContainer>
<Zoom />
<Flex size={{ '@sm': 'small' }}>
<ToolsPanelContainer>
<LeftWrap size={{ '@initial': 'mobile', '@sm': 'small' }}>
<Zoom />
<Container>
<Tooltip label="Select">
<IconButton
name="select"
bp={{ '@initial': 'mobile', '@sm': 'small' }}
size={{ '@initial': 'small', '@sm': 'small', '@md': 'medium' }}
onClick={selectSelectTool}
isActive={activeTool === 'select'}
>
<CursorArrowIcon />
</IconButton>
</Tooltip>
<SecondaryButton
label={'Select'}
onClick={selectSelectTool}
isActive={activeTool === 'select'}
>
<CursorArrowIcon />
</SecondaryButton>
</Container>
</LeftWrap>
<CenterWrap>
<Container>
<Tooltip label="Draw">
<IconButton
name={ShapeType.Draw}
bp={{ '@initial': 'mobile', '@sm': 'small' }}
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
onClick={selectDrawTool}
isActive={activeTool === ShapeType.Draw}
>
<Pencil1Icon />
</IconButton>
</Tooltip>
<Tooltip label="Rectangle">
<IconButton
name={ShapeType.Rectangle}
bp={{ '@initial': 'mobile', '@sm': 'small' }}
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
onClick={selectRectangleTool}
isActive={activeTool === ShapeType.Rectangle}
>
<SquareIcon />
</IconButton>
</Tooltip>
<Tooltip label="Ellipse">
<IconButton
name={ShapeType.Ellipse}
bp={{ '@initial': 'mobile', '@sm': 'small' }}
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
onClick={selectEllipseTool}
isActive={activeTool === ShapeType.Ellipse}
>
<CircleIcon />
</IconButton>
</Tooltip>
<Tooltip label="Arrow">
<IconButton
name={ShapeType.Arrow}
bp={{ '@initial': 'mobile', '@sm': 'small' }}
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
onClick={selectArrowTool}
isActive={activeTool === ShapeType.Arrow}
>
<ArrowTopRightIcon />
</IconButton>
</Tooltip>
<Tooltip label="Text">
<IconButton
name={ShapeType.Text}
bp={{ '@initial': 'mobile', '@sm': 'small' }}
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
onClick={selectTextTool}
isActive={activeTool === ShapeType.Text}
>
<TextIcon />
</IconButton>
</Tooltip>
<PrimaryButton
label={ShapeType.Draw}
onClick={selectDrawTool}
isActive={activeTool === ShapeType.Draw}
>
<Pencil1Icon />
</PrimaryButton>
<PrimaryButton
label={ShapeType.Rectangle}
onClick={selectRectangleTool}
isActive={activeTool === ShapeType.Rectangle}
>
<SquareIcon />
</PrimaryButton>
<PrimaryButton
label={ShapeType.Ellipse}
onClick={selectEllipseTool}
isActive={activeTool === ShapeType.Ellipse}
>
<CircleIcon />
</PrimaryButton>
<PrimaryButton
label={ShapeType.Arrow}
onClick={selectArrowTool}
isActive={activeTool === ShapeType.Arrow}
>
<ArrowTopRightIcon />
</PrimaryButton>
<PrimaryButton
label={ShapeType.Text}
onClick={selectTextTool}
isActive={activeTool === ShapeType.Text}
>
<TextIcon />
</PrimaryButton>
</Container>
</CenterWrap>
<RightWrap size={{ '@initial': 'mobile', '@sm': 'small' }}>
<Container>
<Tooltip label="Lock Tool">
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
size={{ '@initial': 'small', '@sm': 'small', '@md': 'medium' }}
onClick={selectToolLock}
>
{isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
</IconButton>
</Tooltip>
{isPenLocked && (
<Tooltip label="Unlock Pen">
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
size={{ '@initial': 'small', '@sm': 'small', '@md': 'medium' }}
onClick={selectToolLock}
>
<Pencil2Icon />
</IconButton>
</Tooltip>
)}
<SecondaryButton
label={'Lock Tool'}
onClick={toggleToolLock}
isActive={isToolLocked}
>
{isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
</SecondaryButton>
</Container>
</Flex>
<UndoRedo />
</OuterContainer>
<UndoRedo />
</RightWrap>
</ToolsPanelContainer>
)
}
const OuterContainer = styled('div', {
const ToolsPanelContainer = styled('div', {
position: 'fixed',
bottom: 44,
left: 0,
right: 0,
padding: '0 8px 12px 8px',
width: '100%',
display: 'flex',
minWidth: 0,
maxWidth: '100%',
display: 'grid',
gridTemplateColumns: '1fr auto 1fr',
padding: '0 8px 12px 8px',
alignItems: 'flex-end',
justifyContent: 'center',
flexWrap: 'wrap',
gap: 16,
zIndex: 200,
gap: 12,
})
const Flex = styled('div', {
const CenterWrap = styled('div', {
gridRow: 1,
gridColumn: 2,
display: 'flex',
width: '100%',
padding: '0 4px',
justifyContent: 'space-between',
alignItems: 'flex-end',
width: 'fit-content',
justifyContent: 'center',
})
const LeftWrap = styled('div', {
gridRow: 1,
gridColumn: 1,
display: 'flex',
variants: {
size: {
mobile: {
flexDirection: 'column',
justifyContent: 'flex-end',
alignItems: 'flex-start',
'& > *:nth-of-type(1)': {
marginBottom: '8px',
},
},
small: {
width: 'auto',
padding: '0',
justifyContent: 'center',
'& > *:nth-child(n+2)': {
marginLeft: 16,
flexDirection: 'row',
alignItems: 'flex-end',
justifyContent: 'space-between',
'& > *:nth-of-type(1)': {
marginBottom: '0px',
},
},
},
},
})
const Container = styled('div', {
position: 'relative',
backgroundColor: '$panel',
borderRadius: '4px',
overflow: 'hidden',
border: '1px solid $panel',
pointerEvents: 'all',
userSelect: 'none',
height: '100%',
const RightWrap = styled('div', {
gridRow: 1,
gridColumn: 3,
display: 'flex',
padding: 4,
boxShadow: '0px 2px 4px rgba(0,0,0,.12)',
'& svg': {
strokeWidth: 0,
variants: {
size: {
mobile: {
flexDirection: 'column-reverse',
justifyContent: 'flex-end',
alignItems: 'flex-end',
'& > *:nth-of-type(2)': {
marginBottom: '8px',
},
},
small: {
flexDirection: 'row',
alignItems: 'flex-end',
justifyContent: 'space-between',
'& > *:nth-of-type(2)': {
marginBottom: '0px',
},
},
},
},
})

View file

@ -1,8 +1,6 @@
import { IconButton } from 'components/shared'
import { RotateCcw, RotateCw, Trash2 } from 'react-feather'
import { TertiaryButton, TertiaryButtonsContainer } from './shared'
import { Undo, Redo, Trash } from 'components/icons'
import state from 'state'
import styled from 'styles'
import Tooltip from '../tooltip'
const undo = () => state.send('UNDO')
const redo = () => state.send('REDO')
@ -10,55 +8,16 @@ const clear = () => state.send('CLEARED_PAGE')
export default function UndoRedo(): JSX.Element {
return (
<Container size={{ '@sm': 'small' }}>
<Tooltip label="Undo">
<IconButton onClick={undo}>
<RotateCcw />
</IconButton>
</Tooltip>
<Tooltip label="Redo">
<IconButton onClick={redo}>
<RotateCw />
</IconButton>
</Tooltip>
<Tooltip label="Clear Canvas">
<IconButton onClick={clear}>
<Trash2 />
</IconButton>
</Tooltip>
</Container>
<TertiaryButtonsContainer bp={{ '@initial': 'mobile', '@sm': 'small' }}>
<TertiaryButton label="Undo" onClick={undo}>
<Undo />
</TertiaryButton>
<TertiaryButton label="Redo" onClick={redo}>
<Redo />
</TertiaryButton>
<TertiaryButton label="Delete" onClick={clear}>
<Trash />
</TertiaryButton>
</TertiaryButtonsContainer>
)
}
const Container = styled('div', {
position: 'absolute',
bottom: 64,
right: 12,
backgroundColor: '$panel',
borderRadius: '4px',
overflow: 'hidden',
alignSelf: 'flex-end',
pointerEvents: 'all',
userSelect: 'none',
zIndex: 200,
border: '1px solid $panel',
boxShadow: '0px 2px 4px rgba(0,0,0,.12)',
display: 'flex',
padding: 4,
flexDirection: 'column',
'& svg': {
height: 13,
width: 13,
},
variants: {
size: {
small: {
bottom: 12,
flexDirection: 'row',
alignItems: 'center',
},
},
},
})

View file

@ -1,8 +1,6 @@
import { ZoomInIcon, ZoomOutIcon } from '@radix-ui/react-icons'
import { IconButton } from 'components/shared'
import { TertiaryButton, TertiaryButtonsContainer } from './shared'
import state, { useSelector } from 'state'
import styled from 'styles'
import Tooltip from '../tooltip'
import tld from 'utils/tld'
const zoomIn = () => state.send('ZOOMED_IN')
@ -12,21 +10,15 @@ const zoomToActual = () => state.send('ZOOMED_TO_ACTUAL')
export default function Zoom(): JSX.Element {
return (
<Container size={{ '@sm': 'small' }}>
<Tooltip label="Zoom Out">
<IconButton onClick={zoomOut}>
<ZoomOutIcon />
</IconButton>
</Tooltip>
<Tooltip label="Zoom In">
<IconButton onClick={zoomIn}>
<ZoomInIcon />
</IconButton>
</Tooltip>
<Tooltip label="Reset Zoom">
<ZoomCounter />
</Tooltip>
</Container>
<TertiaryButtonsContainer bp={{ '@initial': 'mobile', '@sm': 'small' }}>
<TertiaryButton label="Zoom Out" onClick={zoomOut}>
<ZoomOutIcon />
</TertiaryButton>
<TertiaryButton label="Zoom In" onClick={zoomIn}>
<ZoomInIcon />
</TertiaryButton>
<ZoomCounter />
</TertiaryButtonsContainer>
)
}
@ -34,50 +26,12 @@ function ZoomCounter() {
const zoom = useSelector((s) => tld.getCurrentCamera(s.data).zoom)
return (
<ZoomButton
<TertiaryButton
label="Reset Zoom"
onClick={zoomToActual}
onDoubleClick={zoomToFit}
style={{ width: '44px' }}
>
{Math.round(zoom * 100)}%
</ZoomButton>
</TertiaryButton>
)
}
const ZoomButton = styled(IconButton, {
fontSize: '$0',
padding: 8,
})
const Container = styled('div', {
position: 'absolute',
left: 12,
bottom: 64,
backgroundColor: '$panel',
borderRadius: '4px',
overflow: 'hidden',
alignSelf: 'flex-end',
pointerEvents: 'all',
userSelect: 'none',
zIndex: 200,
border: '1px solid $panel',
boxShadow: '0px 2px 4px rgba(0,0,0,.12)',
display: 'flex',
padding: 4,
flexDirection: 'column',
alignItems: 'center',
'& svg': {
strokeWidth: 0,
},
variants: {
size: {
small: {
bottom: 12,
flexDirection: 'row',
alignItems: 'center',
},
},
},
})

View file

@ -8,8 +8,13 @@ import {
fastTranslate,
} from 'state/hacks'
import inputs from 'state/inputs'
import { isMobile } from 'utils'
import Vec from 'utils/vec'
function handleFocusOut() {
state.send('BLURRED_EDITING_SHAPE')
}
export default function useCanvasEvents(
rCanvas: MutableRefObject<SVGGElement>
) {
@ -76,14 +81,12 @@ export default function useCanvasEvents(
// Send event on iOS when a user presses the "Done" key while editing a text element
useEffect(() => {
function handleFocusOut() {
state.send('BLURRED_EDITING_SHAPE')
}
if (isMobile()) {
document.addEventListener('focusout', handleFocusOut)
document.addEventListener('focusout', handleFocusOut)
return () => {
document.removeEventListener('focusout', handleFocusOut)
return () => {
document.removeEventListener('focusout', handleFocusOut)
}
}
}, [])

View file

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { useEffect } from 'react'
import state from 'state'
import coopState from 'state/coop/coop-state'
// import coopState from 'state/coop/coop-state'
export default function useLoadOnMount(roomId?: string) {
useEffect(() => {
@ -21,7 +21,7 @@ export default function useLoadOnMount(roomId?: string) {
return () => {
state.send('UNMOUNTED', { roomId })
coopState.send('LEFT_ROOM', { id: roomId })
// coopState.send('LEFT_ROOM', { id: roomId })
}
}, [roomId])
}

4
public/icons/Redo.svg Normal file
View file

@ -0,0 +1,4 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5 2.49538C12.2239 2.49538 12 2.71923 12 2.99538V5.49559H9.49979C9.22364 5.49559 8.99979 5.71945 8.99979 5.99559C8.99979 6.27173 9.22364 6.49559 9.49979 6.49559H12.5C12.7761 6.49559 13 6.27173 13 5.99559V2.99538C13 2.71923 12.7761 2.49538 12.5 2.49538Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.69698 2.04877C6.62345 1.89773 5.52991 2.09968 4.58113 2.62417C3.63236 3.14867 2.87973 3.9673 2.43667 4.95673C1.99361 5.94616 1.8841 7.05278 2.12465 8.10985C2.3652 9.16693 2.94278 10.1172 3.77036 10.8175C4.59794 11.5177 5.63069 11.9301 6.713 11.9924C7.79531 12.0547 8.86855 11.7635 9.77101 11.1628C10.6735 10.5621 11.3563 9.68441 11.7165 8.66191C11.8083 8.40146 11.6715 8.11593 11.4111 8.02417C11.1506 7.93241 10.8651 8.06916 10.7733 8.32961C10.4851 9.14762 9.93888 9.84981 9.21691 10.3304C8.49493 10.811 7.63632 11.0439 6.77046 10.994C5.9046 10.9442 5.07839 10.6143 4.41631 10.0541C3.75424 9.49386 3.29217 8.73363 3.09972 7.88796C2.90728 7.04229 2.99488 6.15698 3.34934 5.36542C3.7038 4.57387 4.30591 3.91895 5.06494 3.49935C5.82398 3.07974 6.69882 2.91819 7.55765 3.03902C8.41649 3.15985 9.21279 3.55653 9.82658 4.16928L9.83745 4.17981L12.1576 6.35996C12.3588 6.54906 12.6753 6.53921 12.8644 6.33797C13.0535 6.13673 13.0436 5.8203 12.8424 5.63121L10.5276 3.4561C9.76111 2.69329 8.76794 2.19945 7.69698 2.04877Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

6
public/icons/Trash.svg Normal file
View file

@ -0,0 +1,6 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 4.65555C2 4.37941 2.22386 4.15555 2.5 4.15555H12.2C12.4761 4.15555 12.7 4.37941 12.7 4.65555C12.7 4.93169 12.4761 5.15555 12.2 5.15555H2.5C2.22386 5.15555 2 4.93169 2 4.65555Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.27208 3C6.11885 3 5.97189 3.06087 5.86353 3.16923C5.75518 3.27758 5.6943 3.42454 5.6943 3.57778V4.15556H9.00542V3.57778C9.00542 3.42454 8.94454 3.27758 8.83619 3.16923C8.72783 3.06087 8.58087 3 8.42764 3H6.27208ZM10.0054 4.15556V3.57778C10.0054 3.15933 9.83919 2.75801 9.54329 2.46212C9.2474 2.16623 8.84609 2 8.42764 2H6.27208C5.85363 2 5.45232 2.16623 5.15642 2.46212C4.86053 2.75801 4.6943 3.15933 4.6943 3.57778V4.15556H3.57764C3.30149 4.15556 3.07764 4.37941 3.07764 4.65556V12.2C3.07764 12.6185 3.24387 13.0198 3.53976 13.3157C3.83565 13.6115 4.23696 13.7778 4.65541 13.7778H10.0443C10.4628 13.7778 10.8641 13.6115 11.16 13.3157C11.4559 13.0198 11.6221 12.6185 11.6221 12.2V4.65556C11.6221 4.37941 11.3982 4.15556 11.1221 4.15556H10.0054ZM4.07764 5.15556V12.2C4.07764 12.3532 4.13851 12.5002 4.24686 12.6086C4.35522 12.7169 4.50218 12.7778 4.65541 12.7778H10.0443C10.1975 12.7778 10.3445 12.7169 10.4529 12.6086C10.5612 12.5002 10.6221 12.3532 10.6221 12.2V5.15556H4.07764Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.27246 6.85001C6.5486 6.85001 6.77246 7.07386 6.77246 7.35001V10.5833C6.77246 10.8595 6.5486 11.0833 6.27246 11.0833C5.99632 11.0833 5.77246 10.8595 5.77246 10.5833V7.35001C5.77246 7.07386 5.99632 6.85001 6.27246 6.85001Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.42773 6.85001C8.70388 6.85001 8.92773 7.07386 8.92773 7.35001V10.5833C8.92773 10.8595 8.70388 11.0833 8.42773 11.0833C8.15159 11.0833 7.92773 10.8595 7.92773 10.5833V7.35001C7.92773 7.07386 8.15159 6.85001 8.42773 6.85001Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

4
public/icons/Undo.svg Normal file
View file

@ -0,0 +1,4 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 2.49538C2.77614 2.49538 3 2.71923 3 2.99538V5.49559H5.50021C5.77636 5.49559 6.00021 5.71945 6.00021 5.99559C6.00021 6.27173 5.77636 6.49559 5.50021 6.49559H2.5C2.22386 6.49559 2 6.27173 2 5.99559V2.99538C2 2.71923 2.22386 2.49538 2.5 2.49538Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.30302 2.04877C8.37655 1.89773 9.47009 2.09968 10.4189 2.62417C11.3676 3.14867 12.1203 3.9673 12.5633 4.95673C13.0064 5.94616 13.1159 7.05278 12.8753 8.10985C12.6348 9.16693 12.0572 10.1172 11.2296 10.8175C10.4021 11.5177 9.36931 11.9301 8.287 11.9924C7.20469 12.0547 6.13145 11.7635 5.22899 11.1628C4.32653 10.5621 3.64374 9.68441 3.2835 8.66191C3.19174 8.40146 3.32849 8.11593 3.58894 8.02417C3.84939 7.93241 4.13492 8.06916 4.22668 8.32961C4.51488 9.14762 5.06112 9.84981 5.78309 10.3304C6.50507 10.811 7.36368 11.0439 8.22954 10.994C9.0954 10.9442 9.92161 10.6143 10.5837 10.0541C11.2458 9.49386 11.7078 8.73363 11.9003 7.88796C12.0927 7.04229 12.0051 6.15698 11.6507 5.36542C11.2962 4.57387 10.6941 3.91895 9.93506 3.49935C9.17602 3.07974 8.30118 2.91819 7.44235 3.03902C6.58351 3.15985 5.78721 3.55653 5.17342 4.16928L5.16255 4.17981L2.84239 6.35996C2.64115 6.54906 2.32472 6.53921 2.13562 6.33797C1.94653 6.13673 1.95637 5.8203 2.15761 5.63121L4.47241 3.4561C5.23889 2.69329 6.23206 2.19945 7.30302 2.04877Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -734,7 +734,6 @@ const state = createState({
do: 'breakSession',
to: 'pinching.toolPinching',
},
TOGGLED_TOOL_LOCK: 'toggleToolLock',
},
states: {
draw: {