Просмотр исходного кода

Merge pull request #7057 from weseek/support/move-theme-into-external-package

support: Build preset themes within external package
Yuki Takei 3 лет назад
Родитель
Сommit
e0cc3a89e1
100 измененных файлов с 2966 добавлено и 1044 удалено
  1. 6 3
      .github/workflows/ci-app.yml
  2. 1 0
      package.json
  3. 1 0
      packages/app/docker/Dockerfile
  4. 2 1
      packages/app/package.json
  5. 1 1
      packages/app/src/client/util/toastr.ts
  6. 5 7
      packages/app/src/components/Admin/Customize/CustomizeThemeOptions.jsx
  7. 26 25
      packages/app/src/components/Admin/Customize/CustomizeThemeSetting.tsx
  8. 5 13
      packages/app/src/components/Layout/RawLayout.tsx
  9. 7 5
      packages/app/src/components/SlackNotification.module.scss
  10. 3 1
      packages/app/src/components/SlackNotification.tsx
  11. 0 32
      packages/app/src/components/Theme/ThemeAntarctic.tsx
  12. 0 8
      packages/app/src/components/Theme/ThemeBlackboard.tsx
  13. 0 32
      packages/app/src/components/Theme/ThemeChristmas.tsx
  14. 0 8
      packages/app/src/components/Theme/ThemeDefault.tsx
  15. 0 8
      packages/app/src/components/Theme/ThemeFireRed.tsx
  16. 0 8
      packages/app/src/components/Theme/ThemeFuture.tsx
  17. 0 32
      packages/app/src/components/Theme/ThemeHalloween.tsx
  18. 0 36
      packages/app/src/components/Theme/ThemeHufflepuff.tsx
  19. 0 32
      packages/app/src/components/Theme/ThemeIsland.tsx
  20. 0 8
      packages/app/src/components/Theme/ThemeJadeGreen.tsx
  21. 0 8
      packages/app/src/components/Theme/ThemeKibela.tsx
  22. 0 8
      packages/app/src/components/Theme/ThemeMonoBlue.tsx
  23. 0 8
      packages/app/src/components/Theme/ThemeNature.tsx
  24. 0 32
      packages/app/src/components/Theme/ThemeSpring.tsx
  25. 0 32
      packages/app/src/components/Theme/ThemeWood.tsx
  26. 0 36
      packages/app/src/components/Theme/utils/ThemeInjector.tsx
  27. 0 66
      packages/app/src/components/Theme/utils/ThemeProvider.tsx
  28. 2 2
      packages/app/src/components/User/SeenUserInfo.module.scss
  29. 4 0
      packages/app/src/interfaces/customize.ts
  30. 2 4
      packages/app/src/pages/_app.page.tsx
  31. 46 5
      packages/app/src/pages/_document.page.tsx
  32. 0 3
      packages/app/src/pages/utils/commons.ts
  33. 5 0
      packages/app/src/server/crowi/express-init.js
  34. 12 40
      packages/app/src/server/routes/apiv3/customize-setting.js
  35. 31 4
      packages/app/src/stores/admin/customize.tsx
  36. 0 4
      packages/app/src/stores/context.tsx
  37. 0 59
      packages/app/src/styles/_mixins.scss
  38. 0 43
      packages/app/src/styles/_page.scss
  39. 0 32
      packages/app/src/styles/_vendor.scss
  40. 60 0
      packages/app/src/styles/atoms/mixins/_buttons.scss
  41. 34 3
      packages/app/src/styles/bootstrap/_apply.scss
  42. 36 58
      packages/app/src/styles/style-app.scss
  43. 0 149
      packages/app/src/styles/style-next.scss
  44. 0 15
      packages/app/src/styles/style-themes.scss
  45. 2 2
      packages/app/src/styles/theme/_apply-colors-dark.scss
  46. 2 2
      packages/app/src/styles/theme/_apply-colors-light.scss
  47. 1 1
      packages/app/src/styles/theme/_apply-colors.scss
  48. 1 1
      packages/app/src/styles/theme/_reboot-bootstrap-colors.scss
  49. 3 3
      packages/app/src/styles/theme/_reboot-bootstrap-theme-colors.scss
  50. 1 2
      packages/hackmd/package.json
  51. 2 0
      packages/preset-themes/.eslintignore
  52. 18 0
      packages/preset-themes/.eslintrc.js
  53. 1 0
      packages/preset-themes/.gitignore
  54. 1 0
      packages/preset-themes/README.md
  55. 27 0
      packages/preset-themes/package.json
  56. 0 0
      packages/preset-themes/public/images/antarctic/bg.svg
  57. 0 0
      packages/preset-themes/public/images/antarctic/topimage.svg
  58. 0 0
      packages/preset-themes/public/images/christmas/christmas-navbar.jpg
  59. 0 0
      packages/preset-themes/public/images/christmas/christmas.jpg
  60. 0 0
      packages/preset-themes/public/images/halloween/halloween-navbar.jpg
  61. 0 0
      packages/preset-themes/public/images/halloween/halloween.jpg
  62. 0 0
      packages/preset-themes/public/images/hufflepuff/badger-dark.jpg
  63. 0 0
      packages/preset-themes/public/images/hufflepuff/badger-light.png
  64. 0 0
      packages/preset-themes/public/images/hufflepuff/badger-light3.png
  65. 0 0
      packages/preset-themes/public/images/island/island.png
  66. 0 0
      packages/preset-themes/public/images/spring/spring.svg
  67. 0 0
      packages/preset-themes/public/images/spring/spring02.svg
  68. 0 0
      packages/preset-themes/public/images/wood/wood-navbar.jpg
  69. 0 0
      packages/preset-themes/public/images/wood/wood.jpg
  70. 2 0
      packages/preset-themes/src/index.ts
  71. 7 0
      packages/preset-themes/src/interfaces/manifest.ts
  72. 162 0
      packages/preset-themes/src/styles/_mixins.scss
  73. 40 0
      packages/preset-themes/src/styles/_variables.scss
  74. 13 59
      packages/preset-themes/src/styles/antarctic.scss
  75. 60 0
      packages/preset-themes/src/styles/atoms/mixins/_buttons.scss
  76. 5 0
      packages/preset-themes/src/styles/atoms/mixins/_code.scss
  77. 6 6
      packages/preset-themes/src/styles/blackboard.scss
  78. 40 0
      packages/preset-themes/src/styles/bootstrap/_apply.scss
  79. 9 0
      packages/preset-themes/src/styles/bootstrap/_init.scss
  80. 174 0
      packages/preset-themes/src/styles/bootstrap/_override.scss
  81. 163 0
      packages/preset-themes/src/styles/bootstrap/_variables.scss
  82. 12 6
      packages/preset-themes/src/styles/christmas.scss
  83. 7 7
      packages/preset-themes/src/styles/default.scss
  84. 9 9
      packages/preset-themes/src/styles/fire-red.scss
  85. 6 6
      packages/preset-themes/src/styles/future.scss
  86. 12 10
      packages/preset-themes/src/styles/halloween.scss
  87. 22 14
      packages/preset-themes/src/styles/hufflepuff.scss
  88. 13 8
      packages/preset-themes/src/styles/island.scss
  89. 9 9
      packages/preset-themes/src/styles/jade-green.scss
  90. 6 6
      packages/preset-themes/src/styles/kibela.scss
  91. 9 9
      packages/preset-themes/src/styles/mono-blue.scss
  92. 6 6
      packages/preset-themes/src/styles/nature.scss
  93. 14 7
      packages/preset-themes/src/styles/spring.scss
  94. 538 0
      packages/preset-themes/src/styles/theme/_apply-colors-dark.scss
  95. 430 0
      packages/preset-themes/src/styles/theme/_apply-colors-light.scss
  96. 697 0
      packages/preset-themes/src/styles/theme/_apply-colors.scss
  97. 29 0
      packages/preset-themes/src/styles/theme/_reboot-bootstrap-border-colors.scss
  98. 21 0
      packages/preset-themes/src/styles/theme/_reboot-bootstrap-buttons.scss
  99. 60 0
      packages/preset-themes/src/styles/theme/_reboot-bootstrap-colors.scss
  100. 37 0
      packages/preset-themes/src/styles/theme/_reboot-bootstrap-dropdown.scss

+ 6 - 3
.github/workflows/ci-app.yml

@@ -52,12 +52,15 @@ jobs:
         run: |
           npx lerna bootstrap -- --frozen-lockfile
 
-      - name: lerna run lint for plugins
+      - name: lerna run lint for dependent packages
         run: |
-          yarn lerna run lint --scope @growi/remark-*
+          yarn lerna run lint --scope @growi/codemirror-textlint --scope @growi/core --scope @growi/hackmd --scope @growi/preset-themes --scope @growi/remark-* --scope @growi/slack --scope @growi/ui
+      - name: build dependent packages
+        run: |
+          yarn lerna run build --scope @growi/preset-themes
       - name: lerna run lint for app
         run: |
-          yarn lerna run lint --scope @growi/app --scope @growi/codemirror-textlint --scope @growi/core --scope @growi/slack --scope @growi/ui
+          yarn lerna run lint --scope @growi/app
 
       - name: Slack Notification
         uses: weseek/ghaction-slack-notification@master

+ 1 - 0
package.json

@@ -89,6 +89,7 @@
     "ts-node": "^10.9.1",
     "tsconfig-paths": "^3.9.0",
     "typescript": "~4.7",
+    "vite": "^3.2.5",
     "yargs": "^17.3.1"
   },
   "engines": {

+ 1 - 0
packages/app/docker/Dockerfile

@@ -111,6 +111,7 @@ COPY packages/remark-drawio-plugin packages/remark-drawio-plugin
 COPY packages/remark-growi-plugin packages/remark-growi-plugin
 COPY packages/remark-lsx packages/remark-lsx
 COPY packages/hackmd packages/hackmd
+COPY packages/preset-themes packages/preset-themes
 
 # build
 RUN yarn lerna run build

+ 2 - 1
packages/app/package.json

@@ -46,7 +46,7 @@
     "openapi:v3": "yarn cross-env API_VERSION=3 yarn swagger-jsdoc -- \"src/server/routes/apiv3/**/*.js\" \"src/server/models/**/*.js\"",
     "openapi:v1": "yarn cross-env API_VERSION=1 yarn swagger-jsdoc -- \"src/server/*/*.js\" \"src/server/models/**/*.js\"",
     "resources:hackmd": "yarn lerna run build --scope=@growi/hackmd",
-    "resources:dummy": "true",
+    "resources:preset-themes": "yarn lerna run build --scope=@growi/preset-themes",
     "// resources:dl-resources": "yarn ts-node bin/download-cdn-resources.ts",
     "ts-node": "node -r ts-node/register -r tsconfig-paths/register -r dotenv-flow/config"
   },
@@ -67,6 +67,7 @@
     "@growi/codemirror-textlint": "^6.0.0-RC.9",
     "@growi/core": "^6.0.0-RC.9",
     "@growi/hackmd": "^6.0.0-RC.9",
+    "@growi/preset-themes": "^6.0.0-RC.9",
     "@growi/remark-drawio-plugin": "^6.0.0-RC.9",
     "@growi/remark-growi-plugin": "^6.0.0-RC.9",
     "@growi/remark-lsx": "^6.0.0-RC.9",

+ 1 - 1
packages/app/src/client/util/toastr.ts

