docs: redesigned navigation (#9525)

Redesign navigation bar to reflect new design and allow for dropdowns

Closes DX-943
This commit is contained in:
Shahed Nasser
2024-10-11 10:10:00 +03:00
committed by GitHub
parent 7c5415ba3a
commit 49a91fd40e
63 changed files with 934 additions and 978 deletions

View File

@@ -34,6 +34,7 @@ export default function RootLayout({
bodyClassName={clsx(inter.variable, robotoMono.variable)}
showToc={false}
showBanner={false}
showBreadcrumbs={false}
>
{children}
</WideLayout>

View File

@@ -1,5 +1,4 @@
import { DocsConfig } from "types"
import { getMobileSidebarItems } from "docs-ui"
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
@@ -16,9 +15,10 @@ export const config: DocsConfig = {
loaded: true,
},
],
mobile: getMobileSidebarItems({
baseUrl,
version: "v2",
}),
mobile: [],
},
project: {
title: "API Reference",
key: "api-reference",
},
}

View File

@@ -8,8 +8,6 @@ import {
} from "docs-ui"
import { useMemo } from "react"
import { config } from "../config"
import { usePathname } from "next/navigation"
import basePathUrl from "../utils/base-path-url"
type MainNavProviderProps = {
children?: React.ReactNode
@@ -17,15 +15,12 @@ type MainNavProviderProps = {
export const MainNavProvider = ({ children }: MainNavProviderProps) => {
const { isBrowser } = useIsBrowser()
const pathname = usePathname()
const navigationDropdownItems = useMemo(
() =>
getNavDropdownItems({
basePath: config.baseUrl,
activePath: basePathUrl(pathname),
version: "v2",
}),
[pathname]
[]
)
const reportLink = useMemo(

View File

@@ -3,7 +3,7 @@
import {
usePageLoading,
SearchProvider as UiSearchProvider,
searchFiltersV2,
searchFilters,
AiAssistantIcon,
AiAssistantProvider,
} from "docs-ui"
@@ -51,7 +51,7 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
checkInternalPattern: new RegExp(
`^${config.baseUrl}${basePathUrl(`/(admin|store)`)}`
),
filterOptions: searchFiltersV2,
filterOptions: searchFilters,
}}
commands={[
{

View File

@@ -7,5 +7,12 @@ export const config: DocsConfig = {
titleSuffix: "Medusa v2 Docs",
baseUrl,
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
sidebar: sidebarConfig(baseUrl),
sidebar: sidebarConfig,
project: {
title: "Documentation",
key: "book",
},
breadcrumbOptions: {
showCategories: false,
},
}

View File

@@ -1,4 +1,4 @@
import { Badge, getMobileSidebarItems } from "docs-ui"
import { Badge } from "docs-ui"
import type { SidebarConfig, SidebarItem } from "@/types"
import { sidebar } from "../sidebar.mjs"
@@ -20,12 +20,7 @@ const normalizeSidebarItems = (items: SidebarItem[]) =>
return item
})
export const sidebarConfig = (baseUrl: string): SidebarConfig => {
return {
default: normalizeSidebarItems(sidebar),
mobile: getMobileSidebarItems({
baseUrl,
version: "v2",
}),
}
export const sidebarConfig: SidebarConfig = {
default: normalizeSidebarItems(sidebar),
mobile: [],
}

View File

@@ -8,7 +8,6 @@ import {
} from "docs-ui"
import { useMemo } from "react"
import { config } from "../config"
import { basePathUrl } from "../utils/base-path-url"
import { usePathname } from "next/navigation"
import { generatedEditDates } from "../generated/edit-dates.mjs"
@@ -23,8 +22,6 @@ export const MainNavProvider = ({ children }: MainNavProviderProps) => {
() =>
getNavDropdownItems({
basePath: config.baseUrl,
activePath: basePathUrl(),
version: "v2",
}),
[]
)
@@ -51,9 +48,6 @@ export const MainNavProvider = ({ children }: MainNavProviderProps) => {
navItems={navigationDropdownItems}
reportIssueLink={reportLink}
editDate={editDate}
breadcrumbOptions={{
showCategories: false,
}}
>
{children}
</UiMainNavProvider>

View File

@@ -4,7 +4,7 @@ import {
AiAssistantIcon,
AiAssistantProvider,
SearchProvider as UiSearchProvider,
searchFiltersV2,
searchFilters,
} from "docs-ui"
import { config } from "../config"
@@ -50,7 +50,7 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
checkInternalPattern: new RegExp(
`^${config.baseUrl}/v2/([^(resources)])*`
),
filterOptions: searchFiltersV2,
filterOptions: searchFilters,
}}
commands={[
{

View File

@@ -1,5 +1,4 @@
import { DocsConfig, SidebarItem } from "types"
import { getMobileSidebarItems } from "docs-ui"
import { generatedSidebar } from "../generated/sidebar.mjs"
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
@@ -10,9 +9,13 @@ export const config: DocsConfig = {
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
sidebar: {
default: generatedSidebar as SidebarItem[],
mobile: getMobileSidebarItems({
baseUrl,
version: "v2",
}),
mobile: [],
},
project: {
title: "Development Resources",
key: "resources",
},
breadcrumbOptions: {
showCategories: true,
},
}

View File

@@ -8,7 +8,6 @@ import {
} from "docs-ui"
import { useMemo } from "react"
import { config } from "../config"
import { basePathUrl } from "../utils/base-path-url"
import { usePathname } from "next/navigation"
import { generatedEditDates } from "../generated/edit-dates.mjs"
@@ -23,14 +22,8 @@ export const MainNavProvider = ({ children }: MainNavProviderProps) => {
() =>
getNavDropdownItems({
basePath: config.baseUrl,
activePath: basePathUrl(
pathname.startsWith("/commerce-modules")
? "/commerce-modules"
: undefined
),
version: "v2",
}),
[pathname]
[]
)
const reportLink = useMemo(

View File

@@ -4,7 +4,7 @@ import {
AiAssistantIcon,
AiAssistantProvider,
SearchProvider as UiSearchProvider,
searchFiltersV2,
searchFilters,
} from "docs-ui"
import { config } from "../config"
@@ -39,7 +39,7 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
},
],
checkInternalPattern: new RegExp(`^${config.baseUrl}/v2/resources/.*`),
filterOptions: searchFiltersV2,
filterOptions: searchFilters,
}}
commands={[
{

View File

@@ -1,6 +1,4 @@
import { getMobileSidebarItems } from "docs-ui"
import { SidebarSectionItems } from "types"
import { siteConfig } from "./site"
type DocsConfig = {
sidebar: SidebarSectionItems
@@ -359,9 +357,6 @@ export const docsConfig: DocsConfig = {
],
},
],
mobile: getMobileSidebarItems({
baseUrl: siteConfig.baseUrl,
version: "v1",
}),
mobile: [],
},
}

View File

@@ -19,4 +19,11 @@ export const siteConfig: SiteConfig = {
default: [],
mobile: [],
},
project: {
title: "Medusa UI",
key: "ui",
},
breadcrumbOptions: {
showCategories: true,
},
}

View File

@@ -8,7 +8,6 @@ import {
} from "docs-ui"
import { useMemo } from "react"
import { siteConfig } from "../config/site"
import { basePathUrl } from "../lib/base-path-url"
type MainNavProviderProps = {
children?: React.ReactNode
@@ -20,8 +19,6 @@ export const MainNavProvider = ({ children }: MainNavProviderProps) => {
() =>
getNavDropdownItems({
basePath: siteConfig.baseUrl,
activePath: basePathUrl(),
version: "v1",
}),
[]
)

View File

@@ -4,7 +4,7 @@ import {
AiAssistantIcon,
AiAssistantProvider,
SearchProvider as UiSearchProvider,
searchFiltersV1,
searchFilters,
} from "docs-ui"
import { absoluteUrl } from "../lib/absolute-url"
@@ -34,7 +34,7 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
},
],
checkInternalPattern: new RegExp(`^${absoluteUrl()}/ui`),
filterOptions: searchFiltersV1,
filterOptions: searchFilters,
}}
initialDefaultFilters={["ui"]}
commands={[

View File

@@ -30,6 +30,12 @@
@apply text-ui-fg-subtle;
}
/* Hack to hide navbar / toc when some components like prompt are opened. */
body[data-scroll-locked] .z-20,
body[data-scroll-locked] .z-10 {
z-index: 0 !important;
}
body[data-modal="opened"] {
@apply !overflow-hidden text-ui-fg-base;
}

View File

@@ -1,5 +1,4 @@
import { DocsConfig, SidebarItem } from "types"
import { getMobileSidebarItems } from "docs-ui"
import { generatedSidebar as sidebar } from "@/generated/sidebar.mjs"
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
@@ -10,9 +9,13 @@ export const config: DocsConfig = {
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
sidebar: {
default: sidebar as SidebarItem[],
mobile: getMobileSidebarItems({
baseUrl,
version: "v2",
}),
mobile: [],
},
project: {
title: "User Guide",
key: "user-guide",
},
breadcrumbOptions: {
showCategories: true,
},
}

View File

@@ -8,7 +8,6 @@ import {
} from "docs-ui"
import { useMemo } from "react"
import { config } from "../config"
import { basePathUrl } from "../utils/base-path-url"
type MainNavProviderProps = {
children?: React.ReactNode
@@ -20,8 +19,6 @@ export const MainNavProvider = ({ children }: MainNavProviderProps) => {
() =>
getNavDropdownItems({
basePath: config.baseUrl,
activePath: basePathUrl(),
version: "v2",
}),
[]
)

View File

@@ -4,7 +4,7 @@ import {
SearchProvider as UiSearchProvider,
AiAssistantIcon,
AiAssistantProvider,
searchFiltersV1,
searchFilters,
} from "docs-ui"
import { config } from "../config"
@@ -42,9 +42,10 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
},
],
checkInternalPattern: new RegExp(`^${config.baseUrl}/user-guide`),
filterOptions: searchFiltersV1,
filterOptions: searchFilters,
}}
initialDefaultFilters={["user-guide"]}
// TODO change later when we have a user guide filter
initialDefaultFilters={["guides"]}
commands={[
{
name: "ai-assistant",

View File

@@ -1,29 +1,55 @@
"use client"
import React, { useMemo } from "react"
import { CurrentItemsState, useSidebar } from "@/providers"
import { TriangleRightMini } from "@medusajs/icons"
import Link from "next/link"
import clsx from "clsx"
type BreadcrumbItem = Map<string, string>
import Link from "next/link"
import { SidebarItemLink } from "types"
import { CurrentItemsState, useSidebar, useSiteConfig } from "../../providers"
import { Button } from "../Button"
import { TriangleRightMini } from "@medusajs/icons"
export const Breadcrumbs = () => {
const { currentItems } = useSidebar()
const { currentItems, activeItem: sidebarActiveItem } = useSidebar()
const {
config: { breadcrumbOptions, project, baseUrl, basePath },
} = useSiteConfig()
const getBreadcrumbsOfItem = (item: CurrentItemsState): BreadcrumbItem => {
let tempBreadcrumbItems: BreadcrumbItem = new Map()
const getLinkPath = (item?: SidebarItemLink): string | undefined => {
if (!item) {
return
}
return item.isPathHref ? item.path : `#${item.path}`
}
const getBreadcrumbsOfItem = (
item: CurrentItemsState
): Map<string, string> => {
let tempBreadcrumbItems: Map<string, string> = new Map()
if (item.previousSidebar) {
tempBreadcrumbItems = getBreadcrumbsOfItem(item.previousSidebar)
}
const parentPath =
item.parentItem?.type === "link" ? item.parentItem.path : undefined
item.parentItem?.type === "link"
? getLinkPath(item.parentItem)
: (item.parentItem?.type === "category" &&
breadcrumbOptions?.showCategories) ||
item.parentItem?.type === "sub-category"
? "#"
: undefined
const firstItemPath =
item.default[0].type === "link" ? item.default[0].path : undefined
item.default[0].type === "link"
? getLinkPath(item.default[0])
: (item.default[0].type === "category" &&
breadcrumbOptions?.showCategories) ||
item.default[0].type === "sub-category"
? "#"
: undefined
const breadcrumbPath = parentPath || firstItemPath || "/"
tempBreadcrumbItems.set(
parentPath || firstItemPath || "/",
breadcrumbPath,
item.parentItem?.childSidebarTitle || item.parentItem?.title || ""
)
@@ -31,51 +57,65 @@ export const Breadcrumbs = () => {
}
const breadcrumbItems = useMemo(() => {
const tempBreadcrumbItems: BreadcrumbItem = new Map()
if (!currentItems) {
return tempBreadcrumbItems
const tempBreadcrumbItems: Map<string, string> = new Map()
tempBreadcrumbItems.set(`${baseUrl}${basePath}`, project.title)
if (currentItems) {
getBreadcrumbsOfItem(currentItems).forEach((value, key) =>
tempBreadcrumbItems.set(key, value)
)
}
tempBreadcrumbItems.set("/", "Home")
getBreadcrumbsOfItem(currentItems).forEach((value, key) =>
tempBreadcrumbItems.set(key, value)
)
if (sidebarActiveItem) {
if (
sidebarActiveItem.parentItem &&
(sidebarActiveItem.parentItem.type !== "category" ||
breadcrumbOptions?.showCategories)
) {
tempBreadcrumbItems.set(
sidebarActiveItem.parentItem.type === "link"
? getLinkPath(sidebarActiveItem.parentItem) || "#"
: "#",
sidebarActiveItem.parentItem.title || ""
)
}
tempBreadcrumbItems.set(
getLinkPath(sidebarActiveItem) || "/",
sidebarActiveItem.title || ""
)
}
return tempBreadcrumbItems
}, [currentItems])
const getBreadcrumbItemElms = (): React.ReactNode[] => {
const elms: React.ReactNode[] = []
breadcrumbItems.forEach((title, path) => {
elms.push(
<React.Fragment key={path}>
{elms.length !== 0 && (
<TriangleRightMini className={clsx("text-medusa-fg-muted")} />
)}
<Link
href={path}
className={clsx(
"text-compact-x-small-plus text-medusa-fg-muted",
"hover:text-medusa-fg-subtle transition-colors"
)}
>
{title}
</Link>
</React.Fragment>
)
})
return elms
}
}, [currentItems, sidebarActiveItem, breadcrumbOptions])
return (
<>
{breadcrumbItems.size > 0 && (
<div className="flex gap-docs_0.25 items-center mb-docs_1">
{...getBreadcrumbItemElms()}
</div>
<div
className={clsx(
"flex items-center gap-docs_0.25",
"text-medusa-fg-muted text-compact-small",
"mb-docs_1"
)}
</>
>
{Array.from(breadcrumbItems).map(([link, title], index) => (
<React.Fragment key={link}>
{index > 0 && <TriangleRightMini />}
<Button
variant="transparent-clear"
className={clsx(
"px-docs_0.5 py-docs_0.25",
link === "#" && "hover:cursor-default",
"!p-0 hover:!bg-transparent hover:!text-medusa-fg-subtle"
)}
>
<Link
href={link}
className={clsx(link === "#" && "hover:cursor-default")}
>
{title}
</Link>
</Button>
</React.Fragment>
))}
</div>
)
}

View File

@@ -1,115 +0,0 @@
"use client"
import React, { useMemo } from "react"
import { Button, CurrentItemsState, useMainNav, useSidebar } from "../../.."
import clsx from "clsx"
import Link from "next/link"
import { SidebarItemLink } from "types"
export const MainNavBreadcrumbs = () => {
const { currentItems, activeItem } = useSidebar()
const {
activeItem: mainNavActiveItem,
breadcrumbOptions: { showCategories },
} = useMainNav()
const getLinkPath = (item?: SidebarItemLink): string | undefined => {
if (!item) {
return
}
return item.isPathHref ? item.path : `#${item.path}`
}
const getBreadcrumbsOfItem = (
item: CurrentItemsState
): Map<string, string> => {
let tempBreadcrumbItems: Map<string, string> = new Map()
if (item.previousSidebar) {
tempBreadcrumbItems = getBreadcrumbsOfItem(item.previousSidebar)
}
const parentPath =
item.parentItem?.type === "link"
? getLinkPath(item.parentItem)
: (item.parentItem?.type === "category" && showCategories) ||
item.parentItem?.type === "sub-category"
? "#"
: undefined
const firstItemPath =
item.default[0].type === "link"
? getLinkPath(item.default[0])
: (item.default[0].type === "category" && showCategories) ||
item.default[0].type === "sub-category"
? "#"
: undefined
const breadcrumbPath = parentPath || firstItemPath || "/"
if (!mainNavActiveItem?.path.endsWith(breadcrumbPath)) {
tempBreadcrumbItems.set(
breadcrumbPath,
item.parentItem?.childSidebarTitle || item.parentItem?.title || ""
)
}
return tempBreadcrumbItems
}
const breadcrumbItems = useMemo(() => {
const tempBreadcrumbItems: Map<string, string> = new Map()
if (currentItems) {
getBreadcrumbsOfItem(currentItems).forEach((value, key) =>
tempBreadcrumbItems.set(key, value)
)
}
if (activeItem && !mainNavActiveItem?.path.endsWith(activeItem.path)) {
if (
activeItem.parentItem &&
(activeItem.parentItem.type !== "category" || showCategories)
) {
tempBreadcrumbItems.set(
activeItem.parentItem.type === "link"
? getLinkPath(activeItem.parentItem) || "#"
: "#",
activeItem.parentItem.title || ""
)
}
tempBreadcrumbItems.set(
getLinkPath(activeItem) || "/",
activeItem.title || ""
)
}
return tempBreadcrumbItems
}, [currentItems, activeItem])
return (
<div
className={clsx(
"flex items-center gap-docs_0.25",
"text-medusa-fg-muted text-compact-small"
)}
>
{Array.from(breadcrumbItems).map(([link, title]) => (
<React.Fragment key={link}>
<span>/</span>
<Button
variant="transparent-clear"
className={clsx(
"px-docs_0.5 py-docs_0.25",
link === "#" && "hover:!bg-transparent hover:cursor-default"
)}
>
<Link
href={link}
className={clsx(link === "#" && "hover:cursor-default")}
>
{title}
</Link>
</Button>
</React.Fragment>
))}
</div>
)
}

View File

@@ -1,24 +0,0 @@
"use client"
import React from "react"
import { useColorMode } from "../../../providers"
import { Button, Tooltip } from "../../.."
import { Moon, Sun } from "@medusajs/icons"
import clsx from "clsx"
export const MainNavColorMode = () => {
const { colorMode, toggleColorMode } = useColorMode()
return (
<Tooltip place="bottom" tooltipChildren="Change Theme">
<Button
variant="transparent-clear"
className={clsx("!p-[6.5px] text-medusa-fg-muted")}
onClick={toggleColorMode}
>
{colorMode === "light" && <Sun />}
{colorMode === "dark" && <Moon />}
</Button>
</Tooltip>
)
}

View File

@@ -0,0 +1,73 @@
"use client"
import React from "react"
import { useColorMode } from "../../../../providers"
import clsx from "clsx"
import { EllipseMiniSolid } from "@medusajs/icons"
export const MainNavThemeMenu = () => {
const { colorMode, setColorMode } = useColorMode()
return (
<>
<div
className={clsx(
"flex items-center gap-docs_0.5",
"py-docs_0.25 px-docs_0.5",
"rounded-docs_xs text-compact-x-small-plus",
"text-medusa-fg-subtle"
)}
>
Theme
</div>
<div className="px-docs_0.25">
<div
className={clsx(
"flex items-center gap-docs_0.5",
"py-docs_0.25 px-docs_0.5 cursor-pointer",
"rounded-docs_xs text-medusa-fg-base",
"hover:bg-medusa-bg-component-hover"
)}
tabIndex={-1}
onClick={() => setColorMode("light")}
>
<EllipseMiniSolid
className={clsx(colorMode !== "light" && "invisible")}
/>
<span
className={clsx(
colorMode !== "light" && "text-compact-small",
colorMode === "light" && "text-compact-small-plus"
)}
>
Light
</span>
</div>
</div>
<div className="px-docs_0.25">
<div
className={clsx(
"flex items-center gap-docs_0.5",
"py-docs_0.25 px-docs_0.5 cursor-pointer",
"rounded-docs_xs text-medusa-fg-base",
"hover:bg-medusa-bg-component-hover"
)}
tabIndex={-1}
onClick={() => setColorMode("dark")}
>
<EllipseMiniSolid
className={clsx(colorMode !== "dark" && "invisible")}
/>
<span
className={clsx(
colorMode !== "dark" && "text-compact-small",
colorMode === "dark" && "text-compact-small-plus"
)}
>
Dark
</span>
</div>
</div>
</>
)
}

View File

@@ -0,0 +1,91 @@
"use client"
import {
BarsThree,
QuestionMarkCircle,
SidebarLeft,
TimelineVertical,
} from "@medusajs/icons"
import React, { useRef, useState } from "react"
import {
Button,
getOsShortcut,
Menu,
useClickOutside,
useSidebar,
} from "../../.."
import clsx from "clsx"
import { HouseIcon } from "../../Icons/House"
import { MainNavThemeMenu } from "./ThemeMenu"
export const MainNavDesktopMenu = () => {
const [isOpen, setIsOpen] = useState(false)
const { setDesktopSidebarOpen } = useSidebar()
const ref = useRef(null)
useClickOutside({
elmRef: ref,
onClickOutside: () => setIsOpen(false),
})
return (
<div
className="relative hidden lg:flex justify-center items-center"
ref={ref}
>
<Button
variant="transparent"
onClick={() => setIsOpen((prev) => !prev)}
className="!p-[6.5px]"
>
<BarsThree className="text-medusa-fg-subtle" />
</Button>
<Menu
className={clsx(
"absolute top-[calc(100%+8px)] right-0 min-w-[200px]",
!isOpen && "hidden"
)}
items={[
{
type: "link",
icon: <HouseIcon />,
title: "Homepage",
link: "https://medusajs.com",
},
{
type: "link",
icon: <TimelineVertical />,
title: "Changelog",
link: "https://medusajs.com/changelog",
},
{
type: "link",
icon: <QuestionMarkCircle />,
title: "Troubleshooting",
link: "https://docs.medusajs.com/v2/resources/troubleshooting",
},
{
type: "divider",
},
{
type: "action",
title: "Hide Sidebar",
icon: <SidebarLeft />,
shortcut: `${getOsShortcut()}\\`,
action: () => {
setDesktopSidebarOpen((prev) => !prev)
setIsOpen(false)
},
},
{
type: "divider",
},
{
type: "custom",
content: <MainNavThemeMenu />,
},
]}
/>
</div>
)
}

View File

@@ -1,10 +0,0 @@
import clsx from "clsx"
import React from "react"
export const MainNavDivider = () => {
return (
<span
className={clsx("h-docs_0.75 w-px block bg-medusa-border-base")}
></span>
)
}

View File

@@ -18,7 +18,7 @@ export const MainNavEditDate = ({ date }: MainNavEditDateProps) => {
return (
<>
<span className="text-compact-small">
<span className="text-compact-small-plus">
Edited {dateMatch.groups.month} {dateObj.getDate()}
{dateObj.getFullYear() !== today.getFullYear()
? `, ${dateObj.getFullYear()}`

View File

@@ -1,76 +0,0 @@
"use client"
import React, { useRef, useState } from "react"
import { Button, Kbd, Tooltip } from "@/components"
import { Bug, Discord, QuestionMark } from "@medusajs/icons"
import { Menu, useClickOutside } from "../../.."
import { MenuItem } from "types"
import clsx from "clsx"
import { GithubIcon } from "../../Icons/Github"
export const MainNavHelpButton = () => {
const [showMenu, setShowMenu] = useState(false)
const ref = useRef<HTMLDivElement>(null)
useClickOutside({
elmRef: ref,
onClickOutside: () => {
setShowMenu(false)
},
})
const menuItems: MenuItem[] = [
{
type: "link",
title: "Create a GitHub Issue",
link: "https://github.com/medusajs/medusa/issues/new/choose",
icon: <GithubIcon className="text-medusa-fg-base" />,
},
{
type: "link",
title: "Get Support on Discord",
link: "https://discord.gg/medusajs",
icon: <Discord />,
},
{
type: "divider",
},
{
type: "link",
title: "Troubleshooting Guides",
link: "https://docs.medusajs.com/v2/resources/troubleshooting",
icon: <Bug />,
},
]
return (
<div className="relative" ref={ref}>
<Tooltip
tooltipChildren={
<span className="flex gap-[6px] items-center">
<span>Need help?</span>
<Kbd className="w-docs_1 h-docs_1">?</Kbd>
</span>
}
place="bottom"
hidden={showMenu}
>
<Button
variant="transparent-clear"
className="text-medusa-fg-muted !px-[6.5px] !py-[6.5px]"
onClick={() => setShowMenu((prev) => !prev)}
>
<QuestionMark />
</Button>
</Tooltip>
<div
className={clsx(
"absolute bottom-0 left-0",
"z-50 -translate-x-[40%] translate-y-[calc(100%+8px)]",
!showMenu && "hidden"
)}
>
<Menu items={menuItems} className="w-[200px]" />
</div>
</div>
)
}

View File

@@ -0,0 +1,55 @@
"use client"
import { TriangleDownMini } from "@medusajs/icons"
import clsx from "clsx"
import React, { useRef, useState } from "react"
import { NavigationItemDropdown } from "types"
import { Menu, useClickOutside } from "../../../.."
type MainNavItemDropdownProps = {
item: NavigationItemDropdown
isActive: boolean
}
export const MainNavItemDropdown = ({
item,
isActive,
}: MainNavItemDropdownProps) => {
const [isOpen, setIsOpen] = useState(false)
const ref = useRef(null)
useClickOutside({
elmRef: ref,
onClickOutside: () => setIsOpen(false),
})
return (
<div className={clsx("relative")} ref={ref}>
<div
className={clsx(
"cursor-pointer flex gap-docs_0.25 items-center",
isActive && "text-medusa-fg-base",
!isActive && [
"text-medusa-fg-muted hover:text-medusa-fg-subtle",
isOpen && "text-medusa-fg-subtle",
]
)}
tabIndex={-1}
onClick={() => setIsOpen((prev) => !prev)}
>
<span className="text-compact-small-plus">{item.title}</span>
<TriangleDownMini
className={clsx("transition-transform", isOpen && "rotate-180")}
/>
</div>
<Menu
className={clsx(
"absolute top-[calc(100%+4px)] -left-docs_0.75 min-w-[190px]",
!isOpen && "hidden"
)}
items={item.children}
itemsOnClick={() => setIsOpen(false)}
/>
</div>
)
}

View File

@@ -0,0 +1,25 @@
"use client"
import React from "react"
import { NavigationItemLink } from "types"
import { LinkButton } from "../../../.."
import clsx from "clsx"
type MainNavItemLinkProps = {
item: NavigationItemLink
isActive: boolean
}
export const MainNavItemLink = ({ item, isActive }: MainNavItemLinkProps) => {
return (
<LinkButton
href={item.path}
className={clsx(
isActive && "text-medusa-fg-base",
!isActive && "text-medusa-fg-muted hover:text-medusa-fg-subtle"
)}
>
{item.title}
</LinkButton>
)
}

View File

@@ -0,0 +1,35 @@
"use client"
import React from "react"
import { useMainNav } from "../../.."
import clsx from "clsx"
import { MainNavItemLink } from "./Link"
import { MainNavItemDropdown } from "./Dropdown"
export const MainNavItems = () => {
const { navItems, activeItemIndex } = useMainNav()
return (
<ul
className={clsx(
"hidden lg:flex justify-start gap-docs_1 items-center",
"my-docs_0.75"
)}
>
{navItems.map((item, index) => {
const isActive = index === activeItemIndex
return (
<li className={clsx("flex items-center group")} key={index}>
{item.type === "link" && (
<MainNavItemLink item={item} isActive={isActive} />
)}
{item.type === "dropdown" && (
<MainNavItemDropdown item={item} isActive={isActive} />
)}
</li>
)
})}
</ul>
)
}

View File

@@ -0,0 +1,59 @@
"use client"
import React from "react"
import { useMainNav } from "../../../../providers"
import Link from "next/link"
import { TriangleRightMini } from "@medusajs/icons"
import clsx from "clsx"
import { SelectedMenu } from ".."
type MainNavMobileMainMenu = {
setSelectedMenu: (menu: SelectedMenu) => void
}
export const MainNavMobileMainMenu = ({
setSelectedMenu,
}: MainNavMobileMainMenu) => {
const { navItems } = useMainNav()
return (
<div className="flex flex-col gap-[23px]">
<span className="text-compact-small-plus text-medusa-fg-muted uppercase">
Menu
</span>
<ul className="flex flex-col gap-[18px]">
{navItems.map((item, index) => (
<li
key={index}
className={clsx(
"text-h1 text-medusa-fg-base cursor-pointer",
"flex justify-between gap-docs_1"
)}
onClick={() => {
if (item.type !== "dropdown") {
return
}
setSelectedMenu({
title: item.title,
menu: item.children,
})
}}
>
{item.type === "link" && (
<Link href={item.path} className="block w-full">
{item.title}
</Link>
)}
{item.type === "dropdown" && (
<>
<span>{item.title}</span>
<TriangleRightMini />
</>
)}
</li>
))}
</ul>
</div>
)
}

View File

@@ -0,0 +1,42 @@
"use client"
import clsx from "clsx"
import Link from "next/link"
import React, { useMemo } from "react"
import { MenuItem, MenuItemLink } from "types"
type MainNavMobileSubMenuProps = {
menu: MenuItem[]
title: string
}
export const MainNavMobileSubMenu = ({
menu,
title,
}: MainNavMobileSubMenuProps) => {
const filteredItems: MenuItemLink[] = useMemo(() => {
return menu.filter((item) => item.type === "link") as MenuItemLink[]
}, [menu])
return (
<div className="flex flex-col gap-[23px]">
<span className="text-compact-small-plus text-medusa-fg-muted uppercase">
{title}
</span>
<ul className="flex flex-col gap-[18px]">
{filteredItems.map((item, index) => (
<li
key={index}
className={clsx(
"text-h1 text-medusa-fg-base cursor-pointer",
"flex justify-between gap-docs_1"
)}
>
<Link href={item.link} className="block w-full">
{item.title}
</Link>
</li>
))}
</ul>
</div>
)
}

View File

@@ -0,0 +1,77 @@
"use client"
import React, { useRef, useState } from "react"
import { Button } from "../../Button"
import { ArrowUturnLeft, BarsThree, XMark } from "@medusajs/icons"
import clsx from "clsx"
import { MenuItem } from "types"
import { CSSTransition, SwitchTransition } from "react-transition-group"
import { MainNavMobileMainMenu } from "./Main"
import { MainNavMobileSubMenu } from "./SubMenu"
export type SelectedMenu = {
title: string
menu: MenuItem[]
}
export const MainNavMobileMenu = () => {
const [isOpen, setIsOpen] = useState(false)
const [selectedMenu, setSelectedMenu] = useState<SelectedMenu>()
const ref = useRef(null)
return (
<div className="flex lg:hidden justify-center items-center">
<Button
variant="transparent"
onClick={() => setIsOpen((prev) => !prev)}
className="text-medusa-fg-subtle !p-[6.5px]"
>
{!isOpen && <BarsThree />}
{isOpen && <XMark />}
</Button>
<div
className={clsx(
"flex items-center justify-center fixed w-full h-[calc(100vh-52px)]",
"top-[52px] transition-[left] bg-medusa-bg-subtle z-50",
!isOpen && "-left-full",
isOpen && "left-0"
)}
>
<SwitchTransition>
<CSSTransition
key={!selectedMenu ? "main" : "sub"}
classNames={{
enter: "animate-fadeInLeft animate-fast",
exit: "animate-fadeOutRight animate-fast",
}}
nodeRef={ref}
timeout={250}
>
<div ref={ref} className="w-full px-docs_1.5">
{!selectedMenu && (
<MainNavMobileMainMenu setSelectedMenu={setSelectedMenu} />
)}
{selectedMenu && (
<>
<div
className={clsx(
"flex items-center gap-docs_0.5",
"text-medusa-fg-base my-[14px]",
"cursor-pointer"
)}
tabIndex={-1}
onClick={() => setSelectedMenu(undefined)}
>
<ArrowUturnLeft />
<span className="text-h1">Back</span>
</div>
<MainNavMobileSubMenu {...selectedMenu} />
</>
)}
</div>
</CSSTransition>
</SwitchTransition>
</div>
</div>
)
}

View File

@@ -1,21 +0,0 @@
import React from "react"
import { BorderedIcon } from "@/components"
import { IconProps } from "@medusajs/icons/dist/types"
export type MainNavigationDropdownIconProps = {
icon: React.FC<IconProps>
inDropdown?: boolean
}
export const MainNavigationDropdownIcon = ({
icon,
inDropdown = false,
}: MainNavigationDropdownIconProps) => {
return (
<BorderedIcon
IconComponent={icon}
iconClassName={inDropdown ? "w-docs_1 h-docs_1" : ""}
iconWrapperClassName="rounded-docs_xs"
/>
)
}

View File

@@ -1,50 +0,0 @@
"use client"
import React from "react"
import { NavigationDropdownItem } from "types"
import Link from "next/link"
import clsx from "clsx"
import { EllipseMiniSolid } from "@medusajs/icons"
import { MainNavigationDropdownIcon } from "../../Icon"
import { DottedSeparator } from "../../../.."
export type MainNavigationDropdownMenuItemProps = {
item: NavigationDropdownItem
onSelect: () => void
}
export const MainNavigationDropdownMenuItem = ({
item,
onSelect,
}: MainNavigationDropdownMenuItemProps) => {
switch (item.type) {
case "divider":
return <DottedSeparator className="my-docs_0.25" />
case "link":
return (
<Link
href={item.path}
className={clsx(
"hover:bg-medusa-bg-component-hover",
"block rounded-docs_xs"
)}
prefetch={false}
onClick={onSelect}
>
<li
className={clsx(
"px-docs_0.5 py-docs_0.25",
"rounded-docs_xs text-medusa-fg-base",
"flex gap-docs_0.5 justify-start items-center"
)}
>
<span className={clsx(!item.isActive && "invisible")}>
<EllipseMiniSolid />
</span>
<MainNavigationDropdownIcon icon={item.icon} inDropdown={true} />
<span className="whitespace-nowrap">{item.title}</span>
</li>
</Link>
)
}
}

View File

@@ -1,36 +0,0 @@
"use client"
import clsx from "clsx"
import React from "react"
import { NavigationDropdownItem } from "types"
import { MainNavigationDropdownMenuItem } from "./Item"
export type MainNavigationDropdownMenuProps = {
items: NavigationDropdownItem[]
open: boolean
onSelect: () => void
}
export const MainNavigationDropdownMenu = ({
items,
open,
onSelect,
}: MainNavigationDropdownMenuProps) => {
return (
<ul
className={clsx(
"absolute top-[calc(100%+4px)] p-docs_0.25 z-50 lg:-left-docs_0.25",
"bg-medusa-bg-component rounded shadow-elevation-flyout",
!open && "hidden"
)}
>
{items.map((item, index) => (
<MainNavigationDropdownMenuItem
item={item}
key={index}
onSelect={onSelect}
/>
))}
</ul>
)
}

View File

@@ -1,49 +0,0 @@
"use client"
import React from "react"
import { NavigationDropdownItem } from "types"
import { TrianglesMini } from "@medusajs/icons"
import clsx from "clsx"
import { MainNavigationDropdownIcon } from "../Icon"
export type MainNavigationDropdownSelectedProps = {
item: NavigationDropdownItem
onClick: () => void
isActive: boolean
}
export const MainNavigationDropdownSelected = ({
item,
onClick,
isActive,
}: MainNavigationDropdownSelectedProps) => {
if (item.type === "divider") {
return <></>
}
return (
<div
className={clsx(
"flex justify-between items-center gap-docs_0.25",
"cursor-pointer rounded-docs_sm group"
)}
tabIndex={-1}
onClick={onClick}
>
<MainNavigationDropdownIcon icon={item.icon} />
<div
className={clsx(
"flex gap-[6px] py-docs_0.25 px-docs_0.5",
"items-center group-hover:bg-medusa-button-transparent-hover",
"rounded-docs_sm",
isActive && "bg-medusa-button-transparent-hover"
)}
>
<span className="text-medusa-fg-base whitespace-nowrap flex-1 text-compact-small-plus">
{item.title}
</span>
<TrianglesMini className="text-medusa-fg-muted" />
</div>
</div>
)
}

View File

@@ -1,55 +0,0 @@
"use client"
import clsx from "clsx"
import React, { useMemo, useRef, useState } from "react"
import { MainNavigationDropdownSelected } from "./Selected"
import { MainNavigationDropdownMenu } from "./Menu"
import { useAnalytics, useClickOutside, useMainNav } from "../../.."
export const MainNavigationDropdown = () => {
const { navItems: items } = useMainNav()
const navigationRef = useRef<HTMLDivElement>(null)
const [menuOpen, setMenuOpen] = useState(false)
const { track } = useAnalytics()
useClickOutside({
elmRef: navigationRef,
onClickOutside: () => {
setMenuOpen(false)
},
})
const selectedItem = useMemo(() => {
const activeItem = items.find(
(item) => item.type === "link" && item.isActive
)
if (!activeItem) {
return items.length ? items[0] : undefined
}
return activeItem
}, [items])
return (
<div className={clsx("relative z-50")} ref={navigationRef}>
{selectedItem && (
<MainNavigationDropdownSelected
item={selectedItem}
onClick={() => {
setMenuOpen((prev) => !prev)
if (!menuOpen) {
track("nav_main_open", {
url: window.location.href,
})
}
}}
isActive={menuOpen}
/>
)}
<MainNavigationDropdownMenu
items={items}
open={menuOpen}
onSelect={() => setMenuOpen(false)}
/>
</div>
)
}

View File

@@ -1,24 +0,0 @@
"use client"
import React from "react"
import { Button, useSidebar } from "../../.."
import clsx from "clsx"
import { SidebarLeft } from "@medusajs/icons"
export const MainNavSidebarOpener = () => {
const { desktopSidebarOpen, setDesktopSidebarOpen } = useSidebar()
if (desktopSidebarOpen) {
return <></>
}
return (
<Button
variant="transparent-clear"
className={clsx("!p-[6.5px] text-medusa-fg-muted", "mr-docs_0.5")}
onClick={() => setDesktopSidebarOpen(true)}
>
<SidebarLeft />
</Button>
)
}

View File

@@ -2,57 +2,65 @@
import clsx from "clsx"
import React from "react"
import { MainNavigationDropdown } from "./NavigationDropdown"
import { MainNavBreadcrumbs } from "./Breadcrumb"
import {
BorderedIcon,
Button,
LinkButton,
SearchModalOpener,
useMainNav,
useSidebar,
} from "../.."
import { MainNavColorMode } from "./ColorMode"
import { MainNavDivider } from "./Divider"
import { MainNavSidebarOpener } from "./SidebarOpener"
import { MainNavHelpButton } from "./HelpButton"
import { SidebarLeftIcon } from "../Icons/SidebarLeft"
import { MainNavEditDate } from "./EditDate"
import { MainNavItems } from "./Items"
import { MedusaIcon } from "../Icons/MedusaLogo"
import { MainNavDesktopMenu } from "./DesktopMenu"
import { SidebarLeftIcon } from "../Icons/SidebarLeft"
import { MainNavMobileMenu } from "./MobileMenu"
export const MainNav = () => {
const { reportIssueLink, editDate } = useMainNav()
const { setMobileSidebarOpen } = useSidebar()
return (
<div
className={clsx(
"hidden sm:flex justify-between items-center",
"px-docs_1 py-docs_0.75 w-full z-20",
"flex justify-between items-center",
"px-docs_1 w-full z-20",
"sticky top-0 bg-medusa-bg-base"
)}
>
<div className="flex items-center gap-docs_0.25">
<Button
className="lg:hidden"
variant="transparent-clear"
onClick={() => setMobileSidebarOpen(true)}
>
<SidebarLeftIcon />
</Button>
<MainNavSidebarOpener />
<MainNavigationDropdown />
<MainNavBreadcrumbs />
<div className="flex items-center gap-docs_1">
<div className="flex items-center gap-[10px]">
<Button
className="lg:hidden my-docs_0.75 !p-[6.5px]"
variant="transparent-clear"
onClick={() => setMobileSidebarOpen(true)}
>
<SidebarLeftIcon />
</Button>
<BorderedIcon
IconComponent={MedusaIcon}
iconWrapperClassName="my-[14px]"
/>
</div>
<MainNavItems />
</div>
<div className="flex items-center gap-docs_0.75">
<div className="flex items-center gap-[6px] text-medusa-fg-muted">
<div className="flex items-center gap-docs_0.75 my-docs_0.75">
<div className="lg:flex items-center gap-docs_0.5 text-medusa-fg-subtle hidden">
{editDate && <MainNavEditDate date={editDate} />}
<LinkButton href={reportIssueLink} variant="muted" target="_blank">
<LinkButton
href={reportIssueLink}
variant="subtle"
target="_blank"
className="text-compact-small-plus"
>
Report Issue
</LinkButton>
</div>
<MainNavDivider />
<div className="flex items-center gap-docs_0.25">
<MainNavHelpButton />
<MainNavColorMode />
<SearchModalOpener />
<MainNavDesktopMenu />
<MainNavMobileMenu />
</div>
</div>
</div>

View File

@@ -2,13 +2,14 @@
import clsx from "clsx"
import React from "react"
import { MenuItemAction } from "types"
import { MenuItem, MenuItemAction } from "types"
export type MenuActionProps = {
item: MenuItemAction
onClick?: (item: MenuItem) => void
}
export const MenuAction = ({ item }: MenuActionProps) => {
export const MenuAction = ({ item, onClick }: MenuActionProps) => {
return (
<div className="px-docs_0.25">
<span
@@ -19,7 +20,10 @@ export const MenuAction = ({ item }: MenuActionProps) => {
"text-medusa-fg-base cursor-pointer"
)}
tabIndex={-1}
onClick={item.action}
onClick={() => {
item.action()
onClick?.(item)
}}
>
<span className="text-medusa-fg-subtle mt-[2.5px] block">
{item.icon}

View File

@@ -3,13 +3,14 @@
import clsx from "clsx"
import Link from "next/link"
import React from "react"
import { MenuItemLink } from "types"
import { MenuItem as MenuItemType, MenuItemLink } from "types"
export type MenuItemProps = {
item: MenuItemLink
onClick?: (item: MenuItemType) => void
}
export const MenuItem = ({ item }: MenuItemProps) => {
export const MenuItem = ({ item, onClick }: MenuItemProps) => {
return (
<div className="px-docs_0.25">
<Link
@@ -20,10 +21,13 @@ export const MenuItem = ({ item }: MenuItemProps) => {
"text-medusa-fg-base"
)}
href={item.link}
onClick={() => onClick?.(item)}
>
<span className="text-medusa-fg-subtle mt-[2.5px] block">
{item.icon}
</span>
{item.icon && (
<span className="text-medusa-fg-subtle mt-[2.5px] block">
{item.icon}
</span>
)}
<span className="text-compact-small">{item.title}</span>
</Link>
</div>

View File

@@ -8,9 +8,10 @@ import { MenuAction } from "./Action"
export type MenuProps = {
items: MenuItemType[]
className?: string
itemsOnClick?: (item: MenuItemType) => void
}
export const Menu = ({ items, className }: MenuProps) => {
export const Menu = ({ items, className, itemsOnClick }: MenuProps) => {
return (
<div
className={clsx(
@@ -21,9 +22,14 @@ export const Menu = ({ items, className }: MenuProps) => {
>
{items.map((item, index) => (
<React.Fragment key={index}>
{item.type === "link" && <MenuItem item={item} />}
{item.type === "action" && <MenuAction item={item} />}
{item.type === "link" && (
<MenuItem item={item} onClick={itemsOnClick} />
)}
{item.type === "action" && (
<MenuAction item={item} onClick={itemsOnClick} />
)}
{item.type === "divider" && <MenuDivider />}
{item.type === "custom" && item.content}
</React.Fragment>
))}
</div>

View File

@@ -1,32 +0,0 @@
"use client"
import clsx from "clsx"
import React from "react"
import { SidebarLeftIcon } from "../Icons/SidebarLeft"
import { Button, SearchModalOpener, useSidebar } from "../.."
import { MainNavigationDropdown } from "../MainNav/NavigationDropdown"
export const MobileNavigation = () => {
const { setMobileSidebarOpen } = useSidebar()
return (
<div
className={clsx(
"sm:hidden bg-medusa-bg-base",
"sticky top-0 w-full z-40 h-min",
"px-docs_0.75 py-docs_0.5",
"flex justify-between items-center",
"border-b border-medusa-border-base"
)}
>
<Button
variant="transparent-clear"
onClick={() => setMobileSidebarOpen(true)}
>
<SidebarLeftIcon />
</Button>
<MainNavigationDropdown />
<SearchModalOpener />
</div>
)
}

View File

@@ -1,9 +1,8 @@
"use client"
import React, { MouseEvent, useMemo } from "react"
import clsx from "clsx"
import { useMobile, useSearch } from "@/providers"
import { Button, Tooltip } from "@/components"
import React, { MouseEvent } from "react"
import { useSearch } from "@/providers"
import { Button } from "@/components"
import { MagnifyingGlass } from "@medusajs/icons"
import { useKeyboardShortcut } from "@/hooks"
@@ -14,15 +13,8 @@ export type SearchModalOpenerProps = {
export const SearchModalOpener = ({
isLoading = false,
className,
}: SearchModalOpenerProps) => {
const { isMobile } = useMobile()
const { setIsOpen } = useSearch()
const isApple = useMemo(() => {
return typeof navigator !== "undefined"
? navigator.userAgent.toLowerCase().indexOf("mac") !== 0
: true
}, [])
useKeyboardShortcut({
shortcutKeys: ["k"],
action: () => setIsOpen((prev) => !prev),
@@ -46,29 +38,12 @@ export const SearchModalOpener = ({
}
return (
<>
{isMobile && (
<Button variant="transparent" onClick={handleOpen}>
<MagnifyingGlass className="text-medusa-fg-muted" />
</Button>
)}
{!isMobile && (
<Tooltip place="bottom" tooltipChildren="Ask or search">
<Button
className={clsx(
"relative hover:cursor-pointer group",
"flex gap-[6px] !py-docs_0.25 !px-docs_0.5",
"justify-between items-center text-medusa-fg-muted",
className
)}
variant="transparent-clear"
onClick={handleOpen}
>
<MagnifyingGlass />
<span>{isApple ? "⌘" : "Ctrl"}K</span>
</Button>
</Tooltip>
)}
</>
<Button
variant="transparent"
onClick={handleOpen}
className="hidden sm:flex !p-[6.5px]"
>
<MagnifyingGlass className="text-medusa-fg-subtle" />
</Button>
)
}

View File

@@ -4,7 +4,6 @@ import React from "react"
import { SidebarChild } from "../Child"
import { InteractiveSidebarItem } from "types"
import { SidebarTopMobileClose } from "./MobileClose"
import { SidebarTopMedusaMenu } from "./MedusaMenu"
import { DottedSeparator } from "../../.."
export type SidebarTopProps = {
@@ -17,14 +16,12 @@ export const SidebarTop = React.forwardRef<HTMLDivElement, SidebarTopProps>(
<div className="pt-docs_0.25">
<SidebarTopMobileClose />
<div ref={ref}>
<SidebarTopMedusaMenu />
{parentItem && (
<>
<DottedSeparator />
<SidebarChild item={parentItem} />
<DottedSeparator wrapperClassName="!my-0" />
</>
)}
<DottedSeparator wrapperClassName="!my-0" />
</div>
</div>
)

View File

@@ -118,8 +118,8 @@ export const Sidebar = ({
}}
id="sidebar"
>
{/* MOBILE SIDEBAR */}
<div className={clsx("lg:hidden")}>
{/* MOBILE SIDEBAR - keeping this in case we need it in the future */}
{/* <div className={clsx("lg:hidden")}>
{!sidebarItems.mobile.length && !staticSidebarItems && (
<Loading className="px-0" />
)}
@@ -131,8 +131,8 @@ export const Sidebar = ({
hasNextItems={index !== sidebarItems.default.length - 1}
/>
))}
<DottedSeparator />
</div>
{sidebarItems.mobile.length > 0 && <DottedSeparator />}
</div> */}
{/* DESKTOP SIDEBAR */}
<div className="mt-docs_0.75 lg:mt-0">
{!sidebarItems.default.length && !staticSidebarItems && (

View File

@@ -1,195 +1,118 @@
import React from "react"
import { Badge } from "@/components"
import { OptionType } from "@/hooks"
import { NavigationDropdownItem, SidebarItem } from "types"
import { NavigationDropdownDocIcon } from "./components/Icons/NavigationDropdown/Doc"
import { NavigationDropdownStoreIcon } from "./components/Icons/NavigationDropdown/Store"
import { NavigationDropdownAdminIcon } from "./components/Icons/NavigationDropdown/Admin"
import { NavigationDropdownUiIcon } from "./components/Icons/NavigationDropdown/Ui"
import { NavigationDropdownDocV1Icon } from "./components/Icons/NavigationDropdown/DocV1"
import { NavigationDropdownUserIcon } from "./components/Icons/NavigationDropdown/User"
import { NavigationDropdownResourcesIcon } from "./components/Icons/NavigationDropdown/Resources"
import { NavigationDropdownModulesIcon } from "./components/Icons/NavigationDropdown/Modules"
import { NavigationItem } from "types"
export const GITHUB_ISSUES_PREFIX = `https://github.com/medusajs/medusa/issues/new?assignees=&labels=type%3A+docs&template=docs.yml`
export const GITHUB_UI_ISSUES_PREFIX = `https://github.com/medusajs/ui/issues/new?labels=documentation`
export const navDropdownItemsV2: NavigationDropdownItem[] = [
export const navDropdownItems: NavigationItem[] = [
{
type: "link",
path: `/v2`,
icon: NavigationDropdownDocIcon,
title: "Documentation",
title: "Get Started",
project: "book",
},
{
type: "link",
path: `/v2/resources/commerce-modules`,
icon: NavigationDropdownModulesIcon,
title: "Commerce Modules",
type: "dropdown",
title: "Product",
children: [
{
type: "link",
title: "Commerce Modules",
link: "/v2/resources/commerce-modules",
},
{
type: "link",
title: "Architectural Modules",
link: "/v2/resources/architectural-modules",
},
],
},
{
type: "link",
path: `/v2/resources`,
icon: NavigationDropdownResourcesIcon,
title: "Development Resources",
type: "dropdown",
title: "Resources",
children: [
{
type: "link",
title: "Guides",
link: "/v2/resources",
useAsFallback: true,
},
{
type: "link",
title: "Recipes",
link: "/v2/resources/recipes",
},
{
type: "link",
title: "UI Library",
link: "/ui",
},
{
type: "divider",
},
{
type: "link",
title: "Storefront Development",
link: "/v2/resources/storefront-development",
},
],
},
{
type: "link",
path: `/v2/api/store`,
icon: NavigationDropdownStoreIcon,
title: "Store API",
type: "dropdown",
title: "Tools & SDKs",
children: [
{
type: "link",
title: "Medusa CLI",
link: "/v2/resources/medusa-cli",
},
{
type: "link",
title: "Next.js Starter",
link: "/v2/resources/nextjs-starter",
},
{
type: "divider",
},
{
type: "link",
title: "Integrations",
link: "/v2/resources/integrations",
},
],
},
{
type: "link",
path: `/v2/api/admin`,
icon: NavigationDropdownAdminIcon,
title: "Admin API",
},
{
type: "link",
path: `/ui`,
icon: NavigationDropdownUiIcon,
title: "UI",
},
{
type: "link",
path: `/`,
icon: NavigationDropdownDocV1Icon,
title: "Medusa v1",
type: "dropdown",
title: "Reference",
children: [
{
type: "link",
title: "Admin API",
link: "/v2/api/admin",
},
{
type: "link",
title: "Store API",
link: "/v2/api/store",
},
{
type: "divider",
},
{
type: "link",
title: "Workflows",
link: "/v2/resources/medusa-workflows-reference",
},
{
type: "link",
title: "Data Model API",
link: "/v2/resources/references/data-model",
},
],
},
]
export const navDropdownItemsV1: NavigationDropdownItem[] = [
{
type: "link",
path: `/`,
icon: NavigationDropdownDocV1Icon,
title: "Documentation",
},
{
type: "link",
path: `/user-guide`,
icon: NavigationDropdownUserIcon,
title: "User Guide",
},
{
type: "link",
path: `/api/store`,
icon: NavigationDropdownStoreIcon,
title: "Store API",
},
{
type: "link",
path: `/api/admin`,
icon: NavigationDropdownAdminIcon,
title: "Admin API",
},
{
type: "link",
path: `/ui`,
icon: NavigationDropdownUiIcon,
title: "UI",
},
{
type: "link",
path: `/v2`,
icon: NavigationDropdownDocIcon,
title: "Medusa v2",
},
]
export const mobileSidebarItemsV1: SidebarItem[] = [
{
type: "link",
title: "Docs",
path: `/`,
loaded: true,
isPathHref: true,
},
{
type: "link",
title: "User Guide",
path: `/user-guide`,
loaded: true,
isPathHref: true,
},
{
type: "link",
title: "Store API",
path: `/api/store`,
loaded: true,
isPathHref: true,
},
{
type: "link",
title: "Admin API",
path: `/api/admin`,
loaded: true,
isPathHref: true,
},
{
type: "link",
title: "UI",
path: `/ui`,
loaded: true,
isPathHref: true,
},
{
type: "link",
title: "Learn Medusa V2",
path: `/v2`,
loaded: true,
isPathHref: true,
additionalElms: <Badge variant="blue">v2</Badge>,
},
]
export const mobileSidebarItemsV2: SidebarItem[] = [
{
type: "link",
title: "Docs",
path: `/v2`,
loaded: true,
isPathHref: true,
},
{
type: "link",
title: "Learning Resources",
path: `/v2/resources`,
loaded: true,
isPathHref: true,
},
{
type: "link",
title: "Store API",
path: `/v2/api/store`,
loaded: true,
isPathHref: true,
},
{
type: "link",
title: "Admin API",
path: `/v2/api/admin`,
loaded: true,
isPathHref: true,
},
{
type: "link",
title: "UI",
path: `/ui`,
loaded: true,
isPathHref: true,
},
{
type: "link",
title: "Medusa v1",
path: `/`,
loaded: true,
isPathHref: true,
},
]
export const searchFiltersV2: OptionType[] = [
export const searchFilters: OptionType[] = [
{
value: "guides",
label: "Guides",
@@ -206,36 +129,9 @@ export const searchFiltersV2: OptionType[] = [
value: "store-v2",
label: "Store API (v2)",
},
// TODO add more filters
]
export const searchFiltersV1: OptionType[] = [
{
value: "admin",
label: "Admin API",
},
{
value: "store",
label: "Store API",
},
{
value: "docs",
label: "Docs",
},
{
value: "user-guide",
label: "User Guide",
},
{
value: "plugins",
label: "Plugins",
},
{
value: "reference",
label: "References",
},
{
value: "ui",
label: "UI",
label: "Medusa UI",
},
// TODO add more filters
]

View File

@@ -50,7 +50,8 @@ export const MainContentLayout = ({
"flex-col items-center",
"h-full w-full",
"overflow-y-scroll overflow-x-hidden",
"md:rounded-t-docs_DEFAULT shadow-elevation-card-rest",
"md:rounded-t-docs_DEFAULT",
"shadow-elevation-card-rest dark:shadow-elevation-card-rest-dark",
mainWrapperClasses
)}
id="main"

View File

@@ -1,7 +1,6 @@
import React from "react"
import clsx from "clsx"
import { RootProviders, Sidebar, SidebarProps } from "@/components"
import { MobileNavigation } from "../components/MobileNavigation"
import { Toc } from "../components/Toc"
import { MainContentLayout, MainContentLayoutProps } from "./main-content"
@@ -14,6 +13,7 @@ export type RootLayoutProps = {
showPagination?: boolean
feedbackComponent?: React.ReactNode
editComponent?: React.ReactNode
showBreadcrumbs?: boolean
} & MainContentLayoutProps
export const RootLayout = ({
@@ -38,7 +38,6 @@ export const RootLayout = ({
>
<RootProviders>
<ProvidersComponent>
<MobileNavigation />
<Sidebar {...sidebarProps} />
<div className={clsx("relative", "h-screen", "flex")}>
<MainContentLayout {...mainProps} />

View File

@@ -1,13 +1,14 @@
import React from "react"
import { RootLayout, RootLayoutProps } from "./root"
import clsx from "clsx"
import { Pagination } from ".."
import { Breadcrumbs, Pagination } from ".."
export const TightLayout = ({
children,
showPagination,
feedbackComponent,
editComponent,
showBreadcrumbs = true,
...props
}: RootLayoutProps) => {
return (
@@ -21,6 +22,7 @@ export const TightLayout = ({
"px-docs_1 md:px-docs_4 lg:px-0"
)}
>
{showBreadcrumbs && <Breadcrumbs />}
{children}
{feedbackComponent}
{showPagination && <Pagination />}

View File

@@ -1,13 +1,14 @@
import React from "react"
import { RootLayout, RootLayoutProps } from "./root"
import clsx from "clsx"
import { Pagination } from ".."
import { Breadcrumbs, Pagination } from ".."
export const WideLayout = ({
children,
showPagination,
feedbackComponent,
editComponent,
showBreadcrumbs = true,
...props
}: RootLayoutProps) => {
return (
@@ -22,6 +23,7 @@ export const WideLayout = ({
"lg:max-w-lg-wide-content xl:max-w-xl-wide-content relative mt-4 w-full flex-1 lg:mt-7"
)}
>
{showBreadcrumbs && <Breadcrumbs />}
{children}
{feedbackComponent}
{showPagination && <Pagination />}

View File

@@ -1,27 +1,24 @@
"use client"
import { usePathname } from "next/navigation"
import React, { createContext, useContext, useMemo } from "react"
import {
BreadcrumbOptions,
NavigationDropdownItem,
NavigationDropdownItemLink,
} from "types"
import { NavigationItem } from "types"
import { useSiteConfig } from "../SiteConifg"
export type MainNavContext = {
navItems: NavigationDropdownItem[]
activeItem?: NavigationDropdownItemLink
navItems: NavigationItem[]
activeItemIndex?: number
activeItem?: NavigationItem
reportIssueLink: string
editDate?: string
breadcrumbOptions: BreadcrumbOptions
}
const MainNavContext = createContext<MainNavContext | null>(null)
export type MainNavProviderProps = {
navItems: NavigationDropdownItem[]
navItems: NavigationItem[]
reportIssueLink: string
editDate?: string
breadcrumbOptions?: BreadcrumbOptions
children?: React.ReactNode
}
@@ -30,26 +27,72 @@ export const MainNavProvider = ({
reportIssueLink,
children,
editDate,
breadcrumbOptions = {
showCategories: true,
},
}: MainNavProviderProps) => {
const activeItem = useMemo(
() =>
navItems.find(
(item) => item.type === "link" && item.isActive
) as NavigationDropdownItemLink,
[navItems]
)
const pathname = usePathname()
const { config } = useSiteConfig()
const baseUrl = `${config.baseUrl}${config.basePath}`
const activeItemIndex = useMemo(() => {
const currentUrl = `${baseUrl}${pathname}`.replace(/\/$/, "")
let fallbackIndex: number | undefined
const index = navItems.findIndex((item, index) => {
if (item.type === "dropdown") {
return item.children.some((childItem) => {
if (childItem.type !== "link") {
return
}
const isItemActive = currentUrl.startsWith(childItem.link)
if (
isItemActive &&
childItem.useAsFallback &&
fallbackIndex === undefined
) {
fallbackIndex = index
return false
}
return isItemActive
})
}
if (item.project && item.project !== config.project.key) {
return false
}
const isItemActive = currentUrl.startsWith(item.path)
if (isItemActive && item.useAsFallback && fallbackIndex === undefined) {
fallbackIndex = index
return false
}
return isItemActive
})
return index !== -1 ? index : fallbackIndex
}, [navItems, pathname, baseUrl, config])
const activeItem = useMemo(() => {
if (activeItemIndex === undefined) {
return
}
return navItems[activeItemIndex]
}, [navItems, activeItemIndex])
return (
<MainNavContext.Provider
value={{
navItems,
activeItem,
activeItemIndex,
reportIssueLink,
editDate,
breadcrumbOptions,
activeItem,
}}
>
{children}

View File

@@ -26,6 +26,13 @@ export const SiteConfigProvider = ({
default: [],
mobile: [],
},
project: {
title: "",
key: "",
},
breadcrumbOptions: {
showCategories: true,
},
}
)

View File

@@ -1,25 +0,0 @@
import { SidebarItem } from "types"
import { mobileSidebarItemsV1, mobileSidebarItemsV2 } from ".."
type Options = {
baseUrl: string
version?: "v1" | "v2"
}
export function getMobileSidebarItems({
baseUrl,
version = "v1",
}: Options): SidebarItem[] {
const mobileItems =
version === "v2" ? mobileSidebarItemsV2 : mobileSidebarItemsV1
return mobileItems.map((item) => {
if (item.type !== "link") {
return item
}
return {
...item,
path: `${baseUrl}${item.path}`,
}
})
}

View File

@@ -1,27 +1,31 @@
import { NavigationDropdownItem } from "types"
import { navDropdownItemsV1, navDropdownItemsV2 } from ".."
import { NavigationItem } from "types"
import { navDropdownItems } from ".."
type Options = {
basePath: string
activePath: string
version?: "v1" | "v2"
}
export function getNavDropdownItems({
basePath,
activePath,
version = "v1",
}: Options): NavigationDropdownItem[] {
const items = version === "v2" ? navDropdownItemsV2 : navDropdownItemsV1
return items.map((item) => {
if (item.type === "divider") {
return item
export function getNavDropdownItems({ basePath }: Options): NavigationItem[] {
return navDropdownItems.map((item) => {
const newItem = {
...item,
}
return {
...item,
isActive: activePath === item.path,
path: `${basePath}${item.path}`,
if (newItem.type === "link") {
newItem.path = `${basePath}${newItem.path}`
} else {
newItem.children = newItem.children.map((childItem) => {
if (childItem.type !== "link") {
return childItem
}
return {
...childItem,
link: `${basePath}${childItem.link}`,
}
})
}
return newItem
})
}

View File

@@ -5,7 +5,6 @@ export * from "./decode-str"
export * from "./dom-utils"
export * from "./format-report-link"
export * from "./get-link-with-base-path"
export * from "./get-mobile-sidebar-items"
export * from "./get-navbar-items"
export * from "./get-os-shortcut"
export * from "./get-scrolled-top"

View File

@@ -191,23 +191,23 @@ module.exports = {
"elevation-card-rest":
"0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 1px 2px -1px rgba(0, 0, 0, 0.08), 0px 2px 4px 0px rgba(0, 0, 0, 0.04)",
"elevation-card-rest-dark":
"0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(39, 39, 42, 1), 0px 1px 2px 0px rgba(0, 0, 0, 0.32), 0px 2px 4px 0px rgba(0, 0, 0, 0.32)",
"0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px #27272A, 0px 1px 2px 0px rgba(0, 0, 0, 0.32), 0px 2px 4px 0px rgba(0, 0, 0, 0.32)",
"elevation-card-hover":
"0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 1px 2px -1px rgba(0, 0, 0, 0.08), 0px 2px 8px 0px rgba(0, 0, 0, 0.1)",
"0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 1px 2px -1px rgba(0, 0, 0, 0.08), 0px 2px 8px 0px rgba(0, 0, 0, 0.10)",
"elevation-card-hover-dark":
"0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(39, 39, 42, 1), 0px 1px 4px 0px rgba(0, 0, 0, 0.48), 0px 2px 8px 0px rgba(0, 0, 0, 0.48)",
"0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px #27272A, 0px 1px 4px 0px rgba(0, 0, 0, 0.48), 0px 2px 8px 0px rgba(0, 0, 0, 0.48)",
"elevation-tooltip":
"0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 2px 4px 0px rgba(0, 0, 0, 0.08), 0px 4px 8px 0px rgba(0, 0, 0, 0.08)",
"elevation-tooltip-dark":
"0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.1), 0px 2px 4px 0px rgba(0, 0, 0, 0.32), 0px 4px 8px 0px rgba(0, 0, 0, 0.32)",
"0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.10), 0px 2px 4px 0px rgba(0, 0, 0, 0.32), 0px 4px 8px 0px rgba(0, 0, 0, 0.32)",
"elevation-flyout":
"0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 4px 8px 0px rgba(0, 0, 0, 0.08), 0px 8px 16px 0px rgba(0, 0, 0, 0.08)",
"elevation-flyout-dark":
"0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.1), 0px 4px 8px 0px rgba(0, 0, 0, 0.32), 0px 8px 16px 0px rgba(0, 0, 0, 0.32)",
"0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.10), 0px 4px 8px 0px rgba(0, 0, 0, 0.32), 0px 8px 16px 0px rgba(0, 0, 0, 0.32)",
"elevation-modal":
"0px 0px 0px 1px rgba(255, 255, 255, 1) inset, 0px 0px 0px 1.5px rgba(228, 228, 231, 0.6) inset, 0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 8px 16px 0px rgba(0, 0, 0, 0.08), 0px 16px 32px 0px rgba(0, 0, 0, 0.08)",
"0px 0px 0px 1px #FFF inset, 0px 0px 0px 1.5px rgba(228, 228, 231, 0.60) inset, 0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 8px 16px 0px rgba(0, 0, 0, 0.08), 0px 16px 32px 0px rgba(0, 0, 0, 0.08)",
"elevation-modal-dark":
"0px 0px 0px 1px rgba(24, 24, 27, 1) inset, 0px 0px 0px 1.5px rgba(255, 255, 255, 0.06) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.1), 0px 4px 8px 0px rgba(0, 0, 0, 0.32), 0px 8px 16px 0px rgba(0, 0, 0, 0.32)",
"0px 0px 0px 1px #18181B inset, 0px 0px 0px 1.5px rgba(255, 255, 255, 0.06) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.10), 0px 4px 8px 0px rgba(0, 0, 0, 0.32), 0px 8px 16px 0px rgba(0, 0, 0, 0.32)",
"button-neutral":
"0px 1px 2px 0px rgba(0, 0, 0, 0.12), 0px 0px 0px 1px rgba(0, 0, 0, 0.08)",
"button-neutral-dark":
@@ -234,7 +234,7 @@ module.exports = {
"0px -1px 0px 0px rgba(255, 255, 255, 0.12), 0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 0px 0px 1px rgba(82, 82, 91, 1), 0px 0px 0px 2px rgba(24, 24, 27, 1), 0px 0px 0px 4px rgba(96, 165, 250, 0.8)",
"elevation-code-block":
"0px 0px 0px 1px #18181B, 0px 0px 0px 1.5px rgba(255, 255, 255, 0.20)",
"0px 0px 0px 1px #18181B inset, 0px 0px 0px 1.5px rgba(255, 255, 255, 0.20) inset",
"elevation-code-block-dark":
"0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px #27272A, 0px 1px 2px 0px rgba(0, 0, 0, 0.32), 0px 2px 4px 0px rgba(0, 0, 0, 0.32)",
active: "0px 0px 0px 3px #E1F0FF",
@@ -326,7 +326,7 @@ module.exports = {
h1: [
"24px",
{
lineHeight: "36px",
lineHeight: "125%",
fontWeight: "500",
},
],

View File

@@ -1,5 +1,9 @@
import { SidebarSectionItems } from "./sidebar.js"
export type BreadcrumbOptions = {
showCategories?: boolean
}
export declare type DocsConfig = {
titleSuffix?: string
baseUrl: string
@@ -7,4 +11,9 @@ export declare type DocsConfig = {
sidebar: SidebarSectionItems
filesBasePath?: string
useNextLinks?: boolean
project: {
title: string
key: string
}
breadcrumbOptions?: BreadcrumbOptions
}

View File

@@ -2,6 +2,7 @@ export * from "./api-testing.js"
export * from "./config.js"
export * from "./general.js"
export * from "./menu.js"
export * from "./navigation.js"
export * from "./navigation-dropdown.js"
export * from "./sidebar.js"
export * from "./toc.js"

View File

@@ -1,6 +1,6 @@
export type MenuItemLink = {
type: "link"
icon: React.ReactNode
icon?: React.ReactNode
title: string
link: string
}
@@ -17,4 +17,13 @@ export type MenuItemAction = {
action: () => void
}
export type MenuItem = MenuItemLink | MenuItemDivider | MenuItemAction
export type MenuItemCustom = {
type: "custom"
content: React.ReactNode
}
export type MenuItem =
| MenuItemLink
| MenuItemDivider
| MenuItemAction
| MenuItemCustom

View File

@@ -14,7 +14,3 @@ export type NavigationDropdownItem =
| {
type: "divider"
}
export type BreadcrumbOptions = {
showCategories?: boolean
}

View File

@@ -0,0 +1,20 @@
import { MenuItem } from "./menu.js"
export type NavigationItemDropdown = {
type: "dropdown"
title: string
children: (MenuItem & {
useAsFallback?: boolean
})[]
project?: string
}
export type NavigationItemLink = {
type: "link"
path: string
title: string
project?: string
useAsFallback?: boolean
}
export type NavigationItem = NavigationItemLink | NavigationItemDropdown