change locale dropdown's icon and background; adjust navbar dropdown spacing.

This commit is contained in:
Hossein Mehrabi
2022-10-07 18:52:23 +03:30
parent fafe86d8c3
commit fe2856392c
6 changed files with 290 additions and 0 deletions
+3
View File
@@ -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);
}
}
+190
View File
@@ -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;
}
+3
View File
@@ -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