Skip to content

Commit 8c5bbfe

Browse files
authored
feat: DC-4631 Add new dropdown to toc section (#7037)
* Add new dropdown to toc section * Update hrefs on dropdown * Update icons and add T3 * Update link text Update dropdown order * revert deleted files * cleanup
1 parent f7c7c4c commit 8c5bbfe

File tree

13 files changed

+427
-7
lines changed

13 files changed

+427
-7
lines changed

src/components/Icon.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ import clsx from "clsx";
22
import React, { useEffect, useRef, useState } from "react";
33

44
interface IconProps {
5-
icon: string;
5+
icon?: string;
6+
customicon?: any;
67
color?: string;
78
className?: string;
89
size?: string;
910
btn?: "left" | "right";
1011
fit?: "width" | "height";
1112
}
1213

13-
export const Icon = ({ icon, color, className, size, btn, fit }: IconProps) => {
14+
export const Icon = ({ customicon, icon, color, className, size, btn, fit }: IconProps) => {
1415
const iconRef = useRef<any>(null);
1516
const [font, setFontSize] = useState<number>(0);
1617
const [measure, setMeasure] = useState<string>("vw");
@@ -34,8 +35,15 @@ export const Icon = ({ icon, color, className, size, btn, fit }: IconProps) => {
3435
window.addEventListener("resize", setFont);
3536
return () => window.removeEventListener("resize", setFont);
3637
}, []);
38+
3739
return (
38-
<i
40+
customicon ? <span
41+
style={{
42+
textAlign: "center",
43+
fontSize: size ? size : `${font}${measure}`,
44+
color: color ? color : "currentcolor",
45+
margin: btn?.length ? (btn === "right" ? "0 0 0 8px" : "0 8px 0 0") : "",
46+
}}>{customicon}</span> : <i
3947
ref={iconRef}
4048
className={clsx(icon, className)}
4149
style={{
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import useComponentVisible from "@site/src/utils/useComponentVisible";
2+
import clsx from "clsx";
3+
import { useEffect, useState } from "react";
4+
5+
import { Icon } from "../Icon";
6+
import styles from "./styles.module.scss";
7+
8+
type DropdownType = {
9+
items: Array<React.ReactNode>;
10+
anchorText: string;
11+
dark?: boolean;
12+
rightClick?: boolean;
13+
pos?: "top" | "bottom";
14+
};
15+
const Dropdown = ({
16+
anchorText,
17+
items,
18+
dark = false,
19+
rightClick = false,
20+
pos = "bottom",
21+
}: DropdownType) => {
22+
const [isOpen, openDrop] = useState<boolean>(false);
23+
const { ref, isComponentVisible, setIsComponentVisible } = useComponentVisible(true);
24+
25+
useEffect(() => {
26+
if (!isComponentVisible) openDrop(false);
27+
}, [isComponentVisible]);
28+
29+
const escEvent = (e: KeyboardEvent) => {
30+
if (e.key === "Escape") openDrop(false);
31+
};
32+
33+
useEffect(() => {
34+
window.addEventListener("keydown", escEvent);
35+
return () => window.removeEventListener("keydown", escEvent);
36+
}, []);
37+
38+
return (
39+
<div
40+
className={clsx(styles.root)}
41+
ref={ref}
42+
onClick={() => {
43+
if (!rightClick) {
44+
setIsComponentVisible(true);
45+
openDrop(!isOpen);
46+
}
47+
}}
48+
onContextMenu={(e) => {
49+
if (rightClick && window.innerWidth > 940) {
50+
e.preventDefault();
51+
setIsComponentVisible(true);
52+
openDrop(true);
53+
}
54+
}}
55+
>
56+
<div className={clsx(styles.anchor, isOpen && styles.active)}>
57+
<Icon icon="fa-regular fa-arrow-up-right" size="inherit" btn="left"/>
58+
{anchorText}
59+
<Icon icon="fa-regular fa-chevron-down" size="inherit" btn="right"/>
60+
</div>
61+
<div
62+
className={clsx(
63+
styles.overlayWrapper,
64+
isOpen && styles.showOverlay
65+
)}
66+
onClick={(e: any) => {
67+
e.stopPropagation();
68+
openDrop(false);
69+
}}
70+
>
71+
<div className={clsx(pos === "top" && styles.topPos, styles.container)}>
72+
{items.map((item: any, idx: number) => (
73+
<div key={idx} className={styles.item}>
74+
{item}
75+
</div>
76+
))}
77+
</div>
78+
</div>
79+
</div>
80+
);
81+
};
82+
83+
export default Dropdown;
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
$border-color: #2d3748;
2+
3+
.root {
4+
color: var(--primary-font-color);
5+
font-family: var(--font-family-body, Inter);
6+
font-size: var(--font-size-xs, 14px);
7+
font-style: normal;
8+
font-weight: 400;
9+
line-height: 140%; /* 19.6px */
10+
width: fit-content;
11+
position: absolute;
12+
right: 0;
13+
top: 0;
14+
}
15+
16+
.anchor {
17+
border-radius: 8px;
18+
padding: 4px 8px;
19+
cursor: pointer;
20+
width: fit-content;
21+
border: 1px solid var(--disabled-font-color);
22+
background: var(--surface-primary);
23+
&.active {
24+
background: var(--surface-brand-grey);
25+
}
26+
}
27+
28+
.overlayWrapper {
29+
opacity: 0;
30+
pointer-events: none;
31+
@media (max-width: 599px) {
32+
position: fixed;
33+
height: 100vh;
34+
z-index: 102;
35+
width: 100vw;
36+
background: rgb(9, 10, 21, 0.75);
37+
top: 0;
38+
left: 0;
39+
}
40+
.container {
41+
transform: translateY(-8px);
42+
}
43+
&.showOverlay {
44+
.container {
45+
transform: translateY(0);
46+
}
47+
}
48+
}
49+
50+
.showOverlay {
51+
opacity: 1;
52+
pointer-events: auto;
53+
}
54+
55+
.container {
56+
background: var(--surface-primary);
57+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.10), 0 2px 4px -2px rgba(0, 0, 0, 0.10);
58+
border-radius: 8px;
59+
bottom: 0;
60+
position: absolute;
61+
width: 100%;
62+
overflow: hidden;
63+
right: 0;
64+
transition: transform 300ms ease;
65+
display: flex;
66+
flex-direction: column;
67+
gap: 8px;
68+
@media (min-width: 600px) {
69+
top: 100%;
70+
margin-top: 16px;
71+
bottom: unset;
72+
width: auto;
73+
}
74+
}
75+
76+
.item {
77+
color: var(--primary-font-color);
78+
width: max-content;
79+
min-width: 100%;
80+
cursor: pointer;
81+
padding: 6px 12px;
82+
background: var(--surface-primary);
83+
transition: background 300ms ease-out;
84+
&:hover {
85+
background: var(--surface-brand-grey-strong);
86+
}
87+
}
88+
89+
.topPos {
90+
@media (min-width: 600px) {
91+
top: unset !important;
92+
left: 0 !important;
93+
transform: unset !important;
94+
bottom: 100% !important;
95+
}
96+
& > * {
97+
width: 100%;
98+
display: inline-block;
99+
}
100+
101+
a {
102+
color: inherit;
103+
}
104+
}

src/css/all.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,14 @@
844844
content: "\f09b";
845845
}
846846

847+
.fa-markdown::before {
848+
content: "\f60f";
849+
}
850+
851+
.fa-openai::before {
852+
content: "\e7cf";
853+
}
854+
847855
.fa-globe::before {
848856
content: "\f0ac";
849857
}

src/css/theming.css

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@
6969
--surface-brand-light: #d9f9f6;
7070
--primary-font-color: var(--gray-800);
7171
--surface-primary: #ffffff;
72+
--disabled-font-color: #CBD5E0;
7273
--navbar-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.1);
7374
--navbar-gradient: linear-gradient(180deg, #fff, transparent);
7475
--navbar-items-bg: #ffffffcc;
7576
--surface-secondary: #2d3748;
76-
--surface-primary: #fff;
7777
--secondary-font-color: #4a5568;
7878
--tertiary-font-color: #718096;
7979
--ifm-dropdown-background-color: #fff;
@@ -222,6 +222,7 @@ html[data-theme="dark"] {
222222
--ifm-tabs-bg-color-active: #2d3748;
223223
--main-font-color: #e2e8f0;
224224
--primary-font-color: rgb(247, 250, 252);
225+
--disabled-font-color: #4A5568;
225226
--surface-primary: var(--gray-1000);
226227
--navbar-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.5);
227228
--navbar-gradient: linear-gradient(180deg, #090a15, rgba(9, 10, 21, 0));
@@ -363,6 +364,7 @@ html[data-theme="dark"] {
363364
--navbar-items-bg: #ffffffcc;
364365
--surface-secondary: #2d3748;
365366
--surface-primary: #ffffff;
367+
--disabled-font-color: #CBD5E0;
366368
--icon-wrapper-bg: rgb(5, 7, 10);
367369
--icon-svg-color: #5a67d8;
368370
--teal-link-color: #16a394;
@@ -493,6 +495,7 @@ html[data-theme="dark"] {
493495
--ifm-tabs-bg-color-active: #2d3748;
494496
--main-font-color: #e2e8f0;
495497
--primary-font-color: rgb(247, 250, 252);
498+
--disabled-font-color: #4A5568;
496499
--surface-primary: var(--gray-1000);
497500
--navbar-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.5);
498501
--navbar-gradient: linear-gradient(180deg, #090a15, rgba(9, 10, 21, 0));

src/theme/DocBreadcrumbs/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from "react";
1+
import React from "react";
22
import clsx from "clsx";
33
import { ThemeClassNames } from "@docusaurus/theme-common";
44
import { useHomePageRoute } from "@docusaurus/theme-common/internal";

src/theme/DocItem/Layout/index.tsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React, {type ReactNode} from 'react';
2+
import clsx from 'clsx';
3+
import {useWindowSize} from '@docusaurus/theme-common';
4+
import {useDoc} from '@docusaurus/plugin-content-docs/client';
5+
import DocItemPaginator from '@theme/DocItem/Paginator';
6+
import DocVersionBanner from '@theme/DocVersionBanner';
7+
import DocVersionBadge from '@theme/DocVersionBadge';
8+
import DocItemFooter from '@theme/DocItem/Footer';
9+
import DocItemTOCMobile from '@theme/DocItem/TOC/Mobile';
10+
import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop';
11+
import DocItemContent from '@theme/DocItem/Content';
12+
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
13+
import ContentVisibility from '@theme/ContentVisibility';
14+
import type {Props} from '@theme/DocItem/Layout';
15+
16+
import styles from './styles.module.css';
17+
18+
/**
19+
* Decide if the toc should be rendered, on mobile or desktop viewports
20+
*/
21+
function useDocTOC() {
22+
const {frontMatter, toc} = useDoc();
23+
const windowSize = useWindowSize();
24+
25+
const hidden = frontMatter.hide_table_of_contents;
26+
const canRender = !hidden && toc.length > 0;
27+
28+
const mobile = canRender ? <DocItemTOCMobile /> : undefined;
29+
30+
const desktop =
31+
canRender && (windowSize === 'desktop' || windowSize === 'ssr') ? (
32+
<DocItemTOCDesktop />
33+
) : undefined;
34+
35+
return {
36+
hidden,
37+
mobile,
38+
desktop,
39+
};
40+
}
41+
42+
export default function DocItemLayout({children}: Props): ReactNode {
43+
const docTOC = useDocTOC();
44+
const {metadata} = useDoc();
45+
return (
46+
<div className="row">
47+
<div className={clsx('col', !docTOC.hidden && styles.docItemCol)}>
48+
<ContentVisibility metadata={metadata} />
49+
<DocVersionBanner />
50+
<div className={styles.docItemContainer}>
51+
<article>
52+
<DocBreadcrumbs />
53+
<DocVersionBadge />
54+
{docTOC.mobile}
55+
<DocItemContent>{children}</DocItemContent>
56+
<DocItemFooter />
57+
</article>
58+
<DocItemPaginator />
59+
</div>
60+
</div>
61+
{docTOC.desktop && <div className="col col--3">
62+
{React.cloneElement(docTOC.desktop as React.ReactElement, {
63+
//@ts-ignore
64+
metadata,
65+
})}
66+
</div>}
67+
</div>
68+
);
69+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.docItemContainer header + *,
2+
.docItemContainer article > *:first-child {
3+
margin-top: 0;
4+
}
5+
6+
@media (min-width: 997px) {
7+
.docItemCol {
8+
max-width: 75% !important;
9+
}
10+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React, {type ReactNode} from 'react';
2+
import {ThemeClassNames} from '@docusaurus/theme-common';
3+
import {useDoc} from '@docusaurus/plugin-content-docs/client';
4+
import TOC from '@site/src/theme/TOC';
5+
6+
export default function DocItemTOCDesktop({metadata}: any): ReactNode {
7+
const {toc, frontMatter} = useDoc();
8+
return (
9+
<TOC
10+
toc={toc}
11+
minHeadingLevel={frontMatter.toc_min_heading_level}
12+
maxHeadingLevel={frontMatter.toc_max_heading_level}
13+
className={ThemeClassNames.docs.docTocDesktop}
14+
metadata={metadata}
15+
/>
16+
);
17+
}

0 commit comments

Comments
 (0)