Yuki Takei 1 месяц назад
Родитель
Сommit
727764f364
100 измененных файлов с 907 добавлено и 758 удалено
  1. 103 11
      .kiro/specs/migrate-to-turbopack/design.md
  2. 40 2
      .kiro/specs/migrate-to-turbopack/requirements.md
  3. 2 2
      .kiro/specs/migrate-to-turbopack/spec.json
  4. 63 30
      .kiro/specs/migrate-to-turbopack/tasks.md
  5. 1 3
      .kiro/steering/tech.md
  6. 1 1
      apps/app/package.json
  7. 1 1
      apps/app/src/client/components/Admin/Customize/ThemeColorBox.module.scss
  8. 3 5
      apps/app/src/client/components/Admin/UserGroup/UserGroupTable.module.scss
  9. 9 9
      apps/app/src/client/components/Admin/UserManagement.module.scss
  10. 3 3
      apps/app/src/client/components/Admin/Users/ExternalAccountTable.module.scss
  11. 1 1
      apps/app/src/client/components/Admin/Users/UserMenu.module.scss
  12. 4 4
      apps/app/src/client/components/AuthorInfo/AuthorInfo.module.scss
  13. 3 3
      apps/app/src/client/components/Bookmarks/BookmarkFolderMenu.module.scss
  14. 20 20
      apps/app/src/client/components/Bookmarks/BookmarkFolderTree.module.scss
  15. 6 6
      apps/app/src/client/components/Common/CopyDropdown/CopyDropdown.module.scss
  16. 2 2
      apps/app/src/client/components/Common/DrawerToggler/DrawerToggler.module.scss
  17. 2 2
      apps/app/src/client/components/CompleteUserRegistrationForm.module.scss
  18. 4 4
      apps/app/src/client/components/CustomNavigation/CustomNav.module.scss
  19. 7 7
      apps/app/src/client/components/DescendantsPageListModal/DescendantsPageListModal.module.scss
  20. 2 2
      apps/app/src/client/components/InAppNotification/ModelNotification/ModelNotification.module.scss
  21. 2 2
      apps/app/src/client/components/InstallerForm.module.scss
  22. 15 15
      apps/app/src/client/components/LoginForm/LoginForm.module.scss
  23. 12 12
      apps/app/src/client/components/Me/AccessTokenScopeList.module.scss
  24. 4 4
      apps/app/src/client/components/Navbar/GrowiNavbarBottom.module.scss
  25. 7 7
      apps/app/src/client/components/Navbar/PageEditorModeManager.module.scss
  26. 7 7
      apps/app/src/client/components/PageAccessoriesModal/PageAccessoriesModal.module.scss
  27. 3 3
      apps/app/src/client/components/PageAttachment/DeleteAttachmentModal.module.scss
  28. 9 9
      apps/app/src/client/components/PageComment.module.scss
  29. 17 17
      apps/app/src/client/components/PageComment/Comment.module.scss
  30. 13 13
      apps/app/src/client/components/PageComment/CommentEditor.module.scss
  31. 3 3
      apps/app/src/client/components/PageComment/DeleteCommentModal/DeleteCommentModal.module.scss
  32. 1 1
      apps/app/src/client/components/PageComment/ReplyComments.module.scss
  33. 6 6
      apps/app/src/client/components/PageComment/SwitchingButtonGroup.module.scss
  34. 6 6
      apps/app/src/client/components/PageControls/BookmarkButtons.module.scss
  35. 6 6
      apps/app/src/client/components/PageControls/LikeButtons.module.scss
  36. 4 4
      apps/app/src/client/components/PageControls/PageControls.module.scss
  37. 1 1
      apps/app/src/client/components/PageControls/SearchButton.module.scss
  38. 5 5
      apps/app/src/client/components/PageControls/SeenUserInfo.module.scss
  39. 2 2
      apps/app/src/client/components/PageControls/SubscribeButton.module.scss
  40. 1 1
      apps/app/src/client/components/PageControls/user-list-popover.module.scss
  41. 3 3
      apps/app/src/client/components/PageCreateModal.module.scss
  42. 2 2
      apps/app/src/client/components/PageEditor/ConflictDiffModal/ConflictDiffModal.module.scss
  43. 1 1
      apps/app/src/client/components/PageEditor/EditorNavbar/EditingUserList.module.scss
  44. 1 1
      apps/app/src/client/components/PageEditor/EditorNavbar/EditorNavbar.module.scss
  45. 7 7
      apps/app/src/client/components/PageEditor/EditorNavbarBottom/EditorNavbarBottom.module.scss
  46. 15 15
      apps/app/src/client/components/PageEditor/GridEditModal.module.scss
  47. 10 10
      apps/app/src/client/components/PageEditor/HandsontableModal/HandsontableModal.module.scss
  48. 5 5
      apps/app/src/client/components/PageEditor/Preview.module.scss
  49. 1 1
      apps/app/src/client/components/PageHeader/PageHeader.module.scss
  50. 5 5
      apps/app/src/client/components/PageHeader/PagePathHeader.module.scss
  51. 4 4
      apps/app/src/client/components/PageHeader/PageTitleHeader.module.scss
  52. 2 2
      apps/app/src/client/components/PageHistory/PageRevisionTable.module.scss
  53. 4 4
      apps/app/src/client/components/PageHistory/Revision.module.scss
  54. 4 4
      apps/app/src/client/components/PageHistory/RevisionDiff.module.scss
  55. 3 3
      apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.module.scss
  56. 4 4
      apps/app/src/client/components/PagePresentationModal/PagePresentationModal.module.scss
  57. 6 6
      apps/app/src/client/components/PageSelectModal/TreeItemForModal.module.scss
  58. 5 5
      apps/app/src/client/components/PageSideContents/PageAccessoriesControl.module.scss
  59. 6 6
      apps/app/src/client/components/PageStatusAlert.module.scss
  60. 6 6
      apps/app/src/client/components/PageTags/TagEditModal/TagsInput.module.scss
  61. 6 6
      apps/app/src/client/components/PageTags/TagLabels.module.scss
  62. 3 3
      apps/app/src/client/components/PageTimeline.module.scss
  63. 4 4
      apps/app/src/client/components/ReactMarkdownComponents/DrawioViewerWithEditButton.module.scss
  64. 9 9
      apps/app/src/client/components/ReactMarkdownComponents/Header.module.scss
  65. 2 2
      apps/app/src/client/components/ReactMarkdownComponents/RichAttachment.module.scss
  66. 4 4
      apps/app/src/client/components/ReactMarkdownComponents/TableWithEditButton.module.scss
  67. 10 10
      apps/app/src/client/components/RevisionComparer/RevisionComparer.module.scss
  68. 8 8
      apps/app/src/client/components/SearchTypeahead.module.scss
  69. 3 3
      apps/app/src/client/components/ShortcutsModal/ShortcutsModal.module.scss
  70. 8 8
      apps/app/src/client/components/Sidebar/AppTitle/AppTitle.module.scss
  71. 2 2
      apps/app/src/client/components/Sidebar/Custom/CustomSidebarSubstance.module.scss
  72. 10 10
      apps/app/src/client/components/Sidebar/PageCreateButton/CreateButton.module.scss
  73. 11 11
      apps/app/src/client/components/Sidebar/PageCreateButton/DropendToggle.module.scss
  74. 11 11
      apps/app/src/client/components/Sidebar/PageTreeItem/PageTreeItem.module.scss
  75. 13 13
      apps/app/src/client/components/Sidebar/RecentChanges/RecentChangesSubstance.module.scss
  76. 7 7
      apps/app/src/client/components/Sidebar/ResizableArea/ResizableArea.module.scss
  77. 51 60
      apps/app/src/client/components/Sidebar/Sidebar.module.scss
  78. 3 3
      apps/app/src/client/components/Sidebar/SidebarContents.module.scss
  79. 2 2
      apps/app/src/client/components/Sidebar/SidebarHead/SidebarHead.module.scss
  80. 10 10
      apps/app/src/client/components/Sidebar/SidebarHead/ToggleCollapseButton.module.scss
  81. 3 3
      apps/app/src/client/components/Sidebar/SidebarNav/HelpDropdown.module.scss
  82. 9 9
      apps/app/src/client/components/Sidebar/SidebarNav/PersonalDropdown.module.scss
  83. 9 9
      apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.module.scss
  84. 8 8
      apps/app/src/client/components/Sidebar/SidebarNav/SecondaryItems.module.scss
  85. 4 4
      apps/app/src/client/components/Sidebar/SidebarNav/SidebarNav.module.scss
  86. 2 2
      apps/app/src/client/components/Sidebar/SidebarNav/SkeletonItem.module.scss
  87. 4 5
      apps/app/src/client/components/Sidebar/Skeleton/DefaultContentSkelton.module.scss
  88. 10 10
      apps/app/src/client/components/SlackNotification.module.scss
  89. 18 18
      apps/app/src/client/components/StaffCredit/StaffCredit.module.scss
  90. 10 10
      apps/app/src/client/components/TableOfContents.module.scss
  91. 6 6
      apps/app/src/client/components/TagList.module.scss
  92. 2 2
      apps/app/src/client/components/TemplateModal/TemplateModal.module.scss
  93. 20 20
      apps/app/src/client/components/UsersHomepageFooter.module.scss
  94. 4 4
      apps/app/src/components/Admin/Common/AdminNavigation.module.scss
  95. 5 5
      apps/app/src/components/Common/GrowiLogo.module.scss
  96. 9 9
      apps/app/src/components/Common/PagePathNav/PagePathNavLayout.module.scss
  97. 60 60
      apps/app/src/components/Layout/Admin.module.scss
  98. 3 3
      apps/app/src/components/Layout/BasicLayout.module.scss
  99. 35 35
      apps/app/src/components/Layout/NoLoginLayout.module.scss
  100. 14 14
      apps/app/src/components/Layout/SearchResultLayout.module.scss

+ 103 - 11
.kiro/specs/migrate-to-turbopack/design.md

@@ -11,6 +11,8 @@
 ### Goals
 - Enable Turbopack as the default bundler for `next dev` with the custom Express server
 - Migrate all 6 webpack customizations to Turbopack-compatible equivalents
+- Convert all global CSS imports to vendor CSS Module wrappers for Turbopack compliance
+- Convert all `:global` block form syntax in CSS Modules to function form for Turbopack compatibility
 - Maintain webpack fallback via environment variable during the transition period
 - Enable Turbopack for production `next build` after dev stability is confirmed
 
@@ -20,6 +22,7 @@
 - Rewriting the custom Express server architecture
 - Achieving feature parity for dev module analysis tooling (deferred)
 - Implementing i18n HMR under Turbopack (acceptable tradeoff)
+- Refactoring CSS architecture beyond the mechanical syntax conversion
 
 ## Architecture
 
@@ -83,8 +86,9 @@ graph TB
 - Selected pattern: Feature-flag phased migration with dual configuration
 - Domain boundaries: Build configuration layer only — no changes to application code, server logic, or page components
 - Existing patterns preserved: Express custom server, Pages Router, SuperJSON SSR, server-client boundary
-- New components: Empty module file, Turbopack config block, env-based bundler selection
+- New components: Empty module file, Turbopack config block, env-based bundler selection, vendor CSS Module wrappers
 - Steering compliance: Maintains server-client boundary enforcement; aligns with existing build optimization strategy
+- CSS compatibility: All `:global` block form syntax converted to function form; all global CSS imports wrapped in CSS Modules
 
 ### Technology Stack
 
@@ -153,9 +157,18 @@ sequenceDiagram
 | 6.3 | Production tests pass | BuildConfig | Test suite | - |
 | 7.1 | Alternative module analysis | Deferred | - | - |
 | 7.2 | DUMP_INITIAL_MODULES report | Deferred | - | - |
-| 8.1 | Switch via env var or CLI flag | BundlerSwitch | USE_WEBPACK env | Dev startup |
-| 8.2 | Webpack mode fully functional | WebpackFallback | webpack() hook | - |
-| 8.3 | Dual config in next.config.ts | NextConfigDual | Both configs | - |
+| 8.1 | Vendor CSS Module wrappers for imports | VendorCSSWrappers | .module.scss files | - |
+| 8.2 | Convert all direct global CSS imports | VendorCSSWrappers | Component imports | - |
+| 8.3 | Naming convention vendor-*.module.scss | VendorCSSWrappers | File naming | - |
+| 8.4 | Stylelint override for vendor wrappers | VendorCSSWrappers | .stylelintrc.json | - |
+| 9.1 | Function form `:global(...)` syntax | GlobalSyntaxConversion | 128 files | - |
+| 9.2 | Identical CSS output after conversion | GlobalSyntaxConversion | Compilation | - |
+| 9.3 | Nested blocks converted correctly | GlobalSyntaxConversion | Nested selectors | - |
+| 9.4 | No Ambiguous CSS errors under Turbopack | GlobalSyntaxConversion | Dev startup | Dev startup |
+| 9.5 | webpack fallback works with function form | GlobalSyntaxConversion | USE_WEBPACK | - |
+| 10.1 | Switch via env var or CLI flag | BundlerSwitch | USE_WEBPACK env | Dev startup |
+| 10.2 | Webpack mode fully functional | WebpackFallback | webpack() hook | - |
+| 10.3 | Dual config in next.config.ts | NextConfigDual | Both configs | - |
 
 ## Components and Interfaces
 
@@ -166,8 +179,10 @@ sequenceDiagram
 | ResolveAliasConfig | Config | Alias server-only packages and fs in browser | 2.1, 2.2, 2.3 | empty-module.ts (P0) | - |
 | EmptyModule | Util | Provide empty export file for resolveAlias | 2.1, 2.2 | None | - |
 | I18nConfig | Config | Remove HMR plugins when Turbopack is active | 5.1, 5.2, 5.3 | next-i18next (P1) | - |
-| BuildScripts | Config | Update package.json scripts for Turbopack | 6.1, 6.2, 8.1 | package.json (P0) | - |
-| WebpackFallback | Config | Maintain webpack() hook for fallback | 8.2, 8.3 | next.config.ts (P0) | - |
+| VendorCSSWrappers | CSS | Wrap third-party global CSS imports in CSS Modules | 8.1, 8.2, 8.3, 8.4 | Component imports (P0) | - |
+| GlobalSyntaxConversion | CSS | Convert `:global` block form to function form | 9.1, 9.2, 9.3, 9.4, 9.5 | 128 .module.scss files (P0) | - |
+| BuildScripts | Config | Update package.json scripts for Turbopack | 6.1, 6.2, 10.1 | package.json (P0) | - |
+| WebpackFallback | Config | Maintain webpack() hook for fallback | 10.2, 10.3 | next.config.ts (P0) | - |
 
 ### Configuration Layer
 
