blob: bcf5d207521b700032035a32d44c8c1019fe9adf [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from "react";
import { useLocation } from "@docusaurus/router";
import { translate } from "@docusaurus/Translate";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { useNavbarMobileSidebar } from "@docusaurus/theme-common/internal";
import NavbarItem from "@theme/NavbarItem";
import NavbarNavLink from "@theme/NavbarItem/NavbarNavLink";
import UtilityItems, { isUtilityItem } from "./UtilityItems";
function classNames(...values) {
return values.filter(Boolean).join(" ");
}
function normalizePath(path) {
if (!path) {
return "";
}
return path.replace(/\/+$/, "") || "/";
}
function isExternalUrl(value) {
return /^https?:\/\//i.test(value || "");
}
function isRegexpStringMatch(pattern, text) {
if (!pattern) {
return false;
}
return new RegExp(pattern).test(text);
}
function isSamePath(path, localPathname) {
return normalizePath(path) === normalizePath(localPathname);
}
function isItemActive(item, localPathname) {
if (!isExternalUrl(item.to) && 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, localPathname) {
return items.some((item) => isItemActive(item, localPathname));
}
function isAsfDropdownItem(item) {
return item.label === "ASF" && Array.isArray(item.items);
}
function CollapseButton({ collapsed, onClick }) {
return (
<button
aria-label={
collapsed
? translate({
id: "theme.navbar.mobileDropdown.collapseButton.expandAriaLabel",
message: "Expand the dropdown",
description:
"The ARIA label of the button to expand the mobile dropdown navbar item",
})
: translate({
id: "theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel",
message: "Collapse the dropdown",
description:
"The ARIA label of the button to collapse the mobile dropdown navbar item",
})
}
aria-expanded={!collapsed}
type="button"
className="clean-btn menu__caret"
onClick={onClick}
/>
);
}
function useItemCollapsible({ active }) {
const [collapsed, setCollapsed] = React.useState(() => !active);
React.useEffect(() => {
if (active) {
setCollapsed(false);
}
}, [active, setCollapsed]);
return {
collapsed,
toggleCollapsed: () => {
setCollapsed((value) => !value);
},
};
}
function AsfMobileDropdown({ item, onNavigate }) {
const { pathname } = useLocation();
const { items, className, ...dropdownProps } = item;
const props = { ...dropdownProps };
delete props.position;
delete props.type;
delete props.onClick;
const isActive = isSamePath(props.to, pathname);
const containsActive = containsActiveItems(items, pathname);
const { collapsed, toggleCollapsed } = useItemCollapsible({
active: isActive || containsActive,
});
const href = props.to ? undefined : "#";
return (
<li
className={classNames(
"menu__list-item",
collapsed && "menu__list-item--collapsed",
)}
>
<div
className={classNames(
"menu__list-item-collapsible",
isActive && "menu__list-item-collapsible--active",
)}
>
<NavbarNavLink
role="button"
className={classNames(
"menu__link menu__link--sublist odl-mobile-drawer-asf-link",
className,
)}
href={href}
{...props}
onClick={(event) => {
if (href === "#") {
event.preventDefault();
}
toggleCollapsed();
}}
>
{props.children ?? props.label}
</NavbarNavLink>
<CollapseButton
collapsed={collapsed}
onClick={(event) => {
event.preventDefault();
toggleCollapsed();
}}
/>
</div>
<ul
className="menu__list odl-mobile-drawer-asf-items"
style={{ display: collapsed ? "none" : "block" }}
>
{items.map((childItemProps, index) => (
<NavbarItem
mobile
isDropdownItem
onClick={onNavigate}
activeClassName="menu__link--active"
{...childItemProps}
key={index}
/>
))}
</ul>
</li>
);
}
export default function NavbarMobilePrimaryMenu() {
const { siteConfig } = useDocusaurusContext();
const mobileSidebar = useNavbarMobileSidebar();
const items = siteConfig.themeConfig.navbar.items;
let renderedUtilities = false;
const handleNavigate = () => {
mobileSidebar.toggle();
};
return (
<ul className="menu__list">
{items.map((item, index) => {
if (isUtilityItem(item)) {
return null;
}
if (isAsfDropdownItem(item)) {
renderedUtilities = true;
return (
<React.Fragment key={index}>
<AsfMobileDropdown item={item} onNavigate={handleNavigate} />
<UtilityItems onNavigate={handleNavigate} />
</React.Fragment>
);
}
return (
<NavbarItem
mobile
{...item}
onClick={handleNavigate}
key={index}
/>
);
})}
{!renderedUtilities && <UtilityItems onNavigate={handleNavigate} />}
</ul>
);
}