Tighten up editor ui (#2102)
This PR tightens up the editor UI. It removes padding around the editor. ![Kapture 2023-10-28 at 18 27 15](https://github.com/tldraw/tldraw/assets/23072548/18075308-7b62-43a1-8c80-ff4e4136197b) <img width="1196" alt="image" src="https://github.com/tldraw/tldraw/assets/23072548/a8205ef1-b142-4fdc-9745-e400c0c4939a"> <img width="1196" alt="image" src="https://github.com/tldraw/tldraw/assets/23072548/87e9dcd1-39f5-466a-a256-9cbd2ff2cf7e"> ### Change Type - [x] `minor` — New feature ### Release Notes - Small adjustment to editor ui.
This commit is contained in:
parent
d018cb9877
commit
ddb73cb6cf
50 changed files with 921 additions and 990 deletions
|
@ -38,6 +38,7 @@ export async function setupPage(page: PlaywrightTestArgs['page']) {
|
|||
await page.evaluate(() => {
|
||||
editor.user.updateUserPreferences({ animationSpeed: 0 })
|
||||
})
|
||||
await page.mouse.move(50, 50)
|
||||
}
|
||||
|
||||
export async function setupPageWithShapes(page: PlaywrightTestArgs['page']) {
|
||||
|
|
|
@ -221,13 +221,7 @@ test.describe('Export snapshots', () => {
|
|||
})
|
||||
|
||||
async function snapshotTest(page: Page) {
|
||||
const downloadEvent = page.waitForEvent('download')
|
||||
await page.click('[data-testid="main.menu"]')
|
||||
await page.click('[data-testid="menu-item.edit"]')
|
||||
await page.click('[data-testid="menu-item.export-as"]')
|
||||
await page.click('[data-testid="menu-item.export-as-svg"]')
|
||||
|
||||
const download = await downloadEvent
|
||||
page.waitForEvent('download').then(async (download) => {
|
||||
const path = (await download.path()) as string
|
||||
assert(path)
|
||||
await rename(path, path + '.svg')
|
||||
|
@ -248,4 +242,6 @@ async function snapshotTest(page: Page) {
|
|||
omitBackground: true,
|
||||
clip,
|
||||
})
|
||||
})
|
||||
await page.evaluate(() => (window as any)['tldraw-export']())
|
||||
}
|
||||
|
|
|
@ -76,8 +76,14 @@ test.describe('Shape Tools', () => {
|
|||
if (!(await page.getByTestId(`tools.more`).isVisible())) {
|
||||
throw Error(`Tool more is not visible`)
|
||||
}
|
||||
|
||||
await page.getByTestId('tools.more').click()
|
||||
|
||||
if (!(await page.getByTestId(`tools.more.${tool}`).isVisible())) {
|
||||
throw Error(`Tool in more panel is not visible`)
|
||||
}
|
||||
await page.getByTestId(`tools.more.${tool}`).click()
|
||||
|
||||
await page.getByTestId(`tools.more`).click()
|
||||
}
|
||||
|
||||
if (!(await page.getByTestId(`tools.${tool}`).isVisible())) {
|
||||
|
@ -106,6 +112,8 @@ test.describe('Shape Tools', () => {
|
|||
// Find and click the button
|
||||
if (!(await page.getByTestId(`tools.${tool}`).isVisible())) {
|
||||
await page.getByTestId('tools.more').click()
|
||||
await page.getByTestId(`tools.more.${tool}`).click()
|
||||
await page.getByTestId('tools.more').click()
|
||||
}
|
||||
await page.getByTestId(`tools.${tool}`).click()
|
||||
|
||||
|
@ -124,7 +132,7 @@ test.describe('Shape Tools', () => {
|
|||
expect(await getAllShapeTypes(page)).toEqual([shape])
|
||||
|
||||
// Reset for next time
|
||||
await page.mouse.click(0, 0) // to ensure we're not focused
|
||||
await page.mouse.click(50, 50) // to ensure we're not focused
|
||||
await page.keyboard.press('v') // go to the select tool
|
||||
await page.keyboard.press('Control+a')
|
||||
await page.keyboard.press('Backspace')
|
||||
|
@ -142,7 +150,10 @@ test.describe('Shape Tools', () => {
|
|||
// Find and click the button
|
||||
if (!(await page.getByTestId(`tools.${tool}`).isVisible())) {
|
||||
await page.getByTestId('tools.more').click()
|
||||
await page.getByTestId(`tools.more.${tool}`).click()
|
||||
await page.getByTestId('tools.more').click()
|
||||
}
|
||||
|
||||
await page.getByTestId(`tools.${tool}`).click()
|
||||
|
||||
// Button should be selected
|
||||
|
@ -163,7 +174,7 @@ test.describe('Shape Tools', () => {
|
|||
expect(await getAllShapeTypes(page)).toEqual([shape])
|
||||
|
||||
// Reset for next time
|
||||
await page.mouse.click(0, 0) // to ensure we're not focused
|
||||
await page.mouse.click(50, 50) // to ensure we're not focused
|
||||
await page.keyboard.press('v')
|
||||
await page.keyboard.press('Control+a')
|
||||
await page.keyboard.press('Backspace')
|
||||
|
|
|
@ -71,7 +71,8 @@ test.describe('smoke tests', () => {
|
|||
expect(await getSelectedShapeColor()).toBe('black')
|
||||
|
||||
// when on a mobile device...
|
||||
const hasMobileMenu = await page.isVisible('.tlui-toolbar__styles__button')
|
||||
const mobileStylesButton = page.getByTestId('mobile.styles')
|
||||
const hasMobileMenu = await mobileStylesButton.isVisible()
|
||||
|
||||
if (hasMobileMenu) {
|
||||
// open the style menu
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Tldraw } from '@tldraw/tldraw'
|
||||
import { Tldraw, useActions } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/tldraw.css'
|
||||
import { useEffect } from 'react'
|
||||
;(window as any).__tldraw_ui_event = { id: 'NOTHING_YET' }
|
||||
;(window as any).__tldraw_editor_events = []
|
||||
|
||||
|
@ -15,7 +16,19 @@ export default function EndToEnd() {
|
|||
onUiEvent={(name, data) => {
|
||||
;(window as any).__tldraw_ui_event = { name, data }
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<SneakyExportButton />
|
||||
</Tldraw>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SneakyExportButton() {
|
||||
const actions = useActions()
|
||||
|
||||
useEffect(() => {
|
||||
;(window as any)['tldraw-export'] = () => actions['export-as-svg'].onSelect('unknown')
|
||||
}, [actions])
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 3L27 3L27 27L3 27L3 3Z" stroke="black" stroke-width="2"/>
|
||||
<path d="M8 8H22V22H8V8Z" stroke="black" stroke-opacity="0.5" stroke-width="2"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 173 B After Width: | Height: | Size: 184 B |
|
@ -295,7 +295,7 @@
|
|||
"style-panel.align": "Align",
|
||||
"style-panel.vertical-align": "Vertical align",
|
||||
"style-panel.position": "Position",
|
||||
"style-panel.arrowheads": "Arrowheads",
|
||||
"style-panel.arrowheads": "Arrows",
|
||||
"style-panel.arrowhead-start": "Start",
|
||||
"style-panel.arrowhead-end": "End",
|
||||
"style-panel.color": "Color",
|
||||
|
|
|
@ -18,12 +18,15 @@
|
|||
/* Radius */
|
||||
--radius-0: 2px;
|
||||
--radius-1: 4px;
|
||||
--radius-2: 7px;
|
||||
--radius-2: 6px;
|
||||
--radius-3: 9px;
|
||||
--radius-4: 12px;
|
||||
--radius-5: 16px;
|
||||
--radius-4: 13px;
|
||||
--layer-background: 100;
|
||||
--layer-grid: 150;
|
||||
--layer-canvas: 200;
|
||||
--layer-shapes: 300;
|
||||
--layer-overlays: 400;
|
||||
--layer-following-indicator: 1000;
|
||||
/* Misc */
|
||||
--tl-zoom: 1;
|
||||
--tl-dpr-multiple: 100;
|
||||
|
@ -98,8 +101,10 @@
|
|||
--color-brush-fill: rgba(144, 144, 144, 0.102);
|
||||
--color-brush-stroke: rgba(144, 144, 144, 0.251);
|
||||
--color-grid: rgb(109, 109, 109);
|
||||
--color-low: rgb(237, 240, 242);
|
||||
--color-low: hsl(204, 16%, 94%);
|
||||
--color-low-border: hsl(204, 16%, 92%);
|
||||
--color-culled: rgb(235, 238, 240);
|
||||
--color-muted-none: rgba(0, 0, 0, 0);
|
||||
--color-muted-0: rgba(0, 0, 0, 0.02);
|
||||
--color-muted-1: rgba(0, 0, 0, 0.1);
|
||||
--color-muted-2: rgba(0, 0, 0, 0.042);
|
||||
|
@ -116,19 +121,19 @@
|
|||
--color-selection-stroke: #2f80ed;
|
||||
--color-text-0: #1d1d1d;
|
||||
--color-text-1: #2d2d2d;
|
||||
--color-text-2: #5f6369;
|
||||
--color-text-3: #b6b7ba;
|
||||
--color-text-3: #a4a5a7;
|
||||
--color-text-shadow: #ffffff;
|
||||
--color-primary: #2f80ed;
|
||||
--color-warn: #d10b0b;
|
||||
--color-text: #000000;
|
||||
--color-laser: #ff0000;
|
||||
|
||||
--shadow-1: 0px 1px 2px rgba(0, 0, 0, 0.22), 0px 1px 3px rgba(0, 0, 0, 0.09);
|
||||
--shadow-2: 0px 0px 2px rgba(0, 0, 0, 0.12), 0px 2px 3px rgba(0, 0, 0, 0.24),
|
||||
--shadow-1: 0px 1px 2px rgba(0, 0, 0, 0.25), 0px 1px 3px rgba(0, 0, 0, 0.09);
|
||||
--shadow-2: 0px 0px 2px rgba(0, 0, 0, 0.16), 0px 2px 3px rgba(0, 0, 0, 0.24),
|
||||
0px 2px 6px rgba(0, 0, 0, 0.1), inset 0px 0px 0px 1px var(--color-panel-contrast);
|
||||
--shadow-3: 0px 1px 2px rgba(0, 0, 0, 0.25), 0px 2px 6px rgba(0, 0, 0, 0.14),
|
||||
--shadow-3: 0px 1px 2px rgba(0, 0, 0, 0.28), 0px 2px 6px rgba(0, 0, 0, 0.14),
|
||||
inset 0px 0px 0px 1px var(--color-panel-contrast);
|
||||
--shadow-4: 0px 0px 3px rgba(0, 0, 0, 0.16), 0px 5px 4px rgba(0, 0, 0, 0.16),
|
||||
--shadow-4: 0px 0px 3px rgba(0, 0, 0, 0.19), 0px 5px 4px rgba(0, 0, 0, 0.16),
|
||||
0px 2px 16px rgba(0, 0, 0, 0.06), inset 0px 0px 0px 1px var(--color-panel-contrast);
|
||||
}
|
||||
|
||||
|
@ -139,7 +144,9 @@
|
|||
--color-brush-stroke: rgba(180, 180, 180, 0.25);
|
||||
--color-grid: #909090;
|
||||
--color-low: #2c3136;
|
||||
--color-culled: rgb(47, 52, 57);
|
||||
--color-low-border: #30363b
|
||||
--color-culled: blue;
|
||||
--color-muted-none: rgba(255, 255, 255, 0);
|
||||
--color-muted-0: rgba(255, 255, 255, 0.02);
|
||||
--color-muted-1: rgba(255, 255, 255, 0.1);
|
||||
--color-muted-2: rgba(255, 255, 255, 0.05);
|
||||
|
@ -156,10 +163,10 @@
|
|||
--color-selection-stroke: #2f80ed;
|
||||
--color-text-0: #f0eded;
|
||||
--color-text-1: #d9d9d9;
|
||||
--color-text-2: #8e9094;
|
||||
--color-text-3: #515a62;
|
||||
--color-text-3: #6d747b;
|
||||
--color-text-shadow: #292f35;
|
||||
--color-primary: #2f80ed;
|
||||
--color-warn: #d10b0b;
|
||||
--color-warn: #ef6464;
|
||||
--color-text: #f8f9fa;
|
||||
--color-laser: #ff0000;
|
||||
|
||||
|
@ -234,12 +241,12 @@ input,
|
|||
|
||||
.tl-shapes {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
z-index: var(--layer-shapes);
|
||||
}
|
||||
|
||||
.tl-overlays {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
z-index: var(--layer-overlays);
|
||||
}
|
||||
|
||||
.tl-overlays__item {
|
||||
|
@ -275,6 +282,7 @@ input,
|
|||
position: absolute;
|
||||
background-color: var(--color-background);
|
||||
inset: 0px;
|
||||
z-index: var(--layer-background);
|
||||
}
|
||||
|
||||
/* --------------------- Grid Layer --------------------- */
|
||||
|
@ -287,7 +295,7 @@ input,
|
|||
height: 100%;
|
||||
touch-action: none;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
z-index: var(--layer-grid);
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
|
@ -586,7 +594,7 @@ input,
|
|||
transform-origin: top right;
|
||||
background-color: var(--color-background);
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius-1);
|
||||
}
|
||||
|
||||
/* --------------------- Nametag -------------------- */
|
||||
|
@ -967,7 +975,7 @@ input,
|
|||
color: var(--color-text);
|
||||
text-overflow: ellipsis;
|
||||
text-decoration: none;
|
||||
color: var(--color-text-2);
|
||||
color: var(--color-text-1);
|
||||
cursor: var(--tl-cursor-pointer);
|
||||
}
|
||||
|
||||
|
@ -1516,17 +1524,18 @@ it from receiving any pointer events or affecting the cursor. */
|
|||
width: 400px;
|
||||
max-height: 100%;
|
||||
background-color: var(--color-panel);
|
||||
padding: var(--space-6);
|
||||
border-radius: var(--radius-4);
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
box-shadow: var(--shadow-2);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
overflow: auto;
|
||||
z-index: 600;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tl-error-boundary__content__expanded {
|
||||
width: 600px;
|
||||
}
|
||||
|
@ -1549,7 +1558,7 @@ it from receiving any pointer events or affecting the cursor. */
|
|||
overflow: auto;
|
||||
font-size: 12px;
|
||||
max-height: 320px;
|
||||
margin: 0px;
|
||||
|
||||
}
|
||||
|
||||
.tl-error-boundary__content button {
|
||||
|
@ -1574,7 +1583,7 @@ it from receiving any pointer events or affecting the cursor. */
|
|||
text-decoration: none;
|
||||
}
|
||||
.tl-error-boundary__content a:hover {
|
||||
color: var(--color-text-2);
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
.tl-error-boundary__content__error {
|
||||
|
@ -1594,8 +1603,8 @@ it from receiving any pointer events or affecting the cursor. */
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-4);
|
||||
margin: calc(var(--space-4) * -1);
|
||||
margin-top: 0px;
|
||||
margin: 0px;
|
||||
margin-left: -4px;
|
||||
}
|
||||
.tl-error-boundary__content__actions__group {
|
||||
display: flex;
|
||||
|
|
|
@ -1024,10 +1024,11 @@ export function parseTldrawJsonFile({ json, schema, }: {
|
|||
function RadioItem({ children, onSelect, ...rest }: DropdownMenuCheckboxItemProps): JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
function Root({ id, children, modal, }: {
|
||||
function Root({ id, children, modal, debugOpen, }: {
|
||||
id: string;
|
||||
children: any;
|
||||
modal?: boolean;
|
||||
debugOpen?: boolean;
|
||||
}): JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -1309,7 +1310,7 @@ export interface TLUiButtonProps extends React_3.HTMLAttributes<HTMLButtonElemen
|
|||
// (undocumented)
|
||||
spinner?: boolean;
|
||||
// (undocumented)
|
||||
type?: 'danger' | 'normal' | 'primary';
|
||||
type: 'danger' | 'help' | 'icon' | 'low' | 'menu' | 'normal' | 'primary' | 'tool';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -1518,7 +1519,7 @@ export interface TLUiToast {
|
|||
// (undocumented)
|
||||
description?: string;
|
||||
// (undocumented)
|
||||
icon?: string;
|
||||
icon?: TLUiIconType;
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
|
@ -1534,7 +1535,7 @@ export interface TLUiToastAction {
|
|||
// (undocumented)
|
||||
onClick: () => void;
|
||||
// (undocumented)
|
||||
type: 'primary' | 'secondary' | 'warn';
|
||||
type: 'danger' | 'normal' | 'primary';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
|
|
@ -4137,11 +4137,11 @@
|
|||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "export declare function Root({ id, children, modal, }: "
|
||||
"text": "export declare function Root({ id, children, modal, debugOpen, }: "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "{\n id: string;\n children: any;\n modal?: boolean;\n}"
|
||||
"text": "{\n id: string;\n children: any;\n modal?: boolean;\n debugOpen?: boolean;\n}"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
@ -4166,7 +4166,7 @@
|
|||
"overloadIndex": 1,
|
||||
"parameters": [
|
||||
{
|
||||
"parameterName": "{ id, children, modal, }",
|
||||
"parameterName": "{ id, children, modal, debugOpen, }",
|
||||
"parameterTypeTokenRange": {
|
||||
"startIndex": 1,
|
||||
"endIndex": 2
|
||||
|
@ -14545,11 +14545,11 @@
|
|||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "type?: "
|
||||
"text": "type: "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "'danger' | 'normal' | 'primary'"
|
||||
"text": "'danger' | 'help' | 'icon' | 'low' | 'menu' | 'normal' | 'primary' | 'tool'"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
@ -14557,7 +14557,7 @@
|
|||
}
|
||||
],
|
||||
"isReadonly": false,
|
||||
"isOptional": true,
|
||||
"isOptional": false,
|
||||
"releaseTag": "Public",
|
||||
"name": "type",
|
||||
"propertyTypeTokenRange": {
|
||||
|
@ -14583,7 +14583,7 @@
|
|||
"text": "export interface TLUiContextMenuProps "
|
||||
}
|
||||
],
|
||||
"fileUrlPath": "packages/tldraw/.tsbuild-api/lib/ui/components/ContextMenu.d.ts",
|
||||
"fileUrlPath": "packages/tldraw/src/lib/ui/components/ContextMenu.tsx",
|
||||
"releaseTag": "Public",
|
||||
"name": "TLUiContextMenuProps",
|
||||
"preserveMemberOrder": false,
|
||||
|
@ -14606,7 +14606,6 @@
|
|||
"text": ";"
|
||||
}
|
||||
],
|
||||
"fileUrlPath": "packages/tldraw/src/lib/ui/components/ContextMenu.tsx",
|
||||
"isReadonly": false,
|
||||
"isOptional": false,
|
||||
"releaseTag": "Public",
|
||||
|
@ -16746,8 +16745,9 @@
|
|||
"text": "icon?: "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "string"
|
||||
"kind": "Reference",
|
||||
"text": "TLUiIconType",
|
||||
"canonicalReference": "@tldraw/tldraw!TLUiIconType:type"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
@ -16927,7 +16927,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "'primary' | 'secondary' | 'warn'"
|
||||
"text": "'danger' | 'normal' | 'primary'"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -141,6 +141,7 @@ const TldrawUiContent = React.memo(function TldrawUI({
|
|||
{isFocusMode ? (
|
||||
<div className="tlui-layout__top">
|
||||
<Button
|
||||
type="icon"
|
||||
className="tlui-focus-button"
|
||||
title={`${msg('focus-mode.toggle-focus-mode')}`}
|
||||
icon="dot"
|
||||
|
|
|
@ -25,9 +25,9 @@ export const ActionsMenu = memo(function ActionsMenu() {
|
|||
return (
|
||||
<Button
|
||||
key={id}
|
||||
className="tlui-button-grid__button"
|
||||
data-testid={`menu-item.${item.id}`}
|
||||
icon={icon}
|
||||
type="icon"
|
||||
title={
|
||||
label
|
||||
? kbd
|
||||
|
@ -46,13 +46,14 @@ export const ActionsMenu = memo(function ActionsMenu() {
|
|||
}
|
||||
|
||||
return (
|
||||
<Popover id="actions menu">
|
||||
<Popover id="actions-menu">
|
||||
<PopoverTrigger>
|
||||
<Button
|
||||
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
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
|
@ -63,7 +64,7 @@ export const ActionsMenu = memo(function ActionsMenu() {
|
|||
dir="ltr"
|
||||
sideOffset={6}
|
||||
>
|
||||
<div className="tlui-actions-menu tlui-button-grid__four">
|
||||
<div className="tlui-actions-menu tlui-buttons__grid">
|
||||
{menuSchema.map(getActionMenuItem)}
|
||||
</div>
|
||||
</PopoverPrimitive.Content>
|
||||
|
|
|
@ -43,6 +43,7 @@ export function BackToContent() {
|
|||
<Button
|
||||
iconLeft={action.icon}
|
||||
label={action.label}
|
||||
type="low"
|
||||
onClick={() => {
|
||||
action.onSelect('helper-buttons')
|
||||
setShowBackToContent(false)
|
||||
|
|
|
@ -144,7 +144,7 @@ function ContextMenuContent() {
|
|||
<_ContextMenu.Sub key={item.id} onOpenChange={handleSubOpenChange}>
|
||||
<_ContextMenu.SubTrigger dir="ltr" disabled={item.disabled} asChild>
|
||||
<Button
|
||||
className="tlui-menu__button"
|
||||
type="menu"
|
||||
label={item.label}
|
||||
data-testid={`menu-item.${item.id}`}
|
||||
icon="chevron-right"
|
||||
|
@ -170,7 +170,7 @@ function ContextMenuContent() {
|
|||
return (
|
||||
<_ContextMenu.CheckboxItem
|
||||
key={id}
|
||||
className="tlui-button tlui-menu__button tlui-menu__checkbox-item"
|
||||
className="tlui-button tlui-button__menu tlui-button__checkbox"
|
||||
dir="ltr"
|
||||
disabled={item.disabled}
|
||||
onSelect={(e) => {
|
||||
|
@ -179,18 +179,13 @@ function ContextMenuContent() {
|
|||
}}
|
||||
title={labelStr ? labelStr : undefined}
|
||||
checked={item.checked}
|
||||
>
|
||||
<div
|
||||
className="tlui-menu__checkbox-item__check"
|
||||
style={{
|
||||
transformOrigin: '75% center',
|
||||
transform: `scale(${item.checked ? 1 : 0.5})`,
|
||||
opacity: item.checked ? 1 : 0.5,
|
||||
}}
|
||||
>
|
||||
<Icon small icon={item.checked ? 'check' : 'checkbox-empty'} />
|
||||
</div>
|
||||
{labelStr && <span>{labelStr}</span>}
|
||||
{labelStr && (
|
||||
<span className="tlui-button__label" draggable={false}>
|
||||
{labelStr}
|
||||
</span>
|
||||
)}
|
||||
{kbd && <Kbd>{kbd}</Kbd>}
|
||||
</_ContextMenu.CheckboxItem>
|
||||
)
|
||||
|
@ -199,7 +194,7 @@ function ContextMenuContent() {
|
|||
return (
|
||||
<_ContextMenu.Item key={id} dir="ltr" asChild>
|
||||
<Button
|
||||
className="tlui-menu__button"
|
||||
type="menu"
|
||||
data-testid={`menu-item.${id}`}
|
||||
kbd={kbd}
|
||||
label={labelToUse}
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
import * as React from 'react'
|
||||
import { useDialogs } from '../hooks/useDialogsProvider'
|
||||
import { useToasts } from '../hooks/useToastsProvider'
|
||||
import { useTranslation } from '../hooks/useTranslation/useTranslation'
|
||||
import { untranslated, useTranslation } from '../hooks/useTranslation/useTranslation'
|
||||
import { Button } from './primitives/Button'
|
||||
import * as Dialog from './primitives/Dialog'
|
||||
import * as DropdownMenu from './primitives/DropdownMenu'
|
||||
|
@ -53,7 +53,7 @@ export const DebugPanel = React.memo(function DebugPanel({
|
|||
<ShapeCount />
|
||||
<DropdownMenu.Root id="debug">
|
||||
<DropdownMenu.Trigger>
|
||||
<Button icon="dots-horizontal" title={msg('debug-panel.more')} />
|
||||
<Button type="icon" icon="dots-horizontal" title={msg('debug-panel.more')} />
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content side="top" align="end" alignOffset={0}>
|
||||
<DebugMenuContent renderDebugMenuItems={renderDebugMenuItems} />
|
||||
|
@ -83,29 +83,60 @@ const DebugMenuContent = track(function DebugMenuContent({
|
|||
const editor = useEditor()
|
||||
const { addToast } = useToasts()
|
||||
const { addDialog } = useDialogs()
|
||||
|
||||
const [error, setError] = React.useState<boolean>(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item
|
||||
type="menu"
|
||||
onClick={() => {
|
||||
addToast({
|
||||
id: uniqueId(),
|
||||
title: 'Something happened',
|
||||
description: 'Hey, attend to this thing over here. It might be important!',
|
||||
keepOpen: true,
|
||||
// icon?: string
|
||||
// title?: string
|
||||
// description?: string
|
||||
// actions?: TLUiToastAction[]
|
||||
})
|
||||
addToast({
|
||||
id: uniqueId(),
|
||||
title: 'Something happened',
|
||||
description: 'Hey, attend to this thing over here. It might be important!',
|
||||
keepOpen: true,
|
||||
icon: 'twitter',
|
||||
actions: [
|
||||
{
|
||||
label: 'Primary',
|
||||
type: 'primary',
|
||||
onClick: () => {
|
||||
void null
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Normal',
|
||||
type: 'normal',
|
||||
onClick: () => {
|
||||
void null
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Danger',
|
||||
type: 'danger',
|
||||
onClick: () => {
|
||||
void null
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}}
|
||||
>
|
||||
<span>Show toast</span>
|
||||
</DropdownMenu.Item>
|
||||
label={untranslated('Show toast')}
|
||||
/>
|
||||
|
||||
<DropdownMenu.Item
|
||||
type="menu"
|
||||
onClick={() => {
|
||||
addDialog({
|
||||
component: ({ onClose }) => (
|
||||
|
@ -124,13 +155,15 @@ const DebugMenuContent = track(function DebugMenuContent({
|
|||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
<span>Show dialog</span>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onClick={() => createNShapes(editor, 100)}>
|
||||
<span>Create 100 shapes</span>
|
||||
</DropdownMenu.Item>
|
||||
label={untranslated('Show dialog')}
|
||||
/>
|
||||
<DropdownMenu.Item
|
||||
type="menu"
|
||||
onClick={() => createNShapes(editor, 100)}
|
||||
label={untranslated('Create 100 shapes')}
|
||||
/>
|
||||
<DropdownMenu.Item
|
||||
type="menu"
|
||||
onClick={() => {
|
||||
function countDescendants({ children }: HTMLElement) {
|
||||
let count = 0
|
||||
|
@ -158,27 +191,25 @@ const DebugMenuContent = track(function DebugMenuContent({
|
|||
|
||||
window.alert(`Shapes ${shapes.length}, DOM nodes:${descendants}`)
|
||||
}}
|
||||
>
|
||||
<span>Count shapes and nodes</span>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
label={untranslated('Count shapes / nodes')}
|
||||
/>
|
||||
{(() => {
|
||||
if (error) throw Error('oh no!')
|
||||
})()}
|
||||
<DropdownMenu.Item
|
||||
type="menu"
|
||||
onClick={() => {
|
||||
setError(true)
|
||||
}}
|
||||
>
|
||||
<span>Throw error</span>
|
||||
</DropdownMenu.Item>
|
||||
label={untranslated('Throw error')}
|
||||
/>
|
||||
<DropdownMenu.Item
|
||||
type="menu"
|
||||
onClick={() => {
|
||||
hardResetEditor()
|
||||
}}
|
||||
>
|
||||
<span>Hard reset</span>
|
||||
</DropdownMenu.Item>
|
||||
label={untranslated('Hard reset')}
|
||||
/>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Group>
|
||||
<DebugFlagToggle flag={debugFlags.debugSvg} />
|
||||
|
@ -206,8 +237,14 @@ function Toggle({
|
|||
onChange: (newValue: boolean) => void
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenu.CheckboxItem title={label} checked={value} onSelect={() => onChange(!value)}>
|
||||
<DropdownMenu.CheckboxItem
|
||||
title={untranslated(label)}
|
||||
checked={value}
|
||||
onSelect={() => onChange(!value)}
|
||||
>
|
||||
<span className="tlui-button__label" draggable={false}>
|
||||
{label}
|
||||
</span>
|
||||
</DropdownMenu.CheckboxItem>
|
||||
)
|
||||
}
|
||||
|
@ -262,14 +299,17 @@ function ExampleDialog({
|
|||
<Dialog.Footer className="tlui-dialog__footer__actions">
|
||||
{displayDontShowAgain && (
|
||||
<Button
|
||||
type="normal"
|
||||
onClick={() => setDontShowAgain(!dontShowAgain)}
|
||||
iconLeft={dontShowAgain ? 'checkbox-checked' : 'checkbox-empty'}
|
||||
iconLeft={dontShowAgain ? 'check' : 'checkbox-empty'}
|
||||
style={{ marginRight: 'auto' }}
|
||||
>
|
||||
{`Don't show again`}
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={onCancel}>{cancel}</Button>
|
||||
<Button type="normal" onClick={onCancel}>
|
||||
{cancel}
|
||||
</Button>
|
||||
<Button type="primary" onClick={async () => onContinue()}>
|
||||
{confirm}
|
||||
</Button>
|
||||
|
|
|
@ -13,6 +13,7 @@ export const DuplicateButton = track(function DuplicateButton() {
|
|||
return (
|
||||
<Button
|
||||
icon={action.icon}
|
||||
type="icon"
|
||||
onClick={() => action.onSelect('quick-actions')}
|
||||
disabled={!(editor.isIn('select') && editor.selectedShapeIds.length > 0)}
|
||||
title={`${msg(action.label!)} ${kbdStr(action.kbd!)}`}
|
||||
|
|
|
@ -157,7 +157,7 @@ export const EditLinkDialogInner = track(function EditLinkDialogInner({
|
|||
</div>
|
||||
</Dialog.Body>
|
||||
<Dialog.Footer className="tlui-dialog__footer__actions">
|
||||
<Button onClick={handleCancel} onTouchEnd={handleCancel}>
|
||||
<Button type="normal" onClick={handleCancel} onTouchEnd={handleCancel}>
|
||||
{msg('edit-link-dialog.cancel')}
|
||||
</Button>
|
||||
{isRemoving ? (
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useRef, useState } from 'react'
|
|||
import { TLEmbedResult, getEmbedInfo } from '../../utils/embeds'
|
||||
import { useAssetUrls } from '../hooks/useAssetUrls'
|
||||
import { TLUiDialogProps } from '../hooks/useDialogsProvider'
|
||||
import { useTranslation } from '../hooks/useTranslation/useTranslation'
|
||||
import { untranslated, useTranslation } from '../hooks/useTranslation/useTranslation'
|
||||
import { Button } from './primitives/Button'
|
||||
import * as Dialog from './primitives/Dialog'
|
||||
import { Icon } from './primitives/Icon'
|
||||
|
@ -88,6 +88,7 @@ export const EmbedDialog = track(function EmbedDialog({ onClose }: TLUiDialogPro
|
|||
</Dialog.Body>
|
||||
<Dialog.Footer className="tlui-dialog__footer__actions">
|
||||
<Button
|
||||
type="normal"
|
||||
onClick={() => {
|
||||
setEmbedDefinition(null)
|
||||
setEmbedInfoForUrl(null)
|
||||
|
@ -96,7 +97,7 @@ export const EmbedDialog = track(function EmbedDialog({ onClose }: TLUiDialogPro
|
|||
label="embed-dialog.back"
|
||||
/>
|
||||
<div className="tlui-embed__spacer" />
|
||||
<Button label="embed-dialog.cancel" onClick={onClose} />
|
||||
<Button type="normal" label="embed-dialog.cancel" onClick={onClose} />
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={!embedInfoForUrl}
|
||||
|
@ -121,25 +122,20 @@ export const EmbedDialog = track(function EmbedDialog({ onClose }: TLUiDialogPro
|
|||
<Dialog.Body className="tlui-embed-dialog__list">
|
||||
{EMBED_DEFINITIONS.map((def) => {
|
||||
return (
|
||||
<button
|
||||
<Button
|
||||
type="menu"
|
||||
key={def.type}
|
||||
className="tlui-embed-dialog__item"
|
||||
onClick={() => setEmbedDefinition(def)}
|
||||
label={untranslated(def.title)}
|
||||
>
|
||||
<div className="tlui-embed-dialog__item__image">
|
||||
<div
|
||||
className="tlui-embed-dialog__item__image__img"
|
||||
style={{
|
||||
backgroundImage: `url(${assetUrls.embedIcons[def.type]})`,
|
||||
}}
|
||||
className="tlui-embed-dialog__item__image"
|
||||
style={{ backgroundImage: `url(${assetUrls.embedIcons[def.type]})` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="tlui-embed-dialog__item__title">{def.title}</div>
|
||||
</button>
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</Dialog.Body>
|
||||
<div className="tlui-dialog__scrim" />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -10,5 +10,5 @@ export function FollowingIndicator() {
|
|||
function FollowingIndicatorInner({ userId }: { userId: string }) {
|
||||
const presence = usePresence(userId)
|
||||
if (!presence) return null
|
||||
return <div className="tlui-following" style={{ borderColor: presence.color }} />
|
||||
return <div className="tlui-following-indicator" style={{ borderColor: presence.color }} />
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ import { TLUiTranslationKey } from '../hooks/useTranslation/TLUiTranslationKey'
|
|||
import { useTranslation } from '../hooks/useTranslation/useTranslation'
|
||||
import { TLUiIconType } from '../icon-types'
|
||||
import { LanguageMenu } from './LanguageMenu'
|
||||
import { Button } from './primitives/Button'
|
||||
import * as M from './primitives/DropdownMenu'
|
||||
import { Icon } from './primitives/Icon'
|
||||
|
||||
interface HelpMenuLink {
|
||||
label: TLUiTranslationKey
|
||||
|
@ -32,12 +32,14 @@ export const HelpMenu = React.memo(function HelpMenu() {
|
|||
return (
|
||||
<div className="tlui-help-menu">
|
||||
<Root dir="ltr" open={isOpen} onOpenChange={onOpenChange} modal={false}>
|
||||
<Trigger
|
||||
className="tlui-button tlui-help-menu__button"
|
||||
dir="ltr"
|
||||
<Trigger asChild dir="ltr">
|
||||
<Button
|
||||
type="help"
|
||||
className="tlui-button"
|
||||
smallIcon
|
||||
title={msg('help-menu.title')}
|
||||
>
|
||||
<Icon icon="question-mark" />
|
||||
icon="question-mark"
|
||||
/>
|
||||
</Trigger>
|
||||
<Portal container={container} dir="ltr">
|
||||
<Content
|
||||
|
@ -90,6 +92,7 @@ function HelpMenuContent() {
|
|||
const { id, kbd, label, onSelect, icon } = item.actionItem
|
||||
return (
|
||||
<M.Item
|
||||
type="menu"
|
||||
key={id}
|
||||
kbd={kbd}
|
||||
label={label}
|
||||
|
|
|
@ -25,7 +25,7 @@ export function LanguageMenu() {
|
|||
checked={locale === currentLanguage}
|
||||
onSelect={() => handleLanguageSelect(locale)}
|
||||
>
|
||||
<span>{label}</span>
|
||||
<span className="tlui-button__label">{label}</span>
|
||||
</D.RadioItem>
|
||||
))}
|
||||
</D.Group>
|
||||
|
|
|
@ -17,10 +17,12 @@ export const Menu = React.memo(function Menu() {
|
|||
<M.Root id="main menu">
|
||||
<M.Trigger>
|
||||
<Button
|
||||
type="icon"
|
||||
className="tlui-menu__trigger"
|
||||
data-testid="main.menu"
|
||||
title={msg('menu.title')}
|
||||
icon="menu"
|
||||
smallIcon
|
||||
/>
|
||||
</M.Trigger>
|
||||
<M.Content alignOffset={0} sideOffset={6}>
|
||||
|
@ -100,7 +102,7 @@ function MenuContent() {
|
|||
checked={item.checked}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{labelStr && <span>{labelStr}</span>}
|
||||
{labelStr && <span className="tlui-button__label">{labelStr}</span>}
|
||||
{kbd && <Kbd>{kbd}</Kbd>}
|
||||
</M.CheckboxItem>
|
||||
)
|
||||
|
@ -109,6 +111,7 @@ function MenuContent() {
|
|||
// Item is a button
|
||||
return (
|
||||
<M.Item
|
||||
type="menu"
|
||||
key={id}
|
||||
data-testid={`menu-item.${item.id}`}
|
||||
kbd={kbd}
|
||||
|
|
|
@ -17,13 +17,11 @@ export const MenuZone = track(function MenuZone() {
|
|||
|
||||
return (
|
||||
<div className="tlui-menu-zone">
|
||||
<div className="tlui-menu-zone__controls">
|
||||
<div className="tlui-buttons__horizontal">
|
||||
<Menu />
|
||||
<div className="tlui-menu-zone__divider" />
|
||||
<PageMenu />
|
||||
{breakpoint >= 6 && !isReadonly && !editor.isInAny('hand', 'zoom') && (
|
||||
<>
|
||||
<div className="tlui-menu-zone__divider" />
|
||||
<UndoButton />
|
||||
<RedoButton />
|
||||
<TrashButton />
|
||||
|
|
|
@ -43,7 +43,7 @@ export function MobileStylePanel() {
|
|||
<Popover id="style menu" onOpenChange={handleStylesOpenChange}>
|
||||
<PopoverTrigger disabled={disableStylePanel}>
|
||||
<Button
|
||||
className="tlui-toolbar__tools__button tlui-toolbar__styles__button"
|
||||
type="tool"
|
||||
data-testid="mobile.styles"
|
||||
style={{
|
||||
color: disableStylePanel ? 'var(--color-muted-1)' : currentColor,
|
||||
|
|
|
@ -16,7 +16,7 @@ export const MoveToPageMenu = track(function MoveToPageMenu() {
|
|||
<_ContextMenu.Sub>
|
||||
<_ContextMenu.SubTrigger dir="ltr" asChild>
|
||||
<Button
|
||||
className="tlui-menu__button"
|
||||
type="menu"
|
||||
label="context-menu.move-to-page"
|
||||
data-testid="menu-item.move-to-page"
|
||||
icon="chevron-right"
|
||||
|
@ -60,8 +60,9 @@ export const MoveToPageMenu = track(function MoveToPageMenu() {
|
|||
asChild
|
||||
>
|
||||
<Button
|
||||
type="menu"
|
||||
title={page.name}
|
||||
className="tlui-menu__button tlui-context-menu__move-to-page__name"
|
||||
className="tlui-context-menu__move-to-page__name"
|
||||
>
|
||||
<span>{page.name}</span>
|
||||
</Button>
|
||||
|
@ -88,8 +89,9 @@ export const MoveToPageMenu = track(function MoveToPageMenu() {
|
|||
asChild
|
||||
>
|
||||
<Button
|
||||
type="menu"
|
||||
title={msg('context.pages.new-page')}
|
||||
className="tlui-menu__button tlui-context-menu__move-to-page__name"
|
||||
className="tlui-context-menu__move-to-page__name"
|
||||
>
|
||||
{msg('context.pages.new-page')}
|
||||
</Button>
|
||||
|
|
|
@ -26,23 +26,25 @@ export const NavigationZone = memo(function NavigationZone() {
|
|||
|
||||
return (
|
||||
<div className="tlui-navigation-zone">
|
||||
<div className="tlui-navigation-zone__controls">
|
||||
<div className="tlui-buttons__horizontal">
|
||||
{breakpoint < 6 ? (
|
||||
<ZoomMenu />
|
||||
) : collapsed ? (
|
||||
<>
|
||||
<ZoomMenu />
|
||||
<Button
|
||||
type="icon"
|
||||
icon={collapsed ? 'chevrons-ne' : 'chevrons-sw'}
|
||||
data-testid="minimap.toggle"
|
||||
title={msg('navigation-zone.toggle-minimap')}
|
||||
className="tlui-navigation-zone__toggle"
|
||||
data-testid="minimap.toggle"
|
||||
onClick={toggleMinimap}
|
||||
icon={collapsed ? 'chevrons-ne' : 'chevrons-sw'}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
type="icon"
|
||||
icon="minus"
|
||||
data-testid="minimap.zoom-out"
|
||||
title={`${msg(actions['zoom-out'].label!)} ${kbdStr(actions['zoom-out'].kbd!)}`}
|
||||
|
@ -50,16 +52,19 @@ export const NavigationZone = memo(function NavigationZone() {
|
|||
/>
|
||||
<ZoomMenu />
|
||||
<Button
|
||||
type="icon"
|
||||
icon="plus"
|
||||
data-testid="minimap.zoom-in"
|
||||
title={`${msg(actions['zoom-in'].label!)} ${kbdStr(actions['zoom-in'].kbd!)}`}
|
||||
onClick={() => actions['zoom-in'].onSelect('navigation-zone')}
|
||||
/>
|
||||
<Button
|
||||
type="icon"
|
||||
icon={collapsed ? 'chevrons-ne' : 'chevrons-sw'}
|
||||
data-testid="minimap.toggle"
|
||||
title={msg('navigation-zone.toggle-minimap')}
|
||||
className="tlui-navigation-zone__toggle"
|
||||
onClick={toggleMinimap}
|
||||
icon={collapsed ? 'chevrons-ne' : 'chevrons-sw'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -24,6 +24,7 @@ export const ZoomMenu = track(function ZoomMenu() {
|
|||
<M.Root id="zoom">
|
||||
<M.Trigger>
|
||||
<Button
|
||||
type="icon"
|
||||
title={`${msg('navigation-zone.zoom')}`}
|
||||
data-testid="minimap.zoom-menu"
|
||||
className={breakpoint < 5 ? 'tlui-zoom-menu__button' : 'tlui-zoom-menu__button__pct'}
|
||||
|
@ -74,6 +75,7 @@ function ZoomMenuItem(props: {
|
|||
|
||||
return (
|
||||
<M.Item
|
||||
type="menu"
|
||||
label={actions[action].label}
|
||||
kbd={actions[action].kbd}
|
||||
data-testid={props['data-testid']}
|
||||
|
|
|
@ -45,13 +45,13 @@ export const PageItemSubmenu = track(function PageItemSubmenu({
|
|||
return (
|
||||
<M.Root id={`page item submenu ${index}`}>
|
||||
<M.Trigger>
|
||||
<Button title={msg('page-menu.submenu.title')} icon="dots-vertical" />
|
||||
<Button type="icon" title={msg('page-menu.submenu.title')} icon="dots-vertical" />
|
||||
</M.Trigger>
|
||||
<M.Content alignOffset={0}>
|
||||
<M.Group>
|
||||
{onRename && (
|
||||
<DropdownMenu.Item dir="ltr" onSelect={onRename} asChild>
|
||||
<Button className="tlui-menu__button" label="page-menu.submenu.rename" />
|
||||
<Button type="menu" label="page-menu.submenu.rename" />
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
<DropdownMenu.Item
|
||||
|
@ -60,23 +60,23 @@ export const PageItemSubmenu = track(function PageItemSubmenu({
|
|||
disabled={pages.length >= MAX_PAGES}
|
||||
asChild
|
||||
>
|
||||
<Button className="tlui-menu__button" label="page-menu.submenu.duplicate-page" />
|
||||
<Button type="menu" label="page-menu.submenu.duplicate-page" />
|
||||
</DropdownMenu.Item>
|
||||
{index > 0 && (
|
||||
<DropdownMenu.Item dir="ltr" onSelect={onMoveUp} asChild>
|
||||
<Button className="tlui-menu__button" label="page-menu.submenu.move-up" />
|
||||
<Button type="menu" label="page-menu.submenu.move-up" />
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
{index < listSize - 1 && (
|
||||
<DropdownMenu.Item dir="ltr" onSelect={onMoveDown} asChild>
|
||||
<Button className="tlui-menu__button" label="page-menu.submenu.move-down" />
|
||||
<Button type="menu" label="page-menu.submenu.move-down" />
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
</M.Group>
|
||||
{listSize > 1 && (
|
||||
<M.Group>
|
||||
<DropdownMenu.Item dir="ltr" onSelect={onDelete} asChild>
|
||||
<Button className="tlui-menu__button" label="page-menu.submenu.delete" />
|
||||
<Button type="menu" label="page-menu.submenu.delete" />
|
||||
</DropdownMenu.Item>
|
||||
</M.Group>
|
||||
)}
|
||||
|
|
|
@ -28,7 +28,7 @@ export const PageMenu = function PageMenu() {
|
|||
|
||||
const [isOpen, onOpenChange] = useMenuIsOpen('page-menu', handleOpenChange)
|
||||
|
||||
const ITEM_HEIGHT = breakpoint < 5 ? 36 : 40
|
||||
const ITEM_HEIGHT = 36
|
||||
|
||||
const rSortableContainer = useRef<HTMLDivElement>(null)
|
||||
|
||||
|
@ -251,12 +251,13 @@ export const PageMenu = function PageMenu() {
|
|||
}, [editor, msg, isReadonlyMode])
|
||||
|
||||
return (
|
||||
<Popover id="page menu" onOpenChange={onOpenChange} open={isOpen}>
|
||||
<Popover id="pages" onOpenChange={onOpenChange} open={isOpen}>
|
||||
<PopoverTrigger>
|
||||
<Button
|
||||
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>
|
||||
|
@ -267,14 +268,16 @@ export const PageMenu = function PageMenu() {
|
|||
<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
|
||||
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
|
||||
type="icon"
|
||||
data-testid="page-menu.create"
|
||||
icon="plus"
|
||||
title={msg(
|
||||
|
@ -285,7 +288,7 @@ export const PageMenu = function PageMenu() {
|
|||
disabled={maxPageCountReached}
|
||||
onClick={handleCreatePageClick}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
|
@ -310,6 +313,7 @@ export const PageMenu = function PageMenu() {
|
|||
}}
|
||||
>
|
||||
<Button
|
||||
type="icon"
|
||||
tabIndex={-1}
|
||||
className="tlui-page_menu__item__sortable__handle"
|
||||
icon="drag-handle-dots"
|
||||
|
@ -326,6 +330,7 @@ export const PageMenu = function PageMenu() {
|
|||
// to be fighting over scroll position. Nothing
|
||||
// else seems to work!
|
||||
<Button
|
||||
type="icon"
|
||||
className="tlui-page-menu__item__button"
|
||||
onClick={() => {
|
||||
const name = window.prompt('Rename page', page.name)
|
||||
|
@ -363,6 +368,7 @@ export const PageMenu = function PageMenu() {
|
|||
className="tlui-page-menu__item"
|
||||
>
|
||||
<Button
|
||||
type="icon"
|
||||
className="tlui-page-menu__item__button tlui-page-menu__item__button__checkbox"
|
||||
onClick={() => editor.setCurrentPage(page.id)}
|
||||
onDoubleClick={toggleEditing}
|
||||
|
|
|
@ -15,6 +15,7 @@ export const ExitPenMode = track(function ExitPenMode() {
|
|||
|
||||
return (
|
||||
<Button
|
||||
type="normal"
|
||||
label={action.label}
|
||||
iconLeft={action.icon}
|
||||
onClick={() => action.onSelect('helper-buttons')}
|
||||
|
|
|
@ -16,6 +16,7 @@ export const RedoButton = memo(function RedoButton() {
|
|||
<Button
|
||||
data-testid="main.redo"
|
||||
icon={redo.icon}
|
||||
type="icon"
|
||||
title={`${msg(redo.label!)} ${kbdStr(redo.kbd!)}`}
|
||||
disabled={!canRedo}
|
||||
onClick={() => redo.onSelect('quick-actions')}
|
||||
|
|
|
@ -14,6 +14,7 @@ export const StopFollowing = track(function ExitPenMode() {
|
|||
|
||||
return (
|
||||
<Button
|
||||
type="normal"
|
||||
label={action.label}
|
||||
iconLeft={action.icon}
|
||||
onClick={() => action.onSelect('people-menu')}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Trigger } from '@radix-ui/react-dropdown-menu'
|
||||
import { SharedStyle, StyleProp, preventDefault } from '@tldraw/editor'
|
||||
import classNames from 'classnames'
|
||||
import * as React from 'react'
|
||||
import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
|
||||
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
|
||||
|
@ -60,6 +59,7 @@ export const DoubleDropdownPicker = React.memo(function DoubleDropdownPicker<T e
|
|||
<div title={msg(label)} className="tlui-style-panel__double-select-picker-label">
|
||||
{msg(label)}
|
||||
</div>
|
||||
<div className="tlui-buttons__horizontal">
|
||||
<DropdownMenu.Root id={`style panel ${uiTypeA} A`}>
|
||||
<Trigger
|
||||
asChild
|
||||
|
@ -67,6 +67,7 @@ export const DoubleDropdownPicker = React.memo(function DoubleDropdownPicker<T e
|
|||
onTouchEnd={(e) => preventDefault(e)}
|
||||
>
|
||||
<Button
|
||||
type="icon"
|
||||
data-testid={`style.${uiTypeA}`}
|
||||
title={
|
||||
msg(labelA) +
|
||||
|
@ -81,16 +82,11 @@ export const DoubleDropdownPicker = React.memo(function DoubleDropdownPicker<T e
|
|||
/>
|
||||
</Trigger>
|
||||
<DropdownMenu.Content side="bottom" align="end" sideOffset={0} alignOffset={-2}>
|
||||
<div
|
||||
className={classNames('tlui-button-grid', {
|
||||
'tlui-button-grid__two': itemsA.length < 4,
|
||||
'tlui-button-grid__four': itemsA.length >= 4,
|
||||
})}
|
||||
>
|
||||
<div className="tlui-buttons__grid">
|
||||
{itemsA.map((item) => {
|
||||
return (
|
||||
<DropdownMenu.Item
|
||||
className="tlui-button-grid__button"
|
||||
type="icon"
|
||||
title={
|
||||
msg(labelA) +
|
||||
' — ' +
|
||||
|
@ -114,6 +110,7 @@ export const DoubleDropdownPicker = React.memo(function DoubleDropdownPicker<T e
|
|||
onTouchEnd={(e) => preventDefault(e)}
|
||||
>
|
||||
<Button
|
||||
type="icon"
|
||||
data-testid={`style.${uiTypeB}`}
|
||||
title={
|
||||
msg(labelB) +
|
||||
|
@ -127,16 +124,11 @@ export const DoubleDropdownPicker = React.memo(function DoubleDropdownPicker<T e
|
|||
/>
|
||||
</Trigger>
|
||||
<DropdownMenu.Content side="bottom" align="end" sideOffset={0} alignOffset={-2}>
|
||||
<div
|
||||
className={classNames('tlui-button-grid', {
|
||||
'tlui-button-grid__two': itemsA.length < 4,
|
||||
'tlui-button-grid__four': itemsA.length >= 4,
|
||||
})}
|
||||
>
|
||||
<div className="tlui-buttons__grid">
|
||||
{itemsB.map((item) => {
|
||||
return (
|
||||
<DropdownMenu.Item
|
||||
className="tlui-button-grid__button"
|
||||
type="icon"
|
||||
title={
|
||||
msg(labelB) +
|
||||
' — ' +
|
||||
|
@ -153,5 +145,6 @@ export const DoubleDropdownPicker = React.memo(function DoubleDropdownPicker<T e
|
|||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { Trigger } from '@radix-ui/react-dropdown-menu'
|
||||
import { SharedStyle, StyleProp, preventDefault } 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 { Button } from '../primitives/Button'
|
||||
import { Button, TLUiButtonProps } from '../primitives/Button'
|
||||
import * as DropdownMenu from '../primitives/DropdownMenu'
|
||||
import { StyleValuesForUi } from './styles'
|
||||
|
||||
|
@ -16,6 +15,7 @@ interface DropdownPickerProps<T extends string> {
|
|||
style: StyleProp<T>
|
||||
value: SharedStyle<T>
|
||||
items: StyleValuesForUi<T>
|
||||
type: TLUiButtonProps['type']
|
||||
onValueChange: (style: StyleProp<T>, value: T, squashing: boolean) => void
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ export const DropdownPicker = React.memo(function DropdownPicker<T extends strin
|
|||
uiType,
|
||||
style,
|
||||
items,
|
||||
type,
|
||||
value,
|
||||
onValueChange,
|
||||
}: DropdownPickerProps<T>) {
|
||||
|
@ -43,6 +44,7 @@ export const DropdownPicker = React.memo(function DropdownPicker<T extends strin
|
|||
onTouchEnd={(e) => preventDefault(e)}
|
||||
>
|
||||
<Button
|
||||
type={type}
|
||||
data-testid={`style.${uiType}`}
|
||||
title={
|
||||
value.type === 'mixed'
|
||||
|
@ -54,17 +56,11 @@ export const DropdownPicker = React.memo(function DropdownPicker<T extends strin
|
|||
/>
|
||||
</Trigger>
|
||||
<DropdownMenu.Content side="left" align="center" alignOffset={0}>
|
||||
<div
|
||||
className={classNames('tlui-button-grid', {
|
||||
'tlui-button-grid__two': items.length < 3,
|
||||
'tlui-button-grid__three': items.length == 3,
|
||||
'tlui-button-grid__four': items.length >= 4,
|
||||
})}
|
||||
>
|
||||
<div className="tlui-buttons__grid">
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<DropdownMenu.Item
|
||||
className="tlui-button-grid__button"
|
||||
type="icon"
|
||||
data-testid={`style.${uiType}.${item.value}`}
|
||||
title={msg(`${uiType}-style.${item.value}` as TLUiTranslationKey)}
|
||||
key={item.value}
|
||||
|
|
|
@ -233,8 +233,10 @@ function TextStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
|
|||
value={align}
|
||||
onValueChange={handleValueChange}
|
||||
/>
|
||||
<div className="tlui-style-panel__row__extra-button">
|
||||
{verticalAlign === undefined ? (
|
||||
<Button
|
||||
type="icon"
|
||||
title={msg('style-panel.vertical-align')}
|
||||
data-testid="vertical-align"
|
||||
icon="vertical-align-center"
|
||||
|
@ -242,6 +244,7 @@ function TextStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
|
|||
/>
|
||||
) : (
|
||||
<DropdownPicker
|
||||
type="icon"
|
||||
id="geo-vertical-alignment"
|
||||
uiType="verticalAlign"
|
||||
style={DefaultVerticalAlignStyle}
|
||||
|
@ -251,6 +254,7 @@ function TextStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
@ -267,6 +271,7 @@ function GeoStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
|
|||
return (
|
||||
<DropdownPicker
|
||||
id="geo"
|
||||
type="menu"
|
||||
label={'style-panel.geo'}
|
||||
uiType="geo"
|
||||
style={GeoShapeGeoStyle}
|
||||
|
@ -288,6 +293,7 @@ function SplineStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
|
|||
return (
|
||||
<DropdownPicker
|
||||
id="spline"
|
||||
type="menu"
|
||||
label={'style-panel.spline'}
|
||||
uiType="spline"
|
||||
style={LineShapeSplineStyle}
|
||||
|
|
|
@ -40,28 +40,22 @@ 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
|
||||
className={
|
||||
action.type === 'warn' ? 'tlui-button__warning' : 'tlui-button__primary'
|
||||
}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
<Button type={action.type}>{action.label}</Button>
|
||||
</T.Action>
|
||||
))}
|
||||
{hasActions && (
|
||||
<T.Close asChild>
|
||||
<Button className="tlui-toast__close" style={{ marginLeft: 'auto' }}>
|
||||
<Button type="normal" className="tlui-toast__close" style={{ marginLeft: 'auto' }}>
|
||||
{toast.closeLabel ?? msg('toast.close')}
|
||||
</Button>
|
||||
</T.Close>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!hasActions && (
|
||||
<T.Close asChild>
|
||||
<Button className="tlui-toast__close">{toast.closeLabel ?? msg('toast.close')}</Button>
|
||||
<Button type="normal" className="tlui-toast__close">
|
||||
{toast.closeLabel ?? msg('toast.close')}
|
||||
</Button>
|
||||
</T.Close>
|
||||
)}
|
||||
</T.Root>
|
||||
|
|
|
@ -30,6 +30,7 @@ export function ToggleToolLockedButton({ activeToolId }: ToggleToolLockedButtonP
|
|||
|
||||
return (
|
||||
<Button
|
||||
type="normal"
|
||||
title={msg('action.toggle-tool-lock')}
|
||||
className={classNames('tlui-toolbar__lock-button', {
|
||||
'tlui-toolbar__lock-button__mobile': breakpoint < 5,
|
||||
|
|
|
@ -111,7 +111,7 @@ export const Toolbar = memo(function Toolbar() {
|
|||
{!isReadonly && (
|
||||
<div className="tlui-toolbar__extras">
|
||||
{breakpoint < 6 && !(activeToolId === 'hand' || activeToolId === 'zoom') && (
|
||||
<div className="tlui-toolbar__extras__controls">
|
||||
<div className="tlui-toolbar__extras__controls tlui-buttons__horizontal">
|
||||
<UndoButton />
|
||||
<RedoButton />
|
||||
<TrashButton />
|
||||
|
@ -149,7 +149,6 @@ export const Toolbar = memo(function Toolbar() {
|
|||
{showEditingTools && (
|
||||
<>
|
||||
{/* Draw / Eraser */}
|
||||
<div className="tlui-toolbar__divider" />
|
||||
{toolbarItems.slice(2, 4).map(({ toolItem }) => (
|
||||
<ToolbarButton
|
||||
key={toolItem.id}
|
||||
|
@ -159,7 +158,6 @@ export const Toolbar = memo(function Toolbar() {
|
|||
/>
|
||||
))}
|
||||
{/* Everything Else */}
|
||||
<div className="tlui-toolbar__divider" />
|
||||
{itemsInPanel.map(({ toolItem }) => (
|
||||
<ToolbarButton
|
||||
key={toolItem.id}
|
||||
|
@ -186,8 +184,9 @@ export const Toolbar = memo(function Toolbar() {
|
|||
<M.Root id="toolbar overflow" modal={false}>
|
||||
<M.Trigger>
|
||||
<Button
|
||||
className="tlui-toolbar__tools__button tlui-toolbar__overflow"
|
||||
className="tlui-toolbar__overflow"
|
||||
icon="chevron-up"
|
||||
type="tool"
|
||||
data-testid="tools.more"
|
||||
title={msg('tool-panel.more')}
|
||||
/>
|
||||
|
@ -220,13 +219,14 @@ const OverflowToolsContent = track(function OverflowToolsContent({
|
|||
const msg = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="tlui-button-grid__four tlui-button-grid__reverse">
|
||||
<div className="tlui-buttons__grid">
|
||||
{toolbarItems.map(({ toolItem: { id, meta, kbd, label, onSelect, icon } }) => {
|
||||
return (
|
||||
<M.Item
|
||||
key={id}
|
||||
type="icon"
|
||||
className="tlui-button-grid__button"
|
||||
data-testid={`tools.${id}`}
|
||||
data-testid={`tools.more.${id}`}
|
||||
data-tool={id}
|
||||
data-geo={meta?.geo ?? ''}
|
||||
aria-label={label}
|
||||
|
@ -251,7 +251,7 @@ function ToolbarButton({
|
|||
}) {
|
||||
return (
|
||||
<Button
|
||||
className="tlui-toolbar__tools__button"
|
||||
type="tool"
|
||||
data-testid={`tools.${item.id}`}
|
||||
data-tool={item.id}
|
||||
data-geo={item.meta?.geo ?? ''}
|
||||
|
|
|
@ -18,6 +18,7 @@ export const TrashButton = track(function TrashButton() {
|
|||
return (
|
||||
<Button
|
||||
icon={action.icon}
|
||||
type="icon"
|
||||
onClick={() => action.onSelect('quick-actions')}
|
||||
disabled={!(editor.isIn('select') && editor.selectedShapeIds.length > 0)}
|
||||
title={`${msg(action.label!)} ${kbdStr(action.kbd!)}`}
|
||||
|
|
|
@ -16,6 +16,7 @@ export const UndoButton = memo(function UndoButton() {
|
|||
<Button
|
||||
data-testid="main.undo"
|
||||
icon={undo.icon}
|
||||
type="icon"
|
||||
title={`${msg(undo.label!)} ${kbdStr(undo.kbd!)}`}
|
||||
disabled={!canUndo}
|
||||
onClick={() => undo.onSelect('quick-actions')}
|
||||
|
|
|
@ -19,7 +19,7 @@ export interface TLUiButtonProps extends React.HTMLAttributes<HTMLButtonElement>
|
|||
kbd?: string
|
||||
isChecked?: boolean
|
||||
invertIcon?: boolean
|
||||
type?: 'primary' | 'danger' | 'normal'
|
||||
type: 'normal' | 'primary' | 'danger' | 'low' | 'icon' | 'tool' | 'menu' | 'help'
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
@ -32,7 +32,7 @@ export const Button = React.forwardRef<HTMLButtonElement, TLUiButtonProps>(funct
|
|||
smallIcon,
|
||||
kbd,
|
||||
isChecked = false,
|
||||
type = 'normal',
|
||||
type,
|
||||
children,
|
||||
spinner,
|
||||
...props
|
||||
|
@ -51,10 +51,10 @@ export const Button = React.forwardRef<HTMLButtonElement, TLUiButtonProps>(funct
|
|||
title={props.title ?? labelStr}
|
||||
className={classnames('tlui-button', `tlui-button__${type}`, props.className)}
|
||||
>
|
||||
{iconLeft && <Icon icon={iconLeft} className="tlui-icon-left" small />}
|
||||
{iconLeft && <Icon icon={iconLeft} className="tlui-button__icon-left" small />}
|
||||
{children}
|
||||
{label && (
|
||||
<span draggable={false}>
|
||||
<span className="tlui-button__label" draggable={false}>
|
||||
{labelStr}
|
||||
{isChecked && <Icon icon="check" />}
|
||||
</span>
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
SharedStyle,
|
||||
StyleProp,
|
||||
TLDefaultColorStyle,
|
||||
clamp,
|
||||
getDefaultColorTheme,
|
||||
useEditor,
|
||||
useValue,
|
||||
|
@ -24,7 +23,6 @@ export interface ButtonPickerProps<T extends string> {
|
|||
style: StyleProp<T>
|
||||
value: SharedStyle<T>
|
||||
items: StyleValuesForUi<T>
|
||||
columns?: 2 | 3 | 4
|
||||
onValueChange: (style: StyleProp<T>, value: T, squashing: boolean) => void
|
||||
}
|
||||
|
||||
|
@ -35,7 +33,7 @@ function _ButtonPicker<T extends string>(props: ButtonPickerProps<T>) {
|
|||
title,
|
||||
style,
|
||||
value,
|
||||
columns = clamp(items.length, 2, 4),
|
||||
// columns = clamp(items.length, 2, 4),
|
||||
onValueChange,
|
||||
} = props
|
||||
const editor = useEditor()
|
||||
|
@ -99,15 +97,10 @@ function _ButtonPicker<T extends string>(props: ButtonPickerProps<T>) {
|
|||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('tlui-button-grid', {
|
||||
'tlui-button-grid__two': columns === 2,
|
||||
'tlui-button-grid__three': columns === 3,
|
||||
'tlui-button-grid__four': columns === 4,
|
||||
})}
|
||||
>
|
||||
<div className={classNames('tlui-buttons__grid')}>
|
||||
{items.map((item) => (
|
||||
<Button
|
||||
type="icon"
|
||||
key={item.value}
|
||||
data-id={item.value}
|
||||
data-testid={`style.${uiType}.${item.value}`}
|
||||
|
|
|
@ -22,7 +22,11 @@ export function CloseButton() {
|
|||
return (
|
||||
<div className="tlui-dialog__header__close">
|
||||
<_Dialog.DialogClose data-testid="dialog.close" dir="ltr" asChild>
|
||||
<Button aria-label="Close" onTouchEnd={(e) => (e.target as HTMLButtonElement).click()}>
|
||||
<Button
|
||||
type="icon"
|
||||
aria-label="Close"
|
||||
onTouchEnd={(e) => (e.target as HTMLButtonElement).click()}
|
||||
>
|
||||
<Icon small icon="cross-2" />
|
||||
</Button>
|
||||
</_Dialog.DialogClose>
|
||||
|
|
|
@ -10,15 +10,17 @@ export function Root({
|
|||
id,
|
||||
children,
|
||||
modal = false,
|
||||
debugOpen = false,
|
||||
}: {
|
||||
id: string
|
||||
children: any
|
||||
modal?: boolean
|
||||
debugOpen?: boolean
|
||||
}) {
|
||||
const [open, onOpenChange] = useMenuIsOpen(id)
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root open={open} dir="ltr" modal={modal} onOpenChange={onOpenChange}>
|
||||
<DropdownMenu.Root open={debugOpen || open} dir="ltr" modal={modal} onOpenChange={onOpenChange}>
|
||||
{children}
|
||||
</DropdownMenu.Root>
|
||||
)
|
||||
|
@ -101,7 +103,8 @@ export function SubTrigger({
|
|||
return (
|
||||
<DropdownMenu.SubTrigger dir="ltr" data-direction={dataDirection} data-testid={testId} asChild>
|
||||
<Button
|
||||
className="tlui-menu__button tlui-menu__submenu__trigger"
|
||||
type="menu"
|
||||
className="tlui-menu__submenu__trigger"
|
||||
label={label}
|
||||
icon="chevron-right"
|
||||
/>
|
||||
|
@ -171,7 +174,7 @@ export function Item({ noClose, ...props }: DropdownMenuItemProps) {
|
|||
asChild
|
||||
onClick={noClose || props.isChecked !== undefined ? preventDefault : undefined}
|
||||
>
|
||||
<Button className="tlui-menu__button" {...props} />
|
||||
<Button {...props} />
|
||||
</DropdownMenu.Item>
|
||||
)
|
||||
}
|
||||
|
@ -190,23 +193,14 @@ export function CheckboxItem({ children, onSelect, ...rest }: DropdownMenuCheckb
|
|||
return (
|
||||
<DropdownMenu.CheckboxItem
|
||||
dir="ltr"
|
||||
className="tlui-button tlui-menu__button tlui-menu__checkbox-item"
|
||||
className="tlui-button tlui-button__menu tlui-button__checkbox"
|
||||
onSelect={(e) => {
|
||||
onSelect?.(e)
|
||||
preventDefault(e)
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<div
|
||||
className="tlui-menu__checkbox-item__check"
|
||||
style={{
|
||||
transformOrigin: '75% center',
|
||||
transform: `scale(${rest.checked ? 1 : 0.5})`,
|
||||
opacity: rest.checked ? 1 : 0.5,
|
||||
}}
|
||||
>
|
||||
<Icon small icon={rest.checked ? 'check' : 'checkbox-empty'} />
|
||||
</div>
|
||||
{children}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
)
|
||||
|
@ -217,16 +211,18 @@ export function RadioItem({ children, onSelect, ...rest }: DropdownMenuCheckboxI
|
|||
return (
|
||||
<DropdownMenu.CheckboxItem
|
||||
dir="ltr"
|
||||
className="tlui-button tlui-menu__button tlui-menu__checkbox-item"
|
||||
className="tlui-button tlui-button__menu tlui-button__checkbox"
|
||||
onSelect={(e) => {
|
||||
onSelect?.(e)
|
||||
preventDefault(e)
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<DropdownMenu.ItemIndicator dir="ltr" className="tlui-menu__checkbox-item__check">
|
||||
<Icon icon="check" />
|
||||
<div className="tlui-button__checkbox__indicator">
|
||||
<DropdownMenu.ItemIndicator dir="ltr">
|
||||
<Icon icon="check" small />
|
||||
</DropdownMenu.ItemIndicator>
|
||||
</div>
|
||||
{children}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
)
|
||||
|
|
|
@ -10,11 +10,14 @@ type PopoverProps = {
|
|||
onOpenChange?: (isOpen: boolean) => void
|
||||
}
|
||||
|
||||
export const Popover: FC<PopoverProps> = ({ id, children, onOpenChange }) => {
|
||||
export const Popover: FC<PopoverProps> = ({ id, children, onOpenChange, open }) => {
|
||||
const [isOpen, handleOpenChange] = useMenuIsOpen(id, onOpenChange)
|
||||
|
||||
return (
|
||||
<PopoverPrimitive.Root onOpenChange={handleOpenChange} open={isOpen}>
|
||||
<PopoverPrimitive.Root
|
||||
onOpenChange={handleOpenChange}
|
||||
open={open || isOpen /* allow debugging */}
|
||||
>
|
||||
<div className="tlui-popover">{children}</div>
|
||||
</PopoverPrimitive.Root>
|
||||
)
|
||||
|
|
|
@ -35,6 +35,8 @@ export function useMenuIsOpen(id: string, cb?: (isOpen: boolean) => void) {
|
|||
[editor, id, cb]
|
||||
)
|
||||
|
||||
const isOpen = useValue('is menu open', () => editor.openMenus.includes(id), [editor, id])
|
||||
|
||||
useEffect(() => {
|
||||
// When the effect runs, if the menu is open then
|
||||
// add it to the open menus list.
|
||||
|
@ -67,7 +69,5 @@ export function useMenuIsOpen(id: string, cb?: (isOpen: boolean) => void) {
|
|||
}
|
||||
}, [editor, id, trackEvent])
|
||||
|
||||
const isOpen = useValue('is menu open', () => editor.openMenus.includes(id), [editor, id])
|
||||
|
||||
return [isOpen, onOpenChange] as const
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { Editor, uniqueId } from '@tldraw/editor'
|
||||
import { createContext, useCallback, useContext, useState } from 'react'
|
||||
import { TLUiIconType } from '../icon-types'
|
||||
|
||||
/** @public */
|
||||
export interface TLUiToast {
|
||||
id: string
|
||||
icon?: string
|
||||
icon?: TLUiIconType
|
||||
title?: string
|
||||
description?: string
|
||||
actions?: TLUiToastAction[]
|
||||
|
@ -14,7 +15,7 @@ export interface TLUiToast {
|
|||
|
||||
/** @public */
|
||||
export interface TLUiToastAction {
|
||||
type: 'primary' | 'secondary' | 'warn'
|
||||
type: 'primary' | 'danger' | 'normal'
|
||||
label: string
|
||||
onClick: () => void
|
||||
}
|
||||
|
|
|
@ -302,7 +302,7 @@ export const DEFAULT_TRANSLATION = {
|
|||
'style-panel.align': 'Align',
|
||||
'style-panel.vertical-align': 'Vertical align',
|
||||
'style-panel.position': 'Position',
|
||||
'style-panel.arrowheads': 'Arrowheads',
|
||||
'style-panel.arrowheads': 'Arrows',
|
||||
'style-panel.arrowhead-start': 'Start',
|
||||
'style-panel.arrowhead-end': 'End',
|
||||
'style-panel.color': 'Color',
|
||||
|
|
|
@ -111,3 +111,7 @@ export function useTranslation() {
|
|||
[translation]
|
||||
)
|
||||
}
|
||||
|
||||
export function untranslated(string: string) {
|
||||
return string as TLUiTranslationKey
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue