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';
import { Fragment } from 'react';
import { themes, useThemeContext } from '../theme';
import { themes, useThemeContext } from '../context/theme';
import { DocSection } from '../types/types';
import styles from './Main.module.css';
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 {
cursor: pointer;
display: none;
+4 -58
View File
@@ -1,10 +1,11 @@
'use client';
import Link from 'next/link';
import { Fragment, useState } from 'react';
import { themes, useThemeContext } from '../theme';
import { Doc, DocsStructure } from '../types/types';
import { useState } from 'react';
import { themes, useThemeContext } from '../context/theme';
import { DocsStructure } from '../types/types';
import styles from './NavBar.module.css';
import SectionDropdown from './SectionDropdown';
function VersionsDropdown({
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({
version,
}: {
+38 -1
View File
@@ -1,6 +1,8 @@
'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 Main from './Main';
import NavBar from './NavBar';
@@ -17,6 +19,41 @@ export default function Page({
const { theme } = useThemeContext();
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 (
<div className={styles.wrapper} style={themeStyles}>
<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 { useState } from 'react';
import { useThemeContext } from '../theme';
import { Doc, DocsStructure } from '../types/types.js';
import { useThemeContext } from '../context/theme';
import { Doc, DocsStructure } from '../types/types';
import { toKebabCase } from '../utils/stringUtils';
import styles from './SideBar.module.css';
function TreeNode({ doc }: { doc: Doc }) {
@@ -19,9 +20,7 @@ function TreeNode({ doc }: { doc: Doc }) {
<div className={styles.expandable}>
{doc.copy ? (
<a
href={`#${doc.title
.replaceAll(' ', '-')
.toLowerCase()}`}
href={`#${toKebabCase(doc.title)}`}
className={styles.link}
>
{doc.title}
@@ -66,9 +65,7 @@ function TreeNode({ doc }: { doc: Doc }) {
</div>
) : doc.copy ? (
<a
href={`#${doc.title
.replaceAll(' ', '-')
.toLowerCase()}`}
href={`#${toKebabCase(doc.title)}`}
className={styles.link}
>
{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 { ReactNode } from 'react';
import { SectionContextProvider } from './context/section';
import { ThemeContextProvider } from './context/theme';
import './globals.css';
import { ThemeContextProvider } from './theme';
export const metadata = {
title: 'Onspring API Docs',
@@ -30,7 +31,9 @@ export default function RootLayout({
<html lang="en">
<body>
<ThemeContextProvider>
{children}
<SectionContextProvider>
{children}
</SectionContextProvider>
</ThemeContextProvider>
<Analytics />
</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.