UI components round two (#2847)

This PR:
- replaces the `shareZone` prop with `SharePanel` component
- replaces the `topZone` prop with `TopPanel` components
- replaces the `Button` component with `TldrawUiButton` and
subcomponents
- adds `TldrawUi` prefix to our primitives
- fixes a couple of bugs with the components

### Change Type

- [x] `major` — Breaking change
pull/2850/head^2
Steve Ruiz 2024-02-16 09:13:04 +00:00 zatwierdzone przez GitHub
rodzic 5d87804a76
commit 7ece89a357
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
83 zmienionych plików z 3122 dodań i 2826 usunięć

Wyświetl plik

@ -67,6 +67,13 @@ const components: TLComponents = {
</DefaultDebugMenu>
)
},
SharePanel: () => {
return (
<div className="tlui-share-zone" draggable={false}>
<ShareMenu />
</div>
)
},
}
export function LocalEditor() {
@ -88,11 +95,6 @@ export function LocalEditor() {
overrides={[sharingUiOverrides, fileSystemUiOverrides]}
onUiEvent={handleUiEvent}
components={components}
shareZone={
<div className="tlui-share-zone" draggable={false}>
<ShareMenu />
</div>
}
inferDarkMode
>
<LocalMigration />

Wyświetl plik

@ -13,8 +13,10 @@ import {
Tldraw,
TldrawUiMenuGroup,
TldrawUiMenuItem,
atom,
lns,
useActions,
useValue,
} from '@tldraw/tldraw'
import { useCallback, useEffect } from 'react'
import { useRemoteSyncClient } from '../hooks/useRemoteSyncClient'
@ -39,6 +41,8 @@ import { SneakyOnDropOverride } from './SneakyOnDropOverride'
import { StoreErrorScreen } from './StoreErrorScreen'
import { ThemeUpdater } from './ThemeUpdater/ThemeUpdater'
const shittyOfflineAtom = atom('shitty offline atom', false)
const components: TLComponents = {
ErrorFallback: ({ error }) => {
throw error
@ -78,6 +82,19 @@ const components: TLComponents = {
</DefaultKeyboardShortcutsDialog>
)
},
TopPanel: () => {
const isOffline = useValue('offline', () => shittyOfflineAtom.get(), [])
if (!isOffline) return null
return <OfflineIndicator />
},
SharePanel: () => {
return (
<div className="tlui-share-zone" draggable={false}>
<PeopleMenu />
<ShareMenu />
</div>
)
},
}
export function MultiplayerEditor({
@ -96,6 +113,12 @@ export function MultiplayerEditor({
roomId,
})
const isOffline =
storeWithStatus.status === 'synced-remote' && storeWithStatus.connectionStatus === 'offline'
useEffect(() => {
shittyOfflineAtom.set(isOffline)
}, [isOffline])
const isEmbedded = useIsEmbedded(roomSlug)
const sharingUiOverrides = useSharing()
const fileSystemUiOverrides = useFileSystem({ isMultiplayer: true })
@ -118,9 +141,6 @@ export function MultiplayerEditor({
return <EmbeddedInIFrameWarning />
}
const isOffline =
storeWithStatus.status === 'synced-remote' && storeWithStatus.connectionStatus === 'offline'
return (
<div className="tldraw__editor">
<Tldraw
@ -131,13 +151,6 @@ export function MultiplayerEditor({
initialState={isReadOnly ? 'hand' : 'select'}
onUiEvent={handleUiEvent}
components={components}
topZone={isOffline && <OfflineIndicator />}
shareZone={
<div className="tlui-share-zone" draggable={false}>
<PeopleMenu />
<ShareMenu />
</div>
}
autoFocus
inferDarkMode
>

Wyświetl plik

@ -1,6 +1,8 @@
import * as Popover from '@radix-ui/react-popover'
import {
Button,
TldrawUiButton,
TldrawUiButtonIcon,
TldrawUiButtonLabel,
track,
useContainer,
useEditor,
@ -73,13 +75,16 @@ export const PeopleMenu = track(function PeopleMenu({
)}
{!hideShareMenu && (
<div className="tlui-people-menu__section">
<Button
<TldrawUiButton
type="menu"
data-wd="people-menu.invite"
label={'people-menu.invite'}
icon="plus"
data-testid="people-menu.invite"
onClick={() => editor.addOpenMenu('share menu')}
/>
>
<TldrawUiButtonLabel>
{msg('people-menu.invite')}
<TldrawUiButtonIcon icon="plus" />
</TldrawUiButtonLabel>
</TldrawUiButton>
</div>
)}
</div>

Wyświetl plik

@ -1,6 +1,7 @@
import {
Button,
Icon,
TldrawUiButton,
TldrawUiButtonIcon,
TldrawUiIcon,
track,
useEditor,
usePresence,
@ -34,16 +35,16 @@ export const PeopleMenuItem = track(function PeopleMenuItem({ userId }: { userId
return (
<div className="tlui-people-menu__item tlui-buttons__horizontal">
<Button
<TldrawUiButton
type="menu"
className="tlui-people-menu__item__button"
onClick={() => editor.animateToUser(userId)}
onDoubleClick={handleFollowClick}
>
<Icon icon="color" color={presence.color} />
<TldrawUiIcon icon="color" color={presence.color} />
<div className="tlui-people-menu__name">{presence.userName ?? 'New User'}</div>
</Button>
<Button
</TldrawUiButton>
<TldrawUiButton
type="icon"
className="tlui-people-menu__item__follow"
title={
@ -53,11 +54,14 @@ export const PeopleMenuItem = track(function PeopleMenuItem({ userId }: { userId
? msg('people-menu.following')
: msg('people-menu.follow')
}
icon={theyAreFollowingYou ? 'leading' : youAreFollowingThem ? 'following' : 'follow'}
onClick={handleFollowClick}
disabled={theyAreFollowingYou}
data-active={youAreFollowingThem || theyAreFollowingYou}
/>
>
<TldrawUiButtonIcon
icon={theyAreFollowingYou ? 'leading' : youAreFollowingThem ? 'following' : 'follow'}
/>
</TldrawUiButton>
</div>
)
})

Wyświetl plik

@ -1,6 +1,7 @@
import * as Popover from '@radix-ui/react-popover'
import {
Button,
TldrawUiButton,
TldrawUiButtonIcon,
USER_COLORS,
track,
useContainer,
@ -88,13 +89,14 @@ export const UserPresenceColorPicker = track(function UserPresenceColorPicker()
return (
<Popover.Root onOpenChange={handleOpenChange} open={isOpen}>
<Popover.Trigger dir="ltr" asChild>
<Button
<TldrawUiButton
type="icon"
className="tlui-people-menu__user__color"
icon="color"
style={{ color: editor.user.getColor() }}
title={msg('people-menu.change-color')}
/>
>
<TldrawUiButtonIcon icon="color" />
</TldrawUiButton>
</Popover.Trigger>
<Popover.Portal container={container}>
<Popover.Content
@ -106,11 +108,11 @@ export const UserPresenceColorPicker = track(function UserPresenceColorPicker()
>
<div className={'tlui-buttons__grid'}>
{USER_COLORS.map((item: string) => (
<Button
<TldrawUiButton
type="icon"
key={item}
data-id={item}
data-wd={item}
data-testid={item}
aria-label={item}
data-state={value === item ? 'hinted' : undefined}
title={item}
@ -120,8 +122,9 @@ export const UserPresenceColorPicker = track(function UserPresenceColorPicker()
onPointerDown={handleButtonPointerDown}
onPointerUp={handleButtonPointerUp}
onClick={handleButtonClick}
icon={'color'}
/>
>
<TldrawUiButtonIcon icon="color" />
</TldrawUiButton>
))}
</div>
</Popover.Content>

Wyświetl plik

@ -1,4 +1,12 @@
import { Button, Input, useEditor, useTranslation, useUiEvents, useValue } from '@tldraw/tldraw'
import {
TldrawUiButton,
TldrawUiButtonIcon,
TldrawUiInput,
useEditor,
useTranslation,
useUiEvents,
useValue,
} from '@tldraw/tldraw'
import { useCallback, useRef, useState } from 'react'
import { UI_OVERRIDE_TODO_EVENT } from '../../utils/useHandleUiEvent'
import { UserPresenceColorPicker } from './UserPresenceColorPicker'
@ -36,7 +44,7 @@ export function UserPresenceEditor() {
<div className="tlui-people-menu__user">
<UserPresenceColorPicker />
{isEditingName ? (
<Input
<TldrawUiInput
className="tlui-people-menu__user__input"
defaultValue={userName}
onValueChange={handleValueChange}
@ -62,14 +70,15 @@ export function UserPresenceEditor() {
) : null}
</>
)}
<Button
<TldrawUiButton
type="icon"
className="tlui-people-menu__user__edit"
data-wd="people-menu.change-name"
data-testid="people-menu.change-name"
title={msg('people-menu.change-name')}
icon={isEditingName ? 'check' : 'edit'}
onClick={toggleEditingName}
/>
>
<TldrawUiButtonIcon icon={isEditingName ? 'check' : 'edit'} />
</TldrawUiButton>
</div>
)
}

Wyświetl plik

@ -54,6 +54,13 @@ const components: TLComponents = {
</DefaultKeyboardShortcutsDialog>
)
},
SharePanel: () => {
return (
<div className="tlui-share-zone" draggable={false}>
<ExportMenu />
</div>
)
},
}
type SnapshotEditorProps = {
@ -79,11 +86,6 @@ export function SnapshotsEditor(props: SnapshotEditorProps) {
editor.updateInstanceState({ isReadonly: true })
}}
components={components}
shareZone={
<div className="tlui-share-zone" draggable={false}>
<ExportMenu />
</div>
}
renderDebugMenuItems={() => <DebugMenuItems />}
autoFocus
inferDarkMode

Wyświetl plik

@ -1,4 +1,10 @@
import { Button, LegacyTldrawDocument, useEditor, useValue } from '@tldraw/tldraw'
import {
LegacyTldrawDocument,
TldrawUiButton,
TldrawUiButtonLabel,
useEditor,
useValue,
} from '@tldraw/tldraw'
export function MigrationAnnouncement({
onClose,
@ -107,16 +113,16 @@ export function MigrationAnnouncement({
marginTop: 8,
}}
>
<Button
<TldrawUiButton
type="normal"
style={{ fontSize: 14, marginRight: 'auto' }}
onClick={downloadFile}
>
Download original
</Button>
<Button style={{ fontSize: 14 }} type="primary" onClick={onClose}>
Continue
</Button>
<TldrawUiButtonLabel>Download original</TldrawUiButtonLabel>
</TldrawUiButton>
<TldrawUiButton style={{ fontSize: 14 }} type="primary" onClick={onClose}>
<TldrawUiButtonLabel>Continue</TldrawUiButtonLabel>
</TldrawUiButton>
</div>
</div>
</div>

Wyświetl plik

@ -1,11 +1,13 @@
import {
Button,
DialogBody,
DialogCloseButton,
DialogFooter,
DialogHeader,
DialogTitle,
TLUiDialogsContextType,
TldrawUiButton,
TldrawUiButtonCheck,
TldrawUiButtonLabel,
TldrawUiDialogBody,
TldrawUiDialogCloseButton,
TldrawUiDialogFooter,
TldrawUiDialogHeader,
TldrawUiDialogTitle,
useTranslation,
} from '@tldraw/tldraw'
import { useState } from 'react'
@ -49,26 +51,28 @@ function ConfirmClearDialog({
const [dontShowAgain, setDontShowAgain] = useState(false)
return (
<>
<DialogHeader>
<DialogTitle>{msg('file-system.confirm-clear.title')}</DialogTitle>
<DialogCloseButton />
</DialogHeader>
<DialogBody style={{ maxWidth: 350 }}>
<TldrawUiDialogHeader>
<TldrawUiDialogTitle>{msg('file-system.confirm-clear.title')}</TldrawUiDialogTitle>
<TldrawUiDialogCloseButton />
</TldrawUiDialogHeader>
<TldrawUiDialogBody style={{ maxWidth: 350 }}>
{msg('file-system.confirm-clear.description')}
</DialogBody>
<DialogFooter className="tlui-dialog__footer__actions">
<Button
</TldrawUiDialogBody>
<TldrawUiDialogFooter className="tlui-dialog__footer__actions">
<TldrawUiButton
type="normal"
onClick={() => setDontShowAgain(!dontShowAgain)}
iconLeft={dontShowAgain ? 'checkbox-checked' : 'checkbox-empty'}
style={{ marginRight: 'auto' }}
>
{msg('file-system.confirm-clear.dont-show-again')}
</Button>
<Button type="normal" onClick={onCancel}>
{msg('file-system.confirm-clear.cancel')}
</Button>
<Button
<TldrawUiButtonCheck checked={dontShowAgain} />
<TldrawUiButtonLabel>
{msg('file-system.confirm-clear.dont-show-again')}
</TldrawUiButtonLabel>
</TldrawUiButton>
<TldrawUiButton type="normal" onClick={onCancel}>
<TldrawUiButtonLabel>{msg('file-system.confirm-clear.cancel')}</TldrawUiButtonLabel>
</TldrawUiButton>
<TldrawUiButton
type="primary"
onClick={async () => {
if (dontShowAgain) {
@ -77,9 +81,9 @@ function ConfirmClearDialog({
onContinue()
}}
>
{msg('file-system.confirm-clear.continue')}
</Button>
</DialogFooter>
<TldrawUiButtonLabel>{msg('file-system.confirm-clear.continue')}</TldrawUiButtonLabel>
</TldrawUiButton>
</TldrawUiDialogFooter>
</>
)
}

Wyświetl plik

@ -1,11 +1,13 @@
import {
Button,
DialogBody,
DialogCloseButton,
DialogFooter,
DialogHeader,
DialogTitle,
TLUiDialogsContextType,
TldrawUiButton,
TldrawUiButtonCheck,
TldrawUiButtonLabel,
TldrawUiDialogBody,
TldrawUiDialogCloseButton,
TldrawUiDialogFooter,
TldrawUiDialogHeader,
TldrawUiDialogTitle,
useLocalStorageState,
useTranslation,
} from '@tldraw/tldraw'
@ -50,24 +52,26 @@ function ConfirmLeaveDialog({
return (
<>
<DialogHeader>
<DialogTitle>{msg('sharing.confirm-leave.title')}</DialogTitle>
<DialogCloseButton />
</DialogHeader>
<DialogBody style={{ maxWidth: 350 }}>{msg('sharing.confirm-leave.description')}</DialogBody>
<DialogFooter className="tlui-dialog__footer__actions">
<Button
<TldrawUiDialogHeader>
<TldrawUiDialogTitle>{msg('sharing.confirm-leave.title')}</TldrawUiDialogTitle>
<TldrawUiDialogCloseButton />
</TldrawUiDialogHeader>
<TldrawUiDialogBody style={{ maxWidth: 350 }}>
{msg('sharing.confirm-leave.description')}
</TldrawUiDialogBody>
<TldrawUiDialogFooter className="tlui-dialog__footer__actions">
<TldrawUiButton
type="normal"
onClick={() => setDontShowAgain(!dontShowAgain)}
iconLeft={dontShowAgain ? 'checkbox-checked' : 'checkbox-empty'}
style={{ marginRight: 'auto' }}
onClick={() => setDontShowAgain(!dontShowAgain)}
>
{msg('sharing.confirm-leave.dont-show-again')}
</Button>
<Button type="normal" onClick={onCancel}>
{msg('sharing.confirm-leave.cancel')}
</Button>
<Button
<TldrawUiButtonCheck checked={dontShowAgain} />
<TldrawUiButtonLabel>{msg('sharing.confirm-leave.dont-show-again')}</TldrawUiButtonLabel>
</TldrawUiButton>
<TldrawUiButton type="normal" onClick={onCancel}>
<TldrawUiButtonLabel>{msg('sharing.confirm-leave.cancel')}</TldrawUiButtonLabel>
</TldrawUiButton>
<TldrawUiButton
type="primary"
onClick={async () => {
if (dontShowAgain) {
@ -76,9 +80,9 @@ function ConfirmLeaveDialog({
onContinue()
}}
>
{msg('sharing.confirm-leave.leave')}
</Button>
</DialogFooter>
<TldrawUiButtonLabel>{msg('sharing.confirm-leave.leave')}</TldrawUiButtonLabel>
</TldrawUiButton>
</TldrawUiDialogFooter>
</>
)
}

Wyświetl plik

@ -1,11 +1,13 @@
import {
Button,
DialogBody,
DialogCloseButton,
DialogFooter,
DialogHeader,
DialogTitle,
TLUiDialogsContextType,
TldrawUiButton,
TldrawUiButtonCheck,
TldrawUiButtonLabel,
TldrawUiDialogBody,
TldrawUiDialogCloseButton,
TldrawUiDialogFooter,
TldrawUiDialogHeader,
TldrawUiDialogTitle,
useTranslation,
} from '@tldraw/tldraw'
import { useState } from 'react'
@ -49,26 +51,28 @@ function ConfirmOpenDialog({
const [dontShowAgain, setDontShowAgain] = useState(false)
return (
<>
<DialogHeader>
<DialogTitle>{msg('file-system.confirm-open.title')}</DialogTitle>
<DialogCloseButton />
</DialogHeader>
<DialogBody style={{ maxWidth: 350 }}>
<TldrawUiDialogHeader>
<TldrawUiDialogTitle>{msg('file-system.confirm-open.title')}</TldrawUiDialogTitle>
<TldrawUiDialogCloseButton />
</TldrawUiDialogHeader>
<TldrawUiDialogBody style={{ maxWidth: 350 }}>
{msg('file-system.confirm-open.description')}
</DialogBody>
<DialogFooter className="tlui-dialog__footer__actions">
<Button
</TldrawUiDialogBody>
<TldrawUiDialogFooter className="tlui-dialog__footer__actions">
<TldrawUiButton
type="normal"
onClick={() => setDontShowAgain(!dontShowAgain)}
iconLeft={dontShowAgain ? 'checkbox-checked' : 'checkbox-empty'}
style={{ marginRight: 'auto' }}
>
{msg('file-system.confirm-open.dont-show-again')}
</Button>
<Button type="normal" onClick={onCancel}>
{msg('file-system.confirm-open.cancel')}
</Button>
<Button
<TldrawUiButtonCheck checked={dontShowAgain} />
<TldrawUiButtonLabel>
{msg('file-system.confirm-open.dont-show-again')}
</TldrawUiButtonLabel>
</TldrawUiButton>
<TldrawUiButton type="normal" onClick={onCancel}>
<TldrawUiButtonLabel>{msg('file-system.confirm-open.cancel')}</TldrawUiButtonLabel>
</TldrawUiButton>
<TldrawUiButton
type="primary"
onClick={async () => {
if (dontShowAgain) {
@ -77,9 +81,9 @@ function ConfirmOpenDialog({
onContinue()
}}
>
{msg('file-system.confirm-open.open')}
</Button>
</DialogFooter>
<TldrawUiButtonLabel>{msg('file-system.confirm-open.open')}</TldrawUiButtonLabel>
</TldrawUiButton>
</TldrawUiDialogFooter>
</>
)
}

Wyświetl plik

@ -1,8 +1,8 @@
import {
DefaultSizeStyle,
Icon,
SharedStyleMap,
Tldraw,
TldrawUiIcon,
TLEditorComponents,
track,
useEditor,
@ -85,7 +85,7 @@ const ContextToolbarComponent = track(() => {
editor.setStyleForSelectedShapes(DefaultSizeStyle, value, { squashing: false })
}
>
<Icon icon={icon} />
<TldrawUiIcon icon={icon} />
</div>
)
})}

Wyświetl plik

@ -1,9 +1,9 @@
import {
Button,
DefaultQuickActions,
DefaultQuickActionsContent,
TLComponents,
Tldraw,
TldrawUiMenuItem,
} from '@tldraw/tldraw'
import '@tldraw/tldraw/tldraw.css'
@ -11,7 +11,7 @@ function CustomQuickActions() {
return (
<DefaultQuickActions>
<DefaultQuickActionsContent />
<Button type="icon" icon="code" smallIcon />
<TldrawUiMenuItem id="code" icon="code" onSelect={() => window.alert('code')} />
</DefaultQuickActions>
)
}

Wyświetl plik

@ -1,11 +1,12 @@
import {
Button,
DefaultColorStyle,
DefaultStylePanel,
DefaultStylePanelContent,
TLComponents,
TLUiStylePanelProps,
Tldraw,
TldrawUiButton,
TldrawUiButtonLabel,
useEditor,
} from '@tldraw/tldraw'
import '@tldraw/tldraw/tldraw.css'
@ -17,22 +18,22 @@ function CustomStylePanel(props: TLUiStylePanelProps) {
return (
<DefaultStylePanel {...props}>
<Button
<TldrawUiButton
type="menu"
onClick={() => {
editor.setStyleForSelectedShapes(DefaultColorStyle, 'red', { squashing: true })
}}
>
Red
</Button>
<Button
<TldrawUiButtonLabel>Red</TldrawUiButtonLabel>
</TldrawUiButton>
<TldrawUiButton
type="menu"
onClick={() => {
editor.setStyleForSelectedShapes(DefaultColorStyle, 'green', { squashing: true })
}}
>
Green
</Button>
<TldrawUiButtonLabel>Green</TldrawUiButtonLabel>
</TldrawUiButton>
<DefaultStylePanelContent relevantStyles={props.relevantStyles} />
</DefaultStylePanel>
)

Wyświetl plik

@ -17,6 +17,9 @@ const components: Required<TLUiComponents> = {
QuickActions: null,
HelperButtons: null,
DebugMenu: null,
SharePanel: null,
MenuPanel: null,
TopPanel: null,
}
export default function UiComponentsHiddenExample() {

Wyświetl plik

@ -1,13 +1,18 @@
import { Tldraw } from '@tldraw/tldraw'
import { TLComponents, Tldraw } from '@tldraw/tldraw'
import '@tldraw/tldraw/tldraw.css'
// There's a guide at the bottom of this file!
const components: TLComponents = {
SharePanel: CustomShareZone,
TopPanel: CustomTopZone,
}
// [1]
export default function Example() {
return (
<div className="tldraw__editor">
<Tldraw topZone={<CustomTopZone />} shareZone={<CustomShareZone />} />
<Tldraw components={components} />
</div>
)
}
@ -46,8 +51,8 @@ function CustomShareZone() {
}
/*
This example shows how to pass in a custom component to the share zone and top zone.
The share zone is in the top right corner above the style menu, the top zone is in
This example shows how to pass in a custom component to the share panel and top panel.
The share panel is in the top right corner above the style menu, the top panel is in
the top center.
[1]

Wyświetl plik

@ -28,6 +28,7 @@ import { JSX as JSX_2 } from 'react/jsx-runtime';
import { LANGUAGES } from '@tldraw/editor';
import { Mat } from '@tldraw/editor';
import { MatModel } from '@tldraw/editor';
import { MemoExoticComponent } from 'react';
import { MigrationFailureReason } from '@tldraw/editor';
import { Migrations } from '@tldraw/editor';
import { NamedExoticComponent } from 'react';
@ -49,6 +50,7 @@ import { ShapeUtil } from '@tldraw/editor';
import { SharedStyle } from '@tldraw/editor';
import { StateNode } from '@tldraw/editor';
import { StoreSnapshot } from '@tldraw/editor';
import { StyleProp } from '@tldraw/editor';
import { SvgExportContext } from '@tldraw/editor';
import { T } from '@tldraw/editor';
import { TLAnyShapeUtilConstructor } from '@tldraw/editor';
@ -265,9 +267,6 @@ export function BreakPointProvider({ forceMobile, children, }: {
// @internal (undocumented)
export function buildFromV1Document(editor: Editor, document: LegacyTldrawDocument): void;
// @public (undocumented)
export const Button: React_3.ForwardRefExoticComponent<TLUiButtonProps & React_3.RefAttributes<HTMLButtonElement>>;
// @public
export function containBoxSize(originalSize: BoxWidthHeight, containBoxSize: BoxWidthHeight): BoxWidthHeight;
@ -360,21 +359,6 @@ export const DefaultZoomMenu: NamedExoticComponent<TLUiZoomMenuProps>;
// @public (undocumented)
export function DefaultZoomMenuContent(): JSX_2.Element;
// @public (undocumented)
export function DialogBody({ className, children, style }: TLUiDialogBodyProps): JSX_2.Element;
// @public (undocumented)
export function DialogCloseButton(): JSX_2.Element;
// @public (undocumented)
export function DialogFooter({ className, children }: TLUiDialogFooterProps): JSX_2.Element;
// @public (undocumented)
export function DialogHeader({ className, children }: TLUiDialogHeaderProps): JSX_2.Element;
// @public (undocumented)
export function DialogTitle({ className, children }: TLUiDialogTitleProps): JSX_2.Element;
// @public
export function downsizeImage(blob: Blob, width: number, height: number, opts?: {
type?: string | undefined;
@ -439,36 +423,6 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
static type: "draw";
}
// @public (undocumented)
export function DropdownMenuCheckboxItem({ children, onSelect, ...rest }: TLUiDropdownMenuCheckboxItemProps): JSX_2.Element;
// @public (undocumented)
export function DropdownMenuContent({ side, align, sideOffset, alignOffset, children, }: TLUiDropdownMenuContentProps): JSX_2.Element;
// @public (undocumented)
export function DropdownMenuGroup({ children, size }: TLUiDropdownMenuGroupProps): JSX_2.Element;
// @public (undocumented)
export function DropdownMenuIndicator(): JSX_2.Element;
// @public (undocumented)
export function DropdownMenuItem({ noClose, ...props }: TLUiDropdownMenuItemProps): JSX_2.Element;
// @public (undocumented)
export function DropdownMenuRadioItem({ children, ...rest }: TLUiDropdownMenuRadioItemProps): JSX_2.Element;
// @public (undocumented)
export function DropdownMenuRoot({ id, children, modal, debugOpen, }: TLUiDropdownMenuRootProps): JSX_2.Element;
// @public (undocumented)
export function DropdownMenuSub({ id, children }: TLUiDropdownMenuSubProps): JSX_2.Element;
// @public (undocumented)
export function DropdownMenuSubTrigger({ label, title, disabled, }: TLUiDropdownMenuSubTriggerProps): JSX_2.Element;
// @public (undocumented)
export function DropdownMenuTrigger({ children, ...rest }: TLUiDropdownMenuTriggerProps): JSX_2.Element;
// @public (undocumented)
export class EmbedShapeUtil extends BaseBoxShapeUtil<TLEmbedShape> {
// (undocumented)
@ -815,9 +769,6 @@ export class HighlightShapeUtil extends ShapeUtil<TLHighlightShape> {
static type: "highlight";
}
// @public (undocumented)
export const Icon: NamedExoticComponent<TLUiIconProps>;
// @public (undocumented)
export class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {
// (undocumented)
@ -856,9 +807,6 @@ export class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {
static type: "image";
}
// @public (undocumented)
export const Input: React_3.ForwardRefExoticComponent<TLUiInputProps & React_3.RefAttributes<HTMLInputElement>>;
// @public (undocumented)
export function isGifAnimated(file: Blob): Promise<boolean>;
@ -1059,15 +1007,6 @@ export function parseTldrawJsonFile({ json, schema, }: {
json: string;
}): Result<TLStore, TldrawFileParseError>;
// @public (undocumented)
export function Popover({ id, children, onOpenChange, open }: TLUiPopoverProps): JSX_2.Element;
// @public (undocumented)
export function PopoverContent({ side, children, align, sideOffset, alignOffset, }: TLUiPopoverContentProps): JSX_2.Element;
// @public (undocumented)
export function PopoverTrigger({ children, ...rest }: TLUiPopoverTriggerProps): JSX_2.Element;
// @public
export function removeFrame(editor: Editor, ids: TLShapeId[]): void;
@ -1292,13 +1231,25 @@ export interface TldrawUiBaseProps {
components?: TLUiComponents;
hideUi?: boolean;
renderDebugMenuItems?: () => React_2.ReactNode;
shareZone?: ReactNode;
// @internal
topZone?: ReactNode;
}
// @public (undocumented)
export function TldrawUiComponentsProvider({ overrides, children, }: ComponentsContextProviderProps): JSX_2.Element;
export const TldrawUiButton: React_3.ForwardRefExoticComponent<TLUiButtonProps & React_3.RefAttributes<HTMLButtonElement>>;
// @public (undocumented)
export function TldrawUiButtonCheck({ checked }: TLUiButtonCheckProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiButtonIcon({ icon, small, invertIcon }: TLUiButtonIconProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiButtonLabel({ children }: TLUiButtonLabelProps): JSX_2.Element;
// @public (undocumented)
export const TldrawUiButtonPicker: MemoExoticComponent<(<T extends string>(props: TLUiButtonPickerProps<T>) => JSX_2.Element)>;
// @public (undocumented)
export function TldrawUiComponentsProvider({ overrides, children, }: TLUiComponentsProviderProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiContextProvider({ overrides, components, assetUrls, onUiEvent, forceMobile, children, }: TldrawUiContextProviderProps): JSX_2.Element;
@ -1313,6 +1264,57 @@ export interface TldrawUiContextProviderProps {
overrides?: TLUiOverrides | TLUiOverrides[];
}
// @public (undocumented)
export function TldrawUiDialogBody({ className, children, style }: TLUiDialogBodyProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiDialogCloseButton(): JSX_2.Element;
// @public (undocumented)
export function TldrawUiDialogFooter({ className, children }: TLUiDialogFooterProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiDialogHeader({ className, children }: TLUiDialogHeaderProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiDialogTitle({ className, children }: TLUiDialogTitleProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiDropdownMenuCheckboxItem({ children, onSelect, ...rest }: TLUiDropdownMenuCheckboxItemProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiDropdownMenuContent({ side, align, sideOffset, alignOffset, children, }: TLUiDropdownMenuContentProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiDropdownMenuGroup({ children, size, }: TLUiDropdownMenuGroupProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiDropdownMenuIndicator(): JSX_2.Element;
// @public (undocumented)
export function TldrawUiDropdownMenuItem({ noClose, children }: TLUiDropdownMenuItemProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiDropdownMenuRoot({ id, children, modal, debugOpen, }: TLUiDropdownMenuRootProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiDropdownMenuSub({ id, children }: TLUiDropdownMenuSubProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiDropdownMenuSubTrigger({ label, title, disabled, }: TLUiDropdownMenuSubTriggerProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiDropdownMenuTrigger({ children, ...rest }: TLUiDropdownMenuTriggerProps): JSX_2.Element;
// @public (undocumented)
export const TldrawUiIcon: NamedExoticComponent<TLUiIconProps>;
// @public (undocumented)
export const TldrawUiInput: React_3.ForwardRefExoticComponent<TLUiInputProps & React_3.RefAttributes<HTMLInputElement>>;
// @public (undocumented)
export function TldrawUiKbd({ children }: TLUiKbdProps): JSX_2.Element | null;
// @public (undocumented)
export function TldrawUiMenuCheckboxItem<TranslationKey extends string = string, IconType extends string = string>({ id, kbd, label, readonlyOk, onSelect, disabled, checked, }: TLUiMenuCheckboxItemProps<TranslationKey, IconType>): JSX_2.Element | null;
@ -1328,9 +1330,21 @@ export function TldrawUiMenuItem<TranslationKey extends string = string, IconTyp
// @public (undocumented)
export function TldrawUiMenuSubmenu<Translation extends string = string>({ id, disabled, label, size, children, }: TLUiMenuSubmenuProps<Translation>): any;
// @public (undocumented)
export function TldrawUiPopover({ id, children, onOpenChange, open }: TLUiPopoverProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiPopoverContent({ side, children, align, sideOffset, alignOffset, }: TLUiPopoverContentProps): JSX_2.Element;
// @public (undocumented)
export function TldrawUiPopoverTrigger({ children }: TLUiPopoverTriggerProps): JSX_2.Element;
// @public
export type TldrawUiProps = TldrawUiBaseProps & TldrawUiContextProviderProps;
// @internal (undocumented)
export const TldrawUiSlider: NamedExoticComponent<TLUiSliderProps>;
// @public (undocumented)
export interface TLUiActionItem<TransationKey extends string = string, IconType extends string = string> {
// (undocumented)
@ -1364,29 +1378,44 @@ export type TLUiActionsMenuProps = {
// @public (undocumented)
export type TLUiAssetUrlOverrides = RecursivePartial<TLUiAssetUrls>;
// @public (undocumented)
export type TLUiButtonCheckProps = {
checked: boolean;
};
// @public (undocumented)
export type TLUiButtonIconProps = {
icon: string;
small?: boolean;
invertIcon?: boolean;
};
// @public (undocumented)
export type TLUiButtonLabelProps = {
children?: any;
};
// @public (undocumented)
export interface TLUiButtonPickerProps<T extends string> {
// (undocumented)
items: StyleValuesForUi<T>;
// (undocumented)
onValueChange: (style: StyleProp<T>, value: T, squashing: boolean) => void;
// (undocumented)
style: StyleProp<T>;
// (undocumented)
title: string;
// (undocumented)
uiType: string;
// (undocumented)
value: SharedStyle<T>;
}
// @public (undocumented)
export interface TLUiButtonProps extends React_3.HTMLAttributes<HTMLButtonElement> {
// (undocumented)
disabled?: boolean;
// (undocumented)
icon?: Exclude<string, TLUiIconType> | TLUiIconType;
// (undocumented)
iconLeft?: Exclude<string, TLUiIconType> | TLUiIconType;
// (undocumented)
invertIcon?: boolean;
// (undocumented)
isChecked?: boolean;
// (undocumented)
kbd?: string;
// (undocumented)
label?: Exclude<string, TLUiTranslationKey> | TLUiTranslationKey;
// (undocumented)
loading?: boolean;
// (undocumented)
smallIcon?: boolean;
// (undocumented)
spinner?: boolean;
// (undocumented)
type: 'danger' | 'help' | 'icon' | 'low' | 'menu' | 'normal' | 'primary' | 'tool';
}
@ -1395,6 +1424,12 @@ export type TLUiComponents = Partial<{
[K in keyof BaseTLUiComponents]: BaseTLUiComponents[K] | null;
}>;
// @public (undocumented)
export type TLUiComponentsProviderProps = {
overrides?: TLUiComponents;
children: any;
};
// @public (undocumented)
export interface TLUiContextMenuProps {
// (undocumented)
@ -1491,23 +1526,11 @@ export type TLUiDropdownMenuGroupProps = {
};
// @public (undocumented)
export interface TLUiDropdownMenuItemProps extends TLUiButtonProps {
// (undocumented)
noClose?: boolean;
}
// @public (undocumented)
export interface TLUiDropdownMenuRadioItemProps {
// (undocumented)
checked?: boolean;
export interface TLUiDropdownMenuItemProps {
// (undocumented)
children: any;
// (undocumented)
disabled?: boolean;
// (undocumented)
onSelect?: (e: Event) => void;
// (undocumented)
title: string;
noClose?: boolean;
}
// @public (undocumented)
@ -1533,7 +1556,7 @@ export type TLUiDropdownMenuSubTriggerProps = {
};
// @public (undocumented)
export interface TLUiDropdownMenuTriggerProps extends TLUiButtonProps {
export interface TLUiDropdownMenuTriggerProps {
// (undocumented)
children?: any;
}
@ -1772,6 +1795,12 @@ export interface TLUiInputProps {
value?: string;
}
// @public (undocumented)
export interface TLUiKbdProps {
// (undocumented)
children: string;
}
// @public (undocumented)
export type TLUiKeyboardShortcutsDialogProps = TLUiDialogProps & {
children?: any;
@ -1867,7 +1896,7 @@ export type TLUiPopoverProps = {
};
// @public (undocumented)
export interface TLUiPopoverTriggerProps extends TLUiButtonProps {
export interface TLUiPopoverTriggerProps {
// (undocumented)
children?: React_2.ReactNode;
}
@ -1877,6 +1906,22 @@ export type TLUiQuickActionsProps = {
children?: any;
};
// @internal (undocumented)
export interface TLUiSliderProps {
// (undocumented)
'data-testid'?: string;
// (undocumented)
label: string;
// (undocumented)
onValueChange: (value: number, emphemeral: boolean) => void;
// (undocumented)
steps: number;
// (undocumented)
title: string;
// (undocumented)
value: null | number;
}
// @public (undocumented)
export type TLUiStylePanelContentProps = {
relevantStyles: ReturnType<typeof useRelevantStyles>;
@ -2080,6 +2125,9 @@ export function useTldrawUiComponents(): Partial<{
QuickActions: ComponentType<TLUiQuickActionsProps> | null;
HelperButtons: ComponentType<TLUiHelperButtonsProps> | null;
DebugMenu: ComponentType | null;
MenuPanel: ComponentType | null;
TopPanel: ComponentType | null;
SharePanel: ComponentType | null;
}>;
// @public (undocumented)

Wyświetl plik

@ -42,9 +42,6 @@ export { TldrawUi, type TldrawUiBaseProps, type TldrawUiProps } from './lib/ui/T
export { setDefaultUiAssetUrls, type TLUiAssetUrlOverrides } from './lib/ui/assetUrls'
export { OfflineIndicator } from './lib/ui/components/OfflineIndicator/OfflineIndicator'
export { Spinner } from './lib/ui/components/Spinner'
export { Button, type TLUiButtonProps } from './lib/ui/components/primitives/Button'
export { Icon, type TLUiIconProps } from './lib/ui/components/primitives/Icon'
export { Input, type TLUiInputProps } from './lib/ui/components/primitives/Input'
export {
TldrawUiContextProvider,
type TldrawUiContextProviderProps,
@ -99,7 +96,7 @@ export {
export { type TLUiTranslationKey } from './lib/ui/hooks/useTranslation/TLUiTranslationKey'
export { type TLUiTranslation } from './lib/ui/hooks/useTranslation/translations'
export {
useTranslation as useTranslation,
useTranslation,
type TLUiTranslationContextType,
} from './lib/ui/hooks/useTranslation/useTranslation'
export { type TLUiIconType } from './lib/ui/icon-types'
@ -137,35 +134,13 @@ export { DefaultMinimap } from './lib/ui/components/Minimap/DefaultMinimap'
// Helper to unwrap label from action items
export { unwrapLabel } from './lib/ui/context/actions'
// General UI components for building menus
export {
TldrawUiMenuCheckboxItem,
type TLUiMenuCheckboxItemProps,
} from './lib/ui/components/menus/TldrawUiMenuCheckboxItem'
export {
TldrawUiMenuContextProvider,
type TLUiMenuContextProviderProps,
} from './lib/ui/components/menus/TldrawUiMenuContext'
export {
TldrawUiMenuGroup,
type TLUiMenuGroupProps,
} from './lib/ui/components/menus/TldrawUiMenuGroup'
export {
TldrawUiMenuItem,
type TLUiMenuItemProps,
} from './lib/ui/components/menus/TldrawUiMenuItem'
export {
TldrawUiMenuSubmenu,
type TLUiMenuSubmenuProps,
} from './lib/ui/components/menus/TldrawUiMenuSubmenu'
export {
TldrawUiComponentsProvider,
useTldrawUiComponents,
type TLUiComponents,
type TLUiComponentsProviderProps,
} from './lib/ui/context/components'
// Menus / UI elements that can be customized
export { DefaultPageMenu } from './lib/ui/components/PageMenu/DefaultPageMenu'
export {
@ -236,45 +211,108 @@ export { DefaultToolbar } from './lib/ui/components/Toolbar/DefaultToolbar'
export { type TLComponents } from './lib/Tldraw'
/* ------------------- Primitives ------------------- */
// Button
export {
DialogBody,
DialogCloseButton,
DialogFooter,
DialogHeader,
DialogTitle,
TldrawUiButton,
type TLUiButtonProps,
} from './lib/ui/components/primitives/Button/TldrawUiButton'
export {
TldrawUiButtonCheck,
type TLUiButtonCheckProps,
} from './lib/ui/components/primitives/Button/TldrawUiButtonCheck'
export {
TldrawUiButtonIcon,
type TLUiButtonIconProps,
} from './lib/ui/components/primitives/Button/TldrawUiButtonIcon'
export {
TldrawUiButtonLabel,
type TLUiButtonLabelProps,
} from './lib/ui/components/primitives/Button/TldrawUiButtonLabel'
// Button picker
export {
TldrawUiButtonPicker,
type TLUiButtonPickerProps,
} from './lib/ui/components/primitives/TldrawUiButtonPicker'
// Dialog
export {
TldrawUiDialogBody,
TldrawUiDialogCloseButton,
TldrawUiDialogFooter,
TldrawUiDialogHeader,
TldrawUiDialogTitle,
type TLUiDialogBodyProps,
type TLUiDialogFooterProps,
type TLUiDialogHeaderProps,
type TLUiDialogTitleProps,
} from './lib/ui/components/primitives/Dialog'
} from './lib/ui/components/primitives/TldrawUiDialog'
// Dropdown Menu
export {
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuIndicator,
DropdownMenuItem,
DropdownMenuRadioItem,
DropdownMenuRoot,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
TldrawUiDropdownMenuCheckboxItem,
TldrawUiDropdownMenuContent,
TldrawUiDropdownMenuGroup,
TldrawUiDropdownMenuIndicator,
TldrawUiDropdownMenuItem,
TldrawUiDropdownMenuRoot,
TldrawUiDropdownMenuSub,
TldrawUiDropdownMenuSubTrigger,
TldrawUiDropdownMenuTrigger,
type TLUiDropdownMenuCheckboxItemProps,
type TLUiDropdownMenuContentProps,
type TLUiDropdownMenuGroupProps,
type TLUiDropdownMenuItemProps,
type TLUiDropdownMenuRadioItemProps,
type TLUiDropdownMenuRootProps,
type TLUiDropdownMenuSubProps,
type TLUiDropdownMenuSubTriggerProps,
type TLUiDropdownMenuTriggerProps,
} from './lib/ui/components/primitives/DropdownMenu'
} from './lib/ui/components/primitives/TldrawUiDropdownMenu'
// Icon
export { TldrawUiIcon, type TLUiIconProps } from './lib/ui/components/primitives/TldrawUiIcon'
// Input
export { TldrawUiInput, type TLUiInputProps } from './lib/ui/components/primitives/TldrawUiInput'
// Kbd
export { TldrawUiKbd, type TLUiKbdProps } from './lib/ui/components/primitives/TldrawUiKbd'
// Popover
export {
Popover,
PopoverContent,
PopoverTrigger,
TldrawUiPopover,
TldrawUiPopoverContent,
TldrawUiPopoverTrigger,
type TLUiPopoverContentProps,
type TLUiPopoverProps,
type TLUiPopoverTriggerProps,
} from './lib/ui/components/primitives/Popover'
} from './lib/ui/components/primitives/TldrawUiPopover'
// Slider
export { TldrawUiSlider, type TLUiSliderProps } from './lib/ui/components/primitives/TldrawUiSlider'
/* ----------------- Menu Primitives ---------------- */
// General UI components for building menus
export {
TldrawUiMenuCheckboxItem,
type TLUiMenuCheckboxItemProps,
} from './lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem'
export {
TldrawUiMenuContextProvider,
type TLUiMenuContextProviderProps,
} from './lib/ui/components/primitives/menus/TldrawUiMenuContext'
export {
TldrawUiMenuGroup,
type TLUiMenuGroupProps,
} from './lib/ui/components/primitives/menus/TldrawUiMenuGroup'
export {
TldrawUiMenuItem,
type TLUiMenuItemProps,
} from './lib/ui/components/primitives/menus/TldrawUiMenuItem'
export {
TldrawUiMenuSubmenu,
type TLUiMenuSubmenuProps,
} from './lib/ui/components/primitives/menus/TldrawUiMenuSubmenu'

Wyświetl plik

@ -3,6 +3,7 @@ export type StyleValuesForUi<T> = readonly {
readonly icon: string
}[]
// todo: default styles prop?
export const STYLES = {
color: [
{ value: 'black', icon: 'color' },

Wyświetl plik

@ -75,6 +75,10 @@
opacity: 1;
}
.tlui-button__icon + .tlui-button__label {
margin-left: var(--space-2);
}
@media (hover: hover) {
.tlui-button::after {
background-color: var(--color-muted-2);
@ -127,46 +131,6 @@
position: relative;
}
/* Icon button */
.tlui-button__icon {
height: 40px;
width: 40px;
min-height: 40px;
min-width: 40px;
padding: 0px;
}
.tlui-button__icon-left {
margin-right: var(--space-2);
}
/* Hinted */
.tlui-button__icon[data-state='hinted']::after {
background: var(--color-hint);
opacity: 1;
/* box-shadow: inset 0 0 0 1px var(--color-muted-1); */
}
.tlui-button__icon[data-state='hinted']:not(:disabled, :focus-visible):active::after {
background: var(--color-hint);
opacity: 1;
/* box-shadow: inset 0 0 0 1px var(--color-text-3); */
}
@media (hover: hover) {
.tlui-button__icon::after {
inset: 4px;
border-radius: var(--radius-2);
}
.tlui-button__icon[data-state='hinted']:not(:disabled, :focus-visible):hover::after {
background: var(--color-hint);
/* box-shadow: inset 0 0 0 1px var(--color-text-3); */
}
}
/* Menu button */
.tlui-button__menu {
@ -1225,11 +1189,6 @@
width: 128px;
}
.tlui-page-menu__trigger > span {
flex-grow: 2;
margin-right: var(--space-4);
}
.tlui-page-menu__header {
display: flex;
flex-direction: row;
@ -1373,17 +1332,29 @@
.tlui-page_menu__item__submenu[data-isediting='true'] {
display: block;
opacity: 1;
}
.tlui-page_menu__item__submenu > .tlui-button {
opacity: 0;
}
.tlui-page-menu__item__button .tlui-button__icon {
margin-right: 4px;
}
@media (hover: hover) {
.tlui-page_menu__item__submenu {
opacity: 0;
display: block;
}
.tlui-page_menu__item__submenu:hover,
.tlui-page-menu__item:focus-within > .tlui-page_menu__item__submenu,
.tlui-page_menu__item__sortable:focus-within > .tlui-page_menu__item__submenu {
.tlui-page_menu__item__submenu[data-isediting='true'] > .tlui-button {
opacity: 0;
}
.tlui-page_menu__item__submenu > .tlui-button[data-state='open'],
.tlui-page_menu__item__submenu:hover > .tlui-button,
.tlui-page_menu__item__sortable:focus-within > .tlui-page_menu__item__submenu > .tlui-button {
opacity: 1;
}
}

Wyświetl plik

@ -6,9 +6,9 @@ import { TLUiAssetUrlOverrides } from './assetUrls'
import { DebugPanel } from './components/DebugPanel'
import { Dialogs } from './components/Dialogs'
import { FollowingIndicator } from './components/FollowingIndicator'
import { MenuZone } from './components/MenuZone'
import { ToastViewport, Toasts } from './components/Toasts'
import { Button } from './components/primitives/Button'
import { TldrawUiButton } from './components/primitives/Button/TldrawUiButton'
import { TldrawUiButtonIcon } from './components/primitives/Button/TldrawUiButtonIcon'
import { PORTRAIT_BREAKPOINT } from './constants'
import {
TldrawUiContextProvider,
@ -20,6 +20,7 @@ import { TLUiComponents, useTldrawUiComponents } from './context/components'
import { useNativeClipboardEvents } from './hooks/useClipboardEvents'
import { useEditorEvents } from './hooks/useEditorEvents'
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'
import { useReadonly } from './hooks/useReadonly'
import { useRelevantStyles } from './hooks/useRevelantStyles'
import { useTranslation } from './hooks/useTranslation/useTranslation'
@ -51,17 +52,6 @@ export interface TldrawUiBaseProps {
*/
components?: TLUiComponents
/**
* A component to use for the share zone (will be deprecated)
*/
shareZone?: ReactNode
/**
* A component to use for the top zone (will be deprecated)
* @internal
*/
topZone?: ReactNode
/**
* Additional items to add to the debug menu (will be deprecated)
*/
@ -75,8 +65,6 @@ export interface TldrawUiBaseProps {
* @public
*/
export const TldrawUi = React.memo(function TldrawUi({
shareZone,
topZone,
renderDebugMenuItems,
children,
hideUi,
@ -85,12 +73,7 @@ export const TldrawUi = React.memo(function TldrawUi({
}: TldrawUiProps) {
return (
<TldrawUiContextProvider {...rest} components={components}>
<TldrawUiInner
hideUi={hideUi}
shareZone={shareZone}
topZone={topZone}
renderDebugMenuItems={renderDebugMenuItems}
>
<TldrawUiInner hideUi={hideUi} renderDebugMenuItems={renderDebugMenuItems}>
{children}
</TldrawUiInner>
</TldrawUiContextProvider>
@ -121,17 +104,24 @@ const TldrawUiInner = React.memo(function TldrawUiInner({
)
})
const TldrawUiContent = React.memo(function TldrawUI({ shareZone, topZone }: TldrawUiContentProps) {
const TldrawUiContent = React.memo(function TldrawUI() {
const editor = useEditor()
const msg = useTranslation()
const breakpoint = useBreakpoint()
const isReadonlyMode = useValue('isReadonlyMode', () => editor.getInstanceState().isReadonly, [
editor,
])
const isReadonlyMode = useReadonly()
const isFocusMode = useValue('focus', () => editor.getInstanceState().isFocusMode, [editor])
const isDebugMode = useValue('debug', () => editor.getInstanceState().isDebugMode, [editor])
const { StylePanel, Toolbar, HelpMenu, NavigationPanel, HelperButtons } = useTldrawUiComponents()
const {
SharePanel,
TopPanel,
MenuPanel,
StylePanel,
Toolbar,
HelpMenu,
NavigationPanel,
HelperButtons,
} = useTldrawUiComponents()
useKeyboardShortcuts()
useNativeClipboardEvents()
@ -149,24 +139,25 @@ const TldrawUiContent = React.memo(function TldrawUI({ shareZone, topZone }: Tld
>
{isFocusMode ? (
<div className="tlui-layout__top">
<Button
<TldrawUiButton
type="icon"
className="tlui-focus-button"
title={`${msg('focus-mode.toggle-focus-mode')}`}
icon="dot"
title={msg('focus-mode.toggle-focus-mode')}
onClick={() => toggleFocus.onSelect('menu')}
/>
>
<TldrawUiButtonIcon icon="dot" />
</TldrawUiButton>
</div>
) : (
<>
<div className="tlui-layout__top">
<div className="tlui-layout__top__left">
<MenuZone />
{MenuPanel && <MenuPanel />}
{HelperButtons && <HelperButtons />}
</div>
<div className="tlui-layout__top__center">{topZone}</div>
<div className="tlui-layout__top__center">{TopPanel && <TopPanel />}</div>
<div className="tlui-layout__top__right">
{shareZone}
{SharePanel && <SharePanel />}
{StylePanel && breakpoint >= PORTRAIT_BREAKPOINT.TABLET_SM && !isReadonlyMode && (
<_StylePanel />
)}

Wyświetl plik

@ -4,8 +4,14 @@ import { PORTRAIT_BREAKPOINT } from '../../constants'
import { useBreakpoint } from '../../context/breakpoints'
import { useReadonly } from '../../hooks/useReadonly'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TldrawUiMenuContextProvider } from '../menus/TldrawUiMenuContext'
import { Popover, PopoverContent, PopoverTrigger } from '../primitives/Popover'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
import {
TldrawUiPopover,
TldrawUiPopoverContent,
TldrawUiPopoverTrigger,
} from '../primitives/TldrawUiPopover'
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
import { DefaultActionsMenuContent } from './DefaultActionsMenuContent'
/** @public */
@ -37,16 +43,17 @@ export const DefaultActionsMenu = memo(function DefaultActionsMenu({
if (isReadonlyMode && !isInAcceptableReadonlyState) return
return (
<Popover id="actions-menu">
<PopoverTrigger
className="tlui-menu__trigger"
data-testid="main.action-menu"
icon="dots-vertical"
title={msg('actions-menu.title')}
type="icon" // needs to be here because the trigger also passes down type="button"
smallIcon
/>
<PopoverContent
<TldrawUiPopover id="actions-menu">
<TldrawUiPopoverTrigger>
<TldrawUiButton
type="icon"
data-testid="main.action-menu"
title={msg('actions-menu.title')}
>
<TldrawUiButtonIcon icon="dots-vertical" small />
</TldrawUiButton>
</TldrawUiPopoverTrigger>
<TldrawUiPopoverContent
side={breakpoint >= PORTRAIT_BREAKPOINT.TABLET ? 'bottom' : 'top'}
sideOffset={6}
>
@ -55,7 +62,7 @@ export const DefaultActionsMenu = memo(function DefaultActionsMenu({
{content}
</TldrawUiMenuContextProvider>
</div>
</PopoverContent>
</Popover>
</TldrawUiPopoverContent>
</TldrawUiPopover>
)
})

Wyświetl plik

@ -9,7 +9,7 @@ import {
useThreeStackableItems,
useUnlockedSelectedShapesCount,
} from '../../hooks/menu-hooks'
import { TldrawUiMenuItem } from '../menus/TldrawUiMenuItem'
import { TldrawUiMenuItem } from '../primitives/menus/TldrawUiMenuItem'
/** @public */
export function DefaultActionsMenuContent() {

Wyświetl plik

@ -2,7 +2,7 @@ import * as _ContextMenu from '@radix-ui/react-context-menu'
import { preventDefault, useContainer, useEditor } from '@tldraw/editor'
import { memo, useCallback } from 'react'
import { useMenuIsOpen } from '../../hooks/useMenuIsOpen'
import { TldrawUiMenuContextProvider } from '../menus/TldrawUiMenuContext'
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
import { DefaultContextMenuContent } from './DefaultContextMenuContent'
/** @public */

Wyświetl plik

@ -17,7 +17,7 @@ import {
ToggleLockMenuItem,
UngroupMenuItem,
} from '../menu-items'
import { TldrawUiMenuGroup } from '../menus/TldrawUiMenuGroup'
import { TldrawUiMenuGroup } from '../primitives/menus/TldrawUiMenuGroup'
/** @public */
export function DefaultContextMenuContent() {

Wyświetl plik

@ -1,10 +1,12 @@
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TldrawUiMenuContextProvider } from '../menus/TldrawUiMenuContext'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
import {
DropdownMenuContent,
DropdownMenuRoot,
DropdownMenuTrigger,
} from '../primitives/DropdownMenu'
TldrawUiDropdownMenuContent,
TldrawUiDropdownMenuRoot,
TldrawUiDropdownMenuTrigger,
} from '../primitives/TldrawUiDropdownMenu'
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
import { DefaultDebugMenuContent } from './DefaultDebugMenuContent'
/** @public */
@ -18,13 +20,17 @@ export function DefaultDebugMenu({ children }: TLUiDebugMenuProps) {
const content = children ?? <DefaultDebugMenuContent />
return (
<DropdownMenuRoot id="debug">
<DropdownMenuTrigger type="icon" icon="dots-horizontal" title={msg('debug-panel.more')} />
<DropdownMenuContent side="top" align="end" alignOffset={0}>
<TldrawUiDropdownMenuRoot id="debug">
<TldrawUiDropdownMenuTrigger>
<TldrawUiButton type="icon" title={msg('debug-menu.title')}>
<TldrawUiButtonIcon icon="dots-horizontal" />
</TldrawUiButton>
</TldrawUiDropdownMenuTrigger>
<TldrawUiDropdownMenuContent side="top" align="end" alignOffset={0}>
<TldrawUiMenuContextProvider type="menu" sourceId="debug-panel">
{content}
</TldrawUiMenuContextProvider>
</DropdownMenuContent>
</DropdownMenuRoot>
</TldrawUiDropdownMenuContent>
</TldrawUiDropdownMenuRoot>
)
}

Wyświetl plik

@ -1,4 +1,3 @@
import { DialogTitle } from '@radix-ui/react-dialog'
import {
DebugFlag,
Editor,
@ -15,12 +14,20 @@ import React from 'react'
import { useDialogs } from '../../context/dialogs'
import { useToasts } from '../../context/toasts'
import { untranslated } from '../../hooks/useTranslation/useTranslation'
import { TldrawUiMenuCheckboxItem } from '../menus/TldrawUiMenuCheckboxItem'
import { TldrawUiMenuGroup } from '../menus/TldrawUiMenuGroup'
import { TldrawUiMenuItem } from '../menus/TldrawUiMenuItem'
import { TldrawUiMenuSubmenu } from '../menus/TldrawUiMenuSubmenu'
import { Button } from '../primitives/Button'
import { DialogBody, DialogCloseButton, DialogFooter, DialogHeader } from '../primitives/Dialog'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiButtonCheck } from '../primitives/Button/TldrawUiButtonCheck'
import { TldrawUiButtonLabel } from '../primitives/Button/TldrawUiButtonLabel'
import {
TldrawUiDialogBody,
TldrawUiDialogCloseButton,
TldrawUiDialogFooter,
TldrawUiDialogHeader,
TldrawUiDialogTitle,
} from '../primitives/TldrawUiDialog'
import { TldrawUiMenuCheckboxItem } from '../primitives/menus/TldrawUiMenuCheckboxItem'
import { TldrawUiMenuGroup } from '../primitives/menus/TldrawUiMenuGroup'
import { TldrawUiMenuItem } from '../primitives/menus/TldrawUiMenuItem'
import { TldrawUiMenuSubmenu } from '../primitives/menus/TldrawUiMenuSubmenu'
/** @public */
export function DefaultDebugMenuContent() {
@ -225,29 +232,29 @@ function ExampleDialog({
return (
<>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogCloseButton />
</DialogHeader>
<DialogBody style={{ maxWidth: 350 }}>{body}</DialogBody>
<DialogFooter className="tlui-dialog__footer__actions">
<TldrawUiDialogHeader>
<TldrawUiDialogTitle>{title}</TldrawUiDialogTitle>
<TldrawUiDialogCloseButton />
</TldrawUiDialogHeader>
<TldrawUiDialogBody style={{ maxWidth: 350 }}>{body}</TldrawUiDialogBody>
<TldrawUiDialogFooter className="tlui-dialog__footer__actions">
{displayDontShowAgain && (
<Button
<TldrawUiButton
type="normal"
onClick={() => setDontShowAgain(!dontShowAgain)}
iconLeft={dontShowAgain ? 'check' : 'checkbox-empty'}
style={{ marginRight: 'auto' }}
>
{`Don't show again`}
</Button>
<TldrawUiButtonCheck checked={dontShowAgain} />
<TldrawUiButtonLabel>Don't show again</TldrawUiButtonLabel>
</TldrawUiButton>
)}
<Button type="normal" onClick={onCancel}>
{cancel}
</Button>
<Button type="primary" onClick={async () => onContinue()}>
{confirm}
</Button>
</DialogFooter>
<TldrawUiButton type="normal" onClick={onCancel}>
<TldrawUiButtonLabel>{cancel}</TldrawUiButtonLabel>
</TldrawUiButton>
<TldrawUiButton type="primary" onClick={async () => onContinue()}>
<TldrawUiButtonLabel>{confirm}</TldrawUiButtonLabel>
</TldrawUiButton>
</TldrawUiDialogFooter>
</>
)
}

Wyświetl plik

@ -3,9 +3,15 @@ import { T, TLBaseShape, track, useEditor } from '@tldraw/editor'
import { useCallback, useEffect, useRef, useState } from 'react'
import { TLUiDialogProps } from '../context/dialogs'
import { useTranslation } from '../hooks/useTranslation/useTranslation'
import { Button } from './primitives/Button'
import { DialogBody, DialogCloseButton, DialogFooter, DialogHeader } from './primitives/Dialog'
import { Input } from './primitives/Input'
import { TldrawUiButton } from './primitives/Button/TldrawUiButton'
import { TldrawUiButtonLabel } from './primitives/Button/TldrawUiButtonLabel'
import {
TldrawUiDialogBody,
TldrawUiDialogCloseButton,
TldrawUiDialogFooter,
TldrawUiDialogHeader,
} from './primitives/TldrawUiDialog'
import { TldrawUiInput } from './primitives/TldrawUiInput'
// A url can either be invalid, or valid with a protocol, or valid without a protocol.
// For example, "aol.com" would be valid with a protocol ()
@ -134,13 +140,13 @@ export const EditLinkDialogInner = track(function EditLinkDialogInner({
return (
<>
<DialogHeader>
<TldrawUiDialogHeader>
<DialogTitle>{msg('edit-link-title')}</DialogTitle>
<DialogCloseButton />
</DialogHeader>
<DialogBody>
<TldrawUiDialogCloseButton />
</TldrawUiDialogHeader>
<TldrawUiDialogBody>
<div className="tlui-edit-link-dialog">
<Input
<TldrawUiInput
ref={rInput}
className="tlui-edit-link-dialog__input"
label="edit-link-url"
@ -152,26 +158,26 @@ export const EditLinkDialogInner = track(function EditLinkDialogInner({
/>
<div>{urlInputState.valid ? msg('edit-link-detail') : msg('edit-link-invalid-url')}</div>
</div>
</DialogBody>
<DialogFooter className="tlui-dialog__footer__actions">
<Button type="normal" onClick={handleCancel} onTouchEnd={handleCancel}>
{msg('edit-link-cancel')}
</Button>
</TldrawUiDialogBody>
<TldrawUiDialogFooter className="tlui-dialog__footer__actions">
<TldrawUiButton type="normal" onClick={handleCancel} onTouchEnd={handleCancel}>
<TldrawUiButtonLabel>{msg('edit-link-cancel')}</TldrawUiButtonLabel>
</TldrawUiButton>
{isRemoving ? (
<Button type={'danger'} onTouchEnd={handleClear} onClick={handleClear}>
{msg('edit-link-clear')}
</Button>
<TldrawUiButton type={'danger'} onTouchEnd={handleClear} onClick={handleClear}>
<TldrawUiButtonLabel>{msg('edit-link-clear')}</TldrawUiButtonLabel>
</TldrawUiButton>
) : (
<Button
<TldrawUiButton
type="primary"
disabled={!urlInputState.valid}
onTouchEnd={handleComplete}
onClick={handleComplete}
>
{msg('edit-link-save')}
</Button>
<TldrawUiButtonLabel>{msg('edit-link-save')}</TldrawUiButtonLabel>
</TldrawUiButton>
)}
</DialogFooter>
</TldrawUiDialogFooter>
</>
)
})

Wyświetl plik

@ -5,10 +5,16 @@ import { TLEmbedResult, getEmbedInfo } from '../../utils/embeds/embeds'
import { useAssetUrls } from '../context/asset-urls'
import { TLUiDialogProps } from '../context/dialogs'
import { untranslated, useTranslation } from '../hooks/useTranslation/useTranslation'
import { Button } from './primitives/Button'
import { DialogBody, DialogCloseButton, DialogFooter, DialogHeader } from './primitives/Dialog'
import { Icon } from './primitives/Icon'
import { Input } from './primitives/Input'
import { TldrawUiButton } from './primitives/Button/TldrawUiButton'
import { TldrawUiButtonLabel } from './primitives/Button/TldrawUiButtonLabel'
import {
TldrawUiDialogBody,
TldrawUiDialogCloseButton,
TldrawUiDialogFooter,
TldrawUiDialogHeader,
} from './primitives/TldrawUiDialog'
import { TldrawUiIcon } from './primitives/TldrawUiIcon'
import { TldrawUiInput } from './primitives/TldrawUiInput'
export const EmbedDialog = track(function EmbedDialog({ onClose }: TLUiDialogProps) {
const editor = useEditor()
@ -30,18 +36,18 @@ export const EmbedDialog = track(function EmbedDialog({ onClose }: TLUiDialogPro
return (
<>
<DialogHeader>
<TldrawUiDialogHeader>
<DialogTitle>
{embedDefinition
? `${msg('embed-title')}${embedDefinition.title}`
: msg('embed-title')}
</DialogTitle>
<DialogCloseButton />
</DialogHeader>
<TldrawUiDialogCloseButton />
</TldrawUiDialogHeader>
{embedDefinition ? (
<>
<DialogBody className="tlui-embed-dialog__enter">
<Input
<TldrawUiDialogBody className="tlui-embed-dialog__enter">
<TldrawUiInput
className="tlui-embed-dialog__input"
label="embed-url"
placeholder="http://example.com"
@ -77,7 +83,7 @@ export const EmbedDialog = track(function EmbedDialog({ onClose }: TLUiDialogPro
className="tlui-embed-dialog__instruction__link"
>
Learn more.
<Icon icon="external-link" small />
<TldrawUiIcon icon="external-link" small />
</a>
)}
</div>
@ -86,23 +92,25 @@ export const EmbedDialog = track(function EmbedDialog({ onClose }: TLUiDialogPro
{showError ? msg('embed-invalid-url') : '\xa0'}
</div>
)}
</DialogBody>
<DialogFooter className="tlui-dialog__footer__actions">
<Button
</TldrawUiDialogBody>
<TldrawUiDialogFooter className="tlui-dialog__footer__actions">
<TldrawUiButton
type="normal"
onClick={() => {
setEmbedDefinition(null)
setEmbedInfoForUrl(null)
setUrl('')
}}
label="embed-back"
/>
>
<TldrawUiButtonLabel>{msg('embed-back')}</TldrawUiButtonLabel>
</TldrawUiButton>
<div className="tlui-embed__spacer" />
<Button type="normal" label="embed-cancel" onClick={onClose} />
<Button
<TldrawUiButton type="normal" onClick={onClose}>
<TldrawUiButtonLabel>{msg('embed-cancel')}</TldrawUiButtonLabel>
</TldrawUiButton>
<TldrawUiButton
type="primary"
disabled={!embedInfoForUrl}
label="embed-create"
onClick={() => {
if (!embedInfoForUrl) return
@ -115,28 +123,26 @@ export const EmbedDialog = track(function EmbedDialog({ onClose }: TLUiDialogPro
onClose()
}}
/>
</DialogFooter>
>
<TldrawUiButtonLabel>{msg('embed-create')}</TldrawUiButtonLabel>
</TldrawUiButton>
</TldrawUiDialogFooter>
</>
) : (
<>
<DialogBody className="tlui-embed-dialog__list">
<TldrawUiDialogBody className="tlui-embed-dialog__list">
{EMBED_DEFINITIONS.map((def) => {
return (
<Button
type="menu"
key={def.type}
onClick={() => setEmbedDefinition(def)}
label={untranslated(def.title)}
>
<TldrawUiButton type="menu" key={def.type} onClick={() => setEmbedDefinition(def)}>
<TldrawUiButtonLabel>{untranslated(def.title)}</TldrawUiButtonLabel>
<div
className="tlui-embed-dialog__item__image"
style={{ backgroundImage: `url(${assetUrls.embedIcons[def.type]})` }}
/>
</Button>
</TldrawUiButton>
)
})}
</DialogBody>
</TldrawUiDialogBody>
</>
)}
</>

Wyświetl plik

@ -2,12 +2,14 @@ import { memo } from 'react'
import { PORTRAIT_BREAKPOINT } from '../../constants'
import { useBreakpoint } from '../../context/breakpoints'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TldrawUiMenuContextProvider } from '../menus/TldrawUiMenuContext'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
import {
DropdownMenuContent,
DropdownMenuRoot,
DropdownMenuTrigger,
} from '../primitives/DropdownMenu'
TldrawUiDropdownMenuContent,
TldrawUiDropdownMenuRoot,
TldrawUiDropdownMenuTrigger,
} from '../primitives/TldrawUiDropdownMenu'
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
import { DefaultHelpMenuContent } from './DefaultHelpMenuContent'
/** @public */
@ -29,19 +31,18 @@ export const DefaultHelpMenu = memo(function DefaultHelpMenu({ children }: TLUiH
return (
<div className="tlui-help-menu">
<DropdownMenuRoot id="help menu">
<DropdownMenuTrigger
type="help"
smallIcon
title={msg('help-menu.title')}
icon="question-mark"
/>
<DropdownMenuContent side="top" align="end" alignOffset={0} sideOffset={8}>
<TldrawUiDropdownMenuRoot id="help menu">
<TldrawUiDropdownMenuTrigger>
<TldrawUiButton type="help" title={msg('help-menu.title')}>
<TldrawUiButtonIcon icon="question-mark" small />
</TldrawUiButton>
</TldrawUiDropdownMenuTrigger>
<TldrawUiDropdownMenuContent side="top" align="end" alignOffset={0} sideOffset={8}>
<TldrawUiMenuContextProvider type="menu" sourceId="help-menu">
{content}
</TldrawUiMenuContextProvider>
</DropdownMenuContent>
</DropdownMenuRoot>
</TldrawUiDropdownMenuContent>
</TldrawUiDropdownMenuRoot>
</div>
)
})

Wyświetl plik

@ -1,7 +1,7 @@
import { useTldrawUiComponents } from '../../context/components'
import { useDialogs } from '../../context/dialogs'
import { LanguageMenu } from '../LanguageMenu'
import { TldrawUiMenuItem } from '../menus/TldrawUiMenuItem'
import { TldrawUiMenuItem } from '../primitives/menus/TldrawUiMenuItem'
/** @public */
export function DefaultHelpMenuContent() {

Wyświetl plik

@ -1,7 +1,7 @@
import { useEditor } from '@tldraw/editor'
import { useEffect, useState } from 'react'
import { useActions } from '../../context/actions'
import { TldrawUiMenuItem } from '../menus/TldrawUiMenuItem'
import { TldrawUiMenuItem } from '../primitives/menus/TldrawUiMenuItem'
export function BackToContent() {
const editor = useEditor()

Wyświetl plik

@ -1,4 +1,4 @@
import { TldrawUiMenuContextProvider } from '../menus/TldrawUiMenuContext'
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
import { DefaultHelperButtonsContent } from './DefaultHelperButtonsContent'
/** @public */

Wyświetl plik

@ -1,6 +1,6 @@
import { useEditor, useValue } from '@tldraw/editor'
import { useActions } from '../../context/actions'
import { TldrawUiMenuItem } from '../menus/TldrawUiMenuItem'
import { TldrawUiMenuItem } from '../primitives/menus/TldrawUiMenuItem'
export function ExitPenMode() {
const editor = useEditor()

Wyświetl plik

@ -1,6 +1,6 @@
import { useEditor, useValue } from '@tldraw/editor'
import { useActions } from '../../context/actions'
import { TldrawUiMenuItem } from '../menus/TldrawUiMenuItem'
import { TldrawUiMenuItem } from '../primitives/menus/TldrawUiMenuItem'
export function StopFollowing() {
const editor = useEditor()

Wyświetl plik

@ -2,8 +2,12 @@ import { DialogTitle } from '@radix-ui/react-dialog'
import { memo } from 'react'
import { TLUiDialogProps } from '../../context/dialogs'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TldrawUiMenuContextProvider } from '../menus/TldrawUiMenuContext'
import { DialogBody, DialogCloseButton, DialogHeader } from '../primitives/Dialog'
import {
TldrawUiDialogBody,
TldrawUiDialogCloseButton,
TldrawUiDialogHeader,
} from '../primitives/TldrawUiDialog'
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
import { DefaultKeyboardShortcutsDialogContent } from './DefaultKeyboardShortcutsDialogContent'
/** @public */
@ -21,15 +25,15 @@ export const DefaultKeyboardShortcutsDialog = memo(function DefaultKeyboardShort
return (
<>
<DialogHeader className="tlui-shortcuts-dialog__header">
<TldrawUiDialogHeader className="tlui-shortcuts-dialog__header">
<DialogTitle>{msg('shortcuts-title')}</DialogTitle>
<DialogCloseButton />
</DialogHeader>
<DialogBody className="tlui-shortcuts-dialog__body">
<TldrawUiDialogCloseButton />
</TldrawUiDialogHeader>
<TldrawUiDialogBody className="tlui-shortcuts-dialog__body">
<TldrawUiMenuContextProvider type="keyboard-shortcuts" sourceId="kbd">
{content}
</TldrawUiMenuContextProvider>
</DialogBody>
</TldrawUiDialogBody>
<div className="tlui-dialog__scrim" />
</>
)

Wyświetl plik

@ -1,7 +1,7 @@
import { useActions } from '../../context/actions'
import { useTools } from '../../hooks/useTools'
import { TldrawUiMenuGroup } from '../menus/TldrawUiMenuGroup'
import { TldrawUiMenuItem } from '../menus/TldrawUiMenuItem'
import { TldrawUiMenuGroup } from '../primitives/menus/TldrawUiMenuGroup'
import { TldrawUiMenuItem } from '../primitives/menus/TldrawUiMenuItem'
/** @public */
export function DefaultKeyboardShortcutsDialogContent() {

Wyświetl plik

@ -1,9 +1,9 @@
import { useEditor } from '@tldraw/editor'
import { useUiEvents } from '../context/events'
import { useLanguages } from '../hooks/useTranslation/useLanguages'
import { TldrawUiMenuCheckboxItem } from './menus/TldrawUiMenuCheckboxItem'
import { TldrawUiMenuGroup } from './menus/TldrawUiMenuGroup'
import { TldrawUiMenuSubmenu } from './menus/TldrawUiMenuSubmenu'
import { TldrawUiMenuCheckboxItem } from './primitives/menus/TldrawUiMenuCheckboxItem'
import { TldrawUiMenuGroup } from './primitives/menus/TldrawUiMenuGroup'
import { TldrawUiMenuSubmenu } from './primitives/menus/TldrawUiMenuSubmenu'
export function LanguageMenu() {
const editor = useEditor()

Wyświetl plik

@ -3,8 +3,9 @@ import { useContainer } from '@tldraw/editor'
import { memo } from 'react'
import { useMenuIsOpen } from '../../hooks/useMenuIsOpen'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TldrawUiMenuContextProvider } from '../menus/TldrawUiMenuContext'
import { Button } from '../primitives/Button'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
import { DefaultMainMenuContent } from './DefaultMainMenuContent'
/** @public */
@ -26,14 +27,9 @@ export const DefaultMainMenu = memo(function DefaultMainMenu({ children }: TLUiM
return (
<_Dropdown.Root dir="ltr" open={isOpen} onOpenChange={onOpenChange} modal={false}>
<_Dropdown.Trigger asChild dir="ltr">
<Button
type="icon"
className="tlui-menu__trigger"
data-testid="main.menu"
title={msg('menu.title')}
icon="menu"
smallIcon
/>
<TldrawUiButton type="icon" data-testid="main.menu" title={msg('menu.title')}>
<TldrawUiButtonIcon icon="menu" small />
</TldrawUiButton>
</_Dropdown.Trigger>
<_Dropdown.Portal container={container}>
<_Dropdown.Content

Wyświetl plik

@ -29,9 +29,9 @@ import {
ZoomToFitMenuItem,
ZoomToSelectionMenuItem,
} from '../menu-items'
import { TldrawUiMenuGroup } from '../menus/TldrawUiMenuGroup'
import { TldrawUiMenuItem } from '../menus/TldrawUiMenuItem'
import { TldrawUiMenuSubmenu } from '../menus/TldrawUiMenuSubmenu'
import { TldrawUiMenuGroup } from '../primitives/menus/TldrawUiMenuGroup'
import { TldrawUiMenuItem } from '../primitives/menus/TldrawUiMenuItem'
import { TldrawUiMenuSubmenu } from '../primitives/menus/TldrawUiMenuSubmenu'
/** @public */
export function DefaultMainMenuContent() {

Wyświetl plik

@ -0,0 +1,27 @@
import { memo } from 'react'
import { useBreakpoint } from '../context/breakpoints'
import { useTldrawUiComponents } from '../context/components'
/** @public */
export const DefaultMenuPanel = memo(function MenuPanel() {
const breakpoint = useBreakpoint()
const { MainMenu, QuickActions, ActionsMenu, PageMenu } = useTldrawUiComponents()
if (!MainMenu && !PageMenu && breakpoint < 6) return null
return (
<div className="tlui-menu-zone">
<div className="tlui-buttons__horizontal">
{MainMenu && <MainMenu />}
{PageMenu && <PageMenu />}
{breakpoint < 6 ? null : (
<>
{QuickActions && <QuickActions />}
{ActionsMenu && <ActionsMenu />}
</>
)}
</div>
</div>
)
})

Wyświetl plik

@ -9,8 +9,13 @@ import { useCallback } from 'react'
import { useTldrawUiComponents } from '../context/components'
import { useRelevantStyles } from '../hooks/useRevelantStyles'
import { useTranslation } from '../hooks/useTranslation/useTranslation'
import { Icon } from './primitives/Icon'
import { Popover, PopoverContent, PopoverTrigger } from './primitives/Popover'
import { TldrawUiButton } from './primitives/Button/TldrawUiButton'
import { TldrawUiButtonIcon } from './primitives/Button/TldrawUiButtonIcon'
import {
TldrawUiPopover,
TldrawUiPopoverContent,
TldrawUiPopoverTrigger,
} from './primitives/TldrawUiPopover'
export function MobileStylePanel() {
const editor = useEditor()
@ -42,22 +47,24 @@ export function MobileStylePanel() {
if (!StylePanel) return null
return (
<Popover id="style menu" onOpenChange={handleStylesOpenChange}>
<PopoverTrigger
disabled={disableStylePanel}
type="tool"
data-testid="mobile.styles"
style={{
color: disableStylePanel ? 'var(--color-muted-1)' : currentColor,
}}
title={msg('style-panel.title')}
>
<Icon icon={disableStylePanel ? 'blob' : color?.type === 'mixed' ? 'mixed' : 'blob'} />
</PopoverTrigger>
<PopoverContent side="top" align="end">
<TldrawUiPopover id="mobile style menu" onOpenChange={handleStylesOpenChange}>
<TldrawUiPopoverTrigger>
<TldrawUiButton
type="tool"
title={msg('style-panel.title')}
data-testid="mobile.styles"
disabled={disableStylePanel}
style={{ color: disableStylePanel ? 'var(--color-muted-1)' : currentColor }}
>
<TldrawUiButtonIcon
icon={disableStylePanel ? 'blob' : color?.type === 'mixed' ? 'mixed' : 'blob'}
/>
</TldrawUiButton>
</TldrawUiPopoverTrigger>
<TldrawUiPopoverContent side="top" align="end">
<_StylePanel />
</PopoverContent>
</Popover>
</TldrawUiPopoverContent>
</TldrawUiPopover>
)
}
@ -66,5 +73,5 @@ function _StylePanel() {
const relevantStyles = useRelevantStyles()
if (!StylePanel) return null
return <StylePanel relevantStyles={relevantStyles} />
return <StylePanel relevantStyles={relevantStyles} isMobile />
}

Wyświetl plik

@ -5,8 +5,9 @@ import { useBreakpoint } from '../../context/breakpoints'
import { useTldrawUiComponents } from '../../context/components'
import { useLocalStorageState } from '../../hooks/useLocalStorageState'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { Button } from '../primitives/Button'
import { kbdStr } from '../primitives/shared'
import { kbdStr } from '../../kbd-utils'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
/** @public */
export const DefaultNavigationPanel = memo(function DefaultNavigationPanel() {
@ -35,42 +36,46 @@ export const DefaultNavigationPanel = memo(function DefaultNavigationPanel() {
<>
{ZoomMenu && <ZoomMenu />}
{Minimap && (
<Button
<TldrawUiButton
type="icon"
icon={collapsed ? 'chevrons-ne' : 'chevrons-sw'}
data-testid="minimap.toggle"
title={msg('navigation-zone.toggle-minimap')}
className="tlui-navigation-panel__toggle"
onClick={toggleMinimap}
/>
>
<TldrawUiButtonIcon icon={collapsed ? 'chevrons-ne' : 'chevrons-sw'} />
</TldrawUiButton>
)}
</>
) : (
<>
<Button
<TldrawUiButton
type="icon"
icon="minus"
data-testid="minimap.zoom-out"
title={`${msg(unwrapLabel(actions['zoom-out'].label))} ${kbdStr(actions['zoom-out'].kbd!)}`}
onClick={() => actions['zoom-out'].onSelect('navigation-zone')}
/>
>
<TldrawUiButtonIcon icon="minus" />
</TldrawUiButton>
{ZoomMenu && <ZoomMenu />}
<Button
<TldrawUiButton
type="icon"
icon="plus"
data-testid="minimap.zoom-in"
title={`${msg(unwrapLabel(actions['zoom-in'].label))} ${kbdStr(actions['zoom-in'].kbd!)}`}
onClick={() => actions['zoom-in'].onSelect('navigation-zone')}
/>
>
<TldrawUiButtonIcon icon="plus" />
</TldrawUiButton>
{Minimap && (
<Button
<TldrawUiButton
type="icon"
icon={collapsed ? 'chevrons-ne' : 'chevrons-sw'}
data-testid="minimap.toggle"
title={msg('navigation-zone.toggle-minimap')}
className="tlui-navigation-panel__toggle"
onClick={toggleMinimap}
/>
>
<TldrawUiButtonIcon icon={collapsed ? 'chevrons-ne' : 'chevrons-sw'} />
</TldrawUiButton>
)}
</>
)}

Wyświetl plik

@ -1,7 +1,7 @@
import classNames from 'classnames'
import { useRef } from 'react'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { Icon } from '../primitives/Icon'
import { TldrawUiIcon } from '../primitives/TldrawUiIcon'
/** @public */
export function OfflineIndicator() {
@ -11,7 +11,7 @@ export function OfflineIndicator() {
return (
<div className={classNames('tlui-offline-indicator')} ref={rContainer}>
{msg('status.offline')}
<Icon aria-label="offline" icon="status-offline" small />
<TldrawUiIcon aria-label="offline" icon="status-offline" small />
</div>
)
}

Wyświetl plik

@ -13,9 +13,15 @@ import { useBreakpoint } from '../../context/breakpoints'
import { useMenuIsOpen } from '../../hooks/useMenuIsOpen'
import { useReadonly } from '../../hooks/useReadonly'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { Button } from '../primitives/Button'
import { Icon } from '../primitives/Icon'
import { Popover, PopoverContent, PopoverTrigger } from '../primitives/Popover'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiButtonCheck } from '../primitives/Button/TldrawUiButtonCheck'
import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
import { TldrawUiButtonLabel } from '../primitives/Button/TldrawUiButtonLabel'
import {
TldrawUiPopover,
TldrawUiPopoverContent,
TldrawUiPopoverTrigger,
} from '../primitives/TldrawUiPopover'
import { PageItemInput } from './PageItemInput'
import { PageItemSubmenu } from './PageItemSubmenu'
import { onMovePage } from './edit-pages-shared'
@ -255,33 +261,30 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() {
}, [editor, msg, isReadonlyMode])
return (
<Popover id="pages" onOpenChange={onOpenChange} open={isOpen}>
<PopoverTrigger
className="tlui-page-menu__trigger tlui-menu__trigger"
data-testid="main.page-menu"
icon="chevron-down"
type="menu"
title={currentPage.name}
>
<div className="tlui-page-menu__name">{currentPage.name}</div>
</PopoverTrigger>
<PopoverContent side="bottom" align="start" sideOffset={6}>
<TldrawUiPopover id="pages" onOpenChange={onOpenChange} open={isOpen}>
<TldrawUiPopoverTrigger data-testid="main.page-menu">
<TldrawUiButton type="menu" title={currentPage.name} className="tlui-page-menu__trigger">
<div className="tlui-page-menu__name">{currentPage.name}</div>
<TldrawUiButtonIcon icon="chevron-down" small />
</TldrawUiButton>
</TldrawUiPopoverTrigger>
<TldrawUiPopoverContent side="bottom" align="start" sideOffset={6}>
<div className="tlui-page-menu__wrapper">
<div className="tlui-page-menu__header">
<div className="tlui-page-menu__header__title">{msg('page-menu.title')}</div>
{!isReadonlyMode && (
<div className="tlui-buttons__horizontal">
<Button
<TldrawUiButton
type="icon"
data-testid="page-menu.edit"
title={msg(isEditing ? 'page-menu.edit-done' : 'page-menu.edit-start')}
icon={isEditing ? 'check' : 'edit'}
onClick={toggleEditing}
/>
<Button
>
<TldrawUiButtonIcon icon={isEditing ? 'check' : 'edit'} />
</TldrawUiButton>
<TldrawUiButton
type="icon"
data-testid="page-menu.create"
icon="plus"
title={msg(
maxPageCountReached
? 'page-menu.max-page-count-reached'
@ -289,7 +292,9 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() {
)}
disabled={maxPageCountReached}
onClick={handleCreatePageClick}
/>
>
<TldrawUiButtonIcon icon="plus" />
</TldrawUiButton>
</div>
)}
</div>
@ -314,24 +319,25 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() {
transform: `translate(0px, ${position.y + position.offsetY}px)`,
}}
>
<Button
<TldrawUiButton
type="icon"
tabIndex={-1}
className="tlui-page_menu__item__sortable__handle"
icon="drag-handle-dots"
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
onPointerMove={handlePointerMove}
onKeyDown={handleKeyDown}
data-id={page.id}
data-index={index}
/>
>
<TldrawUiButtonIcon icon="drag-handle-dots" />
</TldrawUiButton>
{breakpoint < PORTRAIT_BREAKPOINT.TABLET_SM && isCoarsePointer ? (
// sigh, this is a workaround for iOS Safari
// because the device and the radix popover seem
// to be fighting over scroll position. Nothing
// else seems to work!
<Button
<TldrawUiButton
type="normal"
className="tlui-page-menu__item__button"
onClick={() => {
@ -341,10 +347,10 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() {
}
}}
onDoubleClick={toggleEditing}
isChecked={page.id === currentPage.id}
>
<span>{page.name}</span>
</Button>
<TldrawUiButtonCheck checked={page.id === currentPage.id} />
<TldrawUiButtonLabel>{page.name}</TldrawUiButtonLabel>
</TldrawUiButton>
) : (
<div
className="tlui-page_menu__item__sortable__title"
@ -369,19 +375,16 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() {
data-testid={`page-menu-item-${page.id}`}
className="tlui-page-menu__item"
>
<Button
<TldrawUiButton
type="normal"
className="tlui-page-menu__item__button tlui-page-menu__item__button__checkbox"
className="tlui-page-menu__item__button"
onClick={() => editor.setCurrentPage(page.id)}
onDoubleClick={toggleEditing}
isChecked={page.id === currentPage.id}
title={msg('page-menu.go-to-page')}
>
<div className="tlui-page-menu__item__button__check">
{page.id === currentPage.id && <Icon icon="check" />}
</div>
<span>{page.name}</span>
</Button>
<TldrawUiButtonCheck checked={page.id === currentPage.id} />
<TldrawUiButtonLabel>{page.name}</TldrawUiButtonLabel>
</TldrawUiButton>
{!isReadonlyMode && (
<div className="tlui-page_menu__item__submenu">
<PageItemSubmenu
@ -409,7 +412,7 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() {
})}
</div>
</div>
</PopoverContent>
</Popover>
</TldrawUiPopoverContent>
</TldrawUiPopover>
)
})

Wyświetl plik

@ -1,6 +1,6 @@
import { TLPageId, useEditor } from '@tldraw/editor'
import { useCallback, useRef } from 'react'
import { Input } from '../primitives/Input'
import { TldrawUiInput } from '../primitives/TldrawUiInput'
export const PageItemInput = function PageItemInput({
name,
@ -31,7 +31,7 @@ export const PageItemInput = function PageItemInput({
)
return (
<Input
<TldrawUiInput
className="tlui-page-menu__item__input"
ref={(el) => (rInput.current = el)}
defaultValue={name}

Wyświetl plik

@ -1,14 +1,16 @@
import { MAX_PAGES, PageRecordType, TLPageId, track, useEditor } from '@tldraw/editor'
import { useCallback } from 'react'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TldrawUiMenuContextProvider } from '../menus/TldrawUiMenuContext'
import { TldrawUiMenuGroup } from '../menus/TldrawUiMenuGroup'
import { TldrawUiMenuItem } from '../menus/TldrawUiMenuItem'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
import {
DropdownMenuContent,
DropdownMenuRoot,
DropdownMenuTrigger,
} from '../primitives/DropdownMenu'
TldrawUiDropdownMenuContent,
TldrawUiDropdownMenuRoot,
TldrawUiDropdownMenuTrigger,
} from '../primitives/TldrawUiDropdownMenu'
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
import { TldrawUiMenuGroup } from '../primitives/menus/TldrawUiMenuGroup'
import { TldrawUiMenuItem } from '../primitives/menus/TldrawUiMenuItem'
import { onMovePage } from './edit-pages-shared'
export interface PageItemSubmenuProps {
@ -48,13 +50,13 @@ export const PageItemSubmenu = track(function PageItemSubmenu({
}, [editor, item])
return (
<DropdownMenuRoot id={`page item submenu ${index}`}>
<DropdownMenuTrigger
type="icon"
title={msg('page-menu.submenu.title')}
icon="dots-vertical"
/>
<DropdownMenuContent alignOffset={0} side="right" sideOffset={-4}>
<TldrawUiDropdownMenuRoot id={`page item submenu ${index}`}>
<TldrawUiDropdownMenuTrigger>
<TldrawUiButton type="icon" title={msg('page-menu.submenu.title')}>
<TldrawUiButtonIcon icon="dots-vertical" />
</TldrawUiButton>
</TldrawUiDropdownMenuTrigger>
<TldrawUiDropdownMenuContent alignOffset={0} side="right" sideOffset={-4}>
<TldrawUiMenuContextProvider type="menu" sourceId="page-menu">
<TldrawUiMenuGroup id="modify">
{onRename && (
@ -87,7 +89,7 @@ export const PageItemSubmenu = track(function PageItemSubmenu({
</TldrawUiMenuGroup>
)}
</TldrawUiMenuContextProvider>
</DropdownMenuContent>
</DropdownMenuRoot>
</TldrawUiDropdownMenuContent>
</TldrawUiDropdownMenuRoot>
)
})

Wyświetl plik

@ -1,5 +1,5 @@
import { memo } from 'react'
import { TldrawUiMenuContextProvider } from '../menus/TldrawUiMenuContext'
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
import { DefaultQuickActionsContent } from './DefaultQuickActionsContent'
/** @public */

Wyświetl plik

@ -2,7 +2,7 @@ import { useEditor, useValue } from '@tldraw/editor'
import { useActions } from '../../context/actions'
import { useCanRedo, useCanUndo, useUnlockedSelectedShapesCount } from '../../hooks/menu-hooks'
import { useReadonly } from '../../hooks/useReadonly'
import { TldrawUiMenuItem } from '../menus/TldrawUiMenuItem'
import { TldrawUiMenuItem } from '../primitives/menus/TldrawUiMenuItem'
/** @public */
export function DefaultQuickActionsContent() {

Wyświetl plik

@ -33,7 +33,7 @@ export const DefaultStylePanel = memo(function DefaultStylePanel({
return (
<div
className={classNames('tlui-style-panel', { 'tlui-style-panel__wrapper': !isMobile })}
className={classNames('', { 'tlui-style-panel__wrapper': !isMobile })}
data-ismobile={isMobile}
onPointerLeave={handlePointerOut}
>

Wyświetl plik

@ -17,15 +17,16 @@ import {
useEditor,
} from '@tldraw/editor'
import React from 'react'
import { STYLES } from '../../../styles'
import { useUiEvents } from '../../context/events'
import { useRelevantStyles } from '../../hooks/useRevelantStyles'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { Button } from '../primitives/Button'
import { ButtonPicker } from '../primitives/ButtonPicker'
import { Slider } from '../primitives/Slider'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
import { TldrawUiButtonPicker } from '../primitives/TldrawUiButtonPicker'
import { TldrawUiSlider } from '../primitives/TldrawUiSlider'
import { DoubleDropdownPicker } from './DoubleDropdownPicker'
import { DropdownPicker } from './DropdownPicker'
import { STYLES } from './styles'
/** @public */
export type TLUiStylePanelContentProps = {
@ -137,7 +138,7 @@ function CommonStylePickerSet({
aria-label="style panel styles"
>
{color === undefined ? null : (
<ButtonPicker
<TldrawUiButtonPicker
title={msg('style-panel.color')}
uiType="color"
style={DefaultColorStyle}
@ -147,7 +148,7 @@ function CommonStylePickerSet({
/>
)}
{opacity === undefined ? null : (
<Slider
<TldrawUiSlider
data-testid="style.opacity"
value={opacityIndex >= 0 ? opacityIndex : tldrawSupportedOpacities.length - 1}
label={
@ -162,7 +163,7 @@ function CommonStylePickerSet({
{showPickers && (
<div className="tlui-style-panel__section" aria-label="style panel styles">
{fill === undefined ? null : (
<ButtonPicker
<TldrawUiButtonPicker
title={msg('style-panel.fill')}
uiType="fill"
style={DefaultFillStyle}
@ -172,7 +173,7 @@ function CommonStylePickerSet({
/>
)}
{dash === undefined ? null : (
<ButtonPicker
<TldrawUiButtonPicker
title={msg('style-panel.dash')}
uiType="dash"
style={DefaultDashStyle}
@ -182,7 +183,7 @@ function CommonStylePickerSet({
/>
)}
{size === undefined ? null : (
<ButtonPicker
<TldrawUiButtonPicker
title={msg('style-panel.size')}
uiType="size"
style={DefaultSizeStyle}
@ -211,7 +212,7 @@ function TextStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
return (
<div className="tlui-style-panel__section" aria-label="style panel text">
{font === undefined ? null : (
<ButtonPicker
<TldrawUiButtonPicker
title={msg('style-panel.font')}
uiType="font"
style={DefaultFontStyle}
@ -223,7 +224,7 @@ function TextStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
{align === undefined ? null : (
<div className="tlui-style-panel__row">
<ButtonPicker
<TldrawUiButtonPicker
title={msg('style-panel.align')}
uiType="align"
style={DefaultHorizontalAlignStyle}
@ -233,13 +234,14 @@ function TextStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
/>
<div className="tlui-style-panel__row__extra-button">
{verticalAlign === undefined ? (
<Button
<TldrawUiButton
type="icon"
title={msg('style-panel.vertical-align')}
data-testid="vertical-align"
icon="vertical-align-center"
disabled
/>
>
<TldrawUiButtonIcon icon="vertical-align-center" />
</TldrawUiButton>
) : (
<DropdownPicker
type="icon"

Wyświetl plik

@ -1,15 +1,16 @@
import { SharedStyle, StyleProp } from '@tldraw/editor'
import * as React from 'react'
import { StyleValuesForUi } from '../../../styles'
import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TLUiIconType } from '../../icon-types'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRoot,
DropdownMenuTrigger,
} from '../primitives/DropdownMenu'
import { StyleValuesForUi } from './styles'
TldrawUiDropdownMenuContent,
TldrawUiDropdownMenuItem,
TldrawUiDropdownMenuRoot,
TldrawUiDropdownMenuTrigger,
} from '../primitives/TldrawUiDropdownMenu'
interface DoubleDropdownPickerProps<T extends string> {
uiTypeA: string
@ -63,78 +64,76 @@ export const DoubleDropdownPicker = React.memo(function DoubleDropdownPicker<T e
{msg(label)}
</div>
<div className="tlui-buttons__horizontal">
<DropdownMenuRoot id={`style panel ${uiTypeA} A`}>
<DropdownMenuTrigger
type="icon"
data-testid={`style.${uiTypeA}`}
title={
msg(labelA) +
' — ' +
(valueA === null || valueA.type === 'mixed'
? msg('style-panel.mixed')
: msg(`${uiTypeA}-style.${valueA.value}` as TLUiTranslationKey))
}
icon={iconA as any}
invertIcon
smallIcon
/>
<DropdownMenuContent side="bottom" align="end" sideOffset={0} alignOffset={-2}>
<TldrawUiDropdownMenuRoot id={`style panel ${uiTypeA} A`}>
<TldrawUiDropdownMenuTrigger>
<TldrawUiButton
type="icon"
data-testid={`style.${uiTypeA}`}
title={
msg(labelA) +
' — ' +
(valueA === null || valueA.type === 'mixed'
? msg('style-panel.mixed')
: msg(`${uiTypeA}-style.${valueA.value}` as TLUiTranslationKey))
}
>
<TldrawUiButtonIcon icon={iconA} small invertIcon />
</TldrawUiButton>
</TldrawUiDropdownMenuTrigger>
<TldrawUiDropdownMenuContent side="bottom" align="end" sideOffset={0} alignOffset={-2}>
<div className="tlui-buttons__grid">
{itemsA.map((item) => {
return (
<DropdownMenuItem
type="icon"
title={
msg(labelA) +
' — ' +
msg(`${uiTypeA}-style.${item.value}` as TLUiTranslationKey)
}
data-testid={`style.${uiTypeA}.${item.value}`}
key={item.value}
icon={item.icon as TLUiIconType}
onClick={() => onValueChange(styleA, item.value, false)}
invertIcon
/>
<TldrawUiDropdownMenuItem data-testid={`style.${uiTypeA}.${item.value}`}>
<TldrawUiButton
type="icon"
key={item.value}
onClick={() => onValueChange(styleA, item.value, false)}
title={`${msg(labelA)}${msg(`${uiTypeA}-style.${item.value}`)}`}
>
<TldrawUiButtonIcon icon={item.icon} invertIcon />
</TldrawUiButton>
</TldrawUiDropdownMenuItem>
)
})}
</div>
</DropdownMenuContent>
</DropdownMenuRoot>
<DropdownMenuRoot id={`style panel ${uiTypeB}`}>
<DropdownMenuTrigger
type="icon"
data-testid={`style.${uiTypeB}`}
title={
msg(labelB) +
' — ' +
(valueB === null || valueB.type === 'mixed'
? msg('style-panel.mixed')
: msg(`${uiTypeB}-style.${valueB.value}` as TLUiTranslationKey))
}
icon={iconB as any}
smallIcon
/>
<DropdownMenuContent side="bottom" align="end" sideOffset={0} alignOffset={-2}>
</TldrawUiDropdownMenuContent>
</TldrawUiDropdownMenuRoot>
<TldrawUiDropdownMenuRoot id={`style panel ${uiTypeB}`}>
<TldrawUiDropdownMenuTrigger>
<TldrawUiButton
type="icon"
data-testid={`style.${uiTypeB}`}
title={
msg(labelB) +
' — ' +
(valueB === null || valueB.type === 'mixed'
? msg('style-panel.mixed')
: msg(`${uiTypeB}-style.${valueB.value}` as TLUiTranslationKey))
}
>
<TldrawUiButtonIcon icon={iconB} small />
</TldrawUiButton>
</TldrawUiDropdownMenuTrigger>
<TldrawUiDropdownMenuContent side="bottom" align="end" sideOffset={0} alignOffset={-2}>
<div className="tlui-buttons__grid">
{itemsB.map((item) => {
return (
<DropdownMenuItem
type="icon"
title={
msg(labelB) +
' — ' +
msg(`${uiTypeB}-style.${item.value}` as TLUiTranslationKey)
}
data-testid={`style.${uiTypeB}.${item.value}`}
key={item.value}
icon={item.icon as TLUiIconType}
onClick={() => onValueChange(styleB, item.value, false)}
/>
<TldrawUiDropdownMenuItem key={item.value}>
<TldrawUiButton
type="icon"
title={`${msg(labelB)}${msg(`${uiTypeB}-style.${item.value}` as TLUiTranslationKey)}`}
data-testid={`style.${uiTypeB}.${item.value}`}
onClick={() => onValueChange(styleB, item.value, false)}
>
<TldrawUiButtonIcon icon={item.icon} />
</TldrawUiButton>
</TldrawUiDropdownMenuItem>
)
})}
</div>
</DropdownMenuContent>
</DropdownMenuRoot>
</TldrawUiDropdownMenuContent>
</TldrawUiDropdownMenuRoot>
</div>
</div>
)

Wyświetl plik

@ -1,16 +1,18 @@
import { SharedStyle, StyleProp } from '@tldraw/editor'
import * as React from 'react'
import { StyleValuesForUi } from '../../../styles'
import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TLUiIconType } from '../../icon-types'
import { TLUiButtonProps } from '../primitives/Button'
import { TLUiButtonProps, TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
import { TldrawUiButtonLabel } from '../primitives/Button/TldrawUiButtonLabel'
import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRoot,
DropdownMenuTrigger,
} from '../primitives/DropdownMenu'
import { StyleValuesForUi } from './styles'
TldrawUiDropdownMenuContent,
TldrawUiDropdownMenuItem,
TldrawUiDropdownMenuRoot,
TldrawUiDropdownMenuTrigger,
} from '../primitives/TldrawUiDropdownMenu'
interface DropdownPickerProps<T extends string> {
id: string
@ -40,35 +42,38 @@ export const DropdownPicker = React.memo(function DropdownPicker<T extends strin
[items, value]
)
const titleStr =
value.type === 'mixed'
? msg('style-panel.mixed')
: msg(`${uiType}-style.${value.value}` as TLUiTranslationKey)
const labelStr = label ? msg(label) : ''
return (
<DropdownMenuRoot id={`style panel ${id}`}>
<DropdownMenuTrigger
type={type}
data-testid={`style.${uiType}`}
title={
value.type === 'mixed'
? msg('style-panel.mixed')
: msg(`${uiType}-style.${value.value}` as TLUiTranslationKey)
}
label={label}
icon={(icon as TLUiIconType) ?? 'mixed'}
/>
<DropdownMenuContent side="left" align="center" alignOffset={0}>
<TldrawUiDropdownMenuRoot id={`style panel ${id}`}>
<TldrawUiDropdownMenuTrigger>
<TldrawUiButton type={type} data-testid={`style.${uiType}`} title={titleStr}>
<TldrawUiButtonLabel>{labelStr}</TldrawUiButtonLabel>
<TldrawUiButtonIcon icon={(icon as TLUiIconType) ?? 'mixed'} />
</TldrawUiButton>
</TldrawUiDropdownMenuTrigger>
<TldrawUiDropdownMenuContent side="left" align="center" alignOffset={0}>
<div className="tlui-buttons__grid">
{items.map((item) => {
return (
<DropdownMenuItem
type="icon"
data-testid={`style.${uiType}.${item.value}`}
title={msg(`${uiType}-style.${item.value}` as TLUiTranslationKey)}
key={item.value}
icon={item.icon as TLUiIconType}
onClick={() => onValueChange(style, item.value, false)}
/>
<TldrawUiDropdownMenuItem key={item.value}>
<TldrawUiButton
type="icon"
data-testid={`style.${uiType}.${item.value}`}
title={msg(`${uiType}-style.${item.value}` as TLUiTranslationKey)}
onClick={() => onValueChange(style, item.value, false)}
>
<TldrawUiButtonIcon icon={item.icon} />
</TldrawUiButton>
</TldrawUiDropdownMenuItem>
)
})}
</div>
</DropdownMenuContent>
</DropdownMenuRoot>
</TldrawUiDropdownMenuContent>
</TldrawUiDropdownMenuRoot>
)
})

Wyświetl plik

@ -3,8 +3,9 @@ import * as React from 'react'
import { TLUiToast, useToasts } from '../context/toasts'
import { useTranslation } from '../hooks/useTranslation/useTranslation'
import { TLUiIconType } from '../icon-types'
import { Button } from './primitives/Button'
import { Icon } from './primitives/Icon'
import { TldrawUiButton } from './primitives/Button/TldrawUiButton'
import { TldrawUiButtonLabel } from './primitives/Button/TldrawUiButtonLabel'
import { TldrawUiIcon } from './primitives/TldrawUiIcon'
function Toast({ toast }: { toast: TLUiToast }) {
const { removeToast } = useToasts()
@ -26,7 +27,7 @@ function Toast({ toast }: { toast: TLUiToast }) {
>
{toast.icon && (
<div className="tlui-toast__icon">
<Icon icon={toast.icon as TLUiIconType} />
<TldrawUiIcon icon={toast.icon as TLUiIconType} />
</div>
)}
<div className="tlui-toast__main">
@ -40,22 +41,28 @@ function Toast({ toast }: { toast: TLUiToast }) {
<div className="tlui-toast__actions">
{toast.actions.map((action, i) => (
<T.Action key={i} altText={action.label} asChild onClick={action.onClick}>
<Button type={action.type}>{action.label}</Button>
<TldrawUiButton type={action.type}>
<TldrawUiButtonLabel>{action.label}</TldrawUiButtonLabel>
</TldrawUiButton>
</T.Action>
))}
<T.Close asChild>
<Button type="normal" className="tlui-toast__close" style={{ marginLeft: 'auto' }}>
{toast.closeLabel ?? msg('toast.close')}
</Button>
<TldrawUiButton
type="normal"
className="tlui-toast__close"
style={{ marginLeft: 'auto' }}
>
<TldrawUiButtonLabel>{toast.closeLabel ?? msg('toast.close')}</TldrawUiButtonLabel>
</TldrawUiButton>
</T.Close>
</div>
)}
</div>
{!hasActions && (
<T.Close asChild>
<Button type="normal" className="tlui-toast__close">
{toast.closeLabel ?? msg('toast.close')}
</Button>
<TldrawUiButton type="normal" className="tlui-toast__close">
<TldrawUiButtonLabel>{toast.closeLabel ?? msg('toast.close')}</TldrawUiButtonLabel>
</TldrawUiButton>
</T.Close>
)}
</T.Root>

Wyświetl plik

@ -10,15 +10,16 @@ import { useReadonly } from '../../hooks/useReadonly'
import { TLUiToolbarItem, useToolbarSchema } from '../../hooks/useToolbarSchema'
import { TLUiToolItem } from '../../hooks/useTools'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { kbdStr } from '../../kbd-utils'
import { MobileStylePanel } from '../MobileStylePanel'
import { Button } from '../primitives/Button'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRoot,
DropdownMenuTrigger,
} from '../primitives/DropdownMenu'
import { kbdStr } from '../primitives/shared'
TldrawUiDropdownMenuContent,
TldrawUiDropdownMenuItem,
TldrawUiDropdownMenuRoot,
TldrawUiDropdownMenuTrigger,
} from '../primitives/TldrawUiDropdownMenu'
import { ToggleToolLockedButton } from './ToggleToolLockedButton'
/** @public */
@ -143,18 +144,21 @@ export const DefaultToolbar = memo(function DefaultToolbar() {
)}
/>
{/* The dropdown to select everything else */}
<DropdownMenuRoot id="toolbar overflow" modal={false}>
<DropdownMenuTrigger
className="tlui-toolbar__overflow"
icon="chevron-up"
type="tool"
data-testid="tools.more"
title={msg('tool-panel.more')}
/>
<DropdownMenuContent side="top" align="center">
<TldrawUiDropdownMenuRoot id="toolbar overflow" modal={false}>
<TldrawUiDropdownMenuTrigger>
<TldrawUiButton
title={msg('tool-panel.more')}
type="tool"
className="tlui-toolbar__overflow"
data-testid="tools.more"
>
<TldrawUiButtonIcon icon="chevron-up" />
</TldrawUiButton>
</TldrawUiDropdownMenuTrigger>
<TldrawUiDropdownMenuContent side="top" align="center">
<OverflowToolsContent toolbarItems={itemsInDropdown} />
</DropdownMenuContent>
</DropdownMenuRoot>
</TldrawUiDropdownMenuContent>
</TldrawUiDropdownMenuRoot>
</>
) : null}
</div>
@ -180,18 +184,22 @@ const OverflowToolsContent = track(function OverflowToolsContent({
<div className="tlui-buttons__grid">
{toolbarItems.map(({ toolItem: { id, meta, kbd, label, onSelect, icon } }) => {
return (
<DropdownMenuItem
<TldrawUiDropdownMenuItem
key={id}
type="icon"
className="tlui-button-grid__button"
data-testid={`tools.more.${id}`}
data-tool={id}
data-geo={meta?.geo ?? ''}
aria-label={label}
onClick={() => onSelect('toolbar')}
title={label ? `${msg(label)} ${kbd ? kbdStr(kbd) : ''}` : ''}
icon={icon}
/>
>
<TldrawUiButton
type="icon"
className="tlui-button-grid__button"
onClick={() => onSelect('toolbar')}
data-testid={`tools.more.${id}`}
title={label ? `${msg(label)} ${kbd ? kbdStr(kbd) : ''}` : ''}
>
<TldrawUiButtonIcon icon={icon} />
</TldrawUiButton>
</TldrawUiDropdownMenuItem>
)
})}
</div>
@ -208,21 +216,22 @@ function ToolbarButton({
isSelected: boolean
}) {
return (
<Button
<TldrawUiButton
type="tool"
data-testid={`tools.${item.id}`}
data-tool={item.id}
data-geo={item.meta?.geo ?? ''}
aria-label={item.label}
title={title}
icon={item.icon}
data-state={isSelected ? 'selected' : undefined}
onClick={() => item.onSelect('toolbar')}
title={title}
onTouchStart={(e) => {
preventDefault(e)
item.onSelect('toolbar')
}}
/>
>
<TldrawUiButtonIcon icon={item.icon} />
</TldrawUiButton>
)
}

Wyświetl plik

@ -3,7 +3,8 @@ import classNames from 'classnames'
import { PORTRAIT_BREAKPOINT } from '../../constants'
import { useBreakpoint } from '../../context/breakpoints'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { Button } from '../primitives/Button'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
interface ToggleToolLockedButtonProps {
activeToolId?: string
@ -32,15 +33,15 @@ export function ToggleToolLockedButton({ activeToolId }: ToggleToolLockedButtonP
if (!activeToolId || NOT_LOCKABLE_TOOLS.includes(activeToolId)) return null
return (
<Button
<TldrawUiButton
type="normal"
title={msg('action.toggle-tool-lock')}
className={classNames('tlui-toolbar__lock-button', {
'tlui-toolbar__lock-button__mobile': breakpoint < PORTRAIT_BREAKPOINT.TABLET_SM,
})}
icon={isToolLocked ? 'lock' : 'unlock'}
onClick={() => editor.updateInstanceState({ isToolLocked: !isToolLocked })}
smallIcon
/>
>
<TldrawUiButtonIcon icon={isToolLocked ? 'lock' : 'unlock'} small />
</TldrawUiButton>
)
}

Wyświetl plik

@ -5,8 +5,8 @@ import { PORTRAIT_BREAKPOINT } from '../../constants'
import { useBreakpoint } from '../../context/breakpoints'
import { useMenuIsOpen } from '../../hooks/useMenuIsOpen'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TldrawUiMenuContextProvider } from '../menus/TldrawUiMenuContext'
import { Button } from '../primitives/Button'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
import { DefaultZoomMenuContent } from './DefaultZoomMenuContent'
/** @public */
@ -59,7 +59,7 @@ const ZoomTriggerButton = forwardRef<HTMLButtonElement, any>(
}, [editor])
return (
<Button
<TldrawUiButton
ref={ref}
{...props}
type="icon"
@ -76,7 +76,7 @@ const ZoomTriggerButton = forwardRef<HTMLButtonElement, any>(
{breakpoint < PORTRAIT_BREAKPOINT.MOBILE ? null : (
<span style={{ flexGrow: 0, textAlign: 'center' }}>{Math.floor(zoom * 100)}%</span>
)}
</Button>
</TldrawUiButton>
)
}
)

Wyświetl plik

@ -1,6 +1,6 @@
import { useActions } from '../../context/actions'
import { ZoomTo100MenuItem, ZoomToFitMenuItem, ZoomToSelectionMenuItem } from '../menu-items'
import { TldrawUiMenuItem } from '../menus/TldrawUiMenuItem'
import { TldrawUiMenuItem } from '../primitives/menus/TldrawUiMenuItem'
/** @public */
export function DefaultZoomMenuContent() {

Wyświetl plik

@ -21,10 +21,10 @@ import {
useThreeStackableItems,
useUnlockedSelectedShapesCount,
} from '../hooks/menu-hooks'
import { TldrawUiMenuCheckboxItem } from './menus/TldrawUiMenuCheckboxItem'
import { TldrawUiMenuGroup } from './menus/TldrawUiMenuGroup'
import { TldrawUiMenuItem } from './menus/TldrawUiMenuItem'
import { TldrawUiMenuSubmenu } from './menus/TldrawUiMenuSubmenu'
import { TldrawUiMenuCheckboxItem } from './primitives/menus/TldrawUiMenuCheckboxItem'
import { TldrawUiMenuGroup } from './primitives/menus/TldrawUiMenuGroup'
import { TldrawUiMenuItem } from './primitives/menus/TldrawUiMenuItem'
import { TldrawUiMenuSubmenu } from './primitives/menus/TldrawUiMenuSubmenu'
/* -------------------- Selection ------------------- */

Wyświetl plik

@ -1,80 +0,0 @@
import { useEditor } from '@tldraw/editor'
import classnames from 'classnames'
import * as React from 'react'
import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TLUiIconType } from '../../icon-types'
import { Spinner } from '../Spinner'
import { Icon } from './Icon'
import { Kbd } from './Kbd'
/** @public */
export interface TLUiButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
loading?: boolean // TODO: loading spinner
disabled?: boolean
label?: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
icon?: TLUiIconType | Exclude<string, TLUiIconType>
spinner?: boolean
iconLeft?: TLUiIconType | Exclude<string, TLUiIconType>
smallIcon?: boolean
kbd?: string
isChecked?: boolean
invertIcon?: boolean
type: 'normal' | 'primary' | 'danger' | 'low' | 'icon' | 'tool' | 'menu' | 'help'
}
/** @public */
export const Button = React.forwardRef<HTMLButtonElement, TLUiButtonProps>(function Button(
{
label,
icon,
invertIcon,
iconLeft,
smallIcon,
kbd,
isChecked = false,
type,
children,
spinner,
disabled,
...props
},
ref
) {
const msg = useTranslation()
const labelStr = label ? msg(label) : ''
const editor = useEditor()
// If the button is getting disabled while it's focused, move focus to the editor
// so that the user can continue using keyboard shortcuts
const current = (ref as React.MutableRefObject<HTMLButtonElement | null>)?.current
if (disabled && current === document.activeElement) {
editor.getContainer().focus()
}
return (
<button
ref={ref}
draggable={false}
type="button"
disabled={disabled}
{...props}
title={props.title ?? labelStr}
className={classnames('tlui-button', `tlui-button__${type}`, props.className)}
>
{iconLeft && <Icon icon={iconLeft} className="tlui-button__icon-left" small />}
{children}
{label && (
<span className="tlui-button__label" draggable={false}>
{labelStr}
{isChecked && <Icon icon="check" />}
</span>
)}
{kbd && <Kbd>{kbd}</Kbd>}
{icon && !spinner && (
<Icon icon={icon} small={!!label || smallIcon} invertIcon={invertIcon} />
)}
{spinner && <Spinner />}
</button>
)
})

Wyświetl plik

@ -0,0 +1,36 @@
import { useEditor } from '@tldraw/editor'
import classnames from 'classnames'
import * as React from 'react'
/** @public */
export interface TLUiButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
disabled?: boolean
type: 'normal' | 'primary' | 'danger' | 'low' | 'icon' | 'tool' | 'menu' | 'help'
}
/** @public */
export const TldrawUiButton = React.forwardRef<HTMLButtonElement, TLUiButtonProps>(
function TldrawUiButton({ children, disabled, type, ...props }, ref) {
const editor = useEditor()
// If the button is getting disabled while it's focused, move focus to the editor
// so that the user can continue using keyboard shortcuts
const current = (ref as React.MutableRefObject<HTMLButtonElement | null>)?.current
if (disabled && current === document.activeElement) {
editor.getContainer().focus()
}
return (
<button
ref={ref}
type="button"
draggable={false}
disabled={disabled}
{...props}
className={classnames('tlui-button', `tlui-button__${type}`, props.className)}
>
{children}
</button>
)
}
)

Wyświetl plik

@ -0,0 +1,11 @@
import { TldrawUiIcon } from '../TldrawUiIcon'
/** @public */
export type TLUiButtonCheckProps = { checked: boolean }
/** @public */
export function TldrawUiButtonCheck({ checked }: TLUiButtonCheckProps) {
return (
<TldrawUiIcon icon={checked ? 'check' : 'checkbox-empty'} className="tlui-button__icon" small />
)
}

Wyświetl plik

@ -0,0 +1,15 @@
import { TldrawUiIcon } from '../TldrawUiIcon'
/** @public */
export type TLUiButtonIconProps = {
icon: string
small?: boolean
invertIcon?: boolean
}
/** @public */
export function TldrawUiButtonIcon({ icon, small, invertIcon }: TLUiButtonIconProps) {
return (
<TldrawUiIcon className="tlui-button__icon" icon={icon} small={small} invertIcon={invertIcon} />
)
}

Wyświetl plik

@ -0,0 +1,7 @@
/** @public */
export type TLUiButtonLabelProps = { children?: any }
/** @public */
export function TldrawUiButtonLabel({ children }: TLUiButtonLabelProps) {
return <span className="tlui-button__label">{children}</span>
}

Wyświetl plik

@ -0,0 +1,6 @@
import { Spinner } from '../../Spinner'
/** @public */
export function TldrawUiButtonSpinner() {
return <Spinner />
}

Wyświetl plik

@ -1,165 +0,0 @@
import { stopEventPropagation, useEditor } from '@tldraw/editor'
import classNames from 'classnames'
import * as React from 'react'
import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TLUiIconType } from '../../icon-types'
import { Icon } from './Icon'
/** @public */
export interface TLUiInputProps {
disabled?: boolean
label?: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
icon?: TLUiIconType | Exclude<string, TLUiIconType>
iconLeft?: TLUiIconType | Exclude<string, TLUiIconType>
autofocus?: boolean
autoselect?: boolean
children?: any
defaultValue?: string
placeholder?: string
onComplete?: (value: string) => void
onValueChange?: (value: string) => void
onCancel?: (value: string) => void
onBlur?: (value: string) => void
className?: string
/**
* Usually on iOS when you focus an input, the browser will adjust the viewport to bring the input
* into view. Sometimes this doesn't work properly though - for example, if the input is newly
* created, iOS seems to have a hard time adjusting the viewport for it. This prop allows you to
* opt-in to some extra code to manually bring the input into view when the visual viewport of the
* browser changes, but we don't want to use it everywhere because generally the native behavior
* looks nicer in scenarios where it's sufficient.
*/
shouldManuallyMaintainScrollPositionWhenFocused?: boolean
value?: string
}
/** @public */
export const Input = React.forwardRef<HTMLInputElement, TLUiInputProps>(function Input(
{
className,
label,
icon,
iconLeft,
autoselect = false,
autofocus = false,
defaultValue,
placeholder,
onComplete,
onValueChange,
onCancel,
onBlur,
shouldManuallyMaintainScrollPositionWhenFocused = false,
children,
value,
},
ref
) {
const editor = useEditor()
const rInputRef = React.useRef<HTMLInputElement>(null)
// combine rInputRef and ref
React.useImperativeHandle(ref, () => rInputRef.current as HTMLInputElement)
const msg = useTranslation()
const rInitialValue = React.useRef<string>(defaultValue ?? '')
const rCurrentValue = React.useRef<string>(defaultValue ?? '')
const [isFocused, setIsFocused] = React.useState(false)
const handleFocus = React.useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
setIsFocused(true)
const elm = e.currentTarget as HTMLInputElement
rCurrentValue.current = elm.value
requestAnimationFrame(() => {
if (autoselect) {
elm.select()
}
})
},
[autoselect]
)
const handleChange = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.currentTarget.value
rCurrentValue.current = value
onValueChange?.(value)
},
[onValueChange]
)
const handleKeyUp = React.useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
switch (e.key) {
case 'Enter': {
e.currentTarget.blur()
stopEventPropagation(e)
onComplete?.(e.currentTarget.value)
break
}
case 'Escape': {
e.currentTarget.value = rInitialValue.current
e.currentTarget.blur()
stopEventPropagation(e)
onCancel?.(e.currentTarget.value)
break
}
}
},
[onComplete, onCancel]
)
const handleBlur = React.useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
setIsFocused(false)
const value = e.currentTarget.value
onBlur?.(value)
},
[onBlur]
)
React.useEffect(() => {
if (!editor.environment.isIos) return
const visualViewport = window.visualViewport
if (isFocused && shouldManuallyMaintainScrollPositionWhenFocused && visualViewport) {
const onViewportChange = () => {
rInputRef.current?.scrollIntoView({ block: 'center' })
}
visualViewport.addEventListener('resize', onViewportChange)
visualViewport.addEventListener('scroll', onViewportChange)
requestAnimationFrame(() => {
rInputRef.current?.scrollIntoView({ block: 'center' })
})
return () => {
visualViewport.removeEventListener('resize', onViewportChange)
visualViewport.removeEventListener('scroll', onViewportChange)
}
}
}, [editor, isFocused, shouldManuallyMaintainScrollPositionWhenFocused])
return (
<div draggable={false} className="tlui-input__wrapper">
{children}
{label && <label>{msg(label)}</label>}
{iconLeft && <Icon icon={iconLeft} className="tlui-icon-left" small />}
<input
ref={rInputRef}
className={classNames('tlui-input', className)}
type="text"
defaultValue={defaultValue}
onKeyUp={handleKeyUp}
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
autoFocus={autofocus}
placeholder={placeholder}
value={value}
/>
{icon && <Icon icon={icon} small={!!label} />}
</div>
)
})

Wyświetl plik

@ -8,16 +8,15 @@ import {
useValue,
} from '@tldraw/editor'
import classNames from 'classnames'
import * as React from 'react'
import { useRef } from 'react'
import { memo, useMemo, useRef } from 'react'
import { StyleValuesForUi } from '../../../styles'
import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TLUiIconType } from '../../icon-types'
import { StyleValuesForUi } from '../StylePanel/styles'
import { Button } from './Button'
import { TldrawUiButton } from './Button/TldrawUiButton'
import { TldrawUiButtonIcon } from './Button/TldrawUiButtonIcon'
/** @internal */
export interface ButtonPickerProps<T extends string> {
/** @public */
export interface TLUiButtonPickerProps<T extends string> {
title: string
uiType: string
style: StyleProp<T>
@ -26,7 +25,10 @@ export interface ButtonPickerProps<T extends string> {
onValueChange: (style: StyleProp<T>, value: T, squashing: boolean) => void
}
function _ButtonPicker<T extends string>(props: ButtonPickerProps<T>) {
/** @public */
export const TldrawUiButtonPicker = memo(function TldrawUiButtonPicker<T extends string>(
props: TLUiButtonPickerProps<T>
) {
const {
uiType,
items,
@ -46,7 +48,7 @@ function _ButtonPicker<T extends string>(props: ButtonPickerProps<T>) {
handleButtonPointerDown,
handleButtonPointerEnter,
handleButtonPointerUp,
} = React.useMemo(() => {
} = useMemo(() => {
const handlePointerUp = () => {
rPointing.current = false
window.removeEventListener('pointerup', handlePointerUp)
@ -99,7 +101,7 @@ function _ButtonPicker<T extends string>(props: ButtonPickerProps<T>) {
return (
<div className={classNames('tlui-buttons__grid')}>
{items.map((item) => (
<Button
<TldrawUiButton
type="icon"
key={item.value}
data-id={item.value}
@ -117,12 +119,10 @@ function _ButtonPicker<T extends string>(props: ButtonPickerProps<T>) {
onPointerDown={handleButtonPointerDown}
onPointerUp={handleButtonPointerUp}
onClick={handleButtonClick}
icon={item.icon as TLUiIconType}
/>
>
<TldrawUiButtonIcon icon={item.icon} />
</TldrawUiButton>
))}
</div>
)
}
/** @internal */
export const ButtonPicker = React.memo(_ButtonPicker) as typeof _ButtonPicker
})

Wyświetl plik

@ -1,7 +1,7 @@
import * as _Dialog from '@radix-ui/react-dialog'
import classNames from 'classnames'
import { Button } from './Button'
import { Icon } from './Icon'
import { TldrawUiButton } from './Button/TldrawUiButton'
import { TldrawUiButtonIcon } from './Button/TldrawUiButtonIcon'
/** @public */
export type TLUiDialogHeaderProps = {
@ -10,7 +10,7 @@ export type TLUiDialogHeaderProps = {
}
/** @public */
export function DialogHeader({ className, children }: TLUiDialogHeaderProps) {
export function TldrawUiDialogHeader({ className, children }: TLUiDialogHeaderProps) {
return <div className={classNames('tlui-dialog__header', className)}>{children}</div>
}
@ -21,7 +21,7 @@ export type TLUiDialogTitleProps = {
}
/** @public */
export function DialogTitle({ className, children }: TLUiDialogTitleProps) {
export function TldrawUiDialogTitle({ className, children }: TLUiDialogTitleProps) {
return (
<_Dialog.DialogTitle dir="ltr" className={classNames('tlui-dialog__header__title', className)}>
{children}
@ -30,17 +30,17 @@ export function DialogTitle({ className, children }: TLUiDialogTitleProps) {
}
/** @public */
export function DialogCloseButton() {
export function TldrawUiDialogCloseButton() {
return (
<div className="tlui-dialog__header__close">
<_Dialog.DialogClose data-testid="dialog.close" dir="ltr" asChild>
<Button
<TldrawUiButton
type="icon"
aria-label="Close"
onTouchEnd={(e) => (e.target as HTMLButtonElement).click()}
>
<Icon small icon="cross-2" />
</Button>
<TldrawUiButtonIcon small icon="cross-2" />
</TldrawUiButton>
</_Dialog.DialogClose>
</div>
)
@ -54,7 +54,7 @@ export type TLUiDialogBodyProps = {
}
/** @public */
export function DialogBody({ className, children, style }: TLUiDialogBodyProps) {
export function TldrawUiDialogBody({ className, children, style }: TLUiDialogBodyProps) {
return (
<div className={classNames('tlui-dialog__body', className)} style={style}>
{children}
@ -69,6 +69,6 @@ export type TLUiDialogFooterProps = {
}
/** @public */
export function DialogFooter({ className, children }: TLUiDialogFooterProps) {
export function TldrawUiDialogFooter({ className, children }: TLUiDialogFooterProps) {
return <div className={classNames('tlui-dialog__footer', className)}>{children}</div>
}

Wyświetl plik

@ -1,8 +1,10 @@
import * as _DropdownMenu from '@radix-ui/react-dropdown-menu'
import { preventDefault, useContainer } from '@tldraw/editor'
import { useMenuIsOpen } from '../../hooks/useMenuIsOpen'
import { Button, TLUiButtonProps } from './Button'
import { Icon } from './Icon'
import { TldrawUiButton } from './Button/TldrawUiButton'
import { TldrawUiButtonIcon } from './Button/TldrawUiButtonIcon'
import { TldrawUiButtonLabel } from './Button/TldrawUiButtonLabel'
import { TldrawUiIcon } from './TldrawUiIcon'
/** @public */
export type TLUiDropdownMenuRootProps = {
@ -13,7 +15,7 @@ export type TLUiDropdownMenuRootProps = {
}
/** @public */
export function DropdownMenuRoot({
export function TldrawUiDropdownMenuRoot({
id,
children,
modal = false,
@ -34,20 +36,21 @@ export function DropdownMenuRoot({
}
/** @public */
export interface TLUiDropdownMenuTriggerProps extends TLUiButtonProps {
export interface TLUiDropdownMenuTriggerProps {
children?: any
}
/** @public */
export function DropdownMenuTrigger({ children, ...rest }: TLUiDropdownMenuTriggerProps) {
export function TldrawUiDropdownMenuTrigger({ children, ...rest }: TLUiDropdownMenuTriggerProps) {
return (
<_DropdownMenu.Trigger
dir="ltr"
asChild
// Firefox fix: Stop the dropdown immediately closing after touch
onTouchEnd={(e) => preventDefault(e)}
{...rest}
>
<Button {...rest}>{children}</Button>
{children}
</_DropdownMenu.Trigger>
)
}
@ -63,7 +66,7 @@ export type TLUiDropdownMenuContentProps = {
}
/** @public */
export function DropdownMenuContent({
export function TldrawUiDropdownMenuContent({
side = 'bottom',
align = 'start',
sideOffset = 8,
@ -92,7 +95,7 @@ export function DropdownMenuContent({
export type TLUiDropdownMenuSubProps = { id: string; children: any }
/** @public */
export function DropdownMenuSub({ id, children }: TLUiDropdownMenuSubProps) {
export function TldrawUiDropdownMenuSub({ id, children }: TLUiDropdownMenuSubProps) {
const [open, onOpenChange] = useMenuIsOpen(id)
return (
@ -111,21 +114,22 @@ export type TLUiDropdownMenuSubTriggerProps = {
}
/** @public */
export function DropdownMenuSubTrigger({
export function TldrawUiDropdownMenuSubTrigger({
label,
title,
disabled,
}: TLUiDropdownMenuSubTriggerProps) {
return (
<_DropdownMenu.SubTrigger dir="ltr" asChild>
<Button
<TldrawUiButton
type="menu"
className="tlui-menu__submenu__trigger"
disabled={disabled}
title={title}
label={label}
icon="chevron-right"
/>
>
<TldrawUiButtonLabel>{label}</TldrawUiButtonLabel>
<TldrawUiButtonIcon icon="chevron-right" small />
</TldrawUiButton>
</_DropdownMenu.SubTrigger>
)
}
@ -139,7 +143,7 @@ export type TLUiDropdownMenuSubContentProps = {
}
/** @public */
export function DropdownMenuSubContent({
export function TldrawUiDropdownMenuSubContent({
alignOffset = -1,
sideOffset = -4,
children,
@ -166,7 +170,10 @@ export type TLUiDropdownMenuGroupProps = {
}
/** @public */
export function DropdownMenuGroup({ children, size = 'medium' }: TLUiDropdownMenuGroupProps) {
export function TldrawUiDropdownMenuGroup({
children,
size = 'medium',
}: TLUiDropdownMenuGroupProps) {
return (
<_DropdownMenu.Group dir="ltr" className="tlui-menu__group" data-size={size}>
{children}
@ -175,28 +182,25 @@ export function DropdownMenuGroup({ children, size = 'medium' }: TLUiDropdownMen
}
/** @public */
export function DropdownMenuIndicator() {
export function TldrawUiDropdownMenuIndicator() {
return (
<_DropdownMenu.ItemIndicator dir="ltr" asChild>
<Icon icon="check" />
<TldrawUiIcon icon="check" />
</_DropdownMenu.ItemIndicator>
)
}
/** @public */
export interface TLUiDropdownMenuItemProps extends TLUiButtonProps {
export interface TLUiDropdownMenuItemProps {
noClose?: boolean
children: any
}
/** @public */
export function DropdownMenuItem({ noClose, ...props }: TLUiDropdownMenuItemProps) {
export function TldrawUiDropdownMenuItem({ noClose, children }: TLUiDropdownMenuItemProps) {
return (
<_DropdownMenu.Item
dir="ltr"
asChild
onClick={noClose || props.isChecked !== undefined ? preventDefault : undefined}
>
<Button {...props} />
<_DropdownMenu.Item dir="ltr" asChild onClick={noClose ? preventDefault : undefined}>
{children}
</_DropdownMenu.Item>
)
}
@ -211,7 +215,7 @@ export interface TLUiDropdownMenuCheckboxItemProps {
}
/** @public */
export function DropdownMenuCheckboxItem({
export function TldrawUiDropdownMenuCheckboxItem({
children,
onSelect,
...rest
@ -228,38 +232,7 @@ export function DropdownMenuCheckboxItem({
>
<div className="tlui-button__checkbox__indicator">
<_DropdownMenu.ItemIndicator dir="ltr">
<Icon icon="check" small />
</_DropdownMenu.ItemIndicator>
</div>
{children}
</_DropdownMenu.CheckboxItem>
)
}
/** @public */
export interface TLUiDropdownMenuRadioItemProps {
checked?: boolean
onSelect?: (e: Event) => void
disabled?: boolean
title: string
children: any
}
/** @public */
export function DropdownMenuRadioItem({ children, ...rest }: TLUiDropdownMenuRadioItemProps) {
return (
<_DropdownMenu.CheckboxItem
dir="ltr"
className="tlui-button tlui-button__menu tlui-button__checkbox"
{...rest}
onSelect={(e) => {
preventDefault(e)
rest.onSelect?.(e)
}}
>
<div className="tlui-button__checkbox__indicator">
<_DropdownMenu.ItemIndicator dir="ltr">
<Icon icon="check" small />
<TldrawUiIcon icon="check" small />
</_DropdownMenu.ItemIndicator>
</div>
{children}

Wyświetl plik

@ -14,7 +14,7 @@ export interface TLUiIconProps extends React.HTMLProps<HTMLDivElement> {
}
/** @public */
export const Icon = memo(function Icon({
export const TldrawUiIcon = memo(function TldrawUi({
small,
invertIcon,
icon,

Wyświetl plik

@ -0,0 +1,167 @@
import { stopEventPropagation, useEditor } from '@tldraw/editor'
import classNames from 'classnames'
import * as React from 'react'
import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TLUiIconType } from '../../icon-types'
import { TldrawUiIcon } from './TldrawUiIcon'
/** @public */
export interface TLUiInputProps {
disabled?: boolean
label?: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
icon?: TLUiIconType | Exclude<string, TLUiIconType>
iconLeft?: TLUiIconType | Exclude<string, TLUiIconType>
autofocus?: boolean
autoselect?: boolean
children?: any
defaultValue?: string
placeholder?: string
onComplete?: (value: string) => void
onValueChange?: (value: string) => void
onCancel?: (value: string) => void
onBlur?: (value: string) => void
className?: string
/**
* Usually on iOS when you focus an input, the browser will adjust the viewport to bring the input
* into view. Sometimes this doesn't work properly though - for example, if the input is newly
* created, iOS seems to have a hard time adjusting the viewport for it. This prop allows you to
* opt-in to some extra code to manually bring the input into view when the visual viewport of the
* browser changes, but we don't want to use it everywhere because generally the native behavior
* looks nicer in scenarios where it's sufficient.
*/
shouldManuallyMaintainScrollPositionWhenFocused?: boolean
value?: string
}
/** @public */
export const TldrawUiInput = React.forwardRef<HTMLInputElement, TLUiInputProps>(
function TldrawUiInput(
{
className,
label,
icon,
iconLeft,
autoselect = false,
autofocus = false,
defaultValue,
placeholder,
onComplete,
onValueChange,
onCancel,
onBlur,
shouldManuallyMaintainScrollPositionWhenFocused = false,
children,
value,
},
ref
) {
const editor = useEditor()
const rInputRef = React.useRef<HTMLInputElement>(null)
// combine rInputRef and ref
React.useImperativeHandle(ref, () => rInputRef.current as HTMLInputElement)
const msg = useTranslation()
const rInitialValue = React.useRef<string>(defaultValue ?? '')
const rCurrentValue = React.useRef<string>(defaultValue ?? '')
const [isFocused, setIsFocused] = React.useState(false)
const handleFocus = React.useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
setIsFocused(true)
const elm = e.currentTarget as HTMLInputElement
rCurrentValue.current = elm.value
requestAnimationFrame(() => {
if (autoselect) {
elm.select()
}
})
},
[autoselect]
)
const handleChange = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.currentTarget.value
rCurrentValue.current = value
onValueChange?.(value)
},
[onValueChange]
)
const handleKeyUp = React.useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
switch (e.key) {
case 'Enter': {
e.currentTarget.blur()
stopEventPropagation(e)
onComplete?.(e.currentTarget.value)
break
}
case 'Escape': {
e.currentTarget.value = rInitialValue.current
e.currentTarget.blur()
stopEventPropagation(e)
onCancel?.(e.currentTarget.value)
break
}
}
},
[onComplete, onCancel]
)
const handleBlur = React.useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
setIsFocused(false)
const value = e.currentTarget.value
onBlur?.(value)
},
[onBlur]
)
React.useEffect(() => {
if (!editor.environment.isIos) return
const visualViewport = window.visualViewport
if (isFocused && shouldManuallyMaintainScrollPositionWhenFocused && visualViewport) {
const onViewportChange = () => {
rInputRef.current?.scrollIntoView({ block: 'center' })
}
visualViewport.addEventListener('resize', onViewportChange)
visualViewport.addEventListener('scroll', onViewportChange)
requestAnimationFrame(() => {
rInputRef.current?.scrollIntoView({ block: 'center' })
})
return () => {
visualViewport.removeEventListener('resize', onViewportChange)
visualViewport.removeEventListener('scroll', onViewportChange)
}
}
}, [editor, isFocused, shouldManuallyMaintainScrollPositionWhenFocused])
return (
<div draggable={false} className="tlui-input__wrapper">
{children}
{label && <label>{msg(label)}</label>}
{iconLeft && <TldrawUiIcon icon={iconLeft} className="tlui-icon-left" small />}
<input
ref={rInputRef}
className={classNames('tlui-input', className)}
type="text"
defaultValue={defaultValue}
onKeyUp={handleKeyUp}
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
autoFocus={autofocus}
placeholder={placeholder}
value={value}
/>
{icon && <TldrawUiIcon icon={icon} small={!!label} />}
</div>
)
}
)

Wyświetl plik

@ -1,14 +1,14 @@
import { PORTRAIT_BREAKPOINT } from '../../constants'
import { useBreakpoint } from '../../context/breakpoints'
import { kbd } from './shared'
import { kbd } from '../../kbd-utils'
/** @internal */
export interface KbdProps {
/** @public */
export interface TLUiKbdProps {
children: string
}
/** @internal */
export function Kbd({ children }: KbdProps) {
/** @public */
export function TldrawUiKbd({ children }: TLUiKbdProps) {
const breakpoint = useBreakpoint()
if (breakpoint < PORTRAIT_BREAKPOINT.MOBILE) return null
return (

Wyświetl plik

@ -2,7 +2,6 @@ import * as PopoverPrimitive from '@radix-ui/react-popover'
import { useContainer } from '@tldraw/editor'
import React from 'react'
import { useMenuIsOpen } from '../../hooks/useMenuIsOpen'
import { Button, TLUiButtonProps } from './Button'
/** @public */
export type TLUiPopoverProps = {
@ -13,7 +12,7 @@ export type TLUiPopoverProps = {
}
/** @public */
export function Popover({ id, children, onOpenChange, open }: TLUiPopoverProps) {
export function TldrawUiPopover({ id, children, onOpenChange, open }: TLUiPopoverProps) {
const [isOpen, handleOpenChange] = useMenuIsOpen(id, onOpenChange)
return (
@ -27,15 +26,15 @@ export function Popover({ id, children, onOpenChange, open }: TLUiPopoverProps)
}
/** @public */
export interface TLUiPopoverTriggerProps extends TLUiButtonProps {
export interface TLUiPopoverTriggerProps {
children?: React.ReactNode
}
/** @public */
export function PopoverTrigger({ children, ...rest }: TLUiPopoverTriggerProps) {
export function TldrawUiPopoverTrigger({ children }: TLUiPopoverTriggerProps) {
return (
<PopoverPrimitive.Trigger asChild dir="ltr">
<Button {...rest}>{children}</Button>
{children}
</PopoverPrimitive.Trigger>
)
}
@ -50,7 +49,7 @@ export type TLUiPopoverContentProps = {
}
/** @public */
export function PopoverContent({
export function TldrawUiPopoverContent({
side,
children,
align = 'center',

Wyświetl plik

@ -5,7 +5,7 @@ import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKe
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
/** @internal */
export interface SliderProps {
export interface TLUiSliderProps {
steps: number
value: number | null
label: string
@ -15,7 +15,7 @@ export interface SliderProps {
}
/** @internal */
export const Slider = memo(function Slider(props: SliderProps) {
export const TldrawUiSlider = memo(function Slider(props: TLUiSliderProps) {
const { title, steps, value, label, onValueChange } = props
const editor = useEditor()
const msg = useTranslation()

Wyświetl plik

@ -1,13 +1,13 @@
import * as _ContextMenu from '@radix-ui/react-context-menu'
import * as _DropdownMenu from '@radix-ui/react-dropdown-menu'
import { preventDefault } from '@tldraw/editor'
import { unwrapLabel } from '../../context/actions'
import { TLUiEventSource } from '../../context/events'
import { useReadonly } from '../../hooks/useReadonly'
import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { Icon } from '../primitives/Icon'
import { Kbd } from '../primitives/Kbd'
import { unwrapLabel } from '../../../context/actions'
import { TLUiEventSource } from '../../../context/events'
import { useReadonly } from '../../../hooks/useReadonly'
import { TLUiTranslationKey } from '../../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../../hooks/useTranslation/useTranslation'
import { TldrawUiIcon } from '../TldrawUiIcon'
import { TldrawUiKbd } from '../TldrawUiKbd'
import { useTldrawUiMenuContext } from './TldrawUiMenuContext'
/** @public */
@ -63,13 +63,13 @@ export function TldrawUiMenuCheckboxItem<
disabled={disabled}
checked={checked}
>
<Icon small icon={checked ? 'check' : 'checkbox-empty'} />
<TldrawUiIcon small icon={checked ? 'check' : 'checkbox-empty'} />
{labelStr && (
<span className="tlui-button__label" draggable={false}>
{labelStr}
</span>
)}
{kbd && <Kbd>{kbd}</Kbd>}
{kbd && <TldrawUiKbd>{kbd}</TldrawUiKbd>}
</_DropdownMenu.CheckboxItem>
)
}
@ -87,13 +87,13 @@ export function TldrawUiMenuCheckboxItem<
disabled={disabled}
checked={checked}
>
<Icon small icon={checked ? 'check' : 'checkbox-empty'} />
<TldrawUiIcon small icon={checked ? 'check' : 'checkbox-empty'} />
{labelStr && (
<span className="tlui-button__label" draggable={false}>
{labelStr}
</span>
)}
{kbd && <Kbd>{kbd}</Kbd>}
{kbd && <TldrawUiKbd>{kbd}</TldrawUiKbd>}
</_ContextMenu.CheckboxItem>
)
}

Wyświetl plik

@ -1,5 +1,5 @@
import { createContext, useContext } from 'react'
import { TLUiEventSource } from '../../context/events'
import { TLUiEventSource } from '../../../context/events'
/** @public */
export type TldrawUiMenuContextType =

Wyświetl plik

@ -1,8 +1,8 @@
import { ContextMenuGroup } from '@radix-ui/react-context-menu'
import { unwrapLabel } from '../../context/actions'
import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { DropdownMenuGroup } from '../primitives/DropdownMenu'
import { unwrapLabel } from '../../../context/actions'
import { TLUiTranslationKey } from '../../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../../hooks/useTranslation/useTranslation'
import { TldrawUiDropdownMenuGroup } from '../TldrawUiDropdownMenu'
import { useTldrawUiMenuContext } from './TldrawUiMenuContext'
/** @public */
@ -33,9 +33,12 @@ export function TldrawUiMenuGroup({ id, label, small = false, children }: TLUiMe
}
case 'menu': {
return (
<DropdownMenuGroup data-testid={`${sourceId}-group.${id}`} size={small ? 'tiny' : 'medium'}>
<TldrawUiDropdownMenuGroup
data-testid={`${sourceId}-group.${id}`}
size={small ? 'tiny' : 'medium'}
>
{children}
</DropdownMenuGroup>
</TldrawUiDropdownMenuGroup>
)
}
case 'context-menu': {

Wyświetl plik

@ -1,16 +1,18 @@
import { ContextMenuItem } from '@radix-ui/react-context-menu'
import { preventDefault } from '@tldraw/editor'
import { useState } from 'react'
import { unwrapLabel } from '../../context/actions'
import { TLUiEventSource } from '../../context/events'
import { useReadonly } from '../../hooks/useReadonly'
import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { Spinner } from '../Spinner'
import { Button } from '../primitives/Button'
import { DropdownMenuItem } from '../primitives/DropdownMenu'
import { Kbd } from '../primitives/Kbd'
import { kbdStr } from '../primitives/shared'
import { unwrapLabel } from '../../../context/actions'
import { TLUiEventSource } from '../../../context/events'
import { useReadonly } from '../../../hooks/useReadonly'
import { TLUiTranslationKey } from '../../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../../hooks/useTranslation/useTranslation'
import { kbdStr } from '../../../kbd-utils'
import { Spinner } from '../../Spinner'
import { TldrawUiButton } from '../Button/TldrawUiButton'
import { TldrawUiButtonIcon } from '../Button/TldrawUiButtonIcon'
import { TldrawUiButtonLabel } from '../Button/TldrawUiButtonLabel'
import { TldrawUiDropdownMenuItem } from '../TldrawUiDropdownMenu'
import { TldrawUiKbd } from '../TldrawUiKbd'
import { useTldrawUiMenuContext } from './TldrawUiMenuContext'
/** @public */
@ -90,24 +92,27 @@ export function TldrawUiMenuItem<
switch (menuType) {
case 'menu': {
return (
<DropdownMenuItem
type="menu"
data-testid={`${sourceId}.${id}`}
kbd={kbd}
label={labelStr}
disabled={disabled}
title={titleStr}
onClick={(e) => {
if (noClose) {
preventDefault(e)
}
if (disableClicks) {
setDisableClicks(false)
} else {
onSelect(sourceId)
}
}}
/>
<TldrawUiDropdownMenuItem>
<TldrawUiButton
type="menu"
data-testid={`${sourceId}.${id}`}
disabled={disabled}
title={titleStr}
onClick={(e) => {
if (noClose) {
preventDefault(e)
}
if (disableClicks) {
setDisableClicks(false)
} else {
onSelect(sourceId)
}
}}
>
<TldrawUiButtonLabel>{labelStr}</TldrawUiButtonLabel>
{kbd && <TldrawUiKbd>{kbd}</TldrawUiKbd>}
</TldrawUiButton>
</TldrawUiDropdownMenuItem>
)
}
case 'context-menu': {
@ -133,37 +138,37 @@ export function TldrawUiMenuItem<
<span className="tlui-button__label" draggable={false}>
{labelStr}
</span>
{kbd && <Kbd>{kbd}</Kbd>}
{kbd && <TldrawUiKbd>{kbd}</TldrawUiKbd>}
{spinner && <Spinner />}
</ContextMenuItem>
)
}
case 'panel': {
return (
<Button
<TldrawUiButton
data-testid={`${sourceId}.${id}`}
icon={icon}
type="menu"
label={labelStr}
title={titleStr}
onClick={() => onSelect(sourceId)}
smallIcon
disabled={disabled}
/>
onClick={() => onSelect(sourceId)}
>
<TldrawUiButtonLabel>{labelStr}</TldrawUiButtonLabel>
{icon && <TldrawUiButtonIcon icon={icon} />}
</TldrawUiButton>
)
}
case 'small-icons':
case 'icons': {
return (
<Button
<TldrawUiButton
data-testid={`${sourceId}.${id}`}
icon={icon}
type="icon"
title={titleStr}
onClick={() => onSelect(sourceId)}
disabled={disabled}
smallIcon={menuType === 'small-icons'}
/>
onClick={() => onSelect(sourceId)}
>
<TldrawUiButtonIcon icon={icon!} small={menuType === 'small-icons'} />
</TldrawUiButton>
)
}
case 'keyboard-shortcuts': {
@ -173,14 +178,17 @@ export function TldrawUiMenuItem<
<div className="tlui-shortcuts-dialog__key-pair" data-testid={`${sourceId}.${id}`}>
<div className="tlui-shortcuts-dialog__key-pair__key">{labelStr}</div>
<div className="tlui-shortcuts-dialog__key-pair__value">
<Kbd>{kbd!}</Kbd>
<TldrawUiKbd>{kbd!}</TldrawUiKbd>
</div>
</div>
)
}
case 'helper-buttons': {
return (
<Button type="low" label={labelStr} iconLeft={icon} onClick={() => onSelect(sourceId)} />
<TldrawUiButton type="low" onClick={() => onSelect(sourceId)}>
<TldrawUiButtonIcon icon={icon!} />
<TldrawUiButtonLabel>{labelStr}</TldrawUiButtonLabel>
</TldrawUiButton>
)
}
default: {

Wyświetl plik

@ -5,15 +5,17 @@ import {
ContextMenuSubTrigger,
} from '@radix-ui/react-context-menu'
import { useContainer } from '@tldraw/editor'
import { useMenuIsOpen } from '../../hooks/useMenuIsOpen'
import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { Button } from '../primitives/Button'
import { useMenuIsOpen } from '../../../hooks/useMenuIsOpen'
import { TLUiTranslationKey } from '../../../hooks/useTranslation/TLUiTranslationKey'
import { useTranslation } from '../../../hooks/useTranslation/useTranslation'
import { TldrawUiButton } from '../Button/TldrawUiButton'
import { TldrawUiButtonIcon } from '../Button/TldrawUiButtonIcon'
import { TldrawUiButtonLabel } from '../Button/TldrawUiButtonLabel'
import {
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
} from '../primitives/DropdownMenu'
TldrawUiDropdownMenuSub,
TldrawUiDropdownMenuSubContent,
TldrawUiDropdownMenuSubTrigger,
} from '../TldrawUiDropdownMenu'
import { useTldrawUiMenuContext } from './TldrawUiMenuContext'
/** @public */
@ -46,34 +48,33 @@ export function TldrawUiMenuSubmenu<Translation extends string = string>({
switch (menuType) {
case 'menu': {
return (
<DropdownMenuSub id={`${sourceId}-sub.${id}`}>
<DropdownMenuSubTrigger
<TldrawUiDropdownMenuSub id={`${sourceId}-sub.${id}`}>
<TldrawUiDropdownMenuSubTrigger
id={`${sourceId}-sub.${id}`}
disabled={disabled}
label={labelStr!}
title={labelStr!}
/>
<DropdownMenuSubContent id={`${sourceId}-sub-content.${id}`} data-size={size}>
<TldrawUiDropdownMenuSubContent id={`${sourceId}-sub-content.${id}`} data-size={size}>
{children}
</DropdownMenuSubContent>
</DropdownMenuSub>
</TldrawUiDropdownMenuSubContent>
</TldrawUiDropdownMenuSub>
)
}
case 'context-menu': {
if (disabled) return null
return (
<ContextMenuSubWithMenu id={`${sourceId}-sub.${id}`}>
<ContextMenuSubTrigger
dir="ltr"
disabled={disabled}
data-testid={`${sourceId}-sub-trigger.${id}`}
asChild
>
<Button
<ContextMenuSubTrigger dir="ltr" disabled={disabled} asChild>
<TldrawUiButton
data-testid={`${sourceId}-sub-trigger.${id}`}
type="menu"
className="tlui-menu__submenu__trigger"
label={labelStr}
icon="chevron-right"
/>
>
<TldrawUiButtonLabel>{labelStr}</TldrawUiButtonLabel>
<TldrawUiButtonIcon icon="chevron-right" small />
</TldrawUiButton>
</ContextMenuSubTrigger>
<ContextMenuPortal container={container}>
<ContextMenuSubContent

Wyświetl plik

@ -76,6 +76,7 @@ export function TldrawUiContextProvider({
</AssetUrlsProvider>
)
}
function InternalProviders({
overrides,
children,

Wyświetl plik

@ -19,6 +19,7 @@ import {
TLUiKeyboardShortcutsDialogProps,
} from '../components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialog'
import { DefaultMainMenu, TLUiMainMenuProps } from '../components/MainMenu/DefaultMainMenu'
import { DefaultMenuPanel } from '../components/MenuPanel'
import { DefaultMinimap } from '../components/Minimap/DefaultMinimap'
import { DefaultNavigationPanel } from '../components/NavigationPanel/DefaultNavigationPanel'
import { DefaultPageMenu } from '../components/PageMenu/DefaultPageMenu'
@ -45,6 +46,9 @@ export interface BaseTLUiComponents {
QuickActions: ComponentType<TLUiQuickActionsProps>
HelperButtons: ComponentType<TLUiHelperButtonsProps>
DebugMenu: ComponentType
MenuPanel: ComponentType
TopPanel: ComponentType
SharePanel: ComponentType
}
/** @public */
@ -54,7 +58,8 @@ export type TLUiComponents = Partial<{
const TldrawUiComponentsContext = createContext({} as TLUiComponents)
type ComponentsContextProviderProps = {
/** @public */
export type TLUiComponentsProviderProps = {
overrides?: TLUiComponents
children: any
}
@ -63,7 +68,7 @@ type ComponentsContextProviderProps = {
export function TldrawUiComponentsProvider({
overrides = {},
children,
}: ComponentsContextProviderProps) {
}: TLUiComponentsProviderProps) {
const _overrides = useShallowObjectIdentity(overrides)
return (
@ -84,6 +89,7 @@ export function TldrawUiComponentsProvider({
QuickActions: DefaultQuickActions,
HelperButtons: DefaultHelperButtons,
DebugMenu: DefaultDebugMenu,
MenuPanel: DefaultMenuPanel,
..._overrides,
}),
[_overrides]

Wyświetl plik

@ -1,11 +1,3 @@
/** @internal */
export function toStartCase(str: string) {
return str
.split(' ')
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
.join(' ')
}
const isDarwin =
typeof window === 'undefined'
? false
@ -13,7 +5,7 @@ const isDarwin =
const cmdKey = isDarwin ? '⌘' : 'Ctrl'
const altKey = isDarwin ? '⌥' : 'Alt'
/** @internal */
/** @public */
export function kbd(str: string) {
return str
.split(',')[0]
@ -24,17 +16,7 @@ export function kbd(str: string) {
})
}
/** @internal */
/** @public */
export function kbdStr(str: string) {
return (
'— ' +
str
.split(',')[0]
.split('')
.map((sub) => {
const subStr = sub.replace(/\$/g, cmdKey).replace(/\?/g, altKey).replace(/!/g, '⇧')
return subStr[0].toUpperCase() + subStr.slice(1)
})
.join('')
)
return '— ' + kbd(str).join('')
}