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

Separate BookmarkFolderTree and Bookmark list

https://youtrack.weseek.co.jp/issue/GW-7849
- Remove isSidebarItem props from BookmarkFolderItem component
- Remove conditional rendering of bookmark folder item
- Modify click event handler of Bookmark folder item
- Separate BookmarkItem from BookmarkFolderTree
- Create component for BookmarkItemList
- Modify and implement BookmarkList in UsersHomePageFooter component
- Adjust styling for Bookmark list on UsersHomePageFooter
Mudana-Grune 3 лет назад
Родитель
Сommit
20124c6cf5

+ 26 - 60
packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -21,10 +21,9 @@ import DeleteBookmarkFolderModal from './DeleteBookmarkFolderModal';
 type BookmarkFolderItemProps = {
   bookmarkFolder: BookmarkFolderItems
   isOpen?: boolean
-  isSidebarItem: boolean
 }
 const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderItemProps) => {
-  const { bookmarkFolder, isOpen: _isOpen = false, isSidebarItem } = props;
+  const { bookmarkFolder, isOpen: _isOpen = false } = props;
 
   const { t } = useTranslation();
   const {
@@ -39,7 +38,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
   const [isDeleteFolderModalShown, setIsDeleteFolderModalShown] = useState<boolean>(false);
 
-  const childCount = useCallback((): number => {
+  const getChildCount = useCallback((): number => {
     if (currentChildren != null && currentChildren.length > children.length) {
       return currentChildren.length;
     }
@@ -121,7 +120,8 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
     }
   }, [folderId, loadParent, t]);
 
-  const onClickPlusButton = useCallback(async() => {
+  const onClickPlusButton = useCallback(async(e) => {
+    e.stopPropagation();
     if (!isOpen && hasChildren()) {
       setIsOpen(true);
     }
@@ -135,7 +135,6 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
           <BookmarkFolderItem
             key={childFolder._id}
             bookmarkFolder={childFolder}
-            isSidebarItem={isSidebarItem}
           />
         </div>
       );
@@ -156,7 +155,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
 
 
   return (
-    <div id={`bookmark-folder-item-${folderId}`} className="grw-foldertree-item-container"
+    <div id={`bookmark-folder-item-${folderId}`} className="grw-foldertree-item-container" onClick={loadChildFolder}
     >
       <li className="list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center">
         <div className="grw-triangle-container d-flex justify-content-center">
@@ -185,71 +184,38 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
           />
         ) : (
           <>
-            <div className='grw-foldertree-title-anchor flex-grow-1 pl-2' onClick={loadChildFolder}>
-              {isSidebarItem ? (
-                <p className={'text-truncate m-auto '}>{name}</p>
-              ) : (
-                <div className="d-flex flex-row">
-                  <div className="p-2">
-                    <p className={'text-truncate m-auto '}>{name}</p>
-                  </div>
-                  <div className="p-2">
-                    <div className="grw-foldertree-control d-flex">
-                      <BookmarkFolderItemControl
-                        onClickRename={onClickRenameHandler}
-                        onClickDelete={onClickDeleteHandler}
-                      >
-                        <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">
-                          <i className="icon-options fa fa-rotate-90 p-1"></i>
-                        </DropdownToggle>
-                      </BookmarkFolderItemControl>
-                      <button
-                        type="button"
-                        className="border-0 rounded btn btn-page-item-control p-0 grw-visible-on-hover"
-                        onClick={onClickPlusButton}
-                      >
-                        <i className="icon-plus d-block p-0" />
-                      </button>
-                    </div>
-                  </div>
-                  <div className="p-2">
-                    {hasChildren() && (
-                      <div className="grw-foldertree-count-wrapper">
-                        <CountBadge count={ childCount() } />
-                      </div>
-                    )}
-                  </div>
-                </div>
-              )}
+            <div className='grw-foldertree-title-anchor pl-2' >
+              <p className={'text-truncate m-auto '}>{name}</p>
             </div>
-            {hasChildren() && isSidebarItem && (
+            {hasChildren() && (
               <div className="grw-foldertree-count-wrapper">
-                <CountBadge count={ childCount() } />
+                <CountBadge count={ getChildCount() } />
               </div>
             )}
           </>
         )
 
         }
-        { isSidebarItem && (
-          <div className="grw-foldertree-control d-flex">
-            <BookmarkFolderItemControl
-              onClickRename={onClickRenameHandler}
-              onClickDelete={onClickDeleteHandler}
-            >
+        <div className="grw-foldertree-control d-flex">
+          <BookmarkFolderItemControl
+            onClickRename={onClickRenameHandler}
+            onClickDelete={onClickDeleteHandler}
+          >
+            <div onClick={e => e.stopPropagation()}>
               <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">
                 <i className="icon-options fa fa-rotate-90 p-1"></i>
               </DropdownToggle>
-            </BookmarkFolderItemControl>
-            <button
-              type="button"
-              className="border-0 rounded btn btn-page-item-control p-0 grw-visible-on-hover"
-              onClick={onClickPlusButton}
-            >
-              <i className="icon-plus d-block p-0" />
-            </button>
-          </div>
-        )}
+            </div>
+          </BookmarkFolderItemControl>
+          <button
+            type="button"
+            className="border-0 rounded btn btn-page-item-control p-0 grw-visible-on-hover"
+            onClick={onClickPlusButton}
+          >
+            <i className="icon-plus d-block p-0" />
+          </button>
+
+        </div>
 
       </li>
       {isCreateAction && (

+ 1 - 49
packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx

@@ -1,74 +1,26 @@
 
-import { useCallback } from 'react';
-
 import { useTranslation } from 'next-i18next';
 
-import { toastSuccess } from '~/client/util/apiNotification';
-import { IPageToDeleteWithMeta } from '~/interfaces/page';
-import { OnDeletedFunction } from '~/interfaces/ui';
-import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
 import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
-import { usePageDeleteModal } from '~/stores/modal';
 
 import BookmarkFolderItem from './BookmarkFolderItem';
-import BookmarkItem from './BookmarkItem';
 
 import styles from './BookmarkFolderTree.module.scss';
 
 
 const BookmarkFolderTree = (): JSX.Element => {
   const { t } = useTranslation();
-  const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks();
   const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild();
-  const { open: openDeleteModal } = usePageDeleteModal();
-
-  const deleteMenuItemClickHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => {
-    const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => {
-      if (typeof pathOrPathsToDelete !== 'string') {
-        return;
-      }
-      const path = pathOrPathsToDelete;
-
-      if (isCompletely) {
-        toastSuccess(t('deleted_pages_completely', { path }));
-      }
-      else {
-        toastSuccess(t('deleted_pages', { path }));
-      }
-      mutateCurrentUserBookmarks();
-    };
-    openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
-  }, [mutateCurrentUserBookmarks, openDeleteModal, t]);
-
 
   return (
     <>
-      <ul className={`grw-foldertree ${styles['grw-foldertree']} list-group p-3`}>
+      <ul className={`grw-foldertree ${styles['grw-foldertree']} list-group px-3 pt-3`}>
         {bookmarkFolderData?.map((item) => {
           return (
             <BookmarkFolderItem
               key={item._id}
               bookmarkFolder={item}
               isOpen={false}
-              isSidebarItem={true}
-            />
-          );
-        })}
-        {currentUserBookmarksData?.length === 0 && (
-          <div className="pt-3">
-            <h5 className="pl-3">
-              { t('No bookmarks yet') }
-            </h5>
-          </div>
-        )}
-        { currentUserBookmarksData?.map((currentUserBookmark) => {
-          return (
-            <BookmarkItem
-              key={currentUserBookmark._id}
-              bookmarkedPage={currentUserBookmark}
-              onUnbookmarked={mutateCurrentUserBookmarks}
-              onRenamed={mutateCurrentUserBookmarks}
-              onClickDeleteMenuItem={deleteMenuItemClickHandler}
             />
           );
         })}

+ 65 - 0
packages/app/src/components/Bookmarks/BookmarkItemList.tsx

@@ -0,0 +1,65 @@
+import React, { useCallback } from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+import { toastSuccess } from '~/client/util/apiNotification';
+import { IPageToDeleteWithMeta } from '~/interfaces/page';
+import { OnDeletedFunction } from '~/interfaces/ui';
+import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
+import { usePageDeleteModal } from '~/stores/modal';
+
+import BookmarkItem from './BookmarkItem';
+
+import styles from './BookmarkFolderTree.module.scss';
+
+
+const BookmarkItemList = (): JSX.Element => {
+  const { t } = useTranslation();
+  const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks();
+  const { open: openDeleteModal } = usePageDeleteModal();
+
+  const deleteMenuItemClickHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => {
+    const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => {
+      if (typeof pathOrPathsToDelete !== 'string') {
+        return;
+      }
+      const path = pathOrPathsToDelete;
+
+      if (isCompletely) {
+        toastSuccess(t('deleted_pages_completely', { path }));
+      }
+      else {
+        toastSuccess(t('deleted_pages', { path }));
+      }
+      mutateCurrentUserBookmarks();
+    };
+    openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
+  }, [mutateCurrentUserBookmarks, openDeleteModal, t]);
+
+  return (
+    <>
+      {currentUserBookmarksData?.length === 0 && (
+        <div className="pt-3">
+          <h5 className="pl-3">
+            { t('No bookmarks yet') }
+          </h5>
+        </div>
+      )}
+      <ul className={`grw-foldertree ${styles['grw-foldertree']} list-group px-3 pt-2 pb-3`}>
+        { currentUserBookmarksData?.map((currentUserBookmark) => {
+          return (
+            <BookmarkItem
+              key={currentUserBookmark._id}
+              bookmarkedPage={currentUserBookmark}
+              onUnbookmarked={mutateCurrentUserBookmarks}
+              onRenamed={mutateCurrentUserBookmarks}
+              onClickDeleteMenuItem={deleteMenuItemClickHandler}
+            />
+          );
+        })}
+      </ul>
+    </>
+  );
+};
+
+export default BookmarkItemList;

+ 107 - 19
packages/app/src/components/PageList/BookmarkList.tsx

@@ -1,35 +1,123 @@
-import React from 'react';
+import React, { useCallback, useState } from 'react';
 
+import nodePath from 'path';
+
+import {
+  IPageInfoAll, IPageToDeleteWithMeta, pathUtils,
+} from '@growi/core';
 import { useTranslation } from 'next-i18next';
+import { DropdownToggle } from 'reactstrap';
 
-import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
+import { unbookmark } from '~/client/services/page-operation';
+import { toastError, toastSuccess } from '~/client/util/apiNotification';
+import { apiv3Put } from '~/client/util/apiv3-client';
+import { IPageHasId } from '~/interfaces/page';
 import loggerFactory from '~/utils/logger';
 
+import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput';
+import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
+
 import { PageListItemS } from './PageListItemS';
 
 const logger = loggerFactory('growi:BookmarkList');
 
-export const BookmarkList = (): JSX.Element => {
-
+type Props = {
+  page: IPageHasId
+  onRenamed: () => void
+  onUnbookmarked: () => void
+  onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void
+}
+export const BookmarkList = (props:Props): JSX.Element => {
+  const {
+    page, onRenamed, onUnbookmarked, onClickDeleteMenuItem,
+  } = props;
   const { t } = useTranslation();
-  const { data: currentUserBookmarksData } = useSWRxCurrentUserBookmarks();
+  const [isRenameInputShown, setIsRenameInputShown] = useState(false);
+
+  const inputValidator = (title: string | null): AlertInfo | null => {
+    if (title == null || title === '' || title.trim() === '') {
+      return {
+        type: AlertType.WARNING,
+        message: t('form_validation.title_required'),
+      };
+    }
+
+    return null;
+  };
+
+  const bookmarkMenuItemClickHandler = useCallback(async() => {
+    await unbookmark(page._id);
+    onUnbookmarked();
+  }, [page._id, onUnbookmarked]);
 
+  const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise<void> => {
+    if (page._id == null || page.path == null) {
+      throw Error('_id and path must not be null.');
+    }
+
+    const pageToDelete: IPageToDeleteWithMeta = {
+      data: {
+        _id: page._id,
+        revision: page.revision as string,
+        path: page.path,
+      },
+      meta: pageInfo,
+    };
+
+    onClickDeleteMenuItem(pageToDelete);
+  }, [onClickDeleteMenuItem, page]);
+
+  const pressEnterForRenameHandler = useCallback(async(inputText: string) => {
+    const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? ''));
+    const newPagePath = nodePath.resolve(parentPath, inputText);
+    if (newPagePath === page.path) {
+      setIsRenameInputShown(false);
+      return;
+    }
+
+    try {
+      setIsRenameInputShown(false);
+      await apiv3Put('/pages/rename', {
+        pageId: page._id,
+        revisionId: page.revision,
+        newPagePath,
+      });
+      onRenamed();
+      toastSuccess(t('renamed_pages', { path: page.path }));
+    }
+    catch (err) {
+      setIsRenameInputShown(true);
+      toastError(err);
+    }
+  }, [onRenamed, page, t]);
 
   return (
-    <div className="bookmarks-list-container">
-      {currentUserBookmarksData?.length === 0 ? t('No bookmarks yet') : (
-        <>
-          <ul className="page-list-ul page-list-ul-flat mb-3">
-
-            {currentUserBookmarksData?.map(page => (
-              <li key={`my-bookmarks:${page?._id}`} className="mt-4">
-                <PageListItemS page={page} />
-              </li>
-            ))}
-
-          </ul>
-        </>
+    <li key={`my-bookmarks:${page?._id}`} className="list-group-item list-group-item-action border-0 py-0 pl-3 d-flex align-items-center">
+      { isRenameInputShown ? (
+        <ClosableTextInput
+          value={nodePath.basename(page.path ?? '')}
+          placeholder={t('Input page name')}
+          onClickOutside={() => { setIsRenameInputShown(false) }}
+          onPressEnter={pressEnterForRenameHandler}
+          inputValidator={inputValidator}
+        />
+      ) : (
+        <PageListItemS page={page} />
       )}
-    </div>
+
+      <PageItemControl
+        pageId={page._id}
+        isEnableActions
+        forceHideMenuItems={[MenuItemType.DUPLICATE]}
+        onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
+        onClickRenameMenuItem={() => setIsRenameInputShown(true)}
+        onClickDeleteMenuItem={deleteMenuItemClickHandler}
+      >
+        <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">
+          <i className="icon-options fa fa-rotate-90 p-1"></i>
+        </DropdownToggle>
+      </PageItemControl>
+    </li>
+
   );
 };

+ 2 - 0
packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx

@@ -6,6 +6,7 @@ import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import { apiv3Post } from '~/client/util/apiv3-client';
 import BookmarkFolderNameInput from '~/components/Bookmarks/BookmarkFolderNameInput';
 import BookmarkFolderTree from '~/components/Bookmarks/BookmarkFolderTree';
+import BookmarkItemList from '~/components/Bookmarks/BookmarkItemList';
 import FolderPlusIcon from '~/components/Icons/FolderPlusIcon';
 import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
 
@@ -65,6 +66,7 @@ const BookmarkContents = (): JSX.Element => {
         renderAddNewBookmarkFolder()
       }
       <BookmarkFolderTree />
+      <BookmarkItemList />
     </>
   );
 };

+ 27 - 10
packages/app/src/components/UsersHomePageFooter.module.scss

@@ -2,24 +2,41 @@
 
 .user-page-footer :global {
   .grw-user-page-list-m {
-    .list-group-item {
-      svg{
-        width: 20px;
-        height: 20px;
-      }
+    .list-group{
+      .list-group-item {
+        .grw-visible-on-hover {
+          display: none;
+        }
 
-    }
-    .grw-triangle-container{
-      svg {
-        width: 12px;
-        height: 12px;
+        &:hover {
+          .grw-visible-on-hover {
+            display: block;
+          }
+        }
+        .grw-triangle-container{
+          svg {
+            width: 12px;
+            height: 12px;
+          }
+        }
+        svg{
+          width: 20px;
+          height: 20px;
+        }
+        min-height: 40px;
+        border-radius: 0px;
       }
     }
+
     .grw-foldertree-item-container {
       input {
         max-width: 25%;
       }
     }
+    .grw-foldertree-title-anchor{
+      width: fit-content !important;
+      margin-right: 20px;
+    }
     svg {
       width: 35px;
       height: 35px;

+ 45 - 0
packages/app/src/components/UsersHomePageFooter.tsx

@@ -7,11 +7,16 @@ import { apiv3Post } from '~/client/util/apiv3-client';
 import { RecentlyCreatedIcon } from '~/components/Icons/RecentlyCreatedIcon';
 import { RecentCreated } from '~/components/RecentCreated/RecentCreated';
 import styles from '~/components/UsersHomePageFooter.module.scss';
+import { IPageToDeleteWithMeta } from '~/interfaces/page';
+import { OnDeletedFunction } from '~/interfaces/ui';
+import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
 import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
+import { usePageDeleteModal } from '~/stores/modal';
 
 import BookmarkFolderNameInput from './Bookmarks/BookmarkFolderNameInput';
 import BookmarkFolderTree from './Bookmarks/BookmarkFolderTree';
 import FolderPlusIcon from './Icons/FolderPlusIcon';
+import { BookmarkList } from './PageList/BookmarkList';
 
 
 export type UsersHomePageFooterProps = {
@@ -23,6 +28,27 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen
   const { creatorId } = props;
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
   const { mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(null);
+  const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks();
+  const { open: openDeleteModal } = usePageDeleteModal();
+
+
+  const deleteMenuItemClickHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => {
+    const pageDeletedHandler : OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => {
+      if (typeof pathOrPathsToDelete !== 'string') {
+        return;
+      }
+      const path = pathOrPathsToDelete;
+
+      if (isCompletely) {
+        toastSuccess(t('deleted_pages_completely', { path }));
+      }
+      else {
+        toastSuccess(t('deleted_pages', { path }));
+      }
+      mutateCurrentUserBookmarks();
+    };
+    openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
+  }, [mutateCurrentUserBookmarks, openDeleteModal, t]);
 
 
   const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
@@ -66,7 +92,26 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen
         )}
         {
           <BookmarkFolderTree />
+
         }
+        <div id="user-bookmark-list" className={`page-list p-3 ${styles['page-list']}`}>
+          <div className="grw-bookmarks-list-container">
+            {currentUserBookmarksData?.length === 0
+              ? t('No bookmarks yet')
+              : <ul className="list-group page-list-ul page-list-ul-flat mb-3">
+                {currentUserBookmarksData?.map(page => (
+                  <BookmarkList
+                    key={page._id}
+                    page={page}
+                    onRenamed={mutateCurrentUserBookmarks}
+                    onUnbookmarked={mutateCurrentUserBookmarks}
+                    onClickDeleteMenuItem={deleteMenuItemClickHandler}
+                  />
+                ))}
+              </ul>
+            }
+          </div>
+        </div>
 
       </div>
       <div className="grw-user-page-list-m mt-5 d-edit-none">

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

@@ -363,6 +363,7 @@ ul.pagination {
 }
 
 .grw-user-page-list-m {
+  @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
   .grw-foldertree {
     @include override-list-group-item-for-pagetree(
       $body-color,
@@ -377,6 +378,17 @@ ul.pagination {
       @include mixins.button-outline-svg-icon-variant($secondary, $gray-200);
     }
   }
+  .page-list > .grw-bookmarks-list-container > .list-group {
+    @include override-list-group-item-for-pagetree(
+      $body-color,
+      lighten($body-bg, 8%),
+      lighten($body-bg, 15%),
+      darken($body-color, 15%),
+      darken($body-color, 10%),
+      lighten($body-bg, 18%),
+      lighten($body-bg, 24%)
+    );
+  }
 }
 
 .btn.btn-page-item-control {

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

@@ -241,6 +241,7 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
 }
 
 .grw-user-page-list-m {
+  @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
   .grw-foldertree {
     @include override-list-group-item-for-pagetree(
       $body-color,
@@ -255,6 +256,17 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
       @include mixins.button-outline-svg-icon-variant($gray-400, $primary);
     }
   }
+  .page-list > .grw-bookmarks-list-container > .list-group {
+    @include override-list-group-item-for-pagetree(
+      $body-color,
+      darken($body-bg, 5%),
+      darken($body-bg, 12%),
+      lighten($body-color, 10%),
+      lighten($body-color, 8%),
+      darken($body-bg, 15%),
+      darken($body-bg, 24%)
+    );
+  }
 }
 
 .btn.btn-page-item-control {