fix: sync whiteboard theme with app mode

This commit is contained in:
lucaronin
2026-05-08 13:04:24 +02:00
parent 4fd0a265f4
commit 91e02f0719
4 changed files with 117 additions and 3 deletions

View File

@@ -1,11 +1,18 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { act, cleanup, render, screen, waitFor } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { Editor } from 'tldraw'
import { TldrawWhiteboard } from './TldrawWhiteboard'
interface MockTldrawProps {
assetUrls: MockAssetUrls
onMount: (editor: Editor) => () => void
user?: MockTldrawUser
}
interface MockTldrawUser {
userPreferences: {
get: () => { colorScheme?: string }
}
}
interface MockAssetUrls {
@@ -64,8 +71,18 @@ vi.mock('tldraw', async () => {
createTLStore: vi.fn(() => ({
listen: vi.fn(() => vi.fn()),
})),
defaultUserPreferences: {
colorScheme: 'light',
locale: 'en',
},
getSnapshot: vi.fn(() => ({ document: {} })),
loadSnapshot: vi.fn(),
useTldrawUser: vi.fn(({ userPreferences }: { userPreferences: { colorScheme: string } }) => ({
setUserPreferences: vi.fn(),
userPreferences: {
get: () => userPreferences,
},
})),
}
})
@@ -76,7 +93,7 @@ function renderedTldrawAssetUrls(): MockAssetUrls {
}
function renderedTldrawProps(): MockTldrawProps {
const props = tldrawMock.Tldraw.mock.calls[0]?.[0] as MockTldrawProps
const props = tldrawMock.Tldraw.mock.lastCall?.[0] as MockTldrawProps
expect(props).toBeDefined()
return props
}
@@ -125,6 +142,13 @@ function expectBundledTldrawAssetUrls(assetUrls: MockAssetUrls) {
}
describe('TldrawWhiteboard', () => {
afterEach(() => {
cleanup()
document.documentElement.removeAttribute('data-theme')
document.documentElement.classList.remove('dark')
vi.clearAllMocks()
})
it('uses bundled tldraw assets instead of CDN URLs', () => {
render(
<TldrawWhiteboard
@@ -142,6 +166,50 @@ describe('TldrawWhiteboard', () => {
expectBundledTldrawAssetUrls(renderedTldrawAssetUrls())
})
it('passes Tolaria dark mode to tldraw', () => {
document.documentElement.setAttribute('data-theme', 'dark')
document.documentElement.classList.add('dark')
render(
<TldrawWhiteboard
boardId="board-1"
height="520"
snapshot=""
width=""
onSizeChange={vi.fn()}
onSnapshotChange={vi.fn()}
/>
)
expect(renderedTldrawProps().user?.userPreferences.get().colorScheme).toBe('dark')
})
it('updates the tldraw color scheme when Tolaria theme changes', async () => {
document.documentElement.setAttribute('data-theme', 'light')
render(
<TldrawWhiteboard
boardId="board-1"
height="520"
snapshot=""
width=""
onSizeChange={vi.fn()}
onSnapshotChange={vi.fn()}
/>
)
expect(renderedTldrawProps().user?.userPreferences.get().colorScheme).toBe('light')
act(() => {
document.documentElement.setAttribute('data-theme', 'dark')
document.documentElement.classList.add('dark')
})
await waitFor(() => {
expect(renderedTldrawProps().user?.userPreferences.get().colorScheme).toBe('dark')
})
})
it('installs the text measurement guard when the canvas mounts', () => {
render(
<TldrawWhiteboard

View File

@@ -9,20 +9,26 @@ import {
TldrawUiMenuContextProvider,
Tldraw,
createTLStore,
defaultUserPreferences,
getSnapshot,
loadSnapshot,
react as reactToTldrawSignal,
useDialogs,
useTldrawUser,
useTranslation,
type Editor,
type TLEventInfo,
type TLUiDialog,
type TLStoreSnapshot,
type TLUserPreferences,
} from 'tldraw'
import 'tldraw/tldraw.css'
import { useDocumentThemeMode } from '../hooks/useDocumentThemeMode'
import type { ResolvedThemeMode } from '../lib/themeMode'
import { installTldrawTextMeasurementGuard } from './tldrawTextMeasurementGuard'
const EMPTY_TLDRAW_TRANSLATION_URL = 'data:application/json;base64,e30K'
const TOLARIA_TLDRAW_USER_ID = 'tolaria-whiteboard'
function resolveTldrawAssetUrl(assetUrl: string | undefined): string {
return assetUrl ?? EMPTY_TLDRAW_TRANSLATION_URL
@@ -89,6 +95,18 @@ function cssSize({ height, width }: PixelSize): CSSProperties {
} as CSSProperties
}
function tldrawUserPreferences(themeMode: ResolvedThemeMode): TLUserPreferences {
return {
...defaultUserPreferences,
id: TOLARIA_TLDRAW_USER_ID,
colorScheme: themeMode,
}
}
function ignoreTldrawUserPreferencesUpdate(preferences: TLUserPreferences) {
void preferences
}
function parseSnapshot(source: string): TLStoreSnapshot | null {
if (!source.trim()) return null
@@ -387,6 +405,12 @@ export function TldrawWhiteboard({
const persistedSize = useMemo(() => normalizeSize({ height, width }), [height, width])
const [resizingSize, setResizingSize] = useState<PixelSize | null>(null)
const visibleSize = resizingSize ?? persistedSize
const themeMode = useDocumentThemeMode()
const userPreferences = useMemo(() => tldrawUserPreferences(themeMode), [themeMode])
const tldrawUser = useTldrawUser({
setUserPreferences: ignoreTldrawUserPreferencesUpdate,
userPreferences,
})
useEffect(() => {
onSnapshotChangeRef.current = onSnapshotChange
@@ -483,6 +507,7 @@ export function TldrawWhiteboard({
components={tldrawUiComponents}
onMount={installWhiteboardRuntimeGuards}
store={store}
user={tldrawUser}
/>
<div
aria-label="Resize whiteboard width"