diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..429e43e56 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,126 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository Overview +This is the Optimism Documentation website repository that powers docs.optimism.io - the official technical documentation for the Optimism Collective, covering the OP Stack, Superchain, and interoperability features. + +## Tech Stack +- **Framework**: Next.js 14.2.21 with React 18.2.0 +- **Documentation Engine**: Nextra 2.13.2 (docs theme) +- **Language**: TypeScript +- **Package Manager**: pnpm (required - do not use npm or yarn) +- **Content Format**: MDX (Markdown with React components) +- **Deployment**: Netlify + +## Essential Commands + +### Development +```bash +pnpm dev # Start development server at localhost:3001 +pnpm build # Create production build +``` + +### Quality Checks (Run before committing) +```bash +pnpm lint # Run all linting (ESLint + spellcheck + breadcrumbs + redirects + metadata) +pnpm fix # Auto-fix all fixable issues (runs on pre-push automatically) +``` + +### Individual Linting Commands +```bash +pnpm spellcheck:lint # Check spelling +pnpm spellcheck:fix # Add words to dictionary (words.txt) +pnpm lint:eslint # Run ESLint +pnpm lint:breadcrumbs # Validate breadcrumb structure +pnpm lint:redirects # Check redirect configuration +pnpm lint:metadata # Validate page metadata +``` + +## Architecture & Structure + +### Content Organization +``` +pages/ # All documentation content (MDX files) +├── app-developers/ # Application developer guides +├── operators/ # Node & chain operator documentation +├── stack/ # OP Stack protocol documentation +├── superchain/ # Superchain network documentation +├── interop/ # Interoperability documentation +└── connect/ # Contributing guides and resources +``` + +### Key Directories +- `components/`: Reusable React components for documentation +- `public/`: Static assets, images, and tutorial files +- `utils/`: Utility scripts for linting, validation, and build processes +- `providers/`: React context providers for global state + +## Important Patterns + +### MDX Page Structure +All documentation pages use MDX format with frontmatter metadata: +```mdx +--- +title: Page Title +lang: en-US +description: Page description for SEO +--- + +import { ComponentName } from '@/components/ComponentName' + +# Content here... +``` + +### Component Imports +Use the configured path alias for component imports: +```typescript +import { ComponentName } from '@/components/ComponentName' +``` + +### Adding New Documentation +1. Create MDX file in appropriate `pages/` subdirectory +2. Include required frontmatter (title, lang, description) +3. Run `pnpm lint` to validate metadata and content +4. Use existing components from `components/` directory when possible + +### Spell Checking +- Custom dictionary maintained in `words.txt` +- Add technical terms using `pnpm spellcheck:fix` +- Spell checking runs automatically in the lint pipeline + +## Git Workflow +- **Pre-push hook**: Automatically runs `pnpm fix` via Husky +- **Auto-commit**: Fixes are automatically committed if changes are made +- **No pre-commit hooks**: Only validation on push + +## Special Features +- **Kapa.ai Widget**: AI assistant integrated for documentation queries +- **Algolia Search**: Full-text search across documentation +- **Feelback**: User feedback collection system +- **Growth Book**: A/B testing framework for feature experiments + +## Common Tasks + +### Adding a New Page +1. Create `.mdx` file in appropriate `pages/` directory +2. Add frontmatter with title, lang, and description +3. Write content using Markdown and import React components as needed +4. Run `pnpm dev` to preview +5. Run `pnpm lint` before committing + +### Updating Components +- Components are in `components/` directory +- Follow existing patterns and TypeScript types +- Test component changes across multiple pages that use them + +### Working with Images +- Place images in `public/img/` directory +- Reference using `/img/filename.ext` in MDX files +- Optimize images before adding to repository + +## Notes +- The repository uses automated quality checks - always run `pnpm lint` before pushing +- Netlify handles deployment automatically on merge to main +- TypeScript is configured with relaxed strict mode - follow existing patterns +- MDX allows mixing Markdown with React components - leverage this for interactive content \ No newline at end of file diff --git a/components/AskAIButton.tsx b/components/AskAIButton.tsx index 344e20d17..1a6b13c0d 100644 --- a/components/AskAIButton.tsx +++ b/components/AskAIButton.tsx @@ -2,7 +2,13 @@ import { RiSparkling2Fill } from '@remixicon/react'; import { useFeature } from '@growthbook/growthbook-react'; import { useEffect, useState } from 'react'; -const AskAIButton = () => { +interface AskAIButtonProps { + fullWidth?: boolean; + large?: boolean; + id?: string; +} + +const AskAIButton = ({ fullWidth = false, large = false, id = 'custom-ask-ai-button' }: AskAIButtonProps) => { const [mounted, setMounted] = useState(false); const enableDocsAIWidget = useFeature('enable_docs_ai_widget').on; @@ -19,10 +25,17 @@ const AskAIButton = () => { return null; } + const baseClasses = 'nx-flex nx-gap-2 nx-items-center nx-rounded-lg nx-font-semibold nx-justify-center'; + const sizeClasses = large + ? 'nx-py-3 nx-px-6 nx-text-base' + : 'nx-py-1.5 nx-px-3 nx-text-sm'; + const widthClasses = fullWidth ? 'nx-w-full' : ''; + const iconSize = large ? 16 : 14; + return ( ); }; diff --git a/components/CustomHeader/index.tsx b/components/CustomHeader/index.tsx new file mode 100644 index 000000000..997853ba0 --- /dev/null +++ b/components/CustomHeader/index.tsx @@ -0,0 +1,273 @@ +/** @format */ + +import React, { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { Search } from '../Search'; +import { AskAIButton } from '../AskAIButton'; +import { useTheme } from 'nextra-theme-docs'; +import { MoonIcon, SunIcon, Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'; + +const CustomHeader = () => { + const router = useRouter(); + const { theme, setTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const [isDesktop, setIsDesktop] = useState(false); + const [isNavigating, setIsNavigating] = useState(false); + + useEffect(() => { + setMounted(true); + + const checkScreenSize = () => { + setIsDesktop(window.innerWidth >= 768); + }; + + checkScreenSize(); + window.addEventListener('resize', checkScreenSize); + + return () => window.removeEventListener('resize', checkScreenSize); + }, []); + + useEffect(() => { + const handleStart = () => setIsNavigating(true); + const handleComplete = () => setIsNavigating(false); + + router.events.on('routeChangeStart', handleStart); + router.events.on('routeChangeComplete', handleComplete); + router.events.on('routeChangeError', handleComplete); + + return () => { + router.events.off('routeChangeStart', handleStart); + router.events.off('routeChangeComplete', handleComplete); + router.events.off('routeChangeError', handleComplete); + }; + }, [router]); + + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + setIsMobileMenuOpen(false); + } + }; + + if (isMobileMenuOpen) { + document.addEventListener('keydown', handleEscape); + document.body.style.overflow = 'hidden'; + return () => { + document.removeEventListener('keydown', handleEscape); + document.body.style.overflow = ''; + }; + } else { + document.body.style.overflow = ''; + } + }, [isMobileMenuOpen]); + + const navItems = [ + { title: 'Get started', href: '/get-started/superchain' }, + { title: 'Superchain', href: '/superchain/superchain-explainer' }, + { title: 'Interoperability', href: '/interop/get-started' }, + { title: 'App Devs', href: '/app-developers/get-started' }, + { title: 'Operators', href: '/operators/chain-operators/architecture' }, + { title: 'OP Stack', href: '/stack/getting-started' } + ]; + + const isActive = (href: string) => { + return router.pathname.startsWith(href); + }; + + const handleMouseEnter = (href: string) => { + router.prefetch(href); + }; + + if (!mounted) return null; + + return ( +
+ {/* Top Section: Logo, Search, Actions */} +
+ {/* Logo */} + + + + + + + + {/* Search Bar - hidden on mobile, shown on larger screens */} + {isDesktop && ( +
+ +
+ )} + + {/* Right side: Ask AI + Theme Toggle + Mobile Menu Button */} +
+ +
+ +
+ + {/* Mobile menu button - only visible on mobile */} + {!isDesktop && ( + + )} +
+
+ + {/* Mobile search bar - only visible on mobile */} + {!isDesktop && ( +
+ +
+ )} + + {/* Desktop Navigation */} + {isDesktop && ( + + )} + + {/* Mobile Full-Screen Menu Overlay */} + {!isDesktop && ( +
+ {/* Mobile Menu Header */} +
+ setIsMobileMenuOpen(false)} + className='nx-flex nx-items-center' + > + + + + + + +
+ + {/* Mobile Menu Content - Scrollable */} +
+ {/* Navigation Links */} + + + {/* Mobile Actions - Bottom of menu */} +
+
+ +
+
+
+
+ )} +
+ ); +}; + +export default CustomHeader; diff --git a/components/LoadingBar/index.tsx b/components/LoadingBar/index.tsx new file mode 100644 index 000000000..33de15ac7 --- /dev/null +++ b/components/LoadingBar/index.tsx @@ -0,0 +1,55 @@ +import React, { useState, useEffect } from 'react'; +import { useRouter } from 'next/router'; + +const LoadingBar = () => { + const [loading, setLoading] = useState(false); + const [progress, setProgress] = useState(0); + const router = useRouter(); + + useEffect(() => { + let progressTimer: NodeJS.Timeout; + let finishTimer: NodeJS.Timeout; + + const handleStart = () => { + setLoading(true); + setProgress(0); + + progressTimer = setTimeout(() => { + setProgress(70); + }, 200); + }; + + const handleComplete = () => { + setProgress(100); + finishTimer = setTimeout(() => { + setLoading(false); + setProgress(0); + }, 300); + }; + + router.events.on('routeChangeStart', handleStart); + router.events.on('routeChangeComplete', handleComplete); + router.events.on('routeChangeError', handleComplete); + + return () => { + router.events.off('routeChangeStart', handleStart); + router.events.off('routeChangeComplete', handleComplete); + router.events.off('routeChangeError', handleComplete); + clearTimeout(progressTimer); + clearTimeout(finishTimer); + }; + }, [router]); + + if (!loading) return null; + + return ( +
+
+
+ ); +}; + +export default LoadingBar; \ No newline at end of file diff --git a/components/Search/docsearch.tsx b/components/Search/docsearch.tsx index 902a5c34c..1687b6808 100644 --- a/components/Search/docsearch.tsx +++ b/components/Search/docsearch.tsx @@ -1,26 +1,16 @@ -import { Transition } from "@headlessui/react"; -import cn from "clsx"; -import { useRouter } from "next/router"; -import { useMounted } from "nextra/hooks"; -import { InformationCircleIcon, SpinnerIcon } from "nextra/icons"; -import type { - ReactNode, - CompositionEvent, - KeyboardEvent, - ReactElement, -} from "react"; -import { - Fragment, - useCallback, - useEffect, - useRef, - useState, - useContext, -} from "react"; -import { Input } from "./input"; -import Link from "next/link"; -import * as aa from "search-insights"; -import AlgoliaContext from "@/utils/contexts/AlgoliaContext"; +/** @format */ + +import { Transition } from '@headlessui/react'; +import cn from 'clsx'; +import { useRouter } from 'next/router'; +import { useMounted } from 'nextra/hooks'; +import { InformationCircleIcon, SpinnerIcon } from 'nextra/icons'; +import type { ReactNode, CompositionEvent, KeyboardEvent, ReactElement } from 'react'; +import { Fragment, useCallback, useEffect, useRef, useState, useContext } from 'react'; +import { Input } from './input'; +import Link from 'next/link'; +import * as aa from 'search-insights'; +import AlgoliaContext from '@/utils/contexts/AlgoliaContext'; type SearchResult = { children: ReactNode; @@ -40,7 +30,7 @@ type SearchProps = { results: SearchResult[]; }; -const INPUTS = ["input", "select", "button", "textarea"]; +const INPUTS = ['input', 'select', 'button', 'textarea']; export function DocSearch({ className, @@ -50,7 +40,7 @@ export function DocSearch({ onActive, loading, error, - results, + results }: SearchProps): ReactElement { const [show, setShow] = useState(false); const [active, setActive] = useState(0); @@ -78,22 +68,21 @@ export function DocSearch({ ) return; if ( - e.key === "/" || - (e.key === "k" && - (e.metaKey /* for Mac */ || /* for non-Mac */ e.ctrlKey)) + e.key === '/' || + (e.key === 'k' && (e.metaKey /* for Mac */ || /* for non-Mac */ e.ctrlKey)) ) { e.preventDefault(); // prevent scrolling to the top input.current.focus({ preventScroll: true }); - } else if (e.key === "Escape") { + } else if (e.key === 'Escape') { setShow(false); input.current.blur(); } }; - window.addEventListener("keydown", down); + window.addEventListener('keydown', down); return () => { - window.removeEventListener("keydown", down); + window.removeEventListener('keydown', down); }; }, []); @@ -102,31 +91,28 @@ export function DocSearch({ const result = results[i]; - aa.default("clickedObjectIDsAfterSearch", { - index: "docs", - eventName: "Search Option Clicked", + aa.default('clickedObjectIDsAfterSearch', { + index: 'docs', + eventName: 'Search Option Clicked', queryID: queryID, objectIDs: [result.id], - positions: [results.indexOf(result) + 1], + positions: [results.indexOf(result) + 1] }); setObjectID(result.id); - onChange(""); + onChange(''); setShow(false); }; - const handleActive = useCallback( - (e: { currentTarget: { dataset: DOMStringMap } }) => { - const { index } = e.currentTarget.dataset; - setActive(Number(index)); - }, - [] - ); + const handleActive = useCallback((e: { currentTarget: { dataset: DOMStringMap } }) => { + const { index } = e.currentTarget.dataset; + setActive(Number(index)); + }, []); const handleKeyDown = useCallback( function (e: KeyboardEvent) { switch (e.key) { - case "ArrowDown": { + case 'ArrowDown': { if (active + 1 < results.length) { const el = ulRef.current?.querySelector( `li:nth-of-type(${active + 2}) > a` @@ -139,7 +125,7 @@ export function DocSearch({ } break; } - case "ArrowUp": { + case 'ArrowUp': { if (active - 1 >= 0) { const el = ulRef.current?.querySelector( `li:nth-of-type(${active}) > a` @@ -152,7 +138,7 @@ export function DocSearch({ } break; } - case "Enter": { + case 'Enter': { const result = results[active]; if (result && composition) { void router.push(result.route); @@ -160,7 +146,7 @@ export function DocSearch({ } break; } - case "Escape": { + case 'Escape': { setShow(false); input.current?.blur(); break; @@ -177,57 +163,49 @@ export function DocSearch({ { - onChange(""); + onChange(''); }} > {value && focused - ? "ESC" + ? 'ESC' : mounted && - (navigator.userAgent.includes("Macintosh") ? ( + (navigator.userAgent.includes('Macintosh') ? ( <> - K + K ) : ( - "CTRL K" + 'CTRL K' ))} ); - const handleComposition = useCallback( - (e: CompositionEvent) => { - setComposition(e.type === "compositionend"); - }, - [] - ); + const handleComposition = useCallback((e: CompositionEvent) => { + setComposition(e.type === 'compositionend'); + }, []); return ( -
- {renderList && ( -
setShow(false)} - /> - )} +
+ {renderList &&
setShow(false)} />} @@ -256,41 +234,47 @@ export function DocSearch({ show={renderList} // Transition.Child is required here, otherwise popup will be still present in DOM after focus out as={Transition.Child} - leave="nx-transition-opacity nx-duration-100" - leaveFrom="nx-opacity-100" - leaveTo="nx-opacity-0" + leave='nx-transition-opacity nx-duration-100' + leaveFrom='nx-opacity-100' + leaveTo='nx-opacity-0' >
    {error ? ( - - - {"Error"} + + + {'Error'} ) : loading ? ( - - - {"Loading"} + + + {'Loading'} ) : results.length > 0 ? ( results.map(({ route, prefix, children, id }, i) => ( @@ -298,15 +282,15 @@ export function DocSearch({ {prefix}
  • )) ) : ( - + No results found. )}
- Algolia logo + Algolia logo
diff --git a/components/Search/index.tsx b/components/Search/index.tsx index f1442cf1a..230e1a1cb 100644 --- a/components/Search/index.tsx +++ b/components/Search/index.tsx @@ -1,18 +1,20 @@ -import type { Item as NormalItem } from "nextra/normalize-pages"; -import type { ReactElement } from "react"; -import { useContext, useEffect, useState } from "react"; -import { HighlightMatches } from "./highlight-matches"; -import { DocSearch } from "./docsearch"; -import algoliasearch from "algoliasearch"; -import AlgoliaContext from "@/utils/contexts/AlgoliaContext"; +/** @format */ + +import type { Item as NormalItem } from 'nextra/normalize-pages'; +import type { ReactElement } from 'react'; +import { useContext, useEffect, useState } from 'react'; +import { HighlightMatches } from './highlight-matches'; +import { DocSearch } from './docsearch'; +import algoliasearch from 'algoliasearch'; +import AlgoliaContext from '@/utils/contexts/AlgoliaContext'; // Using environment variables for Algolia configuration const client = algoliasearch( - process.env.NEXT_PUBLIC_ALGOLIA_APPLICATION_ID || "", - process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY || "" + process.env.NEXT_PUBLIC_ALGOLIA_APPLICATION_ID || '', + process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY || '' ); -const index = client.initIndex(process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME || "docs"); +const index = client.initIndex(process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME || 'docs'); type AlgoliaHits = { hits: AlgoliaHit[]; @@ -28,13 +30,13 @@ export type AlgoliaHit = { export function Search({ className, - directories, + directories }: Readonly<{ className?: string; directories: NormalItem[]; }>): ReactElement { // Rest of your code remains the same - const [search, setSearch] = useState(""); + const [search, setSearch] = useState(''); const [results, setResults] = useState([]); const { queryID, setQueryID } = useContext(AlgoliaContext); @@ -42,7 +44,7 @@ export function Search({ useEffect(() => { async function fetchData() { const hits: AlgoliaHits = await index.search(search, { - clickAnalytics: true, + clickAnalytics: true }); setQueryID(hits.queryID); @@ -53,11 +55,11 @@ export function Search({ children: ( <> -
+
- ), + ) })); setResults(mappedHits); } @@ -70,8 +72,8 @@ export function Search({ value={search} onChange={setSearch} className={className} - overlayClassName="nx-w-screen nx-min-h-[100px] nx-max-w-[min(calc(100vw-2rem),calc(100%+20rem))]" + overlayClassName='nx-min-h-[100px] nx-w-[600px] nx-max-w-[calc(100vw-2rem)] nx-left-1/2 -nx-translate-x-1/2' results={results} /> ); -} \ No newline at end of file +} diff --git a/package.json b/package.json index 97163bd87..9ad08ac64 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@feelback/react": "^0.3.4", "@growthbook/growthbook-react": "^1.3.1", "@headlessui/react": "^2.1.8", + "@heroicons/react": "^2.2.0", "@remixicon/react": "^4.6.0", "algoliasearch": "^4.23.3", "clsx": "^2.1.1", diff --git a/pages/_app.tsx b/pages/_app.tsx index b32bfeab1..8ee602e60 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -6,6 +6,7 @@ import * as gtag from '../utils/gtag'; import * as aa from 'search-insights'; import AlgoliaContext from '@/utils/contexts/AlgoliaContext'; import ScrollDispatcher from '@/components/ScrollDispatcher'; +import LoadingBar from '@/components/LoadingBar'; import { CustomGrowthBookProvider } from '../providers/GrowthbookProvider'; export default function App({ Component, pageProps }) { @@ -31,6 +32,7 @@ export default function App({ Component, pageProps }) { return ( + = 16 || ^19.0.0-rc' + '@humanwhocodes/config-array@0.11.13': resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} engines: {node: '>=10.10.0'} @@ -4718,6 +4726,10 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + '@heroicons/react@2.2.0(react@18.2.0)': + dependencies: + react: 18.2.0 + '@humanwhocodes/config-array@0.11.13': dependencies: '@humanwhocodes/object-schema': 2.0.1 @@ -7617,7 +7629,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@2.13.2(next@14.2.21(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(nextra@2.13.2(patch_hash=a4rp2hgojklggjmthmkiyqaek4)(next@14.2.21(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + nextra-theme-docs@2.13.2(next@14.2.21(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(nextra@2.13.2(patch_hash=81936321c37741ec218dc19817c4a4939f4655b8371e793561fc236bebccc249)(next@14.2.21(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@headlessui/react': 1.7.17(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@popperjs/core': 2.11.8 @@ -7631,13 +7643,13 @@ snapshots: next: 14.2.21(react-dom@18.2.0(react@18.2.0))(react@18.2.0) next-seo: 6.4.0(next@14.2.21(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) next-themes: 0.2.1(next@14.2.21(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - nextra: 2.13.2(patch_hash=a4rp2hgojklggjmthmkiyqaek4)(next@14.2.21(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + nextra: 2.13.2(patch_hash=81936321c37741ec218dc19817c4a4939f4655b8371e793561fc236bebccc249)(next@14.2.21(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) scroll-into-view-if-needed: 3.1.0 zod: 3.22.4 - nextra@2.13.2(patch_hash=a4rp2hgojklggjmthmkiyqaek4)(next@14.2.21(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + nextra@2.13.2(patch_hash=81936321c37741ec218dc19817c4a4939f4655b8371e793561fc236bebccc249)(next@14.2.21(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@headlessui/react': 1.7.17(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@mdx-js/mdx': 2.3.0 @@ -7959,7 +7971,7 @@ snapshots: hast-util-raw: 9.0.1 vfile: 6.0.1 - remark-code-import@1.2.0(patch_hash=heylvfasxh3ubj2edns2svea2m): + remark-code-import@1.2.0(patch_hash=f6b78667b2fd0da0247b6e898a35f71bfde2a83adfa62fa6017a02dc7fb5d436): dependencies: strip-indent: 4.0.0 to-gatsby-remark-plugin: 0.1.0 @@ -8032,7 +8044,7 @@ snapshots: unified: 10.1.2 unified-lint-rule: 2.1.2 - remark-lint-frontmatter-schema@3.15.4(patch_hash=jaxvkozlhcbn7zjsiti5ocoubi): + remark-lint-frontmatter-schema@3.15.4(patch_hash=32c1574b8fd989888047ea0226d42029f451eb7c875349c28b6259a73cc7e59f): dependencies: '@apidevtools/json-schema-ref-parser': 11.1.0 ajv: 8.12.0 diff --git a/theme.config.tsx b/theme.config.tsx index 143030bac..a3798144e 100644 --- a/theme.config.tsx +++ b/theme.config.tsx @@ -9,29 +9,13 @@ import '@feelback/react/styles/feelback.css'; import { Search } from './components/Search'; import { Footer } from './components/Footer'; import { AskAIButton } from './components/AskAIButton'; +import CustomHeader from './components/CustomHeader'; import { useFeature } from '@growthbook/growthbook-react'; const config: DocsThemeConfig = { - logo: ( - <> - - - - - - ), + navbar: { + component: CustomHeader + }, darkMode: true, // banner: { // key: 'viem/op-stack', @@ -42,12 +26,6 @@ const config: DocsThemeConfig = { // // ) // }, - search: { - component: Search - }, - navbar: { - extraContent: AskAIButton - }, docsRepositoryBase: 'https://github.com/ethereum-optimism/docs/blob/main/', footer: { text: