فهرست منبع

Merge branch 'dev/7.0.x' into feat/multiple-group-grant-for-page

Futa Arai 2 سال پیش
والد
کامیت
72a85149b6

+ 2 - 2
apps/app/src/components/ItemsTree/ItemsTree.tsx

@@ -25,7 +25,7 @@ import { usePageTreeDescCountMap, useSidebarScrollerRef } from '~/stores/ui';
 import { useGlobalSocket } from '~/stores/websocket';
 import loggerFactory from '~/utils/logger';
 
-import { ItemNode, SimpleItemProps } from '../TreeItem';
+import { ItemNode, type TreeItemProps } from '../TreeItem';
 
 import ItemsTreeContentSkeleton from './ItemsTreeContentSkeleton';
 
@@ -92,7 +92,7 @@ type ItemsTreeProps = {
   targetPath: string
   targetPathOrId?: Nullable<string>
   targetAndAncestorsData?: TargetAndAncestors
-  CustomTreeItem: React.FunctionComponent<SimpleItemProps>
+  CustomTreeItem: React.FunctionComponent<TreeItemProps>
 }
 
 /*

+ 1 - 1
apps/app/src/components/PageList/PageListItemL.tsx

@@ -226,7 +226,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
               <Clamp lines={1}>
                 <span className="h5 mb-0">
                   {/* Use permanent links to care for pages with the same name (Cannot use page path url) */}
-                  <span className="grw-page-path-hierarchical-link text-break">
+                  <span className="text-break">
                     <Link
                       legacyBehavior
                       href={returnPathForURL(pageData.path, pageData._id)}

+ 5 - 4
apps/app/src/components/PageSelectModal/TreeItemForModal.tsx

@@ -1,11 +1,12 @@
-import React, { FC } from 'react';
+import React, { type FC } from 'react';
 
 import {
-  SimpleItem, SimpleItemProps, SimpleItemTool, useNewPageInput,
+  SimpleItem, SimpleItemTool, useNewPageInput, type TreeItemProps,
 } from '../TreeItem';
 
