Merge pull request #2 from StevanFreeborn/feat/update-nav-dropdown-on-scroll

Feat: Update Nav Dropdown on Scroll
This commit is contained in:
Stevan Freeborn
2023-04-28 07:59:33 -05:00
committed by GitHub
12 changed files with 168 additions and 93 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
'use client'; 'use client';
import { Fragment } from 'react'; import { Fragment } from 'react';
import { themes, useThemeContext } from '../theme'; import { themes, useThemeContext } from '../context/theme';
import { DocSection } from '../types/types'; import { DocSection } from '../types/types';
import styles from './Main.module.css'; import styles from './Main.module.css';
import Section from './Section'; import Section from './Section';
-22
View File
@@ -77,28 +77,6 @@
} }
} }
.dropdownSelect {
color: #64adac;
background-color: inherit;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1rem;
font-weight: bold;
padding: 0 1rem;
}
.dropdownSelect:focus-visible {
outline: none;
}
.selectItem {
color: #64adac;
cursor: pointer;
font-weight: bold;
margin: 1rem;
}
.navbarToggle { .navbarToggle {
cursor: pointer; cursor: pointer;
display: none; display: none;
+4 -58
View File
@@ -1,10 +1,11 @@
'use client'; 'use client';
import Link from 'next/link'; import Link from 'next/link';
import { Fragment, useState } from 'react'; import { useState } from 'react';
import { themes, useThemeContext } from '../theme'; import { themes, useThemeContext } from '../context/theme';
import { Doc, DocsStructure } from '../types/types'; import { DocsStructure } from '../types/types';
import styles from './NavBar.module.css'; import styles from './NavBar.module.css';
import SectionDropdown from './SectionDropdown';
function VersionsDropdown({ function VersionsDropdown({
version, version,
@@ -94,61 +95,6 @@ function VersionsDropdown({
); );
} }
function SectionDropdownGroup({ doc }: { doc: Doc }) {
return (
<Fragment>
{doc.copy && doc.example && (
<option
className={styles.selectItem}
label={doc.title}
>
{doc.title}
</option>
)}
{doc.children && doc.children.length > 0 && (
<optgroup label={doc.title}>
{doc.children.map(child => (
<option
className={styles.selectItem}
label={child.title}
key={child.title}
>
{child.title}
</option>
))}
</optgroup>
)}
</Fragment>
);
}
function SectionDropdown({
version,
}: {
version: DocsStructure;
}) {
const handleSelectChange = (e: any) => {
const selectedOption = e.target.value;
location.hash = selectedOption
.replaceAll(' ', '-')
.toLowerCase();
};
return (
<select
onChange={handleSelectChange}
title="navigation dropdown"
className={styles.dropdownSelect}
>
{version.docs.map(doc => {
return (
<SectionDropdownGroup doc={doc} key={doc.title} />
);
})}
</select>
);
}
export default function NavBar({ export default function NavBar({
version, version,
}: { }: {
+38 -1
View File
@@ -1,6 +1,8 @@
'use client'; 'use client';
import { themes, useThemeContext } from '../theme'; import { useEffect } from 'react';
import { useSectionContext } from '../context/section';
import { themes, useThemeContext } from '../context/theme';
import { DocSection, DocsStructure } from '../types/types'; import { DocSection, DocsStructure } from '../types/types';
import Main from './Main'; import Main from './Main';
import NavBar from './NavBar'; import NavBar from './NavBar';
@@ -17,6 +19,41 @@ export default function Page({
const { theme } = useThemeContext(); const { theme } = useThemeContext();
const themeStyles = themes[theme]; const themeStyles = themes[theme];
const { setSection } = useSectionContext();
useEffect(() => {
const titles =
document.querySelectorAll<HTMLHeadingElement>(
'article:nth-child(1) > h1'
);
const observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
setSection(entry.target.id);
history.pushState(
null,
'',
`#${entry.target.id}`
);
}
});
},
{
root: null,
rootMargin: '-10% 0% -70% 0%',
threshold: 1,
}
);
titles.forEach(title => observer.observe(title));
return () => {
titles.forEach(title => observer.unobserve(title));
};
}, [setSection]);
return ( return (
<div className={styles.wrapper} style={themeStyles}> <div className={styles.wrapper} style={themeStyles}>
<SideBar version={version} /> <SideBar version={version} />
@@ -0,0 +1,21 @@
.dropdownSelect {
color: #64adac;
background-color: inherit;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1rem;
font-weight: bold;
padding: 0 1rem;
}
.dropdownSelect:focus-visible {
outline: none;
}
.selectItem {
color: #64adac;
cursor: pointer;
font-weight: bold;
margin: 1rem;
}
+61
View File
@@ -0,0 +1,61 @@
import { useSectionContext } from '../context/section';
import { Doc, DocsStructure } from '../types/types';
import { toKebabCase } from '../utils/stringUtils';
import styles from './SectionDropdown.module.css';
export default function SectionDropdown({
version,
}: {
version: DocsStructure;
}) {
const { section, setSection } = useSectionContext();
const handleSelectChange = (e: any) => {
setSection(e.target.value);
location.hash = e.target.value;
};
const getAllDocs = (docs: Doc[]) => {
let result: Doc[] = [];
docs.forEach(doc => {
result.push(doc);
if (doc.children && doc.children.length > 0) {
result = result.concat(getAllDocs(doc.children));
}
});
return result;
};
const docs = getAllDocs(version.docs);
return (
<select
onChange={handleSelectChange}
title="navigation dropdown"
className={styles.dropdownSelect}
value={section}
>
{docs.map(doc => {
if (
doc.copy === undefined ||
doc.example === undefined
) {
return null;
}
return (
<option
className={styles.selectItem}
label={doc.title}
value={toKebabCase(doc.title)}
key={doc.title}
>
{doc.title}
</option>
);
})}
</select>
);
}
+5 -8
View File
@@ -2,8 +2,9 @@
import Link from 'next/link.js'; import Link from 'next/link.js';
import { useState } from 'react'; import { useState } from 'react';
import { useThemeContext } from '../theme'; import { useThemeContext } from '../context/theme';
import { Doc, DocsStructure } from '../types/types.js'; import { Doc, DocsStructure } from '../types/types';
import { toKebabCase } from '../utils/stringUtils';
import styles from './SideBar.module.css'; import styles from './SideBar.module.css';
function TreeNode({ doc }: { doc: Doc }) { function TreeNode({ doc }: { doc: Doc }) {
@@ -19,9 +20,7 @@ function TreeNode({ doc }: { doc: Doc }) {
<div className={styles.expandable}> <div className={styles.expandable}>
{doc.copy ? ( {doc.copy ? (
<a <a
href={`#${doc.title href={`#${toKebabCase(doc.title)}`}
.replaceAll(' ', '-')
.toLowerCase()}`}
className={styles.link} className={styles.link}
> >
{doc.title} {doc.title}
@@ -66,9 +65,7 @@ function TreeNode({ doc }: { doc: Doc }) {
</div> </div>
) : doc.copy ? ( ) : doc.copy ? (
<a <a
href={`#${doc.title href={`#${toKebabCase(doc.title)}`}
.replaceAll(' ', '-')
.toLowerCase()}`}
className={styles.link} className={styles.link}
> >
{doc.title} {doc.title}
+29
View File
@@ -0,0 +1,29 @@
'use client';
import {
ReactNode,
createContext,
useContext,
useState,
} from 'react';
const SectionContext = createContext({});
export const SectionContextProvider = ({
children,
}: {
children: ReactNode;
}) => {
const [section, setSection] = useState('introduction');
return (
<SectionContext.Provider
value={{ section, setSection }}
>
{children}
</SectionContext.Provider>
);
};
export const useSectionContext: any = () =>
useContext(SectionContext);
+5 -2
View File
@@ -1,7 +1,8 @@
import { Analytics } from '@vercel/analytics/react'; import { Analytics } from '@vercel/analytics/react';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { SectionContextProvider } from './context/section';
import { ThemeContextProvider } from './context/theme';
import './globals.css'; import './globals.css';
import { ThemeContextProvider } from './theme';
export const metadata = { export const metadata = {
title: 'Onspring API Docs', title: 'Onspring API Docs',
@@ -30,7 +31,9 @@ export default function RootLayout({
<html lang="en"> <html lang="en">
<body> <body>
<ThemeContextProvider> <ThemeContextProvider>
{children} <SectionContextProvider>
{children}
</SectionContextProvider>
</ThemeContextProvider> </ThemeContextProvider>
<Analytics /> <Analytics />
</body> </body>
+3
View File
@@ -0,0 +1,3 @@
export function toKebabCase(str: string): string {
return str.replaceAll(' ', '-').toLowerCase();
}
+1 -1
View File
@@ -1,4 +1,4 @@
# Authentication {% #authentication %} # Authentication {% #authentication .section-title %}
The Onpsring API uses API keys to authenticate requests. These keys are specific to a given Onspring instance. You can view and manage your API keys from within your instance. The Onpsring API uses API keys to authenticate requests. These keys are specific to a given Onspring instance. You can view and manage your API keys from within your instance.