Cleans up UI components, removes Shared as single file

This commit is contained in:
Steve Ruiz 2021-08-13 15:29:31 +01:00
parent 88711523c7
commit d66007b142
30 changed files with 784 additions and 711 deletions

View file

@ -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,

View 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>
)
}

View file

@ -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'

View file

@ -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'

View file

@ -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>
)
}

View file

@ -0,0 +1,6 @@
/* -------------------------------------------------- */
/* Breakpoints */
/* -------------------------------------------------- */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const breakpoints: any = { '@initial': 'mobile', '@sm': 'small' }

View 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,
})

View 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>
)
}

View 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',
},
})

View 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>
)
}

View 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',
},
},
},
})

View 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',
},
},
},
})

View 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',
},
},
},
},
})

View 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'

View file

@ -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'
} }

View 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',
})

View 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',
})

View 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',
},
},
},
})

View file

@ -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

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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,

View file

@ -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,

View file

@ -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', {

View file

@ -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'

View file

@ -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 => {

View file

@ -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'