mirror of
https://github.com/wavetermdev/docusaurus-og.git
synced 2026-08-05 13:46:35 -07:00
change locale dropdown's icon and background; adjust navbar dropdown spacing.
This commit is contained in:
@@ -32,4 +32,7 @@
|
||||
:root {
|
||||
--doc-sidebar-width: 16.66vw !important;
|
||||
--doc-sidebar-max-width: 320px;
|
||||
|
||||
--ifm-navbar-item-padding-vertical: 8px;
|
||||
--ifm-navbar-item-padding-horizontal: 12px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
.navbar__item.dropdown {
|
||||
background-color: #eeeef1;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
& > .navbar__link {
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& + svg {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
.navbar__item.dropdown {
|
||||
background-color: var(--ifm-background-color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
import React, { useState, useRef, useEffect } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import {
|
||||
isRegexpStringMatch,
|
||||
useCollapsible,
|
||||
Collapsible,
|
||||
} from '@docusaurus/theme-common'
|
||||
import { isSamePath, useLocalPathname } from '@docusaurus/theme-common/internal'
|
||||
import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink'
|
||||
import NavbarItem, { LinkLikeNavbarItemProps } from '@theme/NavbarItem'
|
||||
import type {
|
||||
DesktopOrMobileNavBarItemProps,
|
||||
Props,
|
||||
} from '@theme/NavbarItem/DropdownNavbarItem'
|
||||
import './DropdownNavbarItem.scss'
|
||||
import DropdownIcon from '@site/static/icons/dropdown.svg'
|
||||
|
||||
function isItemActive(
|
||||
item: LinkLikeNavbarItemProps,
|
||||
localPathname: string,
|
||||
): boolean {
|
||||
if (isSamePath(item.to, localPathname)) {
|
||||
return true
|
||||
}
|
||||
if (isRegexpStringMatch(item.activeBaseRegex, localPathname)) {
|
||||
return true
|
||||
}
|
||||
if (item.activeBasePath && localPathname.startsWith(item.activeBasePath)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function containsActiveItems(
|
||||
items: readonly LinkLikeNavbarItemProps[],
|
||||
localPathname: string,
|
||||
): boolean {
|
||||
return items.some((item) => isItemActive(item, localPathname))
|
||||
}
|
||||
|
||||
function DropdownNavbarItemDesktop({
|
||||
items,
|
||||
position,
|
||||
className,
|
||||
onClick,
|
||||
...props
|
||||
}: DesktopOrMobileNavBarItemProps) {
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
const [showDropdown, setShowDropdown] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
|
||||
if (
|
||||
!dropdownRef.current ||
|
||||
dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
return
|
||||
}
|
||||
setShowDropdown(false)
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
document.addEventListener('touchstart', handleClickOutside)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
document.removeEventListener('touchstart', handleClickOutside)
|
||||
}
|
||||
}, [dropdownRef])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
className={clsx('navbar__item', 'dropdown', 'dropdown--hoverable', {
|
||||
'dropdown--right': position === 'right',
|
||||
'dropdown--show': showDropdown,
|
||||
})}
|
||||
>
|
||||
<NavbarNavLink
|
||||
aria-haspopup="true"
|
||||
aria-expanded={showDropdown}
|
||||
role="button"
|
||||
href={props.to ? undefined : '#'}
|
||||
className={clsx('navbar__link', className)}
|
||||
{...props}
|
||||
onClick={props.to ? undefined : (e) => e.preventDefault()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
setShowDropdown(!showDropdown)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{props.children ?? props.label}
|
||||
</NavbarNavLink>
|
||||
<DropdownIcon />
|
||||
<ul className="dropdown__menu">
|
||||
{items.map((childItemProps, i) => (
|
||||
<NavbarItem
|
||||
isDropdownItem
|
||||
onKeyDown={(e) => {
|
||||
if (i === items.length - 1 && e.key === 'Tab') {
|
||||
e.preventDefault()
|
||||
setShowDropdown(false)
|
||||
const nextNavbarItem = dropdownRef.current!.nextElementSibling
|
||||
if (nextNavbarItem) {
|
||||
const targetItem =
|
||||
nextNavbarItem instanceof HTMLAnchorElement
|
||||
? nextNavbarItem
|
||||
: // Next item is another dropdown; focus on the inner
|
||||
// anchor element instead so there's outline
|
||||
nextNavbarItem.querySelector('a')!
|
||||
targetItem.focus()
|
||||
}
|
||||
}
|
||||
}}
|
||||
activeClassName="dropdown__link--active"
|
||||
{...childItemProps}
|
||||
key={i}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownNavbarItemMobile({
|
||||
items,
|
||||
className,
|
||||
position, // Need to destructure position from props so that it doesn't get passed on.
|
||||
onClick,
|
||||
...props
|
||||
}: DesktopOrMobileNavBarItemProps) {
|
||||
const localPathname = useLocalPathname()
|
||||
const containsActive = containsActiveItems(items, localPathname)
|
||||
|
||||
const { collapsed, toggleCollapsed, setCollapsed } = useCollapsible({
|
||||
initialState: () => !containsActive,
|
||||
})
|
||||
|
||||
// Expand/collapse if any item active after a navigation
|
||||
useEffect(() => {
|
||||
if (containsActive) {
|
||||
setCollapsed(!containsActive)
|
||||
}
|
||||
}, [localPathname, containsActive, setCollapsed])
|
||||
|
||||
return (
|
||||
<li
|
||||
className={clsx('menu__list-item', {
|
||||
'menu__list-item--collapsed': collapsed,
|
||||
})}
|
||||
>
|
||||
<NavbarNavLink
|
||||
role="button"
|
||||
className={clsx(
|
||||
'menu__link menu__link--sublist menu__link--sublist-caret',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
toggleCollapsed()
|
||||
}}
|
||||
>
|
||||
{props.children ?? props.label}
|
||||
</NavbarNavLink>
|
||||
<Collapsible lazy as="ul" className="menu__list" collapsed={collapsed}>
|
||||
{items.map((childItemProps, i) => (
|
||||
<NavbarItem
|
||||
mobile
|
||||
isDropdownItem
|
||||
onClick={onClick}
|
||||
activeClassName="menu__link--active"
|
||||
{...childItemProps}
|
||||
key={i}
|
||||
/>
|
||||
))}
|
||||
</Collapsible>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default function DropdownNavbarItem({
|
||||
mobile = false,
|
||||
...props
|
||||
}: Props): JSX.Element {
|
||||
const Comp = mobile ? DropdownNavbarItemMobile : DropdownNavbarItemDesktop
|
||||
return <Comp {...props} />
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react'
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
|
||||
import { useAlternatePageUtils } from '@docusaurus/theme-common/internal'
|
||||
import { translate } from '@docusaurus/Translate'
|
||||
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem'
|
||||
import IconLanguage from '@theme/Icon/Language'
|
||||
import type { LinkLikeNavbarItemProps } from '@theme/NavbarItem'
|
||||
import type { Props } from '@theme/NavbarItem/LocaleDropdownNavbarItem'
|
||||
|
||||
import styles from './styles.module.css'
|
||||
|
||||
export default function LocaleDropdownNavbarItem({
|
||||
mobile,
|
||||
dropdownItemsBefore,
|
||||
dropdownItemsAfter,
|
||||
...props
|
||||
}: Props): JSX.Element {
|
||||
const {
|
||||
i18n: { currentLocale, locales, localeConfigs },
|
||||
} = useDocusaurusContext()
|
||||
const alternatePageUtils = useAlternatePageUtils()
|
||||
|
||||
const localeItems = locales.map((locale): LinkLikeNavbarItemProps => {
|
||||
const to = `pathname://${alternatePageUtils.createUrl({
|
||||
locale,
|
||||
fullyQualified: false,
|
||||
})}`
|
||||
return {
|
||||
label: localeConfigs[locale]!.label,
|
||||
lang: localeConfigs[locale]!.htmlLang,
|
||||
to,
|
||||
target: '_self',
|
||||
autoAddBaseUrl: false,
|
||||
className:
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
locale === currentLocale
|
||||
? // Similar idea as DefaultNavbarItem: select the right Infima active
|
||||
// class name. This cannot be substituted with isActive, because the
|
||||
// target URLs contain `pathname://` and therefore are not NavLinks!
|
||||
mobile
|
||||
? 'menu__link--active'
|
||||
: 'dropdown__link--active'
|
||||
: '',
|
||||
}
|
||||
})
|
||||
|
||||
const items = [...dropdownItemsBefore, ...localeItems, ...dropdownItemsAfter]
|
||||
|
||||
// Mobile is handled a bit differently
|
||||
const dropdownLabel = mobile
|
||||
? translate({
|
||||
message: 'Languages',
|
||||
id: 'theme.navbar.mobileLanguageDropdown.label',
|
||||
description: 'The label for the mobile language switcher dropdown',
|
||||
})
|
||||
: localeConfigs[currentLocale]!.label
|
||||
|
||||
return (
|
||||
<DropdownNavbarItem
|
||||
{...props}
|
||||
mobile={mobile}
|
||||
label={<>{dropdownLabel}</>}
|
||||
items={items}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
.iconLanguage {
|
||||
vertical-align: text-bottom;
|
||||
margin-right: 5px;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 4L6 8L10 4" stroke="#909091" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 212 B |
Reference in New Issue
Block a user