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

Merge pull request #6188 from weseek/support/convert-to-css-module

support: Convert to css module
Yuki Takei 3 лет назад
Родитель
Сommit
e337ba5ec0

+ 12 - 0
.eslintrc.js

@@ -35,6 +35,18 @@ module.exports = {
             group: 'parent',
             position: 'before',
           },
+          {
+            pattern: '*.css',
+            group: 'type',
+            patternOptions: { matchBase: true },
+            position: 'after',
+          },
+          {
+            pattern: '*.scss',
+            group: 'type',
+            patternOptions: { matchBase: true },
+            position: 'after',
+          },
         ],
         alphabetize: {
           order: 'asc',

+ 20 - 3
packages/app/next.config.js

@@ -1,12 +1,29 @@
+import eazyLogger from 'eazy-logger';
 import { I18NextHMRPlugin } from 'i18next-hmr/plugin';
 import { WebpackManifestPlugin } from 'webpack-manifest-plugin';
 
 import { i18n, localePath } from './src/next-i18next.config';
 import { listScopedPackages } from './src/utils/next.config.utils';
 
-// define transpiled packages for '@growi/*'
-const scopedPackages = listScopedPackages(['@growi']);
-const withTM = require('next-transpile-modules')(scopedPackages);
+
+// setup logger
+const logger = eazyLogger.Logger({
+  prefix: '[{green:next.config.js}] ',
+  useLevelPrefixes: false,
+});
+
+
+const setupWithTM = () => {
+  // define transpiled packages for '@growi/*'
+  const scopedPackages = listScopedPackages(['@growi'], { ignorePackageNames: '@growi/app' });
+
+  logger.info('{bold:Listing scoped packages for transpiling:}');
+  logger.unprefixed('info', `{grey:${JSON.stringify(scopedPackages, null, 2)}}`);
+
+  return require('next-transpile-modules')(scopedPackages);
+};
+const withTM = setupWithTM();
+
 
 // define additional entries
 const additionalWebpackEntries = {

+ 1 - 1
packages/app/src/components/Navbar/GlobalSearch.scss → packages/app/src/components/Navbar/GlobalSearch.module.scss

@@ -1,7 +1,7 @@
 @use '~/styles/bootstrap/init' as bs;
 
 // input styles
-.grw-global-search {
+.grw-global-search :global {
   .dropdown-toggle {
     min-width: 95px;
     padding-left: 1.5rem;

+ 2 - 2
packages/app/src/components/Navbar/GlobalSearch.tsx

@@ -15,7 +15,7 @@ import { useGlobalSearchFormRef } from '~/stores/ui';
 import SearchForm from '../SearchForm';
 
 
-import './GlobalSearch.scss';
+import styles from './GlobalSearch.module.scss';
 
 
 type Props = {
@@ -76,7 +76,7 @@ export const GlobalSearch = (props: Props): JSX.Element => {
   }
 
   return (
-    <div className={`form-group mb-0 d-print-none ${isSearchServiceReachable ? '' : 'has-error'}`}>
+    <div className={`grw-global-search ${styles['grw-global-search']} form-group mb-0 d-print-none ${isSearchServiceReachable ? '' : 'has-error'}`}>
       <div className="input-group flex-nowrap">
         <div className={`input-group-prepend ${dropup ? 'dropup' : ''}`}>
           <button className="btn btn-secondary dropdown-toggle py-0" type="button" data-toggle="dropdown" aria-haspopup="true">

+ 41 - 35
packages/app/src/components/Navbar/GrowiNavbar.scss → packages/app/src/components/Navbar/GrowiNavbar.module.scss

@@ -2,19 +2,23 @@
 @use '~/styles/bootstrap/init' as bs;
 @use '~/styles/mixins';
 
-.grw-logo {
-  svg {
-    width: var.$grw-logo-width;
-    height: var.$grw-navbar-height;
-    padding: (var.$grw-logo-width - var.$grw-logomark-width) / 2;
+.grw-navbar :global {
+
+  .grw-logo {
+    svg {
+      width: var.$grw-logo-width;
+      height: var.$grw-navbar-height;
+      padding: (var.$grw-logo-width - var.$grw-logomark-width) / 2;
+    }
+  }
+
+  .confidential {
+    font-weight: bold;
   }
-}
 
-.confidential {
-  font-weight: bold;
 }
 
-.grw-navbar {
+.grw-navbar :global {
   top: #{-1 * var.$grw-navbar-height} !important;
 
   z-index: var.$grw-navbar-z-index !important;
@@ -89,36 +93,38 @@
 }
 
 // layout for GlobalSearch
-.grw-global-search-top {
-  // centering on navbar
-  top: var.$grw-navbar-height / 2;
-  left: 50vw;
-  z-index: bs.$zindex-fixed + 1;
-  transform: translate(-50%, -50%);
-
-  .rbt-input.form-control {
-    width: 200px;
-    transition: 0.3s ease-out;
-
-    // focus
-    &.focus {
-      width: 300px;
-    }
+.grw-navbar :global {
+  .grw-global-search-container {
+    // centering on navbar
+    top: var.$grw-navbar-height / 2;
+    left: 50vw;
+    z-index: bs.$zindex-fixed + 1;
+    transform: translate(-50%, -50%);
+
+    .rbt-input.form-control {
+      width: 200px;
+      transition: 0.3s ease-out;
 
-    @include bs.media-breakpoint-up(md) {
-      width: 300px;
-    }
-    @include bs.media-breakpoint-up(lg) {
       // focus
       &.focus {
-        width: 400px;
+        width: 300px;
       }
-    }
-    @include bs.media-breakpoint-up(xl) {
-      width: 350px;
-      // focus
-      &.focus {
-        width: 450px;
+
+      @include bs.media-breakpoint-up(md) {
+        width: 300px;
+      }
+      @include bs.media-breakpoint-up(lg) {
+        // focus
+        &.focus {
+          width: 400px;
+        }
+      }
+      @include bs.media-breakpoint-up(xl) {
+        width: 350px;
+        // focus
+        &.focus {
+          width: 450px;
+        }
       }
     }
   }

+ 3 - 3
packages/app/src/components/Navbar/GrowiNavbar.tsx

@@ -19,7 +19,7 @@ import GrowiLogo from '../Icons/GrowiLogo';
 
 import PersonalDropdown from './PersonalDropdown';
 
-import './GrowiNavbar.scss';
+import styles from './GrowiNavbar.module.scss';
 
 
 const ShowSkeltonInSSR = memo(({ children }: HasChildren): JSX.Element => {
@@ -138,7 +138,7 @@ export const GrowiNavbar = (): JSX.Element => {
   const { data: isSearchPage } = useIsSearchPage();
 
   return (
-    <nav id="grw-navbar" className="navbar grw-navbar navbar-expand navbar-dark sticky-top mb-0 px-0">
+    <nav id="grw-navbar" className={`navbar grw-navbar ${styles['grw-navbar']} navbar-expand navbar-dark sticky-top mb-0 px-0`}>
       {/* Brand Logo  */}
       <div className="navbar-brand mr-0">
         <a className="grw-logo d-block" href="/">
@@ -158,7 +158,7 @@ export const GrowiNavbar = (): JSX.Element => {
       </ul>
 
       { isSearchServiceConfigured && !isDeviceSmallerThanMd && !isSearchPage && (
-        <div className="grw-global-search grw-global-search-top position-absolute">
+        <div className="grw-global-search-container position-absolute">
           <GlobalSearch />
         </div>
       ) }

+ 4 - 2
packages/app/src/components/Navbar/GrowiNavbarBottom.scss → packages/app/src/components/Navbar/GrowiNavbarBottom.module.scss

@@ -1,14 +1,16 @@
 @use '~/styles/variables' as var;
 @use '~/styles/mixins';
 
-.grw-navbar-bottom {
+.grw-navbar-bottom :global {
   height: var.$grw-navbar-bottom-height;
 
   // apply transition
   transition-property: bottom;
   @include mixins.apply-navigation-transition();
+}
 
-  &.grw-navbar-bottom-drawer-opened {
+.grw-navbar-bottom {
+  &:global(.grw-navbar-bottom-drawer-opened) {
     bottom: #{-1 * var.$grw-navbar-bottom-height};
   }
 }

+ 2 - 2
packages/app/src/components/Navbar/GrowiNavbarBottom.tsx

@@ -6,7 +6,7 @@ import { useIsDeviceSmallerThanMd, useDrawerOpened } from '~/stores/ui';
 
 import { GlobalSearch } from './GlobalSearch';
 
-import './GrowiNavbarBottom.scss';
+import styles from './GrowiNavbarBottom.module.scss';
 
 
 export const GrowiNavbarBottom = (): JSX.Element => {
@@ -17,7 +17,7 @@ export const GrowiNavbarBottom = (): JSX.Element => {
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: isSearchPage } = useIsSearchPage();
 
-  const additionalClasses = ['grw-navbar-bottom'];
+  const additionalClasses = ['grw-navbar-bottom', styles['grw-navbar-bottom']];
   if (isDrawerOpened) {
     additionalClasses.push('grw-navbar-bottom-drawer-opened');
   }

+ 1 - 1
packages/app/src/components/SearchTypeahead.scss → packages/app/src/components/SearchTypeahead.module.scss

@@ -1,6 +1,6 @@
 @use '~/styles/bootstrap/init' as bs;
 
-.search-typeahead {
+.search-typeahead :global {
   position: relative;
   width: 100%;
   // corner radius

+ 3 - 3
packages/app/src/components/SearchTypeahead.tsx

@@ -13,7 +13,7 @@ import { IPageSearchMeta } from '~/interfaces/search';
 import { useSWRxSearch } from '~/stores/search';
 
 
-import './SearchTypeahead.scss';
+import styles from './SearchTypeahead.module.scss';
 
 
 type ResetFormButtonProps = {
@@ -208,11 +208,11 @@ const SearchTypeahead: ForwardRefRenderFunction<IFocusable, Props> = (props: Pro
     );
   }, [disableIncrementalSearch, helpElement, input, isForcused]);
 
-  const isLoading = searchResult == null && searchError == null;
+  const isLoading = searchResult !== undefined && searchError == null;
   const isOpenAlways = helpElement != null;
 
   return (
-    <div className="search-typeahead">
+    <div className={`search-typeahead ${styles['search-typeahead']}`}>
       <AsyncTypeahead
         {...props}
         id="search-typeahead-asynctypeahead"

+ 1 - 1
packages/app/src/components/ShortcutsModal.scss → packages/app/src/components/ShortcutsModal.module.scss

@@ -1,6 +1,6 @@
 @use '~/styles/bootstrap/init' as bs;
 
-#shortcuts-modal {
+.shortcuts-modal :global {
   h3 {
     margin-bottom: 1em;
   }

+ 2 - 2
packages/app/src/components/ShortcutsModal.tsx

@@ -6,7 +6,7 @@ import { Modal, ModalHeader, ModalBody } from 'reactstrap';
 import KeyboardReturnEnterIcon from '~/components/Icons/KeyboardReturnEnterIcon';
 import { useShortcutsModal } from '~/stores/modal';
 
-import './ShortcutsModal.scss';
+import styles from './ShortcutsModal.module.scss';
 
 
 const ShortcutsModal = (): JSX.Element => {
@@ -22,7 +22,7 @@ const ShortcutsModal = (): JSX.Element => {
   return (
     <>
       { status != null && (
-        <Modal id="shortcuts-modal" size="lg" isOpen={status.isOpened} toggle={close} className="grw-create-page">
+        <Modal id="shortcuts-modal" size="lg" isOpen={status.isOpened} toggle={close} className={`shortcuts-modal ${styles['shortcuts-modal']}`}>
           <ModalHeader tag="h4" toggle={close} className="bg-primary text-light">
             {t('Shortcuts')}
           </ModalHeader>

+ 16 - 9
packages/app/src/components/Sidebar.scss → packages/app/src/components/Sidebar.module.scss

@@ -2,7 +2,7 @@
 @use '~/styles/mixins';
 @use '~/styles/bootstrap/init' as bs;
 
-.grw-sidebar {
+.grw-sidebar :global {
   // sticky
   position: sticky;
   top: var.$grw-navbar-border-width;
@@ -53,7 +53,8 @@
           animation-duration: 0.22s;
           animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
           animation-fill-mode: forwards;
-          .grw-contextual-navigation-sub {
+
+          :global .grw-contextual-navigation-sub {
             box-sizing: border-box;
             display: flex;
             flex-direction: column;
@@ -239,20 +240,26 @@
   }
 }
 
-.grw-sidebar {
-  @include bs.media-breakpoint-down(sm) {
+// '&' could not be set after :global
+// workaround from https://github.com/css-modules/css-modules/issues/295#issuecomment-404873976
+.grw-sidebar :global {
+  .grw-sidebar-drawer {
     @include drawer();
   }
-  @include bs.media-breakpoint-up(md) {
-    &.grw-sidebar-drawer {
+  .grw-sidebar-dock {
+    @include bs.media-breakpoint-down(sm) {
       @include drawer();
     }
-    &:not(.grw-sidebar-drawer) {
+    @include bs.media-breakpoint-up(md) {
       @include dock();
     }
   }
 }
 
-.grw-sidebar-backdrop.modal-backdrop {
-  z-index: bs.$zindex-fixed + 1;
+// '&' could not be set after :global
+// workaround from https://github.com/css-modules/css-modules/issues/295#issuecomment-952885628
+.grw-sidebar-backdrop {
+  &:global(.modal-backdrop) {
+    z-index: bs.$zindex-fixed + 1;
+  }
 }

+ 49 - 46
packages/app/src/components/Sidebar.tsx

@@ -18,7 +18,8 @@ import SidebarContents from './Sidebar/SidebarContents';
 import SidebarNav from './Sidebar/SidebarNav';
 import { StickyStretchableScroller } from './StickyStretchableScroller';
 
-import './Sidebar.scss';
+import styles from './Sidebar.module.scss';
+
 
 const sidebarMinWidth = 240;
 const sidebarMinimizeWidth = 20;
@@ -290,61 +291,63 @@ const Sidebar = (): JSX.Element => {
 
   return (
     <>
-      <div className={`grw-sidebar d-print-none ${isDrawerMode ? 'grw-sidebar-drawer' : ''} ${isDrawerOpened ? 'open' : ''}`}>
-        <div className="data-layout-container">
-          <div
-            className={`navigation ${isTransitionEnabled ? 'transition-enabled' : ''}`}
-            onMouseEnter={hoverOnHandler}
-            onMouseLeave={hoverOutHandler}
-          >
-            <div className="grw-navigation-wrap">
-              <div className="grw-global-navigation">
-                <GlobalNavigation></GlobalNavigation>
-              </div>
-              <div
-                ref={resizableContainer}
-                className="grw-contextual-navigation"
-                onMouseEnter={hoverOnResizableContainerHandler}
-                onMouseLeave={hoverOutResizableContainerHandler}
-                style={{ width: isCollapsed ? sidebarMinimizeWidth : currentProductNavWidth }}
-              >
-                <div className="grw-contextual-navigation-child">
-                  <div role="group" data-testid="grw-contextual-navigation-sub" className={`grw-contextual-navigation-sub ${showContents ? '' : 'd-none'}`}>
-                    <SidebarContentsWrapper></SidebarContentsWrapper>
-                  </div>
+      <div className={`grw-sidebar ${styles['grw-sidebar']}`}>
+        <div className={`d-print-none ${isDrawerMode ? 'grw-sidebar-drawer' : 'grw-sidebar-dock'} ${isDrawerOpened ? 'open' : ''}`}>
+          <div className="data-layout-container">
+            <div
+              className={`navigation ${isTransitionEnabled ? 'transition-enabled' : ''}`}
+              onMouseEnter={hoverOnHandler}
+              onMouseLeave={hoverOutHandler}
+            >
+              <div className="grw-navigation-wrap">
+                <div className="grw-global-navigation">
+                  <GlobalNavigation></GlobalNavigation>
                 </div>
-              </div>
-            </div>
-            <div className="grw-navigation-draggable">
-              { isResizableByDrag && (
                 <div
-                  className="grw-navigation-draggable-hitarea"
-                  onMouseDown={dragableAreaMouseDownHandler}
+                  ref={resizableContainer}
+                  className="grw-contextual-navigation"
+                  onMouseEnter={hoverOnResizableContainerHandler}
+                  onMouseLeave={hoverOutResizableContainerHandler}
+                  style={{ width: isCollapsed ? sidebarMinimizeWidth : currentProductNavWidth }}
                 >
-                  <div className="grw-navigation-draggable-hitarea-child"></div>
+                  <div className="grw-contextual-navigation-child">
+                    <div role="group" data-testid="grw-contextual-navigation-sub" className={`grw-contextual-navigation-sub ${showContents ? '' : 'd-none'}`}>
+                      <SidebarContentsWrapper></SidebarContentsWrapper>
+                    </div>
+                  </div>
                 </div>
-              ) }
-              <button
-                data-testid="grw-navigation-resize-button"
-                className={`grw-navigation-resize-button ${!isDrawerMode ? 'resizable' : ''} ${isCollapsed ? 'collapsed' : ''} `}
-                type="button"
-                aria-expanded="true"
-                aria-label="Toggle navigation"
-                disabled={isDrawerMode}
-                onClick={toggleNavigationBtnClickHandler}
-              >
-                <span className="hexagon-container" role="presentation">
-                  <NavigationResizeHexagon />
-                </span>
-                <span className="hitarea" role="presentation"></span>
-              </button>
+              </div>
+              <div className="grw-navigation-draggable">
+                { isResizableByDrag && (
+                  <div
+                    className="grw-navigation-draggable-hitarea"
+                    onMouseDown={dragableAreaMouseDownHandler}
+                  >
+                    <div className="grw-navigation-draggable-hitarea-child"></div>
+                  </div>
+                ) }
+                <button
+                  data-testid="grw-navigation-resize-button"
+                  className={`grw-navigation-resize-button ${!isDrawerMode ? 'resizable' : ''} ${isCollapsed ? 'collapsed' : ''} `}
+                  type="button"
+                  aria-expanded="true"
+                  aria-label="Toggle navigation"
+                  disabled={isDrawerMode}
+                  onClick={toggleNavigationBtnClickHandler}
+                >
+                  <span className="hexagon-container" role="presentation">
+                    <NavigationResizeHexagon />
+                  </span>
+                  <span className="hitarea" role="presentation"></span>
+                </button>
+              </div>
             </div>
           </div>
         </div>
       </div>
 
       { isDrawerOpened && (
-        <div className="grw-sidebar-backdrop modal-backdrop show" onClick={backdropClickedHandler}></div>
+        <div className={`${styles['grw-sidebar-backdrop']} modal-backdrop show`} onClick={backdropClickedHandler}></div>
       ) }
     </>
   );

+ 1 - 1
packages/app/src/components/Sidebar/SidebarNav.scss → packages/app/src/components/Sidebar/SidebarNav.module.scss

@@ -1,6 +1,6 @@
 @use '~/styles/variables' as var;
 
-.grw-sidebar-nav {
+.grw-sidebar-nav :global {
   $sidebar-nav-button-height: 55px;
 
   %fukidashi-for-active {

+ 3 - 2
packages/app/src/components/Sidebar/SidebarNav.tsx

@@ -5,7 +5,7 @@ import { SidebarContentsType } from '~/interfaces/ui';
 import { useCurrentUser } from '~/stores/context';
 import { useCurrentSidebarContents } from '~/stores/ui';
 
-import './SidebarNav.scss';
+import styles from './SidebarNav.module.scss';
 
 
 type PrimaryItemProps = {
@@ -65,6 +65,7 @@ const SecondaryItem: FC<SecondaryItemProps> = memo((props: SecondaryItemProps) =
     </a>
   );
 });
+SecondaryItem.displayName = 'SecondaryItem';
 
 
 type Props = {
@@ -80,7 +81,7 @@ const SidebarNav: FC<Props> = (props: Props) => {
   const { onItemSelected } = props;
 
   return (
-    <div className="grw-sidebar-nav">
+    <div className={`grw-sidebar-nav ${styles['grw-sidebar-nav']}`}>
       <div className="grw-sidebar-nav-primary-container">
         {/* eslint-disable max-len */}
         <PrimaryItem contents={SidebarContentsType.TREE} label="Page Tree" iconName="format_list_bulleted" onItemSelected={onItemSelected} />

+ 0 - 0
packages/app/src/components/SystemVersion.scss → packages/app/src/components/SystemVersion.module.scss


+ 2 - 2
packages/app/src/components/SystemVersion.tsx

@@ -3,7 +3,7 @@ import React from 'react';
 import { useGrowiVersion } from '~/stores/context';
 import { useShortcutsModal } from '~/stores/modal';
 
-import './SystemVersion.scss';
+import styles from './SystemVersion.module.scss';
 
 const SystemVersion = (): JSX.Element => {
 
@@ -17,7 +17,7 @@ const SystemVersion = (): JSX.Element => {
 
   return (
     <>
-      <div className="system-version d-none d-md-flex d-edit-none d-print-none align-items-center">
+      <div className={`${styles['system-version']} d-none d-md-flex d-edit-none d-print-none align-items-center`}>
         <span>
           <a href="https://growi.org">GROWI</a> {growiVersion}
         </span>

+ 6 - 2
packages/app/src/styles/_layout.scss

@@ -1,6 +1,10 @@
-@use './variables';
+@use './variables' as var;
 @use './bootstrap/init' as bs;
 
+:root {
+  font-size: var.$font-size-root;
+}
+
 body {
   overflow-y: scroll !important;
   overscroll-behavior-y: none;
@@ -30,7 +34,7 @@ body.growi-layout-fluid .grw-container-convertible {
 
 // padding settings for GrowiNavbarBottom
 .page-wrapper {
-  padding-bottom: variables.$grw-navbar-bottom-height;
+  padding-bottom: var.$grw-navbar-bottom-height;
 
   @include bs.media-breakpoint-up(md) {
     padding-bottom: unset;

+ 2 - 0
packages/app/src/styles/_variables.scss

@@ -1,3 +1,5 @@
+$font-size-root: 14px;
+
 //== GROWI Official Color
 $growi-green: #74bc46;
 $growi-blue: #175fa5;

+ 0 - 1
packages/app/src/styles/bootstrap/_variables.scss

@@ -70,7 +70,6 @@ $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-root: 14px;
 $line-height-base: 1.42857;
 
 $blockquote-small-color: $gray-500;

+ 2 - 2
packages/app/src/styles/style-next.scss

@@ -3,8 +3,8 @@
 // // override codemirror
 // @import 'override-codemirror';
 
-// // override react-bootstrap-typeahead styles
-// @import 'override-rbt';
+@import '~react-bootstrap-typeahead/css/Typeahead';
+@import 'override-rbt';
 
 // // override simplebar-react styles
 // @import 'override-simplebar';

+ 4 - 2
packages/app/src/utils/next.config.utils.js

@@ -6,8 +6,10 @@ const path = require('path');
 const nodeModulesPath = path.resolve(__dirname, '../../../../node_modules');
 
 
+const defaultOpts = { ignorePackageNames: [] };
+
 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-export const listScopedPackages = (scopes) => {
+export const listScopedPackages = (scopes, opts = defaultOpts) => {
   const scopedPackages = [];
 
   fs.readdirSync(nodeModulesPath)
@@ -22,7 +24,7 @@ export const listScopedPackages = (scopes) => {
             folderName,
             'package.json',
           ));
-          if (!ignoreTranspileModules) {
+          if (!ignoreTranspileModules && !opts.ignorePackageNames.includes(name)) {
             scopedPackages.push(name);
           }
         });