Skip to content

Commit 3108b53

Browse files
yoonhyejinjayacryl
andauthored
design: revamp navbar dropdown (#11864)
Co-authored-by: Jay <[email protected]>
1 parent b015fd2 commit 3108b53

22 files changed

+878
-0
lines changed

docs-website/docusaurus.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ module.exports = {
336336
require.resolve("./src/styles/sphinx.scss"),
337337
require.resolve("./src/styles/config-table.scss"),
338338
require.resolve("./src/components/SecondNavbar/styles.module.scss"),
339+
require.resolve("./src/components/SolutionsDropdown/styles.module.css"),
339340
],
340341
},
341342
pages: {
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import React, {useState, useRef, useEffect} from 'react';
2+
import clsx from 'clsx';
3+
import {
4+
isRegexpStringMatch,
5+
useCollapsible,
6+
Collapsible,
7+
} from '@docusaurus/theme-common';
8+
import {isSamePath, useLocalPathname} from '@docusaurus/theme-common/internal';
9+
import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink';
10+
import NavbarItem, {type LinkLikeNavbarItemProps} from '@theme/NavbarItem';
11+
import type {
12+
DesktopOrMobileNavBarItemProps,
13+
Props,
14+
} from '@theme/NavbarItem/DropdownNavbarItem';
15+
import styles from './styles.module.scss';
16+
import Link from '@docusaurus/Link';
17+
18+
function isItemActive(
19+
item: LinkLikeNavbarItemProps,
20+
localPathname: string,
21+
): boolean {
22+
if (isSamePath(item.to, localPathname)) {
23+
return true;
24+
}
25+
if (isRegexpStringMatch(item.activeBaseRegex, localPathname)) {
26+
return true;
27+
}
28+
if (item.activeBasePath && localPathname.startsWith(item.activeBasePath)) {
29+
return true;
30+
}
31+
return false;
32+
}
33+
34+
function containsActiveItems(
35+
items: readonly LinkLikeNavbarItemProps[],
36+
localPathname: string,
37+
): boolean {
38+
return items.some((item) => isItemActive(item, localPathname));
39+
}
40+
41+
function DropdownNavbarItemDesktop({
42+
label,
43+
items,
44+
position,
45+
className,
46+
onClick,
47+
...props
48+
}: DesktopOrMobileNavBarItemProps) {
49+
const dropdownRef = useRef<HTMLDivElement>(null);
50+
const [showDropdown, setShowDropdown] = useState(false);
51+
52+
useEffect(() => {
53+
const handleClickOutside = (
54+
event: MouseEvent | TouchEvent | FocusEvent,
55+
) => {
56+
if (
57+
!dropdownRef.current ||
58+
dropdownRef.current.contains(event.target as Node)
59+
) {
60+
return;
61+
}
62+
setShowDropdown(false);
63+
};
64+
65+
document.addEventListener('mousedown', handleClickOutside);
66+
document.addEventListener('touchstart', handleClickOutside);
67+
document.addEventListener('focusin', handleClickOutside);
68+
69+
return () => {
70+
document.removeEventListener('mousedown', handleClickOutside);
71+
document.removeEventListener('touchstart', handleClickOutside);
72+
document.removeEventListener('focusin', handleClickOutside);
73+
};
74+
}, [dropdownRef]);
75+
76+
return (
77+
<div
78+
ref={dropdownRef}
79+
className={clsx('navbar__item', 'dropdown', 'dropdown--hoverable', {
80+
'dropdown--right': position === 'right',
81+
'dropdown--show': showDropdown,
82+
})}>
83+
<NavbarNavLink
84+
aria-haspopup="true"
85+
aria-expanded={showDropdown}
86+
role="button"
87+
label={label}
88+
// # hash permits to make the <a> tag focusable in case no link target
89+
// See https://github.com/facebook/docusaurus/pull/6003
90+
// There's probably a better solution though...
91+
href={props.to ? undefined : '#'}
92+
className={clsx('navbar__link', className)}
93+
{...props}
94+
onClick={props.to ? undefined : (e) => e.preventDefault()}
95+
onKeyDown={(e) => {
96+
if (e.key === 'Enter') {
97+
e.preventDefault();
98+
setShowDropdown(!showDropdown);
99+
}
100+
}}>
101+
{props.children ?? props.label}
102+
</NavbarNavLink>
103+
<ul className={clsx("dropdown__menu", styles.dropdown__menu)}>
104+
<></>
105+
{/* {items.map((childItemProps, i) => (
106+
<NavbarItem
107+
isDropdownItem
108+
activeClassName="dropdown__link--active"
109+
{...childItemProps}
110+
key={i}
111+
/>
112+
))} */}
113+
<div className={clsx(styles.col, styles.wrapper)}>
114+
{items.map((item, index) => (
115+
<div key={`${index}`} className={clsx(styles.cardWrapper)}>
116+
<Link className={clsx(styles.card)} to={item.href}>
117+
<div className={clsx(styles.icon)}>
118+
<img src={item.iconImage} alt={item.title} />
119+
</div>
120+
<div className={clsx(styles.title)}>{item.title}</div>
121+
</Link>
122+
</div>
123+
))}
124+
</div>
125+
</ul>
126+
</div>
127+
);
128+
}
129+
130+
function DropdownNavbarItemMobile({
131+
items,
132+
className,
133+
position, // Need to destructure position from props so that it doesn't get passed on.
134+
onClick,
135+
...props
136+
}: DesktopOrMobileNavBarItemProps) {
137+
const localPathname = useLocalPathname();
138+
const containsActive = containsActiveItems(items, localPathname);
139+
140+
const {collapsed, toggleCollapsed, setCollapsed} = useCollapsible({
141+
initialState: () => !containsActive,
142+
});
143+
144+
// Expand/collapse if any item active after a navigation
145+
useEffect(() => {
146+
if (containsActive) {
147+
setCollapsed(!containsActive);
148+
}
149+
}, [localPathname, containsActive, setCollapsed]);
150+
151+
return (
152+
<li
153+
className={clsx('menu__list-item', {
154+
'menu__list-item--collapsed': collapsed,
155+
})}>
156+
<NavbarNavLink
157+
role="button"
158+
className={clsx(
159+
styles.dropdownNavbarItemMobile,
160+
'menu__link menu__link--sublist menu__link--sublist-caret',
161+
className,
162+
)}
163+
{...props}
164+
onClick={(e) => {
165+
e.preventDefault();
166+
toggleCollapsed();
167+
}}>
168+
{props.children ?? props.label}
169+
</NavbarNavLink>
170+
<Collapsible lazy as="ul" className="menu__list" collapsed={collapsed}>
171+
<></>
172+
</Collapsible>
173+
</li>
174+
);
175+
}
176+
177+
export default function DropdownNavbarItem({
178+
mobile = false,
179+
...props
180+
}: Props): JSX.Element {
181+
const Comp = mobile ? DropdownNavbarItemMobile : DropdownNavbarItemDesktop;
182+
return <Comp {...props} />;
183+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
.dropdownNavbarItemMobile {
9+
cursor: pointer;
10+
}
11+
12+
.dropdown__menu {
13+
display: flex;
14+
width: 13.875rem;
15+
padding: 0.875rem 1.63694rem;
16+
justify-content: center;
17+
align-items: flex-start;
18+
gap: 0.98219rem;
19+
border-radius: var(--number-scales-2s-20, 1.25rem);
20+
background: #FFF;
21+
box-shadow: 0px 16px 16px 0px rgba(0, 0, 0, 0.25);
22+
}
23+
24+
25+
.wrapper {
26+
display: flex;
27+
flex-direction: column;
28+
gap: 0.6rem;
29+
}
30+
31+
.card {
32+
display: flex;
33+
align-items: center;
34+
padding: 1rem 0.8rem;
35+
text-align: left;
36+
gap: 0.5rem;
37+
border-radius: 0.72681rem;
38+
background: #F7F7F7;
39+
width: 12rem;
40+
transition: transform 0.2s, box-shadow 0.2s;
41+
42+
&:hover {
43+
transform: translateY(-5px);
44+
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
45+
text-decoration: none;
46+
color: inherit;
47+
}
48+
}
49+
50+
.icon {
51+
flex-shrink: 0;
52+
width: 1.3rem;
53+
height: 1.3rem;
54+
display: flex;
55+
justify-content: center;
56+
align-items: center;
57+
}
58+
59+
.title {
60+
flex-grow: 1;
61+
color: #1E1E1E;
62+
font-family: Manrope, sans-serif;
63+
font-size: 0.9rem;
64+
font-weight: 600;
65+
line-height: 1.5rem;
66+
letter-spacing: -0.011rem;
67+
}

0 commit comments

Comments
 (0)