Add IDs to UI components (#511)

* Add IDs to Menu and its sub-menus

* Commit uncommitted

* Added ID on Styles and Zoom menu

* Added ID on Tools menu

* Added ID on Context menu
This commit is contained in:
Faraz Shaikh 2022-01-21 15:44:18 +04:00 committed by GitHub
parent 5b521df51e
commit dd1fb73876
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 387 additions and 186 deletions

View file

@ -163,44 +163,48 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
tabIndex={-1} tabIndex={-1}
onBlur={onBlur} onBlur={onBlur}
> >
<MenuContent> <MenuContent id="TD-ContextMenu">
{hasSelection ? ( {hasSelection ? (
<> <>
<CMRowButton onClick={handleDuplicate} kbd="#D"> <CMRowButton onClick={handleDuplicate} kbd="#D" id="TD-ContextMenu-Duplicate">
Duplicate Duplicate
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleFlipHorizontal} kbd="⇧H"> <CMRowButton
onClick={handleFlipHorizontal}
kbd="⇧H"
id="TD-ContextMenu-Flip_Horizontal"
>
Flip Horizontal Flip Horizontal
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleFlipVertical} kbd="⇧V"> <CMRowButton onClick={handleFlipVertical} kbd="⇧V" id="TD-ContextMenu-Flip_Vertical">
Flip Vertical Flip Vertical
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleLock} kbd="#⇧L"> <CMRowButton onClick={handleLock} kbd="#⇧L" id="TD-ContextMenu- Lock_Unlock">
Lock / Unlock Lock / Unlock
</CMRowButton> </CMRowButton>
{(hasTwoOrMore || hasGroupSelected) && <Divider />} {(hasTwoOrMore || hasGroupSelected) && <Divider />}
{hasTwoOrMore && ( {hasTwoOrMore && (
<CMRowButton onClick={handleGroup} kbd="#G"> <CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Group">
Group Group
</CMRowButton> </CMRowButton>
)} )}
{hasGroupSelected && ( {hasGroupSelected && (
<CMRowButton onClick={handleGroup} kbd="#G"> <CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Ungroup">
Ungroup Ungroup
</CMRowButton> </CMRowButton>
)} )}
<Divider /> <Divider />
<ContextMenuSubMenu label="Move"> <ContextMenuSubMenu label="Move" id="TD-ContextMenu-Move">
<CMRowButton onClick={handleMoveToFront} kbd="⇧]"> <CMRowButton onClick={handleMoveToFront} kbd="⇧]" id="TD-ContextMenu-Move-To_Front">
To Front To Front
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleMoveForward} kbd="]"> <CMRowButton onClick={handleMoveForward} kbd="]" id="TD-ContextMenu-Move-Forward">
Forward Forward
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleMoveBackward} kbd="["> <CMRowButton onClick={handleMoveBackward} kbd="[" id="TD-ContextMenu-Move-Backward">
Backward Backward
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleMoveToBack} kbd="⇧["> <CMRowButton onClick={handleMoveToBack} kbd="⇧[" id="TD-ContextMenu-Move-To_Back">
To Back To Back
</CMRowButton> </CMRowButton>
</ContextMenuSubMenu> </ContextMenuSubMenu>
@ -211,53 +215,79 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
{app.callbacks.onExport ? ( {app.callbacks.onExport ? (
<> <>
<Divider /> <Divider />
<ContextMenuSubMenu label="Export" size="small"> <ContextMenuSubMenu label="Export" size="small" id="TD-ContextMenu-Export">
<CMRowButton onClick={handleExportPNG}>PNG</CMRowButton> <CMRowButton onClick={handleExportPNG} id="TD-ContextMenu-Export-PNG">
<CMRowButton onClick={handleExportJPG}>JPG</CMRowButton> PNG
<CMRowButton onClick={handleExportWEBP}>WEBP</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleExportSVG}>SVG</CMRowButton> <CMRowButton onClick={handleExportJPG} id="TD-ContextMenu-Export-JPG">
<CMRowButton onClick={handleExportJSON}>JSON</CMRowButton> JPG
</CMRowButton>
<CMRowButton onClick={handleExportWEBP} id="TD-ContextMenu-Export-WEBP">
WEBP
</CMRowButton>
<CMRowButton onClick={handleExportSVG} id="TD-ContextMenu-Export-SVG">
SVG
</CMRowButton>
<CMRowButton onClick={handleExportJSON} id="TD-ContextMenu-Export-JSON">
JSON
</CMRowButton>
<Divider /> <Divider />
<CMRowButton onClick={handleCopySvg} kbd="#⇧C"> <CMRowButton
onClick={handleCopySvg}
kbd="#⇧C"
id="TD-ContextMenu-Export-Copy_as_SVG"
>
Copy as SVG Copy as SVG
</CMRowButton> </CMRowButton>
{isDebugMode && <CMRowButton onClick={handleCopyJson}>Copy as JSON</CMRowButton>} {isDebugMode && (
<CMRowButton onClick={handleCopyJson} id="TD-ContextMenu-Export-Copy_as_JSON">
Copy as JSON
</CMRowButton>
)}
</ContextMenuSubMenu> </ContextMenuSubMenu>
</> </>
) : ( ) : (
<> <>
<Divider /> <Divider />
<CMRowButton onClick={handleCopySvg} kbd="#⇧C"> <CMRowButton
onClick={handleCopySvg}
kbd="#⇧C"
id="TD-ContextMenu-Export-Copy_as_SVG"
>
Copy as SVG Copy as SVG
</CMRowButton> </CMRowButton>
{isDebugMode && <CMRowButton onClick={handleCopyJson}>Copy as JSON</CMRowButton>} {isDebugMode && (
<CMRowButton onClick={handleCopyJson} id="TD-ContextMenu-Export-Copy_as_JSON">
Copy as JSON
</CMRowButton>
)}
</> </>
)} )}
<Divider /> <Divider />
<CMRowButton onClick={handleCut} kbd="#X"> <CMRowButton onClick={handleCut} kbd="#X" id="TD-ContextMenu-Cut">
Cut Cut
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleCopy} kbd="#C"> <CMRowButton onClick={handleCopy} kbd="#C" id="TD-ContextMenu-Copy">
Copy Copy
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handlePaste} kbd="#V"> <CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste">
Paste Paste
</CMRowButton> </CMRowButton>
<Divider /> <Divider />
<CMRowButton onClick={handleDelete} kbd="⌫"> <CMRowButton onClick={handleDelete} kbd="⌫" id="TD-ContextMenu-Delete">
Delete Delete
</CMRowButton> </CMRowButton>
</> </>
) : ( ) : (
<> <>
<CMRowButton onClick={handlePaste} kbd="#V"> <CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste">
Paste Paste
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleUndo} kbd="#Z"> <CMRowButton onClick={handleUndo} kbd="#Z" id="TD-ContextMenu-Undo">
Undo Undo
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleRedo} kbd="#⇧Z"> <CMRowButton onClick={handleRedo} kbd="#⇧Z" id="TD-ContextMenu-Redo">
Redo Redo
</CMRowButton> </CMRowButton>
</> </>
@ -318,41 +348,60 @@ function AlignDistributeSubMenu({
}, [app]) }, [app])
return ( return (
<span id="TD-ContextMenu-Align_Duplicate">
<RadixContextMenu.Root dir="ltr"> <RadixContextMenu.Root dir="ltr">
<CMTriggerButton isSubmenu>Align / Distribute</CMTriggerButton> <CMTriggerButton isSubmenu>Align / Distribute</CMTriggerButton>
<RadixContextMenu.Content asChild sideOffset={2} alignOffset={-2}> <RadixContextMenu.Content asChild sideOffset={2} alignOffset={-2}>
<StyledGridContent numberOfSelected={hasThreeOrMore ? 'threeOrMore' : 'twoOrMore'}> <StyledGridContent numberOfSelected={hasThreeOrMore ? 'threeOrMore' : 'twoOrMore'}>
<CMIconButton onClick={alignLeft}> <CMIconButton onClick={alignLeft} id="TD-ContextMenu-Align_Duplicate-AlignLeft">
<AlignLeftIcon /> <AlignLeftIcon />
</CMIconButton> </CMIconButton>
<CMIconButton onClick={alignCenterHorizontal}> <CMIconButton
onClick={alignCenterHorizontal}
id="TD-ContextMenu-Align_Duplicate-AlignCenterHorizontal"
>
<AlignCenterHorizontallyIcon /> <AlignCenterHorizontallyIcon />
</CMIconButton> </CMIconButton>
<CMIconButton onClick={alignRight}> <CMIconButton onClick={alignRight} id="TD-ContextMenu-Align_Duplicate-AlignRight">
<AlignRightIcon /> <AlignRightIcon />
</CMIconButton> </CMIconButton>
<CMIconButton onClick={stretchHorizontally}> <CMIconButton
onClick={stretchHorizontally}
id="TD-ContextMenu-Align_Duplicate-StretchHorizontal"
>
<StretchHorizontallyIcon /> <StretchHorizontallyIcon />
</CMIconButton> </CMIconButton>
{hasThreeOrMore && ( {hasThreeOrMore && (
<CMIconButton onClick={distributeHorizontally}> <CMIconButton
onClick={distributeHorizontally}
id="TD-ContextMenu-Align_Duplicate-SpaceEvenlyHorizontal"
>
<SpaceEvenlyHorizontallyIcon /> <SpaceEvenlyHorizontallyIcon />
</CMIconButton> </CMIconButton>
)} )}
<CMIconButton onClick={alignTop}> <CMIconButton onClick={alignTop} id="TD-ContextMenu-Align_Duplicate-AlignTop">
<AlignTopIcon /> <AlignTopIcon />
</CMIconButton> </CMIconButton>
<CMIconButton onClick={alignCenterVertical}> <CMIconButton
onClick={alignCenterVertical}
id="TD-ContextMenu-Align_Duplicate-AlignCenterVertical"
>
<AlignCenterVerticallyIcon /> <AlignCenterVerticallyIcon />
</CMIconButton> </CMIconButton>
<CMIconButton onClick={alignBottom}> <CMIconButton onClick={alignBottom} id="TD-ContextMenu-Align_Duplicate-AlignBottom">
<AlignBottomIcon /> <AlignBottomIcon />
</CMIconButton> </CMIconButton>
<CMIconButton onClick={stretchVertically}> <CMIconButton
onClick={stretchVertically}
id="TD-ContextMenu-Align_Duplicate-StretchVertical"
>
<StretchVerticallyIcon /> <StretchVerticallyIcon />
</CMIconButton> </CMIconButton>
{hasThreeOrMore && ( {hasThreeOrMore && (
<CMIconButton onClick={distributeVertically}> <CMIconButton
onClick={distributeVertically}
id="TD-ContextMenu-Align_Duplicate-SpaceEvenlyVertical"
>
<SpaceEvenlyVerticallyIcon /> <SpaceEvenlyVerticallyIcon />
</CMIconButton> </CMIconButton>
)} )}
@ -360,6 +409,7 @@ function AlignDistributeSubMenu({
</StyledGridContent> </StyledGridContent>
</RadixContextMenu.Content> </RadixContextMenu.Content>
</RadixContextMenu.Root> </RadixContextMenu.Root>
</span>
) )
} }
@ -420,14 +470,17 @@ export interface ContextMenuSubMenuProps {
label: string label: string
size?: 'small' size?: 'small'
children: React.ReactNode children: React.ReactNode
id?: string
} }
export function ContextMenuSubMenu({ export function ContextMenuSubMenu({
children, children,
label, label,
size, size,
id,
}: ContextMenuSubMenuProps): JSX.Element { }: ContextMenuSubMenuProps): JSX.Element {
return ( return (
<span id={id}>
<RadixContextMenu.Root dir="ltr"> <RadixContextMenu.Root dir="ltr">
<CMTriggerButton isSubmenu>{label}</CMTriggerButton> <CMTriggerButton isSubmenu>{label}</CMTriggerButton>
<RadixContextMenu.Content dir="ltr" sideOffset={2} alignOffset={-2} asChild> <RadixContextMenu.Content dir="ltr" sideOffset={2} alignOffset={-2} asChild>
@ -437,6 +490,7 @@ export function ContextMenuSubMenu({
</MenuContent> </MenuContent>
</RadixContextMenu.Content> </RadixContextMenu.Content>
</RadixContextMenu.Root> </RadixContextMenu.Root>
</span>
) )
} }
@ -458,9 +512,9 @@ function CMIconButton({ onSelect, ...rest }: ToolButtonProps): JSX.Element {
/* -------------------- RowButton ------------------- */ /* -------------------- RowButton ------------------- */
const CMRowButton = ({ ...rest }: RowButtonProps) => { const CMRowButton = ({ id, ...rest }: RowButtonProps) => {
return ( return (
<RadixContextMenu.ContextMenuItem asChild> <RadixContextMenu.ContextMenuItem asChild id={id}>
<RowButton {...rest} /> <RowButton {...rest} />
</RadixContextMenu.ContextMenuItem> </RadixContextMenu.ContextMenuItem>
) )

View file

@ -10,6 +10,7 @@ interface DMCheckboxItemProps {
children: React.ReactNode children: React.ReactNode
variant?: RowButtonProps['variant'] variant?: RowButtonProps['variant']
kbd?: string kbd?: string
id?: string
} }
export function DMCheckboxItem({ export function DMCheckboxItem({
@ -18,6 +19,7 @@ export function DMCheckboxItem({
variant, variant,
onCheckedChange, onCheckedChange,
kbd, kbd,
id,
children, children,
}: DMCheckboxItemProps): JSX.Element { }: DMCheckboxItemProps): JSX.Element {
return ( return (
@ -28,6 +30,7 @@ export function DMCheckboxItem({
checked={checked} checked={checked}
disabled={disabled} disabled={disabled}
asChild asChild
id={id}
> >
<RowButton kbd={kbd} variant={variant} hasIndicator> <RowButton kbd={kbd} variant={variant} hasIndicator>
{children} {children}

View file

@ -9,6 +9,7 @@ export interface DMContentProps {
align?: 'start' | 'center' | 'end' align?: 'start' | 'center' | 'end'
sideOffset?: number sideOffset?: number
children: React.ReactNode children: React.ReactNode
id?: string
} }
export function DMContent({ export function DMContent({
@ -16,6 +17,7 @@ export function DMContent({
children, children,
align, align,
variant, variant,
id,
}: DMContentProps): JSX.Element { }: DMContentProps): JSX.Element {
return ( return (
<Content <Content
@ -24,6 +26,7 @@ export function DMContent({
sideOffset={sideOffset} sideOffset={sideOffset}
onEscapeKeyDown={stopPropagation} onEscapeKeyDown={stopPropagation}
asChild asChild
id={id}
> >
<StyledContent variant={variant}>{children}</StyledContent> <StyledContent variant={variant}>{children}</StyledContent>
</Content> </Content>

View file

@ -4,10 +4,11 @@ import { RowButton, RowButtonProps } from '~components/Primitives/RowButton'
export function DMItem({ export function DMItem({
onSelect, onSelect,
id,
...rest ...rest
}: RowButtonProps & { onSelect?: (event: Event) => void }): JSX.Element { }: RowButtonProps & { onSelect?: (event: Event) => void; id?: string }): JSX.Element {
return ( return (
<Item dir="ltr" asChild onSelect={onSelect}> <Item dir="ltr" asChild onSelect={onSelect} id={id}>
<RowButton {...rest} /> <RowButton {...rest} />
</Item> </Item>
) )

View file

@ -8,6 +8,7 @@ export interface DMSubMenuProps {
size?: 'small' size?: 'small'
disabled?: boolean disabled?: boolean
children: React.ReactNode children: React.ReactNode
id?: string
} }
export function DMSubMenu({ export function DMSubMenu({
@ -15,8 +16,10 @@ export function DMSubMenu({
size, size,
disabled = false, disabled = false,
label, label,
id,
}: DMSubMenuProps): JSX.Element { }: DMSubMenuProps): JSX.Element {
return ( return (
<span id={id}>
<Root dir="ltr"> <Root dir="ltr">
<TriggerItem dir="ltr" asChild> <TriggerItem dir="ltr" asChild>
<RowButton disabled={disabled} hasArrow> <RowButton disabled={disabled} hasArrow>
@ -30,5 +33,6 @@ export function DMSubMenu({
</MenuContent> </MenuContent>
</Content> </Content>
</Root> </Root>
</span>
) )
} }

View file

@ -4,11 +4,12 @@ import { ToolButton, ToolButtonProps } from '~components/Primitives/ToolButton'
interface DMTriggerIconProps extends ToolButtonProps { interface DMTriggerIconProps extends ToolButtonProps {
children: React.ReactNode children: React.ReactNode
id?: string
} }
export function DMTriggerIcon({ children, ...rest }: DMTriggerIconProps) { export function DMTriggerIcon({ id, children, ...rest }: DMTriggerIconProps) {
return ( return (
<Trigger asChild> <Trigger asChild id={id}>
<ToolButton {...rest}>{children}</ToolButton> <ToolButton {...rest}>{children}</ToolButton>
</Trigger> </Trigger>
) )

View file

@ -17,6 +17,7 @@ export interface RowButtonProps {
isWarning?: boolean isWarning?: boolean
hasIndicator?: boolean hasIndicator?: boolean
hasArrow?: boolean hasArrow?: boolean
id?: string
} }
export const RowButton = React.forwardRef<HTMLButtonElement, RowButtonProps>( export const RowButton = React.forwardRef<HTMLButtonElement, RowButtonProps>(

View file

@ -14,6 +14,7 @@ export interface ToolButtonProps {
isToolLocked?: boolean isToolLocked?: boolean
variant?: 'icon' | 'text' | 'circle' | 'primary' variant?: 'icon' | 'text' | 'circle' | 'primary'
children: React.ReactNode children: React.ReactNode
id?: string
onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement> onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>
} }
@ -30,6 +31,7 @@ export const ToolButton = React.forwardRef<HTMLButtonElement, ToolButtonProps>(
isActive = false, isActive = false,
isSponsor = false, isSponsor = false,
onKeyDown, onKeyDown,
id,
...rest ...rest
}, },
ref ref
@ -46,6 +48,7 @@ export const ToolButton = React.forwardRef<HTMLButtonElement, ToolButtonProps>(
onDoubleClick={onDoubleClick} onDoubleClick={onDoubleClick}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
bp={breakpoints} bp={breakpoints}
id={id}
{...rest} {...rest}
> >
<StyledToolButtonInner>{children}</StyledToolButtonInner> <StyledToolButtonInner>{children}</StyledToolButtonInner>

View file

@ -11,6 +11,7 @@ interface TooltipProps {
children: React.ReactNode children: React.ReactNode
label: string label: string
kbd?: string kbd?: string
id?: string
side?: 'bottom' | 'left' | 'right' | 'top' side?: 'bottom' | 'left' | 'right' | 'top'
} }
@ -18,9 +19,11 @@ export function Tooltip({
children, children,
label, label,
kbd: kbdProp, kbd: kbdProp,
id,
side = 'top', side = 'top',
}: TooltipProps): JSX.Element { }: TooltipProps): JSX.Element {
return ( return (
<span id={id}>
<RadixTooltip.Root> <RadixTooltip.Root>
<RadixTooltip.Trigger dir="ltr" asChild={true}> <RadixTooltip.Trigger dir="ltr" asChild={true}>
<span>{children}</span> <span>{children}</span>
@ -31,6 +34,7 @@ export function Tooltip({
<StyledArrow /> <StyledArrow />
</StyledContent> </StyledContent>
</RadixTooltip.Root> </RadixTooltip.Root>
</span>
) )
} }

View file

@ -180,7 +180,7 @@ export function ActionButton(): JSX.Element {
return ( return (
<DropdownMenu.Root dir="ltr" onOpenChange={handleMenuOpenChange}> <DropdownMenu.Root dir="ltr" onOpenChange={handleMenuOpenChange}>
<DropdownMenu.Trigger dir="ltr" asChild> <DropdownMenu.Trigger dir="ltr" asChild id="TD-Tools-Dots">
<ToolButton variant="circle"> <ToolButton variant="circle">
<DotsHorizontalIcon /> <DotsHorizontalIcon />
</ToolButton> </ToolButton>
@ -189,22 +189,22 @@ export function ActionButton(): JSX.Element {
<> <>
<ButtonsRow> <ButtonsRow>
<ToolButton variant="icon" disabled={!hasSelection} onClick={handleDuplicate}> <ToolButton variant="icon" disabled={!hasSelection} onClick={handleDuplicate}>
<Tooltip label="Duplicate" kbd={`#D`}> <Tooltip label="Duplicate" kbd={`#D`} id="TD-Tools-Copy">
<CopyIcon /> <CopyIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleRotate}> <ToolButton disabled={!hasSelection} onClick={handleRotate}>
<Tooltip label="Rotate"> <Tooltip label="Rotate" id="TD-Tools-Rotate">
<RotateCounterClockwiseIcon /> <RotateCounterClockwiseIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleToggleLocked}> <ToolButton disabled={!hasSelection} onClick={handleToggleLocked}>
<Tooltip label="Toggle Locked" kbd={`#L`}> <Tooltip label="Toggle Locked" kbd={`#L`} id="TD-Tools-Lock">
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />} {isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleToggleAspectRatio}> <ToolButton disabled={!hasSelection} onClick={handleToggleAspectRatio}>
<Tooltip label="Toggle Aspect Ratio Lock"> <Tooltip label="Toggle Aspect Ratio Lock" id="TD-Tools-AspectRatio">
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />} {isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
@ -212,70 +212,94 @@ export function ActionButton(): JSX.Element {
disabled={!hasSelection || (!isAllGrouped && !hasMultipleSelection)} disabled={!hasSelection || (!isAllGrouped && !hasMultipleSelection)}
onClick={handleGroup} onClick={handleGroup}
> >
<Tooltip label="Group" kbd={`#G`}> <Tooltip label="Group" kbd={`#G`} id="TD-Tools-Group">
<GroupIcon /> <GroupIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
</ButtonsRow> </ButtonsRow>
<ButtonsRow> <ButtonsRow>
<ToolButton disabled={!hasSelection} onClick={handleMoveToBack}> <ToolButton disabled={!hasSelection} onClick={handleMoveToBack}>
<Tooltip label="Move to Back" kbd={`#⇧[`}> <Tooltip label="Move to Back" kbd={`#⇧[`} id="TD-Tools-PinBottom">
<PinBottomIcon /> <PinBottomIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleMoveBackward}> <ToolButton disabled={!hasSelection} onClick={handleMoveBackward}>
<Tooltip label="Move Backward" kbd={`#[`}> <Tooltip label="Move Backward" kbd={`#[`} id="TD-Tools-ArrowDown">
<ArrowDownIcon /> <ArrowDownIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleMoveForward}> <ToolButton disabled={!hasSelection} onClick={handleMoveForward}>
<Tooltip label="Move Forward" kbd={`#]`}> <Tooltip label="Move Forward" kbd={`#]`} id="TD-Tools-ArrowUp">
<ArrowUpIcon /> <ArrowUpIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleMoveToFront}> <ToolButton disabled={!hasSelection} onClick={handleMoveToFront}>
<Tooltip label="Move to Front" kbd={`#⇧]`}> <Tooltip label="Move to Front" kbd={`#⇧]`} id="TD-Tools-PinTop">
<PinTopIcon /> <PinTopIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleResetAngle}> <ToolButton disabled={!hasSelection} onClick={handleResetAngle}>
<Tooltip label="Reset Angle"> <Tooltip label="Reset Angle" id="TD-Tools-ResetAngle">
<AngleIcon /> <AngleIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
</ButtonsRow> </ButtonsRow>
<Divider /> <Divider />
<ButtonsRow> <ButtonsRow>
<ToolButton disabled={!hasTwoOrMore} onClick={alignLeft}> <ToolButton disabled={!hasTwoOrMore} onClick={alignLeft} id="TD-Tools-AlignLeft">
<AlignLeftIcon /> <AlignLeftIcon />
</ToolButton> </ToolButton>
<ToolButton disabled={!hasTwoOrMore} onClick={alignCenterHorizontal}> <ToolButton
disabled={!hasTwoOrMore}
onClick={alignCenterHorizontal}
id="TD-Tools-AlignCenterHorizontal"
>
<AlignCenterHorizontallyIcon /> <AlignCenterHorizontallyIcon />
</ToolButton> </ToolButton>
<ToolButton disabled={!hasTwoOrMore} onClick={alignRight}> <ToolButton disabled={!hasTwoOrMore} onClick={alignRight} id="TD-Tools-AlignRight">
<AlignRightIcon /> <AlignRightIcon />
</ToolButton> </ToolButton>
<ToolButton disabled={!hasTwoOrMore} onClick={stretchHorizontally}> <ToolButton
disabled={!hasTwoOrMore}
onClick={stretchHorizontally}
id="TD-Tools-StretchHorizontal"
>
<StretchHorizontallyIcon /> <StretchHorizontallyIcon />
</ToolButton> </ToolButton>
<ToolButton disabled={!hasThreeOrMore} onClick={distributeHorizontally}> <ToolButton
disabled={!hasThreeOrMore}
onClick={distributeHorizontally}
id="TD-Tools-SpaceEvenlyHorizontal"
>
<SpaceEvenlyHorizontallyIcon /> <SpaceEvenlyHorizontallyIcon />
</ToolButton> </ToolButton>
</ButtonsRow> </ButtonsRow>
<ButtonsRow> <ButtonsRow>
<ToolButton disabled={!hasTwoOrMore} onClick={alignTop}> <ToolButton disabled={!hasTwoOrMore} onClick={alignTop} id="TD-Tools-AlignTop">
<AlignTopIcon /> <AlignTopIcon />
</ToolButton> </ToolButton>
<ToolButton disabled={!hasTwoOrMore} onClick={alignCenterVertical}> <ToolButton
disabled={!hasTwoOrMore}
onClick={alignCenterVertical}
id="TD-Tools-AlignCenterVertical"
>
<AlignCenterVerticallyIcon /> <AlignCenterVerticallyIcon />
</ToolButton> </ToolButton>
<ToolButton disabled={!hasTwoOrMore} onClick={alignBottom}> <ToolButton disabled={!hasTwoOrMore} onClick={alignBottom} id="TD-Tools-AlignBottom">
<AlignBottomIcon /> <AlignBottomIcon />
</ToolButton> </ToolButton>
<ToolButton disabled={!hasTwoOrMore} onClick={stretchVertically}> <ToolButton
disabled={!hasTwoOrMore}
onClick={stretchVertically}
id="TD-Tools-tretchVertical"
>
<StretchVerticallyIcon /> <StretchVerticallyIcon />
</ToolButton> </ToolButton>
<ToolButton disabled={!hasThreeOrMore} onClick={distributeVertically}> <ToolButton
disabled={!hasThreeOrMore}
onClick={distributeVertically}
id="TD-Tools-SpaceEvenlyVertical"
>
<SpaceEvenlyVerticallyIcon /> <SpaceEvenlyVerticallyIcon />
</ToolButton> </ToolButton>
</ButtonsRow> </ButtonsRow>

View file

@ -17,7 +17,7 @@ export const BackToContent = React.memo(function BackToContent() {
if (!isEmptyCanvas) return null if (!isEmptyCanvas) return null
return ( return (
<BackToContentContainer> <BackToContentContainer id="TD-Tools-Back_to_content">
<RowButton onClick={app.zoomToContent}>Back to content</RowButton> <RowButton onClick={app.zoomToContent}>Back to content</RowButton>
</BackToContentContainer> </BackToContentContainer>
) )

View file

@ -18,7 +18,7 @@ export function DeleteButton(): JSX.Element {
) )
return ( return (
<Tooltip label="Delete" kbd="⌫"> <Tooltip label="Delete" kbd="⌫" id="TD-Delete">
<ToolButton variant="circle" disabled={!hasSelection} onSelect={handleDelete}> <ToolButton variant="circle" disabled={!hasSelection} onSelect={handleDelete}>
<TrashIcon /> <TrashIcon />
</ToolButton> </ToolButton>

View file

@ -13,7 +13,7 @@ export function LockButton(): JSX.Element {
const isToolLocked = app.useStore(isToolLockedSelector) const isToolLocked = app.useStore(isToolLockedSelector)
return ( return (
<Tooltip label="Lock Tool" kbd="7"> <Tooltip label="Lock Tool" kbd="7" id="TD-Lock">
<ToolButton variant="circle" isActive={isToolLocked} onSelect={app.toggleToolLock}> <ToolButton variant="circle" isActive={isToolLocked} onSelect={app.toggleToolLock}>
{isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />} {isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
</ToolButton> </ToolButton>

View file

@ -44,6 +44,7 @@ export const PenMenu = React.memo(function PenMenu({ activeTool }: ShapesMenuPro
onDoubleClick={handleDoubleClick} onDoubleClick={handleDoubleClick}
onClick={selectShapeTool} onClick={selectShapeTool}
isActive={penShapes.includes(activeTool as PenShape)} isActive={penShapes.includes(activeTool as PenShape)}
id="TD-Pen"
> >
{penShapeIcons[lastActiveTool]} {penShapeIcons[lastActiveTool]}
</ToolButton> </ToolButton>
@ -55,6 +56,7 @@ export const PenMenu = React.memo(function PenMenu({ activeTool }: ShapesMenuPro
key={shape} key={shape}
label={shape[0].toUpperCase() + shape.slice(1)} label={shape[0].toUpperCase() + shape.slice(1)}
kbd={(1 + i).toString()} kbd={(1 + i).toString()}
id={`TD-Pen-${shape}`}
> >
<DropdownMenu.Item asChild> <DropdownMenu.Item asChild>
<ToolButton <ToolButton

View file

@ -48,12 +48,13 @@ export const PrimaryTools = React.memo(function PrimaryTools(): JSX.Element {
}, [app]) }, [app])
return ( return (
<Panel side="center"> <Panel side="center" id="TD-PrimaryTools">
<ToolButtonWithTooltip <ToolButtonWithTooltip
kbd={'1'} kbd={'1'}
label={'select'} label={'select'}
onClick={selectSelectTool} onClick={selectSelectTool}
isActive={activeTool === 'select'} isActive={activeTool === 'select'}
id="TD-PrimaryTools-CursorArrow"
> >
<CursorArrowIcon /> <CursorArrowIcon />
</ToolButtonWithTooltip> </ToolButtonWithTooltip>
@ -62,6 +63,7 @@ export const PrimaryTools = React.memo(function PrimaryTools(): JSX.Element {
label={TDShapeType.Draw} label={TDShapeType.Draw}
onClick={selectDrawTool} onClick={selectDrawTool}
isActive={activeTool === TDShapeType.Draw} isActive={activeTool === TDShapeType.Draw}
id="TD-PrimaryTools-Pencil"
> >
<Pencil1Icon /> <Pencil1Icon />
</ToolButtonWithTooltip> </ToolButtonWithTooltip>
@ -70,6 +72,7 @@ export const PrimaryTools = React.memo(function PrimaryTools(): JSX.Element {
label={'eraser'} label={'eraser'}
onClick={selectEraseTool} onClick={selectEraseTool}
isActive={activeTool === 'erase'} isActive={activeTool === 'erase'}
id="TD-PrimaryTools-Eraser"
> >
<EraserIcon /> <EraserIcon />
</ToolButtonWithTooltip> </ToolButtonWithTooltip>
@ -80,6 +83,7 @@ export const PrimaryTools = React.memo(function PrimaryTools(): JSX.Element {
onClick={selectArrowTool} onClick={selectArrowTool}
isLocked={isToolLocked} isLocked={isToolLocked}
isActive={activeTool === TDShapeType.Arrow} isActive={activeTool === TDShapeType.Arrow}
id="TD-PrimaryTools-ArrowTopRight"
> >
<ArrowTopRightIcon /> <ArrowTopRightIcon />
</ToolButtonWithTooltip> </ToolButtonWithTooltip>
@ -89,6 +93,7 @@ export const PrimaryTools = React.memo(function PrimaryTools(): JSX.Element {
onClick={selectTextTool} onClick={selectTextTool}
isLocked={isToolLocked} isLocked={isToolLocked}
isActive={activeTool === TDShapeType.Text} isActive={activeTool === TDShapeType.Text}
id="TD-PrimaryTools-Text"
> >
<TextIcon /> <TextIcon />
</ToolButtonWithTooltip> </ToolButtonWithTooltip>
@ -97,6 +102,7 @@ export const PrimaryTools = React.memo(function PrimaryTools(): JSX.Element {
label={TDShapeType.Sticky} label={TDShapeType.Sticky}
onClick={selectStickyTool} onClick={selectStickyTool}
isActive={activeTool === TDShapeType.Sticky} isActive={activeTool === TDShapeType.Sticky}
id="TD-PrimaryTools-Pencil2"
> >
<Pencil2Icon /> <Pencil2Icon />
</ToolButtonWithTooltip> </ToolButtonWithTooltip>

View file

@ -75,7 +75,7 @@ export const ShapesMenu = React.memo(function ShapesMenu({
return ( return (
<DropdownMenu.Root dir="ltr" onOpenChange={selectShapeTool}> <DropdownMenu.Root dir="ltr" onOpenChange={selectShapeTool}>
<DropdownMenu.Trigger dir="ltr" asChild> <DropdownMenu.Trigger dir="ltr" asChild id="TD-PrimaryTools-Shapes">
<ToolButton <ToolButton
disabled={isActive && app.shiftKey} // otherwise this continuously opens and closes on "SpacePanning" disabled={isActive && app.shiftKey} // otherwise this continuously opens and closes on "SpacePanning"
variant="primary" variant="primary"
@ -94,6 +94,7 @@ export const ShapesMenu = React.memo(function ShapesMenu({
key={shape} key={shape}
label={shape[0].toUpperCase() + shape.slice(1)} label={shape[0].toUpperCase() + shape.slice(1)}
kbd={(4 + i).toString()} kbd={(4 + i).toString()}
id={`TD-PrimaryTools-Shapes-${shape}`}
> >
<DropdownMenu.Item asChild> <DropdownMenu.Item asChild>
<ToolButton <ToolButton

View file

@ -13,7 +13,7 @@ export function StatusBar(): JSX.Element | null {
const activeTool = app.useStore(activeToolSelector) const activeTool = app.useStore(activeToolSelector)
return ( return (
<StyledStatusBar bp={breakpoints}> <StyledStatusBar bp={breakpoints} id="TD-StatusBar">
<StyledSection> <StyledSection>
{activeTool} | {status} {activeTool} | {status}
</StyledSection> </StyledSection>

View file

@ -20,7 +20,7 @@ export const ToolsPanel = React.memo(function ToolsPanel({ onBlur }: ToolsPanelP
return ( return (
<StyledToolsPanelContainer onBlur={onBlur}> <StyledToolsPanelContainer onBlur={onBlur}>
<StyledCenterWrap> <StyledCenterWrap id="TD-Tools">
<BackToContent /> <BackToContent />
<StyledPrimaryTools> <StyledPrimaryTools>
<ActionButton /> <ActionButton />

View file

@ -121,48 +121,58 @@ export const Menu = React.memo(function Menu({ showSponsorLink, readOnly }: Menu
return ( return (
<DropdownMenu.Root dir="ltr"> <DropdownMenu.Root dir="ltr">
<DMTriggerIcon isSponsor={showSponsorLink}> <DMTriggerIcon isSponsor={showSponsorLink} id="TD-MenuIcon">
<HamburgerMenuIcon /> <HamburgerMenuIcon />
</DMTriggerIcon> </DMTriggerIcon>
<DMContent variant="menu"> <DMContent variant="menu" id="TD-Menu">
{showFileMenu && ( {showFileMenu && (
<DMSubMenu label="File..."> <DMSubMenu label="File..." id="TD-MenuItem-File">
{app.callbacks.onNewProject && ( {app.callbacks.onNewProject && (
<DMItem onClick={onNewProject} kbd="#N"> <DMItem onClick={onNewProject} kbd="#N" id="TD-MenuItem-File-New_Project">
New Project New Project
</DMItem> </DMItem>
)} )}
{app.callbacks.onOpenProject && ( {app.callbacks.onOpenProject && (
<DMItem onClick={onOpenProject} kbd="#O"> <DMItem onClick={onOpenProject} kbd="#O" id="TD-MenuItem-File-Open">
Open... Open...
</DMItem> </DMItem>
)} )}
{app.callbacks.onSaveProject && ( {app.callbacks.onSaveProject && (
<DMItem onClick={onSaveProject} kbd="#S"> <DMItem onClick={onSaveProject} kbd="#S" id="TD-MenuItem-File-Save">
Save Save
</DMItem> </DMItem>
)} )}
{app.callbacks.onSaveProjectAs && ( {app.callbacks.onSaveProjectAs && (
<DMItem onClick={onSaveProjectAs} kbd="#⇧S"> <DMItem onClick={onSaveProjectAs} kbd="#⇧S" id="TD-MenuItem-File-Save_As">
Save As... Save As...
</DMItem> </DMItem>
)} )}
{app.callbacks.onExport && ( {app.callbacks.onExport && (
<> <>
<Divider /> <Divider />
<DMSubMenu label="Export" size="small"> <DMSubMenu label="Export" size="small" id="TD-MenuItem-File-Export">
<DMItem onClick={handleExportPNG}>PNG</DMItem> <DMItem onClick={handleExportPNG} id="TD-MenuItem-File-Export-PNG">
<DMItem onClick={handleExportJPG}>JPG</DMItem> PNG
<DMItem onClick={handleExportWEBP}>WEBP</DMItem> </DMItem>
<DMItem onClick={handleExportSVG}>SVG</DMItem> <DMItem onClick={handleExportJPG} id="TD-MenuItem-File-Export-JPG">
<DMItem onClick={handleExportJSON}>JSON</DMItem> JPG
</DMItem>
<DMItem onClick={handleExportWEBP} id="TD-MenuItem-File-Export-WEBP">
WEBP
</DMItem>
<DMItem onClick={handleExportSVG} id="TD-MenuItem-File-Export-SVG">
SVG
</DMItem>
<DMItem onClick={handleExportJSON} id="TD-MenuItem-File-Export-JSON">
JSON
</DMItem>
</DMSubMenu> </DMSubMenu>
</> </>
)} )}
{!disableAssets && ( {!disableAssets && (
<> <>
<Divider /> <Divider />
<DMItem onClick={handleUploadMedia} kbd="#U"> <DMItem onClick={handleUploadMedia} kbd="#U" id="TD-MenuItem-File-Upload_Media">
Upload Media Upload Media
</DMItem> </DMItem>
</> </>
@ -171,15 +181,31 @@ export const Menu = React.memo(function Menu({ showSponsorLink, readOnly }: Menu
)} )}
{!readOnly && ( {!readOnly && (
<> <>
<DMSubMenu label="Edit..."> <DMSubMenu label="Edit..." id="TD-MenuItem-Edit">
<DMItem onSelect={preventEvent} onClick={app.undo} kbd="#Z"> <DMItem
onSelect={preventEvent}
onClick={app.undo}
kbd="#Z"
id="TD-MenuItem-Edit-Undo"
>
Undo Undo
</DMItem> </DMItem>
<DMItem onSelect={preventEvent} onClick={app.redo} kbd="#⇧Z"> <DMItem
onSelect={preventEvent}
onClick={app.redo}
kbd="#⇧Z"
id="TD-MenuItem-Edit-Redo"
>
Redo Redo
</DMItem> </DMItem>
<DMDivider dir="ltr" /> <DMDivider dir="ltr" />
<DMItem onSelect={preventEvent} disabled={!hasSelection} onClick={handleCut} kbd="#X"> <DMItem
onSelect={preventEvent}
disabled={!hasSelection}
onClick={handleCut}
kbd="#X"
id="TD-MenuItem-Edit-Cut"
>
Cut Cut
</DMItem> </DMItem>
<DMItem <DMItem
@ -187,10 +213,16 @@ export const Menu = React.memo(function Menu({ showSponsorLink, readOnly }: Menu
disabled={!hasSelection} disabled={!hasSelection}
onClick={handleCopy} onClick={handleCopy}
kbd="#C" kbd="#C"
id="TD-MenuItem-Edit-Copy"
> >
Copy Copy
</DMItem> </DMItem>
<DMItem onSelect={preventEvent} onClick={handlePaste} kbd="#V"> <DMItem
onSelect={preventEvent}
onClick={handlePaste}
kbd="#V"
id="TD-MenuItem-Edit-Paste"
>
Paste Paste
</DMItem> </DMItem>
<DMDivider dir="ltr" /> <DMDivider dir="ltr" />
@ -199,30 +231,45 @@ export const Menu = React.memo(function Menu({ showSponsorLink, readOnly }: Menu
disabled={!hasSelection} disabled={!hasSelection}
onClick={handleCopySvg} onClick={handleCopySvg}
kbd="#⇧C" kbd="#⇧C"
id="TD-MenuItem-Edit-Copy_as_SVG"
> >
Copy as SVG Copy as SVG
</DMItem> </DMItem>
<DMItem onSelect={preventEvent} disabled={!hasSelection} onClick={handleCopyJson}> <DMItem
onSelect={preventEvent}
disabled={!hasSelection}
onClick={handleCopyJson}
id="TD-MenuItem-Edit-Copy_as_JSON"
>
Copy as JSON Copy as JSON
</DMItem> </DMItem>
<DMDivider dir="ltr" /> <DMDivider dir="ltr" />
<DMItem onSelect={preventEvent} onClick={handleSelectAll} kbd="#A"> <DMItem
onSelect={preventEvent}
onClick={handleSelectAll}
kbd="#A"
id="TD-MenuItem-Select_All"
>
Select All Select All
</DMItem> </DMItem>
<DMItem onSelect={preventEvent} onClick={handleSelectNone}> <DMItem
onSelect={preventEvent}
onClick={handleSelectNone}
id="TD-MenuItem-Select_None"
>
Select None Select None
</DMItem> </DMItem>
</DMSubMenu> </DMSubMenu>
</> </>
)} )}
<a href="https://tldraw.com/r"> <a href="https://tldraw.com/r">
<DMItem>Create a Multiplayer Room</DMItem> <DMItem id="TD-MenuItem-Create_a_Multiplayer_Room">Create a Multiplayer Room</DMItem>
</a> </a>
<DMDivider dir="ltr" /> <DMDivider dir="ltr" />
<PreferencesMenu /> <PreferencesMenu />
<DMDivider dir="ltr" /> <DMDivider dir="ltr" />
<a href="https://github.com/Tldraw/Tldraw" target="_blank" rel="nofollow"> <a href="https://github.com/Tldraw/Tldraw" target="_blank" rel="nofollow">
<DMItem> <DMItem id="TD-MenuItem-Github">
GitHub GitHub
<SmallIcon> <SmallIcon>
<GitHubLogoIcon /> <GitHubLogoIcon />
@ -230,7 +277,7 @@ export const Menu = React.memo(function Menu({ showSponsorLink, readOnly }: Menu
</DMItem> </DMItem>
</a> </a>
<a href="https://twitter.com/Tldraw" target="_blank" rel="nofollow"> <a href="https://twitter.com/Tldraw" target="_blank" rel="nofollow">
<DMItem> <DMItem id="TD-MenuItem-Twitter">
Twitter Twitter
<SmallIcon> <SmallIcon>
<TwitterLogoIcon /> <TwitterLogoIcon />
@ -238,7 +285,7 @@ export const Menu = React.memo(function Menu({ showSponsorLink, readOnly }: Menu
</DMItem> </DMItem>
</a> </a>
<a href="https://discord.gg/SBBEVCA4PG" target="_blank" rel="nofollow"> <a href="https://discord.gg/SBBEVCA4PG" target="_blank" rel="nofollow">
<DMItem> <DMItem id="TD-MenuItem-Discord">
Discord Discord
<SmallIcon> <SmallIcon>
<DiscordIcon /> <DiscordIcon />
@ -247,7 +294,7 @@ export const Menu = React.memo(function Menu({ showSponsorLink, readOnly }: Menu
</a> </a>
{showSponsorLink && ( {showSponsorLink && (
<a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow"> <a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow">
<DMItem isSponsor> <DMItem isSponsor id="TD-MenuItem-Become_a_Sponsor">
Become a Sponsor{' '} Become a Sponsor{' '}
<SmallIcon> <SmallIcon>
<HeartIcon /> <HeartIcon />
@ -258,9 +305,13 @@ export const Menu = React.memo(function Menu({ showSponsorLink, readOnly }: Menu
{showSignInOutMenu && ( {showSignInOutMenu && (
<> <>
<DMDivider dir="ltr" />{' '} <DMDivider dir="ltr" />{' '}
{app.callbacks.onSignIn && <DMItem onSelect={handleSignIn}>Sign In</DMItem>} {app.callbacks.onSignIn && (
<DMItem onSelect={handleSignIn} id="TD-MenuItem-Sign_in">
Sign In
</DMItem>
)}
{app.callbacks.onSignOut && ( {app.callbacks.onSignOut && (
<DMItem onSelect={handleSignOut}> <DMItem onSelect={handleSignOut} id="TD-MenuItem-Sign_out">
Sign Out Sign Out
<SmallIcon> <SmallIcon>
<ExitIcon /> <ExitIcon />

View file

@ -47,7 +47,7 @@ export function PageMenu(): JSX.Element {
return ( return (
<DropdownMenu.Root dir="ltr" open={isOpen} onOpenChange={handleOpenChange}> <DropdownMenu.Root dir="ltr" open={isOpen} onOpenChange={handleOpenChange}>
<DropdownMenu.Trigger dir="ltr" asChild> <DropdownMenu.Trigger dir="ltr" asChild id="TD-Page">
<ToolButton variant="text">{currentPageName || 'Page'}</ToolButton> <ToolButton variant="text">{currentPageName || 'Page'}</ToolButton>
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DMContent variant="menu" align="start"> <DMContent variant="menu" align="start">

View file

@ -43,37 +43,65 @@ export function PreferencesMenu() {
}, [app]) }, [app])
return ( return (
<DMSubMenu label="Preferences"> <DMSubMenu label="Preferences" id="TD-MenuItem-Preferences">
<DMCheckboxItem checked={settings.isDarkMode} onCheckedChange={toggleDarkMode} kbd="#⇧D"> <DMCheckboxItem
checked={settings.isDarkMode}
onCheckedChange={toggleDarkMode}
kbd="#⇧D"
id="TD-MenuItem-Preferences-Dark_Mode"
>
Dark Mode Dark Mode
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem checked={settings.isFocusMode} onCheckedChange={toggleFocusMode} kbd="#."> <DMCheckboxItem
checked={settings.isFocusMode}
onCheckedChange={toggleFocusMode}
kbd="#."
id="TD-MenuItem-Preferences-Focus_Mode"
>
Focus Mode Focus Mode
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem checked={settings.isDebugMode} onCheckedChange={toggleDebugMode}> <DMCheckboxItem
checked={settings.isDebugMode}
onCheckedChange={toggleDebugMode}
id="TD-MenuItem-Preferences-Debug_Mode"
>
Debug Mode Debug Mode
</DMCheckboxItem> </DMCheckboxItem>
<DMDivider /> <DMDivider />
<DMCheckboxItem checked={settings.showRotateHandles} onCheckedChange={toggleRotateHandle}> <DMCheckboxItem
checked={settings.showRotateHandles}
onCheckedChange={toggleRotateHandle}
id="TD-MenuItem-Preferences-Rotate_Handles"
>
Rotate Handles Rotate Handles
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem <DMCheckboxItem
checked={settings.showBindingHandles} checked={settings.showBindingHandles}
onCheckedChange={toggleBoundShapesHandle} onCheckedChange={toggleBoundShapesHandle}
id="TD-MenuItem-Preferences-Binding_Handles"
> >
Binding Handles Binding Handles
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem checked={settings.showCloneHandles} onCheckedChange={toggleCloneControls}> <DMCheckboxItem
checked={settings.showCloneHandles}
onCheckedChange={toggleCloneControls}
id="TD-MenuItem-Preferences-Clone_Handles"
>
Clone Handles Clone Handles
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem <DMCheckboxItem
checked={settings.showGrid} checked={settings.showGrid}
onCheckedChange={toggleGrid} onCheckedChange={toggleGrid}
kbd="#⇧G" kbd="#⇧G"
id="TD-MenuItem-Preferences-Grid"
> >
Grid Grid
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem checked={settings.isSnapping} onCheckedChange={toggleisSnapping}> <DMCheckboxItem
checked={settings.isSnapping}
onCheckedChange={toggleisSnapping}
id="TD-MenuItem-Preferences-Always_Show_Snaps"
>
Always Show Snaps Always Show Snaps
</DMCheckboxItem> </DMCheckboxItem>
</DMSubMenu> </DMSubMenu>

View file

@ -167,7 +167,7 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
) )
return ( return (
<DropdownMenu.Root dir="ltr" onOpenChange={handleMenuOpenChange}> <DropdownMenu.Root dir="ltr" onOpenChange={handleMenuOpenChange}>
<DropdownMenu.Trigger asChild> <DropdownMenu.Trigger asChild id="TD-Styles">
<ToolButton variant="text"> <ToolButton variant="text">
Styles Styles
<OverlapIcons <OverlapIcons
@ -187,11 +187,16 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
</ToolButton> </ToolButton>
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DMContent> <DMContent>
<StyledRow variant="tall"> <StyledRow variant="tall" id="TD-Styles-Color-Container">
<span>Color</span> <span>Color</span>
<ColorGrid> <ColorGrid>
{Object.keys(strokes.light).map((style: string) => ( {Object.keys(strokes.light).map((style: string) => (
<DropdownMenu.Item key={style} onSelect={preventEvent} asChild> <DropdownMenu.Item
key={style}
onSelect={preventEvent}
asChild
id={`TD-Styles-Color-Swatch-${style}`}
>
<ToolButton <ToolButton
variant="icon" variant="icon"
isActive={displayedStyle.color === style} isActive={displayedStyle.color === style}
@ -214,10 +219,11 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
variant="styleMenu" variant="styleMenu"
checked={!!displayedStyle.isFilled} checked={!!displayedStyle.isFilled}
onCheckedChange={handleToggleFilled} onCheckedChange={handleToggleFilled}
id="TD-Styles-Fill"
> >
Fill Fill
</DMCheckboxItem> </DMCheckboxItem>
<StyledRow> <StyledRow id="TD-Styles-Dash-Container">
Dash Dash
<StyledGroup dir="ltr" value={displayedStyle.dash} onValueChange={handleDashChange}> <StyledGroup dir="ltr" value={displayedStyle.dash} onValueChange={handleDashChange}>
{Object.values(DashStyle).map((style) => ( {Object.values(DashStyle).map((style) => (
@ -227,13 +233,14 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
value={style} value={style}
onSelect={preventEvent} onSelect={preventEvent}
bp={breakpoints} bp={breakpoints}
id={`TD-Styles-Dash-${style}`}
> >
{DASH_ICONS[style as DashStyle]} {DASH_ICONS[style as DashStyle]}
</DMRadioItem> </DMRadioItem>
))} ))}
</StyledGroup> </StyledGroup>
</StyledRow> </StyledRow>
<StyledRow> <StyledRow id="TD-Styles-Size-Container">
Size Size
<StyledGroup dir="ltr" value={displayedStyle.size} onValueChange={handleSizeChange}> <StyledGroup dir="ltr" value={displayedStyle.size} onValueChange={handleSizeChange}>
{Object.values(SizeStyle).map((sizeStyle) => ( {Object.values(SizeStyle).map((sizeStyle) => (
@ -243,6 +250,7 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
value={sizeStyle} value={sizeStyle}
onSelect={preventEvent} onSelect={preventEvent}
bp={breakpoints} bp={breakpoints}
id={`TD-Styles-Dash-${sizeStyle}`}
> >
{SIZE_ICONS[sizeStyle as SizeStyle]} {SIZE_ICONS[sizeStyle as SizeStyle]}
</DMRadioItem> </DMRadioItem>
@ -252,7 +260,7 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
{(options === 'text' || options === 'label') && ( {(options === 'text' || options === 'label') && (
<> <>
<Divider /> <Divider />
<StyledRow> <StyledRow id="TD-Styles-Font-Container">
Font Font
<StyledGroup dir="ltr" value={displayedStyle.font} onValueChange={handleFontChange}> <StyledGroup dir="ltr" value={displayedStyle.font} onValueChange={handleFontChange}>
{Object.values(FontStyle).map((fontStyle) => ( {Object.values(FontStyle).map((fontStyle) => (
@ -262,6 +270,7 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
value={fontStyle} value={fontStyle}
onSelect={preventEvent} onSelect={preventEvent}
bp={breakpoints} bp={breakpoints}
id={`TD-Styles-Font-${fontStyle}`}
> >
<FontIcon fontStyle={fontStyle}>Aa</FontIcon> <FontIcon fontStyle={fontStyle}>Aa</FontIcon>
</DMRadioItem> </DMRadioItem>
@ -269,7 +278,7 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
</StyledGroup> </StyledGroup>
</StyledRow> </StyledRow>
{options === 'text' && ( {options === 'text' && (
<StyledRow> <StyledRow id="TD-Styles-Align-Container">
Align Align
<StyledGroup <StyledGroup
dir="ltr" dir="ltr"
@ -283,6 +292,7 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
value={style} value={style}
onSelect={preventEvent} onSelect={preventEvent}
bp={breakpoints} bp={breakpoints}
id={`TD-Styles-Align-${style}`}
> >
{ALIGN_ICONS[style]} {ALIGN_ICONS[style]}
</DMRadioItem> </DMRadioItem>

View file

@ -32,7 +32,7 @@ export function TopPanel({
return ( return (
<StyledTopPanel> <StyledTopPanel>
{(showMenu || showPages) && ( {(showMenu || showPages) && (
<Panel side="left"> <Panel side="left" id="TD-MenuPanel">
{showMenu && <Menu showSponsorLink={showSponsorLink} readOnly={readOnly} />} {showMenu && <Menu showSponsorLink={showSponsorLink} readOnly={readOnly} />}
{showPages && <PageMenu />} {showPages && <PageMenu />}
</Panel> </Panel>

View file

@ -16,25 +16,30 @@ export const ZoomMenu = React.memo(function ZoomMenu() {
return ( return (
<DropdownMenu.Root dir="ltr"> <DropdownMenu.Root dir="ltr">
<DropdownMenu.Trigger dir="ltr" asChild> <DropdownMenu.Trigger dir="ltr" asChild id="TD-Zoom">
<FixedWidthToolButton onDoubleClick={app.resetZoom} variant="text"> <FixedWidthToolButton onDoubleClick={app.resetZoom} variant="text">
{Math.round(zoom * 100)}% {Math.round(zoom * 100)}%
</FixedWidthToolButton> </FixedWidthToolButton>
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DMContent align="end"> <DMContent align="end">
<DMItem onSelect={preventEvent} onClick={app.zoomIn} kbd="#+"> <DMItem onSelect={preventEvent} onClick={app.zoomIn} kbd="#+" id="TD-Zoom-Zoom_In">
Zoom In Zoom In
</DMItem> </DMItem>
<DMItem onSelect={preventEvent} onClick={app.zoomOut} kbd="#"> <DMItem onSelect={preventEvent} onClick={app.zoomOut} kbd="#" id="TD-Zoom-Zoom_Out">
Zoom Out Zoom Out
</DMItem> </DMItem>
<DMItem onSelect={preventEvent} onClick={app.resetZoom} kbd="⇧0"> <DMItem onSelect={preventEvent} onClick={app.resetZoom} kbd="⇧0" id="TD-Zoom-Zoom_To_100%">
To 100% To 100%
</DMItem> </DMItem>
<DMItem onSelect={preventEvent} onClick={app.zoomToFit} kbd="⇧1"> <DMItem onSelect={preventEvent} onClick={app.zoomToFit} kbd="⇧1" id="TD-Zoom-To_Fit">
To Fit To Fit
</DMItem> </DMItem>
<DMItem onSelect={preventEvent} onClick={app.zoomToSelection} kbd="⇧2"> <DMItem
onSelect={preventEvent}
onClick={app.zoomToSelection}
kbd="⇧2"
id="TD-Zoom-To_Selection"
>
To Selection To Selection
</DMItem> </DMItem>
</DMContent> </DMContent>