Add offline indicator (also to top zone example) (#2083)
This PR adds an offline indicator to the UI package. It's not used in the default app but we'll use it on tldraw.com, and it makes sense to include it here as it's generally useful. ### Change Type - [x] `minor` — New feature ### Test Plan 1. See the zones example. ### Release Notes - [@tldraw/tldraw] add offline indicator to ui components
This commit is contained in:
parent
47e6e4c25a
commit
aaf810b015
16 changed files with 114 additions and 8 deletions
|
@ -1,10 +1,10 @@
|
||||||
import { Tldraw } from '@tldraw/tldraw'
|
import { OfflineIndicator, Tldraw } from '@tldraw/tldraw'
|
||||||
import '@tldraw/tldraw/tldraw.css'
|
import '@tldraw/tldraw/tldraw.css'
|
||||||
|
|
||||||
export default function Example() {
|
export default function Example() {
|
||||||
return (
|
return (
|
||||||
<div className="tldraw__editor">
|
<div className="tldraw__editor">
|
||||||
<Tldraw shareZone={<CustomShareZone />} />
|
<Tldraw topZone={<OfflineIndicator />} shareZone={<CustomShareZone />} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
4
assets/icons/icon/status-offline.svg
Normal file
4
assets/icons/icon/status-offline.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.6244 8.08966C8.94273 8.1985 3.90961 10.7717 0.702285 14.7107C0.294265 15.2118 0.414792 15.9436 0.934176 16.3281C1.47602 16.7292 2.23929 16.5967 2.66573 16.0746C5.14674 13.0369 8.88662 10.9431 13.159 10.4712L14.6244 8.08966ZM10.7965 14.3104C8.56283 15.0878 6.64316 16.4668 5.26375 18.2363C4.90281 18.6993 5.02791 19.3584 5.49977 19.7077C6.03113 20.101 6.78259 19.9442 7.19459 19.4272C7.56508 18.9623 7.98214 18.5316 8.43933 18.1411L10.7965 14.3104ZM20.3686 17.2713L21.5218 15.3972C22.7717 16.1511 23.8623 17.1153 24.7361 18.2362C25.0971 18.6992 24.972 19.3583 24.5001 19.7076C23.9687 20.1009 23.2173 19.9442 22.8053 19.4272C22.1334 18.584 21.3083 17.8534 20.3686 17.2713ZM17.4758 21.9723L18.5681 20.1972C19.2363 20.6457 19.8037 21.2169 20.2325 21.8754C20.4881 22.268 20.3515 22.7786 19.9749 23.0573C19.4173 23.4701 18.5976 23.1841 18.1651 22.6416C17.9663 22.3924 17.7346 22.1674 17.4758 21.9723ZM23.2505 12.5879L24.4704 10.6054C26.3357 11.6891 27.9725 13.0833 29.2976 14.7106C29.7056 15.2117 29.585 15.9436 29.0657 16.3281C28.5238 16.7292 27.7605 16.5967 27.3341 16.0746C26.2091 14.6972 24.8254 13.5139 23.2505 12.5879Z" fill="black"/>
|
||||||
|
<rect x="23.376" y="0.169067" width="3" height="33.372" rx="1.5" transform="rotate(31.6059 23.376 0.169067)" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
5
assets/icons/icon/status-online.svg
Normal file
5
assets/icons/icon/status-online.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0.702361 14.7107C0.294341 15.2118 0.414868 15.9437 0.934252 16.3281V16.3281C1.4761 16.7292 2.23937 16.5968 2.66581 16.0746C5.49739 12.6077 9.96877 10.3703 15 10.3703C20.0312 10.3703 24.5026 12.6077 27.3342 16.0746C27.7606 16.5967 28.5239 16.7292 29.0657 16.3281V16.3281C29.5851 15.9436 29.7057 15.2118 29.2976 14.7107C26.0198 10.6852 20.8351 8.08612 15 8.08612C9.1649 8.08612 3.98017 10.6852 0.702361 14.7107Z" fill="black"/>
|
||||||
|
<path d="M24.5002 19.7077C24.972 19.3584 25.0971 18.6993 24.7362 18.2362C22.5487 15.4302 19.0026 13.6062 15.0001 13.6062C10.9975 13.6062 7.45132 15.4302 5.26383 18.2363C4.90289 18.6993 5.02799 19.3584 5.49985 19.7077V19.7077C6.03121 20.1011 6.78267 19.9443 7.19466 19.4273C8.96644 17.2038 11.803 15.7634 15.0001 15.7634C18.1971 15.7634 21.0336 17.2038 22.8054 19.4272C23.2174 19.9442 23.9688 20.101 24.5002 19.7077V19.7077Z" fill="black"/>
|
||||||
|
<path d="M19.975 23.0574C20.3515 22.7786 20.4881 22.2681 20.2325 21.8755C19.1594 20.2272 17.2175 19.1262 15.0001 19.1262C12.7826 19.1262 10.8406 20.2273 9.76755 21.8756C9.51195 22.2682 9.64855 22.7787 10.0251 23.0575V23.0575C10.5827 23.4702 11.4024 23.1842 11.8349 22.6418C12.5536 21.7404 13.7038 21.1566 15.0001 21.1566C16.2963 21.1566 17.4465 21.7404 18.1652 22.6417C18.5977 23.1841 19.4174 23.4701 19.975 23.0574V23.0574Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -244,6 +244,8 @@
|
||||||
"share-menu.copy-readonly-link-note": "Anyone with the link will be able to view (but not edit) this project.",
|
"share-menu.copy-readonly-link-note": "Anyone with the link will be able to view (but not edit) this project.",
|
||||||
"share-menu.project-too-large": "Sorry, this project can't be shared because it's too large. We're working on it!",
|
"share-menu.project-too-large": "Sorry, this project can't be shared because it's too large. We're working on it!",
|
||||||
"share-menu.upload-failed": "Sorry, we couldn't upload your project at the moment. Please try again or let us know if the problem persists.",
|
"share-menu.upload-failed": "Sorry, we couldn't upload your project at the moment. Please try again or let us know if the problem persists.",
|
||||||
|
"status.offline": "Offline",
|
||||||
|
"status.online": "Online",
|
||||||
"people-menu.title": "People",
|
"people-menu.title": "People",
|
||||||
"people-menu.change-name": "Change name",
|
"people-menu.change-name": "Change name",
|
||||||
"people-menu.change-color": "Change color",
|
"people-menu.change-color": "Change color",
|
||||||
|
|
|
@ -154,6 +154,8 @@ import iconsSplineCubic from './icons/icon/spline-cubic.svg'
|
||||||
import iconsSplineLine from './icons/icon/spline-line.svg'
|
import iconsSplineLine from './icons/icon/spline-line.svg'
|
||||||
import iconsStackHorizontal from './icons/icon/stack-horizontal.svg'
|
import iconsStackHorizontal from './icons/icon/stack-horizontal.svg'
|
||||||
import iconsStackVertical from './icons/icon/stack-vertical.svg'
|
import iconsStackVertical from './icons/icon/stack-vertical.svg'
|
||||||
|
import iconsStatusOffline from './icons/icon/status-offline.svg'
|
||||||
|
import iconsStatusOnline from './icons/icon/status-online.svg'
|
||||||
import iconsStretchHorizontal from './icons/icon/stretch-horizontal.svg'
|
import iconsStretchHorizontal from './icons/icon/stretch-horizontal.svg'
|
||||||
import iconsStretchVertical from './icons/icon/stretch-vertical.svg'
|
import iconsStretchVertical from './icons/icon/stretch-vertical.svg'
|
||||||
import iconsTextAlignCenter from './icons/icon/text-align-center.svg'
|
import iconsTextAlignCenter from './icons/icon/text-align-center.svg'
|
||||||
|
@ -365,6 +367,8 @@ export function getAssetUrlsByImport(opts) {
|
||||||
'spline-line': formatAssetUrl(iconsSplineLine, opts),
|
'spline-line': formatAssetUrl(iconsSplineLine, opts),
|
||||||
'stack-horizontal': formatAssetUrl(iconsStackHorizontal, opts),
|
'stack-horizontal': formatAssetUrl(iconsStackHorizontal, opts),
|
||||||
'stack-vertical': formatAssetUrl(iconsStackVertical, opts),
|
'stack-vertical': formatAssetUrl(iconsStackVertical, opts),
|
||||||
|
'status-offline': formatAssetUrl(iconsStatusOffline, opts),
|
||||||
|
'status-online': formatAssetUrl(iconsStatusOnline, opts),
|
||||||
'stretch-horizontal': formatAssetUrl(iconsStretchHorizontal, opts),
|
'stretch-horizontal': formatAssetUrl(iconsStretchHorizontal, opts),
|
||||||
'stretch-vertical': formatAssetUrl(iconsStretchVertical, opts),
|
'stretch-vertical': formatAssetUrl(iconsStretchVertical, opts),
|
||||||
'text-align-center': formatAssetUrl(iconsTextAlignCenter, opts),
|
'text-align-center': formatAssetUrl(iconsTextAlignCenter, opts),
|
||||||
|
|
|
@ -149,6 +149,8 @@ export function getAssetUrls(opts) {
|
||||||
'spline-line': formatAssetUrl('./icons/icon/spline-line.svg', opts),
|
'spline-line': formatAssetUrl('./icons/icon/spline-line.svg', opts),
|
||||||
'stack-horizontal': formatAssetUrl('./icons/icon/stack-horizontal.svg', opts),
|
'stack-horizontal': formatAssetUrl('./icons/icon/stack-horizontal.svg', opts),
|
||||||
'stack-vertical': formatAssetUrl('./icons/icon/stack-vertical.svg', opts),
|
'stack-vertical': formatAssetUrl('./icons/icon/stack-vertical.svg', opts),
|
||||||
|
'status-offline': formatAssetUrl('./icons/icon/status-offline.svg', opts),
|
||||||
|
'status-online': formatAssetUrl('./icons/icon/status-online.svg', opts),
|
||||||
'stretch-horizontal': formatAssetUrl('./icons/icon/stretch-horizontal.svg', opts),
|
'stretch-horizontal': formatAssetUrl('./icons/icon/stretch-horizontal.svg', opts),
|
||||||
'stretch-vertical': formatAssetUrl('./icons/icon/stretch-vertical.svg', opts),
|
'stretch-vertical': formatAssetUrl('./icons/icon/stretch-vertical.svg', opts),
|
||||||
'text-align-center': formatAssetUrl('./icons/icon/text-align-center.svg', opts),
|
'text-align-center': formatAssetUrl('./icons/icon/text-align-center.svg', opts),
|
||||||
|
|
2
packages/assets/types.d.ts
vendored
2
packages/assets/types.d.ts
vendored
|
@ -139,6 +139,8 @@ export type AssetUrls = {
|
||||||
'spline-line': string
|
'spline-line': string
|
||||||
'stack-horizontal': string
|
'stack-horizontal': string
|
||||||
'stack-vertical': string
|
'stack-vertical': string
|
||||||
|
'status-offline': string
|
||||||
|
'status-online': string
|
||||||
'stretch-horizontal': string
|
'stretch-horizontal': string
|
||||||
'stretch-vertical': string
|
'stretch-vertical': string
|
||||||
'text-align-center': string
|
'text-align-center': string
|
||||||
|
|
|
@ -434,6 +434,14 @@ export function getAssetUrlsByMetaUrl(opts) {
|
||||||
new URL('./icons/icon/stack-vertical.svg', import.meta.url).href,
|
new URL('./icons/icon/stack-vertical.svg', import.meta.url).href,
|
||||||
opts
|
opts
|
||||||
),
|
),
|
||||||
|
'status-offline': formatAssetUrl(
|
||||||
|
new URL('./icons/icon/status-offline.svg', import.meta.url).href,
|
||||||
|
opts
|
||||||
|
),
|
||||||
|
'status-online': formatAssetUrl(
|
||||||
|
new URL('./icons/icon/status-online.svg', import.meta.url).href,
|
||||||
|
opts
|
||||||
|
),
|
||||||
'stretch-horizontal': formatAssetUrl(
|
'stretch-horizontal': formatAssetUrl(
|
||||||
new URL('./icons/icon/stretch-horizontal.svg', import.meta.url).href,
|
new URL('./icons/icon/stretch-horizontal.svg', import.meta.url).href,
|
||||||
opts
|
opts
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,5 @@
|
||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
|
|
||||||
import * as Dialog from './lib/ui/components/primitives/Dialog'
|
|
||||||
import * as DropdownMenu from './lib/ui/components/primitives/DropdownMenu'
|
|
||||||
// eslint-disable-next-line local/no-export-star
|
// eslint-disable-next-line local/no-export-star
|
||||||
export * from '@tldraw/editor'
|
export * from '@tldraw/editor'
|
||||||
export { Tldraw } from './lib/Tldraw'
|
export { Tldraw } from './lib/Tldraw'
|
||||||
|
@ -41,6 +39,7 @@ export {
|
||||||
} from './lib/ui/TldrawUiContextProvider'
|
} from './lib/ui/TldrawUiContextProvider'
|
||||||
export { setDefaultUiAssetUrls } from './lib/ui/assetUrls'
|
export { setDefaultUiAssetUrls } from './lib/ui/assetUrls'
|
||||||
export { ContextMenu, type TLUiContextMenuProps } from './lib/ui/components/ContextMenu'
|
export { ContextMenu, type TLUiContextMenuProps } from './lib/ui/components/ContextMenu'
|
||||||
|
export { OfflineIndicator } from './lib/ui/components/OfflineIndicator/OfflineIndicator'
|
||||||
export { Spinner } from './lib/ui/components/Spinner'
|
export { Spinner } from './lib/ui/components/Spinner'
|
||||||
export { Button, type TLUiButtonProps } from './lib/ui/components/primitives/Button'
|
export { Button, type TLUiButtonProps } from './lib/ui/components/primitives/Button'
|
||||||
export { Icon, type TLUiIconProps } from './lib/ui/components/primitives/Icon'
|
export { Icon, type TLUiIconProps } from './lib/ui/components/primitives/Icon'
|
||||||
|
@ -157,3 +156,5 @@ export {
|
||||||
} from './lib/utils/file'
|
} from './lib/utils/file'
|
||||||
export { truncateStringWithEllipsis } from './lib/utils/text'
|
export { truncateStringWithEllipsis } from './lib/utils/text'
|
||||||
export { Dialog, DropdownMenu }
|
export { Dialog, DropdownMenu }
|
||||||
|
import * as Dialog from './lib/ui/components/primitives/Dialog'
|
||||||
|
import * as DropdownMenu from './lib/ui/components/primitives/DropdownMenu'
|
||||||
|
|
|
@ -1745,3 +1745,24 @@
|
||||||
z-index: 9999999;
|
z-index: 9999999;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------- Offline Indicator --------------- */
|
||||||
|
|
||||||
|
.tlui-offline-indicator {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: var(--space-3);
|
||||||
|
color: var(--color-text);
|
||||||
|
background-color: var(--color-low);
|
||||||
|
border: 3px solid var(--color-background);
|
||||||
|
padding: 0px var(--space-5);
|
||||||
|
height: 42px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 99px;
|
||||||
|
opacity: 0;
|
||||||
|
animation: fade-in;
|
||||||
|
animation-duration: 0.12s;
|
||||||
|
animation-delay: 2s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { useRef } from 'react'
|
||||||
|
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
|
||||||
|
import { Icon } from '../primitives/Icon'
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export function OfflineIndicator() {
|
||||||
|
const msg = useTranslation()
|
||||||
|
const rContainer = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames('tlui-offline-indicator')} ref={rContainer}>
|
||||||
|
{msg('status.offline')}
|
||||||
|
<Icon aria-label="offline" icon="status-offline" small />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -248,6 +248,8 @@ export type TLUiTranslationKey =
|
||||||
| 'share-menu.copy-readonly-link-note'
|
| 'share-menu.copy-readonly-link-note'
|
||||||
| 'share-menu.project-too-large'
|
| 'share-menu.project-too-large'
|
||||||
| 'share-menu.upload-failed'
|
| 'share-menu.upload-failed'
|
||||||
|
| 'status.offline'
|
||||||
|
| 'status.online'
|
||||||
| 'people-menu.title'
|
| 'people-menu.title'
|
||||||
| 'people-menu.change-name'
|
| 'people-menu.change-name'
|
||||||
| 'people-menu.change-color'
|
| 'people-menu.change-color'
|
||||||
|
|
|
@ -251,6 +251,8 @@ export const DEFAULT_TRANSLATION = {
|
||||||
"Sorry, this project can't be shared because it's too large. We're working on it!",
|
"Sorry, this project can't be shared because it's too large. We're working on it!",
|
||||||
'share-menu.upload-failed':
|
'share-menu.upload-failed':
|
||||||
"Sorry, we couldn't upload your project at the moment. Please try again or let us know if the problem persists.",
|
"Sorry, we couldn't upload your project at the moment. Please try again or let us know if the problem persists.",
|
||||||
|
'status.offline': 'Offline',
|
||||||
|
'status.online': 'Online',
|
||||||
'people-menu.title': 'People',
|
'people-menu.title': 'People',
|
||||||
'people-menu.change-name': 'Change name',
|
'people-menu.change-name': 'Change name',
|
||||||
'people-menu.change-color': 'Change color',
|
'people-menu.change-color': 'Change color',
|
||||||
|
|
|
@ -131,6 +131,8 @@ export type TLUiIconType =
|
||||||
| 'spline-line'
|
| 'spline-line'
|
||||||
| 'stack-horizontal'
|
| 'stack-horizontal'
|
||||||
| 'stack-vertical'
|
| 'stack-vertical'
|
||||||
|
| 'status-offline'
|
||||||
|
| 'status-online'
|
||||||
| 'stretch-horizontal'
|
| 'stretch-horizontal'
|
||||||
| 'stretch-vertical'
|
| 'stretch-vertical'
|
||||||
| 'text-align-center'
|
| 'text-align-center'
|
||||||
|
@ -296,6 +298,8 @@ export const iconTypes = [
|
||||||
'spline-line',
|
'spline-line',
|
||||||
'stack-horizontal',
|
'stack-horizontal',
|
||||||
'stack-vertical',
|
'stack-vertical',
|
||||||
|
'status-offline',
|
||||||
|
'status-online',
|
||||||
'stretch-horizontal',
|
'stretch-horizontal',
|
||||||
'stretch-vertical',
|
'stretch-vertical',
|
||||||
'text-align-center',
|
'text-align-center',
|
||||||
|
|
Loading…
Reference in a new issue