@@ -5,7 +5,7 @@ import { toArrayIfNot } from '~/utils/array-utils';
 
 
 export const toastErrorOption: ToastOptions = {
-  autoClose: 0,
+  autoClose: false,
   closeButton: true,
 };
 export const toastError = (err: string | Error | Error[], option: ToastOptions = toastErrorOption): void => {

+ 5 - 7
packages/app/src/components/Admin/Customize/CustomizeThemeOptions.jsx

@@ -49,14 +49,13 @@ const uniqueTheme = [{
 
 const CustomizeThemeOptions = (props) => {
 
-  const { adminCustomizeContainer, currentTheme } = props;
-  const { currentLayout } = adminCustomizeContainer.state;
+  const { selectedTheme } = props;
 
   const { t } = useTranslation('admin');
 
 
   return (
-    <div id="themeOptions" className={`${currentLayout === 'kibela' && 'disabled'}`}>
+    <div id="themeOptions">
       {/* Light and Dark Themes */}
       <div>
         <h3>{t('customize_settings.theme_desc.light_and_dark')}</h3>
@@ -65,7 +64,7 @@ const CustomizeThemeOptions = (props) => {
             return (
               <ThemeColorBox
                 key={theme.name}
-                isSelected={currentTheme === theme.name}
+                isSelected={selectedTheme === theme.name}
                 onSelected={() => props.onSelected(theme.name)}
                 {...theme}
               />
@@ -81,7 +80,7 @@ const CustomizeThemeOptions = (props) => {
             return (
               <ThemeColorBox
                 key={theme.name}
-                isSelected={currentTheme === theme.name}
+                isSelected={selectedTheme === theme.name}
                 onSelected={() => props.onSelected(theme.name)}
                 {...theme}
               />
@@ -97,9 +96,8 @@ const CustomizeThemeOptions = (props) => {
 const CustomizeThemeOptionsWrapper = withUnstatedContainers(CustomizeThemeOptions, [AdminCustomizeContainer]);
 
 CustomizeThemeOptions.propTypes = {
-  adminCustomizeContainer: PropTypes.instanceOf(AdminCustomizeContainer).isRequired,
   onSelected: PropTypes.func,
-  currentTheme: PropTypes.string,
+  selectedTheme: PropTypes.string,
 };
 
 export default CustomizeThemeOptionsWrapper;

+ 26 - 25
packages/app/src/components/Admin/Customize/CustomizeThemeSetting.tsx

@@ -1,60 +1,61 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
-import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
-import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Put } from '~/client/util/apiv3-client';
-import { useGrowiTheme } from '~/stores/context';
+import { toastSuccess, toastError, toastWarning } from '~/client/util/toastr';
+import { useSWRxGrowiTheme } from '~/stores/admin/customize';
 
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
 import CustomizeThemeOptions from './CustomizeThemeOptions';
 
+// eslint-disable-next-line @typescript-eslint/ban-types
 type Props = {
-  adminCustomizeContainer: AdminCustomizeContainer
 }
 
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
 const CustomizeThemeSetting = (props: Props): JSX.Element => {
-
-  const { adminCustomizeContainer } = props;
-  const { data: currentTheme } = useGrowiTheme();
   const { t } = useTranslation();
 
-  const selectedHandler = useCallback((themeName) => {
-    // TODO: preview without using mutate of useGrowiTheme
-    // https://github.com/weseek/growi/pull/6860
-    // mutateGrowiTheme(themeName);
+  const { data: currentTheme, error } = useSWRxGrowiTheme();
+  const [selectedTheme, setSelectedTheme] = useState(currentTheme);
+
+  useEffect(() => {
+    setSelectedTheme(currentTheme);
+  }, [currentTheme]);
+
+  const selectedHandler = useCallback((themeName: string) => {
+    setSelectedTheme(themeName);
   }, []);
 
   const submitHandler = useCallback(async() => {
+    if (selectedTheme == null) {
+      toastWarning('The selected theme is undefined');
+      return;
+    }
+
     try {
-      if (currentTheme != null) {
-        await apiv3Put('/customize-setting/theme', {
-          themeType: currentTheme,
-        });
-      }
+      await apiv3Put('/customize-setting/theme', {
+        theme: selectedTheme,
+      });
 
       toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.theme'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);
     }
-  }, [currentTheme, t]);
+  }, [selectedTheme, t]);
 
   return (
     <div className="row">
       <div className="col-12">
         <h2 className="admin-setting-header">{t('admin:customize_settings.theme')}</h2>
-        <CustomizeThemeOptions onSelected={selectedHandler} currentTheme={currentTheme} />
-        <AdminUpdateButtonRow onClick={submitHandler} disabled={adminCustomizeContainer.state.retrieveError != null} />
+        <CustomizeThemeOptions onSelected={selectedHandler} selectedTheme={selectedTheme} />
+        <AdminUpdateButtonRow onClick={submitHandler} disabled={error != null} />
       </div>
     </div>
   );
 };
 
-const CustomizeThemeSettingWrapper = withUnstatedContainers(CustomizeThemeSetting, [AdminCustomizeContainer]);
-
-export default CustomizeThemeSettingWrapper;
+export default CustomizeThemeSetting;

+ 5 - 13
packages/app/src/components/Layout/RawLayout.tsx

@@ -4,14 +4,10 @@ import Head from 'next/head';
 import { ToastContainer } from 'react-toastify';
 import { useIsomorphicLayoutEffect } from 'usehooks-ts';
 
-import { useGrowiTheme } from '~/stores/context';
 import { ColorScheme, useNextThemes, NextThemesProvider } from '~/stores/use-next-themes';
 import loggerFactory from '~/utils/logger';
 
 
-import { ThemeProvider as GrowiThemeProvider } from '../Theme/utils/ThemeProvider';
-
-
 const logger = loggerFactory('growi:cli:RawLayout');
 
 
@@ -26,8 +22,6 @@ export const RawLayout = ({ children, title, className }: Props): JSX.Element =>
   if (className != null) {
     classNames.push(className);
   }
-  const { data: growiTheme } = useGrowiTheme();
-
   // get color scheme from next-themes
   const { resolvedTheme, resolvedThemeByAttributes } = useNextThemes();
 
@@ -36,7 +30,7 @@ export const RawLayout = ({ children, title, className }: Props): JSX.Element =>
   // set colorScheme in CSR
   useIsomorphicLayoutEffect(() => {
     setColorScheme(resolvedTheme ?? resolvedThemeByAttributes);
-  }, [resolvedTheme]);
+  }, [resolvedTheme, resolvedThemeByAttributes]);
 
   return (
     <>
@@ -46,12 +40,10 @@ export const RawLayout = ({ children, title, className }: Props): JSX.Element =>
         <meta name="viewport" content="initial-scale=1.0, width=device-width" />
       </Head>
       <NextThemesProvider>
-        <GrowiThemeProvider theme={growiTheme} colorScheme={colorScheme}>
-          <div className={classNames.join(' ')} data-color-scheme={colorScheme}>
-            {children}
-            <ToastContainer theme={colorScheme} />
-          </div>
-        </GrowiThemeProvider>
+        <div className={classNames.join(' ')} data-color-scheme={colorScheme}>
+          {children}
+          <ToastContainer theme={colorScheme} />
+        </div>
       </NextThemesProvider>
     </>
   );

+ 7 - 5
packages/app/src/styles/molecules/slack-notification.scss → packages/app/src/components/SlackNotification.module.scss

@@ -1,6 +1,8 @@
-.grw-slack-notification {
-  $input-height-slack: $custom-control-indicator-size * 1.5;
-  border-color: $gray-200;
+@use '~/styles/bootstrap/init' as bs;
+
+.grw-slack-notification :global {
+  $input-height-slack: bs.$custom-control-indicator-size * 1.5;
+  border-color: bs.$gray-200;
 
   border-style: solid;
   border-width: 1px;
@@ -9,10 +11,10 @@
   .form-control {
     height: $input-height-slack;
     border: transparent;
-    @include media-breakpoint-up(sm) {
+    @include bs.media-breakpoint-up(sm) {
       width: 130px;
     }
-    @include media-breakpoint-up(md) {
+    @include bs.media-breakpoint-up(md) {
       width: 180px;
     }
   }

+ 3 - 1
packages/app/src/components/SlackNotification.tsx

@@ -4,6 +4,8 @@ import React, { FC } from 'react';
 import { useTranslation } from 'next-i18next';
 import { PopoverBody, PopoverHeader, UncontrolledPopover } from 'reactstrap';
 
+import styles from './SlackNotification.module.scss';
+
 
 type SlackNotificationProps = {
   id: string;
@@ -35,7 +37,7 @@ export const SlackNotification: FC<SlackNotificationProps> = ({
 
 
   return (
-    <div className="grw-slack-notification w-100">
+    <div className={`grw-slack-notification ${styles['grw-slack-notification']} w-100`}>
       <div className="grw-input-group-slack-notification input-group extended-setting">
         <label className="input-group-addon">
           <div className="custom-control custom-switch custom-switch-lg custom-switch-slack">

+ 0 - 32
packages/app/src/components/Theme/ThemeAntarctic.tsx

@@ -1,32 +0,0 @@
-import Image from 'next/image';
-
-import { Themes } from '~/stores/use-next-themes';
-
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeAntarctic.module.scss';
-
-export const getBackgroundImageSrc = (colorScheme: Themes): string => {
-  switch (colorScheme) {
-    default:
-      return '/images/themes/antarctic/bg.svg';
-  }
-};
-
-type Props = {
-  children: JSX.Element,
-  colorScheme?: Themes,
-}
-
-const ThemeAntarctic = ({ children, colorScheme }: Props): JSX.Element => {
-  const bgImageNode = (
-    <>
-      {colorScheme != null && (
-        <Image alt='background image' src={getBackgroundImageSrc(colorScheme)} layout='fill' quality="100" />
-      )}
-    </>
-  );
-  return <ThemeInjector className="theme-antarctic" bgImageNode={bgImageNode}>{children}</ThemeInjector>;
-};
-
-export default ThemeAntarctic;

+ 0 - 8
packages/app/src/components/Theme/ThemeBlackboard.tsx

@@ -1,8 +0,0 @@
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeBlackboard.module.scss';
-
-const ThemeBlackboard = ({ children }: { children: JSX.Element }): JSX.Element => {
-  return <ThemeInjector className="theme-blackboard">{children}</ThemeInjector>;
-};
-export default ThemeBlackboard;

+ 0 - 32
packages/app/src/components/Theme/ThemeChristmas.tsx

@@ -1,32 +0,0 @@
-import Image from 'next/image';
-
-import { Themes } from '~/stores/use-next-themes';
-
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeChristmas.module.scss';
-
-export const getBackgroundImageSrc = (colorScheme: Themes): string => {
-  switch (colorScheme) {
-    default:
-      return '/images/themes/christmas/christmas.jpg';
-  }
-};
-
-type Props = {
-  children: JSX.Element,
-  colorScheme?: Themes,
-}
-
-const ThemeChristmas = ({ children, colorScheme }: Props): JSX.Element => {
-  const bgImageNode = (
-    <>
-      {colorScheme != null && (
-        <Image alt='background image' src={getBackgroundImageSrc(colorScheme)} layout='fill' quality="100" />
-      )}
-    </>
-  );
-  return <ThemeInjector className="theme-christmas" bgImageNode={bgImageNode}>{children}</ThemeInjector>;
-};
-
-export default ThemeChristmas;

+ 0 - 8
packages/app/src/components/Theme/ThemeDefault.tsx

@@ -1,8 +0,0 @@
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeDefault.module.scss';
-
-const ThemeDefault = ({ children }: { children: JSX.Element }): JSX.Element => {
-  return <ThemeInjector>{children}</ThemeInjector>;
-};
-export default ThemeDefault;

+ 0 - 8
packages/app/src/components/Theme/ThemeFireRed.tsx

@@ -1,8 +0,0 @@
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeFireRed.module.scss';
-
-const ThemeFireRed = ({ children }: { children: JSX.Element }): JSX.Element => {
-  return <ThemeInjector className="theme-fire-red">{children}</ThemeInjector>;
-};
-export default ThemeFireRed;

+ 0 - 8
packages/app/src/components/Theme/ThemeFuture.tsx

@@ -1,8 +0,0 @@
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeFuture.module.scss';
-
-const ThemeFuture = ({ children }: { children: JSX.Element }): JSX.Element => {
-  return <ThemeInjector className="theme-future">{children}</ThemeInjector>;
-};
-export default ThemeFuture;

+ 0 - 32
packages/app/src/components/Theme/ThemeHalloween.tsx

@@ -1,32 +0,0 @@
-import Image from 'next/image';
-
-import { Themes } from '~/stores/use-next-themes';
-
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeHalloween.module.scss';
-
-export const getBackgroundImageSrc = (colorScheme: Themes): string => {
-  switch (colorScheme) {
-    default:
-      return '/images/themes/halloween/halloween.jpg';
-  }
-};
-
-type Props = {
-  children: JSX.Element,
-  colorScheme?: Themes,
-}
-
-const ThemeHalloween = ({ children, colorScheme }: Props): JSX.Element => {
-  const bgImageNode = (
-    <>
-      {colorScheme != null && (
-        <Image alt='background image' src={getBackgroundImageSrc(colorScheme)} layout='fill' quality="100" />
-      )}
-    </>
-  );
-  return <ThemeInjector className="theme-halloween" bgImageNode={bgImageNode}>{children}</ThemeInjector>;
-};
-
-export default ThemeHalloween;

+ 0 - 36
packages/app/src/components/Theme/ThemeHufflepuff.tsx

@@ -1,36 +0,0 @@
-import Image from 'next/image';
-
-import { Themes } from '~/stores/use-next-themes';
-
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeHufflepuff.module.scss';
-
-export const getBackgroundImageSrc = (colorScheme: Themes): string => {
-  switch (colorScheme) {
-    case Themes.light:
-      return '/images/themes/hufflepuff/badger-light3.png';
-    case Themes.dark:
-      return '/images/themes/hufflepuff/badger-dark.jpg';
-    default:
-      return '/images/themes/hufflepuff/badger-light3.png';
-  }
-};
-
-type Props = {
-  children: JSX.Element,
-  colorScheme?: Themes,
-}
-
-const ThemeHufflepuff = ({ children, colorScheme }: Props): JSX.Element => {
-  const bgImageNode = (
-    <>
-      {colorScheme != null && (
-        <Image alt='background image' src={getBackgroundImageSrc(colorScheme)} layout='fill' quality="100" />
-      )}
-    </>
-  );
-  return <ThemeInjector className="theme-hufflepuff" bgImageNode={bgImageNode}>{children}</ThemeInjector>;
-};
-
-export default ThemeHufflepuff;

+ 0 - 32
packages/app/src/components/Theme/ThemeIsland.tsx

@@ -1,32 +0,0 @@
-import Image from 'next/image';
-
-import { Themes } from '~/stores/use-next-themes';
-
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeIsland.module.scss';
-
-export const getBackgroundImageSrc = (colorScheme: Themes): string => {
-  switch (colorScheme) {
-    default:
-      return '/images/themes/island/island.png';
-  }
-};
-
-type Props = {
-  children: JSX.Element,
-  colorScheme?: Themes,
-}
-
-const ThemeIsland = ({ children, colorScheme }: Props): JSX.Element => {
-  const bgImageNode = (
-    <>
-      {colorScheme != null && (
-        <Image alt='background image' src={getBackgroundImageSrc(colorScheme)} layout='fill' quality="100" />
-      )}
-    </>
-  );
-  return <ThemeInjector className="theme-island" bgImageNode={bgImageNode}>{children}</ThemeInjector>;
-};
-
-export default ThemeIsland;

+ 0 - 8
packages/app/src/components/Theme/ThemeJadeGreen.tsx

@@ -1,8 +0,0 @@
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeJadeGreen.module.scss';
-
-const ThemeJadeGreen = ({ children }: { children: JSX.Element }): JSX.Element => {
-  return <ThemeInjector className="theme-jade-green">{children}</ThemeInjector>;
-};
-export default ThemeJadeGreen;

+ 0 - 8
packages/app/src/components/Theme/ThemeKibela.tsx

@@ -1,8 +0,0 @@
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeKibela.module.scss';
-
-const ThemeKibela = ({ children }: { children: JSX.Element }): JSX.Element => {
-  return <ThemeInjector className="theme-kibela">{children}</ThemeInjector>;
-};
-export default ThemeKibela;

+ 0 - 8
packages/app/src/components/Theme/ThemeMonoBlue.tsx

@@ -1,8 +0,0 @@
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeMonoBlue.module.scss';
-
-const ThemeMonoBlue = ({ children }: { children: JSX.Element }): JSX.Element => {
-  return <ThemeInjector className="theme-mono-blue">{children}</ThemeInjector>;
-};
-export default ThemeMonoBlue;

+ 0 - 8
packages/app/src/components/Theme/ThemeNature.tsx

@@ -1,8 +0,0 @@
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeNature.module.scss';
-
-const ThemeNature = ({ children }: { children: JSX.Element }): JSX.Element => {
-  return <ThemeInjector className="theme-nature">{children}</ThemeInjector>;
-};
-export default ThemeNature;

+ 0 - 32
packages/app/src/components/Theme/ThemeSpring.tsx

@@ -1,32 +0,0 @@
-import Image from 'next/image';
-
-import { Themes } from '~/stores/use-next-themes';
-
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeSpring.module.scss';
-
-export const getBackgroundImageSrc = (colorScheme: Themes): string => {
-  switch (colorScheme) {
-    default:
-      return '/images/themes/spring/spring02.svg';
-  }
-};
-
-type Props = {
-  children: JSX.Element,
-  colorScheme?: Themes,
-}
-
-const ThemeSpring = ({ children, colorScheme }: Props): JSX.Element => {
-  const bgImageNode = (
-    <>
-      {colorScheme != null && (
-        <Image alt='background image' src={getBackgroundImageSrc(colorScheme)} layout='fill' quality="100" />
-      )}
-    </>
-  );
-  return <ThemeInjector className="theme-spring" bgImageNode={bgImageNode}>{children}</ThemeInjector>;
-};
-
-export default ThemeSpring;

+ 0 - 32
packages/app/src/components/Theme/ThemeWood.tsx

@@ -1,32 +0,0 @@
-import Image from 'next/image';
-
-import { Themes } from '~/stores/use-next-themes';
-
-import { ThemeInjector } from './utils/ThemeInjector';
-
-// import styles from './ThemeWood.module.scss';
-
-export const getBackgroundImageSrc = (colorScheme: Themes): string => {
-  switch (colorScheme) {
-    default:
-      return '/images/themes/wood/wood.jpg';
-  }
-};
-
-type Props = {
-  children: JSX.Element,
-  colorScheme?: Themes,
-}
-
-const ThemeWood = ({ children, colorScheme }: Props): JSX.Element => {
-  const bgImageNode = (
-    <>
-      {colorScheme != null && (
-        <Image alt='background image' src={getBackgroundImageSrc(colorScheme)} layout='fill' quality="100" />
-      )}
-    </>
-  );
-  return <ThemeInjector className="theme-wood" bgImageNode={bgImageNode}>{children}</ThemeInjector>;
-};
-
-export default ThemeWood;

+ 0 - 36
packages/app/src/components/Theme/utils/ThemeInjector.tsx

@@ -1,36 +0,0 @@
-
-import React from 'react';
-
-import { useIsomorphicLayoutEffect } from 'usehooks-ts';
-
-type Props = {
-  children: JSX.Element,
-  bodyTagClassName?: string,
-  className?: string,
-  bgImageNode?: React.ReactNode,
-}
-
-export const ThemeInjector = ({
-  children, bodyTagClassName, className: childrenClassName, bgImageNode,
-}: Props): JSX.Element => {
-  const className = `${children.props.className ?? ''} ${childrenClassName ?? ''}`;
-
-  // add class name to <body>
-  useIsomorphicLayoutEffect(() => {
-    if (bodyTagClassName != null) {
-      document.body.classList.add(bodyTagClassName);
-    }
-
-    // clean up
-    return () => {
-      if (bodyTagClassName != null) {
-        document.body.classList.remove(bodyTagClassName);
-      }
-    };
-  });
-
-  return React.cloneElement(children, { className }, [
-    <div key="grw-bg-image-wrapper" className="grw-bg-image-wrapper">{bgImageNode}</div>,
-    children.props.children,
-  ]);
-};

+ 0 - 66
packages/app/src/components/Theme/utils/ThemeProvider.tsx

@@ -1,66 +0,0 @@
-
-import React from 'react';
-
-import dynamic from 'next/dynamic';
-
-import { GrowiThemes } from '~/interfaces/theme';
-import { Themes } from '~/stores/use-next-themes';
-
-
-// const ThemeAntarctic = dynamic(() => import('../ThemeAntarctic'));
-// const ThemeBlackboard = dynamic(() => import('../ThemeBlackboard'));
-// const ThemeChristmas = dynamic(() => import('../ThemeChristmas'));
-const ThemeDefault = dynamic(() => import('../ThemeDefault'));
-// const ThemeFireRed = dynamic(() => import('../ThemeFireRed'));
-// const ThemeFuture = dynamic(() => import('../ThemeFuture'));
-// const ThemeHalloween = dynamic(() => import('../ThemeHalloween'));
-// const ThemeHufflepuff = dynamic(() => import('../ThemeHufflepuff'));
-// const ThemeIsland = dynamic(() => import('../ThemeIsland'));
-// const ThemeJadeGreen = dynamic(() => import('../ThemeJadeGreen'));
-// const ThemeKibela = dynamic(() => import('../ThemeKibela'));
-// const ThemeMonoBlue = dynamic(() => import('../ThemeMonoBlue'));
-// const ThemeNature = dynamic(() => import('../ThemeNature'));
-// const ThemeSpring = dynamic(() => import('../ThemeSpring'));
-// const ThemeWood = dynamic(() => import('../ThemeWood'));
-
-
-type Props = {
-  children: JSX.Element,
-  theme?: GrowiThemes,
-  colorScheme?: Themes,
-}
-
-export const ThemeProvider = ({ theme, children, colorScheme }: Props): JSX.Element => {
-  switch (theme) {
-    // case GrowiThemes.ANTARCTIC:
-    //   return <ThemeAntarctic colorScheme={colorScheme}>{children}</ThemeAntarctic>;
-    // case GrowiThemes.BLACKBOARD:
-    //   return <ThemeBlackboard>{children}</ThemeBlackboard>;
-    // case GrowiThemes.CHRISTMAS:
-    //   return <ThemeChristmas colorScheme={colorScheme}>{children}</ThemeChristmas>;
-    // case GrowiThemes.FIRE_RED:
-    //   return <ThemeFireRed>{children}</ThemeFireRed>;
-    // case GrowiThemes.FUTURE:
-    //   return <ThemeFuture>{children}</ThemeFuture>;
-    // case GrowiThemes.HALLOWEEN:
-    //   return <ThemeHalloween colorScheme={colorScheme}>{children}</ThemeHalloween>;
-    // case GrowiThemes.HUFFLEPUFF:
-    //   return <ThemeHufflepuff colorScheme={colorScheme}>{children}</ThemeHufflepuff>;
-    // case GrowiThemes.ISLAND:
-    //   return <ThemeIsland colorScheme={colorScheme}>{children}</ThemeIsland>;
-    // case GrowiThemes.JADE_GREEN:
-    //   return <ThemeJadeGreen>{children}</ThemeJadeGreen>;
-    // case GrowiThemes.KIBELA:
-    //   return <ThemeKibela>{children}</ThemeKibela>;
-    // case GrowiThemes.MONO_BLUE:
-    //   return <ThemeMonoBlue>{children}</ThemeMonoBlue>;
-    // case GrowiThemes.NATURE:
-    //   return <ThemeNature>{children}</ThemeNature>;
-    // case GrowiThemes.SPRING:
-    //   return <ThemeSpring colorScheme={colorScheme}>{children}</ThemeSpring>;
-    // case GrowiThemes.WOOD:
-    //   return <ThemeWood colorScheme={colorScheme}>{children}</ThemeWood>;
-    default:
-      return <ThemeDefault>{children}</ThemeDefault>;
-  }
-};

+ 2 - 2
packages/app/src/components/User/SeenUserInfo.module.scss

@@ -1,12 +1,12 @@
 @use '~/styles/bootstrap/init' as bs;
-@use '~/styles/mixins';
+@use '~/styles/atoms/mixins/buttons' as mixins-buttons;
 
 .grw-seen-user-info :global {
   .btn.btn-seen-user {
     $color-seen-user: #549c79;
 
     @include bs.button-outline-variant($color-seen-user, $color-seen-user, rgba(lighten($color-seen-user, 10%), 0.15), rgba(lighten($color-seen-user, 10%), 0.5));
-    @include mixins.button-outline-svg-icon-variant($color-seen-user, $color-seen-user);
+    @include mixins-buttons.button-outline-svg-icon-variant($color-seen-user, $color-seen-user);
 
     &:not(:disabled):not(.disabled):active,
     &:not(:disabled):not(.disabled).active {

+ 4 - 0
packages/app/src/interfaces/customize.ts

@@ -1,3 +1,7 @@
 export type IResLayoutSetting = {
   isContainerFluid: boolean,
 };
+
+export type IResGrowiTheme = {
+  theme: string,
+}

+ 2 - 4
packages/app/src/pages/_app.page.tsx

@@ -10,7 +10,7 @@ import * as nextI18nConfig from '^/config/next-i18next.config';
 import { ActivatePluginService } from '~/client/services/activate-plugin';
 import { useI18nextHMR } from '~/services/i18next-hmr';
 import {
-  useAppTitle, useConfidential, useGrowiTheme, useGrowiVersion, useSiteUrl, useCustomizedLogoSrc,
+  useAppTitle, useConfidential, useGrowiVersion, useSiteUrl, useCustomizedLogoSrc,
 } from '~/stores/context';
 import { SWRConfigValue, swrGlobalConfiguration } from '~/utils/swr-utils';
 
@@ -18,8 +18,7 @@ import { SWRConfigValue, swrGlobalConfiguration } from '~/utils/swr-utils';
 import { CommonProps } from './utils/commons';
 import { registerTransformerForObjectId } from './utils/objectid-transformer';
 
-import '~/styles/style-next.scss';
-import '~/styles/style-themes.scss';
+import '~/styles/style-app.scss';
 
 
 const isDev = process.env.NODE_ENV === 'development';
@@ -56,7 +55,6 @@ function GrowiApp({ Component, pageProps }: GrowiAppProps): JSX.Element {
   useAppTitle(commonPageProps.appTitle);
   useSiteUrl(commonPageProps.siteUrl);
   useConfidential(commonPageProps.confidential);
-  useGrowiTheme(commonPageProps.theme);
   useGrowiVersion(commonPageProps.growiVersion);
   useCustomizedLogoSrc(commonPageProps.customizedLogoSrc);
 

+ 46 - 5
packages/app/src/pages/_document.page.tsx

@@ -1,6 +1,8 @@
 /* eslint-disable @next/next/google-font-display */
 import React from 'react';
 
+import type { PresetThemesManifest } from '@growi/preset-themes';
+import { getManifestKeyFromTheme } from '@growi/preset-themes';
 import mongoose from 'mongoose';
 import Document, {
   DocumentContext, DocumentInitialProps,
@@ -8,13 +10,39 @@ import Document, {
 } from 'next/document';
 
 import { ActivatePluginService, GrowiPluginManifestEntries } from '~/client/services/activate-plugin';
-import { CrowiRequest } from '~/interfaces/crowi-request';
+import type { CrowiRequest } from '~/interfaces/crowi-request';
 import { GrowiPlugin, GrowiPluginResourceType } from '~/interfaces/plugin';
+import type { GrowiThemes } from '~/interfaces/theme';
+import loggerFactory from '~/utils/logger';
+
+const logger = loggerFactory('growi:page:_document');
+
+type HeadersForPresetThemesProps = {
+  theme: GrowiThemes,
+  manifest: PresetThemesManifest,
+}
+const HeadersForPresetThemes = (props: HeadersForPresetThemesProps): JSX.Element => {
+  const { theme, manifest } = props;
+
+  let themeKey = getManifestKeyFromTheme(theme);
+  if (!(themeKey in manifest)) {
+    logger.warn(`The key for '${theme} does not exist in preset-themes manifest`);
+    themeKey = getManifestKeyFromTheme('default');
+  }
+  const href = `/static/preset-themes/${manifest[themeKey].file}`; // configured by express.static
+
+  const elements: JSX.Element[] = [];
+
+  elements.push(
+    <link rel="stylesheet" key={`link_preset-themes-${theme}`} href={href} />,
+  );
+
+  return <>{elements}</>;
+};
 
 type HeadersForGrowiPluginProps = {
   pluginManifestEntries: GrowiPluginManifestEntries;
 }
-
 const HeadersForGrowiPlugin = (props: HeadersForGrowiPluginProps): JSX.Element => {
   const { pluginManifestEntries } = props;
 
@@ -48,7 +76,9 @@ const HeadersForGrowiPlugin = (props: HeadersForGrowiPluginProps): JSX.Element =
 };
 
 interface GrowiDocumentProps {
+  theme: GrowiThemes,
   customCss: string;
+  presetThemesManifest: PresetThemesManifest,
   pluginManifestEntries: GrowiPluginManifestEntries;
 }
 declare type GrowiDocumentInitialProps = DocumentInitialProps & GrowiDocumentProps;
@@ -58,18 +88,28 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
   static override async getInitialProps(ctx: DocumentContext): Promise<GrowiDocumentInitialProps> {
     const initialProps: DocumentInitialProps = await Document.getInitialProps(ctx);
     const { crowi } = ctx.req as CrowiRequest<any>;
-    const { customizeService } = crowi;
+    const { configManager, customizeService } = crowi;
+
+    const theme = configManager.getConfig('crowi', 'customize:theme');
     const customCss: string = customizeService.getCustomCss();
 
+    // import preset-themes manifest
+    const presetThemesManifest = await import('@growi/preset-themes/dist/themes/manifest.json').then(imported => imported.default);
+
+    // retrieve plugin manifests
     const GrowiPlugin = mongoose.model<GrowiPlugin>('GrowiPlugin');
     const growiPlugins = await GrowiPlugin.find({ isEnabled: true });
     const pluginManifestEntries: GrowiPluginManifestEntries = await ActivatePluginService.retrievePluginManifests(growiPlugins);
 
-    return { ...initialProps, customCss, pluginManifestEntries };
+    return {
+      ...initialProps, theme, customCss, presetThemesManifest, pluginManifestEntries,
+    };
   }
 
   override render(): JSX.Element {
-    const { customCss, pluginManifestEntries } = this.props;
+    const {
+      customCss, theme, presetThemesManifest, pluginManifestEntries,
+    } = this.props;
 
     return (
       <Html>
@@ -87,6 +127,7 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
           <link rel='preload' href="/static/fonts/Lato-Regular-latin-ext.woff2" as="font" type="font/woff2" />
           <link rel='preload' href="/static/fonts/Lato-Bold-latin.woff2" as="font" type="font/woff2" />
           <link rel='preload' href="/static/fonts/Lato-Bold-latin-ext.woff2" as="font" type="font/woff2" />
+          <HeadersForPresetThemes theme={theme} manifest={presetThemesManifest} />
           <HeadersForGrowiPlugin pluginManifestEntries={pluginManifestEntries} />
         </Head>
         <body>

+ 0 - 3
packages/app/src/pages/utils/commons.ts

@@ -7,7 +7,6 @@ import { SSRConfig, UserConfig } from 'next-i18next';
 import * as nextI18NextConfig from '^/config/next-i18next.config';
 
 import { CrowiRequest } from '~/interfaces/crowi-request';
-import { GrowiThemes } from '~/interfaces/theme';
 
 export type CommonProps = {
   namespacesRequired: string[], // i18next
@@ -15,7 +14,6 @@ export type CommonProps = {
   appTitle: string,
   siteUrl: string,
   confidential: string,
-  theme: GrowiThemes,
   customTitleTemplate: string,
   csrfToken: string,
   isContainerFluid: boolean,
@@ -55,7 +53,6 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
     appTitle: appService.getAppTitle(),
     siteUrl: configManager.getConfig('crowi', 'app:siteUrl'), // DON'T USE appService.getSiteUrl()
     confidential: appService.getAppConfidential() || '',
-    theme: configManager.getConfig('crowi', 'customize:theme'),
     customTitleTemplate: customizeService.customTitleTemplate,
     csrfToken: req.csrfToken(),
     isContainerFluid: configManager.getConfig('crowi', 'customize:isContainerFluid') ?? false,

+ 5 - 0
packages/app/src/server/crowi/express-init.js

@@ -1,9 +1,11 @@
+import { manifestPath as presetThemesManifestPath } from '@growi/preset-themes';
 import csrf from 'csurf';
 import mongoose from 'mongoose';
 
 import { i18n, localePath } from '^/config/next-i18next.config';
 
 import loggerFactory from '~/utils/logger';
+import { resolveFromRoot } from '~/utils/project-dir-utils';
 
 const logger = loggerFactory('growi:crowi:express-init');
 
@@ -115,6 +117,9 @@ module.exports = function(crowi, app) {
 
   const staticOption = (crowi.node_env === 'production') ? { maxAge: '30d' } : {};
   app.use(express.static(crowi.publicDir, staticOption));
+  app.use('/static/preset-themes', express.static(
+    resolveFromRoot(`../../node_modules/@growi/preset-themes/${path.dirname(presetThemesManifestPath)}`),
+  ));
   app.use('/plugins', express.static(path.resolve(__dirname, '../../../tmp/plugins')));
 
   app.engine('html', swig.renderFile);

+ 12 - 40
packages/app/src/server/routes/apiv3/customize-setting.js

@@ -108,11 +108,8 @@ module.exports = (crowi) => {
     layout: [
       body('isContainerFluid').isBoolean(),
     ],
-    themeAssetPath: [
-      query('themeName').isString(),
-    ],
     theme: [
-      body('themeType').isString(),
+      body('theme').isString(),
     ],
     sidebar: [
       body('isSidebarDrawerMode').isBoolean(),
@@ -175,7 +172,6 @@ module.exports = (crowi) => {
    */
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
     const customizeParams = {
-      themeType: await crowi.configManager.getConfig('crowi', 'customize:theme'),
       isEnabledTimeline: await crowi.configManager.getConfig('crowi', 'customize:isEnabledTimeline'),
       isEnabledAttachTitleHeader: await crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'),
       pageLimitationS: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationS'),
@@ -272,41 +268,17 @@ module.exports = (crowi) => {
     }
   });
 
-  /**
-   * @swagger
-   *
-   *    /customize-setting/theme/asset-path:
-   *      put:
-   *        tags: [CustomizeSetting]
-   *        operationId: getThemeAssetPath
-   *        summary: /customize-setting/theme/asset-path
-   *        description: Get theme asset path
-   *        parameters:
-   *          - name: themeName
-   *            in: query
-   *            required: true
-   *            schema:
-   *              type: string
-   *        responses:
-   *          200:
-   *            description: Succeeded to get theme asset path
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    assetPath:
-   *                      type: string
-   */
-  router.get('/theme/asset-path', loginRequiredStrictly, adminRequired, validator.themeAssetPath, apiV3FormValidator, async(req, res) => {
-    const { themeName } = req.query;
-
-    const webpackAssetKey = `styles/theme-${themeName}.css`;
-    const assetPath = res.locals.webpack_asset(webpackAssetKey);
+  router.get('/theme', loginRequiredStrictly, adminRequired, async(req, res) => {
 
-    if (assetPath == null) {
-      return res.apiv3Err(new ErrorV3(`The asset for '${webpackAssetKey}' is undefined.`, 'invalid-asset'));
+    try {
+      const theme = await crowi.configManager.getConfig('crowi', 'customize:theme');
+      return res.apiv3({ theme });
+    }
+    catch (err) {
+      const msg = 'Error occurred in getting theme';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'get-theme-failed'));
     }
-    return res.apiv3({ assetPath });
   });
 
   /**
@@ -334,13 +306,13 @@ module.exports = (crowi) => {
    */
   router.put('/theme', loginRequiredStrictly, adminRequired, addActivity, validator.theme, apiV3FormValidator, async(req, res) => {
     const requestParams = {
-      'customize:theme': req.body.themeType,
+      'customize:theme': req.body.theme,
     };
 
     try {
       await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
       const customizedParams = {
-        themeType: await crowi.configManager.getConfig('crowi', 'customize:theme'),
+        theme: await crowi.configManager.getConfig('crowi', 'customize:theme'),
       };
       const parameters = { action: SupportedAction.ACTION_ADMIN_THEME_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);

+ 31 - 4
packages/app/src/stores/admin/customize.tsx

@@ -1,10 +1,11 @@
 import { useCallback } from 'react';
 
-import useSWR, { SWRResponse } from 'swr';
+import { SWRResponse } from 'swr';
+import useSWRImmutable from 'swr/immutable';
 
 import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
-import { updateConfigMethodForAdmin } from '~/interfaces/admin';
-import { IResLayoutSetting } from '~/interfaces/customize';
+import type { updateConfigMethodForAdmin } from '~/interfaces/admin';
+import type { IResLayoutSetting, IResGrowiTheme } from '~/interfaces/customize';
 
 export const useSWRxLayoutSetting = (): SWRResponse<IResLayoutSetting, Error> & updateConfigMethodForAdmin<IResLayoutSetting> => {
 
@@ -13,7 +14,7 @@ export const useSWRxLayoutSetting = (): SWRResponse<IResLayoutSetting, Error> &
     return res.data;
   }, []);
 
-  const swrResponse = useSWR('/customize-setting/layout', fetcher);
+  const swrResponse = useSWRImmutable('/customize-setting/layout', fetcher);
 
   const update = useCallback(async(layoutSetting: IResLayoutSetting) => {
     await apiv3Put('/customize-setting/layout', layoutSetting);
@@ -25,3 +26,29 @@ export const useSWRxLayoutSetting = (): SWRResponse<IResLayoutSetting, Error> &
     update,
   };
 };
+
+export const useSWRxGrowiTheme = (): SWRResponse<string, Error> => {
+
+  const fetcher = useCallback(async() => {
+    const res = await apiv3Get<IResGrowiTheme>('/customize-setting/theme');
+    return res.data.theme;
+  }, []);
+
+  const swrResponse = useSWRImmutable('/customize-setting/theme', fetcher);
+
+  const update = async(theme: string) => {
+    await apiv3Put('/customize-setting/layout', { theme });
+    await swrResponse.mutate();
+    // The updateFn should be a promise or asynchronous function to handle the remote mutation
+    // it should return updated data. see: https://swr.vercel.app/docs/mutation#optimistic-updates
+    // Moreover, `async() => false` does not work since it's too fast to be calculated.
+    await swrResponse.mutate(new Promise(r => setTimeout(() => r(theme), 10)), { optimisticData: () => theme });
+  };
+
+  return Object.assign(
+    swrResponse,
+    {
+      update,
+    },
+  );
+};

+ 0 - 4
packages/app/src/stores/context.tsx

@@ -37,10 +37,6 @@ export const useConfidential = (initialData?: string): SWRResponse<string, Error
   return useContextSWR('confidential', initialData);
 };
 
-export const useGrowiTheme = (initialData?: GrowiThemes): SWRResponse<GrowiThemes, Error> => {
-  return useContextSWR('theme', initialData);
-};
-
 export const useCurrentUser = (initialData?: Nullable<IUser>): SWRResponse<Nullable<IUser>, Error> => {
   return useContextSWR<Nullable<IUser>, Error>('currentUser', initialData);
 };

+ 0 - 59
packages/app/src/styles/_mixins.scss

@@ -141,65 +141,6 @@
   }
 }
 
-@mixin button-svg-icon-variant($background, $hover-background: darken($background, 7.5%), $active-background: darken($background, 10%)) {
-  svg {
-    fill: color-yiq($background);
-  }
-
-  @include bs.hover() {
-    svg {
-      fill: color-yiq($hover-background);
-    }
-  }
-
-  &:focus,
-  &.focus {
-    svg {
-      fill: color-yiq($hover-background);
-    }
-  }
-
-  // Disabled comes first so active can properly restyle
-  &.disabled,
-  &:disabled {
-    svg {
-      fill: color-yiq($background);
-    }
-  }
-
-  &:not(:disabled):not(.disabled):active,
-  &:not(:disabled):not(.disabled).active,
-  .show > &.dropdown-toggle {
-    svg {
-      fill: color-yiq($active-background);
-    }
-  }
-}
-
-@mixin button-outline-svg-icon-variant($value, $color-hover: $value) {
-  svg {
-    fill: $value;
-  }
-  @include bs.hover() {
-    svg {
-      fill: $color-hover;
-    }
-  }
-  &.disabled,
-  &:disabled {
-    svg {
-      fill: $value;
-    }
-  }
-  &:not(:disabled):not(.disabled):active,
-  &:not(:disabled):not(.disabled).active,
-  .show > &.dropdown-toggle {
-    svg {
-      fill: $color-hover;
-    }
-  }
-}
-
 @mixin overlay-processing-style($additionalSelector, $contentFontSize: inherit, $contentPadding: inherit) {
   .overlay.#{$additionalSelector} {
     background: rgba(255, 255, 255, 0.5);

+ 0 - 43
packages/app/src/styles/_page.scss

@@ -1,43 +0,0 @@
-// // import diff2html styles
-// @import '~/diff2html/bundles/css/diff2html.min.css';
-
-/**
- * for table with handsontable modal button
- */
-.editable-with-handsontable {
-  position: relative;
-
-  .handsontable-modal-trigger {
-    position: absolute;
-    top: 11px;
-    right: 10px;
-    padding: 0;
-    font-size: 16px;
-    line-height: 1;
-    vertical-align: bottom;
-    background-color: transparent;
-    border: none;
-    opacity: 0;
-  }
-
-  .page-mobile & .handsontable-modal-trigger {
-    opacity: 0.3;
-  }
-
-  &:hover .handsontable-modal-trigger {
-    opacity: 1;
-  }
-}
-
-/**
- * for drawio with drawio iframe button
- */
-.editable-with-drawio {
-  .drawio-iframe-trigger {
-    top: 11px;
-    right: 10px;
-    z-index: 14;
-    font-size: 12px;
-    line-height: 1;
-  }
-}

+ 0 - 32
packages/app/src/styles/_vendor.scss

@@ -1,32 +0,0 @@
-// import bootstrap configurations
-@import '~bootstrap/scss/functions';
-@import '~bootstrap/scss/variables';
-@import '~bootstrap/scss/mixins';
-@import '~bootstrap/scss/utilities';
-@import '~bootstrap/scss/root';
-
-// increase specificity with ':root' for GROWI theming
-:root {
-  // import bootstrap
-  @import '~bootstrap/scss/bootstrap';
-  // import toastr styles
-  @import '~toastr/build/toastr';
-}
-
-// import react-bootstrap-typeahead
-@import '~react-bootstrap-typeahead/css/Typeahead';
-
-// import CodeMirror styles
-@import '~codemirror/lib/codemirror.css';
-@import '~codemirror/addon/hint/show-hint.css';
-@import '~codemirror/theme/elegant.css';
-@import '~codemirror/theme/eclipse.css';
-
-// import Handsontable styles
-@import '~handsontable/dist/handsontable.full.css';
-
-// import SimpleBar styles
-@import '~simplebar/dist/simplebar.min.css';
-
-// Emoji-mart style
-@import '~emoji-mart/css/emoji-mart.css';

+ 60 - 0
packages/app/src/styles/atoms/mixins/_buttons.scss

@@ -0,0 +1,60 @@
+@use '../../bootstrap/init' as bs;
+
+@mixin button-svg-icon-variant($background, $hover-background: darken($background, 7.5%), $active-background: darken($background, 10%)) {
+  svg {
+    fill: color-yiq($background);
+  }
+
+  @include bs.hover() {
+    svg {
+      fill: color-yiq($hover-background);
+    }
+  }
+
+  &:focus,
+  &.focus {
+    svg {
+      fill: color-yiq($hover-background);
+    }
+  }
+
+  // Disabled comes first so active can properly restyle
+  &.disabled,
+  &:disabled {
+    svg {
+      fill: color-yiq($background);
+    }
+  }
+
+  &:not(:disabled):not(.disabled):active,
+  &:not(:disabled):not(.disabled).active,
+  .show > &.dropdown-toggle {
+    svg {
+      fill: color-yiq($active-background);
+    }
+  }
+}
+
+@mixin button-outline-svg-icon-variant($value, $color-hover: $value) {
+  svg {
+    fill: $value;
+  }
+  @include bs.hover() {
+    svg {
+      fill: $color-hover;
+    }
+  }
+  &.disabled,
+  &:disabled {
+    svg {
+      fill: $value;
+    }
+  }
+  &:not(:disabled):not(.disabled):active,
+  &:not(:disabled):not(.disabled).active,
+  .show > &.dropdown-toggle {
+    svg {
+      fill: $color-hover;
+    }
+  }
+}

+ 34 - 3
packages/app/src/styles/bootstrap/_apply.scss

@@ -1,9 +1,40 @@
 @use 'init' as *;
 
-@import '~bootstrap/scss/utilities';
+// apply bootstrap
 @import '~bootstrap/scss/root';
+@import '~bootstrap/scss/reboot';
+@import '~bootstrap/scss/type';
+@import '~bootstrap/scss/images';
+@import '~bootstrap/scss/code';
+@import '~bootstrap/scss/grid';
+@import '~bootstrap/scss/tables';
+@import '~bootstrap/scss/forms';
+@import '~bootstrap/scss/buttons';
+@import '~bootstrap/scss/transitions';
+@import '~bootstrap/scss/dropdown';
+@import '~bootstrap/scss/button-group';
+@import '~bootstrap/scss/input-group';
+@import '~bootstrap/scss/custom-forms';
+@import '~bootstrap/scss/nav';
+@import '~bootstrap/scss/navbar';
+@import '~bootstrap/scss/card';
+@import '~bootstrap/scss/breadcrumb';
+@import '~bootstrap/scss/pagination';
+@import '~bootstrap/scss/badge';
+@import '~bootstrap/scss/jumbotron';
+@import '~bootstrap/scss/alert';
+@import '~bootstrap/scss/progress';
+@import '~bootstrap/scss/media';
+@import '~bootstrap/scss/list-group';
+@import '~bootstrap/scss/close';
+@import '~bootstrap/scss/toasts';
+@import '~bootstrap/scss/modal';
+@import '~bootstrap/scss/tooltip';
+@import '~bootstrap/scss/popover';
+@import '~bootstrap/scss/carousel';
+@import '~bootstrap/scss/spinners';
+@import '~bootstrap/scss/utilities';
+@import '~bootstrap/scss/print';
 
-// import bootstrap
-@import '~bootstrap/scss/bootstrap';
 // override
 @import './override';

+ 36 - 58
packages/app/src/styles/style-app.scss

@@ -1,81 +1,59 @@
-// import variables
-@import 'variables';
+@import './bootstrap/apply';
 
 @import 'mixins';
-@import 'override-bootstrap-variables';
 
-// vendor
-@import 'vendor';
+// react-bootstrap-typeahead
+@import '~react-bootstrap-typeahead/css/Typeahead';
+@import 'override-rbt';
 
-// override bootstrap
-@import 'override-bootstrap';
+// SimpleBar
+@import '~simplebar/dist/simplebar.min.css';
+@import 'override-simplebar';
 
-// override codemirror
-@import 'override-codemirror';
+// KaTeX
+@import '~katex/dist/katex.min';
 
-// override react-bootstrap-typeahead styles
-@import 'override-rbt';
+// icons
 
-// override simplebar-react styles
-@import 'override-simplebar';
+// DO NOT CHANGE THER OERDER OF font-awesome AND simple-line-icons.
+// font-familiy used in simple-line-icons has to be prioritized than the one used in font-awesome.
+@import '~font-awesome';
+@import '~simple-line-icons';
+@import '~material-icons/iconfont/filled';
+@import '~@icon/themify-icons/themify-icons';
 
 // atoms
 @import 'atoms/buttons';
-@import 'atoms/code';
-@import 'atoms/nav';
-@import 'atoms/pre';
 @import 'atoms/spinners';
 @import 'atoms/custom_control';
+@import 'atoms/code';
 
 // molecules
-@import 'molecules/copy-dropdown';
-@import 'molecules/page-editor-mode-manager';
-@import 'molecules/slack-notification';
-@import 'molecules/duplicated-paths-table.scss';
-
-// growi component
-@import 'admin';
-@import 'attachments';
-@import 'comment';
-@import 'comment_growi';
-@import 'drawio';
-@import 'create-page';
-@import 'draft';
-@import 'editor-attachment';
-@import 'editor-navbar';
-@import 'page-content-footer';
-// @import 'handsontable';
+@import 'molecules/toastr';
+// @import 'molecules/slack-notification';
+// @import 'molecules/duplicated-paths-table.scss';
+
+// organisms
+@import 'organisms/wiki';
+
+// // growi component
+// @import 'attachments';
+// @import 'comment';
+// @import 'comment_growi';
+// @import 'draft';
+@import 'editor';
+@import 'fonts';
 @import 'layout';
-@import 'login';
-@import 'me';
+// @import 'me';
 @import 'mirror_mode';
 @import 'modal';
-@import 'navbar';
-@import 'old-ios';
-@import 'on-edit';
-@import 'page-duplicate-modal';
-@import 'page_list';
-@import 'page-accessories-control';
-@import 'page-accessories-modal';
+// @import 'old-ios';
+// @import 'page-duplicate-modal';
 @import 'page-path';
-@import 'page-tree';
-@import 'page';
-@import 'page-presentation';
-@import 'page-history';
-@import 'recent-changes';
 @import 'search';
-@import 'shortcuts';
-@import 'sidebar';
-@import 'sidebar-wiki';
-@import 'subnav';
 @import 'tag';
-@import 'toc';
-@import 'user';
-@import 'staff_credit';
-@import 'waves';
-@import 'wiki';
-@import 'sharelink';
-@import 'linkedit-preview';
+// @import 'user';
+
 
 /*
  * for Guest User Mode

+ 0 - 149
packages/app/src/styles/style-next.scss

@@ -1,149 +0,0 @@
-@import './bootstrap/apply';
-
-@import 'mixins';
-
-// // override codemirror
-// @import 'override-codemirror';
-
-// react-bootstrap-typeahead
-@import '~react-bootstrap-typeahead/css/Typeahead';
-@import 'override-rbt';
-
-// SimpleBar
-@import '~simplebar/dist/simplebar.min.css';
-@import 'override-simplebar';
-
-// KaTeX
-@import '~katex/dist/katex.min';
-
-// icons
-
-// DO NOT CHANGE THER OERDER OF font-awesome AND simple-line-icons.
-// font-familiy used in simple-line-icons has to be prioritized than the one used in font-awesome.
-@import '~font-awesome';
-@import '~simple-line-icons';
-@import '~material-icons/iconfont/filled';
-@import '~@icon/themify-icons/themify-icons';
-
-// atoms
-@import 'atoms/buttons';
-@import 'atoms/spinners';
-@import 'atoms/custom_control';
-@import 'atoms/code';
-
-// molecules
-@import 'molecules/toastr';
-// @import 'molecules/copy-dropdown';
-// @import 'molecules/slack-notification';
-// @import 'molecules/duplicated-paths-table.scss';
-
-// organisms
-@import 'organisms/wiki';
-
-// // growi component
-// @import 'attachments';
-// @import 'comment';
-// @import 'comment_growi';
-// @import 'create-page';
-// @import 'draft';
-@import 'editor';
-@import 'fonts';
-// @import 'handsontable';
-@import 'layout';
-// @import 'me';
-@import 'mirror_mode';
-@import 'modal';
-// @import 'old-ios';
-// @import 'page-duplicate-modal';
-@import 'page-path';
-// @import 'page';
-@import 'search';
-@import 'tag';
-// @import 'user';
-
-
-/*
- * for Guest User Mode
- */
-// TODO: reactify and replace with `grw-not-available-for-guest`
-.dropdown-toggle.dropdown-toggle-disabled {
-  cursor: not-allowed;
-}
-
-// TODO: reactify and replace with `grw-not-available-for-guest`
-.edit-button.edit-button-disabled {
-  cursor: not-allowed;
-}
-
-.grw-not-available-for-guest {
-  cursor: not-allowed !important;
-}
-
-/*
- * Helper Classes
- */
-
-.mw-0 {
-  min-width: 0;
-}
-
-.flex-basis-0 {
-  flex-basis: 0;
-}
-
-.picture {
-  width: 24px;
-  height: 24px;
-
-  // size list
-  &.picture-lg {
-    width: 48px;
-    height: 48px;
-  }
-
-  &.picture-md {
-    width: 24px;
-    height: 24px;
-  }
-
-  &.picture-sm {
-    width: 18px;
-    height: 18px;
-  }
-
-  &.picture-xs {
-    width: 14px;
-    height: 14px;
-  }
-}
-
-// transplant from FontAwesome
-.icon-fw {
-  display: inline-block;
-  width: 1.4em;
-  text-align: left;
-}
-
-.cmd-key.mac {
-  &:after {
-    content: '⌘';
-  }
-}
-
-.cmd-key.win {
-  &:after {
-    content: 'Ctrl';
-  }
-}
-
-.grw-page-control-dropdown-item {
-  display: flex !important;
-  align-items: center;
-
-  .grw-page-control-dropdown-icon {
-    display: flex;
-    justify-content: center;
-    width: 25px;
-  }
-
-}

+ 0 - 15
packages/app/src/styles/style-themes.scss

@@ -1,15 +0,0 @@
-// @import '~/components/Theme/ThemeAntarctic.global.scss';
-// @import '~/components/Theme/ThemeBlackboard.global.scss';
-// @import '~/components/Theme/ThemeChristmas.global.scss';
-@import '~/components/Theme/ThemeDefault.global.scss';
-// @import '~/components/Theme/ThemeFireRed.global.scss';
-// @import '~/components/Theme/ThemeFuture.global.scss';
-// @import '~/components/Theme/ThemeHalloween.global.scss';
-// @import '~/components/Theme/ThemeHufflepuff.global.scss';
-// @import '~/components/Theme/ThemeIsland.global.scss';
-// @import '~/components/Theme/ThemeJadeGreen.global.scss';
-// @import '~/components/Theme/ThemeKibela.global.scss';
-// @import '~/components/Theme/ThemeMonoBlue.global.scss';
-// @import '~/components/Theme/ThemeNature.global.scss';
-// @import '~/components/Theme/ThemeSpring.global.scss';
-// @import '~/components/Theme/ThemeWood.global.scss';

+ 2 - 2
packages/app/src/styles/theme/_apply-colors-dark.scss

@@ -1,6 +1,6 @@
 @use '../variables' as var;
 @use '../bootstrap/init' as *;
-@use '../mixins';
+@use '../atoms/mixins/buttons' as mixins-buttons;
 @use './mixins/count-badge';
 
 // determine optional variables
@@ -301,7 +301,7 @@ ul.pagination {
       lighten($bgcolor-sidebar-context, 24%)
     );
     .grw-pagetree-triangle-btn {
-      @include mixins.button-outline-svg-icon-variant($secondary, $gray-200);
+      @include mixins-buttons.button-outline-svg-icon-variant($secondary, $gray-200);
     }
     .btn-page-item-control {
       @include button-outline-variant($gray-500, $gray-500, $secondary, transparent);

+ 2 - 2
packages/app/src/styles/theme/_apply-colors-light.scss

@@ -1,6 +1,6 @@
 @use '../variables' as var;
 @use '../bootstrap/init' as *;
-@use '../mixins';
+@use '../atoms/mixins/buttons' as mixins-buttons;
 @use './mixins/count-badge';
 
 // determine optional variables
@@ -201,7 +201,7 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
       darken($bgcolor-sidebar-context, 24%)
     );
     .grw-pagetree-triangle-btn {
-      @include mixins.button-outline-svg-icon-variant($gray-400, $primary);
+      @include mixins-buttons.button-outline-svg-icon-variant($gray-400, $primary);
     }
   }
   .private-legacy-pages-link {

+ 1 - 1
packages/app/src/styles/theme/_apply-colors.scss

@@ -570,7 +570,7 @@ body.editing-sidebar {
 .page-comment-meta .page-comment-revision svg {
   fill: $color-link;
 
-  &:hover() {
+  &:hover {
     fill: $color-link-hover;
   }
 }

+ 1 - 1
packages/app/src/styles/theme/_reboot-bootstrap-colors.scss

@@ -14,7 +14,7 @@
 // 3. Set an explicit initial text-align value so that we can later use
 //    the `inherit` value on things like `<th>` elements.
 
-& {
+&, body {
   color: $body-color;
   background-color: $body-bg; // 2
 

+ 3 - 3
packages/app/src/styles/theme/_reboot-bootstrap-theme-colors.scss

@@ -1,5 +1,5 @@
 @use '../bootstrap/init' as *;
-@use '../mixins';
+@use '../atoms/mixins/buttons' as mixins-buttons;
 
 @each $color, $value in $theme-colors {
   @include bg-variant('.bg-#{$color}', $value);
@@ -18,7 +18,7 @@
 @each $color, $value in $theme-colors {
   .btn-#{$color} {
     @include button-variant($value, $value);
-    @include mixins.button-svg-icon-variant($value, $value);
+    @include mixins-buttons.button-svg-icon-variant($value, $value);
     box-shadow: none !important;
   }
 }
@@ -26,7 +26,7 @@
 @each $color, $value in $theme-colors {
   .btn-outline-#{$color} {
     @include button-outline-variant($value, $color-hover: $value, $active-background: rgba($value, 0.1), $active-border: $value);
-    @include mixins.button-outline-svg-icon-variant($value, $color-hover: $value);
+    @include mixins-buttons.button-outline-svg-icon-variant($value, $color-hover: $value);
 
     &:not(:disabled):not(.disabled):active,
     &:not(:disabled):not(.disabled).active {

+ 1 - 2
packages/hackmd/package.json

@@ -10,7 +10,6 @@
   "dependencies": {},
   "devDependencies": {
     "penpal": "^4.0.0",
-    "throttle-debounce": "^3.0.1",
-    "vite": "^3.1.0"
+    "throttle-debounce": "^3.0.1"
   }
 }

+ 2 - 0
packages/preset-themes/.eslintignore

@@ -0,0 +1,2 @@
+/dist/**
+*.d.ts

+ 18 - 0
packages/preset-themes/.eslintrc.js

@@ -0,0 +1,18 @@
+module.exports = {
+  extends: [
+    'weseek/react',
+    'weseek/typescript',
+  ],
+  env: {
+  },
+  globals: {
+  },
+  settings: {
+    // resolve path aliases by eslint-import-resolver-typescript
+    'import/resolver': {
+      typescript: {},
+    },
+  },
+  rules: {
+  },
+};

+ 1 - 0
packages/preset-themes/.gitignore

@@ -0,0 +1 @@
+/dist

+ 1 - 0
packages/preset-themes/README.md

@@ -0,0 +1 @@
+# GROWI Preset Themes

+ 27 - 0
packages/preset-themes/package.json

@@ -0,0 +1,27 @@
+{
+  "name": "@growi/preset-themes",
+  "description": "GROWI preset themes",
+  "version": "6.0.0-RC.9",
+  "license": "MIT",
+  "main": "dist/libs/index.js",
+  "files": ["dist"],
+  "scripts": {
+    "build": "yarn build:libs & yarn build:themes",
+    "build:w": "yarn build:libs -w & yarn build:themes -w",
+    "build:libs": "vite -c vite.libs.config.ts build",
+    "build:themes": "vite -c vite.themes.config.ts build",
+    "lint:eslint": "eslint --quiet \"**/*.{js,jsx,ts,tsx}\"",
+    "lint:styles": "stylelint src/**/*.scss",
+    "lint": "run-p lint:*",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
+    "react-innertext": "^1.1.5"
+  },
+  "devDependencies": {
+    "bootstrap": "^4.6.1",
+    "sass": "^1.55.0"
+  }
+}

+ 0 - 0
packages/app/public/images/themes/antarctic/bg.svg → packages/preset-themes/public/images/antarctic/bg.svg


+ 0 - 0
packages/app/public/images/themes/antarctic/topimage.svg → packages/preset-themes/public/images/antarctic/topimage.svg


+ 0 - 0
packages/app/public/images/themes/christmas/christmas-navbar.jpg → packages/preset-themes/public/images/christmas/christmas-navbar.jpg


+ 0 - 0
packages/app/public/images/themes/christmas/christmas.jpg → packages/preset-themes/public/images/christmas/christmas.jpg


+ 0 - 0
packages/app/public/images/themes/halloween/halloween-navbar.jpg → packages/preset-themes/public/images/halloween/halloween-navbar.jpg


+ 0 - 0
packages/app/public/images/themes/halloween/halloween.jpg → packages/preset-themes/public/images/halloween/halloween.jpg


+ 0 - 0
packages/app/public/images/themes/hufflepuff/badger-dark.jpg → packages/preset-themes/public/images/hufflepuff/badger-dark.jpg


+ 0 - 0
packages/app/public/images/themes/hufflepuff/badger-light.png → packages/preset-themes/public/images/hufflepuff/badger-light.png


+ 0 - 0
packages/app/public/images/themes/hufflepuff/badger-light3.png → packages/preset-themes/public/images/hufflepuff/badger-light3.png


+ 0 - 0
packages/app/public/images/themes/island/island.png → packages/preset-themes/public/images/island/island.png


+ 0 - 0
packages/app/public/images/themes/spring/spring.svg → packages/preset-themes/public/images/spring/spring.svg


+ 0 - 0
packages/app/public/images/themes/spring/spring02.svg → packages/preset-themes/public/images/spring/spring02.svg


+ 0 - 0
packages/app/public/images/themes/wood/wood-navbar.jpg → packages/preset-themes/public/images/wood/wood-navbar.jpg


+ 0 - 0
packages/app/public/images/themes/wood/wood.jpg → packages/preset-themes/public/images/wood/wood.jpg


+ 2 - 0
packages/preset-themes/src/index.ts

@@ -0,0 +1,2 @@
+export * from './interfaces/manifest';
+export * from './utils';

+ 7 - 0
packages/preset-themes/src/interfaces/manifest.ts

@@ -0,0 +1,7 @@
+export type PresetThemesManifest = {
+  [key: string]: {
+    file: string,
+    src: string,
+    isEntry?: boolean,
+  }
+}

+ 162 - 0
packages/preset-themes/src/styles/_mixins.scss

@@ -0,0 +1,162 @@
+@use './bootstrap/init' as bs;
+
+@mixin variable-font-size($basesize) {
+  font-size: $basesize * 0.6;
+
+  @include bs.media-breakpoint-only(sm) {
+    font-size: #{$basesize * 0.7};
+  }
+  @include bs.media-breakpoint-only(md) {
+    font-size: #{$basesize * 0.8};
+  }
+  @include bs.media-breakpoint-only(lg) {
+    font-size: #{$basesize * 0.9};
+  }
+  @include bs.media-breakpoint-up(xl) {
+    font-size: $basesize;
+  }
+}
+
+@mixin expand-editor($editor-margin-top) {
+  $header-plus-footer: $editor-margin-top + $grw-editor-navbar-bottom-height;
+
+  $editor-margin: $header-plus-footer //
+    + 25px //   add .btn-open-dropzone height
+    + 30px; //  add .navbar-editor height
+
+  .main {
+    width: 100%;
+    height: calc(100vh - #{$editor-margin-top});
+    margin-top: 0px !important;
+
+    .grw-container-convertible {
+      max-width: unset;
+      padding: 0;
+      margin: 0;
+    }
+
+    &,
+    .content-main,
+    .tab-content {
+      display: flex;
+      flex: 1;
+      flex-direction: column;
+
+      .tab-pane {
+        height: calc(100vh - #{$header-plus-footer});
+        min-height: calc(100vh - #{$header-plus-footer}); // for IE11
+      }
+
+      #page-editor {
+        // right(preview)
+        &,
+        & > .row,
+        .page-editor-preview-container,
+        .page-editor-preview-body {
+          height: calc(100vh - #{$header-plus-footer});
+          min-height: calc(100vh - #{$header-plus-footer}); // for IE11
+        }
+
+        // left(editor)
+        .page-editor-editor-container {
+          height: calc(100vh - #{$header-plus-footer});
+          min-height: calc(100vh - #{$header-plus-footer}); // for IE11
+
+          .react-codemirror2,
+          .CodeMirror,
+          .CodeMirror-scroll,
+          .textarea-editor {
+            height: calc(100vh - #{$editor-margin});
+          }
+        }
+      }
+
+      #page-editor-with-hackmd {
+        &,
+        .hackmd-preinit,
+        .hackmd-error,
+        #iframe-hackmd-container > iframe {
+          width: 100%;
+          height: calc(100vh - #{$header-plus-footer});
+          min-height: calc(100vh - #{$header-plus-footer}); // for IE11
+        }
+      }
+    }
+  }
+}
+
+@mixin apply-navigation-transition() {
+  transition-timing-function: cubic-bezier(0.25, 1, 0.5, 1);
+  transition-duration: 300ms;
+}
+
+@mixin border-vertical($beforeOrAfter, $borderLength, $zIndex: initial, $isBtnGroup: false) {
+  position: relative;
+  @if $isBtnGroup {
+    &:not(:first-child) {
+      margin-left: 0;
+      border-left: none;
+    }
+    &:not(:last-child) {
+      border-right: none;
+    }
+  }
+  &:not(:first-child) {
+    &::#{$beforeOrAfter} {
+      position: absolute;
+      top: calc((100% - #{$borderLength}) / 2);
+      left: 0;
+      z-index: $zIndex;
+      width: 100%;
+      height: $borderLength;
+      margin-left: -0.5px;
+      content: '';
+      border-left: 1px solid transparent;
+      transition: border-color 0.15s ease-in-out;
+    }
+  }
+}
+
+@keyframes fadeout {
+  100% {
+    opacity: 0;
+  }
+}
+
+@mixin blink-bgcolor($bgcolor) {
+  position: relative;
+  z-index: 1;
+
+  &::after {
+    position: absolute;
+    top: 15%;
+    left: 0;
+    z-index: -1;
+    width: 100%;
+    height: 70%;
+    content: '';
+    background-color: $bgcolor;
+    border-radius: 2px;
+    animation: fadeout 1s ease-in 1.5s forwards;
+  }
+}
+
+@mixin overlay-processing-style($additionalSelector, $contentFontSize: inherit, $contentPadding: inherit) {
+  .overlay.#{$additionalSelector} {
+    background: rgba(255, 255, 255, 0.5);
+    .overlay-content {
+      padding: $contentPadding;
+      font-size: $contentFontSize;
+      color: bs.$gray-700;
+      background: rgba(200, 200, 200, 0.5);
+    }
+  }
+}
+
+@mixin insertSimpleLineIcons($code) {
+  &:before {
+    margin-right: 0.2em;
+    font-family: 'simple-line-icons';
+    content: $code;
+  }
+}

+ 40 - 0
packages/preset-themes/src/styles/_variables.scss

@@ -0,0 +1,40 @@
+//== GROWI Official Color
+$growi-green: #74bc46;
+$growi-blue: #175fa5;
+
+//== Marker Color
+$grw-marker-yellow: #ff6;
+$grw-marker-red: #f6c;
+$grw-marker-blue: #6cf;
+$grw-marker-cyan: #6ff;
+$grw-marker-green: #6f6;
+
+$font-family-monospace-not-strictly: Monaco, Menlo, Consolas, 'Courier New', MeiryoKe_Gothic, monospace;
+
+//== Layout
+$grw-navbar-height: 52px;
+$grw-navbar-border-width: 3.3333px;
+// slightly larger than $zindex-sticky
+// https://getbootstrap.jp/docs/4.6/layout/overview/#z-index
+$grw-navbar-z-index: 1025;
+
+$grw-subnav-min-height: 95px;
+$grw-subnav-min-height-md: 115px;
+$grw-subnav-height-on-edit: 95px;
+$grw-subnav-height-lg-on-edit: 50px;
+
+$grw-subnav-search-preview-min-height: 90px;
+
+$grw-navbar-bottom-height: 48px;
+$grw-editor-navbar-bottom-height: 48px;
+
+$grw-sidebar-nav-width: 64px; // !!DO NOT CHANGE!! 'margin-left' for '.css-teprsg' is hardcoded
+
+$grw-logo-width: $grw-sidebar-nav-width;
+$grw-logomark-width: 36px;
+
+// fix tab width to 95 pixels
+// see also '_editor.scss'
+$grw-nav-main-left-tab-width: 95px;
+$grw-nav-main-left-tab-width-mobile: 50px;
+$grw-nav-main-tab-height: 42px;

+ 13 - 59
packages/app/src/components/Theme/ThemeAntarctic.global.scss → packages/preset-themes/src/styles/antarctic.scss

@@ -1,6 +1,6 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
 
 // == Define Bootstrap theme colors
 //
@@ -43,7 +43,7 @@ $accentcolor: #ffd700;
 
 //== Light Mode
 //
-:root .theme-antarctic {
+:root {
   $primary: $themecolor;
 
   // Background colors
@@ -109,8 +109,15 @@ $accentcolor: #ffd700;
   // admin theme box
   $color-theme-color-box: lighten($themecolor, 20%);
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-light';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-light';
+
+  &, body {
+    background-image: url('../images/antarctic/bg.svg');
+    background-attachment: fixed;
+    background-position: 50%;
+    background-size: cover;
+  }
 
   //Button
   .btn-group.grw-page-editor-mode-manager {
@@ -142,56 +149,3 @@ $accentcolor: #ffd700;
     }
   }
 }
-
-//== Dark Mode
-//
-// html[dark] {
-//   $primary: #d65a31;
-
-//   $basecolor: #222831;
-
-//   // Background colors
-//   $bgcolor-global: $basecolor;
-//   $bgcolor-navbar: #151515;
-//   $bgcolor-inline-code: darken($basecolor, 5%);
-//   $bgcolor-card: darken($basecolor, 5%);
-
-//   // Font colors
-//   $color-global: #eeeeee;
-//   $color-reversal: $gray-900;
-//   // $color-header: desaturate($primary, 20%);
-//   $color-link: $primary;
-//   $color-link-hover: lighten($color-link, 10%);
-//   $color-link-wiki: lighten($basecolor, 50%);
-//   $color-link-wiki-hover: darken($color-link-wiki, 5%);
-//   $color-link-nabvar: $color-global;
-//   $color-inline-code: #c7254e; // optional
-
-//   // List Group colors
-//   $color-list: $color-global;
-//   $bgcolor-list: $bgcolor-global;
-//   $color-list-active: $color-reversal;
-//   $bgcolor-list-active: $primary;
-//   $color-list-hover: $color-reversal;
-
-//   // Logo colors
-//   $bgcolor-logo: $bgcolor-navbar;
-//   $fillcolor-logo-mark: $gray-700;
-
-//   // Icon colors
-//   $color-editor-icons: darken($accentcolor, 15%);
-
-//   // Border colors
-//   $border-color-theme: black; // former: `$navbar-border: $gray-300;`
-
-//   // Dropdown colors
-//   $bgcolor-dropdown-link-active: $primary;
-
-//   // Sidebar
-//   $bgcolor-sidebar: $bgcolor-navbar;
-//   $color-sidebar-context: $color-global;
-//   $bgcolor-sidebar-context: lighten($bgcolor-navbar, 5%);
-
-//   @import 'apply-colors';
-//   @import 'apply-colors-dark';
-// }

+ 60 - 0
packages/preset-themes/src/styles/atoms/mixins/_buttons.scss

@@ -0,0 +1,60 @@
+@use '../../bootstrap/init' as bs;
+
+@mixin button-svg-icon-variant($background, $hover-background: darken($background, 7.5%), $active-background: darken($background, 10%)) {
+  svg {
+    fill: color-yiq($background);
+  }
+
+  @include bs.hover() {
+    svg {
+      fill: color-yiq($hover-background);
+    }
+  }
+
+  &:focus,
+  &.focus {
+    svg {
+      fill: color-yiq($hover-background);
+    }
+  }
+
+  // Disabled comes first so active can properly restyle
+  &.disabled,
+  &:disabled {
+    svg {
+      fill: color-yiq($background);
+    }
+  }
+
+  &:not(:disabled):not(.disabled):active,
+  &:not(:disabled):not(.disabled).active,
+  .show > &.dropdown-toggle {
+    svg {
+      fill: color-yiq($active-background);
+    }
+  }
+}
+
+@mixin button-outline-svg-icon-variant($value, $color-hover: $value) {
+  svg {
+    fill: $value;
+  }
+  @include bs.hover() {
+    svg {
+      fill: $color-hover;
+    }
+  }
+  &.disabled,
+  &:disabled {
+    svg {
+      fill: $value;
+    }
+  }
+  &:not(:disabled):not(.disabled):active,
+  &:not(:disabled):not(.disabled).active,
+  .show > &.dropdown-toggle {
+    svg {
+      fill: $color-hover;
+    }
+  }
+}

+ 5 - 0
packages/preset-themes/src/styles/atoms/mixins/_code.scss

@@ -0,0 +1,5 @@
+@mixin code-inline-color($color-inline-code,$bgcolor-inline-code, $bordercolor-inline-code) {
+  color: $color-inline-code;
+  background-color: $bgcolor-inline-code;
+  border-color: $bordercolor-inline-code;
+}

+ 6 - 6
packages/app/src/components/Theme/ThemeBlackboard.global.scss → packages/preset-themes/src/styles/blackboard.scss

@@ -1,8 +1,8 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
 
-:root .theme-blackboard {
+:root {
   // Theme colors
   $themecolor: #da8506;
   $themelight: #223729;
@@ -79,8 +79,8 @@
   // admin theme box
   $color-theme-color-box: $primary;
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-dark';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-dark';
 
   // Navs
   .nav-tabs {

+ 40 - 0
packages/preset-themes/src/styles/bootstrap/_apply.scss

@@ -0,0 +1,40 @@
+@use 'init' as *;
+
+// apply bootstrap
+@import 'bootstrap/scss/root';
+@import 'bootstrap/scss/reboot';
+@import 'bootstrap/scss/type';
+@import 'bootstrap/scss/images';
+@import 'bootstrap/scss/code';
+@import 'bootstrap/scss/grid';
+@import 'bootstrap/scss/tables';
+@import 'bootstrap/scss/forms';
+@import 'bootstrap/scss/buttons';
+@import 'bootstrap/scss/transitions';
+@import 'bootstrap/scss/dropdown';
+@import 'bootstrap/scss/button-group';
+@import 'bootstrap/scss/input-group';
+@import 'bootstrap/scss/custom-forms';
+@import 'bootstrap/scss/nav';
+@import 'bootstrap/scss/navbar';
+@import 'bootstrap/scss/card';
+@import 'bootstrap/scss/breadcrumb';
+@import 'bootstrap/scss/pagination';
+@import 'bootstrap/scss/badge';
+@import 'bootstrap/scss/jumbotron';
+@import 'bootstrap/scss/alert';
+@import 'bootstrap/scss/progress';
+@import 'bootstrap/scss/media';
+@import 'bootstrap/scss/list-group';
+@import 'bootstrap/scss/close';
+@import 'bootstrap/scss/toasts';
+@import 'bootstrap/scss/modal';
+@import 'bootstrap/scss/tooltip';
+@import 'bootstrap/scss/popover';
+@import 'bootstrap/scss/carousel';
+@import 'bootstrap/scss/spinners';
+@import 'bootstrap/scss/utilities';
+@import 'bootstrap/scss/print';
+
+// override
+@import './override';

+ 9 - 0
packages/preset-themes/src/styles/bootstrap/_init.scss

@@ -0,0 +1,9 @@
+@import './variables';
+
+@import 'bootstrap/scss/functions';
+@import 'bootstrap/scss/variables';
+
+// merge $colors to $theme-colors
+$theme-colors: map-merge($theme-colors, $colors);
+
+@import 'bootstrap/scss/mixins';

+ 174 - 0
packages/preset-themes/src/styles/bootstrap/_override.scss

@@ -0,0 +1,174 @@
+* {
+  outline: none !important;
+}
+
+.container,
+.container-sm,
+.container-md,
+.container-lg,
+.container-xl,
+.container-fluid {
+  @include media-breakpoint-down(xs) {
+    padding-right: 10px;
+    padding-left: 10px;
+  }
+  @include media-breakpoint-up(md) {
+    padding-right: 30px;
+    padding-left: 30px;
+  }
+}
+
+h1 {
+  font-size: 36px;
+  line-height: 48px;
+}
+
+h2 {
+  font-size: 24px;
+  line-height: 36px;
+}
+
+h3 {
+  font-size: 21px;
+  line-height: 30px;
+}
+
+h4 {
+  font-size: 18px;
+  line-height: 22px;
+}
+
+h5 {
+  font-size: 16px;
+  line-height: 18px;
+}
+
+h6 {
+  font-size: 12px;
+  line-height: 14px;
+}
+
+// Navs
+.nav-tabs {
+  .nav-item {
+    margin-right: 0.15rem;
+    a.active {
+      cursor: default;
+    }
+  }
+}
+
+// Custom Control
+.custom-control {
+  .custom-control-input,
+  .custom-control-input + .custom-control-label {
+    cursor: pointer;
+  }
+}
+
+// card (substitute panel of bootstrap3)
+.card {
+  margin-bottom: 20px;
+}
+
+.card-header {
+  font-weight: 700;
+  text-transform: none;
+}
+
+.card-header:first-child {
+}
+
+// Well (substitute Well of bootstrap3)
+.card.well {
+  min-height: 20px;
+  padding: $card-spacer-y $card-spacer-x;
+}
+
+// Dropdowns
+.dropdown-toggle {
+  &.btn.disabled {
+    pointer-events: auto;
+    cursor: not-allowed;
+  }
+
+  // hide caret
+  &.dropdown-toggle-no-caret::after {
+    content: none;
+  }
+}
+
+// Badges
+.badge {
+  @extend .badge-pill;
+}
+
+//Modals
+.modal-open {
+  width: 100%;
+  padding-right: 0 !important;
+}
+
+.modal-content {
+  box-shadow: 0 0.3rem 1rem rgba(0, 0, 0, 0.1);
+}
+
+.modal-header {
+  border-bottom: 1px solid #e5e5e5;
+}
+
+.modal-footer {
+  border-top: 1px solid #e5e5e5;
+}
+
+// When fading in the modal, animate it to slide down
+.modal.fade .modal-dialog {
+  @include transition($modal-transition);
+  transform: $modal-fade-transform;
+}
+
+.modal.show .modal-dialog {
+  transform: $modal-show-transform;
+}
+
+// When trying to close, animate focus to scale
+.modal.modal-static .modal-dialog {
+  transform: $modal-scale-transform;
+}
+
+// col-form-label (substitute for control-label of bootstrap3)
+.col-form-label {
+  text-align: right;
+}
+
+// label
+label {
+  // add with-no-font-weight class in case you do not want to apply font-weight 700 to label
+  :not(.with-no-font-weight) {
+    font-weight: 700;
+  }
+}
+
+// disabled button (reproduction from bootstrap3.)
+// see https://cccabinet.jpn.org/bootstrap4/components/buttons#disabled-state
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+  cursor: not-allowed;
+}
+
+// progress bar
+.progress {
+  margin-bottom: 18px;
+  overflow: hidden;
+}
+
+.text-break {
+  word-break: break-word;
+  overflow-wrap: break-word;
+}
+
+// prevent tooltip flickering (flashing) on hover
+.tooltip {
+  pointer-events: none;
+}

+ 163 - 0
packages/preset-themes/src/styles/bootstrap/_variables.scss

@@ -0,0 +1,163 @@
+@use '../variables';
+
+//
+// Variables
+// --------------------------------------------------
+
+//== Colors
+//
+$primary: variables.$growi-blue !default;
+$secondary: #6c757d !default;
+$info: #009fbb !default;
+$success: #00bb83 !default;
+$warning: #ffa32b !default;
+$danger: #ff0a54 !default;
+$light: #e4e7ea !default;
+$dark: #343a40 !default;
+$gray-50: lighten($light, 7%) !default;
+$gray-100: lighten($light, 4%) !default;
+$gray-200: $light !default;
+$gray-300: darken($light, 5%) !default;
+$gray-400: darken($light, 20%) !default;
+$gray-500: darken($light, 30%) !default;
+$gray-550: lighten($dark, 15%) !default;
+$gray-600: lighten($dark, 10%) !default;
+$gray-700: lighten($dark, 5%) !default;
+$gray-800: $dark !default;
+$gray-900: darken($dark, 5%) !default;
+$grays: (
+  '50': $gray-50,
+) !default;
+$red: #ff0a54 !default;
+
+// Options
+//
+// Quickly modify global styling by enabling or disabling optional features.
+
+$enable-shadows: true;
+
+// Grid breakpoints
+//
+// Define the minimum dimensions at which your layout will change,
+// adapting to different screen sizes, for use in media queries.
+
+$grid-breakpoints: (
+  xs: 0,
+  sm: 576px,
+  md: 768px,
+  lg: 992px,
+  xl: 1200px,
+  xxl: 1480px,
+);
+
+// Grid containers
+//
+// Define the maximum width of `.container` for different screen sizes.
+
+$container-max-widths: (
+  sm: 540px,
+  md: 720px,
+  lg: 960px,
+  xl: 1140px,
+  xxl: 1320px,
+);
+
+//== Typography
+//
+//## Font, line-height, and color for body text, headings, and more.
+$font-family-sans-serif: Lato, -apple-system, BlinkMacSystemFont, 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif;
+$font-family-serif: Georgia, 'Times New Roman', Times, serif;
+$font-family-monospace: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+$font-family-base: $font-family-sans-serif;
+
+$font-size-base: 0.875rem;  // 16px -> 14px
+$line-height-base: 1.42857;
+
+$blockquote-small-color: $gray-500;
+
+//== Components
+//
+$border-radius: 4px;
+$border-radius-sm: 0;
+$border-radius-lg: 8px;
+
+// Buttons
+//
+// For each of Bootstrap's buttons, define text, background, and border color.
+$btn-link-disabled-color: $gray-500;
+$btn-focus-box-shadow: none;
+$btn-active-box-shadow: none;
+
+//== Forms
+//
+$input-border-color: $gray-300;
+$input-placeholder-color: $gray-500;
+
+$custom-control-indicator-border-color: $gray-400;
+$custom-control-label-disabled-color: $gray-500;
+$custom-select-disabled-color: $gray-500;
+$custom-range-thumb-disabled-bg: $gray-400;
+
+//== Navs
+$nav-link-padding-y: 0.75rem;
+$nav-link-padding-x: 1rem;
+$nav-link-disabled-color: $gray-500;
+
+//== Navbar
+$navbar-padding-y: 0;
+$navbar-brand-padding-y: 0;
+$navbar-nav-link-padding-x: 1rem;
+
+//== Dropdowns
+$dropdown-border-radius: $border-radius-lg;
+$dropdown-link-disabled-color: $gray-500;
+$dropdown-header-color: $gray-500;
+$dropdown-box-shadow: 0 0.5rem 0.7rem rgba(black, 0.1);
+
+//== Popovers
+$popover-border-radius: $border-radius;
+$popover-box-shadow: 0 0.5rem 0.7rem rgba(black, 0.1);
+
+//== Pagination
+$pagination-disabled-color: $gray-500;
+
+//== Cards
+$card-spacer-y: 7px;
+$card-spacer-x: 15px;
+
+//== Toasts
+$toast-header-color: $gray-500;
+
+//== Modals
+$modal-content-border-width: 0;
+$modal-content-border-radius: $border-radius-lg;
+$modal-header-padding-y: 0.75rem;
+$modal-header-padding-x: 1rem;
+
+//== Alerts
+$alert-bg-level: -2;
+$alert-border-level: 0;
+$alert-color-level: -10;
+
+//== Progress bar
+$progress-height: 4px;
+$progress-bg: $gray-100;
+$progress-box-shadow: none;
+
+//== List group
+$list-group-disabled-color: $gray-500;
+
+//==  Figures
+$figure-caption-color: $gray-500;
+
+//==  Breadcrumbs
+$breadcrumb-divider-color: $gray-500;
+$breadcrumb-active-color: $gray-500;
+
+//== Code
+$pre-color: dummyinvalildcolor; // disable pre color specification with invalid value
+
+//== Custom Checkbox
+$custom-checkbox-indicator-border-radius: 0px;
+$custom-control-indicator-focus-box-shadow: none;
+$custom-control-indicator-size: 1.2rem;

+ 12 - 6
packages/app/src/components/Theme/ThemeChristmas.global.scss → packages/preset-themes/src/styles/christmas.scss

@@ -1,6 +1,6 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
 
 // == Define Bootstrap theme colors
 //
@@ -37,7 +37,7 @@ $color-link-wiki-hover: lighten($color-link-wiki, 15%);
 
 //== Light Mode
 //
-:root .theme-christmas {
+:root {
   $primary: #d3c665;
   // Background colors
   $bgcolor-card: $gray-50;
@@ -101,8 +101,14 @@ $color-link-wiki-hover: lighten($color-link-wiki, 15%);
   // admin theme box
   $color-theme-color-box: lighten($themecolor, 20%);
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-light';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-light';
+
+  &, body {
+    background-image: url('../images/christmas/christmas.jpg');
+    background-attachment: fixed;
+    background-size: cover;
+  }
 
   // change color of highlighted header in wiki (default: orange)
 

+ 7 - 7
packages/app/src/components/Theme/ThemeDefault.global.scss → packages/preset-themes/src/styles/default.scss

@@ -1,6 +1,6 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
 
 // == Define Bootstrap theme colors
 //
@@ -103,8 +103,8 @@
   // admin theme box
   $color-theme-color-box: lighten($primary, 20%);
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-light';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-light';
 
   // Button
   .btn-group.grw-page-editor-mode-manager {
@@ -200,8 +200,8 @@
   // admin theme box
   $color-theme-color-box: $primary;
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-dark';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-dark';
 
   //Button
   .btn-group.grw-page-editor-mode-manager {

+ 9 - 9
packages/app/src/components/Theme/ThemeFireRed.global.scss → packages/preset-themes/src/styles/fire-red.scss

@@ -1,8 +1,8 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
 
-:root[data-theme='light'] .theme-fire-red {
+:root[data-theme='light'] {
   // Theme colors
   $themecolor: #ea5532;
   $themelight: #ffffff;
@@ -71,8 +71,8 @@
   // admin theme box
   $color-theme-color-box: $primary;
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-light';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-light';
 
   // Navs {
   .nav-tabs {
@@ -95,7 +95,7 @@
   }
 }
 
-:root[data-theme='dark'] .theme-fire-red {
+:root[data-theme='dark'] {
   // Theme colors
   $themecolor: #ea5532;
   $themedark: #333333;
@@ -172,8 +172,8 @@
   // admin theme box
   $color-theme-color-box: $primary;
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-dark';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-dark';
 
   // Navs
   .nav-tabs {

+ 6 - 6
packages/app/src/components/Theme/ThemeFuture.global.scss → packages/preset-themes/src/styles/future.scss

@@ -1,12 +1,12 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
 
 $primary: #00b5b7;
 $themecolor: #16282d;
 $accentcolor: #00fff5;
 
-:root .theme-future {
+:root {
   // Background colors
   $bgcolor-global: $themecolor;
   $bgcolor-inline-code: #1f1f22; //optional
@@ -83,8 +83,8 @@ $accentcolor: #00fff5;
   // admin theme box
   $color-theme-color-box: lighten($primary, 20%);
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-dark';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-dark';
 
   //Button
   .btn-group.grw-page-editor-mode-manager {

+ 12 - 10
packages/app/src/components/Theme/ThemeHalloween.global.scss → packages/preset-themes/src/styles/halloween.scss

@@ -1,6 +1,6 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
 
 $themecolor: #aa4a04;
 $themelight: #f0f8ff;
@@ -23,14 +23,12 @@ $light: lighten($secondary, 10%);
 .growi:not(.login-page) {
   // add background-image
   .page-editor-preview-container {
-    background-image: url('/images/themes/halloween/halloween.jpg');
+    background-image: url('../images/halloween/halloween.jpg');
   }
 }
 
 
-//== Light Mode
-//
-:root .theme-halloween {
+:root {
   // Background colors
   $bgcolor-global: #050000;
   $bgcolor-inline-code: #1f1f22; //optional
@@ -101,11 +99,15 @@ $light: lighten($secondary, 10%);
   // admin theme box
   $color-theme-color-box: lighten($primary, 20%);
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-dark';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-dark';
+
+  &, body {
+    background-image: url('../images/halloween/halloween.jpg');
+  }
 
   .grw-navbar {
-    background-image: url('/images/themes/halloween/halloween-navbar.jpg') !important;
+    background-image: url('../images/halloween/halloween-navbar.jpg') !important;
   }
 
   // Button

+ 22 - 14
packages/app/src/components/Theme/ThemeHufflepuff.global.scss → packages/preset-themes/src/styles/hufflepuff.scss

@@ -1,6 +1,6 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
 
 // == Define Bootstrap theme colors
 //
@@ -20,7 +20,7 @@
 
 //== Light Mode
 //
-:root[data-theme='light'] .theme-hufflepuff {
+:root[data-theme='light'] {
   // Theme colors
   $themecolor: #eaab20;
   $themelight: #efe2cf;
@@ -92,10 +92,14 @@
   // admin theme box
   $color-theme-color-box: darken($primary, 5%);
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-light';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-light';
 
-  //Button
+  &, body {
+    background-image: url('../images/hufflepuff/badger-light3.png');
+  }
+
+  // Button
   .btn.btn-outline-primary {
     @include page-editor-mode-manager.btn-page-editor-mode-manager(darken($primary, 50%), darken($primary, 50%), lighten($primary, 20%));
   }
@@ -108,7 +112,7 @@
   .growi:not(.login-page) {
     // add background-image
     .page-editor-preview-container {
-      background-image: url('/images/themes/hufflepuff/badger-light3.png');
+      background-image: url('../images/themes/hufflepuff/badger-light3.png');
       background-attachment: fixed;
       background-position: bottom;
       background-size: cover;
@@ -119,7 +123,7 @@
   .nologin {
     #page-wrapper {
       background-color: $themelight;
-      background-image: url('/images/themes/hufflepuff/badger-light.png');
+      background-image: url('../images/themes/hufflepuff/badger-light.png');
       background-attachment: fixed;
       background-position: bottom;
       background-size: cover;
@@ -156,7 +160,7 @@
   }
 }
 
-:root[data-theme='dark'] .theme-hufflepuff {
+:root[data-theme='dark'] {
   // Theme colors
   $themecolor: #eaab20;
   $themedark: #3d3f38;
@@ -234,8 +238,12 @@
   // admin theme box
   $color-theme-color-box: $primary;
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-dark';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-dark';
+
+  &, body {
+    background-image: url('../images/hufflepuff/badger-dark.jpg');
+  }
 
   // Navs
   .nav-tabs {
@@ -275,7 +283,7 @@
   .growi:not(.login-page) {
     // add background-image
     .page-editor-preview-container {
-      background-image: url('/images/themes/hufflepuff/badger-dark.jpg');
+      background-image: url('../images/themes/hufflepuff/badger-dark.jpg');
       background-attachment: fixed;
       background-position: bottom;
       background-size: cover;
@@ -286,7 +294,7 @@
   .nologin {
     #page-wrapper {
       background-color: $themedark;
-      background-image: url('/images/themes/hufflepuff/badger-light.png');
+      background-image: url('../images/themes/hufflepuff/badger-light.png');
       background-attachment: fixed;
       background-position: bottom;
       background-size: cover;

+ 13 - 8
packages/app/src/components/Theme/ThemeIsland.global.scss → packages/preset-themes/src/styles/island.scss

@@ -1,12 +1,12 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
-@use '../../styles/mixins';
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
+@use './atoms/mixins/buttons' as mixins-buttons;
 
 $color-primary: #97cbc3;
 $color-themelight: rgba(183, 226, 219, 1);
 
-:root .theme-island {
+:root {
   $primary: $color-primary;
   // Background colors
   $bgcolor-card: $gray-50;
@@ -82,8 +82,13 @@ $color-themelight: rgba(183, 226, 219, 1);
   // admin theme box
   $color-theme-color-box: darken($primary, 15%);
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-light';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-light';
+
+  &, body {
+    background-image: url('../images/island/island.png');
+    background-attachment: fixed;
+  }
 
   .rbt-menu {
     background: lighten($color-themelight, 5%);
@@ -124,7 +129,7 @@ $color-themelight: rgba(183, 226, 219, 1);
     // Pagetree
     .grw-pagetree {
       .grw-pagetree-triangle-btn {
-        @include mixins.button-outline-svg-icon-variant($gray-400, $bgcolor-sidebar);
+        @include mixins-buttons.button-outline-svg-icon-variant($gray-400, $bgcolor-sidebar);
       }
     }
   }

+ 9 - 9
packages/app/src/components/Theme/ThemeJadeGreen.global.scss → packages/preset-themes/src/styles/jade-green.scss

@@ -1,8 +1,8 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
 
-:root[data-theme='light'] .theme-jade-green {
+:root[data-theme='light'] {
   // Theme colors
   $themecolor: #38b48b;
   $themelight: #ffffff;
@@ -71,8 +71,8 @@
   // admin theme box
   $color-theme-color-box: $primary;
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-light';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-light';
 
   // Navs {
   .nav-tabs {
@@ -95,7 +95,7 @@
   }
 }
 
-:root[data-theme='dark'] .theme-jade-green {
+:root[data-theme='dark'] {
   // Theme colors
   $themecolor: #38b48b;
   $themedark: #333333;
@@ -172,8 +172,8 @@
   // admin theme box
   $color-theme-color-box: $primary;
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-dark';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-dark';
 
   // Navs
   .nav-tabs {

+ 6 - 6
packages/app/src/components/Theme/ThemeKibela.global.scss → packages/preset-themes/src/styles/kibela.scss

@@ -1,6 +1,6 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
 
 $bgcolor-theme: rgb(18, 86, 163);
 $themelight: #f4f5f6;
@@ -8,7 +8,7 @@ $subthemecolor: rgb(88, 130, 250);
 $lightthemecolor: rgba(181, 203, 247, 0.61);
 
 // Light Mode
-:root .theme-kibela {
+:root {
   // Background colors
   $bgcolor-navbar: white;
   $bgcolor-navbar-active: $bgcolor-theme;
@@ -78,8 +78,8 @@ $lightthemecolor: rgba(181, 203, 247, 0.61);
   // Sidebar list group
   $bgcolor-sidebar-list-group: #fafbff; // optional
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-light';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-light';
 
   .main {
     .container,

+ 9 - 9
packages/app/src/components/Theme/ThemeMonoBlue.global.scss → packages/preset-themes/src/styles/mono-blue.scss

@@ -1,8 +1,8 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
 
-:root[data-theme='light'] .theme-mono-blue {
+:root[data-theme='light'] {
   // Theme colors
   $themecolor: #00587a;
   $themelight: #f7fbfd;
@@ -71,8 +71,8 @@
   // admin theme box
   $color-theme-color-box: lighten($primary, 20%);
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-light';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-light';
 
   // Navs {
   .nav-tabs {
@@ -95,7 +95,7 @@
   }
 }
 
-:root[data-theme='dark'] .theme-mono-blue {
+:root[data-theme='dark'] {
   // Theme colors
   $themecolor: #00587a;
   $themedark: #061f2f;
@@ -168,8 +168,8 @@
   // admin theme box
   $color-theme-color-box: $primary;
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-dark';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-dark';
 
   // Navs
   .nav-tabs {

+ 6 - 6
packages/app/src/components/Theme/ThemeNature.global.scss → packages/preset-themes/src/styles/nature.scss

@@ -1,6 +1,6 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
 
 // == Define Bootstrap theme colors
 //
@@ -36,7 +36,7 @@ $themecolor: #12b105;
 
 //== Light Mode
 //
-:root .theme-nature {
+:root {
   $primary: #460039;
   $light: $gray-100;
 
@@ -90,8 +90,8 @@ $themecolor: #12b105;
   // admin theme box
   $color-theme-color-box: lighten($primary, 20%);
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-light';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-light';
 
   // Search Top
   .grw-global-search {

+ 14 - 7
packages/app/src/components/Theme/ThemeSpring.global.scss → packages/preset-themes/src/styles/spring.scss

@@ -1,7 +1,7 @@
-@use '../../styles/variables' as *;
-@use '../../styles/bootstrap/variables' as *;
-@use '../../styles/theme/mixins/page-editor-mode-manager';
-@use '../../styles/bootstrap/init' as bs;
+@use './variables' as *;
+@use './bootstrap/variables' as *;
+@use './theme/mixins/page-editor-mode-manager';
+@use './bootstrap/init' as bs;
 
 // == Define Bootstrap theme colors
 //
@@ -27,7 +27,7 @@ $accentcolor: #e08dbc;
 
 //== Light Mode
 //
-:root .theme-spring {
+:root {
   $primary: $themecolor;
   $secondary: $accentcolor;
 
@@ -91,8 +91,15 @@ $accentcolor: #e08dbc;
   // admin theme box
   $color-theme-color-box: darken($primary, 20%);
 
-  @import '../../styles/theme/apply-colors';
-  @import '../../styles/theme/apply-colors-light';
+  @import './theme/apply-colors';
+  @import './theme/apply-colors-light';
+
+  &, body {
+    background-image: url('../images/spring/spring02.svg');
+    background-attachment: fixed;
+    background-position: bottom;
+    background-size: cover;
+  }
 
   //Button
   // Outline buttons are applyed the accent color to this spring theme cuz the primary is too light and it looks like unable to click them.

+ 538 - 0
packages/preset-themes/src/styles/theme/_apply-colors-dark.scss

@@ -0,0 +1,538 @@
+@use '../variables' as var;
+@use '../bootstrap/init' as *;
+@use '../atoms/mixins/buttons' as mixins-buttons;
+@use './mixins/count-badge';
+
+// determine optional variables
+$color-list: $color-global !default;
+$bgcolor-list: $bgcolor-global !default;
+$color-list-hover: $color-global !default;
+$bgcolor-list-hover: lighten($bgcolor-global, 8%) !default;
+$color-list-active: $color-reversal !default;
+$bgcolor-list-active: $primary !default;
+$bgcolor-subnav: lighten($bgcolor-global, 3%) !default;
+$color-table: white !default;
+$bgcolor-table: #343a40 !default;
+$border-color-table: lighten($bgcolor-table, 7.5%) !default;
+$color-table-hover: rgba(white, 0.075) !default;
+$bgcolor-table-hover: lighten($bgcolor-table, 7.5%) !default;
+$bgcolor-sidebar-list-group: $bgcolor-list !default;
+$color-tags: #949494 !default;
+$bgcolor-tags: $dark !default;
+$border-color-global: $gray-500 !default;
+$border-color-toc: $border-color-global !default;
+$color-dropdown: $color-global !default;
+$bgcolor-dropdown: $bgcolor-global !default;
+$color-dropdown-link: $color-global !default;
+$color-dropdown-link-hover: $light !default;
+$bgcolor-dropdown-link-hover: lighten($bgcolor-global, 15%) !default;
+$color-dropdown-link-active: $light !default;
+$bgcolor-dropdown-link-active: $primary !default;
+
+// override bootstrap variables
+$text-muted: $gray-550;
+$table-dark-color: $color-table;
+$table-dark-bg: $bgcolor-table;
+$table-dark-border-color: $border-color-table;
+$table-dark-hover-color: $color-table-hover;
+$table-dark-hover-bg: $bgcolor-table-hover;
+$border-color: $border-color-global;
+$dropdown-color: $color-dropdown;
+$dropdown-bg: $bgcolor-dropdown;
+$dropdown-link-color: $color-dropdown-link;
+$dropdown-link-hover-color: $color-dropdown-link-hover;
+$dropdown-link-hover-bg: $bgcolor-dropdown-link-hover;
+$dropdown-link-active-color: $color-dropdown-link-active;
+$dropdown-link-active-bg: $bgcolor-dropdown-link-active;
+
+@import './mixins/list-group';
+@import './reboot-bootstrap-text';
+@import './reboot-bootstrap-border-colors';
+@import './reboot-bootstrap-tables';
+@import './reboot-bootstrap-dropdown';
+
+// List Group
+@include override-list-group-item($color-list, $bgcolor-list, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
+
+/*
+  * Form
+  */
+input.form-control,
+select.form-control,
+select.custom-select,
+textarea.form-control {
+  color: $color-global;
+  background-color: darken($bgcolor-global, 5%);
+  border-color: $border-color-global;
+  &:focus {
+    background-color: $bgcolor-global;
+  }
+  // FIXME: accent color
+  // border: 1px solid darken($border, 30%);
+}
+
+.form-control[disabled],
+.form-control[readonly] {
+  color: lighten($color-global, 10%);
+  background-color: lighten($bgcolor-global, 5%);
+}
+
+.input-group > .input-group-prepend > .input-group-text {
+  color: theme-color('light');
+  background-color: theme-color('secondary');
+  border: 1px solid theme-color('secondary');
+  border-right: none;
+  &.text-muted {
+    color: theme-color('light') !important;
+  }
+}
+
+.input-group input {
+  border-color: $border-color-global;
+}
+
+label.custom-control-label::before {
+  background-color: darken($bgcolor-global, 5%);
+}
+
+/*
+ * Table
+ */
+.table {
+  @extend .table-dark;
+}
+
+/*
+ * Card
+ */
+.card:not([class*='bg-']):not(.well):not(.card-disabled) {
+  @extend .bg-dark;
+}
+
+// [TODO] GW-3219 modify common color of well in dark theme, then remove below css.
+.card.well {
+  border-color: $secondary;
+}
+
+.card.card-disabled {
+  background-color: lighten($dark, 10%);
+  border-color: $secondary;
+}
+
+/*
+ * Pagination
+ */
+ul.pagination {
+  li.page-item {
+    button.page-link {
+      @extend .btn-dark;
+    }
+  }
+}
+
+/*
+ * GROWI Login form
+ */
+.nologin {
+  // background color
+  $color-gradient: #3c465c;
+  background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
+    linear-gradient(135deg, darken(var.$growi-green, 30%) 10%, hsla(225, 95%, 50%, 0) 70%),
+    linear-gradient(225deg, darken(var.$growi-blue, 20%) 10%, hsla(140, 90%, 50%, 0) 80%),
+    linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
+
+  .noLogin-header {
+    background-color: rgba(black, 0.5);
+
+    .logo {
+      background-color: rgba(white, 0);
+      fill: rgba(white, 0.5);
+    }
+
+    h1 {
+      color: rgba(white, 0.5);
+    }
+  }
+
+  .noLogin-dialog {
+    background-color: rgba(black, 0.5);
+  }
+
+  .input-group {
+    .input-group-text {
+      color: darken(white, 30%);
+      background-color: rgba($gray-700, 0.7);
+    }
+
+    .form-control {
+      color: white;
+      background-color: rgba(#505050, 0.7);
+      box-shadow: unset;
+
+      &::placeholder {
+        color: darken(white, 30%);
+      }
+    }
+  }
+
+  .btn-fill {
+    .btn-label {
+      color: $gray-300;
+    }
+    .btn-label-text {
+      color: $gray-400;
+    }
+  }
+
+  .grw-external-auth-form {
+    border-color: gray !important;
+  }
+
+  .btn-external-auth-tab {
+    @extend .btn-dark;
+  }
+
+  // footer link text
+  .link-growi-org {
+    color: rgba(white, 0.4);
+
+    &:hover,
+    &.focus {
+      color: rgba(white, 0.7);
+
+      .growi {
+        color: darken(var.$growi-green, 5%);
+      }
+
+      .org {
+        color: darken(var.$growi-blue, 5%);
+      }
+    }
+  }
+}
+
+/*
+ * GROWI subnavigation
+ */
+.grw-drawer-toggler {
+  @extend .btn-dark;
+  color: $gray-400;
+}
+
+/*
+ * GROWI page list
+ */
+.page-list {
+  .page-list-ul {
+    > li {
+      > span.page-list-meta {
+        color: darken($color-global, 10%);
+      }
+    }
+  }
+
+  // List group
+  .list-group-item {
+    &.active {
+      background-color: lighten($bgcolor-global, 9%) !important;
+    }
+    .list-group-item-action:hover {
+      background-color: $bgcolor-list-hover;
+    }
+    .page-list-snippet {
+      color: darken($body-color, 10%);
+    }
+  }
+}
+
+/*
+ * GROWI ToC
+ */
+.revision-toc-content {
+  ::marker {
+    color: lighten($bgcolor-global, 30%);
+  }
+}
+
+/*
+ * GROWI subnavigation
+ */
+.grw-subnav {
+  background-color: $bgcolor-subnav;
+}
+
+.grw-subnav-fixed-container .grw-subnav {
+  background-color: rgba($bgcolor-subnav, 0.85);
+}
+
+.grw-page-editor-mode-manager {
+  .btn-outline-primary {
+    &:hover {
+      color: $primary;
+      background-color: $gray-700;
+    }
+  }
+}
+
+// Search drop down
+#search-typeahead-asynctypeahead {
+  background-color: $bgcolor-global;
+  .table {
+    background-color: transparent;
+  }
+}
+
+/*
+ * GROWI Sidebar
+ */
+.grw-sidebar {
+  // List
+  @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
+
+  // Pagetree
+  .grw-pagetree {
+    @include override-list-group-item-for-pagetree(
+      $color-sidebar-context,
+      lighten($bgcolor-sidebar-context, 8%),
+      lighten($bgcolor-sidebar-context, 15%),
+      darken($color-sidebar-context, 15%),
+      darken($color-sidebar-context, 10%),
+      lighten($bgcolor-sidebar-context, 18%),
+      lighten($bgcolor-sidebar-context, 24%)
+    );
+    .grw-pagetree-triangle-btn {
+      @include mixins-buttons.button-outline-svg-icon-variant($secondary, $gray-200);
+    }
+    .btn-page-item-control {
+      @include button-outline-variant($gray-500, $gray-500, $secondary, transparent);
+      @include hover() {
+        background-color: lighten($bgcolor-sidebar-context, 20%);
+      }
+      &:not(:disabled):not(.disabled):active,
+      &:not(:disabled):not(.disabled).active {
+        background-color: lighten($bgcolor-sidebar-context, 34%);
+      }
+      box-shadow: none !important;
+    }
+  }
+  .private-legacy-pages-link {
+    &:hover {
+      background: $bgcolor-list-hover;
+    }
+  }
+}
+
+.btn.btn-page-item-control {
+  @include button-outline-variant($gray-500, $gray-500, $secondary, transparent);
+  @include hover() {
+    background-color: $gray-700;
+  }
+  &:not(:disabled):not(.disabled):active,
+  &:not(:disabled):not(.disabled).active {
+    color: $gray-200;
+    background-color: $gray-600;
+  }
+  box-shadow: none !important;
+}
+
+/*
+ * Popover
+ */
+.popover {
+  background-color: $bgcolor-global;
+  border-color: $secondary;
+  .popover-header {
+    color: $white;
+    background-color: $secondary;
+    border-color: $secondary;
+  }
+  .popover-body {
+    color: inherit;
+  }
+
+  &.bs-popover-top .arrow {
+    &::before {
+      border-top-color: $secondary;
+    }
+
+    &::after {
+      border-top-color: $bgcolor-global;
+    }
+  }
+  &.bs-popover-bottom .arrow {
+    &::before {
+      border-bottom-color: $secondary;
+    }
+
+    &::after {
+      border-bottom-color: $bgcolor-global;
+    }
+  }
+  &.bs-popover-right .arrow {
+    &::before {
+      border-right-color: $secondary;
+    }
+
+    &::after {
+      border-right-color: $bgcolor-global;
+    }
+  }
+  &.bs-popover-left .arrow {
+    &::before {
+      border-left-color: $secondary;
+    }
+
+    &::after {
+      border-left-color: $bgcolor-global;
+    }
+  }
+}
+
+/*
+ * GROWI Grid Edit Modal
+ */
+.grw-grid-edit-preview {
+  background: $gray-900;
+}
+
+/*
+ * Slack
+ */
+.grw-slack-notification {
+  background-color: transparent;
+  $color-slack: #4b144c;
+
+  .form-control {
+    background: $bgcolor-global;
+  }
+
+  .custom-control-label {
+    &::before {
+      background-color: $secondary;
+      border-color: transparent;
+    }
+    &::after {
+      background-color: darken($color-slack, 5%);
+      // background-image: url(/images/icons/slack/slack-logo-dark-off.svg); -- should be specified in app -- 2022.12.09 Yuki Takei
+    }
+  }
+
+  .custom-control-input:checked ~ .custom-control-label {
+    &::before {
+      background-color: lighten($color-slack, 10%);
+    }
+    &::after {
+      background-color: darken($color-slack, 5%);
+      // background-image: url(/images/icons/slack/slack-logo-dark-on.svg); -- should be specified in app -- 2022.12.09 Yuki Takei
+    }
+  }
+  .grw-slack-logo svg {
+    fill: #dd80de;
+  }
+
+  .grw-btn-slack {
+    background-color: black;
+    &:focus,
+    &:hover {
+      background-color: black;
+    }
+  }
+
+  .grw-btn-slack-triangle {
+    color: $secondary;
+  }
+}
+
+/*
+ * GROWI HandsontableModal
+ */
+.grw-hot-modal-navbar {
+  background-color: $dark;
+}
+
+.wiki {
+  h1 {
+    border-color: lighten($border-color-theme, 10%);
+  }
+  h2 {
+    border-color: $border-color-theme;
+  }
+}
+
+/*
+ * GROWI comment form
+ */
+.comment-form {
+  #slack-mark-black {
+    display: none;
+  }
+}
+
+.page-comments-row {
+  background: $bgcolor-subnav;
+}
+
+/*
+ * GROWI tags
+ */
+.grw-tag-labels {
+  .grw-tag-label {
+    color: $color-tags;
+    background-color: $bgcolor-tags;
+  }
+}
+
+/*
+ * GROWI popular tags
+ */
+.grw-popular-tag-labels {
+  .grw-tag-label {
+    color: $color-tags;
+    background-color: $bgcolor-tags;
+  }
+}
+
+/*
+ * admin settings
+ */
+.admin-setting-header {
+  border-color: $border-color-global;
+}
+
+/*
+* grw-side-contents
+*/
+.grw-side-contents-sticky-container {
+  .grw-count-badge {
+    @include count-badge.count-badge($gray-400, $gray-700);
+  }
+
+  .grw-border-vr {
+    border-color: $border-color-toc;
+  }
+
+  .revision-toc {
+    border-color: $border-color-toc;
+  }
+}
+
+/*
+ * drawio
+ */
+.drawio-viewer {
+  border-color: $border-color-global;
+}
+
+/*
+ * modal
+ */
+.grw-modal-head {
+  border-color: $border-color-global;
+}
+
+/*
+ * skeleton
+ */
+.grw-skeleton {
+  background-color: lighten($bgcolor-subnav, 15%);
+}

+ 430 - 0
packages/preset-themes/src/styles/theme/_apply-colors-light.scss

@@ -0,0 +1,430 @@
+@use '../variables' as var;
+@use '../bootstrap/init' as *;
+@use '../atoms/mixins/buttons' as mixins-buttons;
+@use './mixins/count-badge';
+
+// determine optional variables
+$color-list: $color-global !default;
+$bgcolor-list: $bgcolor-global !default;
+$color-list-hover: $color-global !default;
+$bgcolor-list-hover: lighten($primary, 72%) !default;
+$bgcolor-list-active: lighten($primary, 65%) !default;
+$color-list-active: color-yiq($bgcolor-list-active) !default;
+$color-table: $color-global !default;
+$bgcolor-table: null !default;
+$border-color-table: $gray-200 !default;
+$color-table-hover: $color-table !default;
+$bgcolor-table-hover: rgba(black, 0.075) !default;
+$bgcolor-sidebar-list-group: $bgcolor-list !default;
+$color-tags: $secondary !default;
+$bgcolor-tags: $gray-200 !default;
+$border-color-global: $gray-300 !default;
+$border-color-toc: $border-color-global !default;
+$color-dropdown: $color-global !default;
+$color-dropdown-link: $color-global !default;
+$color-dropdown-link-hover: $color-global !default;
+$color-dropdown-link-active: $color-reversal !default;
+$bgcolor-dropdown-link-active: $primary !default;
+
+// override bootstrap variables
+$text-muted: $gray-500;
+$table-color: $color-table;
+$table-bg: $bgcolor-table;
+$table-border-color: $border-color-table;
+$table-hover-color: $color-table-hover;
+$table-hover-bg: $bgcolor-table-hover;
+$border-color: $border-color-global;
+$dropdown-color: $color-dropdown;
+$dropdown-link-color: $color-dropdown-link;
+$dropdown-link-hover-color: $color-dropdown-link-hover;
+$dropdown-link-active-color: $color-dropdown-link-active;
+$dropdown-link-active-bg: $bgcolor-dropdown-link-active;
+
+@import './mixins/list-group';
+@import './reboot-bootstrap-text';
+@import './reboot-bootstrap-border-colors';
+@import './reboot-bootstrap-tables';
+@import './reboot-bootstrap-dropdown';
+
+// List Group
+@include override-list-group-item($color-list, $bgcolor-list, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
+
+/*
+ * Form
+ */
+.form-control {
+  background-color: $bgcolor-global;
+}
+
+.form-control::placeholder {
+  color: darken($bgcolor-global, 20%);
+}
+
+.form-control[disabled],
+.form-control[readonly] {
+  color: lighten($color-global, 10%);
+  background-color: darken($bgcolor-global, 5%);
+}
+
+/*
+ * card
+ */
+.card.card-disabled {
+  background-color: darken($bgcolor-card, 5%);
+  border-color: $gray-200;
+}
+
+/*
+ * GROWI Login form
+ */
+.nologin {
+  // background color
+  $color-gradient: #3e4d6c;
+  background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
+    linear-gradient(135deg, var.$growi-green 10%, hsla(225, 95%, 50%, 0) 70%), linear-gradient(225deg, var.$growi-blue 10%, hsla(140, 90%, 50%, 0) 80%),
+    linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
+
+  .noLogin-header {
+    background-color: rgba(white, 0.5);
+
+    .logo {
+      background-color: rgba(black, 0);
+      fill: rgba(black, 0.5);
+    }
+
+    h1 {
+      color: rgba(black, 0.5);
+    }
+  }
+
+  .noLogin-dialog {
+    background-color: rgba(white, 0.5);
+  }
+
+  .dropdown-with-icon {
+    .dropdown-toggle {
+      color: white;
+      background-color: rgba($gray-600, 0.7);
+      box-shadow: unset;
+      &:focus {
+        color: white;
+        background-color: rgba($gray-600, 0.7);
+      }
+    }
+    i {
+      color: darken(white, 30%);
+      background-color: rgba($gray-700, 0.7);
+    }
+  }
+
+  .input-group {
+    .input-group-text {
+      color: darken(white, 30%);
+      background-color: rgba($gray-700, 0.7);
+    }
+
+    .form-control {
+      color: white;
+      background-color: rgba($gray-600, 0.7);
+      box-shadow: unset;
+
+      &::placeholder {
+        color: darken(white, 30%);
+      }
+    }
+  }
+
+  // footer link text
+  .link-growi-org {
+    color: rgba(black, 0.4);
+
+    &:hover,
+    &.focus {
+      color: black;
+
+      .growi {
+        color: darken(var.$growi-green, 20%);
+      }
+
+      .org {
+        color: darken(var.$growi-blue, 15%);
+      }
+    }
+  }
+}
+
+/*
+ * GROWI subnavigation
+ */
+.grw-subnav {
+  background-color: $bgcolor-subnav;
+}
+
+.grw-subnav-fixed-container .grw-subnav {
+  background-color: rgba($bgcolor-subnav, 0.85);
+}
+
+.grw-page-editor-mode-manager {
+  .btn-outline-primary {
+    &:hover {
+      color: $primary;
+      background-color: $gray-200;
+    }
+  }
+}
+
+.grw-drawer-toggler {
+  @extend .btn-light;
+  color: $gray-500;
+}
+
+/*
+ * GROWI Sidebar
+ */
+.grw-sidebar {
+  // List
+  @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
+  // sidebar-centent-bg
+  .grw-navigation-wrap {
+    // Drop a shadow on the light theme. The dark theme makes '$ bgcolor-sidebar-context' brighter than the body.
+    box-shadow: 0px 0px 3px rgba(black, 0.24);
+  }
+  // Pagetree
+  .grw-pagetree {
+    @include override-list-group-item-for-pagetree(
+      $color-sidebar-context,
+      darken($bgcolor-sidebar-context, 5%),
+      darken($bgcolor-sidebar-context, 12%),
+      lighten($color-sidebar-context, 10%),
+      lighten($color-sidebar-context, 8%),
+      darken($bgcolor-sidebar-context, 15%),
+      darken($bgcolor-sidebar-context, 24%)
+    );
+    .grw-pagetree-triangle-btn {
+      @include mixins-buttons.button-outline-svg-icon-variant($gray-400, $primary);
+    }
+  }
+  .private-legacy-pages-link {
+    &:hover {
+      background: $bgcolor-list-hover;
+    }
+  }
+}
+
+.btn.btn-page-item-control {
+  @include button-outline-variant($gray-500, $primary, lighten($primary, 52%), transparent);
+  @include hover() {
+    background-color: lighten($primary, 58%);
+  }
+  &:not(:disabled):not(.disabled):active,
+  &:not(:disabled):not(.disabled).active {
+    color: $primary;
+  }
+  box-shadow: none !important;
+}
+
+/*
+ * GROWI page list
+ */
+.page-list {
+  .page-list-ul {
+    > li {
+      > span.page-list-meta {
+        color: lighten($color-global, 10%);
+      }
+    }
+  }
+  // List group
+  .list-group-item {
+    .page-list-snippet {
+      color: lighten($body-color, 10%);
+    }
+  }
+}
+
+/*
+ * GROWI ToC
+ */
+.revision-toc-content {
+  ::marker {
+    color: darken($bgcolor-global, 20%);
+  }
+}
+
+/*
+ * GROWI Editor
+ */
+.grw-editor-navbar-bottom {
+  background-color: $gray-50;
+
+  #slack-mark-white {
+    display: none;
+  }
+
+  .input-group-text {
+    margin-right: 1px;
+    color: $secondary;
+    border-color: $light;
+  }
+
+  .btn.btn-outline-secondary {
+    border-color: $border-color;
+  }
+}
+
+/*
+ * GROWI Link Edit Modal
+ */
+.link-edit-modal {
+  span i {
+    color: $gray-400;
+  }
+}
+
+/*
+ * GROWI Grid Edit Modal
+ */
+
+.grw-grid-edit-preview {
+  background: $gray-100;
+}
+
+/*
+ * Slack
+ */
+.grw-slack-notification {
+  background-color: white;
+  $color-slack: #4b144c;
+
+  .form-control {
+    background: white;
+  }
+
+  .custom-control-label {
+    &::before {
+      background-color: $gray-200;
+      border-color: transparent;
+    }
+    &::after {
+      background-color: white;
+      // background-image: url(/images/icons/slack/slack-logo-off.svg); -- should be specified in app -- 2022.12.09 Yuki Takei
+    }
+  }
+  .custom-control-input:checked ~ .custom-control-label {
+    &::before {
+      background-color: lighten($color-slack, 60%);
+    }
+    &::after {
+      // background-image: url(/images/icons/slack/slack-logo-on.svg); -- should be specified in app -- 2022.12.09 Yuki Takei
+    }
+  }
+  .grw-slack-logo svg {
+    fill: #af30b0;
+  }
+
+  .grw-btn-slack {
+    background-color: white;
+
+    &:hover,
+    &:focus {
+      background-color: white;
+    }
+  }
+
+  .grw-btn-slack-triangle {
+    color: $secondary;
+  }
+}
+
+/*
+ * GROWI HandsontableModal
+ */
+.grw-hot-modal-navbar {
+  background-color: $light;
+}
+
+.wiki {
+  h1 {
+    border-color: $border-color-theme;
+  }
+  h2 {
+    border-color: $border-color-theme;
+  }
+}
+
+/*
+ * GROWI comment form
+ */
+.comment-form {
+  #slack-mark-white {
+    display: none;
+  }
+}
+
+.page-comments-row {
+  background: $bgcolor-subnav;
+}
+
+/*
+ * GROWI tags
+ */
+.grw-tag-labels {
+  .grw-tag-label {
+    color: $color-tags;
+    background-color: $bgcolor-tags;
+  }
+}
+
+/*
+ * GROWI popular tags
+ */
+.grw-popular-tag-labels {
+  .grw-tag-label {
+    color: $color-tags;
+    background-color: $bgcolor-tags;
+  }
+}
+
+/*
+* grw-side-contents
+*/
+.grw-side-contents-sticky-container {
+  .grw-count-badge {
+    @include count-badge.count-badge($gray-600, $gray-200);
+  }
+
+  .grw-border-vr {
+    border-color: $border-color-toc;
+  }
+  .revision-toc {
+    border-color: $border-color-toc;
+  }
+}
+
+/*
+ * drawio
+ */
+.drawio-viewer {
+  border-color: $border-color-global;
+}
+
+/*
+ * admin settings
+ */
+.admin-setting-header {
+  border-color: $border-color;
+}
+
+/*
+ * modal
+ */
+.grw-modal-head {
+  border-color: $border-color-global;
+}
+
+/*
+ * skeleton
+ */
+.grw-skeleton {
+  background-color: darken($bgcolor-subnav, 10%);
+}

+ 697 - 0
packages/preset-themes/src/styles/theme/_apply-colors.scss

@@ -0,0 +1,697 @@
+@use '../variables' as var;
+@use '../bootstrap/init' as *;
+@use '../mixins';
+@use './mixins/tables'; // comment out and use _reboot-bootstrap-tables instead -- 2020.05.28 Yuki Takei
+@use '../atoms/mixins/code';
+
+//
+//== Apply to Bootstrap
+//
+
+// determine optional variables
+$border-image-navbar: linear-gradient(to right, $gray-300 0%, $gray-300 100%) !default;
+$bgcolor-search-top-dropdown: $secondary !default;
+$bgcolor-sidebar-nav-item-active: darken($bgcolor-sidebar, 10%) !default;
+$text-shadow-sidebar-nav-item-active: 1px 1px 2px $primary !default;
+$bgcolor-inline-code: $gray-100 !default;
+$color-inline-code: darken($red, 15%) !default;
+$bordercolor-inline-code: $gray-400 !default;
+$bordercolor-nav-tabs: $gray-300 !default;
+$bordercolor-nav-tabs-hover: $gray-200 $gray-200 $bordercolor-nav-tabs !default;
+$color-nav-tabs-link-active: $gray-600 !default;
+$bordercolor-nav-tabs-active: $bordercolor-nav-tabs $bordercolor-nav-tabs $bgcolor-global !default;
+$color-btn-reload-in-sidebar: $gray-500;
+$bgcolor-keyword-highlighted: var.$grw-marker-yellow !default;
+$bgcolor-page-list-group-item-active: lighten($primary, 76%) !default;
+$color-page-list-group-item-meta: $gray-500 !default;
+$color-search-page-list-title: $color-global !default;
+$bgcolor-subnav: darken($bgcolor-global, 3%) !default;
+
+// override bootstrap variables
+$body-bg: $bgcolor-global;
+$body-color: $color-global;
+$link-color: $color-link;
+$link-hover-color: $color-link-hover;
+$input-focus-color: $color-global;
+$nav-tabs-border-color: $bordercolor-nav-tabs;
+$nav-tabs-link-hover-border-color: $bordercolor-nav-tabs-hover;
+$nav-tabs-link-active-color: $color-nav-tabs-link-active;
+$nav-tabs-link-active-bg: $bgcolor-global;
+$nav-tabs-link-active-border-color: $bordercolor-nav-tabs-active;
+$theme-colors: map-merge($theme-colors, ( primary: $primary ));
+
+@import 'reboot-bootstrap-buttons';
+@import 'reboot-bootstrap-colors';
+@import 'reboot-bootstrap-theme-colors';
+@import 'reboot-bootstrap-nav';
+@import 'reboot-toastr-colors';
+
+// determine variables with bootstrap function (These variables can be used after importing bootstrap above)
+$color-modal-header: color-yiq($primary) !default;
+
+code:not([class^='language-']) {
+  @include code.code-inline-color($color-inline-code, $bgcolor-inline-code, $bordercolor-inline-code);
+}
+
+.code-highlighted {
+  border-color: $bordercolor-inline-code;
+}
+
+//
+//== Apply to Bootstrap Elements
+//
+
+// Alert link
+@each $color, $value in $theme-colors {
+  .alert.alert-#{$color} {
+    a,
+    a:hover {
+      color: theme-color-level($color, $alert-color-level - 2);
+    }
+  }
+}
+
+// Dropdown
+.grw-apperance-mode-dropdown {
+  .grw-sidebar-mode-icon svg {
+    fill: $secondary;
+  }
+  .grw-color-mode-icon svg {
+    fill: $color-global;
+  }
+  .grw-color-mode-icon-muted svg {
+    fill: $secondary;
+  }
+}
+
+// Form
+.form-control {
+  @include form-control-focus();
+}
+
+// Tabs
+.nav.nav-tabs .nav-link.active {
+  color: $color-link !important;
+  background: transparent;
+
+  &:hover,
+  &:focus {
+    color: $color-link-hover !important;
+  }
+}
+
+// Pagination
+ul.pagination {
+  li.page-item.disabled {
+    button.page-link {
+      color: $gray-400;
+    }
+  }
+  li.page-item.active {
+    button.page-link {
+      color: color-yiq($primary);
+      background-color: $primary;
+      &:hover,
+      &:focus {
+        color: color-yiq($primary);
+        background-color: $primary;
+      }
+    }
+  }
+  li.page-item {
+    button.page-link {
+      color: $primary;
+      border-color: $secondary;
+      &:hover,
+      &:active,
+      &:focus {
+        color: $primary;
+      }
+    }
+  }
+}
+
+//
+//== Apply to Handsontable
+//
+.handsontable {
+  color: initial;
+}
+
+//
+//== Apply to GROWI Elements
+//
+
+.grw-logo {
+  // set transition for fill
+  svg * {
+    transition: fill 0.8s ease-out;
+  }
+
+  svg {
+    fill: $fillcolor-logo-mark;
+  }
+
+  &:hover {
+    svg {
+      .group1 {
+        fill: var.$growi-green;
+      }
+
+      .group2 {
+        fill: var.$growi-blue;
+      }
+    }
+  }
+}
+
+.grw-navbar {
+  background: $bgcolor-navbar;
+  .nav-item .nav-link {
+    color: $color-link-nabvar;
+  }
+
+  border-image: $border-image-navbar;
+  border-image-slice: 1;
+
+  .grw-app-title {
+    color: $fillcolor-logo-mark;
+  }
+}
+
+.grw-global-search {
+  .btn-secondary.dropdown-toggle {
+    @include button-variant($bgcolor-search-top-dropdown, $bgcolor-search-top-dropdown);
+  }
+
+  // for https://youtrack.weseek.co.jp/issue/GW-2603
+  .search-typeahead {
+    background-color: rgba($bgcolor-global, 0.9);
+  }
+}
+
+.grw-sidebar {
+  .grw-navigation-resize-button {
+    $color-resize-button: $color-global !default;
+    $bgcolor-resize-button: white !default;
+    $color-resize-button-hover: $color-reversal !default;
+    $bgcolor-resize-button-hover: lighten($bgcolor-resize-button, 5%) !default;
+
+    .hexagon-container svg {
+      .background {
+        fill: $bgcolor-resize-button;
+      }
+      .icon {
+        fill: $color-resize-button;
+      }
+    }
+    &:hover .hexagon-container svg {
+      .background {
+        fill: $bgcolor-resize-button-hover;
+      }
+      .icon {
+        fill: $color-resize-button-hover;
+      }
+    }
+  }
+  div.grw-global-navigation {
+    > div {
+      background-color: $bgcolor-sidebar;
+    }
+  }
+  div.grw-contextual-navigation {
+    > div {
+      color: $color-sidebar-context;
+      background-color: $bgcolor-sidebar-context;
+    }
+  }
+
+  .grw-sidebar-nav {
+    .btn {
+      @include button-variant(
+        $bgcolor-sidebar,
+        $bgcolor-sidebar,
+        darken($bgcolor-sidebar, 7.5%),
+        darken($bgcolor-sidebar, 10%),
+        $bgcolor-sidebar-nav-item-active,
+        $bgcolor-sidebar-nav-item-active
+      );
+    }
+  }
+  .grw-sidebar-nav-primary-container {
+    .btn.active {
+      i {
+        text-shadow: $text-shadow-sidebar-nav-item-active;
+      }
+      // fukidashi
+      &:after {
+        border-right-color: $bgcolor-sidebar-context;
+      }
+    }
+  }
+
+  .grw-sidebar-content-header {
+    .grw-btn-reload {
+      color: $color-btn-reload-in-sidebar;
+    }
+
+    .grw-recent-changes-resize-button {
+      .custom-control-label::before {
+        background-color: $primary;
+      }
+
+      .custom-control-label::after {
+        background-color: $bgcolor-global;
+      }
+
+      .custom-control-input:not(:checked) + .custom-control-label::before {
+        color: $bgcolor-global;
+      }
+
+      .custom-control-input:checked + .custom-control-label::before {
+        color: $bgcolor-global;
+        background-color: $primary;
+        // border-color: $primary !important;
+      }
+      .custom-control-input:checked + .custom-control-label::after {
+        color: $bgcolor-global;
+      }
+    }
+  }
+
+  .grw-pagetree {
+    .list-group-item {
+      .grw-pagetree-title-anchor {
+        color: inherit;
+      }
+    }
+  }
+  .grw-pagetree-footer {
+    .h5.grw-private-legacy-pages-anchor {
+      color: inherit;
+    }
+  }
+
+  .grw-recent-changes {
+    .list-group {
+      .list-group-item {
+        background-color: transparent;
+
+        .icon-lock {
+          color: $color-link;
+        }
+
+        .grw-recent-changes-item-lower {
+          color: $gray-500;
+
+          svg {
+            fill: $gray-500;
+          }
+        }
+      }
+    }
+  }
+}
+
+/*
+ * Icon
+ */
+.editor-container .navbar-editor svg {
+  fill: $color-editor-icons;
+}
+
+// page preview button in link form
+.btn-page-preview svg {
+  fill: white;
+}
+
+/*
+ * Modal
+ */
+.modal {
+  .modal-header {
+    border-bottom-color: $border-color-theme;
+    .modal-title {
+      color: $color-modal-header;
+    }
+    .close {
+      color: $color-modal-header;
+      opacity: 0.5;
+      &:hover {
+        opacity: 0.9;
+      }
+    }
+  }
+
+  .modal-content {
+    background-color: $bgcolor-global;
+  }
+
+  .modal-footer {
+    border-top-color: $border-color-theme;
+  }
+}
+
+.grw-custom-nav-tab {
+  .nav-item {
+    &:hover,
+    &:focus {
+      background-color: rgba($color-link, 0.08);
+    }
+    .nav-link {
+      -webkit-appearance: none;
+      color: $color-link;
+      svg {
+        fill: $color-link;
+      }
+
+      // Disabled state lightens text
+      &.disabled {
+        color: $nav-link-disabled-color;
+        svg {
+          fill: $nav-link-disabled-color;
+        }
+      }
+    }
+  }
+
+  .grw-nav-slide-hr {
+    border-color: $color-link;
+  }
+}
+
+.grw-page-accessories-modal {
+  .modal-header {
+    .close {
+      color: $secondary;
+    }
+  }
+}
+
+/*
+ * cards
+ */
+.card.well {
+  color: $color-global;
+  background-color: $bgcolor-card;
+  border-color: $light;
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.admin-bot-card {
+  .grw-botcard-title-active {
+    color: $gray-200;
+  }
+}
+
+/*
+ * Form Slider
+ */
+.admin-page {
+  span.slider {
+    background-color: $gray-300;
+
+    &:before {
+      background-color: white;
+    }
+  }
+
+  input:checked + .slider {
+    background-color: #007bff;
+  }
+
+  input:focus + .slider {
+    box-shadow: 0 0 1px #007bff;
+  }
+}
+
+/*
+ * GROWI wiki
+ */
+.wiki {
+  h1,
+  h2,
+  h3,
+  h4,
+  h5,
+  h6,
+  h7 {
+    &.blink {
+      @include mixins.blink-bgcolor($bgcolor-blinked-section);
+    }
+  }
+
+  .highlighted-keyword {
+    background: linear-gradient(transparent 60%, $bgcolor-keyword-highlighted 60%);
+  }
+
+  a {
+    color: $color-link-wiki;
+
+    &:hover {
+      color: $color-link-wiki-hover;
+    }
+  }
+
+  // table with handsontable modal button
+  .editable-with-handsontable {
+    button {
+      color: $color-link-wiki;
+    }
+
+    button:hover {
+      color: $color-link-wiki-hover;
+    }
+  }
+}
+
+/*
+ * GROWI page-list
+ */
+.page-list {
+  // List group
+  .list-group {
+    .list-group-item {
+      a {
+        svg {
+          fill: $color-global;
+        }
+
+        @include hover() {
+          svg {
+            fill: $color-global;
+          }
+        }
+      }
+
+      .page-list-meta {
+        color: $color-page-list-group-item-meta;
+        svg {
+          fill: $color-page-list-group-item-meta;
+        }
+      }
+
+      &.list-group-item-action {
+        &.active {
+          background-color: $bgcolor-page-list-group-item-active;
+          border-left-color: $primary;
+        }
+      }
+    }
+  }
+}
+
+/*
+ * GROWI Editor
+ */
+.layout-root.editing {
+  .main {
+    background-color: darken($bgcolor-global, 2%);
+
+    .page-editor-editor-container {
+      border-right-color: $border-color-theme;
+
+      .navbar-editor {
+        background-color: $bgcolor-global; // same color with active tab
+        border-bottom-color: $border-color-theme;
+      }
+    }
+
+    .page-editor-preview-container {
+      background-color: $bgcolor-global;
+    }
+  }
+}
+
+/*
+ * Preview for editing /Sidebar
+ */
+body.editing-sidebar {
+  .page-editor-preview-body {
+    color: $color-sidebar-context;
+    background-color: $bgcolor-sidebar-context;
+  }
+}
+
+/*
+ * GROWI Grid Edit Modal
+ */
+.grw-grid-edit-preview {
+  .desktop-preview,
+  .tablet-preview,
+  .mobile-preview {
+    background: $bgcolor-global;
+  }
+  .grid-edit-border-for-each-cols {
+    border: 2px solid $bgcolor-global;
+  }
+}
+
+.grid-preview-col-0 {
+  background: var.$growi-blue;
+}
+
+.grid-preview-col-1 {
+  background: $info;
+}
+
+.grid-preview-col-2 {
+  background: $success;
+}
+
+.grid-preview-col-3 {
+  background: var.$growi-green;
+}
+
+/*
+ * GROWI comment
+ */
+.page-comment-meta .page-comment-revision svg {
+  fill: $color-link;
+
+  &:hover {
+    fill: $color-link-hover;
+  }
+}
+
+/*
+ * GROWI comment form
+ */
+.page-comments {
+  .page-comment .page-comment-main,
+  .page-comment-form .comment-form-main {
+    background-color: $bgcolor-global;
+
+    &:before {
+      border-right-color: $bgcolor-global;
+    }
+
+    .nav.nav-tabs {
+      > li > a.active {
+        background: transparent;
+        border-bottom: solid 1px darken($bgcolor-global, 4%);
+        border-bottom-color: darken($bgcolor-global, 4%);
+      }
+    }
+  }
+}
+
+/*
+ * GROWI search result
+ */
+.search-result-base {
+  .grw-search-page-nav {
+    background-color: $bgcolor-subnav;
+  }
+  .search-control {
+    background-color: $bgcolor-global;
+  }
+  .page-list {
+    .highlighted-keyword {
+      background: linear-gradient(transparent 60%, $bgcolor-keyword-highlighted 60%);
+    }
+  }
+}
+
+/*
+ * react bootstrap typeahead
+ */
+mark.rbt-highlight-text {
+  // Temporarily the highlight color is black
+  color: black;
+}
+
+/*
+ * GROWI page content footer
+ */
+.page-content-footer {
+  background-color: darken($bgcolor-global, 2%);
+  border-top-color: $border-color-theme;
+}
+
+/*
+ * GROWI admin page #layoutOptions #themeOptions
+ */
+.admin-page {
+  #layoutOptions {
+    .customize-layout-card {
+      &.border-active {
+        border-color: $color-theme-color-box;
+      }
+    }
+  }
+
+  #themeOptions {
+    .theme-option-container.active {
+      .theme-option-name {
+        color: $color-global;
+      }
+      a {
+        background-color: $color-theme-color-box;
+        border-color: $color-theme-color-box;
+      }
+    }
+  }
+}
+
+/*
+ * HackMd
+ */
+.bg-box {
+  background-color: $bgcolor-global;
+}
+
+.grw-fab {
+  .btn-create-page {
+    fill: color-yiq($primary);
+  }
+
+  .btn-scroll-to-top {
+    fill: $gray-900;
+  }
+}
+
+/*
+  Slack Integration
+*/
+.selecting-bot-type {
+  .bot-type-disc {
+    width: 20px;
+  }
+}
+
+/*
+  In App Notification
+*/
+.grw-unopend-notification {
+  width: 7px;
+  height: 7px;
+  background-color: $primary;
+}
+
+/*
+Emoji picker modal
+*/
+.emoji-picker-modal {
+  background-color: transparent !important;
+}

+ 29 - 0
packages/preset-themes/src/styles/theme/_reboot-bootstrap-border-colors.scss

@@ -0,0 +1,29 @@
+@use '../bootstrap/init' as *;
+
+//
+// Border
+//
+
+.border {
+  border: $border-width solid $border-color !important;
+}
+
+.border-top {
+  border-top: $border-width solid $border-color !important;
+}
+
+.border-right {
+  border-right: $border-width solid $border-color !important;
+}
+
+.border-bottom {
+  border-bottom: $border-width solid $border-color !important;
+}
+
+.border-left {
+  border-left: $border-width solid $border-color !important;
+}
+
+.border-info {
+  border-color: $info !important;
+}

+ 21 - 0
packages/preset-themes/src/styles/theme/_reboot-bootstrap-buttons.scss

@@ -0,0 +1,21 @@
+.btn-link {
+  color: $link-color;
+  svg {
+    fill: $link-color;
+  }
+
+  @include hover() {
+    color: $link-hover-color;
+    svg {
+      fill: $link-hover-color;
+    }
+  }
+
+  &:disabled,
+  &.disabled {
+    color: $btn-link-disabled-color;
+    svg {
+      fill: $btn-link-disabled-color;
+    }
+  }
+}

+ 60 - 0
packages/preset-themes/src/styles/theme/_reboot-bootstrap-colors.scss

@@ -0,0 +1,60 @@
+//
+//
+// Apply partially
+//   https://github.com/twbs/bootstrap/blob/v4.5.0/scss/_reboot.scss
+//
+//
+
+// stylelint-disable at-rule-no-vendor-prefix, declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix
+
+// Body
+//
+// 1. Remove the margin in all browsers.
+// 2. As a best practice, apply a default `background-color`.
+// 3. Set an explicit initial text-align value so that we can later use
+//    the `inherit` value on things like `<th>` elements.
+
+&, body {
+  color: $body-color;
+  background-color: $body-bg; // 2
+
+  svg {
+    fill: $body-color;
+  }
+}
+
+// Links
+
+a {
+  color: $link-color;
+  text-decoration: $link-decoration;
+  background-color: transparent; // Remove the gray background on active links in IE 10.
+
+  svg {
+    fill: $link-color;
+  }
+
+  @include hover() {
+    color: $link-hover-color;
+    text-decoration: $link-hover-decoration;
+
+    svg {
+      fill: $link-hover-color;
+    }
+  }
+}
+
+// And undo these styles for placeholder links/named anchors (without href).
+// It would be more straightforward to just use a[href] in previous block, but that
+// causes specificity issues in many other styles that are too complex to fix.
+// See https://github.com/twbs/bootstrap/issues/19402
+
+// a:not([href]) {
+//   color: inherit;
+//   text-decoration: none;
+
+//   @include hover() {
+//     color: inherit;
+//     text-decoration: none;
+//   }
+// }

+ 37 - 0
packages/preset-themes/src/styles/theme/_reboot-bootstrap-dropdown.scss

@@ -0,0 +1,37 @@
+@use '../bootstrap/init' as *;
+
+.dropdown-menu {
+  color: $dropdown-color;
+  svg {
+    fill: $dropdown-color;
+  }
+
+  background-color: $dropdown-bg;
+}
+
+.dropdown-item {
+  color: $dropdown-link-color;
+  svg {
+    fill: $dropdown-link-color;
+  }
+
+  @include hover-focus() {
+    color: $dropdown-link-hover-color;
+    svg {
+      fill: $dropdown-link-hover-color;
+    }
+
+    @include gradient-bg($dropdown-link-hover-bg);
+  }
+
+  &:active,
+  &.active,
+  &:active:hover,
+  &.active:hover {
+    color: $dropdown-link-active-color;
+    background-color: $dropdown-link-active-bg;
+    svg {
+      fill: $dropdown-link-active-color;
+    }
+  }
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов