fix: default light theme splash 🔧 (#5660)
Co-authored-by: dwelle <luzar.david@gmail.com> Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
This commit is contained in:
parent
ec4b3d913e
commit
7eaf47c9d4
@ -137,7 +137,6 @@ export class ActionManager {
|
||||
*/
|
||||
renderAction = (name: ActionName, data?: PanelComponentProps["data"]) => {
|
||||
const canvasActions = this.app.props.UIOptions.canvasActions;
|
||||
|
||||
if (
|
||||
this.actions[name] &&
|
||||
"PanelComponent" in this.actions[name] &&
|
||||
|
@ -552,10 +552,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
typeof this.props?.zenModeEnabled === "undefined" &&
|
||||
this.state.zenModeEnabled
|
||||
}
|
||||
showThemeBtn={
|
||||
typeof this.props?.theme === "undefined" &&
|
||||
this.props.UIOptions.canvasActions.theme
|
||||
}
|
||||
libraryReturnUrl={this.props.libraryReturnUrl}
|
||||
UIOptions={this.props.UIOptions}
|
||||
focusContainer={this.focusContainer}
|
||||
@ -645,7 +641,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false;
|
||||
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
|
||||
let gridSize = actionResult?.appState?.gridSize || null;
|
||||
let theme = actionResult?.appState?.theme || THEME.LIGHT;
|
||||
const theme =
|
||||
actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
|
||||
let name = actionResult?.appState?.name ?? this.state.name;
|
||||
if (typeof this.props.viewModeEnabled !== "undefined") {
|
||||
viewModeEnabled = this.props.viewModeEnabled;
|
||||
@ -659,10 +656,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
gridSize = this.props.gridModeEnabled ? GRID_SIZE : null;
|
||||
}
|
||||
|
||||
if (typeof this.props.theme !== "undefined") {
|
||||
theme = this.props.theme;
|
||||
}
|
||||
|
||||
if (typeof this.props.name !== "undefined") {
|
||||
name = this.props.name;
|
||||
}
|
||||
@ -755,6 +748,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.theme) {
|
||||
this.setState({ theme: this.props.theme });
|
||||
}
|
||||
if (!this.state.isLoading) {
|
||||
this.setState({ isLoading: true });
|
||||
}
|
||||
@ -784,6 +780,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const scene = restore(initialData, null, null);
|
||||
scene.appState = {
|
||||
...scene.appState,
|
||||
theme: this.props.theme || scene.appState.theme,
|
||||
// we're falling back to current (pre-init) state when deciding
|
||||
// whether to open the library, to handle a case where we
|
||||
// update the state outside of initialData (e.g. when loading the app
|
||||
|
@ -1,20 +1,12 @@
|
||||
import React from "react";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { AppState } from "../types";
|
||||
|
||||
export const BackgroundPickerAndDarkModeToggle = ({
|
||||
appState,
|
||||
setAppState,
|
||||
actionManager,
|
||||
showThemeBtn,
|
||||
}: {
|
||||
actionManager: ActionManager;
|
||||
appState: AppState;
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
showThemeBtn: boolean;
|
||||
}) => (
|
||||
<div style={{ display: "flex" }}>
|
||||
{actionManager.renderAction("changeViewBackgroundColor")}
|
||||
{showThemeBtn && actionManager.renderAction("toggleTheme")}
|
||||
{actionManager.renderAction("toggleTheme")}
|
||||
</div>
|
||||
);
|
||||
|
@ -2,10 +2,12 @@ import React, { useEffect, useState } from "react";
|
||||
|
||||
import { LoadingMessage } from "./LoadingMessage";
|
||||
import { defaultLang, Language, languages, setLanguage } from "../i18n";
|
||||
import { Theme } from "../element/types";
|
||||
|
||||
interface Props {
|
||||
langCode: Language["code"];
|
||||
children: React.ReactElement;
|
||||
theme?: Theme;
|
||||
}
|
||||
|
||||
export const InitializeApp = (props: Props) => {
|
||||
@ -21,5 +23,5 @@ export const InitializeApp = (props: Props) => {
|
||||
updateLang();
|
||||
}, [props.langCode]);
|
||||
|
||||
return loading ? <LoadingMessage /> : props.children;
|
||||
return loading ? <LoadingMessage theme={props.theme} /> : props.children;
|
||||
};
|
||||
|
@ -53,7 +53,6 @@ interface LayerUIProps {
|
||||
onPenModeToggle: () => void;
|
||||
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
|
||||
showExitZenModeBtn: boolean;
|
||||
showThemeBtn: boolean;
|
||||
langCode: Language["code"];
|
||||
isCollaborating: boolean;
|
||||
renderTopRightUI?: ExcalidrawProps["renderTopRightUI"];
|
||||
@ -78,7 +77,6 @@ const LayerUI = ({
|
||||
onPenModeToggle,
|
||||
onInsertElements,
|
||||
showExitZenModeBtn,
|
||||
showThemeBtn,
|
||||
isCollaborating,
|
||||
renderTopRightUI,
|
||||
renderCustomFooter,
|
||||
@ -209,12 +207,7 @@ const LayerUI = ({
|
||||
/>
|
||||
)}
|
||||
</Stack.Row>
|
||||
<BackgroundPickerAndDarkModeToggle
|
||||
appState={appState}
|
||||
actionManager={actionManager}
|
||||
setAppState={setAppState}
|
||||
showThemeBtn={showThemeBtn}
|
||||
/>
|
||||
<BackgroundPickerAndDarkModeToggle actionManager={actionManager} />
|
||||
{appState.fileHandle && (
|
||||
<>{actionManager.renderAction("saveToActiveFile")}</>
|
||||
)}
|
||||
@ -424,7 +417,6 @@ const LayerUI = ({
|
||||
canvas={canvas}
|
||||
isCollaborating={isCollaborating}
|
||||
renderCustomFooter={renderCustomFooter}
|
||||
showThemeBtn={showThemeBtn}
|
||||
onImageAction={onImageAction}
|
||||
renderTopRightUI={renderTopRightUI}
|
||||
renderCustomStats={renderCustomStats}
|
||||
|
@ -1,8 +1,14 @@
|
||||
import { t } from "../i18n";
|
||||
import { useState, useEffect } from "react";
|
||||
import Spinner from "./Spinner";
|
||||
import clsx from "clsx";
|
||||
import { THEME } from "../constants";
|
||||
import { Theme } from "../element/types";
|
||||
|
||||
export const LoadingMessage: React.FC<{ delay?: number }> = ({ delay }) => {
|
||||
export const LoadingMessage: React.FC<{ delay?: number; theme?: Theme }> = ({
|
||||
delay,
|
||||
theme,
|
||||
}) => {
|
||||
const [isWaiting, setIsWaiting] = useState(!!delay);
|
||||
|
||||
useEffect(() => {
|
||||
@ -20,7 +26,11 @@ export const LoadingMessage: React.FC<{ delay?: number }> = ({ delay }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="LoadingMessage">
|
||||
<div
|
||||
className={clsx("LoadingMessage", {
|
||||
"LoadingMessage--dark": theme === THEME.DARK,
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
<Spinner />
|
||||
</div>
|
||||
|
@ -38,7 +38,6 @@ type MobileMenuProps = {
|
||||
isMobile: boolean,
|
||||
appState: AppState,
|
||||
) => JSX.Element | null;
|
||||
showThemeBtn: boolean;
|
||||
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
|
||||
renderTopRightUI?: (
|
||||
isMobile: boolean,
|
||||
@ -61,7 +60,6 @@ export const MobileMenu = ({
|
||||
canvas,
|
||||
isCollaborating,
|
||||
renderCustomFooter,
|
||||
showThemeBtn,
|
||||
onImageAction,
|
||||
renderTopRightUI,
|
||||
renderCustomStats,
|
||||
@ -171,14 +169,7 @@ export const MobileMenu = ({
|
||||
onClick={onCollabButtonClick}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
<BackgroundPickerAndDarkModeToggle
|
||||
actionManager={actionManager}
|
||||
appState={appState}
|
||||
setAppState={setAppState}
|
||||
showThemeBtn={showThemeBtn}
|
||||
/>
|
||||
}
|
||||
{<BackgroundPickerAndDarkModeToggle actionManager={actionManager} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -149,7 +149,7 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = {
|
||||
export: { saveFileToDisk: true },
|
||||
loadScene: true,
|
||||
saveToActiveFile: true,
|
||||
theme: true,
|
||||
toggleTheme: null,
|
||||
saveAsImage: true,
|
||||
},
|
||||
};
|
||||
|
@ -1,3 +1,5 @@
|
||||
@import "open-color/open-color.scss";
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute !important;
|
||||
height: 1px;
|
||||
@ -30,3 +32,8 @@
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
.LoadingMessage--dark {
|
||||
background-color: $oc-black;
|
||||
color: $oc-white;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ export const STORAGE_KEYS = {
|
||||
LOCAL_STORAGE_APP_STATE: "excalidraw-state",
|
||||
LOCAL_STORAGE_COLLAB: "excalidraw-collab",
|
||||
LOCAL_STORAGE_LIBRARY: "excalidraw-library",
|
||||
LOCAL_STORAGE_THEME: "excalidraw-theme",
|
||||
VERSION_DATA_STATE: "version-dataState",
|
||||
VERSION_FILES: "version-files",
|
||||
} as const;
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
APP_NAME,
|
||||
COOKIES,
|
||||
EVENT,
|
||||
THEME,
|
||||
TITLE_TIMEOUT,
|
||||
VERSION_TIMEOUT,
|
||||
} from "../constants";
|
||||
@ -17,6 +18,7 @@ import {
|
||||
ExcalidrawElement,
|
||||
FileId,
|
||||
NonDeletedExcalidrawElement,
|
||||
Theme,
|
||||
} from "../element/types";
|
||||
import { useCallbackRefState } from "../hooks/useCallbackRefState";
|
||||
import { t } from "../i18n";
|
||||
@ -512,6 +514,18 @@ const ExcalidrawWrapper = () => {
|
||||
languageDetector.cacheUserLanguage(langCode);
|
||||
}, [langCode]);
|
||||
|
||||
const [theme, setTheme] = useState<Theme>(
|
||||
() =>
|
||||
localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_THEME) ||
|
||||
// FIXME migration from old LS scheme. Can be removed later. #5660
|
||||
importFromLocalStorage().appState?.theme ||
|
||||
THEME.LIGHT,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEYS.LOCAL_STORAGE_THEME, theme);
|
||||
}, [theme]);
|
||||
|
||||
const onChange = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
@ -521,6 +535,8 @@ const ExcalidrawWrapper = () => {
|
||||
collabAPI.syncElements(elements);
|
||||
}
|
||||
|
||||
setTheme(appState.theme);
|
||||
|
||||
// this check is redundant, but since this is a hot path, it's best
|
||||
// not to evaludate the nested expression every time
|
||||
if (!LocalData.isSavePaused()) {
|
||||
@ -710,6 +726,7 @@ const ExcalidrawWrapper = () => {
|
||||
onPointerUpdate={collabAPI?.onPointerUpdate}
|
||||
UIOptions={{
|
||||
canvasActions: {
|
||||
toggleTheme: true,
|
||||
export: {
|
||||
onExportToBackend,
|
||||
renderCustomUI: (elements, appState, files) => {
|
||||
@ -739,6 +756,7 @@ const ExcalidrawWrapper = () => {
|
||||
handleKeyboardGlobally={true}
|
||||
onLibraryChange={onLibraryChange}
|
||||
autoFocus={true}
|
||||
theme={theme}
|
||||
/>
|
||||
{excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />}
|
||||
{errorMessage && (
|
||||
|
@ -17,11 +17,13 @@ Please add the latest change on the top under the correct section.
|
||||
|
||||
#### Features
|
||||
|
||||
- Support [theme](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#theme) to be semi-controlled [#5660](https://github.com/excalidraw/excalidraw/pull/5660).
|
||||
- Added support for storing [`customData`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#storing-custom-data-to-excalidraw-elements) on Excalidraw elements [#5592].
|
||||
- Added `exportPadding?: number;` to [exportToCanvas](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exporttocanvas) and [exportToBlob](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exporttoblob). The default value of the padding is 10.
|
||||
|
||||
#### Breaking Changes
|
||||
|
||||
- `props.UIOptions.canvasActions.theme` is now renamed to `props.UIOptions.canvasActions.toggleTheme` [#5660](https://github.com/excalidraw/excalidraw/pull/5660).
|
||||
- `setToastMessage` API is now renamed to `setToast` API and the function signature is also updated [#5427](https://github.com/excalidraw/excalidraw/pull/5427). You can also pass `duration` and `closable` attributes along with `message`.
|
||||
|
||||
## 0.12.0 (2022-07-07)
|
||||
|
@ -637,7 +637,9 @@ If supplied, this URL will be used when user tries to install a library from [li
|
||||
|
||||
#### `theme`
|
||||
|
||||
This prop controls Excalidraw's theme. When supplied, the value takes precedence over `intialData.appState.theme`, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app. You can use [`THEME`](#THEME-1) to specify the theme.
|
||||
This prop controls Excalidraw's theme. When supplied, the value takes precedence over `intialData.appState.theme`, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app unless `UIOptions.canvasActions.toggleTheme` is set to `true`, in which case the `theme` prop will control Excalidraw's default theme with ability to allow theme switching (you must take care of updating the `theme` prop when you detect a change to `appState.theme` from the [onChange](#onChange) callback).
|
||||
|
||||
You can use [`THEME`](#THEME-1) to specify the theme.
|
||||
|
||||
#### `name`
|
||||
|
||||
@ -660,7 +662,7 @@ This prop can be used to customise UI of Excalidraw. Currently we support custom
|
||||
| `export` | false | [exportOpts](#exportOpts) | <pre>{ saveFileToDisk: true }</pre> | This prop allows to customize the UI inside the export dialog. By default it shows the "saveFileToDisk". If this prop is `false` the export button will not be rendered. For more details visit [`exportOpts`](#exportOpts). |
|
||||
| `loadScene` | boolean | true | Implies whether to show `Load button` |
|
||||
| `saveToActiveFile` | boolean | true | Implies whether to show `Save button` to save to current file |
|
||||
| `theme` | boolean | true | Implies whether to show `Theme toggle` |
|
||||
| `toggleTheme` | boolean | null | null | Implies whether to show `Theme toggle`. When defined as `boolean`, takes precedence over [`props.theme`](#theme) to show `Theme toggle` |
|
||||
| `saveAsImage` | boolean | true | Implies whether to show `Save as image button` |
|
||||
|
||||
##### `dockedSidebarBreakpoint`
|
||||
|
@ -56,6 +56,13 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
||||
DEFAULT_UI_OPTIONS.canvasActions.export.saveFileToDisk;
|
||||
}
|
||||
|
||||
if (
|
||||
UIOptions.canvasActions.toggleTheme === null &&
|
||||
typeof theme === "undefined"
|
||||
) {
|
||||
UIOptions.canvasActions.toggleTheme = true;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Block pinch-zooming on iOS outside of the content area
|
||||
const handleTouchMove = (event: TouchEvent) => {
|
||||
@ -75,7 +82,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<InitializeApp langCode={langCode}>
|
||||
<InitializeApp langCode={langCode} theme={theme}>
|
||||
<Provider unstable_createStore={() => jotaiStore} scope={jotaiScope}>
|
||||
<App
|
||||
onChange={onChange}
|
||||
|
@ -88,21 +88,42 @@ describe("<Excalidraw/>", () => {
|
||||
});
|
||||
|
||||
describe("Test theme prop", () => {
|
||||
it('should show the dark mode toggle when the theme prop is "undefined"', async () => {
|
||||
it("should show the theme toggle by default", async () => {
|
||||
const { container } = await render(<Excalidraw />);
|
||||
expect(h.state.theme).toBe(THEME.LIGHT);
|
||||
|
||||
const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
|
||||
|
||||
expect(darkModeToggle).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not show the dark mode toggle when the theme prop is not "undefined"', async () => {
|
||||
it("should not show theme toggle when the theme prop is defined", async () => {
|
||||
const { container } = await render(<Excalidraw theme="dark" />);
|
||||
expect(h.state.theme).toBe(THEME.DARK);
|
||||
|
||||
expect(queryByTestId(container, "toggle-dark-mode")).toBe(null);
|
||||
});
|
||||
|
||||
it("should show theme mode toggle when `UIOptions.canvasActions.toggleTheme` is true", async () => {
|
||||
const { container } = await render(
|
||||
<Excalidraw
|
||||
theme={THEME.DARK}
|
||||
UIOptions={{ canvasActions: { toggleTheme: true } }}
|
||||
/>,
|
||||
);
|
||||
expect(h.state.theme).toBe(THEME.DARK);
|
||||
const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
|
||||
expect(darkModeToggle).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should not show theme toggle when `UIOptions.canvasActions.toggleTheme` is false", async () => {
|
||||
const { container } = await render(
|
||||
<Excalidraw
|
||||
UIOptions={{ canvasActions: { toggleTheme: false } }}
|
||||
theme={THEME.DARK}
|
||||
/>,
|
||||
);
|
||||
expect(h.state.theme).toBe(THEME.DARK);
|
||||
const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
|
||||
expect(darkModeToggle).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test name prop", () => {
|
||||
@ -214,7 +235,7 @@ describe("<Excalidraw/>", () => {
|
||||
|
||||
it("should hide the theme toggle when theme is false", async () => {
|
||||
const { container } = await render(
|
||||
<Excalidraw UIOptions={{ canvasActions: { theme: false } }} />,
|
||||
<Excalidraw UIOptions={{ canvasActions: { toggleTheme: false } }} />,
|
||||
);
|
||||
|
||||
expect(queryByTestId(container, "toggle-dark-mode")).toBeNull();
|
||||
|
@ -344,13 +344,17 @@ export type ExportOpts = {
|
||||
) => JSX.Element;
|
||||
};
|
||||
|
||||
// NOTE at the moment, if action name coressponds to canvasAction prop, its
|
||||
// truthiness value will determine whether the action is rendered or not
|
||||
// (see manager renderAction). We also override canvasAction values in
|
||||
// excalidraw package index.tsx.
|
||||
type CanvasActions = {
|
||||
changeViewBackgroundColor?: boolean;
|
||||
clearCanvas?: boolean;
|
||||
export?: false | ExportOpts;
|
||||
loadScene?: boolean;
|
||||
saveToActiveFile?: boolean;
|
||||
theme?: boolean;
|
||||
toggleTheme?: boolean | null;
|
||||
saveAsImage?: boolean;
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user