Skip to content

Commit

Permalink
design: revamp navbar dropdown (#11864)
Browse files Browse the repository at this point in the history
Co-authored-by: Jay <[email protected]>
  • Loading branch information
yoonhyejin and jayacryl authored Jan 15, 2025
1 parent b015fd2 commit 3108b53
Show file tree
Hide file tree
Showing 22 changed files with 878 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs-website/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ module.exports = {
require.resolve("./src/styles/sphinx.scss"),
require.resolve("./src/styles/config-table.scss"),
require.resolve("./src/components/SecondNavbar/styles.module.scss"),
require.resolve("./src/components/SolutionsDropdown/styles.module.css"),
],
},
pages: {
Expand Down
183 changes: 183 additions & 0 deletions docs-website/src/components/CardDropdown/CardDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
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, {type LinkLikeNavbarItemProps} from '@theme/NavbarItem';
import type {
DesktopOrMobileNavBarItemProps,
Props,
} from '@theme/NavbarItem/DropdownNavbarItem';
import styles from './styles.module.scss';
import Link from '@docusaurus/Link';

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({
label,
items,
position,
className,
onClick,
...props
}: DesktopOrMobileNavBarItemProps) {
const dropdownRef = useRef<HTMLDivElement>(null);
const [showDropdown, setShowDropdown] = useState(false);

useEffect(() => {
const handleClickOutside = (
event: MouseEvent | TouchEvent | FocusEvent,
) => {
if (
!dropdownRef.current ||
dropdownRef.current.contains(event.target as Node)
) {
return;
}
setShowDropdown(false);
};

document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('touchstart', handleClickOutside);
document.addEventListener('focusin', handleClickOutside);

return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('touchstart', handleClickOutside);
document.removeEventListener('focusin', 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"
label={label}
// # hash permits to make the <a> tag focusable in case no link target
// See https://github.com/facebook/docusaurus/pull/6003
// There's probably a better solution though...
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>
<ul className={clsx("dropdown__menu", styles.dropdown__menu)}>
<></>
{/* {items.map((childItemProps, i) => (
<NavbarItem
isDropdownItem
activeClassName="dropdown__link--active"
{...childItemProps}
key={i}
/>
))} */}
<div className={clsx(styles.col, styles.wrapper)}>
{items.map((item, index) => (
<div key={`${index}`} className={clsx(styles.cardWrapper)}>
<Link className={clsx(styles.card)} to={item.href}>
<div className={clsx(styles.icon)}>
<img src={item.iconImage} alt={item.title} />
</div>
<div className={clsx(styles.title)}>{item.title}</div>
</Link>
</div>
))}
</div>
</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(
styles.dropdownNavbarItemMobile,
'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}>
<></>
</Collapsible>
</li>
);
}

export default function DropdownNavbarItem({
mobile = false,
...props
}: Props): JSX.Element {
const Comp = mobile ? DropdownNavbarItemMobile : DropdownNavbarItemDesktop;
return <Comp {...props} />;
}
67 changes: 67 additions & 0 deletions docs-website/src/components/CardDropdown/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* 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.
*/

.dropdownNavbarItemMobile {
cursor: pointer;
}

.dropdown__menu {
display: flex;
width: 13.875rem;
padding: 0.875rem 1.63694rem;
justify-content: center;
align-items: flex-start;
gap: 0.98219rem;
border-radius: var(--number-scales-2s-20, 1.25rem);
background: #FFF;
box-shadow: 0px 16px 16px 0px rgba(0, 0, 0, 0.25);
}


.wrapper {
display: flex;
flex-direction: column;
gap: 0.6rem;
}

.card {
display: flex;
align-items: center;
padding: 1rem 0.8rem;
text-align: left;
gap: 0.5rem;
border-radius: 0.72681rem;
background: #F7F7F7;
width: 12rem;
transition: transform 0.2s, box-shadow 0.2s;

&:hover {
transform: translateY(-5px);
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
text-decoration: none;
color: inherit;
}
}

.icon {
flex-shrink: 0;
width: 1.3rem;
height: 1.3rem;
display: flex;
justify-content: center;
align-items: center;
}

.title {
flex-grow: 1;
color: #1E1E1E;
font-family: Manrope, sans-serif;
font-size: 0.9rem;
font-weight: 600;
line-height: 1.5rem;
letter-spacing: -0.011rem;
}
Loading

0 comments on commit 3108b53

Please sign in to comment.