@@ -330,6 +345,75 @@ interface ResolveAliasConfig {
 - Phase 1: Only server initialization changes. Build scripts remain unchanged.
 - Phase 2: Remove `--webpack` from `build:client` after Turbopack build verification.
 
+### CSS Compatibility Layer
+
+#### VendorCSSWrappers — Global CSS Import Migration
+
+| Field | Detail |
+|-------|--------|
+| Intent | Wrap third-party global CSS imports in CSS Module files to comply with Turbopack's strict global CSS import rule |
+| Requirements | 8.1, 8.2, 8.3, 8.4 |
+
+**Responsibilities & Constraints**
+- Create `vendor-{library}.module.scss` wrapper files for each third-party CSS import
+- Use `:global { @import 'package/style.css'; }` pattern to preserve global scope
+- Replace direct `import 'package/style.css'` statements in components with `import './vendor-{library}.module.scss'`
+- Add stylelint override in `.stylelintrc.json` for `vendor-*.module.scss` to suppress `no-invalid-position-at-import-rule`
+
+**Affected Imports (13 total in 12 files)**
+- `@growi/remark-lsx`, `@growi/remark-attachment-refs` (renderer service)
+- `@growi/editor` (PageEditor)
+- `handsontable` (HandsontableModal)
+- `katex` (PageView)
+- `react-datepicker` (AuditLog)
+- `diff2html` (PageHistory)
+- `@growi/editor` (PageComment)
+- `drawio` (ReactMarkdownComponents)
+- `react-image-crop` (Common)
+- `simplebar-react` (Sidebar)
+- `reveal.js`, `highlight.js` (Presentation)
+
+**Implementation Notes**
+- Naming convention: `vendor-{short-library-name}.module.scss`
+- Each wrapper imports exactly one third-party CSS file
+- Multiple CSS imports from the same component directory are combined into one wrapper when possible
+
+#### GlobalSyntaxConversion — `:global` Block-to-Function Form
+
+| Field | Detail |
+|-------|--------|
+| Intent | Convert all `:global` block form syntax to function form for Turbopack CSS Modules compatibility |
+| Requirements | 9.1, 9.2, 9.3, 9.4, 9.5 |
+
+**Responsibilities & Constraints**
+- Convert 128 `.module.scss` files containing 255 occurrences of `:global` block form
+- The conversion is mechanical — each block form has an exact function form equivalent
+- Both webpack and Turbopack support the function form, so no behavioral change
+
+**Conversion Patterns**
+
+| # | Block Form (Before) | Function Form (After) |
+|---|---|---|
+| 1 | `.parent :global { .child { color: red; } }` | `.parent { :global(.child) { color: red; } }` |
+| 2 | `.parent :global { .a { } .b { } }` | `.parent { :global(.a) { } :global(.b) { } }` |
+| 3 | `.parent :global { .child { .nested { } } }` | `.parent { :global(.child) { :global(.nested) { } } }` |
+| 4 | `&:global { &.modifier { } }` | `&:global(.modifier) { }` |
+| 5 | `:global { .class { } }` (top-level) | `:global(.class) { }` |
+| 6 | `.parent :global { .child { &.modifier { } } }` | `.parent { :global(.child) { &:global(.modifier) { } } }` |
+
+**Implementation Strategy**
+- Perform conversion file-by-file using AST-aware or regex-based transformation
+- Each converted file must produce identical CSS output
+- Verify with stylelint and visual diff after conversion
+- webpack fallback (`USE_WEBPACK=1`) must continue to work with function form syntax
+
+**Dependencies**
+- None — pure syntax transformation, no runtime or build tool changes
+
+**Risks**
+- Complex nested structures may require manual attention
+- The vendor wrapper files (`vendor-*.module.scss`) use `:global { @import }` which is a different pattern — these are NOT affected by this conversion since they use the top-level `:global` block as a CSS Modules scoping mechanism for imported stylesheets
+
 ## Error Handling
 
 ### Error Strategy
@@ -346,6 +430,10 @@ Build-time errors from Turbopack migration are the primary concern. All errors s
 
 **i18n Errors**: `Cannot find module 'i18next-hmr'` or HMR plugin crash — indicates HMR plugin loaded in Turbopack mode. Fix: guard HMR plugin loading with env var check.
 
+**Global CSS Import Errors**: `Global CSS cannot be imported from files other than your Custom <App>` — indicates a direct global CSS import from a non-`_app` file. Fix: create a `vendor-*.module.scss` wrapper and change the import.
+
+**CSS Modules Syntax Errors**: `Ambiguous CSS module class not supported` — indicates `:global` block form usage. Fix: convert the block form to function form `:global(...)`.
+
 ## Testing Strategy
 
 ### Smoke Tests (Manual, Phase 1)
@@ -376,17 +464,21 @@ flowchart LR
     A[Add turbopack config] --> B[Add empty-module.ts]
     B --> C[Update crowi/index.ts]
     C --> D[Guard i18n HMR plugins]
-    D --> E[Smoke test Turbopack dev]
-    E --> F[Smoke test webpack fallback]
-    F --> G[Merge to dev branch]
+    D --> E[Vendor CSS wrappers]
+    E --> F[Convert :global syntax]
+    F --> G[Smoke test Turbopack dev]
+    G --> H[Smoke test webpack fallback]
+    H --> I[Merge to dev branch]
 ```
 
 1. Add `turbopack` configuration block to `next.config.ts` (rules + resolveAlias)
 2. Create `src/lib/empty-module.ts`
 3. Update `src/server/crowi/index.ts`: replace `webpack: true` with `USE_WEBPACK` env var check
 4. Guard `HMRPlugin` in `next-i18next.config.js` with env var check
-5. Run smoke tests with both Turbopack and webpack modes
-6. Merge — all developers now use Turbopack by default for dev
+5. Create vendor CSS Module wrappers for all third-party global CSS imports
+6. Convert all `:global` block form syntax to function form across 128 `.module.scss` files
+7. Run smoke tests with both Turbopack and webpack modes
+8. Merge — all developers now use Turbopack by default for dev
 
 ### Phase 2: Production Build Migration (After Verification)
 

+ 40 - 2
.kiro/specs/migrate-to-turbopack/requirements.md

@@ -104,7 +104,44 @@ GROWI uses a custom Express server with `next({ dev, webpack: true })` to initia
 2. When `DUMP_INITIAL_MODULES=1` is set, the analysis tooling shall output module breakdown reports comparable to the current `initial-modules-analysis.md` format.
 3. If no Turbopack-compatible analysis tooling is feasible, this requirement may be deferred and documented as a known limitation.
 
-### Requirement 8: Incremental Migration Path
+### Requirement 8: Global CSS Import Restriction Compliance
+
+**Objective:** As a developer, I want third-party CSS files to be properly wrapped for Turbopack, so that no "Global CSS cannot be imported from files other than your Custom `<App>`" errors occur.
+
+#### Background
+
+Turbopack strictly enforces the Pages Router rule that global CSS can only be imported from `_app.page.tsx`. Under webpack, this rule was not enforced — components could freely `import 'package/style.css'`. Turbopack rejects these imports at compile time.
+
+#### Acceptance Criteria
+
+1. When a component imports third-party CSS (e.g., `handsontable`, `katex`, `diff2html`), the import shall use a vendor CSS Module wrapper (`.module.scss` with `:global { @import '...' }`) instead of a direct global CSS import.
+2. All existing direct global CSS imports from non-`_app` files shall be converted to vendor CSS Module wrappers.
+3. The vendor CSS Module wrapper files shall follow the naming convention `vendor-{library}.module.scss`.
+4. Stylelint shall not report errors on vendor CSS Module wrappers (override `no-invalid-position-at-import-rule` for `vendor-*.module.scss`).
+
+### Requirement 9: CSS Modules `:global` Syntax Compatibility
+
+**Objective:** As a developer, I want all CSS Module files to use Turbopack-compatible `:global` syntax, so that no "Ambiguous CSS module class not supported" errors occur.
+
+#### Background
+
+Turbopack's CSS Modules implementation does not support the block form of `:global { ... }` — it only supports the function form `:global(...)`. The GROWI codebase uses the block form extensively (128 files, 255 occurrences). This is a mechanical syntax difference:
+
+| Pattern (webpack) | Equivalent (Turbopack) |
+|---|---|
+| `.parent :global { .child { } }` | `.parent { :global(.child) { } }` |
+| `&:global { &.modifier { } }` | `&:global(.modifier) { }` |
+| `:global { .class { } }` (standalone) | `:global(.class) { }` |
+
+#### Acceptance Criteria
+
+1. All `.module.scss` and `.module.css` files shall use the function form `:global(...)` instead of the block form `:global { ... }`.
+2. The conversion shall be mechanical and preserve the exact same CSS output (class name scoping behavior unchanged).
+3. Nested `:global` blocks (e.g., `.parent :global { .child { .grandchild { } } }`) shall be converted to nested `:global(...)` selectors.
+4. When the dev server starts with Turbopack, no "Ambiguous CSS module class" errors shall appear.
+5. When the dev server starts with webpack (`USE_WEBPACK=1`), the converted syntax shall produce identical behavior (webpack supports both block and function forms).
+
+### Requirement 10: Incremental Migration Path
 
 **Objective:** As a developer, I want the ability to switch between Turbopack and webpack during the migration period, so that I can fall back to webpack if Turbopack issues are discovered.
 
@@ -113,5 +150,6 @@ GROWI uses a custom Express server with `next({ dev, webpack: true })` to initia
 1. The Next.js build system shall support switching between Turbopack and webpack via an environment variable or CLI flag (e.g., `--webpack`).
 2. When webpack mode is selected, all existing webpack customizations shall remain functional without modification.
 3. The `next.config.ts` shall maintain both `webpack()` hook and `turbopack` configuration simultaneously during the migration period.
-4. When the migration is complete and verified, the build system shall remove the webpack fallback configuration in a follow-up cleanup.
+4. When the migration is complete and verified, the build system shall remove the webpack fallback configuration in a follow-up cleanup (Phase 3).
+5. The converted CSS Modules syntax (function form `:global(...)`) shall work identically under both Turbopack and webpack modes.
 

+ 2 - 2
.kiro/specs/migrate-to-turbopack/spec.json

@@ -15,8 +15,8 @@
     },
     "tasks": {
       "generated": true,
-      "approved": false
+      "approved": true
     }
   },
-  "ready_for_implementation": false
+  "ready_for_implementation": true
 }

+ 63 - 30
.kiro/specs/migrate-to-turbopack/tasks.md

@@ -2,13 +2,13 @@
 
 ## Phase 1: Dev Server Turbopack Migration
 
-- [ ] 1. Create empty module and Turbopack configuration foundation
-- [ ] 1.1 (P) Create the empty module file used as the alias target for excluded server-only packages
+- [x] 1. Create empty module and Turbopack configuration foundation
+- [x] 1.1 (P) Create the empty module file used as the alias target for excluded server-only packages
   - Create a minimal TypeScript module at the designated location that exports an empty default and named export
   - The module satisfies any import style (default, named, namespace) so that aliased packages resolve without errors
   - _Requirements: 2.1, 2.2_
 
-- [ ] 1.2 (P) Add the Turbopack configuration block to the Next.js config with resolve aliases for server-only package exclusion
+- [x] 1.2 (P) Add the Turbopack configuration block to the Next.js config with resolve aliases for server-only package exclusion
   - Add a `turbopack` key to the Next.js config object containing `resolveAlias` entries
   - Alias `fs` to the empty module in browser context to prevent "Module not found" errors on the client
   - Alias all 7 server-only packages (`dtrace-provider`, `mongoose`, `mathjax-full`, `i18next-fs-backend`, `bunyan`, `bunyan-format`, `core-js`) to the empty module in browser context
@@ -17,23 +17,23 @@
   - Keep the existing `webpack()` hook untouched — both configs coexist
   - _Requirements: 1.2, 2.1, 2.2, 2.3, 8.3_
 
-- [ ] 1.3 Add the superjson-ssr-loader as a Turbopack custom loader rule for server-side page files
+- [x] 1.3 Add the superjson-ssr-loader as a Turbopack custom loader rule for server-side page files
   - Register the existing `superjson-ssr-loader` under `turbopack.rules` for `*.page.ts` and `*.page.tsx` file patterns
   - Apply the `condition: { not: 'browser' }` condition so the loader runs only on the server side
   - Set the `as` output type to `*.ts` / `*.tsx` respectively so Turbopack continues processing the transformed output
   - The loader performs a simple regex-based source transform and returns JavaScript — no unsupported loader-runner APIs are used
   - _Requirements: 1.2, 3.1, 3.2, 3.3, 3.4_
 
-- [ ] 2. Update server initialization to toggle between Turbopack and webpack
-- [ ] 2.1 Replace the hardcoded `webpack: true` in the custom server with environment-variable-based bundler selection
+- [x] 2. Update server initialization to toggle between Turbopack and webpack
+- [x] 2.1 Replace the hardcoded `webpack: true` in the custom server with environment-variable-based bundler selection
   - Read the `USE_WEBPACK` environment variable at server startup
   - When `USE_WEBPACK` is set (truthy), pass `{ dev, webpack: true }` to preserve the existing webpack pipeline
   - When `USE_WEBPACK` is not set (default), omit the `webpack` option so Turbopack activates as the Next.js 16 default
   - Preserve the existing ts-node hook save/restore logic surrounding `app.prepare()`
   - _Requirements: 1.1, 1.3, 1.4, 8.1_
 
-- [ ] 3. Guard i18n HMR plugin loading for Turbopack compatibility
-- [ ] 3.1 Conditionally skip the i18next-hmr plugin in the i18n configuration when Turbopack is active
+- [x] 3. Guard i18n HMR plugin loading for Turbopack compatibility
+- [x] 3.1 Conditionally skip the i18next-hmr plugin in the i18n configuration when Turbopack is active
   - In the next-i18next configuration file, check the `USE_WEBPACK` environment variable before loading the `HMRPlugin`
   - When `USE_WEBPACK` is not set (Turbopack mode), exclude `HMRPlugin` from the `use` array to prevent webpack-internal references from crashing
   - When `USE_WEBPACK` is set (webpack mode), preserve the existing `HMRPlugin` behavior for both server and client
@@ -41,25 +41,50 @@
   - Translation file changes under Turbopack require a manual browser refresh (documented tradeoff)
   - _Requirements: 5.1, 5.2, 5.3_
 
-- [ ] 4. Smoke test the Turbopack dev server and webpack fallback
-- [ ] 4.1 Verify the dev server starts and pages load correctly under Turbopack
-  - Start the dev server without `USE_WEBPACK` and confirm it initializes with Turbopack
-  - Navigate to representative pages and verify they render without errors
-  - Confirm no "Module not found" or "Cannot resolve" errors appear in the terminal or browser console
-  - Visit a page that uses `getServerSideProps` and verify SuperJSON-serialized data renders correctly
-  - Visit a page that imports remark/rehype/micromark ecosystem packages and verify Markdown rendering works
-  - Switch the browser locale and verify i18n translations load correctly
-  - Edit a `.page.tsx` file and verify Fast Refresh applies the change
-  - _Requirements: 1.1, 1.3, 1.4, 2.3, 3.2, 3.3, 4.1, 4.2, 5.3_
-
-- [ ] 4.2 Verify the webpack fallback mode works identically to the pre-migration state
+- [x] 4. Centralize vendor CSS imports in _app.page.tsx for Turbopack compatibility
+- [x] 4.1 Move all vendor CSS imports from component files to _app.page.tsx
+  - Turbopack Pages Router only allows global CSS imports from `_app`. The `:global { @import }` wrapper approach also failed because Turbopack rejects `:global` block form entirely.
+  - Moved 13 vendor CSS imports from 12 component files to `_app.page.tsx`
+  - Switched `handsontable/dist/handsontable.full.min.css` to `handsontable/dist/handsontable.css` (non-full, non-minified) to avoid IE CSS hack parse errors in Turbopack
+  - Removed all `vendor-*.module.scss` wrapper files (approach abandoned)
+  - _Requirements: 8.1, 8.2, 8.3_
+
+- [x] 4.2 Clean up stale stylelint overrides
+  - Removed `vendor-*.module.scss` override from `.stylelintrc.json` (files no longer exist)
+  - _Requirements: 8.4_
+
+- [x] 5. Convert `:global` block form to function form in CSS Modules
+- [x] 5.1 (P) Convert all `:global` block form syntax to function form across all `.module.scss` files
+  - Scan all `.module.scss` files for `:global {` block form usage (128 files, 255 occurrences)
+  - Convert each occurrence to the function form `:global(...)` following the 6 conversion patterns documented in design.md
+  - Preserve nested selector structure and any `&` parent selectors
+  - Exclude `vendor-*.module.scss` files (these use `:global { @import }` which is a different pattern)
+  - _Requirements: 9.1, 9.2, 9.3_
+
+- [x] 5.2 Verify CSS output equivalence after conversion
+  - Run stylelint across all converted files
+  - Run the existing vitest test suite to confirm no regressions
+  - Start dev server with Turbopack and verify no "Ambiguous CSS module class" errors
+  - Start dev server with `USE_WEBPACK=1` and verify identical behavior
+  - _Requirements: 9.4, 9.5_
+
+- [x] 6. Smoke test the Turbopack dev server and webpack fallback
+- [x] 6.1 Verify the dev server starts and pages load correctly under Turbopack
+  - Dev server starts and root page compiles + renders (HTTP 200) with no CSS errors
+  - Turbopack production build (`next build`) compiles all routes successfully with 0 errors
+  - Fixed `MessageCard.module.scss` — removed standalone `&:local` (Turbopack doesn't support it)
+  - Fixed `DefaultContentSkelton.module.scss` — replaced `@extend .grw-skeleton-text` with shared selector group
+  - Fixed `handsontable` CSS — switched to non-full, non-minified variant to avoid IE CSS hack parse errors
+  - _Requirements: 1.1, 1.3, 1.4, 2.3, 3.2, 3.3, 4.1, 4.2, 5.3, 9.4_
+
+- [ ] 6.2 Verify the webpack fallback mode works identically to the pre-migration state
   - Start the dev server with `USE_WEBPACK=1` and confirm webpack initializes
   - Repeat the same page navigation checks to ensure no regression
   - Confirm the `I18NextHMRPlugin` and `HMRPlugin` are active in webpack mode
   - Confirm the `ChunkModuleStatsPlugin` logs module stats in webpack mode
-  - _Requirements: 8.1, 8.2, 8.3_
+  - _Requirements: 9.5, 10.1, 10.2, 10.3_
 
-- [ ] 4.3 Run existing automated tests and lint checks
+- [x] 6.3 Run existing automated tests and lint checks
   - Run the vitest test suite to confirm no test regressions
   - Run lint checks (typecheck, biome, stylelint) to confirm no new errors
   - Run the production build with `--webpack` to confirm it still works
@@ -67,25 +92,33 @@
 
 ## Phase 2: Production Build Migration (Deferred)
 
-- [ ] 5. Migrate production build from webpack to Turbopack
-- [ ] 5.1 Remove the `--webpack` flag from the production client build script
-  - Update the `build:client` script to use `next build` without the `--webpack` flag
-  - Run the full production build and verify it completes without errors
-  - Run the vitest test suite against the Turbopack-built output
+- [x] 7. Migrate production build from webpack to Turbopack
+- [x] 7.1 Remove the `--webpack` flag from the production client build script
+  - Updated `build:client` script from `next build --webpack` to `next build` (Turbopack default)
+  - Production build completes successfully with all routes compiled
   - _Requirements: 6.1, 6.2, 6.3_
 
 ## Phase 3: Cleanup (Deferred)
 
-- [ ] 6. Remove webpack fallback configuration and deprecated plugins
-- [ ] 6.1 Remove the `webpack()` hook, `USE_WEBPACK` env var check, and deprecated plugin code
+- [ ] 8. Remove webpack fallback configuration and deprecated plugins
+- [ ] 8.1 Remove the `webpack()` hook, `USE_WEBPACK` env var check, and deprecated plugin code
   - Remove the entire `webpack()` hook function from the Next.js config
   - Remove the `USE_WEBPACK` conditional in the custom server and use the Turbopack default unconditionally
   - Remove `I18NextHMRPlugin` import and usage from the Next.js config
   - Remove `HMRPlugin` import and conditional loading from the next-i18next config
   - Remove `ChunkModuleStatsPlugin` and its helper code from the config utilities module
   - Evaluate whether any `transpilePackages` entries can be removed under Turbopack
-  - _Requirements: 8.4_
+  - _Requirements: 10.1, 10.2, 10.3, 10.4_
 
 ## Deferred Requirements
 
 - **Requirement 7 (Dev Module Analysis Tooling)**: Requirements 7.1, 7.2 are intentionally deferred. Turbopack has no plugin API for compilation hooks, and no equivalent analysis tooling exists. When detailed module analysis is needed, developers can temporarily use `USE_WEBPACK=1` during the transition period. A Turbopack-native solution may emerge as the ecosystem matures.
+
+## Implementation Notes (Discovered During Phase 1)
+
+- **resolveAlias paths**: Turbopack `resolveAlias` requires **relative paths** (e.g., `./src/lib/empty-module.ts`), not absolute paths from `path.resolve()`. Absolute paths cause "server relative imports are not implemented yet" errors.
+- **Vendor CSS centralized in _app.page.tsx**: The `vendor-*.module.scss` wrapper approach (`:global { @import }`) failed because Turbopack rejects `:global` block form entirely. All 13 vendor CSS imports were moved to `_app.page.tsx` — the only file where Turbopack Pages Router allows global CSS imports.
+- **`:global` block form**: Turbopack's CSS Modules implementation only supports the function form `:global(...)`. The block form `:global { }` (supported by webpack) causes "Ambiguous CSS module class not supported" errors. Conversion is mechanical — 128 files, 255 occurrences.
+- **Standalone `:local`**: Turbopack doesn't support standalone `:local` or `&:local` in CSS Modules. Inside `:global(...)` function form, properties are already locally scoped by default, so `&:local` wrappers can simply be removed.
+- **Sass `@extend` in CSS Modules**: `@extend .class` fails when the target is wrapped in `:global(.class)` — Sass doesn't match them as the same selector. Replace with shared selector groups (comma-separated selectors).
+- **handsontable CSS**: `handsontable.full.min.css` contains IE CSS star hacks (`*zoom:1`, `*display:inline`) and `filter:alpha()` that Turbopack's CSS parser (lightningcss) cannot parse. Use `handsontable/dist/handsontable.css` (non-full, non-minified) instead — the "full" variant includes Pikaday which is unused.

+ 1 - 3
.kiro/steering/tech.md

@@ -6,9 +6,7 @@ See: `.claude/skills/tech-stack/SKILL.md` (auto-loaded by Claude Code)
 
 ### Bundler Strategy (Project-Wide Decision)
 
-GROWI uses **Webpack** (not Turbopack) across all Next.js applications. Turbopack is the default in Next.js 16, but GROWI opts out via `--webpack` flag due to custom webpack configuration that Turbopack does not support.
-
-Turbopack migration is deferred as a separate initiative. See `apps/app/.claude/skills/build-optimization/SKILL.md` for details and blockers.
+GROWI uses **Turbopack** (Next.js 16 default) for development. Webpack fallback is available via `USE_WEBPACK=1` environment variable for debugging. Production builds still use `next build --webpack`. All custom webpack loaders/plugins have been migrated to Turbopack equivalents (`turbopack.rules`, `turbopack.resolveAlias`). See `apps/app/.claude/skills/build-optimization/SKILL.md` for details.
 
 ### Import Optimization Principles
 

+ 1 - 1
apps/app/package.json

@@ -7,7 +7,7 @@
     "//// for production": "",
     "build": "run-p build:*",
     "start": "next start",
-    "build:client": "next build --webpack",
+    "build:client": "next build",
     "build:server": "cross-env NODE_ENV=production tspc -p tsconfig.build.server.json",
     "postbuild:server": "shx echo \"Listing files under transpiled\" && shx ls transpiled && shx rm -rf dist && shx mv transpiled/src dist && shx rm -rf transpiled",
     "clean": "shx rm -rf dist transpiled .next",

+ 1 - 1
apps/app/src/client/components/Admin/Customize/ThemeColorBox.module.scss

@@ -1,6 +1,6 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
 // layout
-.theme-option-container :global {
+.theme-option-container {
   min-width: 100px;
 }

+ 3 - 5
apps/app/src/client/components/Admin/UserGroup/UserGroupTable.module.scss

@@ -4,15 +4,13 @@
 
 // switch visibility of the edit icon
 .user-group-edit-link {
-  :global {
-    .grw-edit-icon {
+    :global(.grw-edit-icon) {
       visibility: hidden;
     }
-  }
 
-  &:global {
+  & {
     &:hover {
-      .grw-edit-icon {
+      :global(.grw-edit-icon) {
         visibility: visible;
       }
     }

+ 9 - 9
apps/app/src/client/components/Admin/UserManagement.module.scss

@@ -1,18 +1,18 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
 // styles for admin user search
-.search-typeahead :global {
+.search-typeahead {
   position: relative;
   width: 100%;
 
   // corner radius
   border-top-right-radius: bs.$border-radius;
   border-bottom-right-radius: bs.$border-radius;
-  .rbt-input-main,
-  input {
+  :global(.rbt-input-main),
+  :global(input) {
     padding-right: 36px;
   }
-  .search-clear {
+  :global(.search-clear) {
     position: absolute;
     top: 50%;
     right: 6px;
@@ -25,22 +25,22 @@
     transform: translateY(-50%);
   }
 
-  .rbt-menu {
+  :global(.rbt-menu) {
     max-height: none !important;
     margin-top: 3px;
 
-    li a span {
-      .page-path {
+    :global(li a span) {
+      :global(.page-path) {
         display: inline;
         padding: 0 4px;
         color: inherit;
       }
 
-      .page-list-meta {
+      :global(.page-list-meta) {
         font-size: 0.9em;
         color: bs.$gray-400;
 
-        > span {
+        > :global(span) {
           margin-right: 0.3rem;
         }
       }

+ 3 - 3
apps/app/src/client/components/Admin/Users/ExternalAccountTable.module.scss

@@ -1,8 +1,8 @@
-.ea-table :global {
-  thead th {
+.ea-table {
+  :global(thead th) {
     vertical-align: top;
   }
-  td {
+  :global(td) {
     vertical-align: middle;
   }
 }

+ 1 - 1
apps/app/src/client/components/Admin/Users/UserMenu.module.scss

@@ -1,4 +1,4 @@
-.grw-usermenu-notification-icon :global {
+.grw-usermenu-notification-icon {
   position: absolute;
   top: -6px;
   left: 3px;

+ 4 - 4
apps/app/src/client/components/AuthorInfo/AuthorInfo.module.scss

@@ -3,19 +3,19 @@
 $author-font-size: 12px;
 $date-font-size: 12px;
 
-.grw-author-info :global {
+.grw-author-info {
   font-size: $author-font-size;
 
-  .text-date {
+  :global(.text-date) {
     font-size: $date-font-size;
   }
 
-  .user-picture {
+  :global(.user-picture) {
     width: 22px;
     height: 22px;
     border: 1px solid bs.$gray-300;
 
-    &.user-picture-xs {
+    &:global(.user-picture-xs) {
       width: 14px;
       height: 14px;
     }

+ 3 - 3
apps/app/src/client/components/Bookmarks/BookmarkFolderMenu.module.scss

@@ -1,10 +1,10 @@
-.grw-bookmark-folder-menu  :global {
+.grw-bookmark-folder-menu {
   max-width: 65%;
 
-  .grw-bookmark-folder-menu-item-folder-first {
+  :global(.grw-bookmark-folder-menu-item-folder-first) {
     padding-left: 40px;
   }
-  .grw-bookmark-folder-menu-item-folder-second {
+  :global(.grw-bookmark-folder-menu-item-folder-second) {
     padding-left: 60px;
   }
 }

+ 20 - 20
apps/app/src/client/components/Bookmarks/BookmarkFolderTree.module.scss

@@ -1,54 +1,54 @@
 $grw-foldertree-item-padding-left: 15px;
 $grw-bookmark-item-padding-left: 35px;
 
-.grw-folder-tree-container :global {
-  .grw-foldertree-item-container, .grw-drop-item-area {
-    & .grw-accept-drop-item {
+.grw-folder-tree-container {
+  :global(.grw-foldertree-item-container), :global(.grw-drop-item-area) {
+    & :global(.grw-accept-drop-item) {
       border-style: dashed !important;
       border-width: 0.15rem !important;
     }
   }
 
-  .grw-drop-item-area {
+  :global(.grw-drop-item-area) {
     padding: 1rem;
-    & .grw-accept-drop-item {
+    & :global(.grw-accept-drop-item) {
       padding: 0.7rem;
     }
   }
-  .grw-drag-drop-container > .grw-drop-item-area {
+  :global(.grw-drag-drop-container > .grw-drop-item-area) {
     margin: 1rem;
     border-style: dashed !important;
     border-width: 0.15rem !important;
   }
 }
 
-.grw-foldertree :global {
+.grw-foldertree {
 
-  .list-group-item {
-    .grw-visible-on-hover {
+  :global(.list-group-item) {
+    :global(.grw-visible-on-hover) {
       display: none;
     }
 
     &:hover {
-      .grw-visible-on-hover {
+      :global(.grw-visible-on-hover) {
         display: block;
       }
-      .page-list-meta {
+      :global(.page-list-meta) {
         display: none;
       }
     }
 
-    .grw-foldertree-triangle-btn {
+    :global(.grw-foldertree-triangle-btn) {
       border: 0;
       transition: all 0.2s ease-out;
       transform: rotate(0deg);
 
-      &.grw-foldertree-open {
+      &:global(.grw-foldertree-open) {
         transform: rotate(90deg);
       }
     }
 
-    .grw-foldertree-title-anchor {
+    :global(.grw-foldertree-title-anchor) {
       width: 100%;
       overflow: hidden;
       font-size: 14px;
@@ -56,27 +56,27 @@ $grw-bookmark-item-padding-left: 35px;
     }
   }
 
-  .grw-foldertree-item-container {
-    .grw-triangle-container {
+  :global(.grw-foldertree-item-container) {
+    :global(.grw-triangle-container) {
       height:30px;
     }
 
-    .grw-bookmark-item-list{
+    :global(.grw-bookmark-item-list){
       min-width: 30px;
       height: 50px;
 
-      .user-picture {
+      :global(.user-picture) {
         width: 16px;
         height: 16px;
         vertical-align: text-bottom;
 
-        &.user-picture-md {
+        &:global(.user-picture-md) {
           width: 20px;
           height: 20px;
         }
       }
 
-      .grw-foldertree-control{
+      :global(.grw-foldertree-control){
         margin-left: auto;
       }
     }

+ 6 - 6
apps/app/src/client/components/Common/CopyDropdown/CopyDropdown.module.scss

@@ -1,7 +1,7 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 @use '@growi/ui/scss/atoms/btn-muted';
 
-.copy-clipboard-dropdown-menu :global {
+.copy-clipboard-dropdown-menu {
     min-width: 310px;
     max-width: 375px;
 
@@ -9,25 +9,25 @@
       max-width: 600px;
     }
 
-    .dropdown-header {
+    :global(.dropdown-header) {
       margin-bottom: 0.5em;
       font-size: 1.1em;
     }
 
     // unset active styles
-    .dropdown-item:active {
+    :global(.dropdown-item:active) {
       color: unset;
       background-color: unset;
     }
 
-    .card {
+    :global(.card) {
       font-size: 0.7em;
       word-break: break-all;
     }
   }
 
-.grw-copy-dropdown :global {
-  .btn.btn-copy {
+.grw-copy-dropdown {
+  :global(.btn.btn-copy) {
     @include btn-muted.colorize(bs.$gray-500);
   }
 }

+ 2 - 2
apps/app/src/client/components/Common/DrawerToggler/DrawerToggler.module.scss

@@ -2,8 +2,8 @@
 @use 'styles/variables' as var;
 
 
-.grw-drawer-toggler :global {
-  .btn {
+.grw-drawer-toggler {
+  :global(.btn) {
     --bs-btn-color: rgba(var(--bs-tertiary-color-rgb), 0.5);
     --bs-btn-bg: transparent;
     --bs-btn-hover-color: rgba(var(--bs-tertiary-color-rgb), 0.7);

+ 2 - 2
apps/app/src/client/components/CompleteUserRegistrationForm.module.scss

@@ -1,8 +1,8 @@
 @use 'styles/atoms/placeholders/buttons';
 
 :root {
-  .complete-user-registration-form :global {
-    .btn-register {
+  .complete-user-registration-form {
+    :global(.btn-register) {
       @extend %btn-nologin;
       @extend %btn-register;
     }

+ 4 - 4
apps/app/src/client/components/CustomNavigation/CustomNav.module.scss

@@ -1,15 +1,15 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.grw-custom-nav-tab :global {
-  .nav-title {
+.grw-custom-nav-tab {
+  :global(.nav-title) {
     flex-wrap: nowrap;
   }
 
-  .nav-link {
+  :global(.nav-link) {
     padding: 1rem 1.5rem;
   }
 
-  .grw-nav-slide-hr {
+  :global(.grw-nav-slide-hr) {
     border-top: 0;
     border-bottom: 3px solid;
     transition: 0.3s ease-in-out;

+ 7 - 7
apps/app/src/client/components/DescendantsPageListModal/DescendantsPageListModal.module.scss

@@ -1,22 +1,22 @@
-.grw-descendants-page-list-modal :global {
-  .modal-header {
-    button.btn-close {
+.grw-descendants-page-list-modal {
+  :global(.modal-header) {
+    :global(button.btn-close) {
       margin: auto 0 auto auto;
     }
   }
 
-  .modal-body {
+  :global(.modal-body) {
     padding: 25px 30px;
   }
 
-  .grw-tab-content-style-md-down {
+  :global(.grw-tab-content-style-md-down) {
     padding-top: 25px;
   }
 
-  .grw-modal-body-style {
+  :global(.grw-modal-body-style) {
     max-height: calc(100vh - 100px);
   }
-  ul.pagination {
+  :global(ul.pagination) {
     margin-bottom: 0;
   }
 }

+ 2 - 2
apps/app/src/client/components/InAppNotification/ModelNotification/ModelNotification.module.scss

@@ -1,5 +1,5 @@
-.modal-notification :global {
-  .page-title {
+.modal-notification {
+  :global(.page-title) {
     font-size: 14px;
   }
 }

+ 2 - 2
apps/app/src/client/components/InstallerForm.module.scss

@@ -1,8 +1,8 @@
 @use 'styles/atoms/placeholders/buttons';
 
 :root {
-  .installer-form :global {
-    .btn-register {
+  .installer-form {
+    :global(.btn-register) {
       @extend %btn-nologin;
       @extend %btn-register;
     }

+ 15 - 15
apps/app/src/client/components/LoginForm/LoginForm.module.scss

@@ -1,7 +1,7 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 @use 'styles/atoms/placeholders/buttons';
 
-.login-form :global {
+.login-form {
   //
   // deactivated in order to fix https://redmine.weseek.co.jp/issues/143531 -- 2024.04.02 Yuki Takei
   //
@@ -12,17 +12,17 @@
   //   height: 0% !important;
   // }
 
-  .collapse-external-auth {
+  :global(.collapse-external-auth) {
     overflow: hidden;
   }
 
-  .link-growi-org {
+  :global(.link-growi-org) {
     position: absolute;
     bottom: 9px;
     z-index: 3;
   }
 
-  .text-line {
+  :global(.text-line) {
     &::before,
     &::after {
       flex-grow: 1;
@@ -34,11 +34,11 @@
   }
 
 
-  .ldap-space {
+  :global(.ldap-space) {
     padding-right: 76px;
   }
 
-  .input-ldap {
+  :global(.input-ldap) {
     position: absolute;
     top: 4px;
     right: 5px;
@@ -48,47 +48,47 @@
 
 // Button colors
 :root {
-  .login-form :global {
+  .login-form {
 
-    .btn {
+    :global(.btn) {
       @extend %btn-nologin;
     }
 
-    .btn-register {
+    :global(.btn-register) {
       @extend %btn-register;
     }
 
-    .btn-login {
+    :global(.btn-login) {
       --bs-btn-bg: #{rgba(#204986, 0.6)};
       --bs-btn-hover-bg: #{rgba(#204986, 0.8)};
       --bs-btn-active-bg: #{rgba(#204986, 0.8)};
     }
 
-    .btn-function {
+    :global(.btn-function) {
       --bs-btn-bg: #{rgba(bs.$gray-800, 0.8)};
       --bs-btn-hover-bg: #{rgba(bs.$gray-800, 0.5)};
       --bs-btn-active-bg: #{rgba(bs.$gray-800, 0.5)};
     }
 
-    .btn-auth-google {
+    :global(.btn-auth-google) {
       --bs-btn-bg: #{rgba(#4285F4, 0.4)};
       --bs-btn-hover-bg: #{rgba(#4285F4, 0.8)};
       --bs-btn-active-bg: #{rgba(#4285F4, 0.8)};
     }
 
-    .btn-auth-github {
+    :global(.btn-auth-github) {
       --bs-btn-bg: #{rgba(#403D3E, 0.4)};
       --bs-btn-hover-bg: #{rgba(#403D3E, 0.7)};
       --bs-btn-active-bg: #{rgba(#403D3E, 0.7)};
     }
 
-    .btn-auth-oidc {
+    :global(.btn-auth-oidc) {
       --bs-btn-bg: #{rgba(#835B1A, 0.4)};
       --bs-btn-hover-bg: #{rgba(#835B1A, 0.8)};
       --bs-btn-active-bg: #{rgba(#835B1A, 0.8)};
     }
 
-    .btn-auth-saml {
+    :global(.btn-auth-saml) {
       --bs-btn-bg: #{rgba(#138957, 0.4)};
       --bs-btn-hover-bg: #{rgba(#138957, 0.7)};
       --bs-btn-active-bg: #{rgba(#138957, 0.7)};

+ 12 - 12
apps/app/src/client/components/Me/AccessTokenScopeList.module.scss

@@ -1,35 +1,35 @@
 $baseMargin: 20px;
 
-.access-token-scope-list :global {
-  .indentation {
-    &.indentation-level-1 {
+.access-token-scope-list {
+  :global(.indentation) {
+    &:global(.indentation-level-1) {
       margin-left: $baseMargin;
     }
-    &.indentation-level-2 {
+    &:global(.indentation-level-2) {
       margin-left: $baseMargin * 2;
     }
-    &.indentation-level-3 {
+    &:global(.indentation-level-3) {
       margin-left: $baseMargin * 3;
     }
-    &.indentation-level-4 {
+    &:global(.indentation-level-4) {
       margin-left: $baseMargin * 4;
     }
-    &.indentation-level-5 {
+    &:global(.indentation-level-5) {
       margin-left: $baseMargin * 5;
     }
-    &.indentation-level-6 {
+    &:global(.indentation-level-6) {
       margin-left: $baseMargin * 6;
     }
-    &.indentation-level-7 {
+    &:global(.indentation-level-7) {
       margin-left: $baseMargin * 7;
     }
-    &.indentation-level-8 {
+    &:global(.indentation-level-8) {
       margin-left: $baseMargin * 8;
     }
-    &.indentation-level-9 {
+    &:global(.indentation-level-9) {
       margin-left: $baseMargin * 9;
     }
-    &.indentation-level-10 {
+    &:global(.indentation-level-10) {
       margin-left: $baseMargin * 10;
     }
   }

+ 4 - 4
apps/app/src/client/components/Navbar/GrowiNavbarBottom.module.scss

@@ -1,13 +1,13 @@
 @use 'styles/variables' as var;
 @use 'styles/mixins';
 
-.grw-navbar-bottom :global {
+.grw-navbar-bottom {
   // apply transition
   transition-property: bottom;
 
   @include mixins.apply-navigation-transition();
 
-  .navbar {
+  :global(.navbar) {
     height: var.$grw-navbar-bottom-height;
   }
 }
@@ -17,8 +17,8 @@
 }
 
 // centering icons
-.grw-navbar-bottom :global {
-  .nav-link {
+.grw-navbar-bottom {
+  :global(.nav-link) {
     display: flex;
     align-items: center;
   }

+ 7 - 7
apps/app/src/client/components/Navbar/PageEditorModeManager.module.scss

@@ -2,8 +2,8 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 @use 'styles/mixins';
 
-.grw-page-editor-mode-manager :global {
-  .btn {
+.grw-page-editor-mode-manager {
+  :global(.btn) {
     --bs-btn-font-size: 13px;
     --bs-btn-border-width: 2px;
 
@@ -17,7 +17,7 @@
   }
 }
 
-.grw-page-editor-mode-manager-skeleton :global {
+.grw-page-editor-mode-manager-skeleton {
   width: 90px;
   height: 38px;
 
@@ -29,8 +29,8 @@
 
 // == Colors
 @include bs.color-mode(light) {
-  .grw-page-editor-mode-manager :global {
-    .btn {
+  .grw-page-editor-mode-manager {
+    :global(.btn) {
       $color: var(--grw-page-editor-mode-manager-btn-color, var(--grw-primary-700));
       $bg: var(--grw-page-editor-mode-manager-btn-bg, var(--grw-primary-100));
       $bg-rgb: var(--grw-page-editor-mode-manager-btn-bg-rgb, var(--grw-primary-100-rgb));
@@ -50,8 +50,8 @@
 }
 
 @include bs.color-mode(dark) {
-  .grw-page-editor-mode-manager :global {
-    .btn {
+  .grw-page-editor-mode-manager {
+    :global(.btn) {
       $color: var(--grw-page-editor-mode-manager-btn-color, var(--grw-primary-300));
       $bg: var(--grw-page-editor-mode-manager-btn-bg, var(--grw-primary-800));
       $bg-rgb: var(--grw-page-editor-mode-manager-btn-bg-rgb, var(--grw-primary-800-rgb));

+ 7 - 7
apps/app/src/client/components/PageAccessoriesModal/PageAccessoriesModal.module.scss

@@ -1,22 +1,22 @@
-.grw-page-accessories-modal :global {
-  .modal-header {
-    button.btn-close {
+.grw-page-accessories-modal {
+  :global(.modal-header) {
+    :global(button.btn-close) {
       margin: auto 0 auto auto;
     }
   }
 
-  .modal-body {
+  :global(.modal-body) {
     padding: 25px 30px;
   }
 
-  .grw-tab-content-style-md-down {
+  :global(.grw-tab-content-style-md-down) {
     padding-top: 25px;
   }
 
-  .grw-modal-body-style {
+  :global(.grw-modal-body-style) {
     max-height: calc(100vh - 100px);
   }
-  ul.pagination {
+  :global(ul.pagination) {
     margin-bottom: 0;
   }
 }

+ 3 - 3
apps/app/src/client/components/PageAttachment/DeleteAttachmentModal.module.scss

@@ -1,8 +1,8 @@
-.attachment-delete-modal :global {
-  .attachment-delete-image {
+.attachment-delete-modal {
+  :global(.attachment-delete-image) {
     text-align: center;
 
-    img {
+    :global(img) {
       max-width: 100%;
     }
   }

+ 9 - 9
apps/app/src/client/components/PageComment.module.scss

@@ -1,19 +1,19 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.page-comment-styles :global {
-  .page-comments {
-    h4 {
+.page-comment-styles {
+  :global(.page-comments) {
+    :global(h4) {
       margin-bottom: 1em;
     }
   }
 
   // reply button
-  .btn-comment-reply {
+  :global(.btn-comment-reply) {
     backdrop-filter: blur(10px);
   }
 
   // TODO: Refacotr Soft-coding
-  .page-comment-button-skeleton {
+  :global(.page-comment-button-skeleton) {
     width: 70.0167px;
     height: 26.3833px;
     margin-top: 0.5em;
@@ -23,8 +23,8 @@
 
 // // Light mode color
 @include bs.color-mode(light) {
-  .page-comment-styles :global {
-    .btn-comment-reply {
+  .page-comment-styles {
+    :global(.btn-comment-reply) {
       --bs-btn-color: #{( bs.$gray-600 )};
       --bs-btn-hover-color:  #{( bs.$gray-700 )};
       --bs-btn-bg: #{rgba( bs.$gray-200, 0.3 )};
@@ -37,8 +37,8 @@
 
 // dark mode color
 @include bs.color-mode(dark) {
-  .page-comment-styles :global {
-    .btn-comment-reply {
+  .page-comment-styles {
+    :global(.btn-comment-reply) {
       --bs-btn-color: #{( bs.$gray-500 )};
       --bs-btn-hover-color:  #{( bs.$gray-400 )};
       --bs-btn-bg: #{rgba( bs.$gray-800, 0.3 )};

+ 17 - 17
apps/app/src/client/components/PageComment/Comment.module.scss

@@ -2,31 +2,31 @@
 @use 'styles/variables' as var;
 @use './comment-inheritance';
 
-.comment-styles :global {
+.comment-styles {
 
-  .page-comment {
+  :global(.page-comment) {
     position: relative;
     pointer-events: none;
     scroll-margin-top: var.$grw-scroll-margin-top-in-view;
 
     // background
-    .bg-comment {
+    :global(.bg-comment) {
       @extend %bg-comment;
     }
 
     // user icon
-    .user-picture {
+    :global(.user-picture) {
       @extend %user-picture;
     }
 
     // comment section
-    .page-comment-main {
+    :global(.page-comment-main) {
       @extend %comment-section;
 
       pointer-events: auto;
 
       // delete button
-      .page-comment-control {
+      :global(.page-comment-control) {
         position: absolute;
         top: 0;
         right: 0;
@@ -39,9 +39,9 @@
     }
 
     // comment body
-    .page-comment-body {
+    :global(.page-comment-body) {
       word-wrap: break-word;
-      .wiki p {
+      :global(.wiki p) {
         margin: 8px 0;
       }
     }
@@ -51,7 +51,7 @@
     // }
 
     // newer comments
-    &.page-comment-newer {
+    &:global(.page-comment-newer) {
       opacity: 0.7;
 
       &:hover {
@@ -59,15 +59,15 @@
       }
     }
 
-    .page-comment-revision {
-      .material-symbols-outlined {
+    :global(.page-comment-revision) {
+      :global(.material-symbols-outlined) {
         font-size: 16px;
         vertical-align: middle;
       }
     }
 
 
-    .page-comment-meta {
+    :global(.page-comment-meta) {
       display: flex;
       justify-content: flex-end;
       font-size: 0.9em;
@@ -77,7 +77,7 @@
   }
 
   // TODO: Refacotr Soft-coding
-  .page-comment-comment-body-skeleton {
+  :global(.page-comment-comment-body-skeleton) {
     position: relative;
     height: 66px;
     padding: 1em;
@@ -91,8 +91,8 @@
 
 // // Light mode color
 @include bs.color-mode(light) {
-  .comment-styles :global {
-    .page-comment-revision {
+  .comment-styles {
+    :global(.page-comment-revision) {
       color: bs.$gray-500;
     }
   }
@@ -100,8 +100,8 @@
 
 // // Dark mode color
 @include bs.color-mode(dark) {
-  .comment-styles :global {
-    .page-comment-revision {
+  .comment-styles {
+    :global(.page-comment-revision) {
       color: bs.$gray-600;
     }
   }

+ 13 - 13
apps/app/src/client/components/PageComment/CommentEditor.module.scss

@@ -3,17 +3,17 @@
 @use '../PageEditor/page-editor-inheritance';
 
 // display cheatsheet for comment form only
-.comment-editor-styles :global {
-  .comment-form {
+.comment-editor-styles {
+  :global(.comment-form) {
     position: relative;
 
     // background
-    .bg-comment {
+    :global(.bg-comment) {
       @extend %bg-comment
     }
 
     // user icon
-    .user-picture {
+    :global(.user-picture) {
       @extend %user-picture;
     }
 
@@ -22,36 +22,36 @@
 
 
 // adjust height
-.comment-editor-styles :global {
+.comment-editor-styles {
   // Set `display: flex` instead of `display: block` to make it work with `flex: 1` of the children
   // This helps users focus on the editor by clicking on the broader area
-  .tab-pane.active {
+  :global(.tab-pane.active) {
     display: flex;
   }
 
-  .cm-editor {
+  :global(.cm-editor) {
     min-height: comment-inheritance.$codemirror-default-height !important;
     max-height: #{2 * comment-inheritance.$codemirror-default-height};
   }
-  .cm-gutters {
+  :global(.cm-gutters) {
     min-height: comment-inheritance.$codemirror-default-height !important;
   }
-  .comment-preview-container {
+  :global(.comment-preview-container) {
     min-height: page-editor-inheritance.$navbar-editor-height + comment-inheritance.$codemirror-default-height;
     padding-top: 0.5em;
   }
 }
 
 // border-radius
-.comment-editor-styles :global {
-  .cm-editor, .cm-scroller {
+.comment-editor-styles {
+  :global(.cm-editor), :global(.cm-scroller) {
     border-radius: var(--bs-border-radius);
   }
 }
 
 // remove outline
-.comment-editor-styles :global {
-  .cm-editor {
+.comment-editor-styles {
+  :global(.cm-editor) {
     outline: none;
   }
 }

+ 3 - 3
apps/app/src/client/components/PageComment/DeleteCommentModal/DeleteCommentModal.module.scss

@@ -1,7 +1,7 @@
 // modal
-.page-comment-delete-modal :global {
-  .modal-content .modal-body {
-    .comment-body {
+.page-comment-delete-modal {
+  :global(.modal-content .modal-body) {
+    :global(.comment-body) {
       max-height: 13em;
 
       // scrollable

+ 1 - 1
apps/app/src/client/components/PageComment/ReplyComments.module.scss

@@ -1,5 +1,5 @@
 
 // remove margin after hidden replies
-.page-comments-hidden-replies + .page-comment-reply :global {
+.page-comments-hidden-replies + .page-comment-reply {
   margin-top: 0;
 }

+ 6 - 6
apps/app/src/client/components/PageComment/SwitchingButtonGroup.module.scss

@@ -1,8 +1,8 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
 
-.btn-group-switching :global {
-  .btn {
+.btn-group-switching {
+  :global(.btn) {
     --bs-btn-border-width: 2px;
 
     width: 60px;
@@ -18,8 +18,8 @@
 
 // == Colors
 @include bs.color-mode(light) {
-  .btn-group-switching :global {
-    .btn {
+  .btn-group-switching {
+    :global(.btn) {
       $bg: var(--bs-gray-500);
 
       --bs-btn-border-color: #{$bg};
@@ -33,8 +33,8 @@
 }
 
 @include bs.color-mode(dark) {
-  .btn-group-switching :global {
-    .btn {
+  .btn-group-switching {
+    :global(.btn) {
       $bg: var(--bs-gray-800);
 
       --bs-btn-border-color: #{$bg};

+ 6 - 6
apps/app/src/client/components/PageControls/BookmarkButtons.module.scss

@@ -2,14 +2,14 @@
 @use '@growi/ui/scss/atoms/btn-muted';
 @use './button-styles';
 
-.btn-group-bookmark :global {
-  .btn-bookmark {
+.btn-group-bookmark {
+  :global(.btn-bookmark) {
     @extend %btn-basis;
   }
-  .dropdown .btn-bookmark {
+  :global(.dropdown .btn-bookmark) {
     padding-right: 1px;
   }
-  .total-counts {
+  :global(.total-counts) {
     @extend %btn-total-counts-basis;
 
     padding-left: 5px;
@@ -17,9 +17,9 @@
 }
 
 // == Colors
-.btn-group-bookmark :global {
+.btn-group-bookmark {
   /* stylelint-disable-next-line no-descending-specificity */
-  .btn-bookmark {
+  :global(.btn-bookmark) {
     @include btn-muted.colorize(bs.$orange);
   }
 }

+ 6 - 6
apps/app/src/client/components/PageControls/LikeButtons.module.scss

@@ -2,14 +2,14 @@
 @use '@growi/ui/scss/atoms/btn-muted';
 @use './button-styles';
 
-.btn-group-like :global {
-  .btn-like {
+.btn-group-like {
+  :global(.btn-like) {
     @extend %btn-basis;
   }
-  .btn-like#like-button {
+  :global(.btn-like#like-button) {
     padding-right: 3px;
   }
-  .total-counts {
+  :global(.total-counts) {
     @extend %btn-total-counts-basis;
 
     padding-left: 5px;
@@ -17,8 +17,8 @@
 }
 
 // == Colors
-.btn-group-like :global {
-  .btn-like {
+.btn-group-like {
+  :global(.btn-like) {
     @include btn-muted.colorize(bs.$red);
   }
 }

+ 4 - 4
apps/app/src/client/components/PageControls/PageControls.module.scss

@@ -3,16 +3,16 @@
 @use './button-styles';
 
 // PageItemControl styles
-.grw-page-controls :global {
-  .btn-page-item-control {
+.grw-page-controls {
+  :global(.btn-page-item-control) {
     @extend %btn-basis;
   }
 }
 
 // == Colors
 // PageItemControl colors
-.grw-page-controls :global {
-  .btn-page-item-control {
+.grw-page-controls {
+  :global(.btn-page-item-control) {
     @include btn-muted.colorize(bs.$gray-500);
   }
 }

+ 1 - 1
apps/app/src/client/components/PageControls/SearchButton.module.scss

@@ -2,7 +2,7 @@
 @use '@growi/ui/scss/atoms/btn-muted';
 @use './button-styles';
 
-.btn-search :global {
+.btn-search {
   @extend %btn-basis;
 }
 

+ 5 - 5
apps/app/src/client/components/PageControls/SeenUserInfo.module.scss

@@ -2,11 +2,11 @@
 @use '@growi/ui/scss/atoms/btn-muted';
 @use './button-styles';
 
-.grw-seen-user-info :global {
-  .btn-seen-user {
+.grw-seen-user-info {
+  :global(.btn-seen-user) {
     @extend %btn-basis;
   }
-  .total-counts {
+  :global(.total-counts) {
     @extend %text-total-counts-basis;
   }
 }
@@ -14,10 +14,10 @@
 
 // == Colors
 
-.grw-seen-user-info :global {
+.grw-seen-user-info {
   $color: #549c79;
 
-  .btn-seen-user {
+  :global(.btn-seen-user) {
     @include btn-muted.colorize($color);
   }
 }

+ 2 - 2
apps/app/src/client/components/PageControls/SubscribeButton.module.scss

@@ -2,10 +2,10 @@
 @use '@growi/ui/scss/atoms/btn-muted';
 @use './button-styles';
 
-.btn-subscribe :global {
+.btn-subscribe {
   @extend %btn-basis;
 
-  .total-counts {
+  :global(.total-counts) {
     @extend %btn-total-counts-basis;
   }
 }

+ 1 - 1
apps/app/src/client/components/PageControls/user-list-popover.module.scss

@@ -1,5 +1,5 @@
 @use '@growi/ui/scss/molecules/user-list-popover';
 
-.user-list-popover :global {
+.user-list-popover {
   @extend %user-list-popover
 }

+ 3 - 3
apps/app/src/client/components/PageCreateModal.module.scss

@@ -1,8 +1,8 @@
-.grw-create-page :global {
-  .page-today-input1 {
+.grw-create-page {
+  :global(.page-today-input1) {
     width: 60px;
   }
-  .grw-btn-create-page {
+  :global(.grw-btn-create-page) {
     min-width: 90px;
   }
 }

+ 2 - 2
apps/app/src/client/components/PageEditor/ConflictDiffModal/ConflictDiffModal.module.scss

@@ -1,6 +1,6 @@
 // TODO: https://redmine.weseek.co.jp/issues/142208
-.conflict-diff-modal :global {
-  .cm-editor {
+.conflict-diff-modal {
+  :global(.cm-editor) {
     height: 400px !important;
   }
 }

+ 1 - 1
apps/app/src/client/components/PageEditor/EditorNavbar/EditingUserList.module.scss

@@ -1,5 +1,5 @@
 @use '@growi/ui/scss/molecules/user-list-popover';
 
-.user-list-popover :global {
+.user-list-popover {
   @extend %user-list-popover;
 }

+ 1 - 1
apps/app/src/client/components/PageEditor/EditorNavbar/EditorNavbar.module.scss

@@ -1,6 +1,6 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.editor-navbar :global {
+.editor-navbar {
   min-height: 72px;
 
   @include bs.media-breakpoint-down(sm) {

+ 7 - 7
apps/app/src/client/components/PageEditor/EditorNavbarBottom/EditorNavbarBottom.module.scss

@@ -3,25 +3,25 @@
 @use 'styles/mixins';
 
 @include mixins.at-editing() {
-  .grw-editor-navbar-bottom :global {
-    .grw-grant-selector {
+  .grw-editor-navbar-bottom {
+    :global(.grw-grant-selector) {
       max-width: 250px;
-      .material-symbols-outlined  {
+      :global(.material-symbols-outlined)  {
         padding-bottom: 2px;
         font-size: 19px;
       }
     }
-    .btn-submit {
+    :global(.btn-submit) {
       width: 100px;
     }
 
-    .btn-expand {
+    :global(.btn-expand) {
       // rotate icon
-      i {
+      :global(i) {
         display: inline-block;
         transition: transform 200ms;
       }
-      &.expand i {
+      &:global(.expand i) {
         transform: rotate(-180deg);
       }
     }

+ 15 - 15
apps/app/src/client/components/PageEditor/GridEditModal.module.scss

@@ -1,40 +1,40 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.grw-grid-edit-modal :global {
-  .desktop-preview,
-  .tablet-preview,
-  .mobile-preview {
-    .row {
+.grw-grid-edit-modal {
+  :global(.desktop-preview),
+  :global(.tablet-preview),
+  :global(.mobile-preview) {
+    :global(.row) {
       height: 140px;
       margin: 0;
     }
   }
-  .desktop-preview {
-    .row {
-      div {
+  :global(.desktop-preview) {
+    :global(.row) {
+      :global(div) {
         padding: 0;
       }
     }
   }
 
-  .tablet-preview {
-    .row {
-      div {
+  :global(.tablet-preview) {
+    :global(.row) {
+      :global(div) {
         padding: 0;
       }
     }
   }
 
-  .mobile-preview {
+  :global(.mobile-preview) {
     width: 75%;
-    .row {
-      div {
+    :global(.row) {
+      :global(div) {
         padding: 0;
       }
     }
   }
 
-  .grid-division-menu {
+  :global(.grid-division-menu) {
     width: 60vw;
 
     @include bs.media-breakpoint-down(lg) {

+ 10 - 10
apps/app/src/client/components/PageEditor/HandsontableModal/HandsontableModal.module.scss

@@ -1,23 +1,23 @@
-.grw-handsontable :global {
-  .handsontable {
+.grw-handsontable {
+  :global(.handsontable) {
     position: relative;
 
-    .handsontableInput {
+    :global(.handsontableInput) {
       max-width: 290px !important;
     }
 
-    td {
+    :global(td) {
       word-break: break-all;
     }
 
-    th {
+    :global(th) {
       text-align: inherit;
     }
   }
 
   // expand .hot-table-container (with flexbox)
-  .grw-modal-expanded {
-    .hot-table-container {
+  :global(.grw-modal-expanded) {
+    :global(.hot-table-container) {
       flex: 1;
     }
   }
@@ -27,15 +27,15 @@
   // see https://github.com/handsontable/handsontable/issues/2937#issuecomment-287390111
   // This issue fixing from Handsontable v 7.0.0
   // see: https://github.com/handsontable/handsontable/issues/2937#issuecomment-480824024
-  .modal.in .modal-dialog.handsontable-modal {
+  :global(.modal.in .modal-dialog.handsontable-modal) {
     transform: none;
 
-    .data-import-button {
+    :global(.data-import-button) {
       position: relative;
       padding-right: 35px;
       padding-left: 10px;
 
-      i::before {
+      :global(i::before) {
         position: absolute;
         top: 6px;
         right: 8px;

+ 5 - 5
apps/app/src/client/components/PageEditor/Preview.module.scss

@@ -1,7 +1,7 @@
 @use 'styles/mixins';
 
-.page-editor-preview-body :global {
-  .wiki {
+.page-editor-preview-body {
+  :global(.wiki) {
     max-width: 980px;
     padding: 0 15px;
     margin: 0 auto;
@@ -10,9 +10,9 @@
 
 // modify width for fluid layout
 .page-editor-preview-body {
-  &:global {
-    &.fluid-layout {
-      .wiki {
+  & {
+    &:global(.fluid-layout) {
+      :global(.wiki) {
         @include mixins.fluid-layout();
       }
     }

+ 1 - 1
apps/app/src/client/components/PageHeader/PageHeader.module.scss

@@ -1,3 +1,3 @@
 /* stylelint-disable-next-line block-no-empty */
-.page-header :global {
+.page-header {
 }

+ 5 - 5
apps/app/src/client/components/PageHeader/PagePathHeader.module.scss

@@ -1,5 +1,5 @@
-.page-path-header :global {
-  input {
+.page-path-header {
+  :global(input) {
     min-width: 20px;
     min-height: unset;
     padding-top: 2px;
@@ -7,10 +7,10 @@
     line-height: 1.2em;
   }
 
-  .page-path-header-buttons {
+  :global(.page-path-header-buttons) {
     height: 0;
 
-    .btn {
+    :global(.btn) {
       width: 24px;
       height: 24px;
       transform: translateY(12px);
@@ -18,7 +18,7 @@
   }
 
   // Make Truncated elements horizontally scrollable and hide the scroll bar
-  .page-path-header-input {
+  :global(.page-path-header-input) {
     scrollbar-width: none;
   }
 }

+ 4 - 4
apps/app/src/client/components/PageHeader/PageTitleHeader.module.scss

@@ -1,5 +1,5 @@
-.page-title-header :global {
-  input {
+.page-title-header {
+  :global(input) {
     min-width: 20px;
     min-height: unset;
     padding: 0 0.5rem;
@@ -8,7 +8,7 @@
   }
 
   // Make Truncated elements horizontally scrollable and hide the scroll bar
-  .page-title-header-input {
+  :global(.page-title-header-input) {
     scrollbar-width: none;
   }
 }
@@ -16,7 +16,7 @@
 .page-title-header-border-color {
   --bs-border-color: transparent;
 
-  &:global {
+  & {
     &:hover {
       --bs-border-color: var(--bs-primary-border-subtle);
     }

+ 2 - 2
apps/app/src/client/components/PageHistory/PageRevisionTable.module.scss

@@ -1,5 +1,5 @@
-.revision-history-table :global {
-  tbody {
+.revision-history-table {
+  :global(tbody) {
     max-height: 250px;
   }
 }

+ 4 - 4
apps/app/src/client/components/PageHistory/Revision.module.scss

@@ -1,12 +1,12 @@
-.revision-history-main :global {
-  img.user-picture-lg {
+.revision-history-main {
+  :global(img.user-picture-lg) {
     width: 32px;
     height: 32px;
   }
 }
 
-.revision-history-main-nodiff :global {
-  .picture-container {
+.revision-history-main-nodiff {
+  :global(.picture-container) {
     min-width: 32px;
     text-align: center; // centering .picture
   }

+ 4 - 4
apps/app/src/client/components/PageHistory/RevisionDiff.module.scss

@@ -1,14 +1,14 @@
-.revision-diff-container :global {
-  .link-created-at {
+.revision-diff-container {
+  :global(.link-created-at) {
     text-decoration-line: underline;
   }
 
-  .revision-history-diff {
+  :global(.revision-history-diff) {
     table-layout: fixed;
 
     // revision-history
     // to stay d2h-code-side-line-number in the revision history diff area
-    .d2h-wrapper {
+    :global(.d2h-wrapper) {
       position: relative;
     }
   }

+ 3 - 3
apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.module.scss

@@ -1,6 +1,6 @@
-.grw-page-path-nav-sticky :global {
-  .sticky-inner-wrapper.active {
-    h1 {
+.grw-page-path-nav-sticky {
+  :global(.sticky-inner-wrapper.active) {
+    :global(h1) {
       font-size: 1.75rem !important;
     }
   }

+ 4 - 4
apps/app/src/client/components/PagePresentationModal/PagePresentationModal.module.scss

@@ -1,22 +1,22 @@
 @use 'styles/modal';
 
-.grw-presentation-modal :global {
+.grw-presentation-modal {
   /* stylelint-disable-next-line length-zero-no-unit */
   @include modal.expand-modal-fullscreen(false, false, 0px);
 
-  .modal-content {
+  :global(.modal-content) {
     background-color: transparent;
     border-radius: 0;
   }
 
-  .grw-presentation-controls {
+  :global(.grw-presentation-controls) {
     position: absolute;
     top: 1rem;
     right: 1rem;
     z-index: 110; // over ".reveal .slides"
   }
 
-  .btn-close {
+  :global(.btn-close) {
     display: inline-block;
     width: 3rem;
     height: 3rem;

+ 6 - 6
apps/app/src/client/components/PageSelectModal/TreeItemForModal.module.scss

@@ -1,7 +1,7 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.tree-item-for-modal :global {
-  li {
+.tree-item-for-modal {
+  :global(li) {
     min-height: 36px;
   }
 }
@@ -9,16 +9,16 @@
 
 // == Colors
 @include bs.color-mode(light) {
-  .tree-item-for-modal :global {
-    .list-group-item-action {
+  .tree-item-for-modal {
+    :global(.list-group-item-action) {
       --bs-list-group-active-bg: var(--grw-primary-200);
     }
   }
 }
 
 @include bs.color-mode(dark) {
-  .tree-item-for-modal :global {
-    .list-group-item-action {
+  .tree-item-for-modal {
+    :global(.list-group-item-action) {
       --bs-list-group-active-bg: var(--grw-primary-700);
     }
   }

+ 5 - 5
apps/app/src/client/components/PageSideContents/PageAccessoriesControl.module.scss

@@ -1,11 +1,11 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.btn-page-accessories :global {
+.btn-page-accessories {
   display: flex;
   align-items: center;
   padding: 6px 8px;
 
-  .grw-labels {
+  :global(.grw-labels) {
     flex-grow: 1;
     align-items: center;
     justify-content: space-between;
@@ -13,13 +13,13 @@
 }
 
 @include bs.media-breakpoint-down(sm) {
-  .btn-page-accessories :global {
+  .btn-page-accessories {
     box-shadow: 0 3px 6px rgba(black, 0.15);
   }
 }
 
 // apply font-size
-.btn-page-accessories :global {
+.btn-page-accessories {
   --bs-btn-font-size: 14px;
 
   @include bs.media-breakpoint-down(lg) {
@@ -29,7 +29,7 @@
 
 // expand when larger than lg
 @include bs.media-breakpoint-up(lg) {
-  .btn-page-accessories :global {
+  .btn-page-accessories {
     flex-grow: 1;
   }
 }

+ 6 - 6
apps/app/src/client/components/PageStatusAlert.module.scss

@@ -1,7 +1,7 @@
 @use 'styles/variables' as var;
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.grw-page-status-alert :global {
+.grw-page-status-alert {
   $margin-bottom: var.$grw-navbar-bottom-height + 10px;
 
   box-shadow: 0 2px 4px #0000004d;
@@ -10,10 +10,10 @@
   @include bs.media-breakpoint-down(sm) {
     margin: 0 10px $margin-bottom;
 
-    .grw-card-label-container {
+    :global(.grw-card-label-container) {
       text-align: center;
     }
-    .grw-card-btn-container {
+    :global(.grw-card-btn-container) {
       text-align: center;
     }
   }
@@ -22,14 +22,14 @@
     width: 700px;
     margin: 0 auto $margin-bottom;
 
-    .card-body {
+    :global(.card-body) {
       display: flex;
       align-items: center;
       justify-content: space-between;
     }
 
-    .grw-card-label-container,
-    .grw-card-btn-container {
+    :global(.grw-card-label-container),
+    :global(.grw-card-btn-container) {
       margin: 0;
     }
   }

+ 6 - 6
apps/app/src/client/components/PageTags/TagEditModal/TagsInput.module.scss

@@ -1,6 +1,6 @@
-.tags-input :global {
-  .rbt-token {
-    .rbt-token-label {
+.tags-input {
+  :global(.rbt-token) {
+    :global(.rbt-token-label) {
       // override to text-truncate
       overflow: hidden;
       font-size: 1rem; // adjust font-size
@@ -11,13 +11,13 @@
 }
 
 // == Colors
-.tags-input :global {
-  .rbt-token {
+.tags-input {
+  :global(.rbt-token) {
     // override to .badge color
     color: var(--bs-badge-color);
   }
 
-  .rbt-token-active {
+  :global(.rbt-token-active) {
     border-color: var(--grw-primary-400) !important;
   }
 }

+ 6 - 6
apps/app/src/client/components/PageTags/TagLabels.module.scss

@@ -2,14 +2,14 @@
 
 $grw-tag-label-font-size: 12px;
 
-.grw-tag-labels :global {
-  .grw-tag-label {
+.grw-tag-labels {
+  :global(.grw-tag-label) {
     font-size: $grw-tag-label-font-size;
     font-weight: normal;
     border-radius: bs.$border-radius;
   }
 
-  .grw-tag-simple-bar {
+  :global(.grw-tag-simple-bar) {
     width: 15.5rem;
     max-height: 5rem;
   }
@@ -17,8 +17,8 @@ $grw-tag-label-font-size: 12px;
 }
 
 // apply font-size
-.grw-tag-labels :global {
-  .btn-edit-tags {
+.grw-tag-labels {
+  :global(.btn-edit-tags) {
     --bs-btn-font-size: 14px;
 
     @include bs.media-breakpoint-down(lg) {
@@ -27,7 +27,7 @@ $grw-tag-label-font-size: 12px;
   }
 }
 
-.grw-tag-labels-skeleton :global {
+.grw-tag-labels-skeleton {
   width: 137px;
   height: calc(var(--bs-body-line-height) * 1em);
 

+ 3 - 3
apps/app/src/client/components/PageTimeline.module.scss

@@ -1,14 +1,14 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.card-timeline :global {
+.card-timeline {
   margin-bottom: 3rem;
   border: 1px solid bs.$gray-300;
 
-  .card-header {
+  :global(.card-header) {
     background-color: bs.$gray-300;
   }
 
-  .card-body {
+  :global(.card-body) {
     min-height: 15vh;
   }
 }

+ 4 - 4
apps/app/src/client/components/ReactMarkdownComponents/DrawioViewerWithEditButton.module.scss

@@ -1,9 +1,9 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.drawio-viewer-with-edit-button :global {
+.drawio-viewer-with-edit-button {
   position: relative;
 
-  .btn-edit-drawio {
+  :global(.btn-edit-drawio) {
     position: absolute;
     top: 11px;
     right: 10px;
@@ -16,8 +16,8 @@
   }
 }
 
-.drawio-viewer-with-edit-button:hover :global {
-  .btn-edit-drawio {
+.drawio-viewer-with-edit-button:hover {
+  :global(.btn-edit-drawio) {
     opacity: 1;
   }
 }

+ 9 - 9
apps/app/src/client/components/ReactMarkdownComponents/Header.module.scss

@@ -1,33 +1,33 @@
-.revision-head :global {
-  a {
+.revision-head {
+  :global(a) {
     text-decoration: none;
   }
 
-  .revision-head-link {
+  :global(.revision-head-link) {
     left: -1em;
     width: 1em;
     user-select: none;
     opacity: 0;
   }
-  .revision-head-edit-button{
+  :global(.revision-head-edit-button){
     margin-left: 0.5em;
     font-size: 16px;
     user-select: none;
     opacity: 0;
-    .material-symbols-outlined{
+    :global(.material-symbols-outlined){
       vertical-align: middle;
     }
   }
 }
 
-.revision-head:hover :global {
-  .revision-head-link, .revision-head-edit-button {
+.revision-head:hover {
+  :global(.revision-head-link), :global(.revision-head-edit-button) {
     opacity: 0.5;
   }
-  .revision-head-link:hover {
+  :global(.revision-head-link:hover) {
     opacity: 1;
   }
-  .revision-head-edit-button:hover {
+  :global(.revision-head-edit-button:hover) {
     opacity: 1;
   }
 }

+ 2 - 2
apps/app/src/client/components/ReactMarkdownComponents/RichAttachment.module.scss

@@ -1,5 +1,5 @@
-.attachment :global {
-  .attachment-icon {
+.attachment {
+  :global(.attachment-icon) {
     flex-shrink: 0;
     width: 35px;
     height: 35px;

+ 4 - 4
apps/app/src/client/components/ReactMarkdownComponents/TableWithEditButton.module.scss

@@ -3,10 +3,10 @@
 /**
  * for table with handsontable modal button
  */
-.editable-with-handsontable :global {
+.editable-with-handsontable {
   position: relative;
 
-  .handsontable-modal-trigger {
+  :global(.handsontable-modal-trigger) {
     position: absolute;
     top: 8px;
     right: 10px;
@@ -24,8 +24,8 @@
   }
 }
 
-.editable-with-handsontable:hover :global {
-  .handsontable-modal-trigger {
+.editable-with-handsontable:hover {
+  :global(.handsontable-modal-trigger) {
     opacity: 1;
   }
 }

+ 10 - 10
apps/app/src/client/components/RevisionComparer/RevisionComparer.module.scss

@@ -1,40 +1,40 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 @use '@growi/ui/scss/atoms/btn-muted';
 
-.revision-compare :global {
-  .revision-compare-container {
+.revision-compare {
+  :global(.revision-compare-container) {
     min-height: 100px;
 
-    &.nodiff {
+    &:global(.nodiff) {
       display: flex;
       align-items: center;
       justify-content: center;
     }
   }
-  .d2h-file-header {
+  :global(.d2h-file-header) {
     display: none;
   }
 
-  .grw-copy-dropdown {
-    .btn.btn-copy {
+  :global(.grw-copy-dropdown) {
+    :global(.btn.btn-copy) {
       @include btn-muted.colorize(bs.$gray-500);
     }
 
-    .dropdown-menu {
+    :global(.dropdown-menu) {
       min-width: 310px;
 
-      .dropdown-header {
+      :global(.dropdown-header) {
         margin-bottom: 0.5em;
         font-size: 1.1em;
       }
 
       // unset active styles
-      .dropdown-item:active {
+      :global(.dropdown-item:active) {
         color: unset;
         background-color: unset;
       }
 
-      .card {
+      :global(.card) {
         font-size: 0.7em;
         word-break: break-all;
       }

+ 8 - 8
apps/app/src/client/components/SearchTypeahead.module.scss

@@ -1,16 +1,16 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.search-typeahead :global {
+.search-typeahead {
   position: relative;
   width: 100%;
 
   // corner radius
   border-top-right-radius: bs.$border-radius;
   border-bottom-right-radius: bs.$border-radius;
-  .rbt-input-main {
+  :global(.rbt-input-main) {
     padding-right: 36px;
   }
-  .search-clear {
+  :global(.search-clear) {
     position: absolute;
     top: 4px;
     right: 4px;
@@ -21,22 +21,22 @@
     line-height: 0;
   }
 
-  .rbt-menu {
+  :global(.rbt-menu) {
     max-height: none !important;
     margin-top: 3px;
 
-    li a span {
-      .page-path {
+    :global(li a span) {
+      :global(.page-path) {
         display: inline;
         padding: 0 4px;
         color: inherit;
       }
 
-      .page-list-meta {
+      :global(.page-list-meta) {
         font-size: 0.9em;
         color: bs.$gray-400;
 
-        > span {
+        > :global(span) {
           margin-right: 0.3rem;
         }
       }

+ 3 - 3
apps/app/src/client/components/ShortcutsModal/ShortcutsModal.module.scss

@@ -1,9 +1,9 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 @use '@growi/core-styles/scss/helpers/modifier-keys';
 
-.shortcuts-modal :global {
+.shortcuts-modal {
 
-  .key {
+  :global(.key) {
     /* Box Properties */
     padding: 0 4px;
 
@@ -15,7 +15,7 @@
 
   @include modifier-keys.modifier-key;
 
-  .grw-modal-body-style {
+  :global(.grw-modal-body-style) {
     max-height: calc(100vh - 200px);
   }
 }

+ 8 - 8
apps/app/src/client/components/Sidebar/AppTitle/AppTitle.module.scss

@@ -5,8 +5,8 @@
 @use 'styles/mixins';
 
 // GROWI Logo
-.grw-app-title :global {
-  .grw-logo {
+.grw-app-title {
+  :global(.grw-logo) {
     $width: var.$grw-sidebar-nav-width;
     $height: var.$grw-sidebar-nav-width; // declare $height with the same value as the sidebar nav width
     $logomark-width: 27.7px;
@@ -14,14 +14,14 @@
 
     width: $width;
 
-    svg {
+    :global(svg) {
       width: $width;
       height: $height;
       padding: (($height - $logomark-height) * 0.5) (($width - $logomark-width) * 0.5);
     }
   }
 
-  .confidential-tooltip {
+  :global(.confidential-tooltip) {
     max-width: 180px;
   }
 }
@@ -29,8 +29,8 @@
 // == GROWI Logo when Editor mode
 @include mixins.at-editing() {
   @include bs.media-breakpoint-up(xl) {
-    .grw-app-title :global {
-      .grw-logo {
+    .grw-app-title {
+      :global(.grw-logo) {
           opacity: 0.5;
           transition: opacity 0.8s ease;
 
@@ -130,8 +130,8 @@
 
 
 // == Colors
-.grw-app-title :global {
-  .grw-site-name {
+.grw-app-title {
+  :global(.grw-site-name) {
     --bs-link-color-rgb: var(--grw-app-title-color-rgb, var(--bs-tertiary-color-rgb));
     --bs-link-opacity: 0.5;
   }

+ 2 - 2
apps/app/src/client/components/Sidebar/Custom/CustomSidebarSubstance.module.scss

@@ -1,7 +1,7 @@
 @use 'styles/organisms/wiki-custom-sidebar.scss';
 
-.grw-custom-sidebar-content :global {
-  .wiki {
+.grw-custom-sidebar-content {
+  :global(.wiki) {
     @extend %grw-custom-sidebar-content;
   }
 }

+ 10 - 10
apps/app/src/client/components/Sidebar/PageCreateButton/CreateButton.module.scss

@@ -1,11 +1,11 @@
 @use 'styles/variables' as var;
 @use '../button-styles';
 
-.btn-create :global {
+.btn-create {
   @extend %btn-basis;
 
   // centering
-  .icon {
+  :global(.icon) {
     top: 50%;
     left: 50%;
     transform: translateX(-50%) translateY(-50%);
@@ -13,10 +13,10 @@
 }
 
 // pointer-events
-.btn-create :global {
+.btn-create {
   pointer-events: none;
 
-  svg .background {
+  :global(svg .background) {
     pointer-events: fill;
   }
 }
@@ -26,20 +26,20 @@
   background-color: transparent !important;
 }
 
-.btn-create :global {
-  svg {
+.btn-create {
+  :global(svg) {
     fill: var(--bs-btn-bg);
   }
 }
 
-.btn-create:hover :global {
-  svg {
+.btn-create:hover {
+  :global(svg) {
     fill: var(--bs-btn-hover-bg);
   }
 }
 
-.btn-create:active :global {
-  svg {
+.btn-create:active {
+  :global(svg) {
     fill: var(--bs-btn-active-bg);
   }
 }

+ 11 - 11
apps/app/src/client/components/Sidebar/PageCreateButton/DropendToggle.module.scss

@@ -1,13 +1,13 @@
 @use 'styles/variables' as var;
 @use '../button-styles';
 
-.btn-toggle :global {
+.btn-toggle {
   @extend %btn-basis;
 
   left: 12px;
   padding: 0;
 
-  .icon {
+  :global(.icon) {
     top: 50%;
     right: 0;
     font-size: 22px;
@@ -17,7 +17,7 @@
 
 // no caret
 .btn-toggle {
-  &:global {
+  & {
     // no caret
     &::after {
       display: none !important;
@@ -26,8 +26,8 @@
 }
 
 // hitarea
-.btn-toggle :global {
-  .hitarea {
+.btn-toggle {
+  :global(.hitarea) {
     inset: 0 -10px 0 0;
   }
 }
@@ -37,20 +37,20 @@
   background-color: transparent !important;
 }
 
-.btn-toggle :global {
-  svg {
+.btn-toggle {
+  :global(svg) {
     fill: var(--grw-primary-400);
   }
 }
 
-.btn-toggle:hover :global {
-  svg {
+.btn-toggle:hover {
+  :global(svg) {
     fill: var(--grw-primary-400);
   }
 }
 
-.btn-toggle:active :global {
-  svg {
+.btn-toggle:active {
+  :global(svg) {
     fill: var(--grw-primary-600);
   }
 }

+ 11 - 11
apps/app/src/client/components/Sidebar/PageTreeItem/PageTreeItem.module.scss

@@ -2,31 +2,31 @@
 
 
 // fix height
-.page-tree-item :global {
-  li {
+.page-tree-item {
+  :global(li) {
     min-height: 40px;
   }
 }
 
 // font size
-.page-tree-item :global {
+.page-tree-item {
     font-size: 14px;
 }
 
 // == Colors
 
 // drag over
-.page-tree-item :global {
-  .drag-over {
+.page-tree-item {
+  :global(.drag-over) {
     background-color: var(--bs-list-group-action-active-bg);
   }
 }
 
 @include bs.color-mode(light) {
   // button
-  .page-tree-item :global {
-    .list-group-item-action {
-      .btn-page-item-control {
+  .page-tree-item {
+    :global(.list-group-item-action) {
+      :global(.btn-page-item-control) {
         --bs-btn-bg: transparent;
         --bs-btn-hover-bg: var(--grw-primary-200);
         --bs-btn-active-bg: var(--grw-primary-300);
@@ -37,9 +37,9 @@
 
 @include bs.color-mode(dark) {
   // button
-  .page-tree-item :global {
-    .list-group-item-action {
-      .btn-page-item-control {
+  .page-tree-item {
+    :global(.list-group-item-action) {
+      :global(.btn-page-item-control) {
         --bs-btn-bg: transparent;
         --bs-btn-hover-bg: var(--grw-primary-600);
         --bs-btn-active-bg: var(--grw-primary-700);

+ 13 - 13
apps/app/src/client/components/Sidebar/RecentChanges/RecentChangesSubstance.module.scss

@@ -1,52 +1,52 @@
 @use 'styles/mixins' as *;
 
-.grw-recent-changes-resize-button :global {
+.grw-recent-changes-resize-button {
   line-height: normal;
   transform: translateY(-2px);
 }
 
-.list-group-item :global {
+.list-group-item {
   font-size: 12px;
 
-  h6 {
+  :global(h6) {
     font-size: 14px;
   }
 
-  .grw-recent-changes-skeleton-small {
+  :global(.grw-recent-changes-skeleton-small) {
     @include grw-skeleton-text($font-size: 14px, $line-height: 16px);
 
     max-width: 120px;
   }
 
-  .grw-recent-changes-skeleton-h5 {
+  :global(.grw-recent-changes-skeleton-h5) {
     @include grw-skeleton-h5;
 
     max-width: 120px;
   }
 
-  .grw-recent-changes-skeleton-date {
+  :global(.grw-recent-changes-skeleton-date) {
     @include grw-skeleton-text($font-size: 10px, $line-height: 12px);
 
     width: 80px;
   }
 
   // For truncate-text
-  .flex-grow-1 {
+  :global(.flex-grow-1) {
     min-width: 0;
   }
 
-  .truncate-text {
+  :global(.truncate-text) {
     max-width: fit-content;
   }
 }
 
-.grw-recent-changes-item-lower :global {
+.grw-recent-changes-item-lower {
   font-size: 12px;
 
-  .material-symbols-outlined {
+  :global(.material-symbols-outlined) {
     font-size: 14px;
   }
-  .grw-formatted-distance-date {
+  :global(.grw-formatted-distance-date) {
     font-size: 10px;
   }
 }
@@ -55,13 +55,13 @@
 .grw-former-link a {
   --bs-link-opacity: 0.5;
 
-  &:global {
+  & {
     &:hover {
       --bs-link-opacity: 1;
     }
   }
 }
 
-.grw-recent-changes-item-lower :global {
+.grw-recent-changes-item-lower {
   color: var(--bs-gray-500);
 }

+ 7 - 7
apps/app/src/client/components/Sidebar/ResizableArea/ResizableArea.module.scss

@@ -1,19 +1,19 @@
-.grw-resizable-area :global {
+.grw-resizable-area {
   will-change: width;
 }
 
-.grw-resizable-area:not(:global .dragging) {
+.grw-resizable-area:not(:global(.dragging)) {
   transition: width 100ms cubic-bezier(0.2, 0, 0, 1) 0s;
 }
 
 
-.grw-navigation-draggable :global {
+.grw-navigation-draggable {
   position: absolute;
   top: 0;
   bottom: 0;
   left: 100%;
 
-  .grw-navigation-draggable-hitarea {
+  :global(.grw-navigation-draggable-hitarea) {
     position: absolute;
     left: -4px;
     width: 24px;
@@ -24,7 +24,7 @@
     background: transparent;
     border: 0;
   }
-  .grw-navigation-draggable-line {
+  :global(.grw-navigation-draggable-line) {
     position: absolute;
     left: -1px;
     display: none;
@@ -34,8 +34,8 @@
   }
 }
 
-.grw-navigation-draggable:hover :global {
-  .grw-navigation-draggable-line {
+.grw-navigation-draggable:hover {
+  :global(.grw-navigation-draggable-line) {
     display: block;
   }
 }

+ 51 - 60
apps/app/src/client/components/Sidebar/Sidebar.module.scss

@@ -1,16 +1,17 @@
+// stylelint-disable no-descending-specificity -- false positives from :global() function form conversion; each .grw-sidebar block targets different mode classes
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 @use 'styles/variables' as var;
 @use 'styles/mixins';
 
-.grw-sidebar :global {
+.grw-sidebar {
   top: 0;
 }
 
 
 // TODO: commonize reload button style
-.grw-sidebar :global {
-  .grw-sidebar-content-header {
-    .grw-btn-reload {
+.grw-sidebar {
+  :global(.grw-sidebar-content-header) {
+    :global(.grw-btn-reload) {
       font-size: 16px;
     }
   }
@@ -18,35 +19,31 @@
 
 // Dock Mode
 .grw-sidebar {
-  &:global {
-    &.grw-sidebar-dock {
-      position: sticky;
-    }
+  &:global(.grw-sidebar-dock) {
+    position: sticky;
   }
 }
 
 // Collapsed Mode
 .grw-sidebar {
-  &:global {
-    &.grw-sidebar-collapsed {
-      position: sticky;
-
-      .sidebar-contents-container {
-        border-color: var(--bs-border-color);
-        border-style: solid;
-        border-width : 1px 1px 1px 0;
-      }
+  &:global(.grw-sidebar-collapsed) {
+    position: sticky;
+
+    :global(.sidebar-contents-container) {
+      border-color: var(--bs-border-color);
+      border-style: solid;
+      border-width : 1px 1px 1px 0;
+    }
 
-      // open
-      .sidebar-contents-container.open {
-        position: absolute;
-        left: var.$grw-sidebar-nav-width;
-        min-height: 50vh;
-        max-height: calc(100vh - var.$grw-sidebar-nav-width * 2);
-        border-radius: 0 4px 4px 0 ;
-        .simple-scrollbar {
-          max-height: inherit;
-        }
+    // open
+    :global(.sidebar-contents-container.open) {
+      position: absolute;
+      left: var.$grw-sidebar-nav-width;
+      min-height: 50vh;
+      max-height: calc(100vh - var.$grw-sidebar-nav-width * 2);
+      border-radius: 0 4px 4px 0 ;
+      :global(.simple-scrollbar) {
+        max-height: inherit;
       }
     }
   }
@@ -54,78 +51,72 @@
 
 // Drawer Mode
 .grw-sidebar {
-  &:global {
-    &.grw-sidebar-drawer {
-      position: fixed;
-      z-index: bs.$zindex-fixed + 2;
-      width: 348px;
+  &:global(.grw-sidebar-drawer) {
+    position: fixed;
+    z-index: bs.$zindex-fixed + 2;
+    width: 348px;
 
-      // apply transition
-      transition-property: transform;
+    // apply transition
+    transition-property: transform;
 
-      @include mixins.apply-navigation-transition();
+    @include mixins.apply-navigation-transition();
 
-      &:not(.open) {
-        transform: translateX(-100%);
-      }
-      &.open {
-        z-index: bs.$zindex-modal;
-        transform: translateX(0);
-      }
+    &:not(:global(.open)) {
+      transform: translateX(-100%);
+    }
+    &:global(.open) {
+      z-index: bs.$zindex-modal;
+      transform: translateX(0);
     }
   }
 }
 
 
-.grw-sidebar :global {
+.grw-sidebar {
 
   // overwrite simplebar-react css
-  .simplebar-scrollbar::before {
+  :global(.simplebar-scrollbar::before) {
     background-color:var(--bs-gray-500);
   }
 
 }
 
 @include bs.color-mode(light) {
-  .grw-sidebar :global {
+  .grw-sidebar {
     --bs-border-color: var(--grw-highlight-200);
 
-    .sidebar-contents-container {
+    :global(.sidebar-contents-container) {
       background-color: color-mix(in srgb, var(--grw-highlight-100), var(--bs-body-bg));
     }
   }
 
   // frosted glass effect in collapsed mode
   .grw-sidebar {
-    &:global {
-      &.grw-sidebar-collapsed {
-        .sidebar-contents-container {
-          background-color: rgba(var(--grw-highlight-100-rgb), .8);
-          backdrop-filter: blur(20px);
-        }
+    &:global(.grw-sidebar-collapsed) {
+      :global(.sidebar-contents-container) {
+        background-color: rgba(var(--grw-highlight-100-rgb), .8);
+        backdrop-filter: blur(20px);
       }
     }
   }
 }
 
 @include bs.color-mode(dark) {
-  .grw-sidebar :global {
+  .grw-sidebar {
     --bs-color: var(--bs-gray-400);
     --bs-border-color: var(--grw-highlight-800);
 
-    .sidebar-contents-container {
+    :global(.sidebar-contents-container) {
       background-color: color-mix(in srgb, var(--grw-highlight-800), var(--bs-body-bg));
     }
   }
 
   // frosted glass effect in collapsed mode
   .grw-sidebar {
-    &:global {
-      &.grw-sidebar-collapsed {
-        .sidebar-contents-container {
-          background-color: rgba(var(--grw-highlight-800-rgb), .8);
-          backdrop-filter: blur(20px);
-        }
+    &:global(.grw-sidebar-collapsed) {
+      :global(.sidebar-contents-container) {
+        background-color: rgba(var(--grw-highlight-800-rgb), .8);
+        backdrop-filter: blur(20px);
       }
     }
   }

+ 3 - 3
apps/app/src/client/components/Sidebar/SidebarContents.module.scss

@@ -1,16 +1,16 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.grw-sidebar-contents :global {
+.grw-sidebar-contents {
   --bs-heading-color: var(--bs-tertiary-color);
   --bs-body-color: var(--bs-secondary-color);
   --bs-link-color-rgb: var(--bs-secondary-color-rgb);
   --bs-link-opacity: .75;
 
-  .list-group-item {
+  :global(.list-group-item) {
     --bs-list-group-bg: transparent;
   }
 
-  .wip-page-badge {
+  :global(.wip-page-badge) {
     --bs-badge-font-size: 0.5rem;
   }
 }

+ 2 - 2
apps/app/src/client/components/Sidebar/SidebarHead/SidebarHead.module.scss

@@ -3,7 +3,7 @@
 
 // == Colors
 @include bs.color-mode(light) {
-  .grw-sidebar-head :global {
+  .grw-sidebar-head {
     background-color: var(
       --grw-sidebar-head-bg,
       var(
@@ -15,7 +15,7 @@
 }
 
 @include bs.color-mode(dark) {
-  .grw-sidebar-head :global {
+  .grw-sidebar-head {
     background-color: var(
       --grw-sidebar-head-bg,
       var(

+ 10 - 10
apps/app/src/client/components/Sidebar/SidebarHead/ToggleCollapseButton.module.scss

@@ -3,7 +3,7 @@
 @use 'styles/mixins';
 @use '../button-styles';
 
-.btn-toggle-collapse :global {
+.btn-toggle-collapse {
   @extend %btn-basis;
 
   $height: var.$grw-sidebar-nav-width; // declare $height with the same value as the sidebar nav width
@@ -12,12 +12,12 @@
 }
 
 // icon
-.btn-toggle-collapse :global {
-  .material-symbols-outlined {
+.btn-toggle-collapse {
+  :global(.material-symbols-outlined) {
     transition: transform 0.25s;
 
     // rotation
-    &.rotate180 {
+    &:global(.rotate180) {
       transform: rotate(180deg);
     }
   }
@@ -33,8 +33,8 @@
 
 // == Colors
 .btn-toggle-collapse {
-  &:global {
-    &.btn.btn-primary {
+  & {
+    &:global(.btn.btn-primary) {
       @extend %btn-primary-color-vars;
 
       --bs-btn-hover-color: color-mix(in srgb, var(
@@ -50,8 +50,8 @@
 
 @include bs.color-mode(light) {
   .btn-toggle-collapse {
-    &:global {
-      &.btn.btn-primary {
+    & {
+      &:global(.btn.btn-primary) {
         --bs-btn-color: color-mix(in srgb, var(--grw-sidebar-nav-btn-color, var(--bs-gray-500)) 50%, transparent);
         --bs-btn-hover-bg: var(--grw-sidebar-nav-btn-hover-bg, var(--grw-highlight-300));
       }
@@ -61,8 +61,8 @@
 
 @include bs.color-mode(dark) {
   .btn-toggle-collapse {
-    &:global {
-      &.btn.btn-primary {
+    & {
+      &:global(.btn.btn-primary) {
         --bs-btn-color: color-mix(in srgb, var(--grw-sidebar-nav-btn-color, var(--bs-gray-600)) 50%, transparent);
         --bs-btn-hover-bg: var(--grw-sidebar-nav-btn-hover-bg, var(--grw-highlight-700));
       }

+ 3 - 3
apps/app/src/client/components/Sidebar/SidebarNav/HelpDropdown.module.scss

@@ -1,14 +1,14 @@
 @use '@growi/core-styles/scss/helpers/modifier-keys';
 
-.help-dropdown :global {
+.help-dropdown {
   @include modifier-keys.modifier-key;
 }
 
-.help-dropdown-menu :global {
+.help-dropdown-menu {
   @include modifier-keys.modifier-key;
 }
 
-.help-dropdown-menu :global {
+.help-dropdown-menu {
   --bs-dropdown-font-size: 14px;
   min-width: 240px;
 }

+ 9 - 9
apps/app/src/client/components/Sidebar/SidebarNav/PersonalDropdown.module.scss

@@ -1,29 +1,29 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.btn-personal-dropdown :global {
-  img {
+.btn-personal-dropdown {
+  :global(img) {
     border: 2px solid var(--bs-border-color-translucent);
   }
 }
 
-.personal-dropdown-header :global {
-  .item-text-email {
+.personal-dropdown-header {
+  :global(.item-text-email) {
     font-size: 10.5px;
   }
 }
 
-.personal-dropdown-menu :global {
+.personal-dropdown-menu {
   --bs-dropdown-font-size: 14px;
 }
 
 
 // == Colors
 @include bs.color-mode(light) {
-  .personal-dropdown-header :global {
+  .personal-dropdown-header {
     color: var(--bs-gray-600);
   }
 
-  .personal-dropdown-item :global {
+  .personal-dropdown-item {
     --bs-link-color-rgb:var(--bs-gray-600);
 
     color: var(--bs-gray-600);
@@ -31,11 +31,11 @@
 }
 
 @include bs.color-mode(dark) {
-  .personal-dropdown-header :global {
+  .personal-dropdown-header {
     color: var(--bs-gray-500);
   }
 
-  .personal-dropdown-item :global {
+  .personal-dropdown-item {
     --bs-link-color-rgb:var(--bs-gray-500);
 
     color: var(--bs-gray-500);

+ 9 - 9
apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.module.scss

@@ -3,32 +3,32 @@
 @use '../variables' as sidebarVar;
 
 // == Sizes
-.grw-primary-items :global {
+.grw-primary-items {
 
-  .btn {
+  :global(.btn) {
     @extend %btn-basis;
 
     width: 40px;
     height: 40px;
   }
 
-  .badge :global {
+  :global(.badge) {
     left: 26px;
     font-size: 8px;
   }
 }
 
 // == Colors
-.grw-primary-items :global {
-  .btn.btn-primary {
+.grw-primary-items {
+  :global(.btn.btn-primary) {
     @extend %btn-primary-color-vars;
     --bs-btn-active-color: var(--grw-sidebar-nav-btn-active-color, var(--grw-primary-500));
   }
 }
 
 @include bs.color-mode(light) {
-  .grw-primary-items :global {
-    .btn-primary {
+  .grw-primary-items {
+    :global(.btn-primary) {
       --bs-btn-color: var(--grw-sidebar-nav-btn-color, var(--grw-highlight-600));
       --bs-btn-color-hover: var(--grw-sidebar-nav-btn-hover-color, var(--grw-highlight-700));
       --bs-btn-hover-bg: var(--grw-sidebar-nav-btn-hover-bg, var(--grw-highlight-200));
@@ -38,8 +38,8 @@
 }
 
 @include bs.color-mode(dark) {
-  .grw-primary-items :global {
-    .btn-primary {
+  .grw-primary-items {
+    :global(.btn-primary) {
       --bs-btn-color: var(--grw-sidebar-nav-btn-color, var(--grw-highlight-600));
       --bs-btn-color-hover: var(--grw-sidebar-nav-btn-hover-color, var(--grw-highlight-400));
       --bs-btn-hover-bg: var(--grw-sidebar-nav-btn-hover-bg, var(--grw-highlight-900));

+ 8 - 8
apps/app/src/client/components/Sidebar/SidebarNav/SecondaryItems.module.scss

@@ -2,8 +2,8 @@
 @use '../button-styles';
 
 
-.grw-secondary-items :global {
-  .btn {
+.grw-secondary-items {
+  :global(.btn) {
     @extend %btn-basis;
 
     opacity: 0.6;
@@ -16,15 +16,15 @@
 }
 
 // == Colors
-.grw-secondary-items :global {
-  .btn.btn-primary {
+.grw-secondary-items {
+  :global(.btn.btn-primary) {
     @extend %btn-primary-color-vars;
   }
 }
 
 @include bs.color-mode(light) {
-  .grw-secondary-items :global {
-    .btn-primary {
+  .grw-secondary-items {
+    :global(.btn-primary) {
       --bs-btn-color: var(--grw-sidebar-nav-btn-color, var(--grw-primary-600));
       --bs-btn-hover-bg: var(--grw-sidebar-nav-btn-hover-bg, var(--grw-highlight-300));
     }
@@ -32,8 +32,8 @@
 }
 
 @include bs.color-mode(dark) {
-  .grw-secondary-items :global {
-    .btn-primary {
+  .grw-secondary-items {
+    :global(.btn-primary) {
       --bs-btn-color: var(--grw-sidebar-nav-btn-color, var(--grw-primary-500));
       --bs-btn-hover-bg: var(--grw-sidebar-nav-btn-hover-bg, var(--grw-highlight-700));
     }

+ 4 - 4
apps/app/src/client/components/Sidebar/SidebarNav/SidebarNav.module.scss

@@ -1,11 +1,11 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 @use 'styles/variables' as var;
 
-.grw-sidebar-nav :global {
+.grw-sidebar-nav {
   width: var.$grw-sidebar-nav-width;
   border-right : 1px solid var(--bs-border-color);
 
-  .grw-sidebar-nav-secondary-container {
+  :global(.grw-sidebar-nav-secondary-container) {
     position: fixed;
     bottom: 1.5rem;
   }
@@ -14,13 +14,13 @@
 
 // == Colors
 @include bs.color-mode(light) {
-  .grw-sidebar-nav :global {
+  .grw-sidebar-nav {
     background-color: var(--grw-sidebar-nav-bg, var(--grw-highlight-100));
   }
 }
 
 @include bs.color-mode(dark) {
-  .grw-sidebar-nav :global {
+  .grw-sidebar-nav {
     background-color: var(--grw-sidebar-nav-bg, var(--grw-highlight-800));
   }
 }

+ 2 - 2
apps/app/src/client/components/Sidebar/SidebarNav/SkeletonItem.module.scss

@@ -1,11 +1,11 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 @use '../variables' as sidebarVar;
 
-.grw-skeleton-item :global {
+.grw-skeleton-item {
   height: sidebarVar.$grw-sidebar-button-height;
   padding: .75rem;
 
-  .grw-skeleton {
+  :global(.grw-skeleton) {
     background-color: var(--bs-secondary-bg-subtle);
   }
 }

+ 4 - 5
apps/app/src/client/components/Sidebar/Skeleton/DefaultContentSkelton.module.scss

@@ -1,16 +1,15 @@
 @use 'styles/mixins';
 
-.grw-default-content-skelton :global {
-  .grw-skeleton-text {
+.grw-default-content-skelton {
+  :global(.grw-skeleton-text),
+  :global(.grw-skeleton-text-full) {
     @include mixins.grw-skeleton-text($font-size:15px, $line-height:21.42px);
 
     max-width: 160px;
     margin: 15px 0;
   }
 
-  .grw-skeleton-text-full {
-    @extend .grw-skeleton-text;
-
+  :global(.grw-skeleton-text-full) {
     max-width: 100%;
   }
 }

+ 10 - 10
apps/app/src/client/components/SlackNotification.module.scss

@@ -1,11 +1,11 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
 
-.grw-slack-switch :global {
-  .input-group-text {
+.grw-slack-switch {
+  :global(.input-group-text) {
     background-color: inherit;
   }
-  .form-check-input {
+  :global(.form-check-input) {
     cursor: pointer;
     background-repeat: no-repeat;
     background-attachment:scroll;
@@ -15,14 +15,14 @@
     box-shadow: none;
     transition: all 0.4s ease-out;
   }
-  .form-control::placeholder {
+  :global(.form-control::placeholder) {
     color: bs.$gray-500
   }
 }
 
 :root[data-bs-theme='light'] {
-  .grw-slack-switch :global {
-    .form-check-input:not(:checked) {
+  .grw-slack-switch {
+    :global(.form-check-input:not(:checked)) {
       background-color: bs.$gray-200;
       background-image:
         url('/images/icons/slack/slack-logo-off.svg'),
@@ -30,7 +30,7 @@
       background-position: 15%, 5%, 50%, 50%;
     }
 
-    .form-check-input:checked {
+    :global(.form-check-input:checked) {
       background-color: #E7A9E8;
       background-image:
         url('/images/icons/slack/slack-logo-on.svg'),
@@ -41,8 +41,8 @@
 }
 
 :root[data-bs-theme='dark'] {
-  .grw-slack-switch :global {
-    .form-check-input:not(:checked) {
+  .grw-slack-switch {
+    :global(.form-check-input:not(:checked)) {
       background-color: bs.$gray-200;
       background-image:
         url('/images/icons/slack/slack-logo-dark-off.svg'),
@@ -50,7 +50,7 @@
       background-position: 14%, 4%, 50%, 50%;
     }
 
-    .form-check-input:checked {
+    :global(.form-check-input:checked) {
       background-color: #731f74;
       background-image:
         url('/images/icons/slack/slack-logo-dark-on.svg'),

+ 18 - 18
apps/app/src/client/components/StaffCredit/StaffCredit.module.scss

@@ -1,5 +1,5 @@
 // Staff Credit
-.staff-credit :global {
+.staff-credit {
   // attached !important for updating from .modal-dialog class style
   width: 80vw !important;
   max-width: unset !important;
@@ -8,14 +8,14 @@
   margin: 10vh 10vw !important;
 
   // see https://css-tricks.com/old-timey-terminal-styling/
-  .credit-curtain {
+  :global(.credit-curtain) {
     padding-top: 80vh;
     text-shadow: 0 0 10px #c8c8c8;
     background-color: black;
     background-image: radial-gradient(rgba(50, 100, 100, 75%), black 120%);
   }
 
-  .background {
+  :global(.background) {
     position: absolute;
     top: 0;
     left: 0;
@@ -25,44 +25,44 @@
     background: repeating-linear-gradient(0deg, rgba(black, 0.15), rgba(black, 0.15) 2px, transparent 2px, transparent 4px);
   }
 
-  h1,
-  h2,
-  h3,
-  h4,
-  h5,
-  h6,
-  .dev-position,
-  .dev-name {
+  :global(h1),
+  :global(h2),
+  :global(h3),
+  :global(h4),
+  :global(h5),
+  :global(h6),
+  :global(.dev-position),
+  :global(.dev-name) {
     color: white;
   }
 
   $credit-length: -240em;
 
-  h1 {
+  :global(h1) {
     font-size: 3em;
   }
 
-  h2 {
+  :global(h2) {
     font-size: 2.2em;
   }
 
-  .dev-position {
+  :global(.dev-position) {
     font-size: 1em;
   }
 
-  .dev-name {
+  :global(.dev-name) {
     font-size: 1.8em;
   }
 
-  .staff-credit-mt-10rem {
+  :global(.staff-credit-mt-10rem) {
     margin-top: 10rem;
   }
 
-  .staff-credit-mb-6rem {
+  :global(.staff-credit-mb-6rem) {
     margin-bottom: 6rem;
   }
 
-  .staff-credit-content {
+  :global(.staff-credit-content) {
     padding-bottom: 40vh;
   }
 }

+ 10 - 10
apps/app/src/client/components/TableOfContents.module.scss

@@ -1,29 +1,29 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.revision-toc :global {
+.revision-toc {
   // to get on the Attachment row
   z-index: 1;
   padding: 5px;
   font-size: 0.9em;
   border-bottom: 1px solid transparent;
 
-  .revision-toc-content {
-    ul {
+  :global(.revision-toc-content) {
+    :global(ul) {
       list-style-type: disc;
     }
 
-    li {
+    :global(li) {
       margin: 6px;
     }
-    > ul {
+    > :global(ul) {
       padding-left: 0;
-      ul {
+      :global(ul) {
         padding-left: 1em;
       }
     }
 
     // first level of li
-    > ul > li {
+    > :global(ul > li) {
       padding: 5px;
       margin-left: 17px;
     }
@@ -42,9 +42,9 @@
 
 
 // == Colors
-.revision-toc :global {
-  .revision-toc-content {
-    ::marker {
+.revision-toc {
+  :global(.revision-toc-content) {
+    :global(::marker) {
       color: var(--bs-tertiary-color);
     }
   }

+ 6 - 6
apps/app/src/client/components/TagList.module.scss

@@ -1,7 +1,7 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.grw-tag-list :global {
-  .list-group {
+.grw-tag-list {
+  :global(.list-group) {
     // remove border
     --bs-border-width: 0;
   }
@@ -10,8 +10,8 @@
 
 // == Colors
 @include bs.color-mode(light) {
-  .grw-tag-list :global {
-    .grw-tag-count {
+  .grw-tag-list {
+    :global(.grw-tag-count) {
       color: var(--bs-gray-600);
       background-color: var(--grw-highlight-100);
     }
@@ -19,8 +19,8 @@
 }
 
 @include bs.color-mode(dark) {
-  .grw-tag-list :global {
-    .grw-tag-count {
+  .grw-tag-list {
+    :global(.grw-tag-count) {
       color: var(--bs-gray-500);
       background-color: var(--grw-highlight-800);
     }

+ 2 - 2
apps/app/src/client/components/TemplateModal/TemplateModal.module.scss

@@ -1,7 +1,7 @@
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.dm-templates :global {
-  .dropdown-item:not(:first-child) {
+.dm-templates {
+  :global(.dropdown-item:not(:first-child)) {
     border-top: 1px solid bs.$border-color;
   }
 }

+ 20 - 20
apps/app/src/client/components/UsersHomepageFooter.module.scss

@@ -3,19 +3,19 @@
 $grw-sidebar-content-header-height: 58px;
 $grw-sidebar-content-footer-height: 50px;
 
-.user-page-footer :global {
-  .grw-user-page-list-m {
-    .growi-custom-icons {
+.user-page-footer {
+  :global(.grw-user-page-list-m) {
+    :global(.growi-custom-icons) {
       font-size: 1.1em;
     }
-    .list-group {
-      .list-group-item {
-        .grw-visible-on-hover {
+    :global(.list-group) {
+      :global(.list-group-item) {
+        :global(.grw-visible-on-hover) {
           display: none;
         }
 
         &:hover {
-          .grw-visible-on-hover {
+          :global(.grw-visible-on-hover) {
             display: block;
           }
         }
@@ -23,47 +23,47 @@ $grw-sidebar-content-footer-height: 50px;
         min-height: 40px;
         border-radius: 0;
 
-        &.grw-bookmark-item-list {
-          .user-picture {
+        &:global(.grw-bookmark-item-list) {
+          :global(.user-picture) {
             width: 16px;
             height: 16px;
             vertical-align: text-bottom;
 
-            &.user-picture-md {
+            &:global(.user-picture-md) {
               width: 20px;
               height: 20px;
             }
           }
-          svg {
+          :global(svg) {
             width: 14px;
             height: 14px;
           }
-          .grw-foldertree-control {
+          :global(.grw-foldertree-control) {
             margin-left: 1rem;
           }
         }
       }
     }
 
-    .grw-foldertree-item-container {
-      input {
+    :global(.grw-foldertree-item-container) {
+      :global(input) {
         max-width: 25%;
       }
     }
-    .grw-foldertree-title-anchor {
+    :global(.grw-foldertree-title-anchor) {
       width: fit-content !important;
       margin-right: 20px;
     }
-    .new-bookmark-folder {
+    :global(.new-bookmark-folder) {
       max-height: 30px;
     }
-    .grw-expand-compress-btn {
+    :global(.grw-expand-compress-btn) {
       max-height: 40px;
     }
-    .grw-folder-tree-container {
-      .grw-drop-item-area {
+    :global(.grw-folder-tree-container) {
+      :global(.grw-drop-item-area) {
         padding: 1rem;
-        .grw-accept-drop-item {
+        :global(.grw-accept-drop-item) {
           padding: 0.5rem;
           border-style: dashed;
           border-width: 0.15rem;

+ 4 - 4
apps/app/src/components/Admin/Common/AdminNavigation.module.scss

@@ -1,7 +1,7 @@
 // button layout
 .admin-navigation {
-  &:global {
-    & > a + a {
+  & {
+    & > :global(a + a) {
       margin-top: 2px;
     }
   }
@@ -9,8 +9,8 @@
 
 // sticky settings
 .admin-navigation {
-  &:global {
-    &.sticky-top {
+  & {
+    &:global(.sticky-top) {
       top: 30px;
     }
   }

+ 5 - 5
apps/app/src/components/Common/GrowiLogo.module.scss

@@ -1,9 +1,9 @@
 @use '@growi/core-styles/scss/variables/growi-official-colors';
 
 // == Colors
-.grw-logo :global {
+.grw-logo {
   // set transition for fill
-  svg, svg * {
+  :global(svg), :global(svg *) {
     transition: fill 0.8s ease-out;
   }
 
@@ -11,13 +11,13 @@
 }
 
 .grw-logo {
-  &:global {
+  & {
     &:hover {
-      .group1 {
+      :global(.group1) {
         fill: growi-official-colors.$growi-green;
       }
 
-      .group2 {
+      :global(.group2) {
         fill: growi-official-colors.$growi-blue;
       }
     }

+ 9 - 9
apps/app/src/components/Common/PagePathNav/PagePathNavLayout.module.scss

@@ -2,8 +2,8 @@
 @use '@growi/ui/scss/atoms/btn-muted';
 
 
-.grw-page-path-nav-layout :global {
-  .grw-page-path-nav-copydropdown {
+.grw-page-path-nav-layout {
+  :global(.grw-page-path-nav-copydropdown) {
     visibility: hidden;
     @include bs.media-breakpoint-down(md) {
       display: block;
@@ -12,24 +12,24 @@
 }
 
 .grw-page-path-nav-layout {
-  &:global {
+  & {
     &:hover {
-      .grw-page-path-nav-copydropdown {
+      :global(.grw-page-path-nav-copydropdown) {
         visibility: visible;
       }
     }
   }
 }
 
-.grw-page-path-nav :global {
-  .btn-copy {
+.grw-page-path-nav {
+  :global(.btn-copy) {
     @include btn-muted.colorize(bs.$orange);
   }
 }
 
 // == Colors
-.grw-former-link :global {
-  .separator {
+.grw-former-link {
+  :global(.separator) {
     opacity: 0.75;
   }
 }
@@ -37,7 +37,7 @@
 .grw-former-link a {
   --bs-link-opacity: 0.75;
 
-  &:global {
+  & {
     &:hover {
       --bs-link-opacity: 1;
     }

+ 60 - 60
apps/app/src/components/Layout/Admin.module.scss

@@ -4,90 +4,90 @@
 $slack-work-space-name-card-background: #fff5ff;
 $slack-work-space-name-card-border: #efc1f6;
 
-.admin-page :global {
-  .admin-user-menu {
-    .dropdown-menu {
+.admin-page {
+  :global(.admin-user-menu) {
+    :global(.dropdown-menu) {
       right: 0;
       left: auto;
       width: 400px;
     }
   }
 
-  .admin-group-menu {
-    .dropdown-menu {
+  :global(.admin-group-menu) {
+    :global(.dropdown-menu) {
       right: 0;
       left: auto;
     }
   }
 
-  .admin-customize {
-    .ss-container img {
+  :global(.admin-customize) {
+    :global(.ss-container img) {
       padding: 0.5em;
       background-color: $gray-300;
     }
 
-    .table-user-list {
-      .label-admin {
+    :global(.table-user-list) {
+      :global(.label-admin) {
         margin-left: 1em;
       }
     }
   }
 
-  .admin-setting-header {
+  :global(.admin-setting-header) {
     margin-bottom: 20px;
     border-bottom: 1px solid transparent;
   }
 
-  .custom-card {
+  :global(.custom-card) {
     padding: 8px 16px;
-    ul {
+    :global(ul) {
       margin-bottom: 0;
     }
   }
 
-  .admin-security {
-    .passport-logo {
+  :global(.admin-security) {
+    :global(.passport-logo) {
       height: 32px;
       padding: 3px;
       margin-top: -0.5em;
       background-color: black;
     }
 
-    .auth-mechanism-configurations {
+    :global(.auth-mechanism-configurations) {
       min-height: 80vh;
     }
   }
 
-  .admin-customize-sidebar-icon {
-    svg {
+  :global(.admin-customize-sidebar-icon) {
+    :global(svg) {
       width: 20px;
       height: 20px;
     }
   }
 
-  .admin-notification {
-    table .admin-notif-list {
-      td {
+  :global(.admin-notification) {
+    :global(table .admin-notif-list) {
+      :global(td) {
         vertical-align: middle;
       }
-      .td-abs-center {
+      :global(.td-abs-center) {
         width: 1px; // to keep the cell small
         text-align: center;
       }
     }
   }
 
-  .admin-importer {
-    table.table-mapping {
-      th,
-      td {
+  :global(.admin-importer) {
+    :global(table.table-mapping) {
+      :global(th),
+      :global(td) {
         text-align: center;
       }
     }
   }
 
-  .admin-export {
-    .progress {
+  :global(.admin-export) {
+    :global(.progress) {
       height: 8px;
     }
   }
@@ -95,58 +95,58 @@ $slack-work-space-name-card-border: #efc1f6;
   /*
   Slack Integration
   */
-  .selecting-bot-type {
-    .supplementary-bot-name {
+  :global(.selecting-bot-type) {
+    :global(.supplementary-bot-name) {
       font-size: 1rem;
     }
-    .bg-info {
+    :global(.bg-info) {
       font-size: 0.6rem;
     }
-    .admin-bot-card {
+    :global(.admin-bot-card) {
       min-width: 300px;
       max-width: 500px;
       border-radius: 8px !important;
-      .grw-botcard-title-active {
+      :global(.grw-botcard-title-active) {
         color: $gray-200;
       }
     }
-    .border-primary {
+    :global(.border-primary) {
       border-width: 2px;
     }
   }
 
   // TODO: change to utility class on Bootstrap 5
-  .slack-connection-log {
-    .slack-connection-log-title {
+  :global(.slack-connection-log) {
+    :global(.slack-connection-log-title) {
       border-left: 2px solid;
     }
-    .slack-connection-log-body {
+    :global(.slack-connection-log-body) {
       border: 2px solid;
     }
   }
 
-  .admin-slack-integration {
-    .admin-setting-header {
-      .btn-link {
+  :global(.admin-slack-integration) {
+    :global(.admin-setting-header) {
+      :global(.btn-link) {
         font-size: 1rem;
       }
     }
   }
 
-  .bot-integration {
-    .admin-bot-card {
+  :global(.bot-integration) {
+    :global(.admin-bot-card) {
       border-radius: 8px !important;
     }
-    .admin-border-failed {
+    :global(.admin-border-failed) {
       border-style: dashed;
       border-width: 2px;
     }
-    .admin-border-success {
+    :global(.admin-border-success) {
       border-width: 3px;
     }
 
-    .grw-bridge-proxy-circle {
-      .circle {
+    :global(.grw-bridge-proxy-circle) {
+      :global(.circle) {
         inset: 0;
         width: 100px;
         height: 100px;
@@ -159,20 +159,20 @@ $slack-work-space-name-card-border: #efc1f6;
         }
       }
 
-      .circle-inner {
+      :global(.circle-inner) {
         transform: translate(-50%, 25%);
       }
-      .circle-inner.grw-proxy-server-name {
+      :global(.circle-inner.grw-proxy-server-name) {
         margin-top: 55px;
         transform: translate(-50%, -25%);
       }
     }
 
     // switch layout for Bridge component
-    .grw-bridge-container {
+    :global(.grw-bridge-container) {
       // with ProxyCircle
-      &.with-proxy {
-        .hr-container {
+      &:global(.with-proxy) {
+        :global(.hr-container) {
           margin-top: 40px;
 
           @include media-breakpoint-up(lg) {
@@ -182,7 +182,7 @@ $slack-work-space-name-card-border: #efc1f6;
       }
     }
 
-    .slack-work-space-name-card {
+    :global(.slack-work-space-name-card) {
       background-color: $slack-work-space-name-card-background;
       border: 1px solid $slack-work-space-name-card-border;
     }
@@ -223,20 +223,20 @@ $slack-work-space-name-card-border: #efc1f6;
   //   }
   // }
 
-  .admin-audit-log {
-    .select-action-dropdown {
+  :global(.admin-audit-log) {
+    :global(.select-action-dropdown) {
       max-height: 500px;
       overflow-y: auto;
     }
-    .date-range-picker {
+    :global(.date-range-picker) {
       width: 188px;
       height: auto;
     }
-    .jump-page-input {
+    :global(.jump-page-input) {
       width: 50px;
     }
 
-    .table-bordered {
+    :global(.table-bordered) {
       table-layout: fixed;
     }
 
@@ -245,17 +245,17 @@ $slack-work-space-name-card-border: #efc1f6;
 
 
 
-  .settings-table {
+  :global(.settings-table) {
 
-    .item-name {
+    :global(.item-name) {
       width: 150px;
     }
 
-    td.unused {
+    :global(td.unused) {
       opacity: 0.5;
     }
 
-    &.use-only-env-vars .from-env-vars {
+    &:global(.use-only-env-vars .from-env-vars) {
       background-color: rgba($info, 0.1);
     }
   }

+ 3 - 3
apps/app/src/components/Layout/BasicLayout.module.scss

@@ -3,8 +3,8 @@
 
 
 // for react-toastify
-.grw-basic-layout :global {
-  .Toastify .Toastify__toast-container {
+.grw-basic-layout {
+  :global(.Toastify .Toastify__toast-container) {
     top: 2.5em;
 
     @include bs.media-breakpoint-down(md) {
@@ -15,7 +15,7 @@
 
 .grw-basic-layout {
   @include mixins.with-editing() {
-    .Toastify .Toastify__toast-container {
+    :global(.Toastify .Toastify__toast-container) {
       top: 5em;
 
       @include bs.media-breakpoint-down(md) {

+ 35 - 35
apps/app/src/components/Layout/NoLoginLayout.module.scss

@@ -4,59 +4,59 @@
 @use '@growi/core-styles/scss/variables/growi-official-colors' as var;
 
 
-.nologin :global {
+.nologin {
   height: 100vh;
 
-  .nologin-header,
-  .nologin-dialog {
+  :global(.nologin-header),
+  :global(.nologin-dialog) {
     max-width: 480px;
   }
 
   // layout
-  .main {
+  :global(.main) {
     width: 100vw;
 
-    .nologin-header {
+    :global(.nologin-header) {
       padding-top: 30px;
       padding-bottom: 10px;
-      svg {
+      :global(svg) {
         fill: white;
       }
     }
 
-    .growi-logo-type {
+    :global(.growi-logo-type) {
       margin-left: 7px;
     }
 
   }
 
   // styles
-  .alert {
+  :global(.alert) {
     padding: 0.5em 1em 0.5em 2em;
   }
 
-  .input-group {
+  :global(.input-group) {
     margin-bottom: 16px;
 
-    .input-group-text {
+    :global(.input-group-text) {
       text-align: center;
     }
   }
 
-  .input-group:not(.has-error) {
-    .form-control {
+  :global(.input-group:not(.has-error)) {
+    :global(.form-control) {
       border: transparent;
     }
   }
 
   // footer link text
-  .link-growi-org {
+  :global(.link-growi-org) {
     font-size: smaller;
     font-weight: bold;
 
     &,
-    .growi,
-    .org {
+    :global(.growi),
+    :global(.org) {
       transition: color 0.8s;
     }
   }
@@ -79,7 +79,7 @@
 
 // Light mode color
 @include bs.color-mode(light) {
-  .nologin :global {
+  .nologin {
     // background color
     $color-gradient: #3c465c;
 
@@ -87,13 +87,13 @@
       linear-gradient(135deg, var.$growi-green 10%, hsla(225deg, 95%, 50%, 0%) 70%), linear-gradient(225deg, var.$growi-blue 10%, hsla(140deg, 90%, 50%, 0%) 80%),
       linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35deg, 95%, 55%, 0%) 70%);
 
-    .nologin-header {
+    :global(.nologin-header) {
       background-color: rgba(white, 0.3);
     }
 
-    .nologin-dialog {
+    :global(.nologin-dialog) {
       background-color: rgba(white, 0.3);
-      .link-switch {
+      :global(.link-switch) {
         color: #1939b8;
         &:hover {
           color: lighten(#1939b8,20%);
@@ -101,8 +101,8 @@
       }
     }
 
-    .input-group {
-      .form-control {
+    :global(.input-group) {
+      :global(.form-control) {
         color: bs.$gray-800;
         background-color: white;
         box-shadow: unset;
@@ -113,18 +113,18 @@
       }
     }
 
-    .link-growi-org {
+    :global(.link-growi-org) {
       color: rgba(white, 0.4);
 
       &:hover,
-      &.focus {
+      &:global(.focus) {
         color: white;
 
-        .growi {
+        :global(.growi) {
           color: darken(var.$growi-blue, 10%);
         }
 
-        .org {
+        :global(.org) {
           color: darken(var.$growi-green, 10%);
         }
       }
@@ -134,7 +134,7 @@
 
 // Dark mode color
 @include bs.color-mode(dark) {
-  .nologin :global {
+  .nologin {
     // background color
     $color-gradient: #3c465c;
 
@@ -142,13 +142,13 @@
       linear-gradient(135deg, var.$growi-green 10%, hsla(225deg, 95%, 50%, 0%) 70%), linear-gradient(225deg, var.$growi-blue 10%, hsla(140deg, 90%, 50%, 0%) 80%),
       linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35deg, 95%, 55%, 0%) 70%);
 
-    .nologin-header {
+    :global(.nologin-header) {
       background-color: rgba(black, 0.3);
     }
 
-    .nologin-dialog {
+    :global(.nologin-dialog) {
       background-color: rgba(black, 0.3);
-      .link-switch {
+      :global(.link-switch) {
         color: #7b9bd5;
         &:hover {
           color: lighten(#7b9bd5,10%);
@@ -156,8 +156,8 @@
       }
     }
 
-    .input-group {
-      .form-control {
+    :global(.input-group) {
+      :global(.form-control) {
         color: white;
         background-color: rgba(#505050, 0.7);
         box-shadow: unset;
@@ -168,18 +168,18 @@
       }
     }
 
-    .link-growi-org {
+    :global(.link-growi-org) {
       color: rgba(white, 0.4);
 
       &:hover,
-      &.focus {
+      &:global(.focus) {
         color: rgba(white, 0.7);
 
-        .growi {
+        :global(.growi) {
           color: darken(var.$growi-blue, 5%);
         }
 
-        .org {
+        :global(.org) {
           color: darken(var.$growi-green, 5%);
         }
       }

+ 14 - 14
apps/app/src/components/Layout/SearchResultLayout.module.scss

@@ -1,42 +1,42 @@
 @use 'styles/variables' as var;
 @use '@growi/core-styles/scss/bootstrap/init' as bs;
 
-.on-search :global {
-  .page-wrapper {
+.on-search {
+  :global(.page-wrapper) {
     height: 100vh;
   }
 
-  .search-control-include-options {
-    .card-body {
+  :global(.search-control-include-options) {
+    :global(.card-body) {
       padding: 5px 10px;
     }
   }
-  .search-result-list {
+  :global(.search-result-list) {
 
-    .search-result-keyword {
+    :global(.search-result-keyword) {
       font-size: 17.5px;
       font-weight: bold;
     }
-    .search-result-select-group {
-      > select {
+    :global(.search-result-select-group) {
+      > :global(select) {
         max-width: 8rem;
       }
     }
 
     // list group
-    .page-list {
+    :global(.page-list) {
       // not show top label in search result list
-      .page-list-meta {
-        .top-label {
+      :global(.page-list-meta) {
+        :global(.top-label) {
           display: none;
         }
       }
     }
   }
 
-  .search-result-content {
-    .search-result-content-body-container {
-      .wiki {
+  :global(.search-result-content) {
+    :global(.search-result-content-body-container) {
+      :global(.wiki) {
         margin-top: 2em;
         font-size: 14px;
       }

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