diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/packages/tldraw/LICENSE.md b/packages/tldraw/LICENSE.md new file mode 100644 index 000000000..bdcc8b850 --- /dev/null +++ b/packages/tldraw/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Stephen Ruiz Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/tldraw/card-repo.png b/packages/tldraw/card-repo.png new file mode 100644 index 000000000..2b3997cbd Binary files /dev/null and b/packages/tldraw/card-repo.png differ diff --git a/packages/tldraw/package.json b/packages/tldraw/package.json index 599830d6a..63a27dd10 100644 --- a/packages/tldraw/package.json +++ b/packages/tldraw/package.json @@ -46,7 +46,7 @@ "@radix-ui/react-id": "^0.1.1", "@radix-ui/react-radio-group": "^0.1.1", "@radix-ui/react-tooltip": "^0.1.1", - "@stitches/core": "^1.2.5", + "@stitches/react": "^1.2.5", "@tldraw/core": "^0.1.13", "@tldraw/intersect": "^0.1.3", "@tldraw/vec": "^0.1.3", @@ -55,4 +55,4 @@ "rko": "^0.5.25" }, "gitHead": "083b36e167b6911927a6b58cbbb830b11b33f00a" -} \ No newline at end of file +} diff --git a/packages/tldraw/scripts/copy-readme.js b/packages/tldraw/scripts/copy-readme.js index b6640b989..210b6525e 100644 --- a/packages/tldraw/scripts/copy-readme.js +++ b/packages/tldraw/scripts/copy-readme.js @@ -1,10 +1,10 @@ /* eslint-disable */ const fs = require('fs') -const filesToCopy = ['README.md', 'card-repo.png'] +const filesToCopy = ['README.md', 'LICENSE.md', 'card-repo.png'] filesToCopy.forEach((file) => { - fs.copyFile(`../../${file}`, `./dist/${file}`, (err) => { + fs.copyFile(`../../${file}`, `./${file}`, (err) => { if (err) throw err }) }) diff --git a/packages/tldraw/src/components/tldraw/tldraw.test.tsx b/packages/tldraw/src/TLDraw.test.tsx similarity index 95% rename from packages/tldraw/src/components/tldraw/tldraw.test.tsx rename to packages/tldraw/src/TLDraw.test.tsx index c04d83c0a..ab258ef27 100644 --- a/packages/tldraw/src/components/tldraw/tldraw.test.tsx +++ b/packages/tldraw/src/TLDraw.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { render } from '@testing-library/react' -import { TLDraw } from './tldraw' +import { TLDraw } from './TLDraw' describe('tldraw', () => { test('mounts component without crashing', () => { diff --git a/packages/tldraw/src/components/tldraw/tldraw.tsx b/packages/tldraw/src/TLDraw.tsx similarity index 85% rename from packages/tldraw/src/components/tldraw/tldraw.tsx rename to packages/tldraw/src/TLDraw.tsx index 1b024fa87..1e0e782e6 100644 --- a/packages/tldraw/src/components/tldraw/tldraw.tsx +++ b/packages/tldraw/src/TLDraw.tsx @@ -1,19 +1,16 @@ import * as React from 'react' import { IdProvider } from '@radix-ui/react-id' import { Renderer } from '@tldraw/core' -import css, { dark } from '~styles' +import styled, { dark } from '~styles' import { Data, TLDrawDocument, TLDrawStatus, TLDrawUser } from '~types' import { TLDrawState } from '~state' import { TLDrawContext, useCustomFonts, useKeyboardShortcuts, useTLDrawContext } from '~hooks' import { shapeUtils } from '~shape-utils' -import { StylePanel } from '~components/style-panel' -import { ToolsPanel } from '~components/tools-panel' -import { PagePanel } from '~components/page-panel' -import { Menu } from '~components/menu' -import { breakpoints, iconButton } from '~components' -import { DotFilledIcon } from '@radix-ui/react-icons' +import { ToolsPanel } from '~components/ToolsPanel' +import { TopPanel } from '~components/TopPanel' import { TLDR } from '~state/tldr' -import { ContextMenu } from '~components/context-menu' +import { ContextMenu } from '~components/ContextMenu' +import { FocusButton } from '~components/FocusButton/FocusButton' // Selectors const isInSelectSelector = (s: Data) => s.appState.activeTool === 'select' @@ -68,6 +65,26 @@ export interface TLDrawProps { */ showPages?: boolean + /** + * (optional) Whether to show the styles UI. + */ + showStyles?: boolean + + /** + * (optional) Whether to show the zoom UI. + */ + showZoom?: boolean + + /** + * (optional) Whether to show the tools UI. + */ + showTools?: boolean + + /** + * (optional) Whether to show the UI. + */ + showUI?: boolean + /** * (optional) A callback to run when the component mounts. */ @@ -88,6 +105,10 @@ export function TLDraw({ autofocus = true, showMenu = true, showPages = true, + showTools = true, + showZoom = true, + showStyles = true, + showUI = true, onMount, onChange, onUserChange, @@ -120,27 +141,41 @@ export function TLDraw({ autofocus={autofocus} showPages={showPages} showMenu={showMenu} + showStyles={showStyles} + showZoom={showZoom} + showTools={showTools} + showUI={showUI} /> ) } +interface InnerTLDrawProps { + id?: string + currentPageId?: string + autofocus: boolean + showPages: boolean + showMenu: boolean + showZoom: boolean + showStyles: boolean + showUI: boolean + showTools: boolean + document?: TLDrawDocument +} + function InnerTldraw({ id, currentPageId, autofocus, showPages, showMenu, + showZoom, + showStyles, + showTools, + showUI, document, -}: { - id?: string - currentPageId?: string - autofocus: boolean - showPages: boolean - showMenu: boolean - document?: TLDrawDocument -}) { +}: InnerTLDrawProps) { const { tlstate, useSelector } = useTLDrawContext() const rWrapper = React.useRef(null) @@ -209,11 +244,7 @@ function InnerTldraw({ }, [currentPageId, tlstate]) return ( -
+ -
- {settings.isFocusMode ? ( -
- -
- ) : ( - <> -
- {showMenu && } - {showPages && } -
-
- - - - )} -
-
+ {showUI && ( + + {settings.isFocusMode ? ( + + ) : ( + <> + + + {showTools && } + + )} + + )} +
) } @@ -328,7 +358,7 @@ const OneOff = React.memo( } ) -const layout = css({ +const StyledLayout = styled('div', { position: 'absolute', height: '100%', width: '100%', @@ -350,7 +380,7 @@ const layout = css({ }, }) -const ui = css({ +const StyledUI = styled('div', { position: 'absolute', top: 0, left: 0, @@ -367,25 +397,6 @@ const ui = css({ }, }) -const spacer = css({ +const StyledSpacer = styled('div', { flexGrow: 2, }) - -const menuButtons = css({ - display: 'flex', - gap: 8, -}) - -const unfocusButton = css({ - opacity: 1, - zIndex: 100, - backgroundColor: 'transparent', - - '& svg': { - color: '$muted', - }, - - '&:hover svg': { - color: '$text', - }, -}) diff --git a/packages/tldraw/src/components/ContextMenu/CMIconButton.tsx b/packages/tldraw/src/components/ContextMenu/CMIconButton.tsx new file mode 100644 index 000000000..9f6a45afc --- /dev/null +++ b/packages/tldraw/src/components/ContextMenu/CMIconButton.tsx @@ -0,0 +1,11 @@ +import * as React from 'react' +import { ContextMenuItem } from '@radix-ui/react-context-menu' +import { ToolButton, ToolButtonProps } from '~components/ToolButton' + +export function CMIconButton({ onSelect, ...rest }: ToolButtonProps): JSX.Element { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/ContextMenu/CMRowButton.tsx b/packages/tldraw/src/components/ContextMenu/CMRowButton.tsx new file mode 100644 index 000000000..ab0405ed0 --- /dev/null +++ b/packages/tldraw/src/components/ContextMenu/CMRowButton.tsx @@ -0,0 +1,11 @@ +import * as React from 'react' +import { ContextMenuItem } from '@radix-ui/react-context-menu' +import { RowButton, RowButtonProps } from '~components/RowButton' + +export const CMRowButton = ({ onSelect, ...rest }: RowButtonProps) => { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/ContextMenu/CMTriggerButton.tsx b/packages/tldraw/src/components/ContextMenu/CMTriggerButton.tsx new file mode 100644 index 000000000..920a100f6 --- /dev/null +++ b/packages/tldraw/src/components/ContextMenu/CMTriggerButton.tsx @@ -0,0 +1,15 @@ +import * as React from 'react' +import { ContextMenuTriggerItem } from '@radix-ui/react-context-menu' +import { RowButton, RowButtonProps } from '~components/RowButton' + +interface CMTriggerButtonProps extends RowButtonProps { + isSubmenu?: boolean +} + +export const CMTriggerButton = ({ isSubmenu, ...rest }: CMTriggerButtonProps) => { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/context-menu/context-menu.test.tsx b/packages/tldraw/src/components/ContextMenu/ContextMenu.test.tsx similarity index 85% rename from packages/tldraw/src/components/context-menu/context-menu.test.tsx rename to packages/tldraw/src/components/ContextMenu/ContextMenu.test.tsx index e68ce3b5a..a956984d4 100644 --- a/packages/tldraw/src/components/context-menu/context-menu.test.tsx +++ b/packages/tldraw/src/components/ContextMenu/ContextMenu.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { ContextMenu } from './context-menu' +import { ContextMenu } from './ContextMenu' import { renderWithContext } from '~test' describe('context menu', () => { diff --git a/packages/tldraw/src/components/ContextMenu/ContextMenu.tsx b/packages/tldraw/src/components/ContextMenu/ContextMenu.tsx new file mode 100644 index 000000000..ae2486798 --- /dev/null +++ b/packages/tldraw/src/components/ContextMenu/ContextMenu.tsx @@ -0,0 +1,372 @@ +import * as React from 'react' +import styled from '~styles' +import * as RadixContextMenu from '@radix-ui/react-context-menu' +import { useTLDrawContext } from '~hooks' +import { Data, AlignType, DistributeType, StretchType } from '~types' +import { + AlignBottomIcon, + AlignCenterHorizontallyIcon, + AlignCenterVerticallyIcon, + AlignLeftIcon, + AlignRightIcon, + AlignTopIcon, + SpaceEvenlyHorizontallyIcon, + SpaceEvenlyVerticallyIcon, + StretchHorizontallyIcon, + StretchVerticallyIcon, +} from '@radix-ui/react-icons' +import { CMRowButton } from './CMRowButton' +import { CMIconButton } from './CMIconButton' +import { CMTriggerButton } from './CMTriggerButton' +import { Divider } from '~components/Divider' +import { MenuContent } from '~components/MenuContent' + +const has1SelectedIdsSelector = (s: Data) => { + return s.document.pageStates[s.appState.currentPageId].selectedIds.length > 0 +} +const has2SelectedIdsSelector = (s: Data) => { + return s.document.pageStates[s.appState.currentPageId].selectedIds.length > 1 +} +const has3SelectedIdsSelector = (s: Data) => { + return s.document.pageStates[s.appState.currentPageId].selectedIds.length > 2 +} + +const isDebugModeSelector = (s: Data) => { + return s.settings.isDebugMode +} + +const hasGroupSelectedSelector = (s: Data) => { + return s.document.pageStates[s.appState.currentPageId].selectedIds.some( + (id) => s.document.pages[s.appState.currentPageId].shapes[id].children !== undefined + ) +} + +interface ContextMenuProps { + children: React.ReactNode +} + +export const ContextMenu = ({ children }: ContextMenuProps): JSX.Element => { + const { tlstate, useSelector } = useTLDrawContext() + const hasSelection = useSelector(has1SelectedIdsSelector) + const hasTwoOrMore = useSelector(has2SelectedIdsSelector) + const hasThreeOrMore = useSelector(has3SelectedIdsSelector) + const isDebugMode = useSelector(isDebugModeSelector) + const hasGroupSelected = useSelector(hasGroupSelectedSelector) + + const rContent = React.useRef(null) + + const handleFlipHorizontal = React.useCallback(() => { + tlstate.flipHorizontal() + }, [tlstate]) + + const handleFlipVertical = React.useCallback(() => { + tlstate.flipVertical() + }, [tlstate]) + + const handleDuplicate = React.useCallback(() => { + tlstate.duplicate() + }, [tlstate]) + + const handleGroup = React.useCallback(() => { + tlstate.group() + }, [tlstate]) + + const handleMoveToBack = React.useCallback(() => { + tlstate.moveToBack() + }, [tlstate]) + + const handleMoveBackward = React.useCallback(() => { + tlstate.moveBackward() + }, [tlstate]) + + const handleMoveForward = React.useCallback(() => { + tlstate.moveForward() + }, [tlstate]) + + const handleMoveToFront = React.useCallback(() => { + tlstate.moveToFront() + }, [tlstate]) + + const handleDelete = React.useCallback(() => { + tlstate.delete() + }, [tlstate]) + + const handleCopyJson = React.useCallback(() => { + tlstate.copyJson() + }, [tlstate]) + + const handleCopy = React.useCallback(() => { + tlstate.copy() + }, [tlstate]) + + const handlePaste = React.useCallback(() => { + tlstate.paste() + }, [tlstate]) + + const handleCopySvg = React.useCallback(() => { + tlstate.copySvg() + }, [tlstate]) + + const handleUndo = React.useCallback(() => { + tlstate.undo() + }, [tlstate]) + + const handleRedo = React.useCallback(() => { + tlstate.redo() + }, [tlstate]) + + return ( + + {children} + + + {hasSelection ? ( + <> + + Flip Horizontal + + + Flip Vertical + + + Duplicate + + + {hasTwoOrMore && ( + + Group + + )} + + {hasGroupSelected && ( + + Ungroup + + )} + + + To Front + + + Forward + + + Backward + + + To Back + + + + {hasTwoOrMore && ( + + )} + + + Copy + + + Copy as SVG + + {isDebugMode && Copy as JSON} + + Paste + + + + Delete + + + ) : ( + <> + + Paste + + + Undo + + + Redo + + + )} + + + + ) +} + +function AlignDistributeSubMenu({ + hasThreeOrMore, +}: { + hasTwoOrMore: boolean + hasThreeOrMore: boolean +}) { + const { tlstate } = useTLDrawContext() + + const alignTop = React.useCallback(() => { + tlstate.align(AlignType.Top) + }, [tlstate]) + + const alignCenterVertical = React.useCallback(() => { + tlstate.align(AlignType.CenterVertical) + }, [tlstate]) + + const alignBottom = React.useCallback(() => { + tlstate.align(AlignType.Bottom) + }, [tlstate]) + + const stretchVertically = React.useCallback(() => { + tlstate.stretch(StretchType.Vertical) + }, [tlstate]) + + const distributeVertically = React.useCallback(() => { + tlstate.distribute(DistributeType.Vertical) + }, [tlstate]) + + const alignLeft = React.useCallback(() => { + tlstate.align(AlignType.Left) + }, [tlstate]) + + const alignCenterHorizontal = React.useCallback(() => { + tlstate.align(AlignType.CenterHorizontal) + }, [tlstate]) + + const alignRight = React.useCallback(() => { + tlstate.align(AlignType.Right) + }, [tlstate]) + + const stretchHorizontally = React.useCallback(() => { + tlstate.stretch(StretchType.Horizontal) + }, [tlstate]) + + const distributeHorizontally = React.useCallback(() => { + tlstate.distribute(DistributeType.Horizontal) + }, [tlstate]) + + return ( + + Align / Distribute + + + + + + + + + + + + + + + {hasThreeOrMore && ( + + + + )} + + + + + + + + + + + + + {hasThreeOrMore && ( + + + + )} + + + + + ) +} + +const StyledGridContent = styled(MenuContent, { + display: 'grid', + variants: { + selectedStyle: { + threeOrMore: { + gridTemplateColumns: 'repeat(5, auto)', + }, + twoOrMore: { + gridTemplateColumns: 'repeat(4, auto)', + }, + }, + }, +}) + +/* ------------------ Move to Page ------------------ */ + +const currentPageIdSelector = (s: Data) => s.appState.currentPageId +const documentPagesSelector = (s: Data) => s.document.pages + +function MoveToPageMenu(): JSX.Element | null { + const { tlstate, useSelector } = useTLDrawContext() + const currentPageId = useSelector(currentPageIdSelector) + const documentPages = useSelector(documentPagesSelector) + + const sorted = Object.values(documentPages) + .sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0)) + .filter((a) => a.id !== currentPageId) + + if (sorted.length === 0) return null + + return ( + + Move To Page + + + {sorted.map(({ id, name }, i) => ( + tlstate.moveToPage(id)} + > + {name || `Page ${i}`} + + ))} + + + + + ) +} + +/* --------------------- Submenu -------------------- */ + +export interface ContextMenuSubMenuProps { + label: string + children: React.ReactNode +} + +export function ContextMenuSubMenu({ children, label }: ContextMenuSubMenuProps): JSX.Element { + return ( + + {label} + + + {children} + + + + + ) +} + +/* ---------------------- Arrow --------------------- */ + +const CMArrow = styled(RadixContextMenu.ContextMenuArrow, { + fill: '$panel', +}) diff --git a/packages/tldraw/src/components/ContextMenu/index.ts b/packages/tldraw/src/components/ContextMenu/index.ts new file mode 100644 index 000000000..47f487393 --- /dev/null +++ b/packages/tldraw/src/components/ContextMenu/index.ts @@ -0,0 +1 @@ +export * from './ContextMenu' diff --git a/packages/tldraw/src/components/Divider/Divider.tsx b/packages/tldraw/src/components/Divider/Divider.tsx new file mode 100644 index 000000000..406840659 --- /dev/null +++ b/packages/tldraw/src/components/Divider/Divider.tsx @@ -0,0 +1,12 @@ +import * as React from 'react' +import styled from '~styles' + +export const Divider = styled('hr', { + height: 1, + marginTop: '$1', + marginRight: '-$2', + marginBottom: '$1', + marginLeft: '-$2', + border: 'none', + borderBottom: '1px solid $hover', +}) diff --git a/packages/tldraw/src/components/Divider/index.ts b/packages/tldraw/src/components/Divider/index.ts new file mode 100644 index 000000000..1b8726417 --- /dev/null +++ b/packages/tldraw/src/components/Divider/index.ts @@ -0,0 +1 @@ +export * from './Divider' diff --git a/packages/tldraw/src/components/DropdownMenu/DMArrow.tsx b/packages/tldraw/src/components/DropdownMenu/DMArrow.tsx new file mode 100644 index 000000000..db5452d7a --- /dev/null +++ b/packages/tldraw/src/components/DropdownMenu/DMArrow.tsx @@ -0,0 +1,5 @@ +import { Arrow } from '@radix-ui/react-dropdown-menu' +import { breakpoints } from '~components/breakpoints' +import styled from '~styles/stitches.config' + +export const DMArrow = styled(Arrow, { fill: '$panel', bp: breakpoints }) diff --git a/packages/tldraw/src/components/DropdownMenu/DMCheckboxItem.tsx b/packages/tldraw/src/components/DropdownMenu/DMCheckboxItem.tsx new file mode 100644 index 000000000..f8dc4b50c --- /dev/null +++ b/packages/tldraw/src/components/DropdownMenu/DMCheckboxItem.tsx @@ -0,0 +1,33 @@ +import * as React from 'react' +import { CheckboxItem } from '@radix-ui/react-dropdown-menu' +import { RowButton } from '~components/RowButton' + +interface DMCheckboxItemProps { + checked: boolean + disabled?: boolean + onCheckedChange: (isChecked: boolean) => void + children: React.ReactNode + kbd?: string +} + +export function DMCheckboxItem({ + checked, + disabled = false, + onCheckedChange, + kbd, + children, +}: DMCheckboxItemProps): JSX.Element { + return ( + + + {children} + + + ) +} diff --git a/packages/tldraw/src/components/DropdownMenu/DMContent.tsx b/packages/tldraw/src/components/DropdownMenu/DMContent.tsx new file mode 100644 index 000000000..5b5fb19c6 --- /dev/null +++ b/packages/tldraw/src/components/DropdownMenu/DMContent.tsx @@ -0,0 +1,36 @@ +import * as React from 'react' +import { Content } from '@radix-ui/react-dropdown-menu' +import styled from '~styles/stitches.config' +import { MenuContent } from '~components/MenuContent' + +export interface DMContentProps { + variant?: 'grid' | 'menu' + align?: 'start' | 'center' | 'end' + children: React.ReactNode +} + +export function DMContent({ children, align, variant }: DMContentProps): JSX.Element { + return ( + + {children} + + ) +} + +export const StyledContent = styled(MenuContent, { + width: 'fit-content', + height: 'fit-content', + minWidth: 0, + variants: { + variant: { + grid: { + display: 'grid', + gridTemplateColumns: 'repeat(4, auto)', + gap: 0, + }, + menu: { + minWidth: 128, + }, + }, + }, +}) diff --git a/packages/tldraw/src/components/DropdownMenu/DMDivider.tsx b/packages/tldraw/src/components/DropdownMenu/DMDivider.tsx new file mode 100644 index 000000000..6e53c348c --- /dev/null +++ b/packages/tldraw/src/components/DropdownMenu/DMDivider.tsx @@ -0,0 +1,11 @@ +import { Separator } from '@radix-ui/react-dropdown-menu' +import styled from '~styles/stitches.config' + +export const DMDivider = styled(Separator, { + backgroundColor: '$hover', + height: 1, + marginTop: '$2', + marginRight: '-$2', + marginBottom: '$2', + marginLeft: '-$2', +}) diff --git a/packages/tldraw/src/components/DropdownMenu/DMIconButton.tsx b/packages/tldraw/src/components/DropdownMenu/DMIconButton.tsx new file mode 100644 index 000000000..05c347ca6 --- /dev/null +++ b/packages/tldraw/src/components/DropdownMenu/DMIconButton.tsx @@ -0,0 +1,23 @@ +import * as React from 'react' +import { Item } from '@radix-ui/react-dropdown-menu' +import { IconButton } from '~components/IconButton/IconButton' + +interface DMIconButtonProps { + onSelect: () => void + disabled?: boolean + children: React.ReactNode +} + +export function DMIconButton({ + onSelect, + children, + disabled = false, +}: DMIconButtonProps): JSX.Element { + return ( + + + {children} + + + ) +} diff --git a/packages/tldraw/src/components/DropdownMenu/DMItem.tsx b/packages/tldraw/src/components/DropdownMenu/DMItem.tsx new file mode 100644 index 000000000..8388a4c0e --- /dev/null +++ b/packages/tldraw/src/components/DropdownMenu/DMItem.tsx @@ -0,0 +1,11 @@ +import * as React from 'react' +import { Item } from '@radix-ui/react-dropdown-menu' +import { RowButton, RowButtonProps } from '~components/RowButton' + +export function DMItem({ onSelect, ...rest }: RowButtonProps): JSX.Element { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/DropdownMenu/DMRadioItem.tsx b/packages/tldraw/src/components/DropdownMenu/DMRadioItem.tsx new file mode 100644 index 000000000..aa1915638 --- /dev/null +++ b/packages/tldraw/src/components/DropdownMenu/DMRadioItem.tsx @@ -0,0 +1,26 @@ +import { RadioItem } from '@radix-ui/react-dropdown-menu' +import styled from '~styles/stitches.config' + +export const DMRadioItem = styled(RadioItem, { + height: '32px', + width: '32px', + backgroundColor: '$panel', + borderRadius: '4px', + padding: '0', + margin: '0', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + outline: 'none', + border: 'none', + pointerEvents: 'all', + cursor: 'pointer', + + '&:focus': { + backgroundColor: '$hover', + }, + + '&:hover:not(:disabled)': { + backgroundColor: '$hover', + }, +}) diff --git a/packages/tldraw/src/components/DropdownMenu/DMSubMenu.tsx b/packages/tldraw/src/components/DropdownMenu/DMSubMenu.tsx new file mode 100644 index 000000000..af3ff7e54 --- /dev/null +++ b/packages/tldraw/src/components/DropdownMenu/DMSubMenu.tsx @@ -0,0 +1,28 @@ +import * as React from 'react' +import { Root, TriggerItem, Content, Arrow } from '@radix-ui/react-dropdown-menu' +import { RowButton } from '~components/RowButton' +import { MenuContent } from '~components/MenuContent' + +export interface DMSubMenuProps { + label: string + disabled?: boolean + children: React.ReactNode +} + +export function DMSubMenu({ children, disabled = false, label }: DMSubMenuProps): JSX.Element { + return ( + + + + {label} + + + + + {children} + + + + + ) +} diff --git a/packages/tldraw/src/components/DropdownMenu/DMTriggerIcon.tsx b/packages/tldraw/src/components/DropdownMenu/DMTriggerIcon.tsx new file mode 100644 index 000000000..ba0018784 --- /dev/null +++ b/packages/tldraw/src/components/DropdownMenu/DMTriggerIcon.tsx @@ -0,0 +1,15 @@ +import * as React from 'react' +import { Trigger } from '@radix-ui/react-dropdown-menu' +import { ToolButton } from '~components/ToolButton' + +interface DMTriggerIconProps { + children: React.ReactNode +} + +export function DMTriggerIcon({ children }: DMTriggerIconProps) { + return ( + + {children} + + ) +} diff --git a/packages/tldraw/src/components/DropdownMenu/index.tsx b/packages/tldraw/src/components/DropdownMenu/index.tsx new file mode 100644 index 000000000..232f0c0d9 --- /dev/null +++ b/packages/tldraw/src/components/DropdownMenu/index.tsx @@ -0,0 +1,9 @@ +export * from './DMArrow' +export * from './DMItem' +export * from './DMCheckboxItem' +export * from './DMContent' +export * from './DMDivider' +export * from './DMIconButton' +export * from './DMRadioItem' +export * from './DMSubMenu' +export * from './DMTriggerIcon' diff --git a/packages/tldraw/src/components/FocusButton/FocusButton.tsx b/packages/tldraw/src/components/FocusButton/FocusButton.tsx new file mode 100644 index 000000000..b214fb7f8 --- /dev/null +++ b/packages/tldraw/src/components/FocusButton/FocusButton.tsx @@ -0,0 +1,32 @@ +import { DotFilledIcon } from '@radix-ui/react-icons' +import * as React from 'react' +import { IconButton } from '~components/IconButton/IconButton' +import styled from '~styles' + +interface FocusButtonProps { + onSelect: () => void +} + +export function FocusButton({ onSelect }: FocusButtonProps) { + return ( + + + + + + ) +} + +const StyledButtonContainer = styled('div', { + opacity: 1, + zIndex: 100, + backgroundColor: 'transparent', + + '& svg': { + color: '$muted', + }, + + '&:hover svg': { + color: '$text', + }, +}) diff --git a/packages/tldraw/src/components/FocusButton/index.ts b/packages/tldraw/src/components/FocusButton/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/tldraw/src/components/shared/icon-button.tsx b/packages/tldraw/src/components/IconButton/IconButton.tsx similarity index 85% rename from packages/tldraw/src/components/shared/icon-button.tsx rename to packages/tldraw/src/components/IconButton/IconButton.tsx index 7c6fbfcd9..63471636b 100644 --- a/packages/tldraw/src/components/shared/icon-button.tsx +++ b/packages/tldraw/src/components/IconButton/IconButton.tsx @@ -1,10 +1,6 @@ -import css from '~styles' +import styled from '~styles' -/* -------------------------------------------------- */ -/* Icon Button */ -/* -------------------------------------------------- */ - -export const iconButton = css({ +export const IconButton = styled('button', { position: 'relative', height: '32px', width: '32px', @@ -12,15 +8,15 @@ export const iconButton = css({ borderRadius: '4px', padding: '0', margin: '0', - display: 'grid', - alignItems: 'center', - justifyContent: 'center', outline: 'none', border: 'none', pointerEvents: 'all', fontSize: '$0', color: '$text', cursor: 'pointer', + display: 'grid', + alignItems: 'center', + justifyContent: 'center', '& > *': { gridRow: 1, diff --git a/packages/tldraw/src/components/IconButton/index.ts b/packages/tldraw/src/components/IconButton/index.ts new file mode 100644 index 000000000..a37a7fc4a --- /dev/null +++ b/packages/tldraw/src/components/IconButton/index.ts @@ -0,0 +1 @@ +export * from './IconButton' diff --git a/packages/tldraw/src/components/shared/kbd.tsx b/packages/tldraw/src/components/Kbd/Kbd.tsx similarity index 85% rename from packages/tldraw/src/components/shared/kbd.tsx rename to packages/tldraw/src/components/Kbd/Kbd.tsx index 311cf1baa..b24cb954b 100644 --- a/packages/tldraw/src/components/shared/kbd.tsx +++ b/packages/tldraw/src/components/Kbd/Kbd.tsx @@ -1,14 +1,12 @@ import * as React from 'react' -import css from '~styles' +import styled from '~styles' import { Utils } from '@tldraw/core' /* -------------------------------------------------- */ /* Keyboard Shortcut */ /* -------------------------------------------------- */ -export function commandKey(): string { - return Utils.isDarwin() ? '⌘' : 'Ctrl' -} +const commandKey = () => (Utils.isDarwin() ? '⌘' : 'Ctrl') export function Kbd({ variant, @@ -18,18 +16,18 @@ export function Kbd({ children: string }): JSX.Element | null { return ( - + {children .replaceAll('#', commandKey()) .split('') .map((k, i) => ( {k} ))} - + ) } -export const kbd = css({ +export const StyledKbd = styled('kbd', { marginLeft: '$3', textShadow: '$2', textAlign: 'center', diff --git a/packages/tldraw/src/components/Kbd/index.ts b/packages/tldraw/src/components/Kbd/index.ts new file mode 100644 index 000000000..f9fae9026 --- /dev/null +++ b/packages/tldraw/src/components/Kbd/index.ts @@ -0,0 +1 @@ +export * from './Kbd' diff --git a/packages/tldraw/src/components/MenuContent/MenuContent.ts b/packages/tldraw/src/components/MenuContent/MenuContent.ts new file mode 100644 index 000000000..3130108bb --- /dev/null +++ b/packages/tldraw/src/components/MenuContent/MenuContent.ts @@ -0,0 +1,17 @@ +import styled from '~styles' + +export const MenuContent = styled('div', { + position: 'relative', + overflow: 'hidden', + userSelect: 'none', + display: 'flex', + flexDirection: 'column', + zIndex: 180, + minWidth: 180, + pointerEvents: 'all', + backgroundColor: '$panel', + boxShadow: '$panel', + padding: '$2 $2', + borderRadius: '$3', + font: '$ui', +}) diff --git a/packages/tldraw/src/components/MenuContent/index.ts b/packages/tldraw/src/components/MenuContent/index.ts new file mode 100644 index 000000000..b83f44f2e --- /dev/null +++ b/packages/tldraw/src/components/MenuContent/index.ts @@ -0,0 +1 @@ +export * from './MenuContent' diff --git a/packages/tldraw/src/components/Panel/Panel.tsx b/packages/tldraw/src/components/Panel/Panel.tsx new file mode 100644 index 000000000..154bcfdfb --- /dev/null +++ b/packages/tldraw/src/components/Panel/Panel.tsx @@ -0,0 +1,30 @@ +import styled from '~styles/stitches.config' + +export const Panel = styled('div', { + backgroundColor: '$panel', + display: 'flex', + flexDirection: 'row', + padding: '0 $2', + boxShadow: '$panel', + variants: { + side: { + center: { + borderTopLeftRadius: '$4', + borderTopRightRadius: '$4', + // borderTop: '1px solid $panelBorder', + // borderLeft: '1px solid $panelBorder', + // borderRight: '1px solid $panelBorder', + }, + left: { + borderBottomRightRadius: '$4', + // borderBottom: '1px solid $panelBorder', + // borderRight: '1px solid $panelBorder', + }, + right: { + borderBottomLeftRadius: '$4', + // borderBottom: '1px solid $panelBorder', + // borderLeft: '1px solid $panelBorder', + }, + }, + }, +}) diff --git a/packages/tldraw/src/components/Panel/index.ts b/packages/tldraw/src/components/Panel/index.ts new file mode 100644 index 000000000..303f56392 --- /dev/null +++ b/packages/tldraw/src/components/Panel/index.ts @@ -0,0 +1 @@ +export * from './Panel' diff --git a/packages/tldraw/src/components/RowButton/RowButton.tsx b/packages/tldraw/src/components/RowButton/RowButton.tsx new file mode 100644 index 000000000..cadfa7a76 --- /dev/null +++ b/packages/tldraw/src/components/RowButton/RowButton.tsx @@ -0,0 +1,142 @@ +import { ItemIndicator } from '@radix-ui/react-dropdown-menu' +import { ChevronRightIcon, CheckIcon } from '@radix-ui/react-icons' +import * as React from 'react' +import { breakpoints } from '~components/breakpoints' +import { Kbd } from '~components/Kbd' +import { SmallIcon } from '~components/SmallIcon' +import styled from '~styles' + +export interface RowButtonProps { + onSelect?: () => void + children: React.ReactNode + disabled?: boolean + kbd?: string + isActive?: boolean + isWarning?: boolean + hasIndicator?: boolean + hasArrow?: boolean +} + +export const RowButton = React.forwardRef( + ( + { + onSelect, + isActive = false, + isWarning = false, + hasIndicator = false, + hasArrow = false, + disabled = false, + kbd, + children, + ...rest + }, + ref + ) => { + return ( + + + {children} + {kbd ? {kbd} : undefined} + {hasIndicator && ( + + + + + + )} + {hasArrow && ( + + + + )} + + + ) + } +) + +const StyledRowButtonInner = styled('div', { + height: '100%', + width: '100%', + color: '$text', + fontFamily: '$ui', + fontWeight: 400, + fontSize: '$1', + backgroundColor: '$panel', + borderRadius: '$2', + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + padding: '0 $3', + justifyContent: 'space-between', + border: '1px solid transparent', + + '& svg': { + position: 'relative', + stroke: '$overlay', + strokeWidth: 1, + zIndex: 1, + }, +}) + +export const StyledRowButton = styled('button', { + position: 'relative', + width: '100%', + background: 'none', + border: 'none', + cursor: 'pointer', + height: '32px', + outline: 'none', + borderRadius: 4, + userSelect: 'none', + margin: 0, + padding: '0 0', + + '&[data-disabled]': { + opacity: 0.3, + }, + + '&:disabled': { + opacity: 0.3, + }, + + variants: { + bp: { + mobile: {}, + small: {}, + }, + size: { + icon: { + padding: '4px ', + width: 'auto', + }, + }, + isWarning: { + true: { + color: '$warn', + }, + }, + isActive: { + true: { + backgroundColor: '$hover', + }, + false: { + [`&:hover:not(:disabled) ${StyledRowButtonInner}`]: { + backgroundColor: '$hover', + border: '1px solid $panel', + '& *[data-shy="true"]': { + opacity: 1, + }, + }, + }, + }, + }, +}) diff --git a/packages/tldraw/src/components/RowButton/index.ts b/packages/tldraw/src/components/RowButton/index.ts new file mode 100644 index 000000000..5bcc7ae0f --- /dev/null +++ b/packages/tldraw/src/components/RowButton/index.ts @@ -0,0 +1 @@ +export * from './RowButton' diff --git a/packages/tldraw/src/components/SmallIcon/SmallIcon.tsx b/packages/tldraw/src/components/SmallIcon/SmallIcon.tsx new file mode 100644 index 000000000..6aee4a78d --- /dev/null +++ b/packages/tldraw/src/components/SmallIcon/SmallIcon.tsx @@ -0,0 +1,27 @@ +import styled from '~styles' + +export const SmallIcon = styled('div', { + height: '100%', + borderRadius: '4px', + marginRight: '1px', + width: 'fit-content', + display: 'grid', + alignItems: 'center', + justifyContent: 'center', + outline: 'none', + border: 'none', + pointerEvents: 'all', + cursor: 'pointer', + color: '$text', + + '& svg': { + height: 16, + width: 16, + strokeWidth: 1, + }, + + '& > *': { + gridRow: 1, + gridColumn: 1, + }, +}) diff --git a/packages/tldraw/src/components/SmallIcon/index.ts b/packages/tldraw/src/components/SmallIcon/index.ts new file mode 100644 index 000000000..3f7098ffa --- /dev/null +++ b/packages/tldraw/src/components/SmallIcon/index.ts @@ -0,0 +1 @@ +export * from './SmallIcon' diff --git a/packages/tldraw/src/components/ToolButton/ToolButton.tsx b/packages/tldraw/src/components/ToolButton/ToolButton.tsx new file mode 100644 index 000000000..3229f64ba --- /dev/null +++ b/packages/tldraw/src/components/ToolButton/ToolButton.tsx @@ -0,0 +1,132 @@ +import * as React from 'react' +import { Tooltip } from '~components/Tooltip' +import styled from '~styles' + +export interface ToolButtonProps { + onSelect?: () => void + onDoubleClick?: () => void + isActive?: boolean + variant?: 'icon' | 'text' | 'circle' | 'primary' + children: React.ReactNode +} + +export const ToolButton = React.forwardRef( + ({ onSelect, onDoubleClick, isActive = false, variant, children, ...rest }, ref) => { + return ( + + + {children} + + + ) + } +) + +/* ------------------ With Tooltip ------------------ */ + +interface ToolButtonWithTooltipProps extends ToolButtonProps { + label: string + kbd?: string +} + +export function ToolButtonWithTooltip({ label, kbd, ...rest }: ToolButtonWithTooltipProps) { + return ( + + + + ) +} + +export const StyledToolButtonInner = styled('div', { + position: 'relative', + height: '100%', + width: '100%', + color: '$text', + backgroundColor: '$panel', + borderRadius: '$2', + margin: '0', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontFamily: '$ui', + userSelect: 'none', + boxSizing: 'border-box', + border: '1px solid transparent', + + variants: { + variant: { + primary: { + '& svg': { + width: 20, + height: 20, + }, + }, + icon: { + display: 'grid', + '& > *': { + gridRow: 1, + gridColumn: 1, + }, + }, + text: { + fontSize: '$1', + padding: '0 $3', + }, + circle: { + borderRadius: '100%', + boxShadow: '$panel', + }, + }, + isActive: { + true: { + backgroundColor: '$selected', + color: '$panelActive', + }, + }, + }, +}) + +export const StyledToolButton = styled('button', { + position: 'relative', + color: '$text', + height: '48px', + width: '40px', + fontSize: '$0', + background: 'none', + margin: '0', + padding: '$3 $2', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + outline: 'none', + cursor: 'pointer', + pointerEvents: 'all', + border: 'none', + + variants: { + variant: { + primary: {}, + icon: {}, + text: { + width: 'auto', + }, + circle: {}, + }, + isActive: { + true: {}, + false: { + [`&:hover:not(:disabled) ${StyledToolButtonInner}`]: { + backgroundColor: '$hover', + border: '1px solid $panel', + }, + }, + }, + }, +}) diff --git a/packages/tldraw/src/components/ToolButton/index.ts b/packages/tldraw/src/components/ToolButton/index.ts new file mode 100644 index 000000000..7f7d067a6 --- /dev/null +++ b/packages/tldraw/src/components/ToolButton/index.ts @@ -0,0 +1 @@ +export * from './ToolButton' diff --git a/packages/tldraw/src/components/ToolsPanel/ActionButton.tsx b/packages/tldraw/src/components/ToolsPanel/ActionButton.tsx new file mode 100644 index 000000000..d60bf2b0f --- /dev/null +++ b/packages/tldraw/src/components/ToolsPanel/ActionButton.tsx @@ -0,0 +1,289 @@ +import * as React from 'react' +import { Tooltip } from '~components/Tooltip/Tooltip' +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' +import { useTLDrawContext } from '~hooks' +import styled from '~styles' +import { AlignType, Data, DistributeType, StretchType } from '~types' +import { + ArrowDownIcon, + ArrowUpIcon, + AspectRatioIcon, + CopyIcon, + DotsHorizontalIcon, + GroupIcon, + LockClosedIcon, + LockOpen1Icon, + PinBottomIcon, + PinTopIcon, + RotateCounterClockwiseIcon, + AlignBottomIcon, + AlignCenterHorizontallyIcon, + AlignCenterVerticallyIcon, + AlignLeftIcon, + AlignRightIcon, + AlignTopIcon, + SpaceEvenlyHorizontallyIcon, + SpaceEvenlyVerticallyIcon, + StretchHorizontallyIcon, + StretchVerticallyIcon, +} from '@radix-ui/react-icons' +import { DMContent } from '~components/DropdownMenu' +import { Divider } from '~components/Divider' +import { TrashIcon } from '~components/icons' +import { IconButton } from '~components/IconButton' +import { ToolButton } from '~components/ToolButton' + +const selectedShapesCountSelector = (s: Data) => + s.document.pageStates[s.appState.currentPageId].selectedIds.length + +const isAllLockedSelector = (s: Data) => { + const page = s.document.pages[s.appState.currentPageId] + const { selectedIds } = s.document.pageStates[s.appState.currentPageId] + return selectedIds.every((id) => page.shapes[id].isLocked) +} + +const isAllAspectLockedSelector = (s: Data) => { + const page = s.document.pages[s.appState.currentPageId] + const { selectedIds } = s.document.pageStates[s.appState.currentPageId] + return selectedIds.every((id) => page.shapes[id].isAspectRatioLocked) +} + +const isAllGroupedSelector = (s: Data) => { + const page = s.document.pages[s.appState.currentPageId] + const selectedShapes = s.document.pageStates[s.appState.currentPageId].selectedIds.map( + (id) => page.shapes[id] + ) + + return selectedShapes.every( + (shape) => + shape.children !== undefined || + (shape.parentId === selectedShapes[0].parentId && + selectedShapes[0].parentId !== s.appState.currentPageId) + ) +} + +const hasSelectionSelector = (s: Data) => { + const { selectedIds } = s.document.pageStates[s.appState.currentPageId] + return selectedIds.length > 0 +} + +const hasMultipleSelectionSelector = (s: Data) => { + const { selectedIds } = s.document.pageStates[s.appState.currentPageId] + return selectedIds.length > 1 +} + +export function ActionButton(): JSX.Element { + const { tlstate, useSelector } = useTLDrawContext() + + const isAllLocked = useSelector(isAllLockedSelector) + + const isAllAspectLocked = useSelector(isAllAspectLockedSelector) + + const isAllGrouped = useSelector(isAllGroupedSelector) + + const hasSelection = useSelector(hasSelectionSelector) + + const hasMultipleSelection = useSelector(hasMultipleSelectionSelector) + + const handleRotate = React.useCallback(() => { + tlstate.rotate() + }, [tlstate]) + + const handleDuplicate = React.useCallback(() => { + tlstate.duplicate() + }, [tlstate]) + + const handleToggleLocked = React.useCallback(() => { + tlstate.toggleLocked() + }, [tlstate]) + + const handleToggleAspectRatio = React.useCallback(() => { + tlstate.toggleAspectRatioLocked() + }, [tlstate]) + + const handleGroup = React.useCallback(() => { + tlstate.group() + }, [tlstate]) + + const handleMoveToBack = React.useCallback(() => { + tlstate.moveToBack() + }, [tlstate]) + + const handleMoveBackward = React.useCallback(() => { + tlstate.moveBackward() + }, [tlstate]) + + const handleMoveForward = React.useCallback(() => { + tlstate.moveForward() + }, [tlstate]) + + const handleMoveToFront = React.useCallback(() => { + tlstate.moveToFront() + }, [tlstate]) + + const handleDelete = React.useCallback(() => { + tlstate.delete() + }, [tlstate]) + + const alignTop = React.useCallback(() => { + tlstate.align(AlignType.Top) + }, [tlstate]) + + const alignCenterVertical = React.useCallback(() => { + tlstate.align(AlignType.CenterVertical) + }, [tlstate]) + + const alignBottom = React.useCallback(() => { + tlstate.align(AlignType.Bottom) + }, [tlstate]) + + const stretchVertically = React.useCallback(() => { + tlstate.stretch(StretchType.Vertical) + }, [tlstate]) + + const distributeVertically = React.useCallback(() => { + tlstate.distribute(DistributeType.Vertical) + }, [tlstate]) + + const alignLeft = React.useCallback(() => { + tlstate.align(AlignType.Left) + }, [tlstate]) + + const alignCenterHorizontal = React.useCallback(() => { + tlstate.align(AlignType.CenterHorizontal) + }, [tlstate]) + + const alignRight = React.useCallback(() => { + tlstate.align(AlignType.Right) + }, [tlstate]) + + const stretchHorizontally = React.useCallback(() => { + tlstate.stretch(StretchType.Horizontal) + }, [tlstate]) + + const distributeHorizontally = React.useCallback(() => { + tlstate.distribute(DistributeType.Horizontal) + }, [tlstate]) + + const selectedShapesCount = useSelector(selectedShapesCountSelector) + + const hasTwoOrMore = selectedShapesCount > 1 + const hasThreeOrMore = selectedShapesCount > 2 + + return ( + + + + + + + + <> + + + + + + + + + + + + + + {isAllLocked ? : } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export const ButtonsRow = styled('div', { + position: 'relative', + display: 'flex', + width: '100%', + background: 'none', + border: 'none', + cursor: 'pointer', + outline: 'none', + alignItems: 'center', + justifyContent: 'flex-start', + padding: 0, +}) diff --git a/packages/tldraw/src/components/tools-panel/back-to-content/back-to-content.tsx b/packages/tldraw/src/components/ToolsPanel/BackToContent.tsx similarity index 63% rename from packages/tldraw/src/components/tools-panel/back-to-content/back-to-content.tsx rename to packages/tldraw/src/components/ToolsPanel/BackToContent.tsx index 215835be9..ac1224e33 100644 --- a/packages/tldraw/src/components/tools-panel/back-to-content/back-to-content.tsx +++ b/packages/tldraw/src/components/ToolsPanel/BackToContent.tsx @@ -1,8 +1,9 @@ import * as React from 'react' -import { floatingContainer, rowButton } from '~components/shared' -import css from '~styles' +import styled from '~styles' import type { Data } from '~types' import { useTLDrawContext } from '~hooks' +import { RowButton } from '~components/RowButton' +import { MenuContent } from '~components/MenuContent' const isEmptyCanvasSelector = (s: Data) => Object.keys(s.document.pages[s.appState.currentPageId].shapes).length > 0 && @@ -16,17 +17,16 @@ export const BackToContent = React.memo(() => { if (!isEmptyCanvas) return null return ( -
- -
+ + Back to content + ) }) -const backToContentButton = css(floatingContainer, { +const BackToContentContainer = styled(MenuContent, { pointerEvents: 'all', width: 'fit-content', + minWidth: 0, gridRow: 1, flexGrow: 2, display: 'block', diff --git a/packages/tldraw/src/components/ToolsPanel/LockButton.tsx b/packages/tldraw/src/components/ToolsPanel/LockButton.tsx new file mode 100644 index 000000000..f387c80fe --- /dev/null +++ b/packages/tldraw/src/components/ToolsPanel/LockButton.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' +import { LockClosedIcon, LockOpen1Icon } from '@radix-ui/react-icons' +import { Tooltip } from '~components/Tooltip' +import { useTLDrawContext } from '~hooks' +import { ToolButton } from '~components/ToolButton' +import type { Data } from '~types' + +const isToolLockedSelector = (s: Data) => s.appState.isToolLocked + +export function LockButton(): JSX.Element { + const { tlstate, useSelector } = useTLDrawContext() + + const isToolLocked = useSelector(isToolLockedSelector) + + return ( + + + {isToolLocked ? : } + + + ) +} diff --git a/packages/tldraw/src/components/tools-panel/primary-tools/primary-tools.tsx b/packages/tldraw/src/components/ToolsPanel/PrimaryTools.tsx similarity index 66% rename from packages/tldraw/src/components/tools-panel/primary-tools/primary-tools.tsx rename to packages/tldraw/src/components/ToolsPanel/PrimaryTools.tsx index d7f7f68f9..7e4182449 100644 --- a/packages/tldraw/src/components/tools-panel/primary-tools/primary-tools.tsx +++ b/packages/tldraw/src/components/ToolsPanel/PrimaryTools.tsx @@ -2,6 +2,7 @@ import * as React from 'react' import { ArrowTopRightIcon, CircleIcon, + CursorArrowIcon, Pencil1Icon, Pencil2Icon, SquareIcon, @@ -9,8 +10,8 @@ import { } from '@radix-ui/react-icons' import { Data, TLDrawShapeType } from '~types' import { useTLDrawContext } from '~hooks' -import { floatingContainer } from '~components/shared' -import { PrimaryButton } from '~components/tools-panel/styled' +import { ToolButtonWithTooltip } from '~components/ToolButton' +import { Panel } from '~components/Panel' const activeToolSelector = (s: Data) => s.appState.activeTool @@ -19,6 +20,10 @@ export const PrimaryTools = React.memo((): JSX.Element => { const activeTool = useSelector(activeToolSelector) + const selectSelectTool = React.useCallback(() => { + tlstate.selectTool('select') + }, [tlstate]) + const selectDrawTool = React.useCallback(() => { tlstate.selectTool(TLDrawShapeType.Draw) }, [tlstate]) @@ -44,55 +49,63 @@ export const PrimaryTools = React.memo((): JSX.Element => { }, [tlstate]) return ( -
- + + + + - - + - - + - - + - - + - - + - -
+ + ) }) diff --git a/packages/tldraw/src/components/tools-panel/status-bar/status-bar.tsx b/packages/tldraw/src/components/ToolsPanel/StatusBar.tsx similarity index 76% rename from packages/tldraw/src/components/tools-panel/status-bar/status-bar.tsx rename to packages/tldraw/src/components/ToolsPanel/StatusBar.tsx index 7a490f706..c60557978 100644 --- a/packages/tldraw/src/components/tools-panel/status-bar/status-bar.tsx +++ b/packages/tldraw/src/components/ToolsPanel/StatusBar.tsx @@ -1,7 +1,8 @@ import * as React from 'react' import { useTLDrawContext } from '~hooks' import type { Data } from '~types' -import css from '~styles' +import styled from '~styles' +import { breakpoints } from '~components/breakpoints' const statusSelector = (s: Data) => s.appState.status const activeToolSelector = (s: Data) => s.appState.activeTool @@ -12,15 +13,15 @@ export function StatusBar(): JSX.Element | null { const activeTool = useSelector(activeToolSelector) return ( -
-
+ + {activeTool} | {status} -
-
+ + ) } -const statusBarContainer = css({ +const StyledStatusBar = styled('div', { height: 40, userSelect: 'none', borderTop: '1px solid $border', @@ -36,7 +37,7 @@ const statusBarContainer = css({ padding: '0 16px', variants: { - size: { + bp: { small: { fontSize: '$1', }, @@ -44,7 +45,7 @@ const statusBarContainer = css({ }, }) -const section = css({ +const StyledSection = styled('div', { whiteSpace: 'nowrap', overflow: 'hidden', }) diff --git a/packages/tldraw/src/components/tools-panel/tools-panel.test.tsx b/packages/tldraw/src/components/ToolsPanel/ToolsPanel.test.tsx similarity index 82% rename from packages/tldraw/src/components/tools-panel/tools-panel.test.tsx rename to packages/tldraw/src/components/ToolsPanel/ToolsPanel.test.tsx index 12ba8d9f6..fce5e771a 100644 --- a/packages/tldraw/src/components/tools-panel/tools-panel.test.tsx +++ b/packages/tldraw/src/components/ToolsPanel/ToolsPanel.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { ToolsPanel } from './tools-panel' +import { ToolsPanel } from './ToolsPanel' import { renderWithContext } from '~test' describe('tools panel', () => { diff --git a/packages/tldraw/src/components/ToolsPanel/ToolsPanel.tsx b/packages/tldraw/src/components/ToolsPanel/ToolsPanel.tsx new file mode 100644 index 000000000..ddf00887c --- /dev/null +++ b/packages/tldraw/src/components/ToolsPanel/ToolsPanel.tsx @@ -0,0 +1,78 @@ +import * as React from 'react' +import styled from '~styles' +import type { Data } from '~types' +import { useTLDrawContext } from '~hooks' +import { StatusBar } from './StatusBar' +import { BackToContent } from './BackToContent' +import { PrimaryTools } from './PrimaryTools' +import { ActionButton } from './ActionButton' +import { LockButton } from './LockButton' + +const isDebugModeSelector = (s: Data) => s.settings.isDebugMode + +export const ToolsPanel = React.memo((): JSX.Element => { + const { useSelector } = useTLDrawContext() + + const isDebugMode = useSelector(isDebugModeSelector) + + return ( + + + + + + + + + + {isDebugMode && ( + + + + )} + + ) +}) + +const StyledToolsPanelContainer = styled('div', { + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + width: '100%', + minWidth: 0, + maxWidth: '100%', + display: 'grid', + gridTemplateColumns: 'auto auto auto', + gridTemplateRows: 'auto auto', + justifyContent: 'space-between', + padding: '0', + alignItems: 'flex-end', + zIndex: 200, + pointerEvents: 'none', + '& > div > *': { + pointerEvents: 'all', + }, +}) + +const StyledCenterWrap = styled('div', { + gridRow: 1, + gridColumn: 2, + display: 'flex', + width: 'fit-content', + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'column', + gap: 12, +}) + +const StyledStatusWrap = styled('div', { + gridRow: 2, + gridColumn: '1 / span 3', +}) + +const StyledPrimaryTools = styled('div', { + position: 'relative', + display: 'flex', + gap: '$2', +}) diff --git a/packages/tldraw/src/components/ToolsPanel/index.ts b/packages/tldraw/src/components/ToolsPanel/index.ts new file mode 100644 index 000000000..78bb05019 --- /dev/null +++ b/packages/tldraw/src/components/ToolsPanel/index.ts @@ -0,0 +1 @@ +export * from './ToolsPanel' diff --git a/packages/tldraw/src/components/shared/tooltip.tsx b/packages/tldraw/src/components/Tooltip/Tooltip.tsx similarity index 75% rename from packages/tldraw/src/components/shared/tooltip.tsx rename to packages/tldraw/src/components/Tooltip/Tooltip.tsx index 677bd2178..7d1aa4693 100644 --- a/packages/tldraw/src/components/shared/tooltip.tsx +++ b/packages/tldraw/src/components/Tooltip/Tooltip.tsx @@ -1,7 +1,7 @@ import * as RadixTooltip from '@radix-ui/react-tooltip' import * as React from 'react' -import css from '~styles' -import { Kbd } from './kbd' +import { Kbd } from '~components/Kbd' +import styled from '~styles' /* -------------------------------------------------- */ /* Tooltip */ @@ -25,22 +25,16 @@ export function Tooltip({ {children} - + {label} {kbdProp ? {kbdProp} : null} - - + + ) } -const button = css({ - border: 'none', - background: 'none', - padding: 0, -}) - -const content = css({ +const StyledContent = styled(RadixTooltip.Content, { borderRadius: 3, padding: '$3 $3 $3 $3', fontSize: '$1', @@ -53,7 +47,7 @@ const content = css({ userSelect: 'none', }) -const arrow = css({ +const StyledArrow = styled(RadixTooltip.Arrow, { fill: '$tooltipBg', margin: '0 8px', }) diff --git a/packages/tldraw/src/components/Tooltip/index.ts b/packages/tldraw/src/components/Tooltip/index.ts new file mode 100644 index 000000000..ba15f4073 --- /dev/null +++ b/packages/tldraw/src/components/Tooltip/index.ts @@ -0,0 +1 @@ +export * from './Tooltip' diff --git a/packages/tldraw/src/components/TopPanel/ColorMenu.tsx b/packages/tldraw/src/components/TopPanel/ColorMenu.tsx new file mode 100644 index 000000000..3387cabd2 --- /dev/null +++ b/packages/tldraw/src/components/TopPanel/ColorMenu.tsx @@ -0,0 +1,43 @@ +import * as React from 'react' +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' +import { strokes } from '~shape-utils' +import { useTheme, useTLDrawContext } from '~hooks' +import type { Data, ColorStyle } from '~types' +import CircleIcon from '~components/icons/CircleIcon' +import { DMContent, DMRadioItem, DMTriggerIcon } from '~components/DropdownMenu' +import { BoxIcon } from '~components/icons' +import { IconButton } from '~components/IconButton' +import { ToolButton } from '~components/ToolButton' +import { Tooltip } from '~components/Tooltip' + +const selectColor = (s: Data) => s.appState.selectedStyle.color + +export const ColorMenu = React.memo((): JSX.Element => { + const { theme } = useTheme() + const { tlstate, useSelector } = useTLDrawContext() + + const color = useSelector(selectColor) + + return ( + + + + + + {Object.keys(strokes[theme]).map((colorStyle: string) => ( + tlstate.style({ color: colorStyle as ColorStyle })} + > + + + ))} + + + ) +}) diff --git a/packages/tldraw/src/components/TopPanel/DashMenu.tsx b/packages/tldraw/src/components/TopPanel/DashMenu.tsx new file mode 100644 index 000000000..e1863f49e --- /dev/null +++ b/packages/tldraw/src/components/TopPanel/DashMenu.tsx @@ -0,0 +1,100 @@ +import * as React from 'react' +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' +import { useTLDrawContext } from '~hooks' +import { DashStyle, Data } from '~types' +import { DMContent, DMRadioItem, DMTriggerIcon } from '~components/DropdownMenu' +import { ToolButton } from '~components/ToolButton' +import { Tooltip } from '~components/Tooltip' + +const dashes = { + [DashStyle.Draw]: , + [DashStyle.Solid]: , + [DashStyle.Dashed]: , + [DashStyle.Dotted]: , +} + +const selectDash = (s: Data) => s.appState.selectedStyle.dash + +export const DashMenu = React.memo((): JSX.Element => { + const { tlstate, useSelector } = useTLDrawContext() + + const dash = useSelector(selectDash) + + return ( + + {dashes[dash]} + + {Object.keys(DashStyle).map((dashStyle) => ( + tlstate.style({ dash: dashStyle as DashStyle })} + > + {dashes[dashStyle as DashStyle]} + + ))} + + + ) +}) + +function DashSolidIcon(): JSX.Element { + return ( + + + + ) +} + +function DashDashedIcon(): JSX.Element { + return ( + + + + ) +} + +const dottedDasharray = `${50.26548 * 0.025} ${50.26548 * 0.1}` + +function DashDottedIcon(): JSX.Element { + return ( + + + + ) +} + +function DashDrawIcon(): JSX.Element { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/TopPanel/FillCheckbox.tsx b/packages/tldraw/src/components/TopPanel/FillCheckbox.tsx new file mode 100644 index 000000000..a3d07c75e --- /dev/null +++ b/packages/tldraw/src/components/TopPanel/FillCheckbox.tsx @@ -0,0 +1,31 @@ +import * as React from 'react' +import * as Checkbox from '@radix-ui/react-checkbox' +import { useTLDrawContext } from '~hooks' +import type { Data } from '~types' +import { Tooltip } from '~components/Tooltip' +import { BoxIcon, IsFilledIcon } from '~components/icons' +import { ToolButton } from '~components/ToolButton' + +const isFilledSelector = (s: Data) => s.appState.selectedStyle.isFilled + +export const FillCheckbox = React.memo((): JSX.Element => { + const { tlstate, useSelector } = useTLDrawContext() + + const isFilled = useSelector(isFilledSelector) + + const handleIsFilledChange = React.useCallback( + (isFilled: boolean) => tlstate.style({ isFilled }), + [tlstate] + ) + + return ( + + + + + + + + + ) +}) diff --git a/packages/tldraw/src/components/TopPanel/Menu.tsx b/packages/tldraw/src/components/TopPanel/Menu.tsx new file mode 100644 index 000000000..ad0e4c8cc --- /dev/null +++ b/packages/tldraw/src/components/TopPanel/Menu.tsx @@ -0,0 +1,111 @@ +import * as React from 'react' +import { ExitIcon, HamburgerMenuIcon } from '@radix-ui/react-icons' +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' +import { useTLDrawContext } from '~hooks' +import { PreferencesMenu } from './PreferencesMenu' +import { DMItem, DMContent, DMDivider, DMSubMenu, DMTriggerIcon } from '~components/DropdownMenu' +import { SmallIcon } from '~components/SmallIcon' + +export const Menu = React.memo(() => { + const { tlstate } = useTLDrawContext() + + const handleNew = React.useCallback(() => { + if (window.confirm('Are you sure you want to start a new project?')) { + tlstate.newProject() + } + }, [tlstate]) + + const handleSave = React.useCallback(() => { + tlstate.saveProject() + }, [tlstate]) + + const handleLoad = React.useCallback(() => { + tlstate.loadProject() + }, [tlstate]) + + const handleSignOut = React.useCallback(() => { + tlstate.signOut() + }, [tlstate]) + + const handleCopy = React.useCallback(() => { + tlstate.copy() + }, [tlstate]) + + const handlePaste = React.useCallback(() => { + tlstate.paste() + }, [tlstate]) + + const handleCopySvg = React.useCallback(() => { + tlstate.copySvg() + }, [tlstate]) + + const handleCopyJson = React.useCallback(() => { + tlstate.copyJson() + }, [tlstate]) + + const handleSelectAll = React.useCallback(() => { + tlstate.selectAll() + }, [tlstate]) + + const handleDeselectAll = React.useCallback(() => { + tlstate.deselectAll() + }, [tlstate]) + + return ( + + + + + + + + New Project + + + Open... + + + Save + + + Save As... + + + + + Undo + + + Redo + + + + Copy + + + Paste + + + + Copy as SVG + + Copy as JSON + + + Select All + + Select None + + + + + + Sign Out + + + + + + + ) +}) diff --git a/packages/tldraw/src/components/page-panel/page-panel.tsx b/packages/tldraw/src/components/TopPanel/PageMenu.tsx similarity index 62% rename from packages/tldraw/src/components/page-panel/page-panel.tsx rename to packages/tldraw/src/components/TopPanel/PageMenu.tsx index de2f49343..63168c68d 100644 --- a/packages/tldraw/src/components/page-panel/page-panel.tsx +++ b/packages/tldraw/src/components/TopPanel/PageMenu.tsx @@ -1,19 +1,14 @@ import * as React from 'react' import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import { PlusIcon, CheckIcon } from '@radix-ui/react-icons' -import { - breakpoints, - DropdownMenuButton, - DropdownMenuDivider, - rowButton, - menuContent, - floatingContainer, - iconWrapper, -} from '~components/shared' -import { PageOptionsDialog } from '~components/page-options-dialog' -import css from '~styles' +import { PageOptionsDialog } from './PageOptionsDialog' +import styled from '~styles' import { useTLDrawContext } from '~hooks' import type { Data } from '~types' +import { DMContent, DMDivider } from '~components/DropdownMenu' +import { SmallIcon } from '~components/SmallIcon' +import { RowButton } from '~components/RowButton' +import { ToolButton } from '~components/ToolButton' const sortedSelector = (s: Data) => Object.values(s.document.pages).sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0)) @@ -22,7 +17,7 @@ const currentPageNameSelector = (s: Data) => s.document.pages[s.appState.current const currentPageIdSelector = (s: Data) => s.document.pages[s.appState.currentPageId].id -export function PagePanel(): JSX.Element { +export function PageMenu(): JSX.Element { const { useSelector } = useTLDrawContext() const rIsOpen = React.useRef(false) @@ -51,14 +46,12 @@ export function PagePanel(): JSX.Element { return ( -
- - {currentPageName || 'Page'} - -
- + + {currentPageName || 'Page'} + + {isOpen && } - +
) } @@ -86,34 +79,40 @@ function PageMenuContent({ onClose }: { onClose: () => void }) { <> {sortedPages.map((page) => ( -
+ - {page.name || 'Page'} - -
- -
-
+ + {page.name || 'Page'} + + + + + +
-
+ ))}
- - - Create Page -
- -
-
+ + + + Create Page + + + + + ) } -const buttonWithOptions = css({ +const ButtonWithOptions = styled('div', { display: 'grid', gridTemplateColumns: '1fr auto', gridAutoFlow: 'column', @@ -126,3 +125,7 @@ const buttonWithOptions = css({ opacity: 1, }, }) + +export const PageButton = styled(RowButton, { + minWidth: 128, +}) diff --git a/packages/tldraw/src/components/TopPanel/PageOptionsDialog.tsx b/packages/tldraw/src/components/TopPanel/PageOptionsDialog.tsx new file mode 100644 index 000000000..efe858e32 --- /dev/null +++ b/packages/tldraw/src/components/TopPanel/PageOptionsDialog.tsx @@ -0,0 +1,139 @@ +import * as React from 'react' +import * as Dialog from '@radix-ui/react-alert-dialog' +import { MixerVerticalIcon } from '@radix-ui/react-icons' +import type { Data, TLDrawPage } from '~types' +import { useTLDrawContext } from '~hooks' +import { RowButton, RowButtonProps } from '~components/RowButton' +import styled from '~styles' +import { Divider } from '~components/Divider' +import { IconButton } from '~components/IconButton/IconButton' +import { SmallIcon } from '~components/SmallIcon' +import { breakpoints } from '~components/breakpoints' + +const canDeleteSelector = (s: Data) => { + return Object.keys(s.document.pages).length > 1 +} + +interface PageOptionsDialogProps { + page: TLDrawPage + onOpen?: () => void + onClose?: () => void +} + +export function PageOptionsDialog({ page, onOpen, onClose }: PageOptionsDialogProps): JSX.Element { + const { tlstate, useSelector } = useTLDrawContext() + + const [isOpen, setIsOpen] = React.useState(false) + + const canDelete = useSelector(canDeleteSelector) + + const rInput = React.useRef(null) + + const handleDuplicate = React.useCallback(() => { + tlstate.duplicatePage(page.id) + onClose?.() + }, [tlstate]) + + const handleDelete = React.useCallback(() => { + if (window.confirm(`Are you sure you want to delete this page?`)) { + tlstate.deletePage(page.id) + onClose?.() + } + }, [tlstate]) + + const handleOpenChange = React.useCallback( + (isOpen: boolean) => { + setIsOpen(isOpen) + + if (isOpen) { + onOpen?.() + return + } + }, + [tlstate, name] + ) + + function stopPropagation(e: React.KeyboardEvent) { + e.stopPropagation() + } + + // TODO: Replace with text input + function handleRename() { + const nextName = window.prompt('New name:', page.name) + tlstate.renamePage(page.id, nextName || page.name || 'Page') + } + + React.useEffect(() => { + if (isOpen) { + requestAnimationFrame(() => { + rInput.current?.focus() + rInput.current?.select() + }) + } + }, [isOpen]) + + return ( + + + + + + + + + + + Rename + Duplicate + + Delete + + + + Cancel + + + + ) +} + +/* -------------------------------------------------- */ +/* Dialog */ +/* -------------------------------------------------- */ + +export const StyledDialogContent = styled(Dialog.Content, { + position: 'fixed', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + minWidth: 240, + maxWidth: 'fit-content', + maxHeight: '85vh', + marginTop: '-5vh', + pointerEvents: 'all', + backgroundColor: '$panel', + border: '1px solid $panelBorder', + padding: '$0', + borderRadius: '$2', + font: '$ui', + '&:focus': { + outline: 'none', + }, +}) + +export const StyledDialogOverlay = styled(Dialog.Overlay, { + backgroundColor: 'rgba(0, 0, 0, .15)', + position: 'fixed', + top: 0, + right: 0, + bottom: 0, + left: 0, +}) + +function DialogAction({ onSelect, ...rest }: RowButtonProps) { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/TopPanel/PreferencesMenu.tsx b/packages/tldraw/src/components/TopPanel/PreferencesMenu.tsx new file mode 100644 index 000000000..e9de8294b --- /dev/null +++ b/packages/tldraw/src/components/TopPanel/PreferencesMenu.tsx @@ -0,0 +1,70 @@ +import * as React from 'react' +import { DMCheckboxItem, DMDivider, DMSubMenu } from '~components/DropdownMenu' +import { useTLDrawContext } from '~hooks' +import type { Data } from '~types' + +const settingsSelector = (s: Data) => s.settings + +export function PreferencesMenu() { + const { tlstate, useSelector } = useTLDrawContext() + + const settings = useSelector(settingsSelector) + + const toggleDebugMode = React.useCallback(() => { + tlstate.setSetting('isDebugMode', (v) => !v) + }, [tlstate]) + + const toggleDarkMode = React.useCallback(() => { + tlstate.setSetting('isDarkMode', (v) => !v) + }, [tlstate]) + + const toggleFocusMode = React.useCallback(() => { + tlstate.setSetting('isFocusMode', (v) => !v) + }, [tlstate]) + + const toggleRotateHandle = React.useCallback(() => { + tlstate.setSetting('showRotateHandles', (v) => !v) + }, [tlstate]) + + const toggleBoundShapesHandle = React.useCallback(() => { + tlstate.setSetting('showBindingHandles', (v) => !v) + }, [tlstate]) + + const toggleisSnapping = React.useCallback(() => { + tlstate.setSetting('isSnapping', (v) => !v) + }, [tlstate]) + + const toggleCloneControls = React.useCallback(() => { + tlstate.setSetting('showCloneHandles', (v) => !v) + }, [tlstate]) + + return ( + + + Dark Mode + + + Focus Mode + + + Debug Mode + + + + Rotate Handles + + + Binding Handles + + + Clone Handles + + + Always Show Snaps + + + ) +} diff --git a/packages/tldraw/src/components/TopPanel/SizeMenu.tsx b/packages/tldraw/src/components/TopPanel/SizeMenu.tsx new file mode 100644 index 000000000..a34f766fd --- /dev/null +++ b/packages/tldraw/src/components/TopPanel/SizeMenu.tsx @@ -0,0 +1,39 @@ +import * as React from 'react' +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' +import { Data, SizeStyle } from '~types' +import { useTLDrawContext } from '~hooks' +import { DMContent, DMTriggerIcon } from '~components/DropdownMenu' +import { ToolButton } from '~components/ToolButton' +import { SizeSmallIcon, SizeMediumIcon, SizeLargeIcon } from '~components/icons' +import { Tooltip } from '~components/Tooltip' + +const sizes = { + [SizeStyle.Small]: , + [SizeStyle.Medium]: , + [SizeStyle.Large]: , +} + +const selectSize = (s: Data) => s.appState.selectedStyle.size + +export const SizeMenu = React.memo((): JSX.Element => { + const { tlstate, useSelector } = useTLDrawContext() + + const size = useSelector(selectSize) + + return ( + + {sizes[size as SizeStyle]} + + {Object.keys(SizeStyle).map((sizeStyle: string) => ( + tlstate.style({ size: sizeStyle as SizeStyle })} + > + {sizes[sizeStyle as SizeStyle]} + + ))} + + + ) +}) diff --git a/packages/tldraw/src/components/TopPanel/TopPanel.tsx b/packages/tldraw/src/components/TopPanel/TopPanel.tsx new file mode 100644 index 000000000..0b5942e5e --- /dev/null +++ b/packages/tldraw/src/components/TopPanel/TopPanel.tsx @@ -0,0 +1,58 @@ +import * as React from 'react' +import { Menu } from './Menu' +import styled from '~styles' +import { PageMenu } from './PageMenu' +import { ZoomMenu } from './ZoomMenu' +import { DashMenu } from './DashMenu' +import { SizeMenu } from './SizeMenu' +import { FillCheckbox } from './FillCheckbox' +import { ColorMenu } from './ColorMenu' +import { Panel } from '~components/Panel' + +interface TopPanelProps { + showPages: boolean + showMenu: boolean + showStyles: boolean + showZoom: boolean +} + +export function TopPanel({ showPages, showMenu, showStyles, showZoom }: TopPanelProps) { + return ( + + {(showMenu || showPages) && ( + + {showMenu && } + {showPages && } + + )} + + {(showStyles || showZoom) && ( + + {showStyles && ( + <> + + + + + + )} + {showZoom && } + + )} + + ) +} + +const StyledTopPanel = styled('div', { + width: '100%', + position: 'absolute', + top: 0, + left: 0, + right: 0, + display: 'flex', + flexDirection: 'row', +}) + +const StyledSpacer = styled('div', { + flexGrow: 2, +}) diff --git a/packages/tldraw/src/components/TopPanel/ZoomMenu.tsx b/packages/tldraw/src/components/TopPanel/ZoomMenu.tsx new file mode 100644 index 000000000..c2520d6e5 --- /dev/null +++ b/packages/tldraw/src/components/TopPanel/ZoomMenu.tsx @@ -0,0 +1,43 @@ +import * as React from 'react' +import { useTLDrawContext } from '~hooks' +import type { Data } from '~types' +import styled from '~styles' +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' +import { DMItem, DMContent } from '~components/DropdownMenu' +import { ToolButton } from '~components/ToolButton' + +const zoomSelector = (s: Data) => s.document.pageStates[s.appState.currentPageId].camera.zoom + +export function ZoomMenu() { + const { tlstate, useSelector } = useTLDrawContext() + const zoom = useSelector(zoomSelector) + + return ( + + + {Math.round(zoom * 100)}% + + + + Zoom In + + + Zoom Out + + + To 100% + + + To Fit + + + To Selection + + + + ) +} + +const FixedWidthToolButton = styled(ToolButton, { + minWidth: 56, +}) diff --git a/packages/tldraw/src/components/TopPanel/index.ts b/packages/tldraw/src/components/TopPanel/index.ts new file mode 100644 index 000000000..acfe58700 --- /dev/null +++ b/packages/tldraw/src/components/TopPanel/index.ts @@ -0,0 +1 @@ +export * from './TopPanel' diff --git a/packages/tldraw/src/components/shared/breakpoints.tsx b/packages/tldraw/src/components/breakpoints.tsx similarity index 100% rename from packages/tldraw/src/components/shared/breakpoints.tsx rename to packages/tldraw/src/components/breakpoints.tsx diff --git a/packages/tldraw/src/components/context-menu/context-menu.tsx b/packages/tldraw/src/components/context-menu/context-menu.tsx deleted file mode 100644 index 69f3d5420..000000000 --- a/packages/tldraw/src/components/context-menu/context-menu.tsx +++ /dev/null @@ -1,381 +0,0 @@ -import * as React from 'react' -import css from '~styles' -import * as RadixContextMenu from '@radix-ui/react-context-menu' -import { useTLDrawContext } from '~hooks' -import { Data, AlignType, DistributeType, StretchType } from '~types' -import { - Kbd, - iconWrapper, - breakpoints, - rowButton, - ContextMenuArrow, - ContextMenuDivider, - ContextMenuButton, - ContextMenuSubMenu, - ContextMenuIconButton, - ContextMenuRoot, - menuContent, -} from '../shared' -import { - ChevronRightIcon, - AlignBottomIcon, - AlignCenterHorizontallyIcon, - AlignCenterVerticallyIcon, - AlignLeftIcon, - AlignRightIcon, - AlignTopIcon, - SpaceEvenlyHorizontallyIcon, - SpaceEvenlyVerticallyIcon, - StretchHorizontallyIcon, - StretchVerticallyIcon, -} from '@radix-ui/react-icons' - -const has1SelectedIdsSelector = (s: Data) => { - return s.document.pageStates[s.appState.currentPageId].selectedIds.length > 0 -} -const has2SelectedIdsSelector = (s: Data) => { - return s.document.pageStates[s.appState.currentPageId].selectedIds.length > 1 -} -const has3SelectedIdsSelector = (s: Data) => { - return s.document.pageStates[s.appState.currentPageId].selectedIds.length > 2 -} - -const isDebugModeSelector = (s: Data) => { - return s.settings.isDebugMode -} - -const hasGroupSelectedSelector = (s: Data) => { - return s.document.pageStates[s.appState.currentPageId].selectedIds.some( - (id) => s.document.pages[s.appState.currentPageId].shapes[id].children !== undefined - ) -} - -interface ContextMenuProps { - children: React.ReactNode -} - -export const ContextMenu = ({ children }: ContextMenuProps): JSX.Element => { - const { tlstate, useSelector } = useTLDrawContext() - const hasSelection = useSelector(has1SelectedIdsSelector) - const hasTwoOrMore = useSelector(has2SelectedIdsSelector) - const hasThreeOrMore = useSelector(has3SelectedIdsSelector) - const isDebugMode = useSelector(isDebugModeSelector) - const hasGroupSelected = useSelector(hasGroupSelectedSelector) - - const rContent = React.useRef(null) - - const handleFlipHorizontal = React.useCallback(() => { - tlstate.flipHorizontal() - }, [tlstate]) - - const handleFlipVertical = React.useCallback(() => { - tlstate.flipVertical() - }, [tlstate]) - - const handleDuplicate = React.useCallback(() => { - tlstate.duplicate() - }, [tlstate]) - - const handleGroup = React.useCallback(() => { - tlstate.group() - }, [tlstate]) - - const handleMoveToBack = React.useCallback(() => { - tlstate.moveToBack() - }, [tlstate]) - - const handleMoveBackward = React.useCallback(() => { - tlstate.moveBackward() - }, [tlstate]) - - const handleMoveForward = React.useCallback(() => { - tlstate.moveForward() - }, [tlstate]) - - const handleMoveToFront = React.useCallback(() => { - tlstate.moveToFront() - }, [tlstate]) - - const handleDelete = React.useCallback(() => { - tlstate.delete() - }, [tlstate]) - - const handleCopyJson = React.useCallback(() => { - tlstate.copyJson() - }, [tlstate]) - - const handleCopy = React.useCallback(() => { - tlstate.copy() - }, [tlstate]) - - const handlePaste = React.useCallback(() => { - tlstate.paste() - }, [tlstate]) - - const handleCopySvg = React.useCallback(() => { - tlstate.copySvg() - }, [tlstate]) - - const handleUndo = React.useCallback(() => { - tlstate.undo() - }, [tlstate]) - - const handleRedo = React.useCallback(() => { - tlstate.redo() - }, [tlstate]) - - return ( - - {children} - - {hasSelection ? ( - <> - - Flip Horizontal - ⇧H - - - Flip Vertical - ⇧V - - - Duplicate - #D - - - {hasGroupSelected || - (hasTwoOrMore && ( - <> - {hasGroupSelected && ( - - Ungroup - #⇧G - - )} - {hasTwoOrMore && ( - - Group - #G - - )} - - ))} - - - To Front - ⇧] - - - Forward - ] - - - Backward - [ - - - To Back - ⇧[ - - - - {hasTwoOrMore && ( - - )} - - - Copy - #C - - - Copy to SVG - ⇧#C - - {isDebugMode && ( - - Copy to JSON - - )} - - Paste - #V - - - - Delete - - - - ) : ( - <> - - Paste - #V - - - Undo - #Z - - - Redo - #⇧Z - - - )} - - - ) -} - -function AlignDistributeSubMenu({ - hasThreeOrMore, -}: { - hasTwoOrMore: boolean - hasThreeOrMore: boolean -}) { - const { tlstate } = useTLDrawContext() - - const alignTop = React.useCallback(() => { - tlstate.align(AlignType.Top) - }, [tlstate]) - - const alignCenterVertical = React.useCallback(() => { - tlstate.align(AlignType.CenterVertical) - }, [tlstate]) - - const alignBottom = React.useCallback(() => { - tlstate.align(AlignType.Bottom) - }, [tlstate]) - - const stretchVertically = React.useCallback(() => { - tlstate.stretch(StretchType.Vertical) - }, [tlstate]) - - const distributeVertically = React.useCallback(() => { - tlstate.distribute(DistributeType.Vertical) - }, [tlstate]) - - const alignLeft = React.useCallback(() => { - tlstate.align(AlignType.Left) - }, [tlstate]) - - const alignCenterHorizontal = React.useCallback(() => { - tlstate.align(AlignType.CenterHorizontal) - }, [tlstate]) - - const alignRight = React.useCallback(() => { - tlstate.align(AlignType.Right) - }, [tlstate]) - - const stretchHorizontally = React.useCallback(() => { - tlstate.stretch(StretchType.Horizontal) - }, [tlstate]) - - const distributeHorizontally = React.useCallback(() => { - tlstate.distribute(DistributeType.Horizontal) - }, [tlstate]) - - return ( - - - Align / Distribute -
- -
-
- - - - - - - - - - - - - - {hasThreeOrMore && ( - - - - )} - - - - - - - - - - - - - {hasThreeOrMore && ( - - - - )} - - -
- ) -} - -const grid = css(menuContent, { - display: 'grid', - variants: { - selectedStyle: { - threeOrMore: { - gridTemplateColumns: 'repeat(5, auto)', - }, - twoOrMore: { - gridTemplateColumns: 'repeat(4, auto)', - }, - }, - }, -}) - -const currentPageIdSelector = (s: Data) => s.appState.currentPageId -const documentPagesSelector = (s: Data) => s.document.pages - -function MoveToPageMenu(): JSX.Element | null { - const { tlstate, useSelector } = useTLDrawContext() - const currentPageId = useSelector(currentPageIdSelector) - const documentPages = useSelector(documentPagesSelector) - - const sorted = Object.values(documentPages) - .sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0)) - .filter((a) => a.id !== currentPageId) - - if (sorted.length === 0) return null - - return ( - - - Move To Page -
- -
-
- - {sorted.map(({ id, name }, i) => ( - tlstate.moveToPage(id)} - > - {name || `Page ${i}`} - - ))} - - -
- ) -} diff --git a/packages/tldraw/src/components/context-menu/index.ts b/packages/tldraw/src/components/context-menu/index.ts deleted file mode 100644 index beff13a99..000000000 --- a/packages/tldraw/src/components/context-menu/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './context-menu' diff --git a/packages/tldraw/src/components/icons/BoxIcon.tsx b/packages/tldraw/src/components/icons/BoxIcon.tsx new file mode 100644 index 000000000..4a3937d0f --- /dev/null +++ b/packages/tldraw/src/components/icons/BoxIcon.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' + +export function BoxIcon({ + fill = 'none', + stroke = 'currentColor', +}: { + fill?: string + stroke?: string +}): JSX.Element { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/icons/check.tsx b/packages/tldraw/src/components/icons/CheckIcon.tsx similarity index 98% rename from packages/tldraw/src/components/icons/check.tsx rename to packages/tldraw/src/components/icons/CheckIcon.tsx index 563acdfdd..19ee800e3 100644 --- a/packages/tldraw/src/components/icons/check.tsx +++ b/packages/tldraw/src/components/icons/CheckIcon.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -function SvgCheck(props: React.SVGProps): JSX.Element { +function CheckIcon(props: React.SVGProps): JSX.Element { return ( ): JSX.Element { ) } -export default SvgCheck +export default CheckIcon diff --git a/packages/tldraw/src/components/icons/circle.tsx b/packages/tldraw/src/components/icons/CircleIcon.tsx similarity index 100% rename from packages/tldraw/src/components/icons/circle.tsx rename to packages/tldraw/src/components/icons/CircleIcon.tsx diff --git a/packages/tldraw/src/components/icons/DashDashedIcon.tsx b/packages/tldraw/src/components/icons/DashDashedIcon.tsx new file mode 100644 index 000000000..14332057b --- /dev/null +++ b/packages/tldraw/src/components/icons/DashDashedIcon.tsx @@ -0,0 +1,17 @@ +import * as React from 'react' + +export function DashDashedIcon(): JSX.Element { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/icons/DashDottedIcon.tsx b/packages/tldraw/src/components/icons/DashDottedIcon.tsx new file mode 100644 index 000000000..f95793aca --- /dev/null +++ b/packages/tldraw/src/components/icons/DashDottedIcon.tsx @@ -0,0 +1,19 @@ +import * as React from 'react' + +const dottedDasharray = `${50.26548 * 0.025} ${50.26548 * 0.1}` + +export function DashDottedIcon(): JSX.Element { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/icons/DashDrawIcon.tsx b/packages/tldraw/src/components/icons/DashDrawIcon.tsx new file mode 100644 index 000000000..85ded7ed6 --- /dev/null +++ b/packages/tldraw/src/components/icons/DashDrawIcon.tsx @@ -0,0 +1,19 @@ +import * as React from 'react' + +export function DashDrawIcon(): JSX.Element { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/icons/DashSolidIcon.tsx b/packages/tldraw/src/components/icons/DashSolidIcon.tsx new file mode 100644 index 000000000..38acf27c6 --- /dev/null +++ b/packages/tldraw/src/components/icons/DashSolidIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react' + +export function DashSolidIcon(): JSX.Element { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/icons/IsFilledIcon.tsx b/packages/tldraw/src/components/icons/IsFilledIcon.tsx new file mode 100644 index 000000000..9efd8429d --- /dev/null +++ b/packages/tldraw/src/components/icons/IsFilledIcon.tsx @@ -0,0 +1,18 @@ +import * as React from 'react' + +export function IsFilledIcon(): JSX.Element { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/icons/redo.tsx b/packages/tldraw/src/components/icons/RedoIcon.tsx similarity index 70% rename from packages/tldraw/src/components/icons/redo.tsx rename to packages/tldraw/src/components/icons/RedoIcon.tsx index ee8320df2..516c8972b 100644 --- a/packages/tldraw/src/components/icons/redo.tsx +++ b/packages/tldraw/src/components/icons/RedoIcon.tsx @@ -1,8 +1,8 @@ import * as React from 'react' -function SvgRedo(props: React.SVGProps): JSX.Element { +export function RedoIcon(props: React.SVGProps): JSX.Element { return ( - + ): JSX.Element { ) } - -export default SvgRedo diff --git a/packages/tldraw/src/components/icons/SizeLargeIcon.tsx b/packages/tldraw/src/components/icons/SizeLargeIcon.tsx new file mode 100644 index 000000000..151eddf2a --- /dev/null +++ b/packages/tldraw/src/components/icons/SizeLargeIcon.tsx @@ -0,0 +1,12 @@ +import * as React from 'react' + +export function SizeLargeIcon(props: React.SVGProps): JSX.Element { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/icons/SizeMediumIcon.tsx b/packages/tldraw/src/components/icons/SizeMediumIcon.tsx new file mode 100644 index 000000000..ae9bf8778 --- /dev/null +++ b/packages/tldraw/src/components/icons/SizeMediumIcon.tsx @@ -0,0 +1,12 @@ +import * as React from 'react' + +export function SizeMediumIcon(props: React.SVGProps): JSX.Element { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/icons/SizeSmallIcon.tsx b/packages/tldraw/src/components/icons/SizeSmallIcon.tsx new file mode 100644 index 000000000..864787d2f --- /dev/null +++ b/packages/tldraw/src/components/icons/SizeSmallIcon.tsx @@ -0,0 +1,12 @@ +import * as React from 'react' + +export function SizeSmallIcon(props: React.SVGProps): JSX.Element { + return ( + + + + ) +} diff --git a/packages/tldraw/src/components/icons/trash.tsx b/packages/tldraw/src/components/icons/TrashIcon.tsx similarity index 90% rename from packages/tldraw/src/components/icons/trash.tsx rename to packages/tldraw/src/components/icons/TrashIcon.tsx index 229412ff6..8eaa9fc56 100644 --- a/packages/tldraw/src/components/icons/trash.tsx +++ b/packages/tldraw/src/components/icons/TrashIcon.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -function SvgTrash(props: React.SVGProps): JSX.Element { +export function TrashIcon(props: React.SVGProps): JSX.Element { return ( ): JSX.Element { ) } - -export default SvgTrash diff --git a/packages/tldraw/src/components/icons/undo.tsx b/packages/tldraw/src/components/icons/UndoIcon.tsx similarity index 70% rename from packages/tldraw/src/components/icons/undo.tsx rename to packages/tldraw/src/components/icons/UndoIcon.tsx index caf816582..d6215a413 100644 --- a/packages/tldraw/src/components/icons/undo.tsx +++ b/packages/tldraw/src/components/icons/UndoIcon.tsx @@ -1,8 +1,8 @@ import * as React from 'react' -function SvgUndo(props: React.SVGProps): JSX.Element { +export function UndoIcon(props: React.SVGProps): JSX.Element { return ( - + ): JSX.Element { ) } - -export default SvgUndo diff --git a/packages/tldraw/src/components/icons/index.ts b/packages/tldraw/src/components/icons/index.ts new file mode 100644 index 000000000..7702dcbe1 --- /dev/null +++ b/packages/tldraw/src/components/icons/index.ts @@ -0,0 +1,14 @@ +export * from './BoxIcon' +export * from './CheckIcon' +export * from './CircleIcon' +export * from './DashDashedIcon' +export * from './DashDottedIcon' +export * from './DashDrawIcon' +export * from './DashSolidIcon' +export * from './IsFilledIcon' +export * from './RedoIcon' +export * from './TrashIcon' +export * from './UndoIcon' +export * from './SizeSmallIcon' +export * from './SizeMediumIcon' +export * from './SizeLargeIcon' diff --git a/packages/tldraw/src/components/icons/index.tsx b/packages/tldraw/src/components/icons/index.tsx deleted file mode 100644 index 9da9315bf..000000000 --- a/packages/tldraw/src/components/icons/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -export { default as Redo } from './redo' -export { default as Trash } from './trash' -export { default as Undo } from './undo' -export { default as Check } from './check' -export { default as CircleIcon } from './circle' diff --git a/packages/tldraw/src/components/index.ts b/packages/tldraw/src/components/index.ts deleted file mode 100644 index 66f3fc9eb..000000000 --- a/packages/tldraw/src/components/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './shared/tooltip' -export * from './shared/kbd' -export * from './shared' -export * from './icons' -export * from './tldraw' diff --git a/packages/tldraw/src/components/menu/index.ts b/packages/tldraw/src/components/menu/index.ts deleted file mode 100644 index 45467d073..000000000 --- a/packages/tldraw/src/components/menu/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './menu' diff --git a/packages/tldraw/src/components/menu/menu.test.tsx b/packages/tldraw/src/components/menu/menu.test.tsx deleted file mode 100644 index e0ac975b3..000000000 --- a/packages/tldraw/src/components/menu/menu.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import * as React from 'react' -import { Menu } from './menu' -import { renderWithContext } from '~test' - -describe('menu', () => { - test('mounts component without crashing', () => { - renderWithContext() - }) -}) diff --git a/packages/tldraw/src/components/menu/menu.tsx b/packages/tldraw/src/components/menu/menu.tsx deleted file mode 100644 index 392a5a224..000000000 --- a/packages/tldraw/src/components/menu/menu.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import * as React from 'react' -import { ExitIcon, HamburgerMenuIcon } from '@radix-ui/react-icons' -import * as DropdownMenu from '@radix-ui/react-dropdown-menu' -import { - floatingContainer, - DropdownMenuRoot, - menuContent, - iconButton, - breakpoints, - DropdownMenuButton, - DropdownMenuSubMenu, - DropdownMenuDivider, - iconWrapper, - Kbd, -} from '~components/shared' -import { useTLDrawContext } from '~hooks' -import { Preferences } from './preferences' - -export const Menu = React.memo(() => { - const { tlstate } = useTLDrawContext() - - const handleNew = React.useCallback(() => { - if (window.confirm('Are you sure you want to start a new project?')) { - tlstate.newProject() - } - }, [tlstate]) - - const handleSave = React.useCallback(() => { - tlstate.saveProject() - }, [tlstate]) - - const handleLoad = React.useCallback(() => { - tlstate.loadProject() - }, [tlstate]) - - const handleSignOut = React.useCallback(() => { - tlstate.signOut() - }, [tlstate]) - - return ( -
- - - - - - - New Project - #N - - - - Open... - #L - - - - - Save - #S - - - Save As... - ⇧#S - - - - - - Sign Out -
- -
-
-
-
-
- ) -}) - -function RecentFiles() { - return ( - - - Project A - - - Project B - - - Project C - - - ) -} diff --git a/packages/tldraw/src/components/menu/preferences.tsx b/packages/tldraw/src/components/menu/preferences.tsx deleted file mode 100644 index a2b1b8fc8..000000000 --- a/packages/tldraw/src/components/menu/preferences.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import * as React from 'react' -import { - DropdownMenuDivider, - DropdownMenuSubMenu, - DropdownMenuCheckboxItem, - Kbd, -} from '~components/shared' -import { useTLDrawContext } from '~hooks' -import type { Data } from '~types' - -const settingsSelector = (s: Data) => s.settings - -export function Preferences() { - const { tlstate, useSelector } = useTLDrawContext() - - const settings = useSelector(settingsSelector) - - const toggleDebugMode = React.useCallback(() => { - tlstate.setSetting('isDebugMode', (v) => !v) - }, [tlstate]) - - const toggleDarkMode = React.useCallback(() => { - tlstate.setSetting('isDarkMode', (v) => !v) - }, [tlstate]) - - const toggleFocusMode = React.useCallback(() => { - tlstate.setSetting('isFocusMode', (v) => !v) - }, [tlstate]) - - const toggleRotateHandle = React.useCallback(() => { - tlstate.setSetting('showRotateHandles', (v) => !v) - }, [tlstate]) - - const toggleBoundShapesHandle = React.useCallback(() => { - tlstate.setSetting('showBindingHandles', (v) => !v) - }, [tlstate]) - - const toggleisSnapping = React.useCallback(() => { - tlstate.setSetting('isSnapping', (v) => !v) - }, [tlstate]) - - const toggleCloneControls = React.useCallback(() => { - tlstate.setSetting('showCloneHandles', (v) => !v) - }, [tlstate]) - - return ( - - - Dark Mode - #⇧D - - - Focus Mode - ⇧. - - - Debug Mode - - - - Rotate Handles - - - Binding Handles - - - Clone Handles - - - Always Show Snaps - - - ) -} diff --git a/packages/tldraw/src/components/page-options-dialog/index.ts b/packages/tldraw/src/components/page-options-dialog/index.ts deleted file mode 100644 index 2d13bdb3b..000000000 --- a/packages/tldraw/src/components/page-options-dialog/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './page-options-dialog' diff --git a/packages/tldraw/src/components/page-options-dialog/page-options-dialog.test.tsx b/packages/tldraw/src/components/page-options-dialog/page-options-dialog.test.tsx deleted file mode 100644 index f9ceb7e19..000000000 --- a/packages/tldraw/src/components/page-options-dialog/page-options-dialog.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import * as React from 'react' -import { PageOptionsDialog } from './page-options-dialog' -import { mockDocument, renderWithContext } from '~test' - -describe('page options dialog', () => { - test('mounts component without crashing', () => { - renderWithContext() - }) -}) diff --git a/packages/tldraw/src/components/page-options-dialog/page-options-dialog.tsx b/packages/tldraw/src/components/page-options-dialog/page-options-dialog.tsx deleted file mode 100644 index 12a9db124..000000000 --- a/packages/tldraw/src/components/page-options-dialog/page-options-dialog.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import * as React from 'react' -import * as Dialog from '@radix-ui/react-alert-dialog' -import { MixerVerticalIcon } from '@radix-ui/react-icons' -import { - breakpoints, - iconButton, - dialogOverlay, - dialogContent, - rowButton, - divider, -} from '~components/shared' -import type { Data, TLDrawPage } from '~types' -import { useTLDrawContext } from '~hooks' - -const canDeleteSelector = (s: Data) => { - return Object.keys(s.document.pages).length > 1 -} - -interface PageOptionsDialogProps { - page: TLDrawPage - onOpen?: () => void - onClose?: () => void -} - -export function PageOptionsDialog({ page, onOpen, onClose }: PageOptionsDialogProps): JSX.Element { - const { tlstate, useSelector } = useTLDrawContext() - - const [isOpen, setIsOpen] = React.useState(false) - - const canDelete = useSelector(canDeleteSelector) - - const rInput = React.useRef(null) - - const handleDuplicate = React.useCallback(() => { - tlstate.duplicatePage(page.id) - onClose?.() - }, [tlstate]) - - const handleDelete = React.useCallback(() => { - if (window.confirm(`Are you sure you want to delete this page?`)) { - tlstate.deletePage(page.id) - onClose?.() - } - }, [tlstate]) - - const handleOpenChange = React.useCallback( - (isOpen: boolean) => { - setIsOpen(isOpen) - - if (isOpen) { - onOpen?.() - return - } - }, - [tlstate, name] - ) - - function stopPropagation(e: React.KeyboardEvent) { - e.stopPropagation() - } - - // TODO: Replace with text input - function handleRename() { - const nextName = window.prompt('New name:', page.name) - tlstate.renamePage(page.id, nextName || page.name || 'Page') - } - - React.useEffect(() => { - if (isOpen) { - requestAnimationFrame(() => { - rInput.current?.focus() - rInput.current?.select() - }) - } - }, [isOpen]) - - return ( - - - - - - - - Rename - - - Duplicate - - - Delete - -
- Cancel - - - ) -} diff --git a/packages/tldraw/src/components/page-panel/index.ts b/packages/tldraw/src/components/page-panel/index.ts deleted file mode 100644 index 333268ef3..000000000 --- a/packages/tldraw/src/components/page-panel/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './page-panel' diff --git a/packages/tldraw/src/components/page-panel/page-panel.test.tsx b/packages/tldraw/src/components/page-panel/page-panel.test.tsx deleted file mode 100644 index bb577e494..000000000 --- a/packages/tldraw/src/components/page-panel/page-panel.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import * as React from 'react' -import { PagePanel } from './page-panel' -import { renderWithContext } from '~test' - -describe('page panel', () => { - test('mounts component without crashing', () => { - renderWithContext() - }) -}) diff --git a/packages/tldraw/src/components/shared/buttons-row.tsx b/packages/tldraw/src/components/shared/buttons-row.tsx deleted file mode 100644 index a49433191..000000000 --- a/packages/tldraw/src/components/shared/buttons-row.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import css from '~styles' - -/* -------------------------------------------------- */ -/* Buttons Row */ -/* -------------------------------------------------- */ - -export const buttonsRow = css({ - position: 'relative', - display: 'flex', - width: '100%', - background: 'none', - border: 'none', - cursor: 'pointer', - outline: 'none', - alignItems: 'center', - justifyContent: 'flex-start', - padding: 0, -}) diff --git a/packages/tldraw/src/components/shared/context-menu.tsx b/packages/tldraw/src/components/shared/context-menu.tsx deleted file mode 100644 index b014c2ee2..000000000 --- a/packages/tldraw/src/components/shared/context-menu.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import * as React from 'react' -import { CheckIcon, ChevronRightIcon } from '@radix-ui/react-icons' -import { - Root as CMRoot, - TriggerItem as CMTriggerItem, - Separator as CMSeparator, - Item as CMItem, - Arrow as CMArrow, - Content as CMContent, - ItemIndicator as CMItemIndicator, - CheckboxItem as CMCheckboxItem, -} from '@radix-ui/react-context-menu' -import { breakpoints } from './breakpoints' -import { rowButton } from './row-button' -import { iconButton } from './icon-button' -import { iconWrapper } from './icon-wrapper' -import { menuContent } from './menu' -import css from '~styles' - -/* -------------------------------------------------- */ -/* Context Menu */ -/* -------------------------------------------------- */ - -export interface ContextMenuRootProps { - onOpenChange?: (isOpen: boolean) => void - children: React.ReactNode -} - -export function ContextMenuRoot({ onOpenChange, children }: ContextMenuRootProps): JSX.Element { - return ( - - {children} - - ) -} - -export interface ContextMenuSubMenuProps { - label: string - children: React.ReactNode -} - -export function ContextMenuSubMenu({ children, label }: ContextMenuSubMenuProps): JSX.Element { - return ( - - - {label} -
- -
-
- - {children} - - -
- ) -} - -const contextMenuDivider = css({ - backgroundColor: '$hover', - height: 1, - margin: '$2 -$2', -}) - -export const ContextMenuDivider = React.forwardRef< - React.ElementRef, - React.ComponentProps ->((props, forwardedRef) => ( - -)) - -const contextMenuArrow = css({ - fill: '$panel', -}) - -export const ContextMenuArrow = React.forwardRef< - React.ElementRef, - React.ComponentProps ->((props, forwardedRef) => ( - -)) - -export interface ContextMenuButtonProps { - onSelect?: () => void - disabled?: boolean - children: React.ReactNode -} - -export function ContextMenuButton({ - onSelect, - children, - disabled = false, -}: ContextMenuButtonProps): JSX.Element { - return ( - - {children} - - ) -} - -interface ContextMenuIconButtonProps { - onSelect: () => void - disabled?: boolean - children: React.ReactNode -} - -export function ContextMenuIconButton({ - onSelect, - children, - disabled = false, -}: ContextMenuIconButtonProps): JSX.Element { - return ( - - {children} - - ) -} - -interface ContextMenuCheckboxItemProps { - checked: boolean - disabled?: boolean - onCheckedChange: (isChecked: boolean) => void - children: React.ReactNode -} - -export function ContextMenuCheckboxItem({ - checked, - disabled = false, - onCheckedChange, - children, -}: ContextMenuCheckboxItemProps): JSX.Element { - return ( - - {children} - -
- -
-
-
- ) -} diff --git a/packages/tldraw/src/components/shared/dialog.tsx b/packages/tldraw/src/components/shared/dialog.tsx deleted file mode 100644 index 10d3d1ec2..000000000 --- a/packages/tldraw/src/components/shared/dialog.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import css from '~styles' - -/* -------------------------------------------------- */ -/* Dialog */ -/* -------------------------------------------------- */ - -export const dialogContent = css({ - position: 'fixed', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - minWidth: 240, - maxWidth: 'fit-content', - maxHeight: '85vh', - marginTop: '-5vh', - pointerEvents: 'all', - backgroundColor: '$panel', - border: '1px solid $panel', - padding: '$0', - boxShadow: '$4', - borderRadius: '4px', - font: '$ui', - - '&:focus': { - outline: 'none', - }, -}) - -export const dialogOverlay = css({ - backgroundColor: 'rgba(0, 0, 0, .15)', - position: 'fixed', - top: 0, - right: 0, - bottom: 0, - left: 0, -}) - -export const dialogInputWrapper = css({ - padding: '$4 $2', -}) - -export const dialogTitleRow = css({ - display: 'flex', - padding: '0 0 0 $4', - alignItems: 'center', - justifyContent: 'space-between', - - h3: { - fontSize: '$1', - }, -}) diff --git a/packages/tldraw/src/components/shared/dropdown-menu.tsx b/packages/tldraw/src/components/shared/dropdown-menu.tsx deleted file mode 100644 index 2fdc8faa8..000000000 --- a/packages/tldraw/src/components/shared/dropdown-menu.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import * as React from 'react' -import { CheckIcon, ChevronRightIcon } from '@radix-ui/react-icons' -import { - Root as DMRoot, - TriggerItem as DMTriggerItem, - Separator as DMSeparator, - Item as DMItem, - Arrow as DMArrow, - Content as DMContent, - Trigger as DMTrigger, - ItemIndicator as DMItemIndicator, - CheckboxItem as DMCheckboxItem, -} from '@radix-ui/react-dropdown-menu' - -import { Tooltip } from './tooltip' -import { breakpoints } from './breakpoints' -import { rowButton } from './row-button' -import { iconButton } from './icon-button' -import { iconWrapper } from './icon-wrapper' -import { menuContent } from './menu' - -import css from '~styles' - -/* -------------------------------------------------- */ -/* Dropdown Menu */ -/* -------------------------------------------------- */ - -export interface DropdownMenuRootProps { - isOpen?: boolean - onOpenChange?: (isOpen: boolean) => void - children: React.ReactNode -} - -export function DropdownMenuRoot({ - isOpen, - onOpenChange, - children, -}: DropdownMenuRootProps): JSX.Element { - return ( - - {children} - - ) -} - -export interface DropdownMenuSubMenuProps { - label: string - disabled?: boolean - children: React.ReactNode -} - -export function DropdownMenuSubMenu({ - children, - disabled = false, - label, -}: DropdownMenuSubMenuProps): JSX.Element { - return ( - - - {label} -
- -
-
- - {children} - - -
- ) -} - -export const dropdownMenuDivider = css({ - backgroundColor: '$hover', - height: 1, - marginTop: '$2', - marginRight: '-$2', - marginBottom: '$2', - marginLeft: '-$2', -}) - -export const DropdownMenuDivider = React.forwardRef< - React.ElementRef, - React.ComponentProps ->((props, forwardedRef) => ( - -)) - -export const dropdownMenuArrow = css({ - fill: '$panel', -}) - -export const DropdownMenuArrow = React.forwardRef< - React.ElementRef, - React.ComponentProps ->((props, forwardedRef) => ( - -)) - -export interface DropdownMenuButtonProps { - onSelect?: () => void - disabled?: boolean - children: React.ReactNode -} - -export function DropdownMenuButton({ - onSelect, - children, - disabled = false, -}: DropdownMenuButtonProps): JSX.Element { - return ( - - {children} - - ) -} - -interface DropdownMenuIconButtonProps { - onSelect: () => void - disabled?: boolean - children: React.ReactNode -} - -export function DropdownMenuIconButton({ - onSelect, - children, - disabled = false, -}: DropdownMenuIconButtonProps): JSX.Element { - return ( - - {children} - - ) -} - -interface DropdownMenuIconTriggerButtonProps { - label: string - kbd?: string - disabled?: boolean - children: React.ReactNode -} - -export function DropdownMenuIconTriggerButton({ - label, - kbd, - children, - disabled = false, -}: DropdownMenuIconTriggerButtonProps): JSX.Element { - return ( - - - {children} - - - ) -} - -interface MenuCheckboxItemProps { - checked: boolean - disabled?: boolean - onCheckedChange: (isChecked: boolean) => void - children: React.ReactNode -} - -export function DropdownMenuCheckboxItem({ - checked, - disabled = false, - onCheckedChange, - children, -}: MenuCheckboxItemProps): JSX.Element { - return ( - - {children} - -
- -
-
-
- ) -} diff --git a/packages/tldraw/src/components/shared/floating-container.tsx b/packages/tldraw/src/components/shared/floating-container.tsx deleted file mode 100644 index cdc611708..000000000 --- a/packages/tldraw/src/components/shared/floating-container.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import css from '~styles' - -/* -------------------------------------------------- */ -/* Floating Container */ -/* -------------------------------------------------- */ - -export const floatingContainer = css({ - backgroundColor: '$panel', - willChange: 'transform', - border: '1px solid $panel', - borderRadius: '4px', - boxShadow: '$4', - display: 'flex', - height: 'fit-content', - padding: '$0', - pointerEvents: 'all', - position: 'relative', - userSelect: 'none', - zIndex: 200, - variants: { - direction: { - row: { - flexDirection: 'row', - }, - column: { - flexDirection: 'column', - }, - }, - elevation: { - 0: { - boxShadow: 'none', - }, - 2: { - boxShadow: '$2', - }, - 3: { - boxShadow: '$3', - }, - 4: { - boxShadow: '$4', - }, - }, - }, -}) diff --git a/packages/tldraw/src/components/shared/icon-wrapper.tsx b/packages/tldraw/src/components/shared/icon-wrapper.tsx deleted file mode 100644 index 089aeb1cc..000000000 --- a/packages/tldraw/src/components/shared/icon-wrapper.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import css from '~styles' - -/* -------------------------------------------------- */ -/* Icon Wrapper */ -/* -------------------------------------------------- */ - -export const iconWrapper = css({ - height: '100%', - borderRadius: '4px', - marginRight: '1px', - display: 'grid', - alignItems: 'center', - justifyContent: 'center', - outline: 'none', - border: 'none', - pointerEvents: 'all', - cursor: 'pointer', - color: '$text', - - '& svg': { - height: 22, - width: 22, - strokeWidth: 1, - }, - - '& > *': { - gridRow: 1, - gridColumn: 1, - }, - - variants: { - size: { - small: { - '& svg': { - height: '16px', - width: '16px', - }, - }, - medium: { - '& svg': { - height: '22px', - width: '22px', - }, - }, - }, - }, -}) diff --git a/packages/tldraw/src/components/shared/index.ts b/packages/tldraw/src/components/shared/index.ts deleted file mode 100644 index 64b34a6b9..000000000 --- a/packages/tldraw/src/components/shared/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export * from './breakpoints' -export * from './buttons-row' -export * from './context-menu' -export * from './dialog' -export * from './dropdown-menu' -export * from './floating-container' -export * from './icon-button' -export * from './icon-wrapper' -export * from './kbd' -export * from './menu' -export * from './radio-group' -export * from './row-button' -export * from './tooltip' diff --git a/packages/tldraw/src/components/shared/menu.tsx b/packages/tldraw/src/components/shared/menu.tsx deleted file mode 100644 index 70d9b1621..000000000 --- a/packages/tldraw/src/components/shared/menu.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { breakpoints } from './breakpoints' -import css from '~styles' -import { rowButton } from './row-button' - -/* -------------------------------------------------- */ -/* Menu */ -/* -------------------------------------------------- */ - -export const menuContent = css({ - position: 'relative', - overflow: 'hidden', - userSelect: 'none', - zIndex: 180, - minWidth: 180, - pointerEvents: 'all', - backgroundColor: '$panel', - border: '1px solid $panel', - padding: '$0', - boxShadow: '$4', - borderRadius: '4px', - font: '$ui', -}) - -export const divider = css({ - backgroundColor: '$hover', - height: 1, - marginTop: '$2', - marginRight: '-$2', - marginBottom: '$2', - marginLeft: '-$2', -}) - -export function MenuButton({ - warn, - onSelect, - children, - disabled = false, -}: { - warn?: boolean - onSelect?: () => void - disabled?: boolean - children: React.ReactNode -}): JSX.Element { - return ( - - ) -} - -export const menuTextInput = css({ - backgroundColor: '$panel', - border: 'none', - padding: '$4 $3', - width: '100%', - outline: 'none', - background: '$input', - borderRadius: '4px', - fontFamily: '$ui', - fontSize: '$1', - userSelect: 'all', -}) diff --git a/packages/tldraw/src/components/shared/radio-group.tsx b/packages/tldraw/src/components/shared/radio-group.tsx deleted file mode 100644 index 2f0021675..000000000 --- a/packages/tldraw/src/components/shared/radio-group.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import css from '~styles' -import { Root as RGRoot } from '@radix-ui/react-radio-group' - -/* -------------------------------------------------- */ -/* Radio Group */ -/* -------------------------------------------------- */ - -export const group = css({ - display: 'flex', -}) - -export const Group = React.forwardRef< - React.ElementRef, - React.ComponentProps ->((props, forwardedRef) => ( - -)) diff --git a/packages/tldraw/src/components/shared/row-button.tsx b/packages/tldraw/src/components/shared/row-button.tsx deleted file mode 100644 index 60ae5bc73..000000000 --- a/packages/tldraw/src/components/shared/row-button.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import css from '~styles' - -/* -------------------------------------------------- */ -/* Row Button */ -/* -------------------------------------------------- */ - -export const rowButton = css({ - position: 'relative', - display: 'flex', - width: '100%', - background: 'none', - height: '32px', - border: 'none', - cursor: 'pointer', - color: '$text', - outline: 'none', - alignItems: 'center', - fontFamily: '$ui', - fontWeight: 400, - fontSize: '$1', - justifyContent: 'space-between', - padding: '4px 8px 4px 12px', - borderRadius: 4, - userSelect: 'none', - - '& label': { - fontWeight: '$1', - margin: 0, - padding: 0, - }, - - '& svg': { - position: 'relative', - stroke: '$overlay', - strokeWidth: 1, - zIndex: 1, - }, - - '&[data-disabled]': { - opacity: 0.3, - }, - - '&:disabled': { - opacity: 0.3, - }, - - variants: { - bp: { - mobile: {}, - small: { - '& *[data-shy="true"]': { - opacity: 0, - }, - '&:hover:not(:disabled)': { - backgroundColor: '$hover', - '& *[data-shy="true"]': { - opacity: 1, - }, - }, - }, - }, - size: { - icon: { - padding: '4px ', - width: 'auto', - }, - }, - variant: { - noIcon: { - padding: '4px 12px', - }, - pageButton: { - display: 'grid', - gridTemplateColumns: '24px auto', - width: '100%', - paddingLeft: '$1', - gap: '$3', - justifyContent: 'flex-start', - [`& > *[data-state="checked"]`]: { - gridRow: 1, - gridColumn: 1, - }, - '& > span': { - gridRow: 1, - gridColumn: 2, - width: '100%', - }, - }, - }, - warn: { - true: { - color: '$warn', - }, - }, - isActive: { - true: { - backgroundColor: '$hover', - }, - }, - }, -}) diff --git a/packages/tldraw/src/components/style-panel/align-distribute.tsx b/packages/tldraw/src/components/style-panel/align-distribute.tsx deleted file mode 100644 index cffafeb84..000000000 --- a/packages/tldraw/src/components/style-panel/align-distribute.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import * as React from 'react' -import { - AlignBottomIcon, - AlignCenterHorizontallyIcon, - AlignCenterVerticallyIcon, - AlignLeftIcon, - AlignRightIcon, - AlignTopIcon, - SpaceEvenlyHorizontallyIcon, - SpaceEvenlyVerticallyIcon, - StretchHorizontallyIcon, - StretchVerticallyIcon, -} from '@radix-ui/react-icons' -import { AlignType, DistributeType, StretchType } from '~types' -import { useTLDrawContext } from '~hooks' -import { breakpoints, buttonsRow, iconButton } from '../shared' - -export interface AlignDistributeProps { - hasTwoOrMore: boolean - hasThreeOrMore: boolean -} - -export const AlignDistribute = React.memo( - ({ hasTwoOrMore, hasThreeOrMore }: AlignDistributeProps): JSX.Element => { - const { tlstate } = useTLDrawContext() - - const alignTop = React.useCallback(() => { - tlstate.align(AlignType.Top) - }, [tlstate]) - - const alignCenterVertical = React.useCallback(() => { - tlstate.align(AlignType.CenterVertical) - }, [tlstate]) - - const alignBottom = React.useCallback(() => { - tlstate.align(AlignType.Bottom) - }, [tlstate]) - - const stretchVertically = React.useCallback(() => { - tlstate.stretch(StretchType.Vertical) - }, [tlstate]) - - const distributeVertically = React.useCallback(() => { - tlstate.distribute(DistributeType.Vertical) - }, [tlstate]) - - const alignLeft = React.useCallback(() => { - tlstate.align(AlignType.Left) - }, [tlstate]) - - const alignCenterHorizontal = React.useCallback(() => { - tlstate.align(AlignType.CenterHorizontal) - }, [tlstate]) - - const alignRight = React.useCallback(() => { - tlstate.align(AlignType.Right) - }, [tlstate]) - - const stretchHorizontally = React.useCallback(() => { - tlstate.stretch(StretchType.Horizontal) - }, [tlstate]) - - const distributeHorizontally = React.useCallback(() => { - tlstate.distribute(DistributeType.Horizontal) - }, [tlstate]) - - return ( - <> -
- - - - - -
-
- - - - - -
- - ) - } -) diff --git a/packages/tldraw/src/components/style-panel/index.ts b/packages/tldraw/src/components/style-panel/index.ts deleted file mode 100644 index 01bd999c5..000000000 --- a/packages/tldraw/src/components/style-panel/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './style-panel' diff --git a/packages/tldraw/src/components/style-panel/quick-color-select.tsx b/packages/tldraw/src/components/style-panel/quick-color-select.tsx deleted file mode 100644 index 6bd7c022b..000000000 --- a/packages/tldraw/src/components/style-panel/quick-color-select.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from 'react' -import * as DropdownMenu from '@radix-ui/react-dropdown-menu' -import { BoxIcon, dropdownItem, dropdownContent } from './styled' -import { DropdownMenuIconTriggerButton } from '../shared' -import { strokes } from '~shape-utils' -import { useTheme, useTLDrawContext } from '~hooks' -import type { Data, ColorStyle } from '~types' - -const selectColor = (s: Data) => s.appState.selectedStyle.color - -export const QuickColorSelect = React.memo((): JSX.Element => { - const { theme } = useTheme() - const { tlstate, useSelector } = useTLDrawContext() - - const color = useSelector(selectColor) - - const handleColorChange = React.useCallback( - (color) => tlstate.style({ color: color as ColorStyle }), - [tlstate] - ) - - return ( - - - - - - - {Object.keys(strokes[theme]).map((colorStyle: string) => ( - - - - ))} - - - - ) -}) diff --git a/packages/tldraw/src/components/style-panel/quick-dash-select.tsx b/packages/tldraw/src/components/style-panel/quick-dash-select.tsx deleted file mode 100644 index 0b6df0efd..000000000 --- a/packages/tldraw/src/components/style-panel/quick-dash-select.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from 'react' -import * as DropdownMenu from '@radix-ui/react-dropdown-menu' -import { DropdownMenuIconTriggerButton } from '../shared' -import { - DashDrawIcon, - DashDottedIcon, - DashSolidIcon, - DashDashedIcon, - dropdownContent, - dropdownItem, -} from './styled' -import { useTLDrawContext } from '~hooks' -import { DashStyle, Data } from '~types' - -const dashes = { - [DashStyle.Draw]: , - [DashStyle.Solid]: , - [DashStyle.Dashed]: , - [DashStyle.Dotted]: , -} - -const selectDash = (s: Data) => s.appState.selectedStyle.dash - -export const QuickDashSelect = React.memo((): JSX.Element => { - const { tlstate, useSelector } = useTLDrawContext() - - const dash = useSelector(selectDash) - - const changeDashStyle = React.useCallback( - (dash) => tlstate.style({ dash: dash as DashStyle }), - [tlstate] - ) - - return ( - - {dashes[dash]} - - - {Object.keys(DashStyle).map((dashStyle: string) => ( - - {dashes[dashStyle as DashStyle]} - - ))} - - - - ) -}) diff --git a/packages/tldraw/src/components/style-panel/quick-fill-select.tsx b/packages/tldraw/src/components/style-panel/quick-fill-select.tsx deleted file mode 100644 index 8bd895daa..000000000 --- a/packages/tldraw/src/components/style-panel/quick-fill-select.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import * as React from 'react' -import * as Checkbox from '@radix-ui/react-checkbox' -import { BoxIcon, IsFilledFillIcon } from './styled' -import { breakpoints, Tooltip, iconButton, iconWrapper } from '../shared' -import { useTLDrawContext } from '~hooks' -import type { Data } from '~types' - -const isFilledSelector = (s: Data) => s.appState.selectedStyle.isFilled - -export const QuickFillSelect = React.memo((): JSX.Element => { - const { tlstate, useSelector } = useTLDrawContext() - - const isFilled = useSelector(isFilledSelector) - - const handleIsFilledChange = React.useCallback( - (isFilled: boolean) => tlstate.style({ isFilled }), - [tlstate] - ) - - return ( - - -
- - - - -
-
-
- ) -}) diff --git a/packages/tldraw/src/components/style-panel/quick-size-select.tsx b/packages/tldraw/src/components/style-panel/quick-size-select.tsx deleted file mode 100644 index 1c81a03b4..000000000 --- a/packages/tldraw/src/components/style-panel/quick-size-select.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import * as React from 'react' -import * as DropdownMenu from '@radix-ui/react-dropdown-menu' -import { DropdownMenuIconTriggerButton } from '../shared/dropdown-menu' -import { CircleIcon } from '../icons' -import { dropdownContent, dropdownItem } from './styled' -import { Data, SizeStyle } from '~types' -import { useTLDrawContext } from '~hooks' - -const sizes = { - [SizeStyle.Small]: 6, - [SizeStyle.Medium]: 12, - [SizeStyle.Large]: 22, -} - -const selectSize = (s: Data) => s.appState.selectedStyle.size - -export const QuickSizeSelect = React.memo((): JSX.Element => { - const { tlstate, useSelector } = useTLDrawContext() - - const size = useSelector(selectSize) - - const changeSizeStyle = React.useCallback( - (size: string) => tlstate.style({ size: size as SizeStyle }), - [tlstate] - ) - - return ( - - - - - - - {Object.keys(SizeStyle).map((sizeStyle: string) => ( - - - - ))} - - - - ) -}) diff --git a/packages/tldraw/src/components/style-panel/shapes-functions.tsx b/packages/tldraw/src/components/style-panel/shapes-functions.tsx deleted file mode 100644 index bb4c4aed4..000000000 --- a/packages/tldraw/src/components/style-panel/shapes-functions.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import * as React from 'react' -import { iconButton, buttonsRow, breakpoints } from '../shared' -import { Trash } from '../icons' -import { Tooltip } from '../shared/tooltip' -import { - ArrowDownIcon, - ArrowUpIcon, - AspectRatioIcon, - CopyIcon, - GroupIcon, - LockClosedIcon, - LockOpen1Icon, - PinBottomIcon, - PinTopIcon, - RotateCounterClockwiseIcon, -} from '@radix-ui/react-icons' -import { useTLDrawContext } from '~hooks' -import type { Data } from '~types' - -const isAllLockedSelector = (s: Data) => { - const page = s.document.pages[s.appState.currentPageId] - const { selectedIds } = s.document.pageStates[s.appState.currentPageId] - return selectedIds.every((id) => page.shapes[id].isLocked) -} - -const isAllAspectLockedSelector = (s: Data) => { - const page = s.document.pages[s.appState.currentPageId] - const { selectedIds } = s.document.pageStates[s.appState.currentPageId] - return selectedIds.every((id) => page.shapes[id].isAspectRatioLocked) -} - -const isAllGroupedSelector = (s: Data) => { - const page = s.document.pages[s.appState.currentPageId] - const selectedShapes = s.document.pageStates[s.appState.currentPageId].selectedIds.map( - (id) => page.shapes[id] - ) - - return selectedShapes.every( - (shape) => - shape.children !== undefined || - (shape.parentId === selectedShapes[0].parentId && - selectedShapes[0].parentId !== s.appState.currentPageId) - ) -} - -const hasSelectionSelector = (s: Data) => { - const { selectedIds } = s.document.pageStates[s.appState.currentPageId] - return selectedIds.length > 0 -} - -const hasMultipleSelectionSelector = (s: Data) => { - const { selectedIds } = s.document.pageStates[s.appState.currentPageId] - return selectedIds.length > 1 -} - -export const ShapesFunctions = React.memo(() => { - const { tlstate, useSelector } = useTLDrawContext() - - const isAllLocked = useSelector(isAllLockedSelector) - - const isAllAspectLocked = useSelector(isAllAspectLockedSelector) - - const isAllGrouped = useSelector(isAllGroupedSelector) - - const hasSelection = useSelector(hasSelectionSelector) - - const hasMultipleSelection = useSelector(hasMultipleSelectionSelector) - - const handleRotate = React.useCallback(() => { - tlstate.rotate() - }, [tlstate]) - - const handleDuplicate = React.useCallback(() => { - tlstate.duplicate() - }, [tlstate]) - - const handleToggleLocked = React.useCallback(() => { - tlstate.toggleLocked() - }, [tlstate]) - - const handleToggleAspectRatio = React.useCallback(() => { - tlstate.toggleAspectRatioLocked() - }, [tlstate]) - - const handleGroup = React.useCallback(() => { - tlstate.group() - }, [tlstate]) - - const handleMoveToBack = React.useCallback(() => { - tlstate.moveToBack() - }, [tlstate]) - - const handleMoveBackward = React.useCallback(() => { - tlstate.moveBackward() - }, [tlstate]) - - const handleMoveForward = React.useCallback(() => { - tlstate.moveForward() - }, [tlstate]) - - const handleMoveToFront = React.useCallback(() => { - tlstate.moveToFront() - }, [tlstate]) - - const handleDelete = React.useCallback(() => { - tlstate.delete() - }, [tlstate]) - - return ( - <> -
- - - - - - - - - -
-
- - - - - - - - - -
- - ) -}) diff --git a/packages/tldraw/src/components/style-panel/style-panel.test.tsx b/packages/tldraw/src/components/style-panel/style-panel.test.tsx deleted file mode 100644 index 985c597b6..000000000 --- a/packages/tldraw/src/components/style-panel/style-panel.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import * as React from 'react' -import { renderWithContext } from '~test' -import { StylePanel } from './style-panel' - -describe('style panel', () => { - test('mounts component without crashing', () => { - renderWithContext() - }) -}) diff --git a/packages/tldraw/src/components/style-panel/style-panel.tsx b/packages/tldraw/src/components/style-panel/style-panel.tsx deleted file mode 100644 index 8dff7d72f..000000000 --- a/packages/tldraw/src/components/style-panel/style-panel.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import * as React from 'react' -import { Utils } from '@tldraw/core' -import { DotsHorizontalIcon, Cross2Icon } from '@radix-ui/react-icons' -import { useTLDrawContext } from '~hooks' -import type { Data } from '~types' -import { ShapesFunctions } from './shapes-functions' -import { AlignDistribute } from './align-distribute' -import { QuickColorSelect } from './quick-color-select' -import { QuickSizeSelect } from './quick-size-select' -import { QuickDashSelect } from './quick-dash-select' -import { QuickFillSelect } from './quick-fill-select' -import { Tooltip } from '../shared/tooltip' -import { - Kbd, - iconButton, - buttonsRow, - breakpoints, - rowButton, - floatingContainer, - divider, -} from '../shared' - -const isStyleOpenSelector = (s: Data) => s.appState.isStyleOpen - -export function StylePanel(): JSX.Element { - const { tlstate, useSelector } = useTLDrawContext() - const isOpen = useSelector(isStyleOpenSelector) - - return ( -
-
- - - - - -
- {isOpen && } -
- ) -} - -const selectedShapesCountSelector = (s: Data) => - s.document.pageStates[s.appState.currentPageId].selectedIds.length - -function SelectedShapeContent(): JSX.Element { - const { tlstate, useSelector } = useTLDrawContext() - const selectedShapesCount = useSelector(selectedShapesCountSelector) - - const [showKbds] = React.useState(() => !Utils.isMobileSize()) - - const handleCopy = React.useCallback(() => { - tlstate.copy() - }, [tlstate]) - - const handlePaste = React.useCallback(() => { - tlstate.paste() - }, [tlstate]) - - const handleCopySvg = React.useCallback(() => { - tlstate.copySvg() - }, [tlstate]) - - return ( - <> -
- -
- 1} - hasThreeOrMore={selectedShapesCount > 2} - /> -
- - - - - ) -} diff --git a/packages/tldraw/src/components/style-panel/styled.tsx b/packages/tldraw/src/components/style-panel/styled.tsx deleted file mode 100644 index 9dda9f363..000000000 --- a/packages/tldraw/src/components/style-panel/styled.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import * as React from 'react' -import css from '~styles' - -export const dropdownContent = css({ - display: 'grid', - padding: 4, - gridTemplateColumns: 'repeat(4, 1fr)', - backgroundColor: '$panel', - borderRadius: 4, - border: '1px solid $panel', - boxShadow: '$4', - - variants: { - direction: { - vertical: { - gridTemplateColumns: '1fr', - }, - }, - }, -}) - -export const dropdownItem = css({ - height: '32px', - width: '32px', - backgroundColor: '$panel', - borderRadius: '4px', - padding: '0', - margin: '0', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - outline: 'none', - border: 'none', - pointerEvents: 'all', - cursor: 'pointer', - - '&:focus': { - backgroundColor: '$hover', - }, - - '&:hover:not(:disabled)': { - backgroundColor: '$hover', - }, - - '&:disabled': { - opacity: '0.5', - }, - - variants: { - isActive: { - true: { - '& svg': { - fill: '$text', - stroke: '$text', - }, - }, - false: { - '& svg': { - fill: '$inactive', - stroke: '$inactive', - }, - }, - }, - }, -}) - -export function BoxIcon({ - fill = 'none', - stroke = 'currentColor', -}: { - fill?: string - stroke?: string -}): JSX.Element { - return ( - - - - ) -} - -export function DashSolidIcon(): JSX.Element { - return ( - - - - ) -} - -export function DashDashedIcon(): JSX.Element { - return ( - - - - ) -} - -const dottedDasharray = `${50.26548 * 0.025} ${50.26548 * 0.1}` - -export function DashDottedIcon(): JSX.Element { - return ( - - - - ) -} - -export function DashDrawIcon(): JSX.Element { - return ( - - - - ) -} - -export function IsFilledFillIcon(): JSX.Element { - return ( - - - - ) -} diff --git a/packages/tldraw/src/components/tldraw/index.ts b/packages/tldraw/src/components/tldraw/index.ts deleted file mode 100644 index b7df7d774..000000000 --- a/packages/tldraw/src/components/tldraw/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tldraw' diff --git a/packages/tldraw/src/components/tools-panel/back-to-content/index.ts b/packages/tldraw/src/components/tools-panel/back-to-content/index.ts deleted file mode 100644 index bd1cb3db6..000000000 --- a/packages/tldraw/src/components/tools-panel/back-to-content/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './back-to-content' diff --git a/packages/tldraw/src/components/tools-panel/index.ts b/packages/tldraw/src/components/tools-panel/index.ts deleted file mode 100644 index a2728adb6..000000000 --- a/packages/tldraw/src/components/tools-panel/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tools-panel' diff --git a/packages/tldraw/src/components/tools-panel/primary-tools/index.ts b/packages/tldraw/src/components/tools-panel/primary-tools/index.ts deleted file mode 100644 index 3aea0d2b0..000000000 --- a/packages/tldraw/src/components/tools-panel/primary-tools/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './primary-tools' diff --git a/packages/tldraw/src/components/tools-panel/status-bar/index.ts b/packages/tldraw/src/components/tools-panel/status-bar/index.ts deleted file mode 100644 index f9d6aa7b1..000000000 --- a/packages/tldraw/src/components/tools-panel/status-bar/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './status-bar' diff --git a/packages/tldraw/src/components/tools-panel/styled.tsx b/packages/tldraw/src/components/tools-panel/styled.tsx deleted file mode 100644 index 4910aebf3..000000000 --- a/packages/tldraw/src/components/tools-panel/styled.tsx +++ /dev/null @@ -1,254 +0,0 @@ -import * as React from 'react' -import { floatingContainer } from '../shared' -import { Tooltip } from '../shared/tooltip' -import css from '~styles' - -export const toolButton = css({ - position: 'relative', - height: '32px', - width: '32px', - color: '$text', - 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 = css(toolButton, { - variants: { - bp: { - mobile: { - height: 44, - width: 36, - '& svg:nth-of-type(1)': { - height: '20px', - width: '20px', - }, - }, - small: { - width: 44, - '&:hover:not(:disabled)': { - backgroundColor: '$hover', - }, - }, - medium: {}, - large: {}, - }, - isActive: { - true: { - color: '$selected', - }, - }, - }, -}) - -export const secondaryToolButton = css(toolButton, { - variants: { - bp: { - mobile: { - height: 44, - width: 36, - '& svg:nth-of-type(1)': { - height: '18px', - width: '18px', - }, - }, - small: { - width: 44, - '&:hover:not(:disabled)': { - backgroundColor: '$hover', - }, - }, - medium: {}, - large: {}, - }, - isActive: { - true: { - color: '$selected', - }, - }, - }, -}) - -export const tertiaryToolButton = css(toolButton, { - variants: { - bp: { - mobile: { - height: 32, - width: 36, - '& 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 - kbd: string - onClick: () => void - onDoubleClick?: () => void - isActive: boolean - children: React.ReactNode -} - -export function PrimaryButton({ - label, - kbd, - onClick, - onDoubleClick, - isActive, - children, -}: PrimaryToolButtonProps): JSX.Element { - return ( - - - - ) -} - -interface SecondaryToolButtonProps { - label: string - kbd: string - onClick: () => void - onDoubleClick?: () => void - isActive: boolean - children: React.ReactNode -} - -export function SecondaryButton({ - label, - kbd, - onClick, - onDoubleClick, - isActive, - children, -}: SecondaryToolButtonProps): JSX.Element { - return ( - - - - ) -} - -interface TertiaryToolProps { - label: string - kbd: string - onClick: () => void - onDoubleClick?: () => void - children: React.ReactNode -} - -export function TertiaryButton({ - label, - kbd, - onClick, - onDoubleClick, - children, -}: TertiaryToolProps): JSX.Element { - return ( - - - - ) -} - -export const tertiaryButtonsContainer = css(floatingContainer, { - boxShadow: '$3', - variants: { - bp: { - mobile: { - alignItems: 'center', - flexDirection: 'column', - }, - small: { - alignItems: 'center', - flexDirection: 'row', - }, - }, - }, -}) diff --git a/packages/tldraw/src/components/tools-panel/tools-panel.tsx b/packages/tldraw/src/components/tools-panel/tools-panel.tsx deleted file mode 100644 index a4ac0ab84..000000000 --- a/packages/tldraw/src/components/tools-panel/tools-panel.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import * as React from 'react' -import { CursorArrowIcon, LockClosedIcon, LockOpen1Icon } from '@radix-ui/react-icons' -import css from '~styles' -import type { Data } from '~types' -import { useTLDrawContext } from '~hooks' -import { floatingContainer } from '~components/shared' -import { StatusBar } from '~components/tools-panel/status-bar' -import { SecondaryButton } from '~components/tools-panel/styled' -import { UndoRedo } from '~components/tools-panel/undo-redo' -import { Zoom } from '~components/tools-panel/zoom' -import { BackToContent } from '~components/tools-panel/back-to-content' -import { PrimaryTools } from '~components/tools-panel/primary-tools' - -const activeToolSelector = (s: Data) => s.appState.activeTool -const isToolLockedSelector = (s: Data) => s.appState.isToolLocked -const isDebugModeSelector = (s: Data) => s.settings.isDebugMode - -export const ToolsPanel = React.memo((): JSX.Element => { - const { tlstate, useSelector } = useTLDrawContext() - - const activeTool = useSelector(activeToolSelector) - - const isToolLocked = useSelector(isToolLockedSelector) - - const isDebugMode = useSelector(isDebugModeSelector) - - const selectSelectTool = React.useCallback(() => { - tlstate.selectTool('select') - }, [tlstate]) - - return ( -
-
- -
- - - -
-
-
- - -
-
-
- - {isToolLocked ? : } - -
- -
- {isDebugMode && ( -
- -
- )} -
- ) -}) - -const toolsPanelContainer = css({ - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - width: '100%', - minWidth: 0, - maxWidth: '100%', - display: 'grid', - gridTemplateColumns: '1fr auto 1fr', - gridTemplateRows: 'auto auto', - padding: '0', - alignItems: 'flex-end', - zIndex: 200, - gridGap: '$4', - gridRowGap: '$4', - pointerEvents: 'none', - '& > div > *': { - pointerEvents: 'all', - }, -}) - -const centerWrap = css({ - gridRow: 1, - gridColumn: 2, - display: 'flex', - width: 'fit-content', - alignItems: 'center', - justifyContent: 'center', - flexDirection: 'column', - gap: 12, -}) - -const leftWrap = css({ - gridRow: 1, - gridColumn: 1, - display: 'flex', - paddingLeft: '$3', - variants: { - size: { - mobile: { - flexDirection: 'column', - justifyContent: 'flex-end', - alignItems: 'flex-start', - '& > *:nth-of-type(1)': { - marginBottom: '8px', - }, - }, - small: { - flexDirection: 'row', - alignItems: 'flex-end', - justifyContent: 'space-between', - '& > *:nth-of-type(1)': { - marginBottom: '0px', - }, - }, - }, - }, -}) - -const rightWrap = css({ - gridRow: 1, - gridColumn: 3, - display: 'flex', - paddingRight: '$3', - opacity: 1, - variants: { - size: { - micro: { - opacity: 0, - }, - mobile: { - flexDirection: 'column-reverse', - justifyContent: 'flex-end', - alignItems: 'flex-end', - '& > *:nth-of-type(2)': { - marginBottom: '8px', - }, - opacity: 1, - }, - small: { - flexDirection: 'row', - alignItems: 'flex-end', - justifyContent: 'space-between', - '& > *:nth-of-type(2)': { - marginBottom: '0px', - }, - opacity: 1, - }, - }, - }, -}) - -const statusWrap = css({ - gridRow: 2, - gridColumn: '1 / span 3', -}) diff --git a/packages/tldraw/src/components/tools-panel/undo-redo/index.ts b/packages/tldraw/src/components/tools-panel/undo-redo/index.ts deleted file mode 100644 index 5e0211785..000000000 --- a/packages/tldraw/src/components/tools-panel/undo-redo/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './undo-redo' diff --git a/packages/tldraw/src/components/tools-panel/undo-redo/undo-redo.tsx b/packages/tldraw/src/components/tools-panel/undo-redo/undo-redo.tsx deleted file mode 100644 index 5564c41b0..000000000 --- a/packages/tldraw/src/components/tools-panel/undo-redo/undo-redo.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import * as React from 'react' -import { useTLDrawContext } from '~hooks' -import { TertiaryButton, tertiaryButtonsContainer } from '~components/tools-panel/styled' -import { Undo, Redo, Trash } from '~components/icons' - -export const UndoRedo = React.memo((): JSX.Element => { - const { tlstate } = useTLDrawContext() - - const handleDelete = React.useCallback(() => { - tlstate.delete() - }, [tlstate]) - - const handleClear = React.useCallback(() => { - tlstate.clear() - }, [tlstate]) - - return ( -
- - - - - - - - - -
- ) -}) diff --git a/packages/tldraw/src/components/tools-panel/zoom/index.ts b/packages/tldraw/src/components/tools-panel/zoom/index.ts deleted file mode 100644 index 7d9ae51b7..000000000 --- a/packages/tldraw/src/components/tools-panel/zoom/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './zoom' diff --git a/packages/tldraw/src/components/tools-panel/zoom/zoom.tsx b/packages/tldraw/src/components/tools-panel/zoom/zoom.tsx deleted file mode 100644 index 656edf3fb..000000000 --- a/packages/tldraw/src/components/tools-panel/zoom/zoom.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as React from 'react' -import { ZoomInIcon, ZoomOutIcon } from '@radix-ui/react-icons' -import { TertiaryButton, tertiaryButtonsContainer } from '~components/tools-panel/styled' -import { useTLDrawContext } from '~hooks' -import type { Data } from '~types' - -export const Zoom = React.memo((): JSX.Element => { - const { tlstate } = useTLDrawContext() - - return ( -
- - - - - - - -
- ) -}) - -const zoomSelector = (s: Data) => s.document.pageStates[s.appState.currentPageId].camera.zoom - -function ZoomCounter() { - const { tlstate, useSelector } = useTLDrawContext() - const zoom = useSelector(zoomSelector) - - return ( - - {Math.round(zoom * 100)}% - - ) -} diff --git a/packages/tldraw/src/hooks/useKeyboardShortcuts.tsx b/packages/tldraw/src/hooks/useKeyboardShortcuts.tsx index 481ee54c5..59d354070 100644 --- a/packages/tldraw/src/hooks/useKeyboardShortcuts.tsx +++ b/packages/tldraw/src/hooks/useKeyboardShortcuts.tsx @@ -261,7 +261,9 @@ export function useKeyboardShortcuts(ref: React.RefObject) { useHotkeys( 'escape', () => { - if (canHandleEvent()) tlstate.cancel() + if (canHandleEvent()) { + tlstate.cancel() + } }, undefined, [tlstate] diff --git a/packages/tldraw/src/index.ts b/packages/tldraw/src/index.ts index 5b8376434..2e45b1fdf 100644 --- a/packages/tldraw/src/index.ts +++ b/packages/tldraw/src/index.ts @@ -1,4 +1,4 @@ -export * from './components/tldraw' +export * from './TLDraw' export * from './types' export * from './shape-utils' export { TLDrawState } from './state' diff --git a/packages/tldraw/src/shape-utils/group/group.tsx b/packages/tldraw/src/shape-utils/group/group.tsx index 6dbefd41a..c3d109ea6 100644 --- a/packages/tldraw/src/shape-utils/group/group.tsx +++ b/packages/tldraw/src/shape-utils/group/group.tsx @@ -5,7 +5,7 @@ import { TLDrawShapeType, GroupShape, ColorStyle, TLDrawMeta } from '~types' import { getBoundsRectangle } from '../shared' import { BINDING_DISTANCE } from '~constants' import { TLDrawShapeUtil } from '../TLDrawShapeUtil' -import css from '~styles' +import styled from '~styles' type T = GroupShape type E = SVGSVGElement @@ -71,15 +71,14 @@ export class GroupUtil extends TLDrawShapeUtil { fill="transparent" pointerEvents="all" /> - {paths} - + ) } @@ -104,9 +103,9 @@ export class GroupUtil extends TLDrawShapeUtil { }) return ( - + {paths} - + ) }) @@ -119,7 +118,7 @@ export class GroupUtil extends TLDrawShapeUtil { } } -const scaledLines = css({ +const ScaledLines = styled('g', { strokeWidth: 'calc(1.5px * var(--tl-scale))', strokeDasharray: `calc(1px * var(--tl-scale)), calc(3px * var(--tl-scale))`, }) diff --git a/packages/tldraw/src/shape-utils/sticky/sticky.tsx b/packages/tldraw/src/shape-utils/sticky/sticky.tsx index e68b2e084..3eb3f9cdf 100644 --- a/packages/tldraw/src/shape-utils/sticky/sticky.tsx +++ b/packages/tldraw/src/shape-utils/sticky/sticky.tsx @@ -6,7 +6,7 @@ import { StickyShape, TLDrawMeta, TLDrawShapeType, TLDrawTransformInfo } from '~ import { getBoundsRectangle, TextAreaUtils } from '../shared' import { TLDrawShapeUtil } from '../TLDrawShapeUtil' import { getStickyFontStyle, getStickyShapeStyle } from '../shape-styles' -import css from '~styles' +import styled from '~styles' import Vec from '@tldraw/vec' type T = StickyShape @@ -163,18 +163,17 @@ export class StickyUtil extends TLDrawShapeUtil { return ( -
-
+ {shape.text}​ -
+ {isEditing && ( -