-type Optional = 'itemRef' | 'itemClass' | 'mainClassName';
-type PageTreeItemProps = Omit<SimpleItemProps, Optional> & {key};
+type PageTreeItemProps = TreeItemProps & {
+  key?: React.Key | null,
+};
 
 export const TreeItemForModal: FC<PageTreeItemProps> = (props) => {
 

+ 12 - 3
apps/app/src/components/Sidebar/InAppNotification/InAppNotification.tsx

@@ -1,14 +1,19 @@
-import React, { Suspense } from 'react';
+import React, { Suspense, useState } from 'react';
 
 import dynamic from 'next/dynamic';
 import { useTranslation } from 'react-i18next';
 
 import ItemsTreeContentSkeleton from '../../ItemsTree/ItemsTreeContentSkeleton';
 
-const InAppNotificationSubstance = dynamic(() => import('./InAppNotificationSubstance').then(mod => mod.InAppNotificationSubstance), { ssr: false });
+import { InAppNotificationForms } from './InAppNotificationSubstance';
+
+const InAppNotificationContent = dynamic(() => import('./InAppNotificationSubstance').then(mod => mod.InAppNotificationContent), { ssr: false });
 
 export const InAppNotification = (): JSX.Element => {
   const { t } = useTranslation();
+
+  const [isUnreadNotificationsVisible, setUnreadNotificationsVisible] = useState(false);
+
   return (
     <div className="px-3">
       <div className="grw-sidebar-content-header py-3 d-flex">
@@ -17,8 +22,12 @@ export const InAppNotification = (): JSX.Element => {
         </h3>
       </div>
 
+      <InAppNotificationForms
+        onChangeUnreadNotificationsVisible={() => { setUnreadNotificationsVisible(!isUnreadNotificationsVisible) }}
+      />
+
       <Suspense fallback={<ItemsTreeContentSkeleton />}>
-        <InAppNotificationSubstance />
+        <InAppNotificationContent isUnreadNotificationsVisible={isUnreadNotificationsVisible} />
       </Suspense>
     </div>
   );

+ 36 - 2
apps/app/src/components/Sidebar/InAppNotification/InAppNotificationSubstance.tsx

@@ -3,13 +3,47 @@ import React from 'react';
 import { useTranslation } from 'next-i18next';
 
 import InAppNotificationList from '~/components/InAppNotification/InAppNotificationList';
+import { InAppNotificationStatuses } from '~/interfaces/in-app-notification';
 import { useSWRxInAppNotifications } from '~/stores/in-app-notification';
 
-export const InAppNotificationSubstance = (): JSX.Element => {
+
+type InAppNotificationFormsProps = {
+  onChangeUnreadNotificationsVisible: () => void
+}
+export const InAppNotificationForms = (props: InAppNotificationFormsProps): JSX.Element => {
+  const { onChangeUnreadNotificationsVisible } = props;
+
+  return (
+    <div className="my-2">
+      <div className="form-check form-switch">
+        <label className="form-check-label" htmlFor="flexSwitchCheckDefault">Only unread</label>
+        <input
+          id="flexSwitchCheckDefault"
+          className="form-check-input"
+          type="checkbox"
+          role="switch"
+          onChange={onChangeUnreadNotificationsVisible}
+        />
+      </div>
+    </div>
+  );
+};
+
+
+type InAppNotificationContentProps = {
+  isUnreadNotificationsVisible: boolean
+}
+export const InAppNotificationContent = (props: InAppNotificationContentProps): JSX.Element => {
+  const { isUnreadNotificationsVisible } = props;
   const { t } = useTranslation('commons');
 
   // TODO: Infinite scroll implemented (https://redmine.weseek.co.jp/issues/138057)
-  const { data: inAppNotificationData } = useSWRxInAppNotifications(6, undefined, undefined, { revalidateOnFocus: true });
+  const { data: inAppNotificationData } = useSWRxInAppNotifications(
+    6,
+    undefined,
+    isUnreadNotificationsVisible ? InAppNotificationStatuses.STATUS_UNREAD : undefined,
+    { revalidateOnFocus: true },
+  );
 
   return (
     <>

+ 6 - 7
apps/app/src/components/Sidebar/PageTreeItem/Ellipsis.tsx

@@ -15,27 +15,26 @@ import { apiv3Put } from '~/client/util/apiv3-client';
 import { ValidationTarget } from '~/client/util/input-validator';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { NotAvailableForGuest } from '~/components/NotAvailableForGuest';
-import { IPageForItem } from '~/interfaces/page';
 import { useSWRMUTxCurrentUserBookmarks } from '~/stores/bookmark';
 import { useSWRMUTxPageInfo } from '~/stores/page';
 
 import ClosableTextInput from '../../Common/ClosableTextInput';
 import { PageItemControl } from '../../Common/Dropdown/PageItemControl';
 import {
-  SimpleItemToolProps, NotDraggableForClosableTextInput, SimpleItemTool,
+  type TreeItemToolProps, NotDraggableForClosableTextInput, SimpleItemTool,
 } from '../../TreeItem';
 
-type EllipsisProps = SimpleItemToolProps & {page: IPageForItem};
-
-export const Ellipsis: FC<EllipsisProps> = (props) => {
+export const Ellipsis: FC<TreeItemToolProps> = (props) => {
   const [isRenameInputShown, setRenameInputShown] = useState(false);
   const { t } = useTranslation();
 
   const {
-    page, onRenamed, onClickDuplicateMenuItem,
+    itemNode, onRenamed, onClickDuplicateMenuItem,
     onClickDeleteMenuItem, isEnableActions, isReadOnlyUser,
   } = props;
 
+  const { page } = itemNode;
+
   const { trigger: mutateCurrentUserBookmarks } = useSWRMUTxCurrentUserBookmarks();
   const { trigger: mutatePageInfo } = useSWRMUTxPageInfo(page._id ?? null);
 
@@ -141,7 +140,7 @@ export const Ellipsis: FC<EllipsisProps> = (props) => {
           </NotDraggableForClosableTextInput>
         </div>
       ) : (
-        <SimpleItemTool page={page} isEnableActions={false} isReadOnlyUser={false} />
+        <SimpleItemTool itemNode={itemNode} isEnableActions={false} isReadOnlyUser={false} />
       )}
       <NotAvailableForGuest>
         <div className="grw-pagetree-control d-flex">

+ 4 - 6
apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx

@@ -1,5 +1,6 @@
 import React, {
-  useCallback, useState, FC,
+  useCallback, useState,
+  type FC,
 } from 'react';
 
 import nodePath from 'path';
@@ -15,17 +16,14 @@ import { mutatePageTree, useSWRxPageChildren } from '~/stores/page-listing';
 import loggerFactory from '~/utils/logger';
 
 import {
-  SimpleItem, type SimpleItemProps, useNewPageInput, ItemNode,
+  SimpleItem, useNewPageInput, ItemNode, type TreeItemProps,
 } from '../../TreeItem';
 
 import { Ellipsis } from './Ellipsis';
 
 const logger = loggerFactory('growi:cli:Item');
 
-type PageTreeItemPropsOptional = 'itemRef' | 'itemClass' | 'mainClassName';
-type PageTreeItemProps = Omit<SimpleItemProps, PageTreeItemPropsOptional>;
-
-export const PageTreeItem: FC<PageTreeItemProps> = (props) => {
+export const PageTreeItem: FC<TreeItemProps> = (props) => {
   const getNewPathAfterMoved = (droppedPagePath: string, newParentPagePath: string): string => {
     const pageTitle = nodePath.basename(droppedPagePath);
     return nodePath.join(newParentPagePath, pageTitle);

+ 7 - 3
apps/app/src/components/Sidebar/Sidebar.tsx

@@ -143,15 +143,19 @@ const CollapsibleContainer = memo((props: CollapsibleContainerProps): JSX.Elemen
 
 });
 
+// for data-* attributes
+type HTMLElementProps = JSX.IntrinsicElements &
+  Record<keyof JSX.IntrinsicElements, { [p: `data-${string}`]: string | number }>;
 
 type DrawableContainerProps = {
+  divProps?: HTMLElementProps['div'],
   className?: string,
   children?: React.ReactNode,
 }
 
 const DrawableContainer = memo((props: DrawableContainerProps): JSX.Element => {
 
-  const { className, children } = props;
+  const { divProps, className, children } = props;
 
   const { data: isDrawerOpened, mutate } = useDrawerOpened();
 
@@ -159,7 +163,7 @@ const DrawableContainer = memo((props: DrawableContainerProps): JSX.Element => {
 
   return (
     <>
-      <div className={`${className} ${openClass}`}>
+      <div {...divProps} className={`${className} ${openClass}`}>
         {children}
       </div>
       { isDrawerOpened && (
@@ -201,7 +205,7 @@ export const Sidebar = (): JSX.Element => {
         </DrawerToggler>
       ) }
       { sidebarMode != null && !isDockMode() && <AppTitleOnSubnavigation /> }
-      <DrawableContainer className={`${grwSidebarClass} ${modeClass} border-end flex-expand-vh-100`} data-testid="grw-sidebar">
+      <DrawableContainer className={`${grwSidebarClass} ${modeClass} border-end flex-expand-vh-100`} divProps={{ 'data-testid': 'grw-sidebar' }}>
         <ResizableContainer>
           { sidebarMode != null && !isCollapsedMode() && <AppTitleOnSidebarHead /> }
           <SidebarHead />

+ 1 - 0
apps/app/src/components/Sidebar/SidebarHead/ToggleCollapseButton.tsx

@@ -37,6 +37,7 @@ export const ToggleCollapseButton = memo((): JSX.Element => {
       type="button"
       className={`btn btn-primary ${styles['btn-toggle-collapse']} p-2`}
       onClick={isDrawerMode() ? toggleDrawer : toggleCollapsed}
+      data-testid="btn-toggle-collapse"
     >
       <span className={`material-symbols-outlined fs-2 ${rotationClass}`}>{icon}</span>
     </button>

+ 16 - 19
apps/app/src/components/TreeItem/NewPageInput/use-new-page-input.tsx

@@ -4,14 +4,14 @@ import { apiv3Post } from '~/client/util/apiv3-client';
 import { useSWRxPageChildren } from '~/stores/page-listing';
 import { usePageTreeDescCountMap } from '~/stores/ui';
 
-import type { SimpleItemContentProps } from '../interfaces';
+import type { TreeItemToolProps } from '../interfaces';
 
 import { NewPageCreateButton } from './NewPageCreateButton';
 import { NewPageInput } from './NewPageInput';
 
 type UseNewPageInput = {
-  Input: FC<SimpleItemContentProps>,
-  CreateButton: FC<SimpleItemContentProps>,
+  Input: FC<TreeItemToolProps>,
+  CreateButton: FC<TreeItemToolProps>,
   isProcessingSubmission: boolean,
 }
 
@@ -22,10 +22,10 @@ export const useNewPageInput = (): UseNewPageInput => {
 
   const { getDescCount } = usePageTreeDescCountMap();
 
-  const CreateButton: FC<SimpleItemContentProps> = (props) => {
+  const CreateButton: FC<TreeItemToolProps> = (props) => {
 
-    const { page, children, stateHandlers } = props;
-    const { setIsOpen } = stateHandlers;
+    const { itemNode, stateHandlers } = props;
+    const { page, children } = itemNode;
 
     // descendantCount
     const descendantCount = getDescCount(page._id) || page.descendantCount || 0;
@@ -37,27 +37,24 @@ export const useNewPageInput = (): UseNewPageInput => {
       setShowInput(true);
 
       if (hasDescendants) {
-        setIsOpen(true);
+        stateHandlers?.setIsOpen(true);
       }
-    }, [hasDescendants, setIsOpen]);
+    }, [hasDescendants, stateHandlers]);
 
     return (
       <NewPageCreateButton
-        page={props.page}
+        page={page}
         onClick={onClick}
       />
     );
   };
 
-  const Input: FC<SimpleItemContentProps> = (props) => {
+  const Input: FC<TreeItemToolProps> = (props) => {
 
-    const {
-      page, children, stateHandlers,
-    } = props;
+    const { itemNode, stateHandlers } = props;
+    const { page, children } = itemNode;
 
-    const { isOpen, setIsOpen } = stateHandlers;
-
-    const { mutate: mutateChildren } = useSWRxPageChildren(isOpen ? page._id : null);
+    const { mutate: mutateChildren } = useSWRxPageChildren(stateHandlers?.isOpen ? page._id : null);
 
     const { getDescCount } = usePageTreeDescCountMap();
     const descendantCount = getDescCount(page._id) || page.descendantCount || 0;
@@ -81,9 +78,9 @@ export const useNewPageInput = (): UseNewPageInput => {
       mutateChildren();
 
       if (!hasDescendants) {
-        setIsOpen(true);
+        stateHandlers?.setIsOpen(true);
       }
-    }, [hasDescendants, mutateChildren, page.grant, page.grantedGroups, setIsOpen]);
+    }, [hasDescendants, mutateChildren, page.grant, page.grantedGroups, stateHandlers]);
 
     const submittionFailedHandler = useCallback(() => {
       setProcessingSubmission(false);
@@ -92,7 +89,7 @@ export const useNewPageInput = (): UseNewPageInput => {
     return showInput
       ? (
         <NewPageInput
-          page={props.page}
+          page={page}
           isEnableActions={props.isEnableActions}
           onSubmit={submitHandler}
           onSubmittionFailed={submittionFailedHandler}

+ 35 - 38
apps/app/src/components/TreeItem/SimpleItem.tsx

@@ -1,5 +1,6 @@
 import React, {
-  useCallback, useState, FC, useEffect,
+  useCallback, useState, useEffect,
+  type FC, type RefObject, type RefCallback,
 } from 'react';
 
 import nodePath from 'path';
@@ -18,7 +19,7 @@ import CountBadge from '../Common/CountBadge';
 
 import { ItemNode } from './ItemNode';
 import { useNewPageInput } from './NewPageInput';
-import type { SimpleItemContentProps, SimpleItemProps, SimpleItemToolProps } from './interfaces';
+import type { TreeItemProps, TreeItemToolProps } from './interfaces';
 
 
 // Utility to mark target
@@ -38,28 +39,14 @@ const markTarget = (children: ItemNode[], targetPathOrId?: Nullable<string>): vo
   });
 };
 
-/**
- * Return new page path after the droppedPagePath is moved under the newParentPagePath
- * @param droppedPagePath
- * @param newParentPagePath
- * @returns
- */
-
-/**
- * Return whether the fromPage could be moved under the newParentPage
- * @param fromPage
- * @param newParentPage
- * @param printLog
- * @returns
- */
-
-export const SimpleItemTool: FC<SimpleItemToolProps> = (props) => {
+
+export const SimpleItemTool: FC<TreeItemToolProps> = (props) => {
   const { t } = useTranslation();
   const router = useRouter();
 
   const { getDescCount } = usePageTreeDescCountMap();
 
-  const page = props.page;
+  const { page } = props.itemNode;
 
   const pageName = nodePath.basename(page.path ?? '') || '/';
 
@@ -103,6 +90,10 @@ export const SimpleItemTool: FC<SimpleItemToolProps> = (props) => {
   );
 };
 
+type SimpleItemProps = TreeItemProps & {
+  itemRef?: RefObject<any> | RefCallback<any>,
+}
+
 export const SimpleItem: FC<SimpleItemProps> = (props) => {
   const {
     itemNode, targetPathOrId, isOpen: _isOpen = false,
@@ -168,8 +159,7 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
 
   const SimpleItemContent = CustomEndComponents ?? [SimpleItemTool];
 
-  const simpleItemProps: SimpleItemProps = {
-    itemNode,
+  const baseProps: Omit<TreeItemProps, 'itemNode'> = {
     isEnableActions,
     isReadOnlyUser,
     isOpen: false,
@@ -179,11 +169,9 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
     onClickDeleteMenuItem,
   };
 
-  const simpleItemContentProps: SimpleItemContentProps = {
-    ...simpleItemProps,
-    page,
-    children,
-    stateHandlers: { isOpen, setIsOpen },
+  const toolProps: TreeItemToolProps = {
+    ...baseProps,
+    itemNode,
   };
 
   const CustomNextComponents = props.customNextComponents;
@@ -216,26 +204,35 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
         </div>
         {SimpleItemContent.map((ItemContent, index) => (
           // eslint-disable-next-line react/no-array-index-key
-          <ItemContent key={index} {...simpleItemContentProps} />
+          <ItemContent key={index} {...toolProps} />
         ))}
       </li>
 
       {CustomNextComponents?.map((UnderItemContent, index) => (
         // eslint-disable-next-line react/no-array-index-key
-        <UnderItemContent key={index} {...simpleItemContentProps} />
+        <UnderItemContent key={index} {...toolProps} />
       ))}
 
       {
-        isOpen && hasChildren() && currentChildren.map((node, index) => (
-          <div key={node.page._id} className="grw-pagetree-item-children">
-            <ItemClassFixed {...simpleItemProps} />
-            {isProcessingSubmission && (currentChildren.length - 1 === index) && (
-              <div className="text-muted text-center">
-                <i className="fa fa-spinner fa-pulse mr-1"></i>
-              </div>
-            )}
-          </div>
-        ))
+        isOpen && hasChildren() && currentChildren.map((node, index) => {
+          const itemProps = {
+            ...baseProps,
+            itemNode: node,
+            itemClass,
+            mainClassName,
+          };
+
+          return (
+            <div key={node.page._id} className="grw-pagetree-item-children">
+              <ItemClassFixed {...itemProps} />
+              {isProcessingSubmission && (currentChildren.length - 1 === index) && (
+                <div className="text-muted text-center">
+                  <i className="fa fa-spinner fa-pulse mr-1"></i>
+                </div>
+              )}
+            </div>
+          );
+        })
       }
     </div>
   );

+ 19 - 27
apps/app/src/components/TreeItem/interfaces/index.ts

@@ -1,38 +1,30 @@
 import type { IPageToDeleteWithMeta } from '@growi/core';
 import type { Nullable } from 'vitest';
 
-import type { IPageForItem } from '~/interfaces/page';
 import type { IPageForPageDuplicateModal } from '~/stores/modal';
 
 import type { ItemNode } from '../ItemNode';
 
-type SimpleItemToolPropsOptional = 'itemNode' | 'targetPathOrId' | 'isOpen' | 'itemRef' | 'itemClass' | 'mainClassName';
-
-export type SimpleItemToolProps = Omit<SimpleItemProps, SimpleItemToolPropsOptional> & {
-  page: IPageForItem,
-};
-
-export type SimpleItemProps = {
-  isEnableActions: boolean
-  isReadOnlyUser: boolean
-  itemNode: ItemNode
-  targetPathOrId?: Nullable<string>
-  isOpen?: boolean
-  onRenamed?(fromPath: string | undefined, toPath: string): void
-  onClickDuplicateMenuItem?(pageToDuplicate: IPageForPageDuplicateModal): void
-  onClickDeleteMenuItem?(pageToDelete: IPageToDeleteWithMeta): void
-  itemRef?
-  itemClass?: React.FunctionComponent<SimpleItemProps>
-  mainClassName?: string
-  customEndComponents?: Array<React.FunctionComponent<SimpleItemToolProps>>
-  customNextComponents?: Array<React.FunctionComponent<SimpleItemToolProps>>
-};
-
-export type SimpleItemContentProps = SimpleItemToolProps & {
-  page: IPageForItem,
-  children: ItemNode[],
-  stateHandlers: {
+type TreeItemBaseProps = {
+  itemNode: ItemNode,
+  isEnableActions: boolean,
+  isReadOnlyUser: boolean,
+  onRenamed?(fromPath: string | undefined, toPath: string): void,
+  onClickDuplicateMenuItem?(pageToDuplicate: IPageForPageDuplicateModal): void,
+  onClickDeleteMenuItem?(pageToDelete: IPageToDeleteWithMeta): void,
+  stateHandlers?: {
     isOpen: boolean,
     setIsOpen: React.Dispatch<React.SetStateAction<boolean>>,
   },
+}
+
+export type TreeItemToolProps = TreeItemBaseProps;
+
+export type TreeItemProps = TreeItemBaseProps & {
+  targetPathOrId?: Nullable<string>,
+  isOpen?: boolean,
+  itemClass?: React.FunctionComponent<TreeItemProps>,
+  mainClassName?: string,
+  customEndComponents?: Array<React.FunctionComponent<TreeItemToolProps>>,
+  customNextComponents?: Array<React.FunctionComponent<TreeItemToolProps>>,
 };

+ 18 - 19
apps/app/test/cypress/e2e/50-sidebar/50-sidebar--access-to-side-bar.cy.ts

@@ -36,9 +36,8 @@ describe('Access to sidebar', () => {
           });
         });
 
-        // TODO: rewrite test case with grw-switch-collapse-button
         it('Successfully collapse sidebar', () => {
-          cy.getByTestid('grw-switch-collapse-button').click({force: true});
+          cy.getByTestid('btn-toggle-collapse').click({force: true});
 
           cy.getByTestid('grw-sidebar-contents').should('not.be.visible');
 
@@ -205,27 +204,28 @@ describe('Access to sidebar', () => {
           });
         });
 
-        it('Successfully redirect to editor', () => {
-          const content = '# HELLO \n ## Hello\n ### Hello';
+        // TODO: fix by https://redmine.weseek.co.jp/issues/138562
+        // it('Successfully redirect to editor', () => {
+        //   const content = '# HELLO \n ## Hello\n ### Hello';
 
-          cy.get('.grw-sidebar-content-header > h3 > a').should('be.visible').click();
+        //   cy.get('.grw-sidebar-content-header > h3 > a').should('be.visible').click();
 
-          cy.get('.layout-root').should('have.class', 'editing');
-          cy.get('.CodeMirror textarea').type(content, {force: true});
+        //   cy.get('.layout-root').should('have.class', 'editing');
+        //   cy.get('.CodeMirror textarea').type(content, {force: true});
 
-          cy.screenshot(`${ssPrefix}custom-sidebar-2-redirect-to-editor`, { blackout: blackoutOverride });
+        //   cy.screenshot(`${ssPrefix}custom-sidebar-2-redirect-to-editor`, { blackout: blackoutOverride });
 
-          cy.getByTestid('save-page-btn').click();
-        });
+        //   cy.getByTestid('save-page-btn').click();
+        // });
 
-        it('Successfully create custom sidebar content', () => {
-          cy.getByTestid('grw-sidebar-nav-primary-custom-sidebar')
-            .should('be.visible')
-            .should('have.class', 'active');
+        // it('Successfully create custom sidebar content', () => {
+        //   cy.getByTestid('grw-sidebar-nav-primary-custom-sidebar')
+        //     .should('be.visible')
+        //     .should('have.class', 'active');
 
-          cy.waitUntilSkeletonDisappear();
-          cy.screenshot(`${ssPrefix}custom-sidebar-3-content-created`, { blackout: blackoutOverride });
-        });
+        //   cy.waitUntilSkeletonDisappear();
+        //   cy.screenshot(`${ssPrefix}custom-sidebar-3-content-created`, { blackout: blackoutOverride });
+        // });
       });
 
       describe('Test recent changes tab', () => {
@@ -307,8 +307,7 @@ describe('Access to sidebar', () => {
             cy.get('a[href*="/trash"]').click();
           });
 
-          cy.get('.grw-page-path-hierarchical-link').should('be.visible');
-          cy.get('.grw-custom-nav-tab').should('be.visible');
+          cy.getByTestid('trash-page-list').should('be.visible');
 
           cy.screenshot(`${ssPrefix}access-to-trash-page`, { blackout: blackoutOverride });
         });

+ 39 - 10
apps/app/test/cypress/e2e/50-sidebar/50-sidebar--switching-sidebar-mode.cy.ts

@@ -9,28 +9,57 @@ const blackoutOverride = [
 context('Switch sidebar mode', () => {
   const ssPrefix = 'switch-sidebar-mode-';
 
-  before(() => {
+  beforeEach(() => {
     // login
     cy.fixture("user-admin.json").then(user => {
       cy.login(user.username, user.password);
     });
+    cy.visit('/');
   });
 
   it('Switching sidebar mode', () => {
-    cy.visit('/');
-    cy.get('.grw-apperance-mode-dropdown').first().click();
-
-    cy.get('[for="swSidebarMode"]').click({force: true});
-    cy.get('.grw-sidebar-nav').should('not.be.visible');
-    cy.screenshot(`${ssPrefix}-switch-sidebar-mode`, {
+    cy.collapseSidebar(false);
+    cy.screenshot(`${ssPrefix}-doc-mode-opened`, {
       blackout: blackoutOverride,
     });
 
-    cy.get('[for="swSidebarMode"]').click({force: true});
-    cy.get('.grw-sidebar-nav').should('be.visible');
-    cy.screenshot(`${ssPrefix}-switch-sidebar-mode-back`, {
+    cy.collapseSidebar(true);
+    cy.screenshot(`${ssPrefix}-doc-mode-closed`, {
       blackout: blackoutOverride,
     });
   });
 
 });
+
+context('Switch viewport size', () => {
+  const ssPrefix = 'switch-viewport-size-';
+
+  const sizes = {
+    'xl': [1200, 1024],
+    'lg': [992, 1024],
+    'md': [768, 1024],
+    'sm': [576, 1024],
+    'xs': [575, 1024],
+    'iphone-x': [375, 812],
+  };
+
+  Object.entries(sizes).forEach(([screenLabel, size]) => {
+    it(`on ${screenLabel} screen`, () => {
+      cy.viewport(size[0], size[1]);
+
+      // login
+      cy.fixture("user-admin.json").then(user => {
+        cy.login(user.username, user.password);
+      });
+      cy.visit('/');
+
+      cy.get('.layout-root').should('be.visible');
+
+      cy.screenshot(`${ssPrefix}-${screenLabel}`, {
+        blackout: blackoutOverride,
+      });
+    });
+  });
+
+});
+

+ 6 - 6
apps/app/test/cypress/e2e/60-home/60-home--home.cy.ts

@@ -64,7 +64,7 @@ context('Access User settings', () => {
 
   it('Access External account', () => {
     cy.getByTestid('grw-personal-settings').find('.nav-title.nav li:eq(1) a').click();
-    cy.scrollTo('top');
+    cy.scrollTo('top', {ensureScrollable: false});
     cy.screenshot(`${ssPrefix}-external-account-1`);
     cy.getByTestid('grw-external-account-add-button').click();
     cy.getByTestid('grw-associate-modal').should('be.visible');
@@ -76,7 +76,7 @@ context('Access User settings', () => {
       cy.get('.Toastify__close-button').should('be.visible').click();
       cy.get('.Toastify__progress-bar').invoke('attr', 'style', 'display: none')
     });
-    cy.getByTestid('grw-associate-modal').find('.close').click();
+    cy.getByTestid('grw-associate-modal').find('[aria-label="Close"]').click();
     cy.screenshot(`${ssPrefix}-external-account-4`);
 
       cy.get('.Toastify__toast').should('not.be.visible');
@@ -84,7 +84,7 @@ context('Access User settings', () => {
 
   it('Access Password setting', () => {
     cy.getByTestid('grw-personal-settings').find('.nav-title.nav li:eq(2) a').click();
-    cy.scrollTo('top');
+    cy.scrollTo('top', {ensureScrollable: false});
     cy.screenshot(`${ssPrefix}-password-settings-1`);
     cy.getByTestid('grw-password-settings-update-button').click();
     cy.get('.Toastify__toast').should('be.visible');
@@ -100,7 +100,7 @@ context('Access User settings', () => {
 
   it('Access API setting', () => {
     cy.getByTestid('grw-personal-settings').find('.nav-title.nav li:eq(3) a').click();
-    cy.scrollTo('top');
+    cy.scrollTo('top', {ensureScrollable: false});
     cy.screenshot(`${ssPrefix}-api-setting-1`);
     cy.getByTestid('grw-api-settings-update-button').click();
     cy.getByTestid('grw-api-settings-input').should('be.visible');
@@ -115,7 +115,7 @@ context('Access User settings', () => {
 
   it('Access In-app notification setting', () => {
     cy.getByTestid('grw-personal-settings').find('.nav-title.nav li:eq(4) a').click();
-    cy.scrollTo('top');
+    cy.scrollTo('top', {ensureScrollable: false});
     cy.screenshot(`${ssPrefix}-in-app-notification-setting-1`);
     cy.getByTestid('grw-in-app-notification-settings-update-button').click();
     cy.get('.Toastify__toast').should('be.visible');
@@ -124,7 +124,7 @@ context('Access User settings', () => {
 
   it('Access Other setting', () => {
     cy.getByTestid('grw-personal-settings').find('.nav-title.nav li:eq(5) a').click();
-    cy.scrollTo('top');
+    cy.scrollTo('top', {ensureScrollable: false});
     cy.screenshot(`${ssPrefix}-other-setting-1`);
     cy.getByTestid('grw-questionnaire-settings-update-btn').click();
     cy.get('.Toastify__toast').should('be.visible').invoke('attr', 'style', 'display: none');

+ 1 - 1
apps/app/test/cypress/support/commands.ts

@@ -88,7 +88,7 @@ Cypress.Commands.add('collapseSidebar', (isCollapsed: boolean, waitUntilSaving =
 
     cy.waitUntil(() => {
       // do
-      cy.getByTestid("grw-switch-collapse-button").click({force: true});
+      cy.getByTestid("btn-toggle-collapse").click({force: true});
       // wait until saving UserUISettings
       if (waitUntilSaving) {
         // eslint-disable-next-line cypress/no-unnecessary-waiting

+ 168 - 133
packages/preset-themes/src/styles/kibela.scss

@@ -1,134 +1,169 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-@use './variables' as var;
-@use './theme/mixins/page-editor-mode-manager';
-@use './theme/hsl-functions' as hsl;
-
-:root[data-bs-theme='light']{
-  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
-  --primary-hs: 212,80%;
-  --primary-l: 35%;
-  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
-  --secondary-hs: 208,7%;
-  --secondary-l: 46%;
-  --subthemecolor: hsl(var(--subthemecolor-hs),var(--subthemecolor-l));
-  --subthemecolor-hs: 224,94%;
-  --subthemecolor-l: 66%;
-  --lightthemecolor: hsl(var(--lightthemecolor-hs),var(--lightthemecolor-l));
-  --lightthemecolor-hs: 220,80%;
-  --lightthemecolor-l: 84%;
-
-  // Background colors
-  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
-  --bgcolor-global-hs: 210,10%;
-  --bgcolor-global-l: 96%;
-  --bgcolor-inline-code: #{hsl.lighten(var(--subthemecolor), 70%)};
-  --bgcolor-card: var(--lightthemecolor);
-  --bgcolor-blinked-section: #{hsl.alpha(var(--primary),20%)};
-  //--bgcolor-keyword-highlighted: #{$grw-marker-yellow};
-
-  // Font colors
-  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
-  --color-global-hs: 217,23%;
-  --color-global-l: 31%;
-  --color-reversal: #{bs.$gray-100};
-  --color-header: var(--primary);
-  --color-link: hsl(var(--color-link-hs),var(--color-link-l));
-  --color-link-hs: 224,56%;
-  --color-link-l: 55%;
-  --color-link-hover: #{hsl.lighten(var(--color-link),12%)};
-  --color-link-wiki: #{hsl.lighten(var(--primary), 20%)};
-  --color-link-wiki-hover: #{hsl.lighten(var(--primary), 40%)};
-  --color-link-nabvar: var(--color-global);
-  --color-inline-code: var(--subthemecolor);
-
-  // List Group colors
-  --color-list: var(--color-global); // optional
-  --bgcolor-list: var(--bgcolor-global); // optional
-  --color-list-hover: var(--color-reversal);
-  --color-list-active: var(--color-reversal);
-  --bgcolor-list-active: var(--primary);
-
-  // Navbar
-  --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
-  --bgcolor-navbar-hs: 0,0%;
-  --bgcolor-navbar-l: 100%;
-  --bgcolor-search-top-dropdown: var(--primary);
-  --bgcolor-search-top-dropdown-hs: var(--primary-hs);
-  --bgcolor-search-top-dropdown-l: var(--primary-l);
-
-  // Logo colors
-  --bgcolor-logo: transparent;
-  --fillcolor-logo-mark: #{hsl.lighten(var(--primary), 20%)};
-
-  // Sidebar
-  --bgcolor-sidebar: var(--primary);
-  --bgcolor-sidebar-hs: var(--primary-hs);
-  --bgcolor-sidebar-l: var(--primary-l);
-  --bgcolor-sidebar-context: #{hsl.lighten(var(--primary), 10%)};
-
-  // Sidebar resize button
-  --color-resize-button: var(--color-reversal);
-  --bgcolor-resize-button: hsl(var(--bgcolor-resize-button-hs),var(--bgcolor-resize-button-l));
-  --bgcolor-resize-button-hs: 199,74%;
-  --bgcolor-resize-button-l: 49%;
-  --color-resize-button-hover: var(--color-reversal);
-  --bgcolor-resize-button-hover: #{hsl.lighten(var(--bgcolor-resize-button), 5%)};
-
-  // Sidebar contents
-  --color-sidebar-context: var(--color-global);
-  --color-sidebar-context-hs: var(--color-global-hs);
-  --color-sidebar-context-l: var(--color-global-l);
-  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
-  --bgcolor-sidebar-context-hs: 225,57%;
-  --bgcolor-sidebar-context-l: 97%;
-
-  // Sidebar list group
-  --bgcolor-sidebar-list-group: #fafbff; // optional
-
-  // Subnavigation
-  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
-  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
-  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
-
-  // Icon colors
-  --color-editor-icons: var(--color-global);
-
-  // border colors
-  --border-color-theme: var(--lightthemecolor);
-  --thickborder: #5584e1;
-  --bordercolor-inline-code: var(--lightthemecolor);
-
-  // dropdown colors
-  --bgcolor-dropdown-link-active: #{var.$growi-blue};
-
-  // admin theme box
-  --color-theme-color-box: #{hsl.lighten(var(--primary), 20%)};
-
-  .main {
-    .container,
-    .container-sm,
-    .container-md,
-    .container-lg,
-    .container-fluid {
-      padding-top: 30px;
-      padding-bottom: 30px;
-      background-color: white;
-      border-radius: 0.35em;
-    }
-  }
-
-  .user-page-footer {
-    margin-top: 3rem;
-    margin-bottom: 3rem;
-    background-color: white;
-    border-radius: 0.35em;
-  }
-
-  //Button
-  .btn-group.grw-page-editor-mode-manager {
-    .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager (#{hsl.darken(var(--primary), 15%)}, #{hsl.lighten(var(--primary), 45%)}, #{hsl.lighten(var(--primary), 50%)});
-    }
-  }
+:root[data-bs-theme] {
+  @import '@growi/core/scss/bootstrap/init-stage-1';
+  @import '@growi/core/scss/bootstrap/theming/variables';
+  @import '@growi/core/scss/bootstrap/theming/utils/color-palette';
+
+  $primary: #3780C0;
+  $highlight: #909090;
+
+  @include generate-color-palette('primary', $primary, black, #F5F5F5, 20%, 25%);
+  @include generate-color-palette('highlight', $highlight, black, white, 20%, 25%);
+
+  $body-color:                $gray-700;
+  $body-bg:                   white;
+
+  $body-secondary-color:      rgba($body-color, .75);
+  $body-secondary-bg:         $gray-200;
+
+  $body-tertiary-color:       rgba($body-color, .5);
+  $body-tertiary-bg:          $gray-100;
+
+  $border-color:              $gray-300;
+
+  $link-color:                $gray-800;
+
+  @import 'bootstrap/scss/variables';
+  @import 'bootstrap/scss/variables-dark';
+
+  @import '@growi/core/scss/bootstrap/init-stage-2';
+
+  @import '@growi/core/scss/bootstrap/theming/apply-light';
+
+  --grw-wiki-link-color-rgb: var(--grw-primary-400-rgb);
+  --grw-wiki-link-hover-color-rgb: var(--grw-primary-500-rgb);
 }
+
+// @use '@growi/core/scss/bootstrap/init' as bs;
+
+// @use './variables' as var;
+// @use './theme/mixins/page-editor-mode-manager';
+// @use './theme/hsl-functions' as hsl;
+
+// :root[data-bs-theme='light']{
+//   --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+//   --primary-hs: 212,80%;
+//   --primary-l: 35%;
+//   --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+//   --secondary-hs: 208,7%;
+//   --secondary-l: 46%;
+//   --subthemecolor: hsl(var(--subthemecolor-hs),var(--subthemecolor-l));
+//   --subthemecolor-hs: 224,94%;
+//   --subthemecolor-l: 66%;
+//   --lightthemecolor: hsl(var(--lightthemecolor-hs),var(--lightthemecolor-l));
+//   --lightthemecolor-hs: 220,80%;
+//   --lightthemecolor-l: 84%;
+
+//   // Background colors
+//   --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+//   --bgcolor-global-hs: 210,10%;
+//   --bgcolor-global-l: 96%;
+//   --bgcolor-inline-code: #{hsl.lighten(var(--subthemecolor), 70%)};
+//   --bgcolor-card: var(--lightthemecolor);
+//   --bgcolor-blinked-section: #{hsl.alpha(var(--primary),20%)};
+//   //--bgcolor-keyword-highlighted: #{$grw-marker-yellow};
+
+//   // Font colors
+//   --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+//   --color-global-hs: 217,23%;
+//   --color-global-l: 31%;
+//   --color-reversal: #{bs.$gray-100};
+//   --color-header: var(--primary);
+//   --color-link: hsl(var(--color-link-hs),var(--color-link-l));
+//   --color-link-hs: 224,56%;
+//   --color-link-l: 55%;
+//   --color-link-hover: #{hsl.lighten(var(--color-link),12%)};
+//   --color-link-wiki: #{hsl.lighten(var(--primary), 20%)};
+//   --color-link-wiki-hover: #{hsl.lighten(var(--primary), 40%)};
+//   --color-link-nabvar: var(--color-global);
+//   --color-inline-code: var(--subthemecolor);
+
+//   // List Group colors
+//   --color-list: var(--color-global); // optional
+//   --bgcolor-list: var(--bgcolor-global); // optional
+//   --color-list-hover: var(--color-reversal);
+//   --color-list-active: var(--color-reversal);
+//   --bgcolor-list-active: var(--primary);
+
+//   // Navbar
+//   --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
+//   --bgcolor-navbar-hs: 0,0%;
+//   --bgcolor-navbar-l: 100%;
+//   --bgcolor-search-top-dropdown: var(--primary);
+//   --bgcolor-search-top-dropdown-hs: var(--primary-hs);
+//   --bgcolor-search-top-dropdown-l: var(--primary-l);
+
+//   // Logo colors
+//   --bgcolor-logo: transparent;
+//   --fillcolor-logo-mark: #{hsl.lighten(var(--primary), 20%)};
+
+//   // Sidebar
+//   --bgcolor-sidebar: var(--primary);
+//   --bgcolor-sidebar-hs: var(--primary-hs);
+//   --bgcolor-sidebar-l: var(--primary-l);
+//   --bgcolor-sidebar-context: #{hsl.lighten(var(--primary), 10%)};
+
+//   // Sidebar resize button
+//   --color-resize-button: var(--color-reversal);
+//   --bgcolor-resize-button: hsl(var(--bgcolor-resize-button-hs),var(--bgcolor-resize-button-l));
+//   --bgcolor-resize-button-hs: 199,74%;
+//   --bgcolor-resize-button-l: 49%;
+//   --color-resize-button-hover: var(--color-reversal);
+//   --bgcolor-resize-button-hover: #{hsl.lighten(var(--bgcolor-resize-button), 5%)};
+
+//   // Sidebar contents
+//   --color-sidebar-context: var(--color-global);
+//   --color-sidebar-context-hs: var(--color-global-hs);
+//   --color-sidebar-context-l: var(--color-global-l);
+//   --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+//   --bgcolor-sidebar-context-hs: 225,57%;
+//   --bgcolor-sidebar-context-l: 97%;
+
+//   // Sidebar list group
+//   --bgcolor-sidebar-list-group: #fafbff; // optional
+
+//   // Subnavigation
+//   --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+//   --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+//   --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
+
+//   // Icon colors
+//   --color-editor-icons: var(--color-global);
+
+//   // border colors
+//   --border-color-theme: var(--lightthemecolor);
+//   --thickborder: #5584e1;
+//   --bordercolor-inline-code: var(--lightthemecolor);
+
+//   // dropdown colors
+//   --bgcolor-dropdown-link-active: #{var.$growi-blue};
+
+//   // admin theme box
+//   --color-theme-color-box: #{hsl.lighten(var(--primary), 20%)};
+
+//   .main {
+//     .container,
+//     .container-sm,
+//     .container-md,
+//     .container-lg,
+//     .container-fluid {
+//       padding-top: 30px;
+//       padding-bottom: 30px;
+//       background-color: white;
+//       border-radius: 0.35em;
+//     }
+//   }
+
+//   .user-page-footer {
+//     margin-top: 3rem;
+//     margin-bottom: 3rem;
+//     background-color: white;
+//     border-radius: 0.35em;
+//   }
+
+//   //Button
+//   .btn-group.grw-page-editor-mode-manager {
+//     .btn.btn-outline-primary {
+//       @include page-editor-mode-manager.btn-page-editor-mode-manager (#{hsl.darken(var(--primary), 15%)}, #{hsl.lighten(var(--primary), 45%)}, #{hsl.lighten(var(--primary), 50%)});
+//     }
+//   }
+// }

+ 158 - 123
packages/preset-themes/src/styles/nature.scss

@@ -1,125 +1,160 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-@use './variables' as var;
-@use './theme/mixins/page-editor-mode-manager';
-@use './theme/hsl-functions' as hsl;
-
-.growi:not(.login-page) {
-  // add background-image
-  .page-editor-preview-container {
-    background-attachment: fixed;
-    background-position: center center;
-    background-size: cover;
-  }
-}
+:root[data-bs-theme] {
+  @import '@growi/core/scss/bootstrap/init-stage-1';
+  @import '@growi/core/scss/bootstrap/theming/variables';
+  @import '@growi/core/scss/bootstrap/theming/utils/color-palette';
+
+  $primary: #4FA529;
+  $highlight: #9DE201;
+
+  @include generate-color-palette('primary', $primary, black, white);
+  @include generate-color-palette('highlight', $highlight, black, white);
+
+  $body-color:                $gray-800;
+  $body-bg:                   mix(#9DE201, white, 5%);
+
+  $body-secondary-color:      rgba($body-color, .75);
+  $body-secondary-bg:         $gray-200;
+
+  $body-tertiary-color:       rgba($body-color, .5);
+  $body-tertiary-bg:          $gray-100;
 
-//== Light Mode
-//
-:root[data-bs-theme='light'] {
-  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
-  --primary-hs: 311,100%;
-  --primary-l: 14%;
-  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
-  --secondary-hs: 208,7%;
-  --secondary-l: 46%;
-
-  // Background colors
-  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
-  --bgcolor-global-hs: 0,0%;
-  --bgcolor-global-l: 99%;
-  --bgcolor-inline-code: #{bs.$gray-100}; //optional
-  --bgcolor-card: #f1ffe4;
-  --bgcolor-blinked-section: #{hsl.alpha(var(--primary), 10%)};
-  //--bgcolor-keyword-highlighted: #{$grw-marker-yellow};
-
-  // Font colors
-  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
-  --color-global-hs: 311,100%;
-  --color-global-l: 14%;
-  --color-reversal: #eeeeee;
-  --color-link: hsl(var(--color-link-hs),var(--color-link-l));
-  --color-link-hs: 328,100%;
-  --color-link-l: 25%;
-  --color-link-hover: #{hsl.lighten(var(--color-link), 20%)};
-  --color-link-wiki: #{hsl.lighten(var(--primary), 20%)};
-  --color-link-wiki-hs: var(--primary-hs);
-  --color-link-wiki-l: calc(var(--primary-l) + 20%);
-  --color-link-wiki-hover: #{hsl.lighten(var(--primary), 40%)};
-  --color-link-nabvar: #a7a7a7;
-  --color-inline-code: #c7254e; // optional
-  --color-search: white;
-
-  // Navbar
-  --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
-  --bgcolor-navbar-hs: 158,30%;
-  --bgcolor-navbar-l: 20%;
-  --bgcolor-search-top-dropdown: hsl(var(--bgcolor-search-top-dropdown-hs),var(--bgcolor-search-top-dropdown-l));
-  --bgcolor-search-top-dropdown-hs: 115,95%;
-  --bgcolor-search-top-dropdown-l: 36%;
-  --border-image-navbar: linear-gradient(to right, #5c78ef 0%, #16bc42 50%, #5c78ef 100%);
-
-  // Logo colors
-  --bgcolor-logo: var(--bgcolor-navbar);
-  --fillcolor-logo-mark: #{lighten(desaturate(bs.$gray-100, 10%), 15%)};
-
-  // Sidebar
-  --bgcolor-sidebar: hsl(var(--bgcolor-sidebar-hs),var(--bgcolor-sidebar-l));
-  --bgcolor-sidebar-hs: 158,71%;
-  --bgcolor-sidebar-l: 33%;
-
-  // Sidebar contents
-  --color-sidebar-context: hsl(var(--color-sidebar-context-hs),var(--color-sidebar-context-l));
-  --color-sidebar-context-hs: 328,100%;
-  --color-sidebar-context-l: 25%;
-  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
-  --bgcolor-sidebar-context-hs: 67,31%;
-  --bgcolor-sidebar-context-l: 94%;
-
-  // Sidebar resize button
-  --color-resize-button: white;
-  --bgcolor-resize-button: hsl(var(--bgcolor-resize-button-hs),var(--bgcolor-resize-button-l));
-  --bgcolor-resize-button-hs: 115,95%;
-  --bgcolor-resize-button-l: 36%;
-  --color-resize-button-hover: var(--color-reversal);
-  --bgcolor-resize-button-hover: #{hsl.lighten(var(--bgcolor-resize-button), 5%)};
-
-  // Subnavigation
-  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
-  --bgcolor-subnav-hs: 0,0%;
-  --bgcolor-subnav-l: 98%;
-
-  // Icon colors
-  --color-editor-icons: var(--color-global);
-
-  // Border colors
-  --border-color-theme: #{bs.$gray-300};
-  --bordercolor-inline-code: #ccc8c8; // optional
-
-  // Table colors
-  --border-color-table: #{bs.$gray-400}; // optional
-
-  // admin theme box
-  --color-theme-color-box: #{hsl.lighten(var(--primary), 20%)};
-
-  // Search Top
-  .grw-global-search {
-    .btn-secondary.dropdown-toggle {
-      color: var(--color-search);
-    }
-  }
-
-  // Navs
-  .nav-tabs .nav-link.active {
-    color: var(--color-link) !important;
-    &:hover {
-      color: var(--color-link-hover) !important;
-    }
-  }
-
-  // Button
-  .btn-group.grw-page-editor-mode-manager {
-    .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(var(--bgcolor-navbar), #{hsl.lighten(var(--bgcolor-navbar), 65%)}, #{hsl.lighten(var(--bgcolor-navbar), 70%)});
-    }
-  }
+  $border-color:              $gray-300;
+
+  $link-color:                $gray-800;
+
+  @import 'bootstrap/scss/variables';
+  @import 'bootstrap/scss/variables-dark';
+
+  @import '@growi/core/scss/bootstrap/init-stage-2';
+
+  @import '@growi/core/scss/bootstrap/theming/apply-light';
+
+  --grw-wiki-link-color-rgb: var(--grw-primary-600-rgb);
+  --grw-wiki-link-hover-color-rgb: var(--grw-primary-700-rgb);
 }
+
+// @use '@growi/core/scss/bootstrap/init' as bs;
+
+// @use './variables' as var;
+// @use './theme/mixins/page-editor-mode-manager';
+// @use './theme/hsl-functions' as hsl;
+
+// .growi:not(.login-page) {
+//   // add background-image
+//   .page-editor-preview-container {
+//     background-attachment: fixed;
+//     background-position: center center;
+//     background-size: cover;
+//   }
+// }
+
+// //== Light Mode
+// //
+// :root[data-bs-theme='light'] {
+//   --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+//   --primary-hs: 311,100%;
+//   --primary-l: 14%;
+//   --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+//   --secondary-hs: 208,7%;
+//   --secondary-l: 46%;
+
+//   // Background colors
+//   --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+//   --bgcolor-global-hs: 0,0%;
+//   --bgcolor-global-l: 99%;
+//   --bgcolor-inline-code: #{bs.$gray-100}; //optional
+//   --bgcolor-card: #f1ffe4;
+//   --bgcolor-blinked-section: #{hsl.alpha(var(--primary), 10%)};
+//   //--bgcolor-keyword-highlighted: #{$grw-marker-yellow};
+
+//   // Font colors
+//   --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+//   --color-global-hs: 311,100%;
+//   --color-global-l: 14%;
+//   --color-reversal: #eeeeee;
+//   --color-link: hsl(var(--color-link-hs),var(--color-link-l));
+//   --color-link-hs: 328,100%;
+//   --color-link-l: 25%;
+//   --color-link-hover: #{hsl.lighten(var(--color-link), 20%)};
+//   --color-link-wiki: #{hsl.lighten(var(--primary), 20%)};
+//   --color-link-wiki-hs: var(--primary-hs);
+//   --color-link-wiki-l: calc(var(--primary-l) + 20%);
+//   --color-link-wiki-hover: #{hsl.lighten(var(--primary), 40%)};
+//   --color-link-nabvar: #a7a7a7;
+//   --color-inline-code: #c7254e; // optional
+//   --color-search: white;
+
+//   // Navbar
+//   --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
+//   --bgcolor-navbar-hs: 158,30%;
+//   --bgcolor-navbar-l: 20%;
+//   --bgcolor-search-top-dropdown: hsl(var(--bgcolor-search-top-dropdown-hs),var(--bgcolor-search-top-dropdown-l));
+//   --bgcolor-search-top-dropdown-hs: 115,95%;
+//   --bgcolor-search-top-dropdown-l: 36%;
+//   --border-image-navbar: linear-gradient(to right, #5c78ef 0%, #16bc42 50%, #5c78ef 100%);
+
+//   // Logo colors
+//   --bgcolor-logo: var(--bgcolor-navbar);
+//   --fillcolor-logo-mark: #{lighten(desaturate(bs.$gray-100, 10%), 15%)};
+
+//   // Sidebar
+//   --bgcolor-sidebar: hsl(var(--bgcolor-sidebar-hs),var(--bgcolor-sidebar-l));
+//   --bgcolor-sidebar-hs: 158,71%;
+//   --bgcolor-sidebar-l: 33%;
+
+//   // Sidebar contents
+//   --color-sidebar-context: hsl(var(--color-sidebar-context-hs),var(--color-sidebar-context-l));
+//   --color-sidebar-context-hs: 328,100%;
+//   --color-sidebar-context-l: 25%;
+//   --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+//   --bgcolor-sidebar-context-hs: 67,31%;
+//   --bgcolor-sidebar-context-l: 94%;
+
+//   // Sidebar resize button
+//   --color-resize-button: white;
+//   --bgcolor-resize-button: hsl(var(--bgcolor-resize-button-hs),var(--bgcolor-resize-button-l));
+//   --bgcolor-resize-button-hs: 115,95%;
+//   --bgcolor-resize-button-l: 36%;
+//   --color-resize-button-hover: var(--color-reversal);
+//   --bgcolor-resize-button-hover: #{hsl.lighten(var(--bgcolor-resize-button), 5%)};
+
+//   // Subnavigation
+//   --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+//   --bgcolor-subnav-hs: 0,0%;
+//   --bgcolor-subnav-l: 98%;
+
+//   // Icon colors
+//   --color-editor-icons: var(--color-global);
+
+//   // Border colors
+//   --border-color-theme: #{bs.$gray-300};
+//   --bordercolor-inline-code: #ccc8c8; // optional
+
+//   // Table colors
+//   --border-color-table: #{bs.$gray-400}; // optional
+
+//   // admin theme box
+//   --color-theme-color-box: #{hsl.lighten(var(--primary), 20%)};
+
+//   // Search Top
+//   .grw-global-search {
+//     .btn-secondary.dropdown-toggle {
+//       color: var(--color-search);
+//     }
+//   }
+
+//   // Navs
+//   .nav-tabs .nav-link.active {
+//     color: var(--color-link) !important;
+//     &:hover {
+//       color: var(--color-link-hover) !important;
+//     }
+//   }
+
+//   // Button
+//   .btn-group.grw-page-editor-mode-manager {
+//     .btn.btn-outline-primary {
+//       @include page-editor-mode-manager.btn-page-editor-mode-manager(var(--bgcolor-navbar), #{hsl.lighten(var(--bgcolor-navbar), 65%)}, #{hsl.lighten(var(--bgcolor-navbar), 70%)});
+//     }
+//   }
+// }

+ 2 - 1
packages/preset-themes/vite.themes.config.ts

@@ -19,10 +19,11 @@ export default defineConfig(({ mode }) => {
           // '/src/styles/halloween.scss',
           // '/src/styles/hufflepuff.scss',
           // '/src/styles/island.scss',
+          '/src/styles/kibela.scss',
           '/src/styles/jade-green.scss',
           // '/src/styles/kibela.scss',
           '/src/styles/mono-blue.scss',
-          // '/src/styles/nature.scss',
+          '/src/styles/nature.scss',
           // '/src/styles/spring.scss',
           // '/src/styles/wood.scss',
         ],