Merge pull request #2 from StevanFreeborn/feat/update-nav-dropdown-on-scroll
Feat: Update Nav Dropdown on Scroll
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
}: {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export function toKebabCase(str: string): string {
|
||||
return str.replaceAll(' ', '-').toLowerCase();
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user