diff --git a/.pnp.cjs b/.pnp.cjs index 3d8665886f..faf53733a5 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -4412,6 +4412,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["eslint", "npm:8.26.0"],\ ["instantsearch.js", "virtual:78e90867e46510db2994cf6ed805ab4ba701c55b6d7a2c33f6cc04865dc082c172bef019989477dda8bc6824512d13cb3bcb74ef12bd113a2f6bdcd291ad70f1#npm:4.56.3"],\ ["jest", "virtual:bcd55ef18af2de5c61a0e7e421459a5298122a73e58d78d0d17e7844bafe08261ee360689337ea7f273d858b0bea9e1ae69c5bf08a019e466a6e255cefe36bc1#npm:29.7.0"],\ + ["jotai", "virtual:78e90867e46510db2994cf6ed805ab4ba701c55b6d7a2c33f6cc04865dc082c172bef019989477dda8bc6824512d13cb3bcb74ef12bd113a2f6bdcd291ad70f1#npm:2.6.0"],\ ["lodash-es", "npm:4.17.21"],\ ["lru-cache", "npm:10.0.1"],\ ["marked", "npm:5.1.2"],\ @@ -13410,6 +13411,28 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["jotai", [\ + ["npm:2.6.0", {\ + "packageLocation": "./.yarn/cache/jotai-npm-2.6.0-dc8e6aef45-6e9ee39770.zip/node_modules/jotai/",\ + "packageDependencies": [\ + ["jotai", "npm:2.6.0"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:78e90867e46510db2994cf6ed805ab4ba701c55b6d7a2c33f6cc04865dc082c172bef019989477dda8bc6824512d13cb3bcb74ef12bd113a2f6bdcd291ad70f1#npm:2.6.0", {\ + "packageLocation": "./.yarn/__virtual__/jotai-virtual-cc67c173a4/0/cache/jotai-npm-2.6.0-dc8e6aef45-6e9ee39770.zip/node_modules/jotai/",\ + "packageDependencies": [\ + ["jotai", "virtual:78e90867e46510db2994cf6ed805ab4ba701c55b6d7a2c33f6cc04865dc082c172bef019989477dda8bc6824512d13cb3bcb74ef12bd113a2f6bdcd291ad70f1#npm:2.6.0"],\ + ["@types/react", "npm:18.0.20"],\ + ["react", "npm:18.2.0"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["js-base64", [\ ["npm:3.7.2", {\ "packageLocation": "./.yarn/cache/js-base64-npm-3.7.2-706fb77a0b-573f28e9a2.zip/node_modules/js-base64/",\ diff --git a/.yarn/cache/jotai-npm-2.6.0-dc8e6aef45-6e9ee39770.zip b/.yarn/cache/jotai-npm-2.6.0-dc8e6aef45-6e9ee39770.zip new file mode 100644 index 0000000000..d6bdf1e9b0 Binary files /dev/null and b/.yarn/cache/jotai-npm-2.6.0-dc8e6aef45-6e9ee39770.zip differ diff --git a/packages/commons/react/theme/src/useTheme.ts b/packages/commons/react/theme/src/useTheme.ts index b2b725e6d9..e7f9c88301 100644 --- a/packages/commons/react/theme/src/useTheme.ts +++ b/packages/commons/react/theme/src/useTheme.ts @@ -3,12 +3,12 @@ import { useEffect, useMemo } from "react"; import { type Theme } from "./theme"; interface UseThemeReturnType { - theme: Theme | undefined; + theme: Theme; setTheme: (theme: Theme) => void; } export function useTheme(colorConfigType: "dark" | "light" | "darkAndLight"): UseThemeReturnType { - const { setTheme, theme } = _useTheme(); + const { setTheme, theme, resolvedTheme } = _useTheme(); useEffect(() => { if (colorConfigType !== "darkAndLight") { @@ -21,6 +21,6 @@ export function useTheme(colorConfigType: "dark" | "light" | "darkAndLight"): Us return { theme: colorConfigType, setTheme }; } - return { theme, setTheme } as UseThemeReturnType; - }, [colorConfigType, setTheme, theme]); + return { theme: theme ?? resolvedTheme ?? "dark", setTheme } as UseThemeReturnType; + }, [colorConfigType, resolvedTheme, setTheme, theme]); } diff --git a/packages/ui/app/package.json b/packages/ui/app/package.json index 210347da76..db227e836c 100644 --- a/packages/ui/app/package.json +++ b/packages/ui/app/package.json @@ -47,6 +47,7 @@ "algoliasearch": "^4.18.0", "classnames": "^2.3.1", "instantsearch.js": "^4.56.3", + "jotai": "^2.6.0", "lodash-es": "^4.17.21", "lru-cache": "^10.0.1", "marked": "^5.0.4", diff --git a/packages/ui/app/src/api-page/artifacts/ApiArtifacts.tsx b/packages/ui/app/src/api-page/artifacts/ApiArtifacts.tsx index 438c6be191..afce022190 100644 --- a/packages/ui/app/src/api-page/artifacts/ApiArtifacts.tsx +++ b/packages/ui/app/src/api-page/artifacts/ApiArtifacts.tsx @@ -3,7 +3,6 @@ import { DocsV1Read } from "@fern-api/fdr-sdk"; import { joinUrlSlugs } from "@fern-ui/app-utils"; import { useApiDefinitionContext } from "../../api-context/useApiDefinitionContext"; import { API_ARTIFACTS_TITLE } from "../../config"; -import { HEADER_HEIGHT } from "../../constants"; import { ApiPageMargins } from "../page-margins/ApiPageMargins"; import { useApiPageCenterElement } from "../useApiPageCenterElement"; import { DotNetLogo } from "./sdk-logos/DotNetLogo"; @@ -28,7 +27,7 @@ export const ApiArtifacts: React.FC = ({ apiArtifacts }) => return ( -
+

{API_ARTIFACTS_TITLE}

Official open-source client libraries for your favorite platforms. diff --git a/packages/ui/app/src/api-page/endpoints/EndpointContent.tsx b/packages/ui/app/src/api-page/endpoints/EndpointContent.tsx index bfc49b52a7..cce493e81a 100644 --- a/packages/ui/app/src/api-page/endpoints/EndpointContent.tsx +++ b/packages/ui/app/src/api-page/endpoints/EndpointContent.tsx @@ -1,10 +1,13 @@ import { APIV1Read } from "@fern-api/fdr-sdk"; import classNames from "classnames"; +import { useAtom } from "jotai"; +import { atomWithStorage } from "jotai/utils"; import React, { useCallback, useMemo, useState } from "react"; import { useInView } from "react-intersection-observer"; import { useApiDefinitionContext } from "../../api-context/useApiDefinitionContext"; import { HEADER_HEIGHT } from "../../constants"; import { useDocsContext } from "../../docs-context/useDocsContext"; +import { useNavigationContext } from "../../navigation-context"; import { useViewportContext } from "../../viewport-context/useViewportContext"; import { type CodeExampleClient } from "../examples/code-example"; import { getCurlLines } from "../examples/curl-example/curlUtils"; @@ -69,6 +72,8 @@ function getAvailableExampleClients(example: APIV1Read.ExampleEndpointCall): Cod return clients; } +const fernClientAtom = atomWithStorage("fern-client", DEFAULT_CLIENT); + export const EndpointContent: React.FC = ({ endpoint, package: package_, @@ -78,6 +83,7 @@ export const EndpointContent: React.FC = ({ route, }) => { const { layoutBreakpoint, viewportSize } = useViewportContext(); + const { navigateToPath } = useNavigationContext(); const [isInViewport, setIsInViewport] = useState(false); const { ref: containerRef } = useInView({ onChange: setIsInViewport, @@ -100,9 +106,19 @@ export const EndpointContent: React.FC = ({ [setHoveredResponsePropertyPath] ); - const [selectedExampleClient, setSelectedExampleClient] = useState(DEFAULT_CLIENT); + const [storedSelectedExampleClient, setSelectedExampleClient] = useAtom(fernClientAtom); const [selectedErrorIndex, setSelectedErrorIndex] = useState(null); + const selectedExampleClient = storedSelectedExampleClient ?? DEFAULT_CLIENT; + + const setSelectedExampleClientAndScrollToTop = useCallback( + (nextClient: CodeExampleClient) => { + setSelectedExampleClient(nextClient); + navigateToPath(route.substring(1)); + }, + [navigateToPath, route, setSelectedExampleClient] + ); + const errors = useMemo(() => { return [...(endpoint.errorsV2 ?? [])] .sort((e1, e2) => (e1.name != null && e2.name != null ? e1.name.localeCompare(e2.name) : 0)) @@ -178,17 +194,16 @@ export const EndpointContent: React.FC = ({ return (
setSelectedErrorIndex(null)} ref={containerRef} >
= ({ example={example} availableExampleClients={availableExampleClients} selectedExampleClient={selectedExampleClient} - onClickExampleClient={setSelectedExampleClient} + onClickExampleClient={setSelectedExampleClientAndScrollToTop} requestCurlLines={curlLines} responseJsonLines={jsonLines} hoveredRequestPropertyPath={hoveredRequestPropertyPath} diff --git a/packages/ui/app/src/api-page/endpoints/EndpointContentCodeSnippets.tsx b/packages/ui/app/src/api-page/endpoints/EndpointContentCodeSnippets.tsx index 14bb4b550f..7bc0d81944 100644 --- a/packages/ui/app/src/api-page/endpoints/EndpointContentCodeSnippets.tsx +++ b/packages/ui/app/src/api-page/endpoints/EndpointContentCodeSnippets.tsx @@ -1,8 +1,8 @@ "use client"; import { APIV1Read } from "@fern-api/fdr-sdk"; import { type Theme } from "@fern-ui/theme"; -import dynamic from "next/dynamic"; import { memo } from "react"; +import { CodeBlockSkeleton } from "../../commons/CodeBlockSkeleton"; import type { CodeExampleClient } from "../examples/code-example"; import { CurlExample } from "../examples/curl-example/CurlExample"; import { CurlLine, curlLinesToString } from "../examples/curl-example/curlUtils"; @@ -12,11 +12,6 @@ import { JsonLine } from "../examples/json-example/jsonLineUtils"; import { TitledExample } from "../examples/TitledExample"; import { CodeExampleClientDropdown } from "./CodeExampleClientDropdown"; -const CodeBlockSkeleton = dynamic( - () => import("../../commons/CodeBlockSkeleton").then(({ CodeBlockSkeleton }) => CodeBlockSkeleton), - { ssr: false } -); - export declare namespace EndpointContentCodeSnippets { export interface Props { theme?: Theme; diff --git a/packages/ui/app/src/api-page/endpoints/EndpointParameter.tsx b/packages/ui/app/src/api-page/endpoints/EndpointParameter.tsx index 5573f95da9..821336e711 100644 --- a/packages/ui/app/src/api-page/endpoints/EndpointParameter.tsx +++ b/packages/ui/app/src/api-page/endpoints/EndpointParameter.tsx @@ -1,7 +1,6 @@ import { APIV1Read } from "@fern-api/fdr-sdk"; import { AbsolutelyPositionedAnchor } from "../../commons/AbsolutelyPositionedAnchor"; import { MonospaceText } from "../../commons/monospace/MonospaceText"; -import { HEADER_HEIGHT } from "../../constants"; import { getAnchorId } from "../../util/anchor"; import { ApiPageDescription } from "../ApiPageDescription"; import { TypeReferenceDefinitions } from "../types/type-reference/TypeReferenceDefinitions"; @@ -28,11 +27,7 @@ export const EndpointParameter: React.FC = ({ const anchorId = getAnchorId(anchorIdParts); const anchorRoute = `${route}#${anchorId}`; return ( -
+
{name} diff --git a/packages/ui/app/src/api-page/endpoints/EndpointSection.tsx b/packages/ui/app/src/api-page/endpoints/EndpointSection.tsx index 09ee387a43..6dced9dcbf 100644 --- a/packages/ui/app/src/api-page/endpoints/EndpointSection.tsx +++ b/packages/ui/app/src/api-page/endpoints/EndpointSection.tsx @@ -1,5 +1,4 @@ import { AbsolutelyPositionedAnchor } from "../../commons/AbsolutelyPositionedAnchor"; -import { HEADER_HEIGHT } from "../../constants"; import { getAnchorId } from "../../util/anchor"; import { Markdown } from "../markdown/Markdown"; @@ -22,7 +21,7 @@ export const EndpointSection: React.FC = ({ const anchorId = getAnchorId(anchorIdParts); const anchorRoute = `${route}#${anchorId}`; return ( -
+
diff --git a/packages/ui/app/src/api-page/subpackages/ApiSubpackage.tsx b/packages/ui/app/src/api-page/subpackages/ApiSubpackage.tsx index 4b3645c0c4..cc614c9f9b 100644 --- a/packages/ui/app/src/api-page/subpackages/ApiSubpackage.tsx +++ b/packages/ui/app/src/api-page/subpackages/ApiSubpackage.tsx @@ -1,6 +1,5 @@ import { APIV1Read } from "@fern-api/fdr-sdk"; import { useApiDefinitionContext } from "../../api-context/useApiDefinitionContext"; -import { HEADER_HEIGHT } from "../../constants"; import { ApiPackageContents } from "../ApiPackageContents"; import { ApiPageMargins } from "../page-margins/ApiPageMargins"; import { useApiPageCenterElement } from "../useApiPageCenterElement"; @@ -26,7 +25,7 @@ export const ApiSubpackage: React.FC = ({ return ( <> -
+
= ({ return (
diff --git a/packages/ui/app/src/api-page/webhooks/WebhookContent.tsx b/packages/ui/app/src/api-page/webhooks/WebhookContent.tsx index 90886665a6..1800fd6ccc 100644 --- a/packages/ui/app/src/api-page/webhooks/WebhookContent.tsx +++ b/packages/ui/app/src/api-page/webhooks/WebhookContent.tsx @@ -71,10 +71,9 @@ export const WebhookContent = React.memo(function WebhookC })} >
diff --git a/packages/ui/app/src/api-page/webhooks/WebhookSection.tsx b/packages/ui/app/src/api-page/webhooks/WebhookSection.tsx index 2ba5decb66..2cb857fbd3 100644 --- a/packages/ui/app/src/api-page/webhooks/WebhookSection.tsx +++ b/packages/ui/app/src/api-page/webhooks/WebhookSection.tsx @@ -2,7 +2,6 @@ import { Url } from "next/dist/shared/lib/router/router"; import { resolveHref } from "next/dist/shared/lib/router/utils/resolve-href"; import { useRouter } from "next/router"; import { AbsolutelyPositionedAnchor } from "../../commons/AbsolutelyPositionedAnchor"; -import { HEADER_HEIGHT } from "../../constants"; import { Markdown } from "../markdown/Markdown"; export declare namespace WebhookSection { @@ -15,13 +14,7 @@ export declare namespace WebhookSection { export const WebhookSection: React.FC = ({ title, description, href, children }) => { return ( -
+
diff --git a/packages/ui/app/src/contexts.tsx b/packages/ui/app/src/contexts.tsx index a1509d19d6..d4711c4791 100644 --- a/packages/ui/app/src/contexts.tsx +++ b/packages/ui/app/src/contexts.tsx @@ -1,11 +1,15 @@ import { ThemeProviderWithLayout } from "@fern-ui/theme"; +import { createStore, Provider as JotaiProvider } from "jotai"; import { PropsWithChildren, ReactElement } from "react"; import { CacheContextProvider } from "./cache-context/CacheContextProvider"; import { MobileSidebarContextProvider } from "./mobile-sidebar-context/MobileSidebarContextProvider"; import { SearchContextProvider } from "./search-context/SearchContextProvider"; import { ViewportContextProvider } from "./viewport-context/ViewportContextProvider"; +const store = createStore(); + export const CONTEXTS = [ + ({ children }: PropsWithChildren): ReactElement => {children}, ({ children }: PropsWithChildren): ReactElement => ( {children} ), diff --git a/packages/ui/app/src/docs-context/DocsContext.ts b/packages/ui/app/src/docs-context/DocsContext.ts index 029729f896..49873c3c59 100644 --- a/packages/ui/app/src/docs-context/DocsContext.ts +++ b/packages/ui/app/src/docs-context/DocsContext.ts @@ -13,6 +13,6 @@ export interface DocsContextValue { resolvePage: (pageId: DocsV1Read.PageId) => DocsV1Read.PageContent; resolveFile: (fileId: DocsV1Read.FileId) => DocsV1Read.Url; - theme: Theme | undefined; + theme: Theme; setTheme: (theme: Theme) => void; } diff --git a/packages/ui/app/src/docs/HeaderLogoSection.tsx b/packages/ui/app/src/docs/HeaderLogoSection.tsx index a41dfe29b3..c11b7920e3 100644 --- a/packages/ui/app/src/docs/HeaderLogoSection.tsx +++ b/packages/ui/app/src/docs/HeaderLogoSection.tsx @@ -14,10 +14,6 @@ export const HeaderLogoSection: React.FC = () => { const { definitionInfo, activeVersionContext } = useDocsSelectors(); const { logo, logoV2, logoHeight, logoHref } = docsDefinition.config; - if (theme == null) { - return null; - } - const logoForTheme = logoV2 != null ? logoV2[theme] : logo; const hasMultipleVersions = definitionInfo.type === "versioned"; const activeVersionId = diff --git a/packages/ui/app/src/mdx/base-components.tsx b/packages/ui/app/src/mdx/base-components.tsx index 2e286adc34..2fbd69836b 100644 --- a/packages/ui/app/src/mdx/base-components.tsx +++ b/packages/ui/app/src/mdx/base-components.tsx @@ -2,7 +2,6 @@ import classNames from "classnames"; import Link from "next/link"; import React, { AnchorHTMLAttributes, HTMLAttributes } from "react"; import { AbsolutelyPositionedAnchor } from "../commons/AbsolutelyPositionedAnchor"; -import { HEADER_HEIGHT } from "../constants"; import { onlyText } from "../util/onlyText"; type InlineCodeProps = { @@ -103,11 +102,8 @@ export const H1: React.FC> = ({ className, .. id={slug} className={classNames( className, - "relative group/anchor-container !text-text-primary-light dark:!text-text-primary-dark text-2xl font-semibold mt-10 mb-3" + "relative group/anchor-container !text-text-primary-light dark:!text-text-primary-dark text-2xl font-semibold mt-10 mb-3 scroll-mt-16" )} - style={{ - scrollMarginTop: HEADER_HEIGHT, - }} {...rest} > @@ -125,11 +121,8 @@ export const H2: React.FC> = ({ className, .. id={slug} className={classNames( className, - "relative group/anchor-container !text-text-primary-light dark:!text-text-primary-dark text-xl font-semibold mt-10 mb-3" + "relative group/anchor-container !text-text-primary-light dark:!text-text-primary-dark text-xl font-semibold mt-10 mb-3 scroll-mt-16" )} - style={{ - scrollMarginTop: HEADER_HEIGHT, - }} {...rest} > @@ -147,11 +140,8 @@ export const H3: React.FC> = ({ className, .. id={slug} className={classNames( className, - "relative group/anchor-container !text-text-primary-light dark:!text-text-primary-dark text-lg font-semibold mt-10 mb-3" + "relative group/anchor-container !text-text-primary-light dark:!text-text-primary-dark text-lg font-semibold mt-10 mb-3 scroll-mt-16" )} - style={{ - scrollMarginTop: HEADER_HEIGHT, - }} {...rest} > @@ -169,11 +159,8 @@ export const H4: React.FC> = ({ className, .. id={slug} className={classNames( className, - "relative group/anchor-container !text-text-primary-light dark:!text-text-primary-dark text-lg font-semibold mt-10 mb-3" + "relative group/anchor-container !text-text-primary-light dark:!text-text-primary-dark text-lg font-semibold mt-10 mb-3 scroll-mt-16" )} - style={{ - scrollMarginTop: HEADER_HEIGHT, - }} {...rest} > @@ -191,11 +178,8 @@ export const H5: React.FC> = ({ className, .. id={slug} className={classNames( className, - "relative group/anchor-container !text-text-primary-light dark:!text-text-primary-dark text-lg font-semibold mt-10 mb-3" + "relative group/anchor-container !text-text-primary-light dark:!text-text-primary-dark text-lg font-semibold mt-10 mb-3 scroll-mt-16" )} - style={{ - scrollMarginTop: HEADER_HEIGHT, - }} {...rest} > @@ -213,11 +197,8 @@ export const H6: React.FC> = ({ className, .. id={slug} className={classNames( className, - "relative group/anchor-container !text-text-primary-light dark:!text-text-primary-dark text-lg font-semibold mt-10 mb-3" + "relative group/anchor-container !text-text-primary-light dark:!text-text-primary-dark text-lg font-semibold mt-10 mb-3 scroll-mt-16" )} - style={{ - scrollMarginTop: HEADER_HEIGHT, - }} {...rest} > diff --git a/packages/ui/app/src/navigation-context/NavigationContextProvider.tsx b/packages/ui/app/src/navigation-context/NavigationContextProvider.tsx index d1abd63cd4..8152f66402 100644 --- a/packages/ui/app/src/navigation-context/NavigationContextProvider.tsx +++ b/packages/ui/app/src/navigation-context/NavigationContextProvider.tsx @@ -165,19 +165,20 @@ export const NavigationContextProvider: React.FC(); + const navigateToPath = useEventCallback((fullSlug: string) => { justNavigated.current = true; const navigatable = resolver.resolveNavigatable(fullSlug); + navigateToRoute.current(`/${fullSlug}`); if (navigatable != null) { setActiveNavigatable(navigatable); } // navigateToPathListeners.invokeListeners(slug); - const timeout = setTimeout(() => { + timeout.current != null && clearTimeout(timeout.current); + timeout.current = setTimeout(() => { justNavigated.current = false; }, 500); - return () => { - clearTimeout(timeout); - }; }); useEffect(() => { diff --git a/yarn.lock b/yarn.lock index 34262a45c0..3c94b88119 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2652,6 +2652,7 @@ __metadata: eslint: ^8.26.0 instantsearch.js: ^4.56.3 jest: ^29.7.0 + jotai: ^2.6.0 lodash-es: ^4.17.21 lru-cache: ^10.0.1 marked: ^5.0.4 @@ -10261,6 +10262,21 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"jotai@npm:^2.6.0": + version: 2.6.0 + resolution: "jotai@npm:2.6.0" + peerDependencies: + "@types/react": ">=17.0.0" + react: ">=17.0.0" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + checksum: 6e9ee397707cb012c57c7a616eaea7f401a1a3d66c0f36428e1eb33c03cc91788aa6c566daf473387bd86486a7215322efff2a29b446f8a9a59bc33648b7148f + languageName: node + linkType: hard + "js-base64@npm:3.7.2": version: 3.7.2 resolution: "js-base64@npm:3.7.2"