[Experiment] Allow users to use system's appearance (dark / light) mode (#3703)
Allow the users to fully use the same colour scheme as their system. Allows the users to either: force dark colour scheme, force light colour scheme, or use the system one. It's reactive to the system changes. https://github.com/tldraw/tldraw/assets/2523721/6d4cef03-9ef0-4098-b299-6bf5d7513e98 ### Change Type <!-- ❗ Please select a 'Scope' label ❗️ --> - [x] `sdk` — Changes the tldraw SDK - [ ] `dotcom` — Changes the tldraw.com web app - [ ] `docs` — Changes to the documentation, examples, or templates. - [ ] `vs code` — Changes to the vscode plugin - [ ] `internal` — Does not affect user-facing stuff <!-- ❗ Please select a 'Type' label ❗️ --> - [ ] `bugfix` — Bug fix - [ ] `feature` — New feature - [x] `improvement` — Improving existing features - [ ] `chore` — Updating dependencies, other boring stuff - [ ] `galaxy brain` — Architectural changes - [ ] `tests` — Changes to any test code - [ ] `tools` — Changes to infrastructure, CI, internal scripts, debugging tools, etc. - [ ] `dunno` — I don't know ### Test Plan 1. Add a step-by-step description of how to test your PR here. 2. - [ ] Unit Tests - [ ] End to end tests ### Release Notes - Add a brief release note for your PR here. --------- Co-authored-by: David Sheldrick <d.j.sheldrick@gmail.com>
This commit is contained in:
parent
6c7b8febbf
commit
12aea7ed68
19 changed files with 124 additions and 48 deletions
|
@ -208,7 +208,7 @@ test.describe('Export snapshots', () => {
|
||||||
for (const [name, shapes] of filteredSnapshots) {
|
for (const [name, shapes] of filteredSnapshots) {
|
||||||
test(`Exports with ${name} in dark mode`, async ({ page, api }) => {
|
test(`Exports with ${name} in dark mode`, async ({ page, api }) => {
|
||||||
await page.evaluate((shapes) => {
|
await page.evaluate((shapes) => {
|
||||||
editor.user.updateUserPreferences({ isDarkMode: true })
|
editor.user.updateUserPreferences({ colorScheme: 'dark' })
|
||||||
editor
|
editor
|
||||||
.updateInstanceState({ exportBackground: false })
|
.updateInstanceState({ exportBackground: false })
|
||||||
.selectAll()
|
.selectAll()
|
||||||
|
|
|
@ -187,7 +187,7 @@ test.describe('Keyboard Shortcuts', () => {
|
||||||
test('Toggle dark mode', async () => {
|
test('Toggle dark mode', async () => {
|
||||||
await page.keyboard.press('Control+/')
|
await page.keyboard.press('Control+/')
|
||||||
expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({
|
expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({
|
||||||
name: 'toggle-dark-mode',
|
name: 'color-scheme',
|
||||||
data: { source: 'kbd' },
|
data: { source: 'kbd' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -73,7 +73,7 @@ export default function TldrawImageExample() {
|
||||||
onMount={(editor: Editor) => {
|
onMount={(editor: Editor) => {
|
||||||
setEditor(editor)
|
setEditor(editor)
|
||||||
editor.updateInstanceState({ isDebugMode: false })
|
editor.updateInstanceState({ isDebugMode: false })
|
||||||
editor.user.updateUserPreferences({ isDarkMode })
|
editor.user.updateUserPreferences({ colorScheme: isDarkMode ? 'dark' : 'light' })
|
||||||
if (currentPageId) {
|
if (currentPageId) {
|
||||||
editor.setCurrentPage(currentPageId)
|
editor.setCurrentPage(currentPageId)
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,6 +115,9 @@
|
||||||
"action.zoom-to-selection": "Zoom to selection",
|
"action.zoom-to-selection": "Zoom to selection",
|
||||||
"assets.files.upload-failed": "Upload failed",
|
"assets.files.upload-failed": "Upload failed",
|
||||||
"assets.url.failed": "Couldn't load URL preview",
|
"assets.url.failed": "Couldn't load URL preview",
|
||||||
|
"color-scheme.dark": "Dark",
|
||||||
|
"color-scheme.light": "Light",
|
||||||
|
"color-scheme.system": "System",
|
||||||
"color-style.white": "White",
|
"color-style.white": "White",
|
||||||
"color-style.black": "Black",
|
"color-style.black": "Black",
|
||||||
"color-style.blue": "Blue",
|
"color-style.blue": "Blue",
|
||||||
|
@ -228,6 +231,7 @@
|
||||||
"tool.embed": "Embed",
|
"tool.embed": "Embed",
|
||||||
"tool.text": "Text",
|
"tool.text": "Text",
|
||||||
"menu.title": "Menu",
|
"menu.title": "Menu",
|
||||||
|
"menu.color-scheme": "Color scheme",
|
||||||
"menu.copy-as": "Copy as",
|
"menu.copy-as": "Copy as",
|
||||||
"menu.edit": "Edit",
|
"menu.edit": "Edit",
|
||||||
"menu.export-as": "Export as",
|
"menu.export-as": "Export as",
|
||||||
|
|
|
@ -718,7 +718,6 @@ export const defaultUserPreferences: Readonly<{
|
||||||
animationSpeed: 0 | 1;
|
animationSpeed: 0 | 1;
|
||||||
color: "#02B1CC" | "#11B3A3" | "#39B178" | "#55B467" | "#7B66DC" | "#9D5BD2" | "#BD54C6" | "#E34BA9" | "#EC5E41" | "#F04F88" | "#F2555A" | "#FF802B";
|
color: "#02B1CC" | "#11B3A3" | "#39B178" | "#55B467" | "#7B66DC" | "#9D5BD2" | "#BD54C6" | "#E34BA9" | "#EC5E41" | "#F04F88" | "#F2555A" | "#FF802B";
|
||||||
edgeScrollSpeed: 1;
|
edgeScrollSpeed: 1;
|
||||||
isDarkMode: false;
|
|
||||||
isDynamicSizeMode: false;
|
isDynamicSizeMode: false;
|
||||||
isSnapMode: false;
|
isSnapMode: false;
|
||||||
isWrapMode: false;
|
isWrapMode: false;
|
||||||
|
@ -3343,12 +3342,12 @@ export interface TLUserPreferences {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
color?: null | string;
|
color?: null | string;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
colorScheme?: 'dark' | 'light' | 'system';
|
||||||
|
// (undocumented)
|
||||||
edgeScrollSpeed?: null | number;
|
edgeScrollSpeed?: null | number;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
id: string;
|
id: string;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
isDarkMode?: boolean | null;
|
|
||||||
// (undocumented)
|
|
||||||
isDynamicSizeMode?: boolean | null;
|
isDynamicSizeMode?: boolean | null;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
isSnapMode?: boolean | null;
|
isSnapMode?: boolean | null;
|
||||||
|
@ -3470,6 +3469,7 @@ export class UserPreferencesManager {
|
||||||
getUserPreferences(): {
|
getUserPreferences(): {
|
||||||
animationSpeed: number;
|
animationSpeed: number;
|
||||||
color: string;
|
color: string;
|
||||||
|
colorScheme: "dark" | "light" | "system" | undefined;
|
||||||
id: string;
|
id: string;
|
||||||
isDarkMode: boolean;
|
isDarkMode: boolean;
|
||||||
isDynamicResizeMode: boolean;
|
isDynamicResizeMode: boolean;
|
||||||
|
@ -3479,6 +3479,8 @@ export class UserPreferencesManager {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
systemColorScheme: Atom<"dark" | "light", unknown>;
|
||||||
|
// (undocumented)
|
||||||
updateUserPreferences: (userPreferences: Partial<TLUserPreferences>) => void;
|
updateUserPreferences: (userPreferences: Partial<TLUserPreferences>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -261,7 +261,7 @@ const TldrawEditorWithLoadingStore = memo(function TldrawEditorBeforeLoading({
|
||||||
const container = useContainer()
|
const container = useContainer()
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (user.userPreferences.get().isDarkMode) {
|
if (user.userPreferences.get().colorScheme === 'dark') {
|
||||||
container.classList.remove('tl-theme__light')
|
container.classList.remove('tl-theme__light')
|
||||||
container.classList.add('tl-theme__dark')
|
container.classList.add('tl-theme__dark')
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ export interface TLUserPreferences {
|
||||||
color?: string | null
|
color?: string | null
|
||||||
animationSpeed?: number | null
|
animationSpeed?: number | null
|
||||||
edgeScrollSpeed?: number | null
|
edgeScrollSpeed?: number | null
|
||||||
isDarkMode?: boolean | null
|
colorScheme?: 'light' | 'dark' | 'system'
|
||||||
isSnapMode?: boolean | null
|
isSnapMode?: boolean | null
|
||||||
isWrapMode?: boolean | null
|
isWrapMode?: boolean | null
|
||||||
isDynamicSizeMode?: boolean | null
|
isDynamicSizeMode?: boolean | null
|
||||||
|
@ -40,9 +40,9 @@ const userTypeValidator: T.Validator<TLUserPreferences> = T.object<TLUserPrefere
|
||||||
name: T.string.nullable().optional(),
|
name: T.string.nullable().optional(),
|
||||||
locale: T.string.nullable().optional(),
|
locale: T.string.nullable().optional(),
|
||||||
color: T.string.nullable().optional(),
|
color: T.string.nullable().optional(),
|
||||||
|
colorScheme: T.literalEnum('light', 'dark', 'system').optional(),
|
||||||
animationSpeed: T.number.nullable().optional(),
|
animationSpeed: T.number.nullable().optional(),
|
||||||
edgeScrollSpeed: T.number.nullable().optional(),
|
edgeScrollSpeed: T.number.nullable().optional(),
|
||||||
isDarkMode: T.boolean.nullable().optional(),
|
|
||||||
isSnapMode: T.boolean.nullable().optional(),
|
isSnapMode: T.boolean.nullable().optional(),
|
||||||
isWrapMode: T.boolean.nullable().optional(),
|
isWrapMode: T.boolean.nullable().optional(),
|
||||||
isDynamicSizeMode: T.boolean.nullable().optional(),
|
isDynamicSizeMode: T.boolean.nullable().optional(),
|
||||||
|
@ -55,6 +55,7 @@ const Versions = {
|
||||||
AddEdgeScrollSpeed: 4,
|
AddEdgeScrollSpeed: 4,
|
||||||
AddExcalidrawSelectMode: 5,
|
AddExcalidrawSelectMode: 5,
|
||||||
AddDynamicSizeMode: 6,
|
AddDynamicSizeMode: 6,
|
||||||
|
AllowSystemColorScheme: 7,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const CURRENT_VERSION = Math.max(...Object.values(Versions))
|
const CURRENT_VERSION = Math.max(...Object.values(Versions))
|
||||||
|
@ -75,6 +76,14 @@ function migrateSnapshot(data: { version: number; user: any }) {
|
||||||
if (data.version < Versions.AddExcalidrawSelectMode) {
|
if (data.version < Versions.AddExcalidrawSelectMode) {
|
||||||
data.user.isWrapMode = false
|
data.user.isWrapMode = false
|
||||||
}
|
}
|
||||||
|
if (data.version < Versions.AllowSystemColorScheme) {
|
||||||
|
if (data.user.isDarkMode === true) {
|
||||||
|
data.user.colorScheme = 'dark'
|
||||||
|
} else if (data.user.isDarkMode === false) {
|
||||||
|
data.user.colorScheme = 'light'
|
||||||
|
}
|
||||||
|
delete data.user.isDarkMode
|
||||||
|
}
|
||||||
|
|
||||||
if (data.version < Versions.AddDynamicSizeMode) {
|
if (data.version < Versions.AddDynamicSizeMode) {
|
||||||
data.user.isDynamicSizeMode = false
|
data.user.isDynamicSizeMode = false
|
||||||
|
@ -104,14 +113,6 @@ function getRandomColor() {
|
||||||
return USER_COLORS[Math.floor(Math.random() * USER_COLORS.length)]
|
return USER_COLORS[Math.floor(Math.random() * USER_COLORS.length)]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export function userPrefersDarkUI() {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ?? false
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export function userPrefersReducedMotion() {
|
export function userPrefersReducedMotion() {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
|
@ -125,7 +126,6 @@ export const defaultUserPreferences = Object.freeze({
|
||||||
name: 'New User',
|
name: 'New User',
|
||||||
locale: getDefaultTranslationLocale(),
|
locale: getDefaultTranslationLocale(),
|
||||||
color: getRandomColor(),
|
color: getRandomColor(),
|
||||||
isDarkMode: false,
|
|
||||||
edgeScrollSpeed: 1,
|
edgeScrollSpeed: 1,
|
||||||
animationSpeed: userPrefersReducedMotion() ? 0 : 1,
|
animationSpeed: userPrefersReducedMotion() ? 0 : 1,
|
||||||
isSnapMode: false,
|
isSnapMode: false,
|
||||||
|
|
|
@ -1,17 +1,28 @@
|
||||||
import { computed } from '@tldraw/state'
|
import { atom, computed } from '@tldraw/state'
|
||||||
import {
|
import { TLUserPreferences, defaultUserPreferences } from '../../config/TLUserPreferences'
|
||||||
TLUserPreferences,
|
|
||||||
defaultUserPreferences,
|
|
||||||
userPrefersDarkUI,
|
|
||||||
} from '../../config/TLUserPreferences'
|
|
||||||
import { TLUser } from '../../config/createTLUser'
|
import { TLUser } from '../../config/createTLUser'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export class UserPreferencesManager {
|
export class UserPreferencesManager {
|
||||||
|
systemColorScheme = atom<'dark' | 'light'>('systemColorScheme', 'light')
|
||||||
constructor(
|
constructor(
|
||||||
private readonly user: TLUser,
|
private readonly user: TLUser,
|
||||||
private readonly inferDarkMode: boolean
|
private readonly inferDarkMode: boolean
|
||||||
) {}
|
) {
|
||||||
|
if (window) {
|
||||||
|
const darkModeMediaQuery = window.matchMedia?.('(prefers-color-scheme: dark)')
|
||||||
|
if (darkModeMediaQuery?.matches) {
|
||||||
|
this.systemColorScheme.set('dark')
|
||||||
|
}
|
||||||
|
darkModeMediaQuery?.addEventListener('change', (e) => {
|
||||||
|
if (e.matches) {
|
||||||
|
this.systemColorScheme.set('dark')
|
||||||
|
} else {
|
||||||
|
this.systemColorScheme.set('light')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateUserPreferences = (userPreferences: Partial<TLUserPreferences>) => {
|
updateUserPreferences = (userPreferences: Partial<TLUserPreferences>) => {
|
||||||
this.user.setUserPreferences({
|
this.user.setUserPreferences({
|
||||||
|
@ -27,16 +38,23 @@ export class UserPreferencesManager {
|
||||||
color: this.getColor(),
|
color: this.getColor(),
|
||||||
animationSpeed: this.getAnimationSpeed(),
|
animationSpeed: this.getAnimationSpeed(),
|
||||||
isSnapMode: this.getIsSnapMode(),
|
isSnapMode: this.getIsSnapMode(),
|
||||||
|
colorScheme: this.user.userPreferences.get().colorScheme,
|
||||||
isDarkMode: this.getIsDarkMode(),
|
isDarkMode: this.getIsDarkMode(),
|
||||||
isWrapMode: this.getIsWrapMode(),
|
isWrapMode: this.getIsWrapMode(),
|
||||||
isDynamicResizeMode: this.getIsDynamicResizeMode(),
|
isDynamicResizeMode: this.getIsDynamicResizeMode(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@computed getIsDarkMode() {
|
@computed getIsDarkMode() {
|
||||||
return (
|
switch (this.user.userPreferences.get().colorScheme) {
|
||||||
this.user.userPreferences.get().isDarkMode ??
|
case 'dark':
|
||||||
(this.inferDarkMode ? userPrefersDarkUI() : false)
|
return true
|
||||||
)
|
case 'light':
|
||||||
|
return false
|
||||||
|
case 'system':
|
||||||
|
return this.systemColorScheme.get() === 'dark'
|
||||||
|
default:
|
||||||
|
return this.inferDarkMode ? this.systemColorScheme.get() === 'dark' : false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,7 +19,7 @@ describe('user', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('gets a user with the correct', () => {
|
it('gets a user with the correct', () => {
|
||||||
editor.user.updateUserPreferences({ isDarkMode: true })
|
editor.user.updateUserPreferences({ colorScheme: 'dark' })
|
||||||
expect(editor.user.getIsDarkMode()).toBe(true)
|
expect(editor.user.getIsDarkMode()).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -222,7 +222,6 @@ export {
|
||||||
ReorderMenuSubmenu,
|
ReorderMenuSubmenu,
|
||||||
SelectAllMenuItem,
|
SelectAllMenuItem,
|
||||||
ToggleAutoSizeMenuItem,
|
ToggleAutoSizeMenuItem,
|
||||||
ToggleDarkModeItem,
|
|
||||||
ToggleDebugModeItem,
|
ToggleDebugModeItem,
|
||||||
ToggleEdgeScrollingItem,
|
ToggleEdgeScrollingItem,
|
||||||
ToggleFocusModeItem,
|
ToggleFocusModeItem,
|
||||||
|
|
41
packages/tldraw/src/lib/ui/components/ColorSchemeMenu.tsx
Normal file
41
packages/tldraw/src/lib/ui/components/ColorSchemeMenu.tsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { useEditor, useValue } from '@tldraw/editor'
|
||||||
|
import { useUiEvents } from '../context/events'
|
||||||
|
import { TldrawUiMenuCheckboxItem } from './primitives/menus/TldrawUiMenuCheckboxItem'
|
||||||
|
import { TldrawUiMenuGroup } from './primitives/menus/TldrawUiMenuGroup'
|
||||||
|
import { TldrawUiMenuSubmenu } from './primitives/menus/TldrawUiMenuSubmenu'
|
||||||
|
|
||||||
|
const COLOR_SCHEMES = [
|
||||||
|
{ colorScheme: 'light' as const, label: 'color-scheme.light' },
|
||||||
|
{ colorScheme: 'dark' as const, label: 'color-scheme.dark' },
|
||||||
|
{ colorScheme: 'system' as const, label: 'color-scheme.system' },
|
||||||
|
]
|
||||||
|
|
||||||
|
/** @public @react */
|
||||||
|
export function ColorSchemeMenu() {
|
||||||
|
const editor = useEditor()
|
||||||
|
const trackEvent = useUiEvents()
|
||||||
|
const currentColorScheme = useValue(
|
||||||
|
'colorScheme',
|
||||||
|
() => editor.user.getUserPreferences().colorScheme,
|
||||||
|
[editor]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TldrawUiMenuSubmenu id="help menu color-scheme" label="menu.color-scheme">
|
||||||
|
<TldrawUiMenuGroup id="languages">
|
||||||
|
{COLOR_SCHEMES.map(({ colorScheme, label }) => (
|
||||||
|
<TldrawUiMenuCheckboxItem
|
||||||
|
id={`color-scheme-${colorScheme}`}
|
||||||
|
key={colorScheme}
|
||||||
|
label={label}
|
||||||
|
checked={colorScheme === currentColorScheme}
|
||||||
|
onSelect={() => {
|
||||||
|
editor.user.updateUserPreferences({ colorScheme })
|
||||||
|
trackEvent('color-scheme', { source: 'menu', value: colorScheme })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TldrawUiMenuGroup>
|
||||||
|
</TldrawUiMenuSubmenu>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import { useEditor, useValue } from '@tldraw/editor'
|
import { useEditor, useValue } from '@tldraw/editor'
|
||||||
import { useActions } from '../../context/actions'
|
import { useActions } from '../../context/actions'
|
||||||
import { useCanRedo, useCanUndo } from '../../hooks/menu-hooks'
|
import { useCanRedo, useCanUndo } from '../../hooks/menu-hooks'
|
||||||
|
import { ColorSchemeMenu } from '../ColorSchemeMenu'
|
||||||
import { LanguageMenu } from '../LanguageMenu'
|
import { LanguageMenu } from '../LanguageMenu'
|
||||||
import {
|
import {
|
||||||
ClipboardMenuGroup,
|
ClipboardMenuGroup,
|
||||||
|
@ -14,7 +15,6 @@ import {
|
||||||
RemoveFrameMenuItem,
|
RemoveFrameMenuItem,
|
||||||
SelectAllMenuItem,
|
SelectAllMenuItem,
|
||||||
ToggleAutoSizeMenuItem,
|
ToggleAutoSizeMenuItem,
|
||||||
ToggleDarkModeItem,
|
|
||||||
ToggleDebugModeItem,
|
ToggleDebugModeItem,
|
||||||
ToggleDynamicSizeModeItem,
|
ToggleDynamicSizeModeItem,
|
||||||
ToggleEdgeScrollingItem,
|
ToggleEdgeScrollingItem,
|
||||||
|
@ -170,13 +170,15 @@ export function PreferencesGroup() {
|
||||||
<ToggleToolLockItem />
|
<ToggleToolLockItem />
|
||||||
<ToggleGridItem />
|
<ToggleGridItem />
|
||||||
<ToggleWrapModeItem />
|
<ToggleWrapModeItem />
|
||||||
<ToggleDarkModeItem />
|
|
||||||
<ToggleFocusModeItem />
|
<ToggleFocusModeItem />
|
||||||
<ToggleEdgeScrollingItem />
|
<ToggleEdgeScrollingItem />
|
||||||
<ToggleReduceMotionItem />
|
<ToggleReduceMotionItem />
|
||||||
<ToggleDynamicSizeModeItem />
|
<ToggleDynamicSizeModeItem />
|
||||||
<ToggleDebugModeItem />
|
<ToggleDebugModeItem />
|
||||||
</TldrawUiMenuGroup>
|
</TldrawUiMenuGroup>
|
||||||
|
<TldrawUiMenuGroup id="color-scheme">
|
||||||
|
<ColorSchemeMenu />
|
||||||
|
</TldrawUiMenuGroup>
|
||||||
<TldrawUiMenuGroup id="language">
|
<TldrawUiMenuGroup id="language">
|
||||||
<LanguageMenu />
|
<LanguageMenu />
|
||||||
</TldrawUiMenuGroup>
|
</TldrawUiMenuGroup>
|
||||||
|
|
|
@ -1110,8 +1110,11 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
||||||
kbd: '$/',
|
kbd: '$/',
|
||||||
readonlyOk: true,
|
readonlyOk: true,
|
||||||
onSelect(source) {
|
onSelect(source) {
|
||||||
trackEvent('toggle-dark-mode', { source })
|
const value = editor.user.getIsDarkMode() ? 'light' : 'dark'
|
||||||
editor.user.updateUserPreferences({ isDarkMode: !editor.user.getIsDarkMode() })
|
trackEvent('color-scheme', { source, value })
|
||||||
|
editor.user.updateUserPreferences({
|
||||||
|
colorScheme: value,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
checkbox: true,
|
checkbox: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -84,7 +84,6 @@ export interface TLUiEventMap {
|
||||||
'toggle-snap-mode': null
|
'toggle-snap-mode': null
|
||||||
'toggle-tool-lock': null
|
'toggle-tool-lock': null
|
||||||
'toggle-grid-mode': null
|
'toggle-grid-mode': null
|
||||||
'toggle-dark-mode': null
|
|
||||||
'toggle-wrap-mode': null
|
'toggle-wrap-mode': null
|
||||||
'toggle-focus-mode': null
|
'toggle-focus-mode': null
|
||||||
'toggle-debug-mode': null
|
'toggle-debug-mode': null
|
||||||
|
@ -92,6 +91,7 @@ export interface TLUiEventMap {
|
||||||
'toggle-lock': null
|
'toggle-lock': null
|
||||||
'toggle-reduce-motion': null
|
'toggle-reduce-motion': null
|
||||||
'toggle-edge-scrolling': null
|
'toggle-edge-scrolling': null
|
||||||
|
'color-scheme': { value: string }
|
||||||
'exit-pen-mode': null
|
'exit-pen-mode': null
|
||||||
'stop-following': null
|
'stop-following': null
|
||||||
'open-cursor-chat': null
|
'open-cursor-chat': null
|
||||||
|
|
|
@ -119,6 +119,9 @@ export type TLUiTranslationKey =
|
||||||
| 'action.zoom-to-selection'
|
| 'action.zoom-to-selection'
|
||||||
| 'assets.files.upload-failed'
|
| 'assets.files.upload-failed'
|
||||||
| 'assets.url.failed'
|
| 'assets.url.failed'
|
||||||
|
| 'color-scheme.dark'
|
||||||
|
| 'color-scheme.light'
|
||||||
|
| 'color-scheme.system'
|
||||||
| 'color-style.white'
|
| 'color-style.white'
|
||||||
| 'color-style.black'
|
| 'color-style.black'
|
||||||
| 'color-style.blue'
|
| 'color-style.blue'
|
||||||
|
@ -232,6 +235,7 @@ export type TLUiTranslationKey =
|
||||||
| 'tool.embed'
|
| 'tool.embed'
|
||||||
| 'tool.text'
|
| 'tool.text'
|
||||||
| 'menu.title'
|
| 'menu.title'
|
||||||
|
| 'menu.color-scheme'
|
||||||
| 'menu.copy-as'
|
| 'menu.copy-as'
|
||||||
| 'menu.edit'
|
| 'menu.edit'
|
||||||
| 'menu.export-as'
|
| 'menu.export-as'
|
||||||
|
|
|
@ -119,6 +119,9 @@ export const DEFAULT_TRANSLATION = {
|
||||||
'action.zoom-to-selection': 'Zoom to selection',
|
'action.zoom-to-selection': 'Zoom to selection',
|
||||||
'assets.files.upload-failed': 'Upload failed',
|
'assets.files.upload-failed': 'Upload failed',
|
||||||
'assets.url.failed': "Couldn't load URL preview",
|
'assets.url.failed': "Couldn't load URL preview",
|
||||||
|
'color-scheme.dark': 'Dark',
|
||||||
|
'color-scheme.light': 'Light',
|
||||||
|
'color-scheme.system': 'System',
|
||||||
'color-style.white': 'White',
|
'color-style.white': 'White',
|
||||||
'color-style.black': 'Black',
|
'color-style.black': 'Black',
|
||||||
'color-style.blue': 'Blue',
|
'color-style.blue': 'Blue',
|
||||||
|
@ -232,6 +235,7 @@ export const DEFAULT_TRANSLATION = {
|
||||||
'tool.embed': 'Embed',
|
'tool.embed': 'Embed',
|
||||||
'tool.text': 'Text',
|
'tool.text': 'Text',
|
||||||
'menu.title': 'Menu',
|
'menu.title': 'Menu',
|
||||||
|
'menu.color-scheme': 'Color scheme',
|
||||||
'menu.copy-as': 'Copy as',
|
'menu.copy-as': 'Copy as',
|
||||||
'menu.edit': 'Edit',
|
'menu.edit': 'Edit',
|
||||||
'menu.export-as': 'Export as',
|
'menu.export-as': 'Export as',
|
||||||
|
|
|
@ -318,5 +318,5 @@ export async function parseAndLoadDocument(
|
||||||
editor.updateInstanceState({ isFocused })
|
editor.updateInstanceState({ isFocused })
|
||||||
})
|
})
|
||||||
|
|
||||||
if (forceDarkMode) editor.user.updateUserPreferences({ isDarkMode: true })
|
if (forceDarkMode) editor.user.updateUserPreferences({ colorScheme: 'dark' })
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ describe('TLUserPreferences', () => {
|
||||||
animationSpeed: 1,
|
animationSpeed: 1,
|
||||||
color: '#000000',
|
color: '#000000',
|
||||||
id: '123',
|
id: '123',
|
||||||
isDarkMode: true,
|
colorScheme: 'dark',
|
||||||
isSnapMode: false,
|
isSnapMode: false,
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
@ -38,15 +38,15 @@ describe('TLUserPreferences', () => {
|
||||||
|
|
||||||
userPreferences.set({
|
userPreferences.set({
|
||||||
...userPreferences.get(),
|
...userPreferences.get(),
|
||||||
isDarkMode: false,
|
colorScheme: 'light',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(editor.user.getIsDarkMode()).toBe(false)
|
expect(editor.user.getIsDarkMode()).toBe(false)
|
||||||
|
|
||||||
editor.user.updateUserPreferences({ isDarkMode: true })
|
editor.user.updateUserPreferences({ colorScheme: 'dark' })
|
||||||
|
|
||||||
expect(editor.user.getIsDarkMode()).toBe(true)
|
expect(editor.user.getIsDarkMode()).toBe(true)
|
||||||
expect(userPreferences.get().isDarkMode).toBe(true)
|
expect(userPreferences.get().colorScheme).toBe('dark')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can have null values and it will use defaults', () => {
|
it('can have null values and it will use defaults', () => {
|
||||||
|
@ -54,7 +54,7 @@ describe('TLUserPreferences', () => {
|
||||||
id: '123',
|
id: '123',
|
||||||
animationSpeed: null,
|
animationSpeed: null,
|
||||||
color: null,
|
color: null,
|
||||||
isDarkMode: null,
|
colorScheme: 'system',
|
||||||
isSnapMode: null,
|
isSnapMode: null,
|
||||||
locale: null,
|
locale: null,
|
||||||
name: null,
|
name: null,
|
||||||
|
|
Loading…
Reference in a new issue