Cleans up UI components, removes Shared as single file
This commit is contained in:
parent
88711523c7
commit
d66007b142
30 changed files with 784 additions and 711 deletions
|
@ -1,11 +1,11 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import styled from '~styles'
|
||||||
import { Utils } from '@tldraw/core'
|
import { Utils } from '@tldraw/core'
|
||||||
import * as RadixContextMenu from '@radix-ui/react-context-menu'
|
import * as RadixContextMenu from '@radix-ui/react-context-menu'
|
||||||
import styled from '~styles'
|
|
||||||
import { useTLDrawContext } from '~hooks'
|
import { useTLDrawContext } from '~hooks'
|
||||||
import type { Data } from '~types'
|
import { Data, AlignType, DistributeType, StretchType } from '~types'
|
||||||
import { Kbd } from '../kbd'
|
|
||||||
import {
|
import {
|
||||||
|
Kbd,
|
||||||
IconWrapper,
|
IconWrapper,
|
||||||
breakpoints,
|
breakpoints,
|
||||||
RowButton,
|
RowButton,
|
||||||
|
@ -17,7 +17,6 @@ import {
|
||||||
ContextMenuRoot,
|
ContextMenuRoot,
|
||||||
MenuContent,
|
MenuContent,
|
||||||
} from '../shared'
|
} from '../shared'
|
||||||
import { AlignType, DistributeType, StretchType } from '~types'
|
|
||||||
import {
|
import {
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
AlignBottomIcon,
|
AlignBottomIcon,
|
||||||
|
|
14
packages/tldraw/src/components/icons/circle.tsx
Normal file
14
packages/tldraw/src/components/icons/circle.tsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
export default function CircleIcon(
|
||||||
|
props: Pick<React.SVGProps<SVGSVGElement>, 'stroke' | 'fill'> & {
|
||||||
|
size: number
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const { size = 16, ...rest } = props
|
||||||
|
return (
|
||||||
|
<svg width={24} height={24} {...rest}>
|
||||||
|
<circle cx={12} cy={12} r={size / 2} />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
|
@ -2,3 +2,4 @@ export { default as Redo } from './redo'
|
||||||
export { default as Trash } from './trash'
|
export { default as Trash } from './trash'
|
||||||
export { default as Undo } from './undo'
|
export { default as Undo } from './undo'
|
||||||
export { default as Check } from './check'
|
export { default as Check } from './check'
|
||||||
|
export { default as CircleIcon } from './circle'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export * from './tooltip'
|
export * from './shared/tooltip'
|
||||||
export * from './kbd'
|
export * from './shared/kbd'
|
||||||
export * from './shared'
|
export * from './shared'
|
||||||
export * from './icons'
|
export * from './icons'
|
||||||
export * from './tldraw'
|
export * from './tldraw'
|
||||||
|
|
|
@ -1,691 +0,0 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import * as React from 'react'
|
|
||||||
import { Tooltip } from './tooltip'
|
|
||||||
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 {
|
|
||||||
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 { Root as RGRoot } from '@radix-ui/react-radio-group'
|
|
||||||
import { CheckIcon, ChevronRightIcon } from '@radix-ui/react-icons'
|
|
||||||
import styled from '~styles'
|
|
||||||
|
|
||||||
export const breakpoints: any = { '@initial': 'mobile', '@sm': 'small' }
|
|
||||||
|
|
||||||
export const IconButton = styled('button', {
|
|
||||||
position: 'relative',
|
|
||||||
height: '32px',
|
|
||||||
width: '32px',
|
|
||||||
backgroundColor: '$panel',
|
|
||||||
borderRadius: '4px',
|
|
||||||
padding: '0',
|
|
||||||
margin: '0',
|
|
||||||
display: 'grid',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
outline: 'none',
|
|
||||||
border: 'none',
|
|
||||||
pointerEvents: 'all',
|
|
||||||
fontSize: '$0',
|
|
||||||
color: '$text',
|
|
||||||
cursor: 'pointer',
|
|
||||||
|
|
||||||
'& > *': {
|
|
||||||
gridRow: 1,
|
|
||||||
gridColumn: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
'&:disabled': {
|
|
||||||
opacity: '0.5',
|
|
||||||
},
|
|
||||||
|
|
||||||
'& > span': {
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
|
|
||||||
variants: {
|
|
||||||
bp: {
|
|
||||||
mobile: {
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
},
|
|
||||||
small: {
|
|
||||||
'&:hover:not(:disabled)': {
|
|
||||||
backgroundColor: '$hover',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
small: {
|
|
||||||
height: 32,
|
|
||||||
width: 32,
|
|
||||||
'& svg:nth-of-type(1)': {
|
|
||||||
height: '16px',
|
|
||||||
width: '16px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
medium: {
|
|
||||||
height: 44,
|
|
||||||
width: 44,
|
|
||||||
'& svg:nth-of-type(1)': {
|
|
||||||
height: '18px',
|
|
||||||
width: '18px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
large: {
|
|
||||||
height: 44,
|
|
||||||
width: 44,
|
|
||||||
'& svg:nth-of-type(1)': {
|
|
||||||
height: '20px',
|
|
||||||
width: '20px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
isActive: {
|
|
||||||
true: {
|
|
||||||
color: '$selected',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const RowButton = styled('button', {
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const Group = styled(RGRoot, {
|
|
||||||
display: 'flex',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const ShortcutKey = styled('span', {
|
|
||||||
fontSize: '$0',
|
|
||||||
width: '16px',
|
|
||||||
height: '16px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
boxShadow: '1px 1px 0px rgba(0,0,0,.5)',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const IconWrapper = styled('div', {
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const VerticalDivider = styled('hr', {
|
|
||||||
width: '1px',
|
|
||||||
margin: '-2px 3px',
|
|
||||||
border: 'none',
|
|
||||||
backgroundColor: '$brushFill',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const FloatingContainer = styled('div', {
|
|
||||||
backgroundColor: '$panel',
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
/* -------------------------------------------------- */
|
|
||||||
/* Dialog */
|
|
||||||
/* -------------------------------------------------- */
|
|
||||||
|
|
||||||
export const DialogContent = styled('div', {
|
|
||||||
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 = styled('div', {
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, .15)',
|
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const DialogInputWrapper = styled('div', {
|
|
||||||
padding: '$4 $2',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const DialogTitleRow = styled('div', {
|
|
||||||
display: 'flex',
|
|
||||||
padding: '0 0 0 $4',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
|
|
||||||
h3: {
|
|
||||||
fontSize: '$1',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
/* -------------------------------------------------- */
|
|
||||||
/* Menus */
|
|
||||||
/* -------------------------------------------------- */
|
|
||||||
|
|
||||||
export const MenuContent = styled('div', {
|
|
||||||
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 = styled('div', {
|
|
||||||
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 (
|
|
||||||
<RowButton bp={breakpoints} disabled={disabled} warn={warn} onSelect={onSelect}>
|
|
||||||
{children}
|
|
||||||
</RowButton>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MenuTextInput = styled('input', {
|
|
||||||
backgroundColor: '$panel',
|
|
||||||
border: 'none',
|
|
||||||
padding: '$4 $3',
|
|
||||||
width: '100%',
|
|
||||||
outline: 'none',
|
|
||||||
background: '$input',
|
|
||||||
borderRadius: '4px',
|
|
||||||
font: '$ui',
|
|
||||||
fontSize: '$1',
|
|
||||||
})
|
|
||||||
|
|
||||||
/* -------------------------------------------------- */
|
|
||||||
/* Dropdown Menu */
|
|
||||||
/* -------------------------------------------------- */
|
|
||||||
|
|
||||||
export function DropdownMenuRoot({
|
|
||||||
isOpen,
|
|
||||||
onOpenChange,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
isOpen?: boolean
|
|
||||||
onOpenChange?: (isOpen: boolean) => void
|
|
||||||
children: React.ReactNode
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
<DMRoot dir="ltr" open={isOpen} onOpenChange={onOpenChange}>
|
|
||||||
{children}
|
|
||||||
</DMRoot>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DropdownMenuSubMenu({
|
|
||||||
children,
|
|
||||||
disabled = false,
|
|
||||||
label,
|
|
||||||
}: {
|
|
||||||
label: string
|
|
||||||
disabled?: boolean
|
|
||||||
children: React.ReactNode
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
<DMRoot dir="ltr">
|
|
||||||
<DMTriggerItem as={RowButton} bp={breakpoints} disabled={disabled}>
|
|
||||||
<span>{label}</span>
|
|
||||||
<IconWrapper size="small">
|
|
||||||
<ChevronRightIcon />
|
|
||||||
</IconWrapper>
|
|
||||||
</DMTriggerItem>
|
|
||||||
<DMContent as={MenuContent} sideOffset={2} alignOffset={-2}>
|
|
||||||
{children}
|
|
||||||
<DropdownMenuArrow offset={13} />
|
|
||||||
</DMContent>
|
|
||||||
</DMRoot>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DropdownMenuDivider = styled(DMSeparator, {
|
|
||||||
backgroundColor: '$hover',
|
|
||||||
height: 1,
|
|
||||||
marginTop: '$2',
|
|
||||||
marginRight: '-$2',
|
|
||||||
marginBottom: '$2',
|
|
||||||
marginLeft: '-$2',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const DropdownMenuArrow = styled(DMArrow, {
|
|
||||||
fill: '$panel',
|
|
||||||
})
|
|
||||||
|
|
||||||
export function DropdownMenuButton({
|
|
||||||
onSelect,
|
|
||||||
children,
|
|
||||||
disabled = false,
|
|
||||||
}: {
|
|
||||||
onSelect?: () => void
|
|
||||||
disabled?: boolean
|
|
||||||
children: React.ReactNode
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
<DMItem as={RowButton} bp={breakpoints} disabled={disabled} onSelect={onSelect}>
|
|
||||||
{children}
|
|
||||||
</DMItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DropdownMenuIconButtonProps {
|
|
||||||
onSelect: () => void
|
|
||||||
disabled?: boolean
|
|
||||||
children: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DropdownMenuIconButton({
|
|
||||||
onSelect,
|
|
||||||
children,
|
|
||||||
disabled = false,
|
|
||||||
}: DropdownMenuIconButtonProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<DMItem as={IconButton} bp={breakpoints} disabled={disabled} onSelect={onSelect}>
|
|
||||||
{children}
|
|
||||||
</DMItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DropdownMenuIconTriggerButtonProps {
|
|
||||||
label: string
|
|
||||||
kbd?: string
|
|
||||||
disabled?: boolean
|
|
||||||
children: React.ReactNode
|
|
||||||
}
|
|
||||||
export function DropdownMenuIconTriggerButton({
|
|
||||||
label,
|
|
||||||
kbd,
|
|
||||||
children,
|
|
||||||
disabled = false,
|
|
||||||
}: DropdownMenuIconTriggerButtonProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<DMTrigger as={IconButton} bp={breakpoints} disabled={disabled}>
|
|
||||||
<Tooltip label={label} kbd={kbd}>
|
|
||||||
{children}
|
|
||||||
</Tooltip>
|
|
||||||
</DMTrigger>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<DMCheckboxItem
|
|
||||||
as={RowButton}
|
|
||||||
bp={breakpoints}
|
|
||||||
onCheckedChange={onCheckedChange}
|
|
||||||
checked={checked}
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<DMItemIndicator>
|
|
||||||
<IconWrapper size="small">
|
|
||||||
<CheckIcon />
|
|
||||||
</IconWrapper>
|
|
||||||
</DMItemIndicator>
|
|
||||||
</DMCheckboxItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------------- */
|
|
||||||
/* Context Menu */
|
|
||||||
/* -------------------------------------------------- */
|
|
||||||
|
|
||||||
export function ContextMenuRoot({
|
|
||||||
onOpenChange,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
onOpenChange?: (isOpen: boolean) => void
|
|
||||||
children: React.ReactNode
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
<CMRoot dir="ltr" onOpenChange={onOpenChange}>
|
|
||||||
{children}
|
|
||||||
</CMRoot>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ContextMenuSubMenu({
|
|
||||||
children,
|
|
||||||
label,
|
|
||||||
}: {
|
|
||||||
label: string
|
|
||||||
children: React.ReactNode
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
<CMRoot dir="ltr">
|
|
||||||
<CMTriggerItem as={RowButton} bp={breakpoints}>
|
|
||||||
<span>{label}</span>
|
|
||||||
<IconWrapper size="small">
|
|
||||||
<ChevronRightIcon />
|
|
||||||
</IconWrapper>
|
|
||||||
</CMTriggerItem>
|
|
||||||
<CMContent as={MenuContent} sideOffset={2} alignOffset={-2}>
|
|
||||||
{children}
|
|
||||||
<ContextMenuArrow offset={13} />
|
|
||||||
</CMContent>
|
|
||||||
</CMRoot>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ContextMenuDivider = styled(CMSeparator, {
|
|
||||||
backgroundColor: '$hover',
|
|
||||||
height: 1,
|
|
||||||
margin: '$2 -$2',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const ContextMenuArrow = styled(CMArrow, {
|
|
||||||
fill: '$panel',
|
|
||||||
})
|
|
||||||
|
|
||||||
export function ContextMenuButton({
|
|
||||||
onSelect,
|
|
||||||
children,
|
|
||||||
disabled = false,
|
|
||||||
}: {
|
|
||||||
onSelect?: () => void
|
|
||||||
disabled?: boolean
|
|
||||||
children: React.ReactNode
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
<RowButton as={CMItem} bp={breakpoints} disabled={disabled} onSelect={onSelect}>
|
|
||||||
{children}
|
|
||||||
</RowButton>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ContextMenuIconButton({
|
|
||||||
onSelect,
|
|
||||||
children,
|
|
||||||
disabled = false,
|
|
||||||
}: {
|
|
||||||
onSelect: () => void
|
|
||||||
disabled?: boolean
|
|
||||||
children: React.ReactNode
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
<CMItem as={IconButton} bp={breakpoints} disabled={disabled} onSelect={onSelect}>
|
|
||||||
{children}
|
|
||||||
</CMItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ContextMenuCheckboxItem({
|
|
||||||
checked,
|
|
||||||
disabled = false,
|
|
||||||
onCheckedChange,
|
|
||||||
children,
|
|
||||||
}: MenuCheckboxItemProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<CMCheckboxItem
|
|
||||||
as={RowButton}
|
|
||||||
bp={breakpoints}
|
|
||||||
onCheckedChange={onCheckedChange}
|
|
||||||
checked={checked}
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<CMItemIndicator>
|
|
||||||
<IconWrapper size="small">
|
|
||||||
<CheckIcon />
|
|
||||||
</IconWrapper>
|
|
||||||
</CMItemIndicator>
|
|
||||||
</CMCheckboxItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CircleIcon(
|
|
||||||
props: Pick<React.SVGProps<SVGSVGElement>, 'stroke' | 'fill'> & {
|
|
||||||
size: number
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const { size = 16, ...rest } = props
|
|
||||||
return (
|
|
||||||
<svg width={24} height={24} {...rest}>
|
|
||||||
<circle cx={12} cy={12} r={size / 2} />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
6
packages/tldraw/src/components/shared/breakpoints.tsx
Normal file
6
packages/tldraw/src/components/shared/breakpoints.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* Breakpoints */
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const breakpoints: any = { '@initial': 'mobile', '@sm': 'small' }
|
18
packages/tldraw/src/components/shared/buttons-row.tsx
Normal file
18
packages/tldraw/src/components/shared/buttons-row.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import styled from '~styles'
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* Buttons Row */
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
134
packages/tldraw/src/components/shared/context-menu.tsx
Normal file
134
packages/tldraw/src/components/shared/context-menu.tsx
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
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 styled from '~styles'
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* Context Menu */
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
export interface ContextMenuRootProps {
|
||||||
|
onOpenChange?: (isOpen: boolean) => void
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ContextMenuRoot({ onOpenChange, children }: ContextMenuRootProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<CMRoot dir="ltr" onOpenChange={onOpenChange}>
|
||||||
|
{children}
|
||||||
|
</CMRoot>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContextMenuSubMenuProps {
|
||||||
|
label: string
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ContextMenuSubMenu({ children, label }: ContextMenuSubMenuProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<CMRoot dir="ltr">
|
||||||
|
<CMTriggerItem as={RowButton} bp={breakpoints}>
|
||||||
|
<span>{label}</span>
|
||||||
|
<IconWrapper size="small">
|
||||||
|
<ChevronRightIcon />
|
||||||
|
</IconWrapper>
|
||||||
|
</CMTriggerItem>
|
||||||
|
<CMContent as={MenuContent} sideOffset={2} alignOffset={-2}>
|
||||||
|
{children}
|
||||||
|
<ContextMenuArrow offset={13} />
|
||||||
|
</CMContent>
|
||||||
|
</CMRoot>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContextMenuDivider = styled(CMSeparator, {
|
||||||
|
backgroundColor: '$hover',
|
||||||
|
height: 1,
|
||||||
|
margin: '$2 -$2',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ContextMenuArrow = styled(CMArrow, {
|
||||||
|
fill: '$panel',
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface ContextMenuButtonProps {
|
||||||
|
onSelect?: () => void
|
||||||
|
disabled?: boolean
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ContextMenuButton({
|
||||||
|
onSelect,
|
||||||
|
children,
|
||||||
|
disabled = false,
|
||||||
|
}: ContextMenuButtonProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<RowButton as={CMItem} bp={breakpoints} disabled={disabled} onSelect={onSelect}>
|
||||||
|
{children}
|
||||||
|
</RowButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContextMenuIconButtonProps {
|
||||||
|
onSelect: () => void
|
||||||
|
disabled?: boolean
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ContextMenuIconButton({
|
||||||
|
onSelect,
|
||||||
|
children,
|
||||||
|
disabled = false,
|
||||||
|
}: ContextMenuIconButtonProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<CMItem as={IconButton} bp={breakpoints} disabled={disabled} onSelect={onSelect}>
|
||||||
|
{children}
|
||||||
|
</CMItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<CMCheckboxItem
|
||||||
|
as={RowButton}
|
||||||
|
bp={breakpoints}
|
||||||
|
onCheckedChange={onCheckedChange}
|
||||||
|
checked={checked}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<CMItemIndicator>
|
||||||
|
<IconWrapper size="small">
|
||||||
|
<CheckIcon />
|
||||||
|
</IconWrapper>
|
||||||
|
</CMItemIndicator>
|
||||||
|
</CMCheckboxItem>
|
||||||
|
)
|
||||||
|
}
|
51
packages/tldraw/src/components/shared/dialog.tsx
Normal file
51
packages/tldraw/src/components/shared/dialog.tsx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import styled from '~styles'
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* Dialog */
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
export const DialogContent = styled('div', {
|
||||||
|
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 = styled('div', {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, .15)',
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DialogInputWrapper = styled('div', {
|
||||||
|
padding: '$4 $2',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DialogTitleRow = styled('div', {
|
||||||
|
display: 'flex',
|
||||||
|
padding: '0 0 0 $4',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
|
||||||
|
h3: {
|
||||||
|
fontSize: '$1',
|
||||||
|
},
|
||||||
|
})
|
173
packages/tldraw/src/components/shared/dropdown-menu.tsx
Normal file
173
packages/tldraw/src/components/shared/dropdown-menu.tsx
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
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 styled 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 (
|
||||||
|
<DMRoot dir="ltr" open={isOpen} onOpenChange={onOpenChange}>
|
||||||
|
{children}
|
||||||
|
</DMRoot>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DropdownMenuSubMenuProps {
|
||||||
|
label: string
|
||||||
|
disabled?: boolean
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DropdownMenuSubMenu({
|
||||||
|
children,
|
||||||
|
disabled = false,
|
||||||
|
label,
|
||||||
|
}: DropdownMenuSubMenuProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<DMRoot dir="ltr">
|
||||||
|
<DMTriggerItem as={RowButton} bp={breakpoints} disabled={disabled}>
|
||||||
|
<span>{label}</span>
|
||||||
|
<IconWrapper size="small">
|
||||||
|
<ChevronRightIcon />
|
||||||
|
</IconWrapper>
|
||||||
|
</DMTriggerItem>
|
||||||
|
<DMContent as={MenuContent} sideOffset={2} alignOffset={-2}>
|
||||||
|
{children}
|
||||||
|
<DropdownMenuArrow offset={13} />
|
||||||
|
</DMContent>
|
||||||
|
</DMRoot>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DropdownMenuDivider = styled(DMSeparator, {
|
||||||
|
backgroundColor: '$hover',
|
||||||
|
height: 1,
|
||||||
|
marginTop: '$2',
|
||||||
|
marginRight: '-$2',
|
||||||
|
marginBottom: '$2',
|
||||||
|
marginLeft: '-$2',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DropdownMenuArrow = styled(DMArrow, {
|
||||||
|
fill: '$panel',
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface DropdownMenuButtonProps {
|
||||||
|
onSelect?: () => void
|
||||||
|
disabled?: boolean
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DropdownMenuButton({
|
||||||
|
onSelect,
|
||||||
|
children,
|
||||||
|
disabled = false,
|
||||||
|
}: DropdownMenuButtonProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<DMItem as={RowButton} bp={breakpoints} disabled={disabled} onSelect={onSelect}>
|
||||||
|
{children}
|
||||||
|
</DMItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DropdownMenuIconButtonProps {
|
||||||
|
onSelect: () => void
|
||||||
|
disabled?: boolean
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DropdownMenuIconButton({
|
||||||
|
onSelect,
|
||||||
|
children,
|
||||||
|
disabled = false,
|
||||||
|
}: DropdownMenuIconButtonProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<DMItem as={IconButton} bp={breakpoints} disabled={disabled} onSelect={onSelect}>
|
||||||
|
{children}
|
||||||
|
</DMItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DropdownMenuIconTriggerButtonProps {
|
||||||
|
label: string
|
||||||
|
kbd?: string
|
||||||
|
disabled?: boolean
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DropdownMenuIconTriggerButton({
|
||||||
|
label,
|
||||||
|
kbd,
|
||||||
|
children,
|
||||||
|
disabled = false,
|
||||||
|
}: DropdownMenuIconTriggerButtonProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<DMTrigger as={IconButton} bp={breakpoints} disabled={disabled}>
|
||||||
|
<Tooltip label={label} kbd={kbd}>
|
||||||
|
{children}
|
||||||
|
</Tooltip>
|
||||||
|
</DMTrigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<DMCheckboxItem
|
||||||
|
as={RowButton}
|
||||||
|
bp={breakpoints}
|
||||||
|
onCheckedChange={onCheckedChange}
|
||||||
|
checked={checked}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<DMItemIndicator>
|
||||||
|
<IconWrapper size="small">
|
||||||
|
<CheckIcon />
|
||||||
|
</IconWrapper>
|
||||||
|
</DMItemIndicator>
|
||||||
|
</DMCheckboxItem>
|
||||||
|
)
|
||||||
|
}
|
44
packages/tldraw/src/components/shared/floating-container.tsx
Normal file
44
packages/tldraw/src/components/shared/floating-container.tsx
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import styled from '~styles'
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* Floating Container */
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
export const FloatingContainer = styled('div', {
|
||||||
|
backgroundColor: '$panel',
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
84
packages/tldraw/src/components/shared/icon-button.tsx
Normal file
84
packages/tldraw/src/components/shared/icon-button.tsx
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import styled from '~styles'
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* Icon Button */
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
export const IconButton = styled('button', {
|
||||||
|
position: 'relative',
|
||||||
|
height: '32px',
|
||||||
|
width: '32px',
|
||||||
|
backgroundColor: '$panel',
|
||||||
|
borderRadius: '4px',
|
||||||
|
padding: '0',
|
||||||
|
margin: '0',
|
||||||
|
display: 'grid',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
outline: 'none',
|
||||||
|
border: 'none',
|
||||||
|
pointerEvents: 'all',
|
||||||
|
fontSize: '$0',
|
||||||
|
color: '$text',
|
||||||
|
cursor: 'pointer',
|
||||||
|
|
||||||
|
'& > *': {
|
||||||
|
gridRow: 1,
|
||||||
|
gridColumn: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:disabled': {
|
||||||
|
opacity: '0.5',
|
||||||
|
},
|
||||||
|
|
||||||
|
'& > span': {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
bp: {
|
||||||
|
mobile: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
small: {
|
||||||
|
'&:hover:not(:disabled)': {
|
||||||
|
backgroundColor: '$hover',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
small: {
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
'& svg:nth-of-type(1)': {
|
||||||
|
height: '16px',
|
||||||
|
width: '16px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
medium: {
|
||||||
|
height: 44,
|
||||||
|
width: 44,
|
||||||
|
'& svg:nth-of-type(1)': {
|
||||||
|
height: '18px',
|
||||||
|
width: '18px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
large: {
|
||||||
|
height: 44,
|
||||||
|
width: 44,
|
||||||
|
'& svg:nth-of-type(1)': {
|
||||||
|
height: '20px',
|
||||||
|
width: '20px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
true: {
|
||||||
|
color: '$selected',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
47
packages/tldraw/src/components/shared/icon-wrapper.tsx
Normal file
47
packages/tldraw/src/components/shared/icon-wrapper.tsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import styled from '~styles'
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* Icon Wrapper */
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
export const IconWrapper = styled('div', {
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
13
packages/tldraw/src/components/shared/index.ts
Normal file
13
packages/tldraw/src/components/shared/index.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
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'
|
|
@ -2,6 +2,10 @@ import * as React from 'react'
|
||||||
import styled from '~styles'
|
import styled from '~styles'
|
||||||
import { Utils } from '@tldraw/core'
|
import { Utils } from '@tldraw/core'
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* Keyboard Shortcut */
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
export function commandKey(): string {
|
export function commandKey(): string {
|
||||||
return Utils.isDarwin() ? '⌘' : 'Ctrl'
|
return Utils.isDarwin() ? '⌘' : 'Ctrl'
|
||||||
}
|
}
|
61
packages/tldraw/src/components/shared/menu.tsx
Normal file
61
packages/tldraw/src/components/shared/menu.tsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { breakpoints } from './breakpoints'
|
||||||
|
import styled from '~styles'
|
||||||
|
import { RowButton } from './row-button'
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* Menu */
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
export const MenuContent = styled('div', {
|
||||||
|
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 = styled('div', {
|
||||||
|
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 (
|
||||||
|
<RowButton bp={breakpoints} disabled={disabled} warn={warn} onSelect={onSelect}>
|
||||||
|
{children}
|
||||||
|
</RowButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MenuTextInput = styled('input', {
|
||||||
|
backgroundColor: '$panel',
|
||||||
|
border: 'none',
|
||||||
|
padding: '$4 $3',
|
||||||
|
width: '100%',
|
||||||
|
outline: 'none',
|
||||||
|
background: '$input',
|
||||||
|
borderRadius: '4px',
|
||||||
|
font: '$ui',
|
||||||
|
fontSize: '$1',
|
||||||
|
})
|
10
packages/tldraw/src/components/shared/radio-group.tsx
Normal file
10
packages/tldraw/src/components/shared/radio-group.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import styled from '~styles'
|
||||||
|
import { Root as RGRoot } from '@radix-ui/react-radio-group'
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* Radio Group */
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
export const Group = styled(RGRoot, {
|
||||||
|
display: 'flex',
|
||||||
|
})
|
101
packages/tldraw/src/components/shared/row-button.tsx
Normal file
101
packages/tldraw/src/components/shared/row-button.tsx
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import styled from '~styles'
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* Row Button */
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
export const RowButton = styled('button', {
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
|
@ -3,6 +3,10 @@ import * as React from 'react'
|
||||||
import styled from '~styles'
|
import styled from '~styles'
|
||||||
import { Kbd } from './kbd'
|
import { Kbd } from './kbd'
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* Tooltip */
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
interface TooltipProps {
|
interface TooltipProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
label: string
|
label: string
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import { BoxIcon, StyleDropdownItem, StyleDropdownContent } from './shared'
|
import { BoxIcon, StyleDropdownItem, StyleDropdownContent } from './styled'
|
||||||
import { DropdownMenuIconTriggerButton } from '../shared'
|
import { DropdownMenuIconTriggerButton } from '../shared'
|
||||||
import { strokes } from '~shape'
|
import { strokes } from '~shape'
|
||||||
import { useTheme, useTLDrawContext } from '~hooks'
|
import { useTheme, useTLDrawContext } from '~hooks'
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
DashDashedIcon,
|
DashDashedIcon,
|
||||||
StyleDropdownContent,
|
StyleDropdownContent,
|
||||||
StyleDropdownItem,
|
StyleDropdownItem,
|
||||||
} from './shared'
|
} from './styled'
|
||||||
import { useTLDrawContext } from '~hooks'
|
import { useTLDrawContext } from '~hooks'
|
||||||
import { DashStyle, Data } from '~types'
|
import { DashStyle, Data } from '~types'
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import * as Checkbox from '@radix-ui/react-checkbox'
|
import * as Checkbox from '@radix-ui/react-checkbox'
|
||||||
import { breakpoints, IconButton, IconWrapper } from '../shared'
|
import { BoxIcon, IsFilledFillIcon } from './styled'
|
||||||
import { BoxIcon, IsFilledFillIcon } from './shared'
|
import { breakpoints, Tooltip, IconButton, IconWrapper } from '../shared'
|
||||||
import { Tooltip } from '../tooltip'
|
|
||||||
import { useTLDrawContext } from '~hooks'
|
import { useTLDrawContext } from '~hooks'
|
||||||
import type { Data } from '~types'
|
import type { Data } from '~types'
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import { DropdownMenuIconTriggerButton, CircleIcon } from '../shared'
|
import { DropdownMenuIconTriggerButton } from '../shared/dropdown-menu'
|
||||||
import { StyleDropdownContent, StyleDropdownItem } from './shared'
|
import { CircleIcon } from '../icons'
|
||||||
|
import { StyleDropdownContent, StyleDropdownItem } from './styled'
|
||||||
import { Data, SizeStyle } from '~types'
|
import { Data, SizeStyle } from '~types'
|
||||||
import { useTLDrawContext } from '~hooks'
|
import { useTLDrawContext } from '~hooks'
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { IconButton, ButtonsRow, breakpoints } from '../shared'
|
import { IconButton, ButtonsRow, breakpoints } from '../shared'
|
||||||
import { Trash } from '../icons'
|
import { Trash } from '../icons'
|
||||||
import { Tooltip } from '../tooltip'
|
import { Tooltip } from '../shared/tooltip'
|
||||||
import {
|
import {
|
||||||
ArrowDownIcon,
|
ArrowDownIcon,
|
||||||
ArrowUpIcon,
|
ArrowUpIcon,
|
||||||
|
|
|
@ -9,8 +9,8 @@ import { QuickColorSelect } from './quick-color-select'
|
||||||
import { QuickSizeSelect } from './quick-size-select'
|
import { QuickSizeSelect } from './quick-size-select'
|
||||||
import { QuickDashSelect } from './quick-dash-select'
|
import { QuickDashSelect } from './quick-dash-select'
|
||||||
import { QuickFillSelect } from './quick-fill-select'
|
import { QuickFillSelect } from './quick-fill-select'
|
||||||
import { Tooltip } from '../tooltip'
|
import { Tooltip } from '../shared/tooltip'
|
||||||
import { Kbd } from '../kbd'
|
import { Kbd } from '../shared/kbd'
|
||||||
import {
|
import {
|
||||||
IconButton,
|
IconButton,
|
||||||
ButtonsRow,
|
ButtonsRow,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { FloatingContainer } from '../shared'
|
import { FloatingContainer } from '../shared'
|
||||||
import { Tooltip } from '../tooltip'
|
import { Tooltip } from '../shared/tooltip'
|
||||||
import styled from '~styles'
|
import styled from '~styles'
|
||||||
|
|
||||||
export const ToolButton = styled('button', {
|
export const ToolButton = styled('button', {
|
|
@ -14,7 +14,7 @@ import { Data, TLDrawShapeType } from '~types'
|
||||||
import { useTLDrawContext } from '~hooks'
|
import { useTLDrawContext } from '~hooks'
|
||||||
import { StatusBar } from './status-bar'
|
import { StatusBar } from './status-bar'
|
||||||
import { FloatingContainer } from '../shared'
|
import { FloatingContainer } from '../shared'
|
||||||
import { PrimaryButton, SecondaryButton } from './shared'
|
import { PrimaryButton, SecondaryButton } from './styled'
|
||||||
import { UndoRedo } from './undo-redo'
|
import { UndoRedo } from './undo-redo'
|
||||||
import { Zoom } from './zoom'
|
import { Zoom } from './zoom'
|
||||||
import { BackToContent } from './back-to-content'
|
import { BackToContent } from './back-to-content'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTLDrawContext } from '~hooks'
|
import { useTLDrawContext } from '~hooks'
|
||||||
import { TertiaryButton, TertiaryButtonsContainer } from './shared'
|
import { TertiaryButton, TertiaryButtonsContainer } from './styled'
|
||||||
import { Undo, Redo, Trash } from '../icons'
|
import { Undo, Redo, Trash } from '../icons'
|
||||||
|
|
||||||
export const UndoRedo = React.memo((): JSX.Element => {
|
export const UndoRedo = React.memo((): JSX.Element => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { ZoomInIcon, ZoomOutIcon } from '@radix-ui/react-icons'
|
import { ZoomInIcon, ZoomOutIcon } from '@radix-ui/react-icons'
|
||||||
import { TertiaryButton, TertiaryButtonsContainer } from './shared'
|
import { TertiaryButton, TertiaryButtonsContainer } from './styled'
|
||||||
import { useTLDrawContext } from '~hooks'
|
import { useTLDrawContext } from '~hooks'
|
||||||
import type { Data } from '~types'
|
import type { Data } from '~types'
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue