🎉

EditorjsでDarkmodeに対応する

2022/08/05に公開

Next+MUIでダークモードが割といい感じにできることを知り早速実装したものの、Editorjsはダークモード的なスタイルがない。
と思っていたらリリースノートにEditorjs開発者用にダークモード実装したよ〜という記載があったのでソースコードを探しに行ったらあった。
https://github.com/tomohirohiratsuka/editor.js/blob/next/example/assets/demo.css

Nextでcssmoduleを使って実装

ArticleEditor.tsx
import { useEffect, useRef } from "react";

import EditorJS, { API, EditorConfig, OutputData } from "@editorjs/editorjs";
import { useTheme } from "@mui/material/styles";
import useId from "@mui/utils/useId";

import styles from "./ArticleEditor.module.css";

import { EditorTools, i18n } from "@constants/EditorTools";
import useLocale from "@hooks/useLocale";

type ArticleEditorProps = {
  defaultValue?: OutputData;
  placeholder?: EditorConfig["placeholder"];
  readOnly?: EditorConfig["readOnly"];
  minHeight?: EditorConfig["minHeight"];
  autofocus?: EditorConfig["autofocus"];
  hideToolbar?: EditorConfig["hideToolbar"];
  onReady?: () => void;
  onSave?: (data: OutputData) => void;
  onChange?: (api: API, event: CustomEvent) => void;
  onInitialize?: (editor: EditorJS) => void;
};

const ArticleEditor = ({
  defaultValue,
  placeholder,
  readOnly,
  minHeight,
  autofocus,
  hideToolbar,
  onReady,
  onChange,
  onSave,
  onInitialize,
}: ArticleEditorProps) => {
  const id = useId();
  const { locale } = useLocale();
  const editorJS = useRef<EditorJS | null>(null);
  const theme = useTheme();
  useEffect(() => {
    if (editorJS.current === null) {
      editorJS.current = new EditorJS({
        placeholder,
        readOnly,
        minHeight,
        autofocus,
        hideToolbar,
        holder: id,
        data: defaultValue,
        i18n: i18n(locale),
        tools: EditorTools(locale),
        onChange(api: API, event: CustomEvent) {
          if (readOnly) return;
          editorJS.current?.save().then((res) => {
            if (onSave) {
              onSave(res);
            }
          });
          if (onChange) {
            onChange(api, event);
          }
        },
        onReady() {
          if (onReady) {
            onReady();
          }
        },
      });
      if (onInitialize) {
        onInitialize(editorJS.current);
      }
    }
  }, []);
  return (
    <div
      id={id}
      className={
        theme.palette.mode === "dark"
          ? `${styles.wrapper} ${styles["dark-mode"]}`
          : ""
      }
    />
  );
};

ArticleEditor.defaultProps = {
  defaultValue: { blocks: [] },
  placeholder: "",
  readOnly: false,
  minHeight: 0,
  autofocus: false,
  hideToolbar: false,
  onReady: () => {},
  onChange: () => {},
  onSave: () => {},
  onInitialize: () => {},
};

export default ArticleEditor;
ArticleEditor.module.css
.wrapper {
    background: var(--color-bg-main);
    color: var(--color-text-main);
}

.dark-mode {
    --color-border-light: rgba(255, 255, 255, .08);
    --color-bg-main: #1c1e24;
    --color-text-main: #737886;
}

/**
 * Dark theme overrides
 */
.dark-mode img {
    opacity: 0.5;
}

.dark-mode .cdx-simple-image__picture--with-border,
.dark-mode .cdx-input {
    border-color: var(--color-border-light);
}

.dark-mode .ce-example__button {
    box-shadow: 0 24px 18px -14px rgba(4, 154, 255, 0.24);
}

.dark-mode .ce-example__output {
    background-color: #17191f;
}

.dark-mode .inline-code {
    background-color: rgba(53, 56, 68, 0.62);
    color: #727683;
}

.dark-mode a {
    color: #959ba8;
}

.dark-mode .ce-example__statusbar-toggler,
.dark-mode .ce-example__statusbar-button {
    background-color: #343842;
}

.dark-mode .ce-example__statusbar-toggler::before {
    transform: translateX(calc(var(--toggler-size) * 2.2 - var(--toggler-size)));
}

.dark-mode .ce-example__statusbar-toggler::after {
    content: '*';
    right: auto;
    left: 6px;
    top: 7px;
    color: #fff;
    box-shadow: none;
    font-size: 32px;
}

.dark-mode.show-block-boundaries .ce-block,
.dark-mode.show-block-boundaries .ce-block__content {
    box-shadow: 0 0 0 1px rgba(128, 144, 159, 0.09) inset;
}

.dark-mode.thin-mode .ce-example__content {
    border-color: var(--color-border-light);
}

.dark-mode .ce-example__statusbar-item:not(:last-of-type)::after {
    color: var(--color-border-light);
}

.dark-mode .ce-block--selected .ce-block__content,
.dark-mode ::selection {
    background-color: rgba(57, 68, 84, 0.57);
}

.dark-mode .ce-toolbox__button,
.dark-mode .ce-toolbar__settings-btn,
.dark-mode .ce-toolbar__plus {
    color: inherit;
}

.dark-mode .ce-stub {
    opacity: 0.3;
}

ce-toolbar__plusとかのCSSが一部当たらなくて若干困っているものの、ダークモードで完全に使えない状態から、キーボードであれば普通に使える状態にまではいけることがわかったのでOK。

Discussion