diff --git a/components/icons/Redo.svg b/components/icons/Redo.svg new file mode 100644 index 000000000..b524e86eb --- /dev/null +++ b/components/icons/Redo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/components/icons/Trash.svg b/components/icons/Trash.svg new file mode 100644 index 000000000..aed40eb98 --- /dev/null +++ b/components/icons/Trash.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/components/icons/Undo.svg b/components/icons/Undo.svg new file mode 100644 index 000000000..c47375398 --- /dev/null +++ b/components/icons/Undo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/components/icons/index.tsx b/components/icons/index.tsx new file mode 100644 index 000000000..4ce8d6300 --- /dev/null +++ b/components/icons/index.tsx @@ -0,0 +1,3 @@ +export { default as Redo } from './redo' +export { default as Trash } from './trash' +export { default as Undo } from './undo' diff --git a/components/icons/redo.tsx b/components/icons/redo.tsx new file mode 100644 index 000000000..84b19c0ca --- /dev/null +++ b/components/icons/redo.tsx @@ -0,0 +1,27 @@ +import * as React from 'react' + +function SvgRedo(props: React.SVGProps): JSX.Element { + return ( + + + + + ) +} + +export default SvgRedo diff --git a/components/icons/trash.tsx b/components/icons/trash.tsx new file mode 100644 index 000000000..01fd1a734 --- /dev/null +++ b/components/icons/trash.tsx @@ -0,0 +1,33 @@ +import * as React from 'react' + +function SvgTrash(props: React.SVGProps): JSX.Element { + return ( + + + + + + ) +} + +export default SvgTrash diff --git a/components/icons/undo.tsx b/components/icons/undo.tsx new file mode 100644 index 000000000..70b654741 --- /dev/null +++ b/components/icons/undo.tsx @@ -0,0 +1,27 @@ +import * as React from 'react' + +function SvgUndo(props: React.SVGProps): JSX.Element { + return ( + + + + + ) +} + +export default SvgUndo diff --git a/components/shared.tsx b/components/shared.tsx index b15d1af82..df7a47d4d 100644 --- a/components/shared.tsx +++ b/components/shared.tsx @@ -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', +}) diff --git a/components/style-panel/style-panel.tsx b/components/style-panel/style-panel.tsx index 896031e6a..eee7d55ff 100644 --- a/components/style-panel/style-panel.tsx +++ b/components/style-panel/style-panel.tsx @@ -123,6 +123,7 @@ const StylePanelRoot = styled(motion(Panel.Root), { alignItems: 'center', pointerEvents: 'all', padding: 2, + zIndex: 300, '& hr': { marginTop: 2, diff --git a/components/tools-panel/shared.tsx b/components/tools-panel/shared.tsx new file mode 100644 index 000000000..ed2f62b7d --- /dev/null +++ b/components/tools-panel/shared.tsx @@ -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 ( + + + {children} + + + ) +} + +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 ( + + + {children} + + + ) +} + +interface TertiaryToolProps { + label: string + onClick: () => void + onDoubleClick?: () => void + children: React.ReactNode +} + +export function TertiaryButton({ + label, + onClick, + onDoubleClick, + children, +}: TertiaryToolProps): JSX.Element { + return ( + + + {children} + + + ) +} + +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', + }, + }, + }, +}) diff --git a/components/tools-panel/tools-panel.tsx b/components/tools-panel/tools-panel.tsx index ed7a9ec06..943686ed1 100644 --- a/components/tools-panel/tools-panel.tsx +++ b/components/tools-panel/tools-panel.tsx @@ -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 ( - - - + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - {isToolLocked ? : } - - - {isPenLocked && ( - - - - - - )} + + {isToolLocked ? : } + - - - + + + ) } -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', + }, + }, + }, }, }) diff --git a/components/tools-panel/undo-redo.tsx b/components/tools-panel/undo-redo.tsx index 836b25974..177ef24eb 100644 --- a/components/tools-panel/undo-redo.tsx +++ b/components/tools-panel/undo-redo.tsx @@ -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 ( - - - - - - - - - - - - - - - - - + + + + + + + + + + + ) } - -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', - }, - }, - }, -}) diff --git a/components/tools-panel/zoom.tsx b/components/tools-panel/zoom.tsx index 94ef58531..00c03b613 100644 --- a/components/tools-panel/zoom.tsx +++ b/components/tools-panel/zoom.tsx @@ -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 ( - - - - - - - - - - - - - - - + + + + + + + + + ) } @@ -34,50 +26,12 @@ function ZoomCounter() { const zoom = useSelector((s) => tld.getCurrentCamera(s.data).zoom) return ( - {Math.round(zoom * 100)}% - + ) } - -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', - }, - }, - }, -}) diff --git a/hooks/useCanvasEvents.ts b/hooks/useCanvasEvents.ts index 55aca9fbb..4ceed8d36 100644 --- a/hooks/useCanvasEvents.ts +++ b/hooks/useCanvasEvents.ts @@ -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 ) { @@ -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) + } } }, []) diff --git a/hooks/useLoadOnMount.ts b/hooks/useLoadOnMount.ts index 11764506d..094278b98 100644 --- a/hooks/useLoadOnMount.ts +++ b/hooks/useLoadOnMount.ts @@ -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]) } diff --git a/public/icons/Redo.svg b/public/icons/Redo.svg new file mode 100644 index 000000000..b524e86eb --- /dev/null +++ b/public/icons/Redo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/Trash.svg b/public/icons/Trash.svg new file mode 100644 index 000000000..aed40eb98 --- /dev/null +++ b/public/icons/Trash.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/icons/Undo.svg b/public/icons/Undo.svg new file mode 100644 index 000000000..c47375398 --- /dev/null +++ b/public/icons/Undo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/state/state.ts b/state/state.ts index 0bad830ab..3b555febf 100644 --- a/state/state.ts +++ b/state/state.ts @@ -734,7 +734,6 @@ const state = createState({ do: 'breakSession', to: 'pinching.toolPinching', }, - TOGGLED_TOOL_LOCK: 'toggleToolLock', }, states: { draw: {