diff --git a/src/components/CarbonAd.js b/src/components/CarbonAd.js index f99f1d29..896d01e1 100644 --- a/src/components/CarbonAd.js +++ b/src/components/CarbonAd.js @@ -2,10 +2,7 @@ import React, { useEffect } from 'react'; const CarbonAds = () => { return ( - <> -
- ); }; diff --git a/src/pages/index.js b/src/pages/index.js index 72791e92..af1fdf9a 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,9 +1,6 @@ import React from 'react'; import Layout from '@theme/Layout'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import Head from '@docusaurus/Head'; -import styles from './index.module.scss'; - import HomePageHeader from '../components/HomePageHeader'; import HomePageFeatures from '../components/HomepageFeatures'; @@ -16,7 +13,7 @@ export default function Home() { description="Dashy, the self-hosted dashboard app for your homelab"> -
+
); diff --git a/src/styles/custom.scss b/src/styles/custom.scss index f0bda5b6..5acc899e 100644 --- a/src/styles/custom.scss +++ b/src/styles/custom.scss @@ -244,3 +244,29 @@ html[data-theme='light'] { --carbon-text-color: hsl(0, 0%, 90%); } } + +.sidebar-ad { + --carbon-bg-primary: var(--background, #18191a); + --carbon-bg-secondary: #282a36; + --carbon-text-color: #e6e6e6; + + #carbonads { + margin: 0.5rem; + } + #carbonads .carbon-img img, .avatar__photo-link { + border-radius: 5px; + } + #carbonads .carbon-wrap { + justify-content: center; + } + #carbonads .carbon-text { + font-size: 1rem; + } + #carbonads > span { + box-shadow: none; + } + #carbonads .carbon-poweredby { + border-radius: 5px; + font-size: 0.7rem; + } +} diff --git a/src/theme/DocSidebar/index.js b/src/theme/DocSidebar/index.js new file mode 100644 index 00000000..76dd38f2 --- /dev/null +++ b/src/theme/DocSidebar/index.js @@ -0,0 +1,353 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import React, {useState, useCallback, useEffect, useRef, memo} from 'react'; +import clsx from 'clsx'; +import { + useThemeConfig, + isSamePath, + usePrevious, + useAnnouncementBar, +} from '@docusaurus/theme-common'; +import useLockBodyScroll from '@theme/hooks/useLockBodyScroll'; +import useWindowSize, {windowSizes} from '@theme/hooks/useWindowSize'; +import useScrollPosition from '@theme/hooks/useScrollPosition'; +import Link from '@docusaurus/Link'; +import isInternalUrl from '@docusaurus/isInternalUrl'; +import Logo from '@theme/Logo'; +import IconArrow from '@theme/IconArrow'; +import IconMenu from '@theme/IconMenu'; +import IconExternalLink from '@theme/IconExternalLink'; +import {translate} from '@docusaurus/Translate'; +import styles from './styles.module.css'; +import CarbonAds from '../../components/CarbonAd'; +const MOBILE_TOGGLE_SIZE = 24; + +const isActiveSidebarItem = (item, activePath) => { + if (item.type === 'link') { + return isSamePath(item.href, activePath); + } + + if (item.type === 'category') { + return item.items.some((subItem) => + isActiveSidebarItem(subItem, activePath), + ); + } + + return false; +}; // Optimize sidebar at each "level" +// TODO this item should probably not receive the "activePath" props +// TODO this triggers whole sidebar re-renders on navigation + +const DocSidebarItems = memo(function DocSidebarItems({items, ...props}) { + return items.map((item, index) => ( + + )); +}); + +function DocSidebarItem(props) { + switch (props.item.type) { + case 'category': + return ; + + case 'link': + default: + return ; + } +} + +function DocSidebarItemCategory({ + item, + onItemClick, + collapsible, + activePath, + ...props +}) { + const {items, label} = item; + const isActive = isActiveSidebarItem(item, activePath); + const wasActive = usePrevious(isActive); // active categories are always initialized as expanded + // the default (item.collapsed) is only used for non-active categories + + const [collapsed, setCollapsed] = useState(() => { + if (!collapsible) { + return false; + } + + return isActive ? false : item.collapsed; + }); + const menuListRef = useRef(null); + const [menuListHeight, setMenuListHeight] = useState(undefined); + + const handleMenuListHeight = (calc = true) => { + setMenuListHeight( + calc ? `${menuListRef.current?.scrollHeight}px` : undefined, + ); + }; // If we navigate to a category, it should automatically expand itself + + useEffect(() => { + const justBecameActive = isActive && !wasActive; + + if (justBecameActive && collapsed) { + setCollapsed(false); + } + }, [isActive, wasActive, collapsed]); + const handleItemClick = useCallback( + (e) => { + e.preventDefault(); + + if (!menuListHeight) { + handleMenuListHeight(); + } + + setTimeout(() => setCollapsed((state) => !state), 100); + }, + [menuListHeight], + ); + + if (items.length === 0) { + return null; + } + + return ( +
  • + + {label} + +
      { + if (!collapsed) { + handleMenuListHeight(false); + } + }}> + +
    +
  • + ); +} + +function DocSidebarItemLink({ + item, + onItemClick, + activePath, + collapsible: _collapsible, + ...props +}) { + const {href, label} = item; + const isActive = isActiveSidebarItem(item, activePath); + return ( +
  • + + {isInternalUrl(href) ? ( + label + ) : ( + + {label} + + + )} + +
  • + ); +} + +function useShowAnnouncementBar() { + const {isClosed} = useAnnouncementBar(); + const [showAnnouncementBar, setShowAnnouncementBar] = useState(!isClosed); + useScrollPosition(({scrollY}) => { + if (!isClosed) { + setShowAnnouncementBar(scrollY === 0); + } + }); + return showAnnouncementBar; +} + +function useResponsiveSidebar() { + const [showResponsiveSidebar, setShowResponsiveSidebar] = useState(false); + useLockBodyScroll(showResponsiveSidebar); + const windowSize = useWindowSize(); + useEffect(() => { + if (windowSize === windowSizes.desktop) { + setShowResponsiveSidebar(false); + } + }, [windowSize]); + const closeResponsiveSidebar = useCallback( + (e) => { + e.target.blur(); + setShowResponsiveSidebar(false); + }, + [setShowResponsiveSidebar], + ); + const toggleResponsiveSidebar = useCallback(() => { + setShowResponsiveSidebar((value) => !value); + }, [setShowResponsiveSidebar]); + return { + showResponsiveSidebar, + closeResponsiveSidebar, + toggleResponsiveSidebar, + }; +} + +function HideableSidebarButton({onClick}) { + return ( + + ); +} + +function ResponsiveSidebarButton({responsiveSidebarOpened, onClick}) { + return ( + + ); +} + +function DocSidebar({ + path, + sidebar, + sidebarCollapsible = true, + onCollapse, + isHidden, +}) { + const showAnnouncementBar = useShowAnnouncementBar(); + const { + navbar: {hideOnScroll}, + hideableSidebar, + } = useThemeConfig(); + const {isClosed: isAnnouncementBarClosed} = useAnnouncementBar(); + const { + showResponsiveSidebar, + closeResponsiveSidebar, + toggleResponsiveSidebar, + } = useResponsiveSidebar(); + return ( +
    + {hideOnScroll && } + + + {hideableSidebar && } +
    + ); +} + +export default DocSidebar; diff --git a/src/theme/DocSidebar/styles.module.css b/src/theme/DocSidebar/styles.module.css new file mode 100644 index 00000000..6dcbbfb1 --- /dev/null +++ b/src/theme/DocSidebar/styles.module.css @@ -0,0 +1,150 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +:root { + --collapse-button-bg-color-dark: #2e333a; +} + +@media (min-width: 997px) { + .sidebar { + display: flex; + flex-direction: column; + max-height: 100vh; + height: 100%; + position: sticky; + top: 0; + padding-top: var(--ifm-navbar-height); + width: var(--doc-sidebar-width); + transition: opacity 50ms ease; + } + + .sidebarWithHideableNavbar { + padding-top: 0; + } + + .sidebarHidden { + opacity: 0; + height: 0; + overflow: hidden; + visibility: hidden; + } + + .sidebarLogo { + display: flex !important; + align-items: center; + margin: 0 var(--ifm-navbar-padding-horizontal); + min-height: var(--ifm-navbar-height); + max-height: var(--ifm-navbar-height); + color: inherit !important; + text-decoration: none !important; + } + + .sidebarLogo img { + margin-right: 0.5rem; + height: 2rem; + } + + .menu { + flex-grow: 1; + padding: 0.5rem; + } + + .menuLinkText { + cursor: initial; + } + + .menuLinkText:hover { + background: none; + } + + .menuWithAnnouncementBar { + margin-bottom: var(--docusaurus-announcement-bar-height); + } + + .collapseSidebarButton { + display: block !important; + background-color: var(--ifm-button-background-color); + height: 40px; + position: sticky; + bottom: 0; + border-radius: 0; + border: 1px solid var(--ifm-toc-border-color); + } + + .collapseSidebarButtonIcon { + transform: rotate(180deg); + margin-top: 4px; + } + html[dir='rtl'] .collapseSidebarButtonIcon { + transform: rotate(0); + } + + html[data-theme='dark'] .collapseSidebarButton { + background-color: var(--collapse-button-bg-color-dark); + } + + html[data-theme='dark'] .collapseSidebarButton:hover, + html[data-theme='dark'] .collapseSidebarButton:focus { + background-color: var(--ifm-color-emphasis-200); + } +} + +.sidebarLogo, +.collapseSidebarButton { + display: none; +} + +.sidebarMenuIcon { + vertical-align: middle; +} + +.sidebarMenuCloseIcon { + display: inline-flex; + justify-content: center; + align-items: center; + height: 24px; + font-size: 1.5rem; + font-weight: var(--ifm-font-weight-bold); + line-height: 0.9; + width: 24px; +} + +:global(.menu__list) :global(.menu__list) { + overflow-y: hidden; + will-change: height; + transition: height var(--ifm-transition-fast) linear; +} + +:global(.menu__list-item--collapsed) :global(.menu__list) { + height: 0 !important; +} + +.sidebar-ad { + --carbon-bg-primary: var(--background, #18191a); + --carbon-bg-secondary: #282a36; + --carbon-text-color: #e6e6e6; +} + +.sidebar-ad #carbonads { + margin: 0.5rem; +} +.sidebar-ad #carbonads .carbon-img img, .sidebar-ad .avatar__photo-link { + border-radius: 5px; +} +.sidebar-ad #carbonads .carbon-wrap { + justify-content: center; +} +.sidebar-ad #carbonads .carbon-text { + font-size: 1rem; +} +.sidebar-ad #carbonads > span { + box-shadow: none; +} +.sidebar-ad #carbonads .carbon-poweredby { + border-radius: 5px; + font-size: 0.7rem; +}