feat: add help panel (#816)
* feat: add help panel * feat: added all the shortcuts and responsive * improve help panel * add modal for the shortcut * add grid * fix language menu * add responsive grid * Styling keyboard shortcuts / panel * fix links ts issue * Improve styling * Fix translation bug Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
This commit is contained in:
parent
240202bb81
commit
77337b1281
18 changed files with 516 additions and 81 deletions
|
@ -43,8 +43,10 @@
|
|||
"dependencies": {
|
||||
"@radix-ui/react-alert-dialog": "^0.1.7",
|
||||
"@radix-ui/react-context-menu": "^0.1.6",
|
||||
"@radix-ui/react-dialog": "^0.1.7",
|
||||
"@radix-ui/react-dropdown-menu": "^0.1.6",
|
||||
"@radix-ui/react-icons": "^1.1.1",
|
||||
"@radix-ui/react-popover": "^0.1.6",
|
||||
"@radix-ui/react-tooltip": "^0.1.7",
|
||||
"@stitches/react": "^1.2.8",
|
||||
"@tldraw/core": "^1.14.1",
|
||||
|
|
|
@ -9,6 +9,7 @@ export interface DMContentProps {
|
|||
align?: 'start' | 'center' | 'end'
|
||||
sideOffset?: number
|
||||
children: React.ReactNode
|
||||
overflow?: boolean
|
||||
id?: string
|
||||
side?: 'top' | 'left' | 'right' | 'bottom' | undefined
|
||||
}
|
||||
|
@ -19,6 +20,7 @@ export function DMContent({
|
|||
align,
|
||||
variant,
|
||||
id,
|
||||
overflow = false,
|
||||
side = 'bottom',
|
||||
}: DMContentProps) {
|
||||
return (
|
||||
|
@ -31,7 +33,9 @@ export function DMContent({
|
|||
id={id}
|
||||
side={side}
|
||||
>
|
||||
<StyledContent variant={variant}>{children}</StyledContent>
|
||||
<StyledContent variant={variant} overflow={overflow}>
|
||||
{children}
|
||||
</StyledContent>
|
||||
</DropdownMenu.Content>
|
||||
)
|
||||
}
|
||||
|
@ -40,11 +44,14 @@ export const StyledContent = styled(MenuContent, {
|
|||
width: 'fit-content',
|
||||
height: 'fit-content',
|
||||
minWidth: 0,
|
||||
maxHeight: '75vh',
|
||||
maxHeight: '100vh',
|
||||
overflowY: 'auto',
|
||||
'& *': {
|
||||
boxSizing: 'border-box',
|
||||
overflowX: 'hidden',
|
||||
'&::webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
'-ms-overflow-style': 'none' /* for Internet Explorer, Edge */,
|
||||
scrollbarWidth: 'none',
|
||||
variants: {
|
||||
variant: {
|
||||
horizontal: {
|
||||
|
@ -54,5 +61,10 @@ export const StyledContent = styled(MenuContent, {
|
|||
minWidth: 128,
|
||||
},
|
||||
},
|
||||
overflow: {
|
||||
true: {
|
||||
maxHeight: '60vh',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -8,10 +8,18 @@ export interface DMSubMenuProps {
|
|||
size?: 'small'
|
||||
disabled?: boolean
|
||||
children: React.ReactNode
|
||||
overflow?: boolean
|
||||
id?: string
|
||||
}
|
||||
|
||||
export function DMSubMenu({ children, size, disabled = false, label, id }: DMSubMenuProps) {
|
||||
export function DMSubMenu({
|
||||
children,
|
||||
size,
|
||||
overflow = false,
|
||||
disabled = false,
|
||||
label,
|
||||
id,
|
||||
}: DMSubMenuProps) {
|
||||
return (
|
||||
<span id={id}>
|
||||
<Root dir="ltr">
|
||||
|
@ -21,7 +29,7 @@ export function DMSubMenu({ children, size, disabled = false, label, id }: DMSub
|
|||
</RowButton>
|
||||
</TriggerItem>
|
||||
<Content dir="ltr" asChild sideOffset={2} alignOffset={-2} align="start">
|
||||
<MenuContent size={size} overflow>
|
||||
<MenuContent size={size} overflow={overflow}>
|
||||
{children}
|
||||
<Arrow offset={13} />
|
||||
</MenuContent>
|
||||
|
|
|
@ -14,6 +14,14 @@ export const MenuContent = styled('div', {
|
|||
padding: '$2 $2',
|
||||
borderRadius: '$3',
|
||||
font: '$ui',
|
||||
maxHeight: '100vh',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
'&::webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
'-ms-overflow-style': 'none' /* for Internet Explorer, Edge */,
|
||||
scrollbarWidth: 'none',
|
||||
variants: {
|
||||
size: {
|
||||
small: {
|
||||
|
@ -23,14 +31,7 @@ export const MenuContent = styled('div', {
|
|||
overflow: {
|
||||
true: {
|
||||
maxHeight: '60vh',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
},
|
||||
},
|
||||
},
|
||||
'-ms-overflow-style': 'none' /* for Internet Explorer, Edge */,
|
||||
scrollbarWidth: 'none',
|
||||
'&::webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import * as React from 'react'
|
||||
|
||||
export function QuestionMarkIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="8"
|
||||
height="16"
|
||||
viewBox="0 0 4 8"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M1.15948 5.85192C1.05082 5.66856 0.972717 5.50556 0.925178 5.36295C0.884431 5.21354 0.864057 5.06074 0.864057 4.90454C0.864057 4.69401 0.921782 4.50046 1.03723 4.32389C1.15269 4.14731 1.2953 3.98093 1.46508 3.82473C1.63486 3.66174 1.80125 3.50214 1.96424 3.34594C2.13402 3.18295 2.27664 3.01317 2.39209 2.8366C2.50754 2.65324 2.56527 2.45629 2.56527 2.24576C2.56527 1.98769 2.48038 1.78056 2.31059 1.62436C2.1476 1.46137 1.93028 1.37988 1.65863 1.37988C1.48206 1.37988 1.32586 1.41044 1.19004 1.47156C1.05421 1.52589 0.911596 1.55305 0.762188 1.55305C0.572033 1.55305 0.429416 1.50551 0.334339 1.41044C0.239261 1.31536 0.191722 1.21009 0.191722 1.09464C0.191722 0.958818 0.252844 0.843366 0.375086 0.748289C0.50412 0.64642 0.701067 0.595485 0.965926 0.595485C1.30549 0.595485 1.6111 0.64642 1.88275 0.748289C2.1544 0.850157 2.38869 0.996169 2.58564 1.18632C2.78938 1.36969 2.94218 1.5904 3.04405 1.84847C3.15271 2.10654 3.20704 2.39517 3.20704 2.71436C3.20704 2.98601 3.14932 3.22031 3.03386 3.41725C2.91841 3.60741 2.7758 3.77719 2.60601 3.9266C2.44302 4.07601 2.27664 4.21862 2.10686 4.35445C1.93708 4.49027 1.79446 4.63628 1.67901 4.79248C1.56356 4.94189 1.50583 5.12186 1.50583 5.33239C1.50583 5.42067 1.50923 5.50896 1.51602 5.59725C1.5296 5.68553 1.54658 5.77042 1.56695 5.85192H1.15948ZM1.3734 7.8078C1.25116 7.8078 1.1391 7.78064 1.03723 7.72631C0.942156 7.67198 0.867452 7.59727 0.813122 7.50219C0.758792 7.40712 0.731627 7.30185 0.731627 7.1864C0.731627 7.06416 0.758792 6.95889 0.813122 6.87061C0.867452 6.77553 0.942156 6.70083 1.03723 6.6465C1.1391 6.59217 1.25116 6.565 1.3734 6.565C1.55677 6.565 1.70957 6.62612 1.83181 6.74837C1.95405 6.86382 2.01518 7.00983 2.01518 7.1864C2.01518 7.36297 1.95405 7.51238 1.83181 7.63462C1.70957 7.75008 1.55677 7.8078 1.3734 7.8078Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
|
@ -15,3 +15,4 @@ export * from './EraserIcon'
|
|||
export * from './MultiplayerIcon'
|
||||
export * from './DiscordIcon'
|
||||
export * from './LineIcon'
|
||||
export * from './QuestionMarkIcon'
|
||||
|
|
|
@ -51,9 +51,6 @@ const BackToContentContainer = styled(MenuContent, {
|
|||
pointerEvents: 'all',
|
||||
width: 'fit-content',
|
||||
minWidth: 0,
|
||||
// gridRow: 1,
|
||||
// flexGrow: 2,
|
||||
// display: 'block',
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
})
|
||||
|
|
184
packages/tldraw/src/components/ToolsPanel/HelpPanel.tsx
Normal file
184
packages/tldraw/src/components/ToolsPanel/HelpPanel.tsx
Normal file
|
@ -0,0 +1,184 @@
|
|||
import * as React from 'react'
|
||||
import * as Popover from '@radix-ui/react-popover'
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
import { styled } from '~styles'
|
||||
import { useTldrawApp } from '~hooks'
|
||||
import { TDSnapshot } from '~types'
|
||||
import { breakpoints } from '~components/breakpoints'
|
||||
import { GitHubLogoIcon, QuestionMarkIcon, TwitterLogoIcon } from '@radix-ui/react-icons'
|
||||
import { RowButton } from '~components/Primitives/RowButton'
|
||||
import { MenuContent } from '~components/Primitives/MenuContent'
|
||||
import { DMContent, DMDivider } from '~components/Primitives/DropdownMenu'
|
||||
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
||||
import { DiscordIcon } from '~components/Primitives/icons'
|
||||
import { LanguageMenu } from '~components/TopPanel/LanguageMenu/LanguageMenu'
|
||||
import { KeyboardShortcutDialog } from './keyboardShortcutDialog'
|
||||
|
||||
const isDebugModeSelector = (s: TDSnapshot) => s.settings.isDebugMode
|
||||
const dockPositionState = (s: TDSnapshot) => s.settings.dockPosition
|
||||
|
||||
export function HelpPanel() {
|
||||
const app = useTldrawApp()
|
||||
const isDebugMode = app.useStore(isDebugModeSelector)
|
||||
const side = app.useStore(dockPositionState)
|
||||
|
||||
const [isKeyboardShortcutsOpen, setIsKeyboardShortcutsOpen] = React.useState(false)
|
||||
|
||||
return (
|
||||
<Popover.Root>
|
||||
<PopoverAnchor dir="ltr">
|
||||
<Popover.Trigger asChild dir="ltr">
|
||||
<HelpButton side={side} debug={isDebugMode} bp={breakpoints}>
|
||||
<QuestionMarkIcon />
|
||||
</HelpButton>
|
||||
</Popover.Trigger>
|
||||
</PopoverAnchor>
|
||||
<Popover.Content dir="ltr">
|
||||
<StyledContent style={{ visibility: isKeyboardShortcutsOpen ? 'hidden' : 'visible' }}>
|
||||
<LanguageMenuDropdown />
|
||||
<KeyboardShortcutDialog onOpenChange={setIsKeyboardShortcutsOpen} />
|
||||
<DMDivider />
|
||||
<Links />
|
||||
</StyledContent>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
)
|
||||
}
|
||||
|
||||
const LanguageMenuDropdown = () => {
|
||||
return (
|
||||
<DropdownMenu.Root dir="ltr">
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<RowButton variant="wide" hasArrow>
|
||||
<FormattedMessage id="language" />
|
||||
</RowButton>
|
||||
</DropdownMenu.Trigger>
|
||||
<LanguageMenu />
|
||||
</DropdownMenu.Root>
|
||||
)
|
||||
}
|
||||
|
||||
const linksData = [
|
||||
{ id: 'github', title: 'Github', icon: GitHubLogoIcon, url: 'https://github.com/tldraw/tldraw' },
|
||||
{ id: 'twitter', title: 'Twitter', icon: TwitterLogoIcon, url: 'https://twitter.com/tldraw' },
|
||||
{ id: 'discord', title: 'Discord', icon: DiscordIcon, url: 'https://discord.gg/SBBEVCA4PG' },
|
||||
]
|
||||
|
||||
const Links = () => {
|
||||
return (
|
||||
<>
|
||||
{linksData.map((item) => (
|
||||
<a key={item.id} href={item.url} target="_blank" rel="nofollow">
|
||||
<RowButton id={`TD-Link-${item.id}`} variant="wide">
|
||||
{item.title}
|
||||
<SmallIcon>
|
||||
<item.icon />
|
||||
</SmallIcon>
|
||||
</RowButton>
|
||||
</a>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const HelpButton = styled('button', {
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: '100%',
|
||||
position: 'fixed',
|
||||
right: 10,
|
||||
display: 'grid',
|
||||
placeItems: 'center',
|
||||
border: 'none',
|
||||
backgroundColor: 'white',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '$panel',
|
||||
bottom: 10,
|
||||
variants: {
|
||||
debug: {
|
||||
true: {},
|
||||
false: {},
|
||||
},
|
||||
bp: {
|
||||
mobile: {},
|
||||
small: {},
|
||||
medium: {},
|
||||
large: {},
|
||||
},
|
||||
side: {
|
||||
top: {},
|
||||
left: {},
|
||||
right: {},
|
||||
bottom: {},
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
bp: 'mobile',
|
||||
side: 'bottom',
|
||||
debug: false,
|
||||
css: {
|
||||
bottom: 70,
|
||||
},
|
||||
},
|
||||
{
|
||||
bp: 'mobile',
|
||||
debug: true,
|
||||
css: {
|
||||
bottom: 50, // 40 + 10
|
||||
},
|
||||
},
|
||||
{
|
||||
bp: 'mobile',
|
||||
side: 'bottom',
|
||||
debug: true,
|
||||
css: {
|
||||
bottom: 110,
|
||||
},
|
||||
},
|
||||
{
|
||||
bp: 'small',
|
||||
side: 'bottom',
|
||||
debug: true,
|
||||
css: {
|
||||
bottom: 50,
|
||||
},
|
||||
},
|
||||
{
|
||||
bp: 'small',
|
||||
debug: false,
|
||||
css: {
|
||||
bottom: 10,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const StyledContent = styled(MenuContent, {
|
||||
width: 'fit-content',
|
||||
height: 'fit-content',
|
||||
minWidth: 200,
|
||||
maxHeight: 380,
|
||||
overflowY: 'auto',
|
||||
'& *': {
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
variants: {
|
||||
variant: {
|
||||
horizontal: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
menu: {
|
||||
minWidth: 128,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const PopoverAnchor = styled(Popover.Anchor, {
|
||||
position: 'absolute',
|
||||
right: 10,
|
||||
zIndex: 999,
|
||||
bottom: 50,
|
||||
})
|
|
@ -97,7 +97,7 @@ export const ShapesMenu = React.memo(function ShapesMenu({
|
|||
{shapeShapes.map((shape, i) => (
|
||||
<Tooltip
|
||||
key={shape}
|
||||
label={intl.formatMessage({ id: shape[0].toUpperCase() + shape.slice(1) })}
|
||||
label={intl.formatMessage({ id: shape })}
|
||||
kbd={(4 + i).toString()}
|
||||
id={`TD-PrimaryTools-Shapes-${shape}`}
|
||||
>
|
||||
|
|
|
@ -8,6 +8,7 @@ import { PrimaryTools } from './PrimaryTools'
|
|||
import { ActionButton } from './ActionButton'
|
||||
import { DeleteButton } from './DeleteButton'
|
||||
import { breakpoints } from '~components/breakpoints'
|
||||
import { HelpPanel } from './HelpPanel'
|
||||
|
||||
const isDebugModeSelector = (s: TDSnapshot) => s.settings.isDebugMode
|
||||
const dockPositionState = (s: TDSnapshot) => s.settings.dockPosition
|
||||
|
@ -35,6 +36,7 @@ export const ToolsPanel = React.memo(function ToolsPanel({ onBlur }: ToolsPanelP
|
|||
</StyledPrimaryTools>
|
||||
</StyledCenterWrap>
|
||||
</StyledToolsPanelContainer>
|
||||
<HelpPanel />
|
||||
{isDebugMode && (
|
||||
<StyledStatusWrap>
|
||||
<StatusBar />
|
||||
|
@ -84,7 +86,7 @@ const StyledToolsPanelContainer = styled('div', {
|
|||
width: '100%',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
bottom: 4,
|
||||
},
|
||||
left: { width: 64, height: '100%', left: 0 },
|
||||
},
|
||||
|
@ -101,7 +103,7 @@ const StyledToolsPanelContainer = styled('div', {
|
|||
side: 'bottom',
|
||||
debug: true,
|
||||
css: {
|
||||
bottom: '40px',
|
||||
bottom: 44,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
import * as React from 'react'
|
||||
import * as Dialog from '@radix-ui/react-dialog'
|
||||
import { Cross2Icon } from '@radix-ui/react-icons'
|
||||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
import { IconButton } from '~components/Primitives/IconButton'
|
||||
import { RowButton } from '~components/Primitives/RowButton'
|
||||
import { styled } from '~styles'
|
||||
import { breakpoints } from '~components/breakpoints'
|
||||
import { Kbd } from '~components/Primitives/Kbd'
|
||||
|
||||
export function KeyboardShortcutDialog({
|
||||
onOpenChange,
|
||||
}: {
|
||||
onOpenChange?: (open: boolean) => void
|
||||
}) {
|
||||
const intl = useIntl()
|
||||
|
||||
const shortcuts = {
|
||||
Tools: [
|
||||
{ label: intl.formatMessage({ id: 'select' }), kbd: '1' },
|
||||
{ label: intl.formatMessage({ id: 'draw' }), kbd: '2' },
|
||||
{ label: intl.formatMessage({ id: 'eraser' }), kbd: '3' },
|
||||
{ label: intl.formatMessage({ id: 'rectangle' }), kbd: '4' },
|
||||
{ label: intl.formatMessage({ id: 'ellipse' }), kbd: '5' },
|
||||
{ label: intl.formatMessage({ id: 'triangle' }), kbd: '6' },
|
||||
{ label: intl.formatMessage({ id: 'line' }), kbd: '7' },
|
||||
{ label: intl.formatMessage({ id: 'arrow' }), kbd: '8' },
|
||||
{ label: intl.formatMessage({ id: 'text' }), kbd: '9' },
|
||||
{ label: intl.formatMessage({ id: 'sticky' }), kbd: '0' },
|
||||
],
|
||||
View: [
|
||||
{ label: intl.formatMessage({ id: 'zoom.in' }), kbd: '#+' },
|
||||
{ label: intl.formatMessage({ id: 'zoom.out' }), kbd: '#-' },
|
||||
{ label: `${intl.formatMessage({ id: 'zoom.to' })} 100%`, kbd: '⇧+0' },
|
||||
{ label: intl.formatMessage({ id: 'zoom.to.fit' }), kbd: '⇧+1' },
|
||||
{ label: intl.formatMessage({ id: 'zoom.to.selection' }), kbd: '⇧+2' },
|
||||
{ label: intl.formatMessage({ id: 'preferences.dark.mode' }), kbd: '#⇧D' },
|
||||
{ label: intl.formatMessage({ id: 'preferences.focus.mode' }), kbd: '#.' },
|
||||
{ label: intl.formatMessage({ id: 'preferences.show.grid' }), kbd: '#⇧G' },
|
||||
],
|
||||
Transform: [
|
||||
{ label: intl.formatMessage({ id: 'flip.horizontal' }), kbd: '⇧H' },
|
||||
{ label: intl.formatMessage({ id: 'flip.vertical' }), kbd: '⇧V' },
|
||||
{
|
||||
label: `${intl.formatMessage({ id: 'lock' })} / ${intl.formatMessage({ id: 'unlock' })}`,
|
||||
kbd: '#⇧L',
|
||||
},
|
||||
{
|
||||
label: `${intl.formatMessage({ id: 'move' })} ${intl.formatMessage({ id: 'to.front' })}`,
|
||||
kbd: '⇧]',
|
||||
},
|
||||
{
|
||||
label: `${intl.formatMessage({ id: 'move' })} ${intl.formatMessage({ id: 'forward' })}`,
|
||||
kbd: ']',
|
||||
},
|
||||
{
|
||||
label: `${intl.formatMessage({ id: 'move' })} ${intl.formatMessage({ id: 'backward' })}`,
|
||||
kbd: '[',
|
||||
},
|
||||
{
|
||||
label: `${intl.formatMessage({ id: 'move' })} ${intl.formatMessage({ id: 'back' })}`,
|
||||
kbd: '⇧[',
|
||||
},
|
||||
],
|
||||
File: [
|
||||
{ label: intl.formatMessage({ id: 'new.project' }), kbd: '#N' },
|
||||
{ label: intl.formatMessage({ id: 'open' }), kbd: '#O' },
|
||||
{ label: intl.formatMessage({ id: 'save' }), kbd: '#S' },
|
||||
{ label: intl.formatMessage({ id: 'save.as' }), kbd: '#⇧S' },
|
||||
{ label: intl.formatMessage({ id: 'upload.media' }), kbd: '#U' },
|
||||
],
|
||||
Edit: [
|
||||
{ label: intl.formatMessage({ id: 'undo' }), kbd: '#Z' },
|
||||
{ label: intl.formatMessage({ id: 'redo' }), kbd: '#⇧Z' },
|
||||
{ label: intl.formatMessage({ id: 'cut' }), kbd: '#X' },
|
||||
{ label: intl.formatMessage({ id: 'copy' }), kbd: '#C' },
|
||||
{ label: intl.formatMessage({ id: 'paste' }), kbd: '#V' },
|
||||
{ label: intl.formatMessage({ id: 'select.all' }), kbd: '#A' },
|
||||
{ label: intl.formatMessage({ id: 'delete' }), kbd: '⌫' },
|
||||
{ label: intl.formatMessage({ id: 'duplicate' }), kbd: '#D' },
|
||||
],
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog.Root onOpenChange={onOpenChange}>
|
||||
<Dialog.Trigger asChild>
|
||||
<RowButton id="TD-HelpItem-Keyboard" variant="wide">
|
||||
<FormattedMessage id="keyboard.shortcuts" />
|
||||
</RowButton>
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Portal>
|
||||
<DialogOverlay />
|
||||
<DialogContent>
|
||||
<DialogTitle>
|
||||
<FormattedMessage id="keyboard.shortcuts" />
|
||||
<Dialog.Close asChild>
|
||||
<DialogIconButton>
|
||||
<Cross2Icon />
|
||||
</DialogIconButton>
|
||||
</Dialog.Close>
|
||||
</DialogTitle>
|
||||
<StyledColumns bp={breakpoints}>
|
||||
{Object.entries(shortcuts).map(([key, value]) => (
|
||||
<StyledSection key={key}>
|
||||
<Label>{key}</Label>
|
||||
<ContentItem>
|
||||
{value.map((shortcut) => (
|
||||
<StyledItem key={shortcut.label}>
|
||||
{shortcut.label}
|
||||
<Kbd variant="menu">{shortcut.kbd}</Kbd>
|
||||
</StyledItem>
|
||||
))}
|
||||
</ContentItem>
|
||||
</StyledSection>
|
||||
))}
|
||||
</StyledColumns>
|
||||
</DialogContent>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
)
|
||||
}
|
||||
|
||||
const Label = styled('h3', {
|
||||
fontSize: '$2',
|
||||
color: '$text',
|
||||
fontFamily: '$ui',
|
||||
margin: 0,
|
||||
paddingBottom: '$5',
|
||||
})
|
||||
|
||||
const StyledSection = styled('div', {
|
||||
breakInside: 'avoid',
|
||||
paddingBottom: 24,
|
||||
})
|
||||
|
||||
const ContentItem = styled('ul', {
|
||||
listStyleType: 'none',
|
||||
width: '100%',
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
})
|
||||
|
||||
const StyledItem = styled('li', {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
height: 32,
|
||||
minHeight: 32,
|
||||
width: '100%',
|
||||
outline: 'none',
|
||||
color: '$text',
|
||||
fontFamily: '$ui',
|
||||
fontWeight: 400,
|
||||
fontSize: '$1',
|
||||
borderRadius: 4,
|
||||
userSelect: 'none',
|
||||
margin: 0,
|
||||
padding: '0 0',
|
||||
})
|
||||
|
||||
const DialogContent = styled(Dialog.Content, {
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 6,
|
||||
boxShadow: 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
|
||||
position: 'fixed',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 'fit-content',
|
||||
maxWidth: '90vw',
|
||||
maxHeight: '74vh',
|
||||
overflowY: 'auto',
|
||||
padding: 25,
|
||||
'&:focus': { outline: 'none' },
|
||||
})
|
||||
|
||||
const StyledColumns = styled('div', {
|
||||
maxWidth: '100%',
|
||||
width: 'fit-content',
|
||||
height: 'fit-content',
|
||||
overflowY: 'auto',
|
||||
columnGap: 64,
|
||||
variants: {
|
||||
bp: {
|
||||
mobile: {
|
||||
columns: 1,
|
||||
[`& ${StyledSection}`]: {
|
||||
minWidth: '0px',
|
||||
},
|
||||
},
|
||||
small: {
|
||||
columns: 2,
|
||||
[`& ${StyledSection}`]: {
|
||||
minWidth: '200px',
|
||||
},
|
||||
},
|
||||
medium: {
|
||||
columns: 3,
|
||||
},
|
||||
large: {
|
||||
columns: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const DialogOverlay = styled(Dialog.Overlay, {
|
||||
backgroundColor: '$overlay',
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
})
|
||||
|
||||
const DialogIconButton = styled(IconButton, {
|
||||
fontFamily: 'inherit',
|
||||
borderRadius: '100%',
|
||||
height: 25,
|
||||
width: 25,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: '$text',
|
||||
cursor: 'pointer',
|
||||
'&:hover': { backgroundColor: '$hover' },
|
||||
})
|
||||
|
||||
const DialogTitle = styled(Dialog.Title, {
|
||||
fontFamily: '$body',
|
||||
fontSize: '$3',
|
||||
color: '$text',
|
||||
paddingBottom: 32,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
margin: 0,
|
||||
})
|
|
@ -1,8 +1,7 @@
|
|||
import { ExternalLinkIcon } from '@radix-ui/react-icons'
|
||||
import * as React from 'react'
|
||||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
import { DMCheckboxItem, DMDivider, DMItem, DMSubMenu } from '~components/Primitives/DropdownMenu'
|
||||
import { HeartIcon } from '~components/Primitives/icons/HeartIcon'
|
||||
import { ExternalLinkIcon } from '@radix-ui/react-icons'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
import { DMCheckboxItem, DMContent, DMDivider, DMItem } from '~components/Primitives/DropdownMenu'
|
||||
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
||||
import { useTldrawApp } from '~hooks'
|
||||
import { TDLanguage, TRANSLATIONS } from '~translations'
|
||||
|
@ -13,7 +12,6 @@ const languageSelector = (s: TDSnapshot) => s.settings.language
|
|||
export function LanguageMenu() {
|
||||
const app = useTldrawApp()
|
||||
const language = app.useStore(languageSelector)
|
||||
const intl = useIntl()
|
||||
|
||||
const handleChangeLanguage = React.useCallback(
|
||||
(locale: TDLanguage) => {
|
||||
|
@ -23,7 +21,7 @@ export function LanguageMenu() {
|
|||
)
|
||||
|
||||
return (
|
||||
<DMSubMenu label={intl.formatMessage({ id: 'language' })}>
|
||||
<DMContent variant="menu" overflow={true} id="language-menu" side="left" sideOffset={8}>
|
||||
{TRANSLATIONS.map(({ locale, label }) => (
|
||||
<DMCheckboxItem
|
||||
key={locale}
|
||||
|
@ -47,6 +45,6 @@ export function LanguageMenu() {
|
|||
</SmallIcon>
|
||||
</DMItem>
|
||||
</a>
|
||||
</DMSubMenu>
|
||||
</DMContent>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import { DiscordIcon } from '~components/Primitives/icons'
|
|||
import { TDExportType, TDSnapshot } from '~types'
|
||||
import { Divider } from '~components/Primitives/Divider'
|
||||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
import { LanguageMenu } from '../LanguageMenu/LanguageMenu'
|
||||
|
||||
interface MenuProps {
|
||||
sponsor: boolean | undefined
|
||||
|
@ -326,53 +325,6 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
</DMSubMenu>
|
||||
<DMDivider dir="ltr" />
|
||||
<PreferencesMenu />
|
||||
<DMDivider dir="ltr" />
|
||||
<LanguageMenu />
|
||||
<DMDivider dir="ltr" />
|
||||
<a href="https://github.com/Tldraw/Tldraw" target="_blank" rel="nofollow">
|
||||
<DMItem id="TD-MenuItem-Github">
|
||||
GitHub
|
||||
<SmallIcon>
|
||||
<GitHubLogoIcon />
|
||||
</SmallIcon>
|
||||
</DMItem>
|
||||
</a>
|
||||
<a href="https://twitter.com/Tldraw" target="_blank" rel="nofollow">
|
||||
<DMItem id="TD-MenuItem-Twitter">
|
||||
Twitter
|
||||
<SmallIcon>
|
||||
<TwitterLogoIcon />
|
||||
</SmallIcon>
|
||||
</DMItem>
|
||||
</a>
|
||||
<a href="https://discord.gg/SBBEVCA4PG" target="_blank" rel="nofollow">
|
||||
<DMItem id="TD-MenuItem-Discord">
|
||||
Discord
|
||||
<SmallIcon>
|
||||
<DiscordIcon />
|
||||
</SmallIcon>
|
||||
</DMItem>
|
||||
</a>
|
||||
{sponsor === false && (
|
||||
<a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow">
|
||||
<DMItem isSponsor id="TD-MenuItem-Become_a_Sponsor">
|
||||
<FormattedMessage id="become.a.sponsor" />{' '}
|
||||
<SmallIcon>
|
||||
<HeartIcon />
|
||||
</SmallIcon>
|
||||
</DMItem>
|
||||
</a>
|
||||
)}
|
||||
{sponsor === true && (
|
||||
<a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow">
|
||||
<DMItem id="TD-MenuItem-is_a_Sponsor">
|
||||
<FormattedMessage id="sponsored" />!
|
||||
<SmallIcon>
|
||||
<HeartFilledIcon />
|
||||
</SmallIcon>
|
||||
</DMItem>
|
||||
</a>
|
||||
)}
|
||||
{showSignInOutMenu && (
|
||||
<>
|
||||
<DMDivider dir="ltr" />{' '}
|
||||
|
|
|
@ -138,7 +138,7 @@ export function PreferencesMenu() {
|
|||
>
|
||||
<FormattedMessage id="preferences.clone.handles" />
|
||||
</DMCheckboxItem>
|
||||
<DMSubMenu label={intl.formatMessage({ id: 'dock.position' })}>
|
||||
<DMSubMenu label={intl.formatMessage({ id: 'dock.position' })} overflow={false}>
|
||||
{DockPosition.map((position) => (
|
||||
<DMCheckboxItem
|
||||
key={position}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{}
|
||||
{}
|
|
@ -91,6 +91,7 @@
|
|||
"language": "Langage",
|
||||
"dock.position": "Position du dock",
|
||||
"bottom": "En bas",
|
||||
"keyboard.shortcuts": "Raccourci clavier",
|
||||
"loading": "Chargement{dots}",
|
||||
"left": "À gauche",
|
||||
"right": "À droite",
|
||||
|
|
|
@ -64,10 +64,10 @@
|
|||
"arrow": "Arrow",
|
||||
"text": "Text",
|
||||
"sticky": "Sticky",
|
||||
"Rectangle": "Rectangle",
|
||||
"Ellipse": "Ellipse",
|
||||
"Triangle": "Triangle",
|
||||
"Line": "Line",
|
||||
"rectangle": "Rectangle",
|
||||
"ellipse": "Ellipse",
|
||||
"triangle": "Triangle",
|
||||
"line": "Line",
|
||||
"rotate": "Rotate",
|
||||
"lock.aspect.ratio": "Lock Aspect Ratio",
|
||||
"unlock.aspect.ratio": "Unlock Aspect Ratio",
|
||||
|
@ -96,5 +96,7 @@
|
|||
"right": "Right",
|
||||
"top": "Top",
|
||||
"page": "Page",
|
||||
"keyboard.shortcuts": "Keyboard shortcuts",
|
||||
"search": "Search",
|
||||
"loading": "Loading{dots}"
|
||||
}
|
23
yarn.lock
23
yarn.lock
|
@ -2482,7 +2482,7 @@
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-dialog@0.1.7":
|
||||
"@radix-ui/react-dialog@0.1.7", "@radix-ui/react-dialog@^0.1.7":
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.1.7.tgz#285414cf66f5bbf42bc9935314e0381abe01e7d0"
|
||||
integrity sha512-jXt8srGhHBRvEr9jhEAiwwJzWCWZoGRJ030aC9ja/gkRJbZdy0iD3FwXf+Ff4RtsZyLUMHW7VUwFOlz3Ixe1Vw==
|
||||
|
@ -2584,6 +2584,27 @@
|
|||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "^2.4.0"
|
||||
|
||||
"@radix-ui/react-popover@^0.1.6":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-0.1.6.tgz#788e969239d9c55239678e615ab591b6b7ba5cdc"
|
||||
integrity sha512-zQzgUqW4RQDb0ItAL1xNW4K4olUrkfV3jeEPs9rG+nsDQurO+W9TT+YZ9H1mmgAJqlthyv1sBRZGdBm4YjtD6Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "0.1.0"
|
||||
"@radix-ui/react-compose-refs" "0.1.0"
|
||||
"@radix-ui/react-context" "0.1.1"
|
||||
"@radix-ui/react-dismissable-layer" "0.1.5"
|
||||
"@radix-ui/react-focus-guards" "0.1.0"
|
||||
"@radix-ui/react-focus-scope" "0.1.4"
|
||||
"@radix-ui/react-id" "0.1.5"
|
||||
"@radix-ui/react-popper" "0.1.4"
|
||||
"@radix-ui/react-portal" "0.1.4"
|
||||
"@radix-ui/react-presence" "0.1.2"
|
||||
"@radix-ui/react-primitive" "0.1.4"
|
||||
"@radix-ui/react-use-controllable-state" "0.1.0"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "^2.4.0"
|
||||
|
||||
"@radix-ui/react-popper@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-0.1.4.tgz#dfc055dcd7dfae6a2eff7a70d333141d15a5d029"
|
||||
|
|
Loading…
Reference in a new issue