From b5a530c228455402dd9e26d1bb5e7ab5c77f106b Mon Sep 17 00:00:00 2001 From: esemyonov <132259164+esemyonov@users.noreply.github.com> Date: Mon, 18 Dec 2023 12:11:06 +0600 Subject: [PATCH] Refactor navigation (#285) * refactor: navigation * feat: add cli page * fix: minor fixes --- sidebar.jsonc | 26 ++++- src/app/cli/page.tsx | 25 +++++ src/build/navigation.mjs | 2 + src/components/DocsHeader.tsx | 60 +++++++---- src/components/Navigation.tsx | 145 +++++++++++++++----------- src/components/PrevNextLinks.tsx | 30 +++--- src/components/Search.tsx | 16 +-- src/lib/enums/navigation-type.tsx | 4 + src/lib/hooks/useFindNavItemByLink.ts | 11 ++ src/lib/policies.ts | 2 +- src/lib/types.ts | 11 +- tsconfig.json | 8 +- types.d.ts | 3 +- 13 files changed, 224 insertions(+), 119 deletions(-) create mode 100644 src/app/cli/page.tsx create mode 100644 src/lib/enums/navigation-type.tsx create mode 100644 src/lib/hooks/useFindNavItemByLink.ts diff --git a/sidebar.jsonc b/sidebar.jsonc index 2ac08ddf..664d7147 100644 --- a/sidebar.jsonc +++ b/sidebar.jsonc @@ -2,6 +2,7 @@ { "label": "Introduction", "href": "/", + "type": "category", "items": [ "articles/what-is-zuplo", "articles/who-uses-and-why", @@ -10,6 +11,7 @@ }, { "label": "Getting Started", + "type": "category", "items": [ "articles/step-1-setup-basic-gateway", "articles/step-2-add-api-key-auth", @@ -33,6 +35,7 @@ // }, { "label": "Getting to Production", + "type": "category", "items": [ "articles/environments", "articles/github-source-control", @@ -47,6 +50,7 @@ }, { "label": "API Keys", + "type": "category", "items": [ "articles/api-key-management", "articles/api-key-authentication", @@ -65,9 +69,11 @@ // }, { "label": "How to Guides", + "type": "category", "items": [ { "label": "Local Development", + "type": "sub_category", "items": [ "articles/local-development", "articles/configure-ide-for-local-development" @@ -93,9 +99,11 @@ { "label": "Policies", "href": "/policies", + "type": "category", "items": [ { "label": "Authentication", + "type": "sub_category", "items": [ "policies/api-key-inbound", "policies/auth0-jwt-auth-inbound", @@ -113,6 +121,7 @@ }, { "label": "Security & Validation", + "type": "sub_category", "items": [ "policies/rate-limit-inbound", "policies/audit-log-inbound", @@ -123,6 +132,7 @@ }, { "label": "Metrics, Billing & Quotas", + "type": "sub_category", "items": [ "policies/quota-inbound", "policies/moesif-inbound", @@ -132,6 +142,7 @@ }, { "label": "Testing", + "type": "sub_category", "items": [ "policies/ab-test-inbound", "policies/mock-api-inbound", @@ -140,6 +151,7 @@ }, { "label": "Request Filtering", + "type": "sub_category", "items": [ "policies/acl-policy-inbound", "policies/geo-filter-inbound", @@ -149,6 +161,7 @@ }, { "label": "Request Modification", + "type": "sub_category", "items": [ "policies/transform-body-inbound", "policies/remove-headers-inbound", @@ -163,6 +176,7 @@ }, { "label": "Response Modification", + "type": "sub_category", "items": [ "policies/transform-body-outbound", "policies/remove-headers-outbound", @@ -173,6 +187,7 @@ }, { "label": "Upstream Authentication", + "type": "sub_category", "items": [ "policies/upstream-azure-ad-service-auth-inbound", "policies/upstream-gcp-service-auth-inbound", @@ -183,6 +198,7 @@ }, { "label": "Other", + "type": "sub_category", "items": [ "policies/composite-inbound", "policies/caching-inbound", @@ -194,10 +210,12 @@ }, { "label": "Reference", + "type": "category", "items": [ { "label": "Handlers", "href": "/handlers", + "type": "sub_category", "items": [ "handlers/url-forward", "handlers/url-rewrite", @@ -210,6 +228,7 @@ }, { "label": "Developer Portal", + "type": "sub_category", "items": [ "articles/developer-portal", "articles/dev-portal-setup", @@ -225,6 +244,7 @@ }, { "label": "Programming API", + "type": "sub_category", "items": [ "articles/zuplo-request", "articles/environment-variables", @@ -251,6 +271,7 @@ { "label": "Zuplo CLI", "href": "/cli", + "type": "sub_category", "items": [ "cli/installation", "cli/analytics", @@ -266,15 +287,18 @@ }, { "label": "Product Info", - "item": [ + "type": "category", + "items": [ "articles/support", { "label": "Changelog", + "isExternal": true, "href": "https://zuplo.com/changelog" }, "articles/security", { "label": "Trust & Compliance", + "isExternal": true, "href": "https://trust.zuplo.com" } ] diff --git a/src/app/cli/page.tsx b/src/app/cli/page.tsx new file mode 100644 index 00000000..597abf2b --- /dev/null +++ b/src/app/cli/page.tsx @@ -0,0 +1,25 @@ +import { DocsLayout } from "@/components/DocsLayout"; +import Link from "next/link"; + +export default async function Page() { + return ( + +

+ The Zuplo CLI, zup, provides convenient tooling for common + tasks that you might want to automate. You can use it to deploy zups + through CI/CD, create and update environment variables, manage your + tunnels, and more! It is powered by the{" "} + + Zuplo Developer API + {" "} + , which you can also call directly, if you want to create your own + tooling. +

+
+ ); +} diff --git a/src/build/navigation.mjs b/src/build/navigation.mjs index c0f08765..50f355c5 100644 --- a/src/build/navigation.mjs +++ b/src/build/navigation.mjs @@ -10,6 +10,8 @@ const __filename = url.fileURLToPath(import.meta.url); function buildNavSection(rawSection) { let section = { label: rawSection.label, + href: rawSection.href, + type: rawSection.type, items: [], }; if (!rawSection.items) { diff --git a/src/components/DocsHeader.tsx b/src/components/DocsHeader.tsx index c900b7a1..7115c3f0 100644 --- a/src/components/DocsHeader.tsx +++ b/src/components/DocsHeader.tsx @@ -1,12 +1,12 @@ "use client"; -import { usePathname } from "next/navigation"; - import { navigation } from "@/build/navigation.mjs"; -import { NavCategory, NavItem, Section } from "@/lib/types"; +import { NavCategory, Section } from "@/lib/types"; import Link from "next/link"; import ChevronRightIcon from "@/components/svgs/chevron-right.svg"; import { MobileTableOfContents } from "@/components/MobileTableOfContents"; +import { useFindNavItemByLink } from "@/lib/hooks/useFindNavItemByLink"; +import { nanoid } from "nanoid"; export function DocsHeader({ title, @@ -15,32 +15,48 @@ export function DocsHeader({ title?: string; tableOfContents?: Array
; }) { - let pathname = usePathname(); - - const findLink = (link: NavCategory | NavItem): boolean => { - if ("href" in link) { - return link.href === pathname.split("#")[0]; - } else { - return link.items.some(findLink); - } - }; - - let section = navigation.find((section) => section.items.find(findLink)); + const section = navigation.find((section) => + section.items.find(useFindNavItemByLink), + ); if (!title && !section) { return null; } + const breadcrumbItems: Array = [{ label: "Home", href: "/" }]; + + if (section) { + breadcrumbItems.push({ + label: section.label, + href: section.href, + }); + let currentSection = section; + + while (currentSection?.items?.length) { + currentSection = currentSection.items.find(useFindNavItemByLink); + + if (currentSection) { + breadcrumbItems.push({ + label: currentSection.label, + href: currentSection?.href, + }); + } + } + } + return (
-
- Home - {section && ( - <> - -

{section.label}

- - )} +
+ {breadcrumbItems.map((item, index) => ( +
+ {!!item?.href ? ( + {item.label} + ) : ( +

{item.label}

+ )} + {index < breadcrumbItems.length - 1 && } +
+ ))}
{tableOfContents && ( diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index ddd7a088..9b35ac64 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -6,16 +6,17 @@ import ChevronDownIcon from "@/components/svgs/chevron-down.svg"; import ArrowIcon from "@/components/svgs/arrow.svg"; import { navigation } from "@/build/navigation.mjs"; -import { NavCategory, NavItem } from "@/lib/types"; import { useState } from "react"; +import { NavCategory } from "@/lib/types"; +import { NavigationType } from "@/lib/enums/navigation-type"; function SubNavSection({ - link, + navItem, onLinkClick, depth, linkClassName, }: { - link: NavCategory; + navItem: NavCategory; onLinkClick?: React.MouseEventHandler; depth: number; linkClassName: string; @@ -23,7 +24,8 @@ function SubNavSection({ const pathname = usePathname(); const chevronClassName = "absolute left-0 top-0"; const [hidden, setHidden] = useState( - !link.items.some((l) => "href" in l && l.href === pathname), + !!navItem?.items && + !navItem.items.some((item) => !!item?.href && item.href === pathname), ); function onClick() { @@ -41,12 +43,22 @@ function SubNavSection({ ) : ( )} - - {link.label} - + {!!navItem.href && hidden ? ( + + {navItem.label} + + ) : ( + + {navItem.label} + + )}
    - {link.items.map((link, i) => ( - - ))} + {!!navItem?.items && + navItem.items.map((item, i) => ( + + ))}
); } function NavSection({ - link, + navItem, onLinkClick, depth, }: { - link: NavCategory | NavItem; + navItem: NavCategory; onLinkClick?: React.MouseEventHandler; depth: number; }) { const pathname = usePathname(); const linkClassName = - "block w-full px-6 leading-6 tracking-wider transition-all hover:text-pink hover:text-shadow cursor-pointer"; + "block w-full px-6 leading-6 tracking-wider transition-all hover:text-pink hover:text-shadow cursor-pointer relative"; - if ("href" in link) { - return ( -
  • - + {navItem?.type === NavigationType.SUB_CATEGORY ? ( + + ) : ( + <> + {!!navItem?.href && ( +
  • + + {navItem.label} + {navItem?.isExternal && ( + + )} + +
  • )} - > - {link.label} - - - - ); - } else { - return ( - - ); - } + + )} + + ); } export function Navigation({ @@ -118,20 +140,27 @@ export function Navigation({ return (