From 6def201da2927847ef81c25bfcdaadf7b0b51b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mime=20=C4=8Cuvalo?= Date: Wed, 27 Mar 2024 09:41:13 +0000 Subject: [PATCH] ui: make toasts look more toasty (#2988) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Screenshot 2024-03-11 at 14 03 44 ### Change Type - [x] `patch` — Bug fix ### Release Notes - UI: Add severity to toasts. --- .../src/components/SneakyOnDropOverride.ts | 1 + apps/dotcom/src/utils/sharing.ts | 2 + apps/dotcom/src/utils/useFileSystem.tsx | 1 + assets/icons/icon/check-circle.svg | 1 + assets/icons/icon/cross-circle.svg | 1 + assets/icons/icon/error.svg | 4 ++ packages/assets/imports.js | 6 ++ packages/assets/imports.vite.js | 6 ++ packages/assets/selfHosted.js | 3 + packages/assets/types.d.ts | 3 + packages/assets/urls.js | 9 +++ packages/editor/editor.css | 8 +++ packages/tldraw/api-report.md | 7 ++- packages/tldraw/api/api.json | 56 ++++++++++++++++++- packages/tldraw/src/index.ts | 1 + .../src/lib/defaultExternalContentHandlers.ts | 3 + packages/tldraw/src/lib/ui.css | 20 ++++++- .../DebugMenu/DefaultDebugMenuContent.tsx | 14 ++++- .../tldraw/src/lib/ui/components/Toasts.tsx | 16 +++++- packages/tldraw/src/lib/ui/context/toasts.tsx | 4 ++ packages/tldraw/src/lib/ui/hooks/useCopyAs.ts | 2 +- .../src/lib/ui/hooks/useEditorEvents.ts | 1 + .../tldraw/src/lib/ui/hooks/useExportAs.ts | 2 +- packages/tldraw/src/lib/ui/icon-types.ts | 6 ++ packages/tldraw/src/lib/utils/tldr/file.ts | 1 + 25 files changed, 167 insertions(+), 11 deletions(-) create mode 100644 assets/icons/icon/check-circle.svg create mode 100644 assets/icons/icon/cross-circle.svg create mode 100644 assets/icons/icon/error.svg diff --git a/apps/dotcom/src/components/SneakyOnDropOverride.ts b/apps/dotcom/src/components/SneakyOnDropOverride.ts index cf957a0d4..aa128f41b 100644 --- a/apps/dotcom/src/components/SneakyOnDropOverride.ts +++ b/apps/dotcom/src/components/SneakyOnDropOverride.ts @@ -16,6 +16,7 @@ export function SneakyOnDropOverride({ isMultiplayer }: { isMultiplayer: boolean addToast({ title: msg('file-system.shared-document-file-open-error.title'), description: msg('file-system.shared-document-file-open-error.description'), + severity: 'error', }) } else { const shouldOverride = await shouldOverrideDocument(addDialog) diff --git a/apps/dotcom/src/utils/sharing.ts b/apps/dotcom/src/utils/sharing.ts index 682645aa8..bdaa9ecfc 100644 --- a/apps/dotcom/src/utils/sharing.ts +++ b/apps/dotcom/src/utils/sharing.ts @@ -147,6 +147,7 @@ export function useSharing(): TLUiOverrides { addToast({ title: 'Error', description: msg('share-menu.upload-failed'), + severity: 'error', }) } }, @@ -242,6 +243,7 @@ async function getRoomData( addToast({ title: 'Too big!', description: msg('share-menu.project-too-large'), + severity: 'warning', }) trackAnalyticsEvent('shared-fail-too-big', { diff --git a/apps/dotcom/src/utils/useFileSystem.tsx b/apps/dotcom/src/utils/useFileSystem.tsx index 0c1e73e5f..c328bb138 100644 --- a/apps/dotcom/src/utils/useFileSystem.tsx +++ b/apps/dotcom/src/utils/useFileSystem.tsx @@ -44,6 +44,7 @@ export function useFileSystem({ isMultiplayer }: { isMultiplayer: boolean }): TL addToast({ title: msg('file-system.shared-document-file-open-error.title'), description: msg('file-system.shared-document-file-open-error.description'), + severity: 'error', }) return } diff --git a/assets/icons/icon/check-circle.svg b/assets/icons/icon/check-circle.svg new file mode 100644 index 000000000..1131b9e12 --- /dev/null +++ b/assets/icons/icon/check-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/icon/cross-circle.svg b/assets/icons/icon/cross-circle.svg new file mode 100644 index 000000000..c8a92bd8e --- /dev/null +++ b/assets/icons/icon/cross-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/icon/error.svg b/assets/icons/icon/error.svg new file mode 100644 index 000000000..854bf824f --- /dev/null +++ b/assets/icons/icon/error.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/assets/imports.js b/packages/assets/imports.js index 49a615e17..b2d4df5ee 100644 --- a/packages/assets/imports.js +++ b/packages/assets/imports.js @@ -55,6 +55,7 @@ import iconsAvatar from './icons/icon/avatar.svg' import iconsBlob from './icons/icon/blob.svg' import iconsBringForward from './icons/icon/bring-forward.svg' import iconsBringToFront from './icons/icon/bring-to-front.svg' +import iconsCheckCircle from './icons/icon/check-circle.svg' import iconsCheck from './icons/icon/check.svg' import iconsCheckboxChecked from './icons/icon/checkbox-checked.svg' import iconsCheckboxEmpty from './icons/icon/checkbox-empty.svg' @@ -71,6 +72,7 @@ import iconsCollab from './icons/icon/collab.svg' import iconsColor from './icons/icon/color.svg' import iconsComment from './icons/icon/comment.svg' import iconsCross2 from './icons/icon/cross-2.svg' +import iconsCrossCircle from './icons/icon/cross-circle.svg' import iconsCross from './icons/icon/cross.svg' import iconsDashDashed from './icons/icon/dash-dashed.svg' import iconsDashDotted from './icons/icon/dash-dotted.svg' @@ -85,6 +87,7 @@ import iconsDotsVertical from './icons/icon/dots-vertical.svg' import iconsDragHandleDots from './icons/icon/drag-handle-dots.svg' import iconsDuplicate from './icons/icon/duplicate.svg' import iconsEdit from './icons/icon/edit.svg' +import iconsError from './icons/icon/error.svg' import iconsExternalLink from './icons/icon/external-link.svg' import iconsFile from './icons/icon/file.svg' import iconsFillNone from './icons/icon/fill-none.svg' @@ -271,6 +274,7 @@ export function getAssetUrlsByImport(opts) { blob: formatAssetUrl(iconsBlob, opts), 'bring-forward': formatAssetUrl(iconsBringForward, opts), 'bring-to-front': formatAssetUrl(iconsBringToFront, opts), + 'check-circle': formatAssetUrl(iconsCheckCircle, opts), check: formatAssetUrl(iconsCheck, opts), 'checkbox-checked': formatAssetUrl(iconsCheckboxChecked, opts), 'checkbox-empty': formatAssetUrl(iconsCheckboxEmpty, opts), @@ -287,6 +291,7 @@ export function getAssetUrlsByImport(opts) { color: formatAssetUrl(iconsColor, opts), comment: formatAssetUrl(iconsComment, opts), 'cross-2': formatAssetUrl(iconsCross2, opts), + 'cross-circle': formatAssetUrl(iconsCrossCircle, opts), cross: formatAssetUrl(iconsCross, opts), 'dash-dashed': formatAssetUrl(iconsDashDashed, opts), 'dash-dotted': formatAssetUrl(iconsDashDotted, opts), @@ -301,6 +306,7 @@ export function getAssetUrlsByImport(opts) { 'drag-handle-dots': formatAssetUrl(iconsDragHandleDots, opts), duplicate: formatAssetUrl(iconsDuplicate, opts), edit: formatAssetUrl(iconsEdit, opts), + error: formatAssetUrl(iconsError, opts), 'external-link': formatAssetUrl(iconsExternalLink, opts), file: formatAssetUrl(iconsFile, opts), 'fill-none': formatAssetUrl(iconsFillNone, opts), diff --git a/packages/assets/imports.vite.js b/packages/assets/imports.vite.js index 880a44d3a..f62fca282 100644 --- a/packages/assets/imports.vite.js +++ b/packages/assets/imports.vite.js @@ -55,6 +55,7 @@ import iconsAvatar from './icons/icon/avatar.svg?url' import iconsBlob from './icons/icon/blob.svg?url' import iconsBringForward from './icons/icon/bring-forward.svg?url' import iconsBringToFront from './icons/icon/bring-to-front.svg?url' +import iconsCheckCircle from './icons/icon/check-circle.svg?url' import iconsCheck from './icons/icon/check.svg?url' import iconsCheckboxChecked from './icons/icon/checkbox-checked.svg?url' import iconsCheckboxEmpty from './icons/icon/checkbox-empty.svg?url' @@ -71,6 +72,7 @@ import iconsCollab from './icons/icon/collab.svg?url' import iconsColor from './icons/icon/color.svg?url' import iconsComment from './icons/icon/comment.svg?url' import iconsCross2 from './icons/icon/cross-2.svg?url' +import iconsCrossCircle from './icons/icon/cross-circle.svg?url' import iconsCross from './icons/icon/cross.svg?url' import iconsDashDashed from './icons/icon/dash-dashed.svg?url' import iconsDashDotted from './icons/icon/dash-dotted.svg?url' @@ -85,6 +87,7 @@ import iconsDotsVertical from './icons/icon/dots-vertical.svg?url' import iconsDragHandleDots from './icons/icon/drag-handle-dots.svg?url' import iconsDuplicate from './icons/icon/duplicate.svg?url' import iconsEdit from './icons/icon/edit.svg?url' +import iconsError from './icons/icon/error.svg?url' import iconsExternalLink from './icons/icon/external-link.svg?url' import iconsFile from './icons/icon/file.svg?url' import iconsFillNone from './icons/icon/fill-none.svg?url' @@ -271,6 +274,7 @@ export function getAssetUrlsByImport(opts) { blob: formatAssetUrl(iconsBlob, opts), 'bring-forward': formatAssetUrl(iconsBringForward, opts), 'bring-to-front': formatAssetUrl(iconsBringToFront, opts), + 'check-circle': formatAssetUrl(iconsCheckCircle, opts), check: formatAssetUrl(iconsCheck, opts), 'checkbox-checked': formatAssetUrl(iconsCheckboxChecked, opts), 'checkbox-empty': formatAssetUrl(iconsCheckboxEmpty, opts), @@ -287,6 +291,7 @@ export function getAssetUrlsByImport(opts) { color: formatAssetUrl(iconsColor, opts), comment: formatAssetUrl(iconsComment, opts), 'cross-2': formatAssetUrl(iconsCross2, opts), + 'cross-circle': formatAssetUrl(iconsCrossCircle, opts), cross: formatAssetUrl(iconsCross, opts), 'dash-dashed': formatAssetUrl(iconsDashDashed, opts), 'dash-dotted': formatAssetUrl(iconsDashDotted, opts), @@ -301,6 +306,7 @@ export function getAssetUrlsByImport(opts) { 'drag-handle-dots': formatAssetUrl(iconsDragHandleDots, opts), duplicate: formatAssetUrl(iconsDuplicate, opts), edit: formatAssetUrl(iconsEdit, opts), + error: formatAssetUrl(iconsError, opts), 'external-link': formatAssetUrl(iconsExternalLink, opts), file: formatAssetUrl(iconsFile, opts), 'fill-none': formatAssetUrl(iconsFillNone, opts), diff --git a/packages/assets/selfHosted.js b/packages/assets/selfHosted.js index e303193d7..ea385142d 100644 --- a/packages/assets/selfHosted.js +++ b/packages/assets/selfHosted.js @@ -50,6 +50,7 @@ export function getAssetUrls(opts) { blob: formatAssetUrl('./icons/icon/blob.svg', opts), 'bring-forward': formatAssetUrl('./icons/icon/bring-forward.svg', opts), 'bring-to-front': formatAssetUrl('./icons/icon/bring-to-front.svg', opts), + 'check-circle': formatAssetUrl('./icons/icon/check-circle.svg', opts), check: formatAssetUrl('./icons/icon/check.svg', opts), 'checkbox-checked': formatAssetUrl('./icons/icon/checkbox-checked.svg', opts), 'checkbox-empty': formatAssetUrl('./icons/icon/checkbox-empty.svg', opts), @@ -66,6 +67,7 @@ export function getAssetUrls(opts) { color: formatAssetUrl('./icons/icon/color.svg', opts), comment: formatAssetUrl('./icons/icon/comment.svg', opts), 'cross-2': formatAssetUrl('./icons/icon/cross-2.svg', opts), + 'cross-circle': formatAssetUrl('./icons/icon/cross-circle.svg', opts), cross: formatAssetUrl('./icons/icon/cross.svg', opts), 'dash-dashed': formatAssetUrl('./icons/icon/dash-dashed.svg', opts), 'dash-dotted': formatAssetUrl('./icons/icon/dash-dotted.svg', opts), @@ -80,6 +82,7 @@ export function getAssetUrls(opts) { 'drag-handle-dots': formatAssetUrl('./icons/icon/drag-handle-dots.svg', opts), duplicate: formatAssetUrl('./icons/icon/duplicate.svg', opts), edit: formatAssetUrl('./icons/icon/edit.svg', opts), + error: formatAssetUrl('./icons/icon/error.svg', opts), 'external-link': formatAssetUrl('./icons/icon/external-link.svg', opts), file: formatAssetUrl('./icons/icon/file.svg', opts), 'fill-none': formatAssetUrl('./icons/icon/fill-none.svg', opts), diff --git a/packages/assets/types.d.ts b/packages/assets/types.d.ts index 6b4a882f7..27ea40ba8 100644 --- a/packages/assets/types.d.ts +++ b/packages/assets/types.d.ts @@ -40,6 +40,7 @@ export type AssetUrls = { blob: string 'bring-forward': string 'bring-to-front': string + 'check-circle': string check: string 'checkbox-checked': string 'checkbox-empty': string @@ -56,6 +57,7 @@ export type AssetUrls = { color: string comment: string 'cross-2': string + 'cross-circle': string cross: string 'dash-dashed': string 'dash-dotted': string @@ -70,6 +72,7 @@ export type AssetUrls = { 'drag-handle-dots': string duplicate: string edit: string + error: string 'external-link': string file: string 'fill-none': string diff --git a/packages/assets/urls.js b/packages/assets/urls.js index 8545423f1..fa3f960a0 100644 --- a/packages/assets/urls.js +++ b/packages/assets/urls.js @@ -140,6 +140,10 @@ export function getAssetUrlsByMetaUrl(opts) { new URL('./icons/icon/bring-to-front.svg', import.meta.url).href, opts ), + 'check-circle': formatAssetUrl( + new URL('./icons/icon/check-circle.svg', import.meta.url).href, + opts + ), check: formatAssetUrl(new URL('./icons/icon/check.svg', import.meta.url).href, opts), 'checkbox-checked': formatAssetUrl( new URL('./icons/icon/checkbox-checked.svg', import.meta.url).href, @@ -186,6 +190,10 @@ export function getAssetUrlsByMetaUrl(opts) { color: formatAssetUrl(new URL('./icons/icon/color.svg', import.meta.url).href, opts), comment: formatAssetUrl(new URL('./icons/icon/comment.svg', import.meta.url).href, opts), 'cross-2': formatAssetUrl(new URL('./icons/icon/cross-2.svg', import.meta.url).href, opts), + 'cross-circle': formatAssetUrl( + new URL('./icons/icon/cross-circle.svg', import.meta.url).href, + opts + ), cross: formatAssetUrl(new URL('./icons/icon/cross.svg', import.meta.url).href, opts), 'dash-dashed': formatAssetUrl( new URL('./icons/icon/dash-dashed.svg', import.meta.url).href, @@ -227,6 +235,7 @@ export function getAssetUrlsByMetaUrl(opts) { ), duplicate: formatAssetUrl(new URL('./icons/icon/duplicate.svg', import.meta.url).href, opts), edit: formatAssetUrl(new URL('./icons/icon/edit.svg', import.meta.url).href, opts), + error: formatAssetUrl(new URL('./icons/icon/error.svg', import.meta.url).href, opts), 'external-link': formatAssetUrl( new URL('./icons/icon/external-link.svg', import.meta.url).href, opts diff --git a/packages/editor/editor.css b/packages/editor/editor.css index 65e64a7d7..a5ecddfd5 100644 --- a/packages/editor/editor.css +++ b/packages/editor/editor.css @@ -124,6 +124,10 @@ --color-text-3: hsl(220, 2%, 65%); --color-text-shadow: hsl(0, 0%, 100%); --color-primary: hsl(214, 84%, 56%); + --color-success: hsl(123, 46%, 34%); + --color-info: hsl(201, 98%, 41%); + --color-warning: hsl(27, 98%, 47%); + --color-error: hsl(0, 65%, 51%); --color-warn: hsl(0, 90%, 43%); --color-text: hsl(0, 0%, 0%); --color-laser: hsl(0, 100%, 50%); @@ -166,6 +170,10 @@ --color-text-3: hsl(210, 6%, 45%); --color-text-shadow: hsl(210, 13%, 18%); --color-primary: hsl(214, 84%, 56%); + --color-success: hsl(123, 38%, 57%); + --color-info: hsl(199, 92%, 56%); + --color-warning: hsl(36, 100%, 57%); + --color-error: hsl(4, 90%, 58%); --color-warn: hsl(0, 81%, 66%); --color-text: hsl(210, 17%, 98%); --color-laser: hsl(0, 100%, 50%); diff --git a/packages/tldraw/api-report.md b/packages/tldraw/api-report.md index 2aaeb4125..f2be50724 100644 --- a/packages/tldraw/api-report.md +++ b/packages/tldraw/api-report.md @@ -128,6 +128,9 @@ import { Vec } from '@tldraw/editor'; import { VecLike } from '@tldraw/editor'; import { VecModel } from '@tldraw/editor'; +// @public (undocumented) +export type AlertSeverity = 'error' | 'info' | 'success' | 'warning'; + // @public (undocumented) export function AlignMenuItems(): JSX_2.Element; @@ -2134,7 +2137,7 @@ export interface TLUiIconProps extends React.HTMLProps { } // @public (undocumented) -export type TLUiIconType = 'align-bottom-center' | 'align-bottom-left' | 'align-bottom-right' | 'align-bottom' | 'align-center-center' | 'align-center-horizontal' | 'align-center-left' | 'align-center-right' | 'align-center-vertical' | 'align-left' | 'align-right' | 'align-top-center' | 'align-top-left' | 'align-top-right' | 'align-top' | 'arrow-left' | 'arrowhead-arrow' | 'arrowhead-bar' | 'arrowhead-diamond' | 'arrowhead-dot' | 'arrowhead-none' | 'arrowhead-square' | 'arrowhead-triangle-inverted' | 'arrowhead-triangle' | 'aspect-ratio' | 'avatar' | 'blob' | 'bring-forward' | 'bring-to-front' | 'check' | 'checkbox-checked' | 'checkbox-empty' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'chevrons-ne' | 'chevrons-sw' | 'clipboard-copied' | 'clipboard-copy' | 'code' | 'collab' | 'color' | 'comment' | 'cross-2' | 'cross' | 'dash-dashed' | 'dash-dotted' | 'dash-draw' | 'dash-solid' | 'discord' | 'distribute-horizontal' | 'distribute-vertical' | 'dot' | 'dots-horizontal' | 'dots-vertical' | 'drag-handle-dots' | 'duplicate' | 'edit' | 'external-link' | 'file' | 'fill-none' | 'fill-pattern' | 'fill-semi' | 'fill-solid' | 'follow' | 'following' | 'font-draw' | 'font-mono' | 'font-sans' | 'font-serif' | 'geo-arrow-down' | 'geo-arrow-left' | 'geo-arrow-right' | 'geo-arrow-up' | 'geo-check-box' | 'geo-cloud' | 'geo-diamond' | 'geo-ellipse' | 'geo-hexagon' | 'geo-octagon' | 'geo-oval' | 'geo-pentagon' | 'geo-rectangle' | 'geo-rhombus-2' | 'geo-rhombus' | 'geo-star' | 'geo-trapezoid' | 'geo-triangle' | 'geo-x-box' | 'github' | 'group' | 'hidden' | 'image' | 'info-circle' | 'leading' | 'link' | 'lock-small' | 'lock' | 'menu' | 'minus' | 'mixed' | 'pack' | 'page' | 'plus' | 'question-mark-circle' | 'question-mark' | 'redo' | 'reset-zoom' | 'rotate-ccw' | 'rotate-cw' | 'ruler' | 'search' | 'send-backward' | 'send-to-back' | 'settings-horizontal' | 'settings-vertical-1' | 'settings-vertical' | 'share-1' | 'share-2' | 'size-extra-large' | 'size-large' | 'size-medium' | 'size-small' | 'spline-cubic' | 'spline-line' | 'stack-horizontal' | 'stack-vertical' | 'status-offline' | 'status-online' | 'stretch-horizontal' | 'stretch-vertical' | 'text-align-center' | 'text-align-justify' | 'text-align-left' | 'text-align-right' | 'tool-arrow' | 'tool-embed' | 'tool-eraser' | 'tool-frame' | 'tool-hand' | 'tool-highlight' | 'tool-laser' | 'tool-line' | 'tool-media' | 'tool-note' | 'tool-pencil' | 'tool-pointer' | 'tool-text' | 'trash' | 'triangle-down' | 'triangle-up' | 'twitter' | 'undo' | 'ungroup' | 'unlock-small' | 'unlock' | 'vertical-align-center' | 'vertical-align-end' | 'vertical-align-start' | 'visible' | 'warning-triangle' | 'zoom-in' | 'zoom-out'; +export type TLUiIconType = 'align-bottom-center' | 'align-bottom-left' | 'align-bottom-right' | 'align-bottom' | 'align-center-center' | 'align-center-horizontal' | 'align-center-left' | 'align-center-right' | 'align-center-vertical' | 'align-left' | 'align-right' | 'align-top-center' | 'align-top-left' | 'align-top-right' | 'align-top' | 'arrow-left' | 'arrowhead-arrow' | 'arrowhead-bar' | 'arrowhead-diamond' | 'arrowhead-dot' | 'arrowhead-none' | 'arrowhead-square' | 'arrowhead-triangle-inverted' | 'arrowhead-triangle' | 'aspect-ratio' | 'avatar' | 'blob' | 'bring-forward' | 'bring-to-front' | 'check-circle' | 'check' | 'checkbox-checked' | 'checkbox-empty' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'chevrons-ne' | 'chevrons-sw' | 'clipboard-copied' | 'clipboard-copy' | 'code' | 'collab' | 'color' | 'comment' | 'cross-2' | 'cross-circle' | 'cross' | 'dash-dashed' | 'dash-dotted' | 'dash-draw' | 'dash-solid' | 'discord' | 'distribute-horizontal' | 'distribute-vertical' | 'dot' | 'dots-horizontal' | 'dots-vertical' | 'drag-handle-dots' | 'duplicate' | 'edit' | 'error' | 'external-link' | 'file' | 'fill-none' | 'fill-pattern' | 'fill-semi' | 'fill-solid' | 'follow' | 'following' | 'font-draw' | 'font-mono' | 'font-sans' | 'font-serif' | 'geo-arrow-down' | 'geo-arrow-left' | 'geo-arrow-right' | 'geo-arrow-up' | 'geo-check-box' | 'geo-cloud' | 'geo-diamond' | 'geo-ellipse' | 'geo-hexagon' | 'geo-octagon' | 'geo-oval' | 'geo-pentagon' | 'geo-rectangle' | 'geo-rhombus-2' | 'geo-rhombus' | 'geo-star' | 'geo-trapezoid' | 'geo-triangle' | 'geo-x-box' | 'github' | 'group' | 'hidden' | 'image' | 'info-circle' | 'leading' | 'link' | 'lock-small' | 'lock' | 'menu' | 'minus' | 'mixed' | 'pack' | 'page' | 'plus' | 'question-mark-circle' | 'question-mark' | 'redo' | 'reset-zoom' | 'rotate-ccw' | 'rotate-cw' | 'ruler' | 'search' | 'send-backward' | 'send-to-back' | 'settings-horizontal' | 'settings-vertical-1' | 'settings-vertical' | 'share-1' | 'share-2' | 'size-extra-large' | 'size-large' | 'size-medium' | 'size-small' | 'spline-cubic' | 'spline-line' | 'stack-horizontal' | 'stack-vertical' | 'status-offline' | 'status-online' | 'stretch-horizontal' | 'stretch-vertical' | 'text-align-center' | 'text-align-justify' | 'text-align-left' | 'text-align-right' | 'tool-arrow' | 'tool-embed' | 'tool-eraser' | 'tool-frame' | 'tool-hand' | 'tool-highlight' | 'tool-laser' | 'tool-line' | 'tool-media' | 'tool-note' | 'tool-pencil' | 'tool-pointer' | 'tool-text' | 'trash' | 'triangle-down' | 'triangle-up' | 'twitter' | 'undo' | 'ungroup' | 'unlock-small' | 'unlock' | 'vertical-align-center' | 'vertical-align-end' | 'vertical-align-start' | 'visible' | 'warning-triangle' | 'zoom-in' | 'zoom-out'; // @public (undocumented) export interface TLUiInputProps { @@ -2326,6 +2329,8 @@ export interface TLUiToast { // (undocumented) keepOpen?: boolean; // (undocumented) + severity?: AlertSeverity; + // (undocumented) title?: string; } diff --git a/packages/tldraw/api/api.json b/packages/tldraw/api/api.json index 5cf599bcf..efaa70492 100644 --- a/packages/tldraw/api/api.json +++ b/packages/tldraw/api/api.json @@ -172,6 +172,32 @@ "name": "", "preserveMemberOrder": false, "members": [ + { + "kind": "TypeAlias", + "canonicalReference": "tldraw!AlertSeverity:type", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export type AlertSeverity = " + }, + { + "kind": "Content", + "text": "'error' | 'info' | 'success' | 'warning'" + }, + { + "kind": "Content", + "text": ";" + } + ], + "fileUrlPath": "packages/tldraw/src/lib/ui/context/toasts.tsx", + "releaseTag": "Public", + "name": "AlertSeverity", + "typeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, { "kind": "Function", "canonicalReference": "tldraw!AlignMenuItems:function(1)", @@ -24086,7 +24112,7 @@ }, { "kind": "Content", - "text": "'align-bottom-center' | 'align-bottom-left' | 'align-bottom-right' | 'align-bottom' | 'align-center-center' | 'align-center-horizontal' | 'align-center-left' | 'align-center-right' | 'align-center-vertical' | 'align-left' | 'align-right' | 'align-top-center' | 'align-top-left' | 'align-top-right' | 'align-top' | 'arrow-left' | 'arrowhead-arrow' | 'arrowhead-bar' | 'arrowhead-diamond' | 'arrowhead-dot' | 'arrowhead-none' | 'arrowhead-square' | 'arrowhead-triangle-inverted' | 'arrowhead-triangle' | 'aspect-ratio' | 'avatar' | 'blob' | 'bring-forward' | 'bring-to-front' | 'check' | 'checkbox-checked' | 'checkbox-empty' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'chevrons-ne' | 'chevrons-sw' | 'clipboard-copied' | 'clipboard-copy' | 'code' | 'collab' | 'color' | 'comment' | 'cross-2' | 'cross' | 'dash-dashed' | 'dash-dotted' | 'dash-draw' | 'dash-solid' | 'discord' | 'distribute-horizontal' | 'distribute-vertical' | 'dot' | 'dots-horizontal' | 'dots-vertical' | 'drag-handle-dots' | 'duplicate' | 'edit' | 'external-link' | 'file' | 'fill-none' | 'fill-pattern' | 'fill-semi' | 'fill-solid' | 'follow' | 'following' | 'font-draw' | 'font-mono' | 'font-sans' | 'font-serif' | 'geo-arrow-down' | 'geo-arrow-left' | 'geo-arrow-right' | 'geo-arrow-up' | 'geo-check-box' | 'geo-cloud' | 'geo-diamond' | 'geo-ellipse' | 'geo-hexagon' | 'geo-octagon' | 'geo-oval' | 'geo-pentagon' | 'geo-rectangle' | 'geo-rhombus-2' | 'geo-rhombus' | 'geo-star' | 'geo-trapezoid' | 'geo-triangle' | 'geo-x-box' | 'github' | 'group' | 'hidden' | 'image' | 'info-circle' | 'leading' | 'link' | 'lock-small' | 'lock' | 'menu' | 'minus' | 'mixed' | 'pack' | 'page' | 'plus' | 'question-mark-circle' | 'question-mark' | 'redo' | 'reset-zoom' | 'rotate-ccw' | 'rotate-cw' | 'ruler' | 'search' | 'send-backward' | 'send-to-back' | 'settings-horizontal' | 'settings-vertical-1' | 'settings-vertical' | 'share-1' | 'share-2' | 'size-extra-large' | 'size-large' | 'size-medium' | 'size-small' | 'spline-cubic' | 'spline-line' | 'stack-horizontal' | 'stack-vertical' | 'status-offline' | 'status-online' | 'stretch-horizontal' | 'stretch-vertical' | 'text-align-center' | 'text-align-justify' | 'text-align-left' | 'text-align-right' | 'tool-arrow' | 'tool-embed' | 'tool-eraser' | 'tool-frame' | 'tool-hand' | 'tool-highlight' | 'tool-laser' | 'tool-line' | 'tool-media' | 'tool-note' | 'tool-pencil' | 'tool-pointer' | 'tool-text' | 'trash' | 'triangle-down' | 'triangle-up' | 'twitter' | 'undo' | 'ungroup' | 'unlock-small' | 'unlock' | 'vertical-align-center' | 'vertical-align-end' | 'vertical-align-start' | 'visible' | 'warning-triangle' | 'zoom-in' | 'zoom-out'" + "text": "'align-bottom-center' | 'align-bottom-left' | 'align-bottom-right' | 'align-bottom' | 'align-center-center' | 'align-center-horizontal' | 'align-center-left' | 'align-center-right' | 'align-center-vertical' | 'align-left' | 'align-right' | 'align-top-center' | 'align-top-left' | 'align-top-right' | 'align-top' | 'arrow-left' | 'arrowhead-arrow' | 'arrowhead-bar' | 'arrowhead-diamond' | 'arrowhead-dot' | 'arrowhead-none' | 'arrowhead-square' | 'arrowhead-triangle-inverted' | 'arrowhead-triangle' | 'aspect-ratio' | 'avatar' | 'blob' | 'bring-forward' | 'bring-to-front' | 'check-circle' | 'check' | 'checkbox-checked' | 'checkbox-empty' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'chevrons-ne' | 'chevrons-sw' | 'clipboard-copied' | 'clipboard-copy' | 'code' | 'collab' | 'color' | 'comment' | 'cross-2' | 'cross-circle' | 'cross' | 'dash-dashed' | 'dash-dotted' | 'dash-draw' | 'dash-solid' | 'discord' | 'distribute-horizontal' | 'distribute-vertical' | 'dot' | 'dots-horizontal' | 'dots-vertical' | 'drag-handle-dots' | 'duplicate' | 'edit' | 'error' | 'external-link' | 'file' | 'fill-none' | 'fill-pattern' | 'fill-semi' | 'fill-solid' | 'follow' | 'following' | 'font-draw' | 'font-mono' | 'font-sans' | 'font-serif' | 'geo-arrow-down' | 'geo-arrow-left' | 'geo-arrow-right' | 'geo-arrow-up' | 'geo-check-box' | 'geo-cloud' | 'geo-diamond' | 'geo-ellipse' | 'geo-hexagon' | 'geo-octagon' | 'geo-oval' | 'geo-pentagon' | 'geo-rectangle' | 'geo-rhombus-2' | 'geo-rhombus' | 'geo-star' | 'geo-trapezoid' | 'geo-triangle' | 'geo-x-box' | 'github' | 'group' | 'hidden' | 'image' | 'info-circle' | 'leading' | 'link' | 'lock-small' | 'lock' | 'menu' | 'minus' | 'mixed' | 'pack' | 'page' | 'plus' | 'question-mark-circle' | 'question-mark' | 'redo' | 'reset-zoom' | 'rotate-ccw' | 'rotate-cw' | 'ruler' | 'search' | 'send-backward' | 'send-to-back' | 'settings-horizontal' | 'settings-vertical-1' | 'settings-vertical' | 'share-1' | 'share-2' | 'size-extra-large' | 'size-large' | 'size-medium' | 'size-small' | 'spline-cubic' | 'spline-line' | 'stack-horizontal' | 'stack-vertical' | 'status-offline' | 'status-online' | 'stretch-horizontal' | 'stretch-vertical' | 'text-align-center' | 'text-align-justify' | 'text-align-left' | 'text-align-right' | 'tool-arrow' | 'tool-embed' | 'tool-eraser' | 'tool-frame' | 'tool-hand' | 'tool-highlight' | 'tool-laser' | 'tool-line' | 'tool-media' | 'tool-note' | 'tool-pencil' | 'tool-pointer' | 'tool-text' | 'trash' | 'triangle-down' | 'triangle-up' | 'twitter' | 'undo' | 'ungroup' | 'unlock-small' | 'unlock' | 'vertical-align-center' | 'vertical-align-end' | 'vertical-align-start' | 'visible' | 'warning-triangle' | 'zoom-in' | 'zoom-out'" }, { "kind": "Content", @@ -25682,6 +25708,34 @@ "endIndex": 2 } }, + { + "kind": "PropertySignature", + "canonicalReference": "tldraw!TLUiToast#severity:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "severity?: " + }, + { + "kind": "Reference", + "text": "AlertSeverity", + "canonicalReference": "tldraw!AlertSeverity:type" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": true, + "releaseTag": "Public", + "name": "severity", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, { "kind": "PropertySignature", "canonicalReference": "tldraw!TLUiToast#title:member", diff --git a/packages/tldraw/src/index.ts b/packages/tldraw/src/index.ts index eb954b308..b1b6431bd 100644 --- a/packages/tldraw/src/index.ts +++ b/packages/tldraw/src/index.ts @@ -76,6 +76,7 @@ export { } from './lib/ui/context/events' export { useToasts, + type AlertSeverity, type TLUiToast, type TLUiToastAction, type TLUiToastsContextType, diff --git a/packages/tldraw/src/lib/defaultExternalContentHandlers.ts b/packages/tldraw/src/lib/defaultExternalContentHandlers.ts index 3d68610aa..ba2ff614a 100644 --- a/packages/tldraw/src/lib/defaultExternalContentHandlers.ts +++ b/packages/tldraw/src/lib/defaultExternalContentHandlers.ts @@ -128,6 +128,7 @@ export function registerDefaultExternalContentHandlers( console.error(error) toasts.addToast({ title: msg('assets.url.failed'), + severity: 'error', }) meta = { image: '', title: truncateStringWithEllipsis(url, 32), description: '' } } @@ -250,6 +251,7 @@ export function registerDefaultExternalContentHandlers( } catch (error) { toasts.addToast({ title: msg('assets.files.upload-failed'), + severity: 'error', }) console.error(error) return null @@ -369,6 +371,7 @@ export function registerDefaultExternalContentHandlers( } catch (e) { toasts.addToast({ title: msg('assets.url.failed'), + severity: 'error', }) return } diff --git a/packages/tldraw/src/lib/ui.css b/packages/tldraw/src/lib/ui.css index 208cb5a02..25c4d4f2d 100644 --- a/packages/tldraw/src/lib/ui.css +++ b/packages/tldraw/src/lib/ui.css @@ -1013,7 +1013,7 @@ } .tlui-toast__icon { - padding-top: var(--space-4); + padding-top: 11px; padding-left: var(--space-4); color: var(--color-text-1); } @@ -1028,6 +1028,22 @@ font-size: 12px; } +.tlui-toast__container[data-severity='success'] .tlui-icon { + color: var(--color-success); +} + +.tlui-toast__container[data-severity='info'] .tlui-icon { + color: var(--color-info); +} + +.tlui-toast__container[data-severity='warning'] .tlui-icon { + color: var(--color-warning); +} + +.tlui-toast__container[data-severity='error'] .tlui-icon { + color: var(--color-error); +} + .tlui-toast__main { flex-grow: 2; max-width: 280px; @@ -1043,6 +1059,8 @@ .tlui-toast__title { font-weight: bold; color: var(--color-text-1); + /* this makes the default toast look better */ + line-height: 16px; } .tlui-toast__description { diff --git a/packages/tldraw/src/lib/ui/components/DebugMenu/DefaultDebugMenuContent.tsx b/packages/tldraw/src/lib/ui/components/DebugMenu/DefaultDebugMenuContent.tsx index 90918f79e..4791d253c 100644 --- a/packages/tldraw/src/lib/ui/components/DebugMenu/DefaultDebugMenuContent.tsx +++ b/packages/tldraw/src/lib/ui/components/DebugMenu/DefaultDebugMenuContent.tsx @@ -44,9 +44,10 @@ export function DefaultDebugMenuContent() { onSelect={() => { addToast({ id: uniqueId(), - title: 'Something happened', + title: 'Something good happened', description: 'Hey, attend to this thing over here. It might be important!', keepOpen: true, + severity: 'success', // icon?: string // title?: string // description?: string @@ -57,6 +58,7 @@ export function DefaultDebugMenuContent() { title: 'Something happened', description: 'Hey, attend to this thing over here. It might be important!', keepOpen: true, + severity: 'info', actions: [ { label: 'Primary', @@ -87,10 +89,10 @@ export function DefaultDebugMenuContent() { }) addToast({ id: uniqueId(), - title: 'Something happened', + title: 'Something maybe bad happened', description: 'Hey, attend to this thing over here. It might be important!', keepOpen: true, - icon: 'twitter', + severity: 'warning', actions: [ { label: 'Primary', @@ -115,6 +117,12 @@ export function DefaultDebugMenuContent() { }, ], }) + addToast({ + id: uniqueId(), + title: 'Something bad happened', + severity: 'error', + keepOpen: true, + }) }} label={untranslated('Show toast')} /> diff --git a/packages/tldraw/src/lib/ui/components/Toasts.tsx b/packages/tldraw/src/lib/ui/components/Toasts.tsx index 51906ba96..2a06a1446 100644 --- a/packages/tldraw/src/lib/ui/components/Toasts.tsx +++ b/packages/tldraw/src/lib/ui/components/Toasts.tsx @@ -1,12 +1,19 @@ import * as T from '@radix-ui/react-toast' import * as React from 'react' -import { TLUiToast, useToasts } from '../context/toasts' +import { AlertSeverity, TLUiToast, useToasts } from '../context/toasts' import { useTranslation } from '../hooks/useTranslation/useTranslation' import { TLUiIconType } from '../icon-types' import { TldrawUiButton } from './primitives/Button/TldrawUiButton' import { TldrawUiButtonLabel } from './primitives/Button/TldrawUiButtonLabel' import { TldrawUiIcon } from './primitives/TldrawUiIcon' +const SEVERITY_TO_ICON: { [msg in AlertSeverity]: TLUiIconType } = { + success: 'check-circle', + warning: 'warning-triangle', + error: 'cross-circle', + info: 'info-circle', +} + function Toast({ toast }: { toast: TLUiToast }) { const { removeToast } = useToasts() const msg = useTranslation() @@ -19,15 +26,18 @@ function Toast({ toast }: { toast: TLUiToast }) { const hasActions = toast.actions && toast.actions.length > 0 + const icon = toast.icon || (toast.severity && SEVERITY_TO_ICON[toast.severity]) + return ( - {toast.icon && ( + {icon && (
- +
)}
diff --git a/packages/tldraw/src/lib/ui/context/toasts.tsx b/packages/tldraw/src/lib/ui/context/toasts.tsx index 70dac9a2b..d7fafe71c 100644 --- a/packages/tldraw/src/lib/ui/context/toasts.tsx +++ b/packages/tldraw/src/lib/ui/context/toasts.tsx @@ -2,10 +2,14 @@ import { Editor, uniqueId } from '@tldraw/editor' import { ReactNode, createContext, useCallback, useContext, useState } from 'react' import { TLUiIconType } from '../icon-types' +/** @public */ +export type AlertSeverity = 'success' | 'info' | 'warning' | 'error' + /** @public */ export interface TLUiToast { id: string icon?: TLUiIconType + severity?: AlertSeverity title?: string description?: string actions?: TLUiToastAction[] diff --git a/packages/tldraw/src/lib/ui/hooks/useCopyAs.ts b/packages/tldraw/src/lib/ui/hooks/useCopyAs.ts index f89c91d1b..9e12be56d 100644 --- a/packages/tldraw/src/lib/ui/hooks/useCopyAs.ts +++ b/packages/tldraw/src/lib/ui/hooks/useCopyAs.ts @@ -15,7 +15,7 @@ export function useCopyAs() { copyAs(editor, ids, format).catch(() => { addToast({ id: 'copy-fail', - icon: 'warning-triangle', + severity: 'warning', title: msg('toast.error.copy-fail.title'), description: msg('toast.error.copy-fail.desc'), }) diff --git a/packages/tldraw/src/lib/ui/hooks/useEditorEvents.ts b/packages/tldraw/src/lib/ui/hooks/useEditorEvents.ts index 6dc87cdb2..e1a353b53 100644 --- a/packages/tldraw/src/lib/ui/hooks/useEditorEvents.ts +++ b/packages/tldraw/src/lib/ui/hooks/useEditorEvents.ts @@ -12,6 +12,7 @@ export function useEditorEvents() { addToast({ title: 'Maximum Shapes Reached', description: `You've reached the maximum number of shapes allowed on ${name} (${count}). Please delete some shapes or move to a different page to continue.`, + severity: 'warning', }) } diff --git a/packages/tldraw/src/lib/ui/hooks/useExportAs.ts b/packages/tldraw/src/lib/ui/hooks/useExportAs.ts index 3a370c3d8..82e7fbf69 100644 --- a/packages/tldraw/src/lib/ui/hooks/useExportAs.ts +++ b/packages/tldraw/src/lib/ui/hooks/useExportAs.ts @@ -19,9 +19,9 @@ export function useExportAs() { console.error(e.message) addToast({ id: 'export-fail', - // icon: 'error', title: msg('toast.error.export-fail.title'), description: msg('toast.error.export-fail.desc'), + severity: 'error', }) }) }, diff --git a/packages/tldraw/src/lib/ui/icon-types.ts b/packages/tldraw/src/lib/ui/icon-types.ts index 4eb1ac072..623e78273 100644 --- a/packages/tldraw/src/lib/ui/icon-types.ts +++ b/packages/tldraw/src/lib/ui/icon-types.ts @@ -32,6 +32,7 @@ export type TLUiIconType = | 'blob' | 'bring-forward' | 'bring-to-front' + | 'check-circle' | 'check' | 'checkbox-checked' | 'checkbox-empty' @@ -48,6 +49,7 @@ export type TLUiIconType = | 'color' | 'comment' | 'cross-2' + | 'cross-circle' | 'cross' | 'dash-dashed' | 'dash-dotted' @@ -62,6 +64,7 @@ export type TLUiIconType = | 'drag-handle-dots' | 'duplicate' | 'edit' + | 'error' | 'external-link' | 'file' | 'fill-none' @@ -199,6 +202,7 @@ export const iconTypes = [ 'blob', 'bring-forward', 'bring-to-front', + 'check-circle', 'check', 'checkbox-checked', 'checkbox-empty', @@ -215,6 +219,7 @@ export const iconTypes = [ 'color', 'comment', 'cross-2', + 'cross-circle', 'cross', 'dash-dashed', 'dash-dotted', @@ -229,6 +234,7 @@ export const iconTypes = [ 'drag-handle-dots', 'duplicate', 'edit', + 'error', 'external-link', 'file', 'fill-none', diff --git a/packages/tldraw/src/lib/utils/tldr/file.ts b/packages/tldraw/src/lib/utils/tldr/file.ts index e7c933316..33bac0e30 100644 --- a/packages/tldraw/src/lib/utils/tldr/file.ts +++ b/packages/tldraw/src/lib/utils/tldr/file.ts @@ -267,6 +267,7 @@ export async function parseAndLoadDocument( addToast({ title: msg('file-system.file-open-error.title'), description, + severity: 'error', }) return