docs: redesign sidebar (#8408)
* initial changes * redesign the sidebar + nav drawer * changes to sidebar items * finish up sidebar redesign * support new sidebar in resources * general fixes * integrate in ui * support api reference * refactor * integrate in user guide * docs: fix build errors * fix user guide build * more refactoring * added banner * added bottom logo + icon * fix up sidebar * fix up paddings * fix shadow bottom * docs: add table of content (#8445) * add toc types * implement toc functionality * finished toc redesign * redesigned table of content * mobile fixes * truncate text in toc * mobile fixes * merge fixes * implement redesign * add hide sidebar * add menu action item * finish up hide sidebar design * implement redesign in resources * integrate in api reference * integrate changes in ui * fixes to api reference scrolling * fix build error * fix build errors * fixes * fixes to sidebar * general fixes * fix active category not closing * fix long titles
This commit is contained in:
@@ -1,15 +1,65 @@
|
||||
import React from "react"
|
||||
import { Card, Link } from "../.."
|
||||
"use client"
|
||||
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { Button, useIsBrowser } from "../.."
|
||||
import { ExclamationCircleSolid, XMark } from "@medusajs/icons"
|
||||
import clsx from "clsx"
|
||||
|
||||
const LOCAL_STORAGE_KEY = "banner-v2"
|
||||
|
||||
export type Bannerv2Props = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const Bannerv2 = ({ className }: Bannerv2Props) => {
|
||||
const [show, setShow] = useState(false)
|
||||
const isBrowser = useIsBrowser()
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBrowser) {
|
||||
return
|
||||
}
|
||||
|
||||
const localStorageValue = localStorage.getItem(LOCAL_STORAGE_KEY)
|
||||
if (!localStorageValue) {
|
||||
setShow(true)
|
||||
}
|
||||
}, [isBrowser])
|
||||
|
||||
const handleClose = () => {
|
||||
setShow(false)
|
||||
localStorage.setItem(LOCAL_STORAGE_KEY, "true")
|
||||
}
|
||||
|
||||
export const Bannerv2 = () => {
|
||||
return (
|
||||
<Card>
|
||||
This documentation is for Medusa v2, which isn't ready for
|
||||
production.
|
||||
<br />
|
||||
<br />
|
||||
For production-use, refer to{" "}
|
||||
<Link href="https://docs.medusajs.com">this documentation</Link> instead.
|
||||
</Card>
|
||||
<div
|
||||
className={clsx(
|
||||
"bg-medusa-bg-base hidden gap-docs_0.5 z-20",
|
||||
"justify-between items-start rounded-docs_DEFAULT",
|
||||
"p-docs_0.75 shadow-elevation-card-rest dark:shadow-elevation-card-rest-dark",
|
||||
show && "lg:flex",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<span className="p-[2.5px]">
|
||||
<ExclamationCircleSolid className="text-medusa-tag-orange-icon" />
|
||||
</span>
|
||||
<div className="flex flex-col gap-docs_0.125 flex-1">
|
||||
<span className="text-compact-small-plus text-medusa-fg-base">
|
||||
Medusa v2 and Docs under development
|
||||
</span>
|
||||
<span className="text-compact-small text-medusa-fg-subtle">
|
||||
We are actively working on building and improving. Some sections may
|
||||
be incomplete or subject to change. Thank you for your patience.
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="transparent-clear"
|
||||
className="text-medusa-fg-muted"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<XMark />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from "react"
|
||||
import { Bordered } from "@/components/Bordered"
|
||||
import clsx from "clsx"
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
|
||||
@@ -15,37 +14,35 @@ export type BorderedIconProps = {
|
||||
export const BorderedIcon = ({
|
||||
icon = "",
|
||||
IconComponent = null,
|
||||
wrapperClassName,
|
||||
iconWrapperClassName,
|
||||
iconClassName,
|
||||
iconColorClassName = "",
|
||||
}: BorderedIconProps) => {
|
||||
return (
|
||||
<Bordered wrapperClassName={wrapperClassName}>
|
||||
<span
|
||||
className={clsx(
|
||||
"rounded-docs_xs p-docs_0.125 bg-medusa-bg-component inline-flex items-center justify-center",
|
||||
iconWrapperClassName
|
||||
)}
|
||||
>
|
||||
{!IconComponent && (
|
||||
<img
|
||||
src={icon || ""}
|
||||
className={clsx(iconClassName, "bordered-icon")}
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
{IconComponent && (
|
||||
<IconComponent
|
||||
className={clsx(
|
||||
"text-medusa-fg-subtle",
|
||||
iconClassName,
|
||||
"bordered-icon",
|
||||
iconColorClassName
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</Bordered>
|
||||
<span
|
||||
className={clsx(
|
||||
"rounded-docs_sm p-docs_0.125 bg-medusa-bg-base inline-flex items-center justify-center",
|
||||
"shadow-border-base dark:shadow-border-base-dark",
|
||||
iconWrapperClassName
|
||||
)}
|
||||
>
|
||||
{!IconComponent && (
|
||||
<img
|
||||
src={icon || ""}
|
||||
className={clsx(iconClassName, "bordered-icon")}
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
{IconComponent && (
|
||||
<IconComponent
|
||||
className={clsx(
|
||||
"text-medusa-fg-subtle rounded-docs_sm",
|
||||
iconClassName,
|
||||
"bordered-icon",
|
||||
iconColorClassName
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,8 +17,13 @@ export const Breadcrumbs = () => {
|
||||
tempBreadcrumbItems = getBreadcrumbsOfItem(item.previousSidebar)
|
||||
}
|
||||
|
||||
const parentPath =
|
||||
item.parentItem?.type === "link" ? item.parentItem.path : undefined
|
||||
const firstItemPath =
|
||||
item.default[0].type === "link" ? item.default[0].path : undefined
|
||||
|
||||
tempBreadcrumbItems.set(
|
||||
item.parentItem?.path || item.top[0].path || "/",
|
||||
parentPath || firstItemPath || "/",
|
||||
item.parentItem?.childSidebarTitle || item.parentItem?.title || ""
|
||||
)
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import React, { useMemo } from "react"
|
||||
import { Card, CardList, MDXComponents, useSidebar } from "../.."
|
||||
import { SidebarItemType } from "types"
|
||||
import { InteractiveSidebarItem, SidebarItem, SidebarItemLink } from "types"
|
||||
import slugify from "slugify"
|
||||
|
||||
type ChildDocsProps = {
|
||||
onlyTopLevel?: boolean
|
||||
@@ -30,16 +31,19 @@ export const ChildDocs = ({
|
||||
: "all"
|
||||
}, [showItems, hideItems])
|
||||
|
||||
const filterCondition = (item: SidebarItemType): boolean => {
|
||||
const filterCondition = (item: SidebarItem): boolean => {
|
||||
if (item.type === "separator") {
|
||||
return false
|
||||
}
|
||||
switch (filterType) {
|
||||
case "hide":
|
||||
return (
|
||||
(!item.path || !hideItems.includes(item.path)) &&
|
||||
(item.type !== "link" || !hideItems.includes(item.path)) &&
|
||||
!hideItems.includes(item.title)
|
||||
)
|
||||
case "show":
|
||||
return (
|
||||
(item.path !== undefined && showItems!.includes(item.path)) ||
|
||||
(item.type === "link" && showItems!.includes(item.path)) ||
|
||||
showItems!.includes(item.title)
|
||||
)
|
||||
case "all":
|
||||
@@ -47,12 +51,16 @@ export const ChildDocs = ({
|
||||
}
|
||||
}
|
||||
|
||||
const filterItems = (items: SidebarItemType[]): SidebarItemType[] => {
|
||||
const filterItems = (items: SidebarItem[]): SidebarItem[] => {
|
||||
return items
|
||||
.filter(filterCondition)
|
||||
.map((item) => Object.assign({}, item))
|
||||
.map((item) => {
|
||||
if (item.children && filterType === "hide") {
|
||||
if (
|
||||
item.type !== "separator" &&
|
||||
item.children &&
|
||||
filterType === "hide"
|
||||
) {
|
||||
item.children = filterItems(item.children)
|
||||
}
|
||||
|
||||
@@ -67,8 +75,7 @@ export const ChildDocs = ({
|
||||
? Object.assign({}, currentItems)
|
||||
: undefined
|
||||
: {
|
||||
top: [...(getActiveItem()?.children || [])],
|
||||
bottom: [],
|
||||
default: [...(getActiveItem()?.children || [])],
|
||||
}
|
||||
if (filterType === "all" || !targetItems) {
|
||||
return targetItems
|
||||
@@ -76,25 +83,34 @@ export const ChildDocs = ({
|
||||
|
||||
return {
|
||||
...targetItems,
|
||||
top: filterItems(targetItems.top),
|
||||
bottom: filterItems(targetItems.bottom),
|
||||
default: filterItems(targetItems.default),
|
||||
}
|
||||
}, [currentItems, type, getActiveItem, filterItems])
|
||||
|
||||
const filterNonInteractiveItems = (
|
||||
items: SidebarItem[] | undefined
|
||||
): InteractiveSidebarItem[] => {
|
||||
return (
|
||||
(items?.filter(
|
||||
(item) => item.type !== "separator"
|
||||
) as InteractiveSidebarItem[]) || []
|
||||
)
|
||||
}
|
||||
|
||||
const getChildrenForLevel = (
|
||||
item: SidebarItemType,
|
||||
item: InteractiveSidebarItem,
|
||||
currentLevel = 1
|
||||
): SidebarItemType[] | undefined => {
|
||||
): InteractiveSidebarItem[] | undefined => {
|
||||
if (currentLevel === childLevel) {
|
||||
return item.children
|
||||
return filterNonInteractiveItems(item.children)
|
||||
}
|
||||
if (!item.children) {
|
||||
return
|
||||
}
|
||||
|
||||
const childrenResult: SidebarItemType[] = []
|
||||
const childrenResult: InteractiveSidebarItem[] = []
|
||||
|
||||
item.children.forEach((child) => {
|
||||
filterNonInteractiveItems(item.children).forEach((child) => {
|
||||
const childChildren = getChildrenForLevel(child, currentLevel + 1)
|
||||
|
||||
if (!childChildren) {
|
||||
@@ -107,20 +123,34 @@ export const ChildDocs = ({
|
||||
return childrenResult
|
||||
}
|
||||
|
||||
const getTopLevelElms = (items?: SidebarItemType[]) => (
|
||||
<CardList
|
||||
items={
|
||||
items?.map((childItem) => ({
|
||||
title: childItem.title,
|
||||
href: childItem.path,
|
||||
showLinkIcon: false,
|
||||
})) || []
|
||||
}
|
||||
/>
|
||||
)
|
||||
const getTopLevelElms = (items?: SidebarItem[]) => {
|
||||
return (
|
||||
<CardList
|
||||
items={
|
||||
filterNonInteractiveItems(items).map((childItem) => {
|
||||
const href =
|
||||
childItem.type === "link"
|
||||
? childItem.path
|
||||
: childItem.children?.length
|
||||
? (
|
||||
childItem.children.find(
|
||||
(item) => item.type === "link"
|
||||
) as SidebarItemLink
|
||||
)?.path
|
||||
: "#"
|
||||
return {
|
||||
title: childItem.title,
|
||||
href,
|
||||
showLinkIcon: false,
|
||||
}
|
||||
}) || []
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const getAllLevelsElms = (items?: SidebarItemType[]) =>
|
||||
items?.map((item, key) => {
|
||||
const getAllLevelsElms = (items?: SidebarItem[]) =>
|
||||
filterNonInteractiveItems(items).map((item, key) => {
|
||||
const itemChildren = getChildrenForLevel(item)
|
||||
const HeadingComponent = itemChildren?.length
|
||||
? MDXComponents["h2"]
|
||||
@@ -130,12 +160,16 @@ export const ChildDocs = ({
|
||||
<React.Fragment key={key}>
|
||||
{HeadingComponent && (
|
||||
<>
|
||||
{!hideTitle && <HeadingComponent>{item.title}</HeadingComponent>}
|
||||
{!hideTitle && (
|
||||
<HeadingComponent id={slugify(item.title)}>
|
||||
{item.title}
|
||||
</HeadingComponent>
|
||||
)}
|
||||
<CardList
|
||||
items={
|
||||
itemChildren?.map((childItem) => ({
|
||||
title: childItem.title,
|
||||
href: childItem.path,
|
||||
href: childItem.type === "link" ? childItem.path : "",
|
||||
showLinkIcon: false,
|
||||
})) || []
|
||||
}
|
||||
@@ -143,20 +177,19 @@ export const ChildDocs = ({
|
||||
</>
|
||||
)}
|
||||
{!HeadingComponent && (
|
||||
<Card title={item.title} href={item.path} showLinkIcon={false} />
|
||||
<Card
|
||||
title={item.title}
|
||||
href={item.type === "link" ? item.path : ""}
|
||||
showLinkIcon={false}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)
|
||||
})
|
||||
|
||||
const getElms = (items?: SidebarItemType[]) => {
|
||||
const getElms = (items?: SidebarItem[]) => {
|
||||
return onlyTopLevel ? getTopLevelElms(items) : getAllLevelsElms(items)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{getElms(filteredItems?.top)}
|
||||
{getElms(filteredItems?.bottom)}
|
||||
</>
|
||||
)
|
||||
return <>{getElms(filteredItems?.default)}</>
|
||||
}
|
||||
|
||||
@@ -1,39 +1,23 @@
|
||||
"use client"
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react"
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import { Button } from "@/components"
|
||||
import clsx from "clsx"
|
||||
import { CSSTransition } from "react-transition-group"
|
||||
import { HelpButtonActions } from "./Actions"
|
||||
import { useIsBrowser } from "../.."
|
||||
import { useClickOutside } from "../.."
|
||||
|
||||
export const HelpButton = () => {
|
||||
const [showText, setShowText] = useState(false)
|
||||
const [showHelp, setShowHelp] = useState(false)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const isBrowser = useIsBrowser()
|
||||
|
||||
const onClickOutside = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
if (!ref.current?.contains(e.target as Node)) {
|
||||
setShowHelp(false)
|
||||
setShowText(false)
|
||||
}
|
||||
useClickOutside({
|
||||
elmRef: ref,
|
||||
onClickOutside: () => {
|
||||
setShowHelp(false)
|
||||
setShowText(false)
|
||||
},
|
||||
[ref.current]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBrowser) {
|
||||
return
|
||||
}
|
||||
|
||||
window.document.addEventListener("click", onClickOutside)
|
||||
|
||||
return () => {
|
||||
window.document.removeEventListener("click", onClickOutside)
|
||||
}
|
||||
}, [isBrowser])
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (showHelp) {
|
||||
|
||||
23
www/packages/docs-ui/src/components/Icons/House/index.tsx
Normal file
23
www/packages/docs-ui/src/components/Icons/House/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from "react"
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
|
||||
export const HouseIcon = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width="15"
|
||||
height="16"
|
||||
viewBox="0 0 15 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12.7044 5.5112L8.03778 1.96454C7.71956 1.72276 7.27956 1.72276 6.96222 1.96454L2.29556 5.5112C2.07422 5.6792 1.94444 5.94143 1.94444 6.21965V12.6676C1.94444 13.6499 2.74 14.4454 3.72222 14.4454H5.94444V10.8899C5.94444 10.3992 6.34267 10.001 6.83333 10.001H8.16667C8.65733 10.001 9.05556 10.3992 9.05556 10.8899V14.4454H11.2778C12.26 14.4454 13.0556 13.6499 13.0556 12.6676V6.21876C13.0556 5.94054 12.9258 5.68009 12.7044 5.5112Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,33 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import React from "react"
|
||||
|
||||
export const NavigationDropdownAdminIcon = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<rect width="20" height="20" className="fill-medusa-tag-green-icon" />
|
||||
<g clipPath="url(#clip0_9988_95571)">
|
||||
<path
|
||||
d="M13.25 4.5H6.75C5.233 4.5 4 5.733 4 7.25V12.75C4 14.267 5.233 15.5 6.75 15.5H13.25C14.767 15.5 16 14.267 16 12.75V7.25C16 5.733 14.767 4.5 13.25 4.5ZM6.5 8C5.948 8 5.5 7.552 5.5 7C5.5 6.448 5.948 6 6.5 6C7.052 6 7.5 6.448 7.5 7C7.5 7.552 7.052 8 6.5 8ZM9.5 8C8.948 8 8.5 7.552 8.5 7C8.5 6.448 8.948 6 9.5 6C10.052 6 10.5 6.448 10.5 7C10.5 7.552 10.052 8 9.5 8Z"
|
||||
className="fill-medusa-fg-on-color"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_9988_95571">
|
||||
<rect
|
||||
width="12"
|
||||
height="12"
|
||||
className="fill-medusa-fg-on-color"
|
||||
transform="translate(4 4)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import React from "react"
|
||||
|
||||
export const NavigationDropdownDocIcon = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<rect
|
||||
width="20"
|
||||
height="20"
|
||||
className="fill-medusa-fg-base dark:fill-medusa-bg-base"
|
||||
/>
|
||||
<g clipPath="url(#clip0_9988_95547)">
|
||||
<path
|
||||
d="M14.25 16H7.25C6.009 16 5 14.991 5 13.75C5 13.336 5.336 13 5.75 13C6.164 13 6.5 13.336 6.5 13.75C6.5 14.164 6.836 14.5 7.25 14.5H14.25C14.664 14.5 15 14.836 15 15.25C15 15.664 14.664 16 14.25 16Z"
|
||||
className="fill-medusa-fg-on-color"
|
||||
/>
|
||||
<path
|
||||
d="M12.75 4H7.25C6.009 4 5 5.009 5 6.25V13.75C5 14.164 5.336 14.5 5.75 14.5C6.164 14.5 6.5 14.164 6.5 13.75C6.5 13.336 6.836 13 7.25 13H14.25C14.664 13 15 12.664 15 12.25V6.25C15 5.009 13.991 4 12.75 4ZM11.25 9H8.75C8.336 9 8 8.664 8 8.25C8 7.836 8.336 7.5 8.75 7.5H11.25C11.664 7.5 12 7.836 12 8.25C12 8.664 11.664 9 11.25 9Z"
|
||||
className="fill-medusa-fg-on-color"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_9988_95547">
|
||||
<rect
|
||||
width="12"
|
||||
height="12"
|
||||
className="fill-medusa-fg-on-color"
|
||||
transform="translate(4 4)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import React from "react"
|
||||
|
||||
export const NavigationDropdownDocV1Icon = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<rect width="20" height="20" className="fill-medusa-fg-subtle" />
|
||||
<g clipPath="url(#clip0_10088_101562)">
|
||||
<path
|
||||
d="M8 4.02405C6.687 4.14505 5.646 5.18605 5.525 6.50005H4.75C4.336 6.50005 4 6.83605 4 7.25005C4 7.66405 4.336 8.00005 4.75 8.00005H5.5V9.25005H4.75C4.336 9.25005 4 9.58605 4 10C4 10.414 4.336 10.75 4.75 10.75H5.5V12H4.75C4.336 12 4 12.336 4 12.75C4 13.164 4.336 13.5 4.75 13.5H5.525C5.646 14.814 6.687 15.855 8 15.976V4.02405Z"
|
||||
className="fill-medusa-fg-on-color"
|
||||
/>
|
||||
<path
|
||||
d="M12.25 4H9.5V16H12.25C13.767 16 15 14.767 15 13.25V6.75C15 5.233 13.767 4 12.25 4Z"
|
||||
className="fill-medusa-fg-on-color"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_10088_101562">
|
||||
<rect
|
||||
width="12"
|
||||
height="12"
|
||||
className="fill-medusa-fg-on-color"
|
||||
transform="translate(4 4)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import React from "react"
|
||||
|
||||
export const NavigationDropdownResourcesIcon = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<rect width="20" height="20" className="fill-medusa-tag-orange-icon" />
|
||||
<g clipPath="url(#clip0_9988_95555)">
|
||||
<path
|
||||
d="M15.25 12C14.836 12 14.5 11.664 14.5 11.25V8.75C14.5 8.061 13.939 7.5 13.25 7.5H10.614C10.386 7.5 10.172 7.397 10.029 7.219L9.425 6.467C9.187 6.17 8.831 6 8.45 6H6.749C6.06 6 5.499 6.561 5.499 7.25V11.25C5.499 11.664 5.163 12 4.749 12C4.335 12 3.999 11.664 3.999 11.25V7.25C4 5.733 5.233 4.5 6.75 4.5H8.451C9.289 4.5 10.07 4.875 10.596 5.528L10.974 6H13.25C14.767 6 16 7.233 16 8.75V11.25C16 11.664 15.664 12 15.25 12Z"
|
||||
className="fill-medusa-fg-on-color"
|
||||
/>
|
||||
<path
|
||||
d="M13.25 8.5H6.75C5.23122 8.5 4 9.73122 4 11.25V12.75C4 14.2688 5.23122 15.5 6.75 15.5H13.25C14.7688 15.5 16 14.2688 16 12.75V11.25C16 9.73122 14.7688 8.5 13.25 8.5Z"
|
||||
className="fill-medusa-fg-on-color"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_9988_95555">
|
||||
<rect
|
||||
width="12"
|
||||
height="12"
|
||||
className="fill-medusa-fg-on-color"
|
||||
transform="translate(4 4)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import React from "react"
|
||||
|
||||
export const NavigationDropdownStoreIcon = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<rect width="20" height="20" className="fill-medusa-tag-purple-icon" />
|
||||
<g clipPath="url(#clip0_9988_95563)">
|
||||
<path
|
||||
d="M15.25 8.548C15.122 8.548 14.993 8.515 14.874 8.446L10 5.617L5.126 8.446C4.77 8.654 4.309 8.532 4.101 8.174C3.893 7.816 4.015 7.357 4.373 7.149L9.624 4.102C9.856 3.966 10.145 3.966 10.377 4.102L15.627 7.15C15.985 7.358 16.107 7.817 15.899 8.175C15.76 8.415 15.508 8.548 15.25 8.548Z"
|
||||
className="fill-medusa-fg-on-color"
|
||||
/>
|
||||
<path
|
||||
d="M10 7.35195L5 10.254V13.75C5 14.715 5.785 15.5 6.75 15.5H9.25V13.25C9.25 12.836 9.586 12.5 10 12.5C10.414 12.5 10.75 12.836 10.75 13.25V15.5H13.25C14.215 15.5 15 14.715 15 13.75V10.254L10 7.35195Z"
|
||||
className="fill-medusa-fg-on-color"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_9988_95563">
|
||||
<rect
|
||||
width="12"
|
||||
height="12"
|
||||
className="fill-medusa-fg-on-color"
|
||||
transform="translate(4 4)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import React from "react"
|
||||
|
||||
export const NavigationDropdownUiIcon = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<rect width="20" height="20" className="fill-medusa-tag-blue-icon" />
|
||||
<g clipPath="url(#clip0_9988_95578)">
|
||||
<path
|
||||
d="M14.18 10.472L9.28899 8.685C8.82599 8.518 8.32399 8.627 7.97699 8.975C7.62799 9.323 7.51699 9.826 7.68599 10.288L9.47199 15.178C9.65599 15.679 10.114 16 10.645 16C10.654 16 10.664 16 10.672 16C11.215 15.989 11.672 15.648 11.836 15.132L12.394 13.394L14.131 12.838C14.648 12.673 14.988 12.216 15 11.673C15.011 11.131 14.689 10.658 14.18 10.472Z"
|
||||
className="fill-medusa-fg-on-color"
|
||||
/>
|
||||
<path
|
||||
d="M6.13499 11.894C6.05799 11.894 5.97999 11.882 5.90299 11.857C4.76499 11.487 4.00099 10.439 4.00099 9.25V7.75C3.99999 6.234 5.23299 5 6.74999 5H13.25C14.767 5 16 6.234 16 7.75V9.052C16 9.466 15.664 9.802 15.25 9.802C14.836 9.802 14.5 9.466 14.5 9.052V7.75C14.5 7.061 13.939 6.5 13.25 6.5H6.74999C6.06099 6.5 5.49999 7.061 5.49999 7.75V9.25C5.49999 9.788 5.84899 10.262 6.36699 10.43C6.76099 10.558 6.97599 10.981 6.84699 11.375C6.74399 11.692 6.45099 11.893 6.13399 11.893L6.13499 11.894Z"
|
||||
className="fill-medusa-fg-on-color"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_9988_95578">
|
||||
<rect
|
||||
width="12"
|
||||
height="12"
|
||||
className="fill-medusa-fg-on-color"
|
||||
transform="translate(4 4)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import React from "react"
|
||||
|
||||
export const NavigationDropdownUserIcon = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<rect width="20" height="20" className="fill-medusa-tag-red-icon" />
|
||||
<g clipPath="url(#clip0_10088_101526)">
|
||||
<path
|
||||
d="M10 8.99097C11.3807 8.99097 12.5 7.87168 12.5 6.49097C12.5 5.11025 11.3807 3.99097 10 3.99097C8.61929 3.99097 7.5 5.11025 7.5 6.49097C7.5 7.87168 8.61929 8.99097 10 8.99097Z"
|
||||
className="fill-medusa-fg-on-color"
|
||||
/>
|
||||
<path
|
||||
d="M14.533 12.639C13.601 11.011 11.864 10 10 10C8.136 10 6.398 11.011 5.467 12.639C5.218 13.073 5.162 13.593 5.313 14.067C5.463 14.539 5.809 14.93 6.26 15.139C7.501 15.713 8.75 16 10 16C11.25 16 12.499 15.713 13.74 15.139C14.191 14.93 14.536 14.539 14.687 14.067C14.838 13.593 14.782 13.073 14.533 12.64V12.639Z"
|
||||
className="fill-medusa-fg-on-color"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_10088_101526">
|
||||
<rect
|
||||
width="12"
|
||||
height="12"
|
||||
className="fill-medusa-fg-on-color"
|
||||
transform="translate(4 4)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
import React from "react"
|
||||
|
||||
export const SidebarLeftIcon = (props: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<g clipPath="url(#clip0_10002_37978)">
|
||||
<path
|
||||
d="M12.6668 2.25H3.33344C2.3516 2.25 1.55566 3.0738 1.55566 4.09V11.91C1.55566 12.9262 2.3516 13.75 3.33344 13.75H12.6668C13.6486 13.75 14.4446 12.9262 14.4446 11.91V4.09C14.4446 3.0738 13.6486 2.25 12.6668 2.25Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M4.3999 5V11"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_10002_37978">
|
||||
<rect
|
||||
width="15"
|
||||
height="15"
|
||||
fill="none"
|
||||
transform="translate(0.5 0.5)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -9,9 +9,9 @@ export const Kbd = ({ children, className, ...props }: KbdProps) => {
|
||||
className={clsx(
|
||||
"rounded-docs_xs border-solid border border-medusa-border-base",
|
||||
"inline-flex items-center justify-center",
|
||||
"py-0 px-[6px]",
|
||||
"py-0 px-docs_0.25",
|
||||
"bg-medusa-bg-field",
|
||||
"text-medusa-tag-neutral-text",
|
||||
"text-medusa-fg-subtle",
|
||||
"text-compact-x-small-plus font-base shadow-none",
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
"use client"
|
||||
|
||||
import React, { useMemo } from "react"
|
||||
import { CurrentItemsState, useSidebar } from "../../.."
|
||||
import clsx from "clsx"
|
||||
import Link from "next/link"
|
||||
import { SidebarItemLink } from "types"
|
||||
|
||||
export const MainNavBreadcrumbs = () => {
|
||||
const { currentItems, getActiveItem } = useSidebar()
|
||||
|
||||
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)
|
||||
: undefined
|
||||
const firstItemPath =
|
||||
item.default[0].type === "link" ? getLinkPath(item.default[0]) : undefined
|
||||
|
||||
tempBreadcrumbItems.set(
|
||||
parentPath || firstItemPath || "/",
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
const activeItem = getActiveItem()
|
||||
if (activeItem) {
|
||||
tempBreadcrumbItems.set(
|
||||
getLinkPath(activeItem) || "/",
|
||||
activeItem?.title || ""
|
||||
)
|
||||
}
|
||||
|
||||
return tempBreadcrumbItems
|
||||
}, [currentItems, getActiveItem])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"flex items-center",
|
||||
"text-medusa-fg-muted text-compact-small"
|
||||
)}
|
||||
>
|
||||
{Array.from(breadcrumbItems).map(([link, title]) => (
|
||||
<React.Fragment key={link}>
|
||||
<span>/</span>
|
||||
<Link
|
||||
href={link}
|
||||
className={clsx(
|
||||
"hover:text-medusa-fg-base transition-colors",
|
||||
"px-docs_0.5 py-docs_0.25"
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</Link>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { useColorMode } from "../../../providers"
|
||||
import { Button } from "../../.."
|
||||
import { Moon, Sun } from "@medusajs/icons"
|
||||
import clsx from "clsx"
|
||||
|
||||
export const MainNavColorMode = () => {
|
||||
const { colorMode, toggleColorMode } = useColorMode()
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="transparent-clear"
|
||||
className={clsx("!p-[6.5px] text-medusa-fg-muted")}
|
||||
onClick={toggleColorMode}
|
||||
>
|
||||
{colorMode === "light" && <Sun />}
|
||||
{colorMode === "dark" && <Moon />}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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",
|
||||
"mx-docs_0.5"
|
||||
)}
|
||||
></span>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
"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 { SidebarSeparator } from "../../../../Sidebar/Separator"
|
||||
|
||||
export type MainNavigationDropdownMenuItemProps = {
|
||||
item: NavigationDropdownItem
|
||||
onSelect: () => void
|
||||
}
|
||||
|
||||
export const MainNavigationDropdownMenuItem = ({
|
||||
item,
|
||||
onSelect,
|
||||
}: MainNavigationDropdownMenuItemProps) => {
|
||||
switch (item.type) {
|
||||
case "divider":
|
||||
return <SidebarSeparator 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
"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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
"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
|
||||
}
|
||||
|
||||
export const MainNavigationDropdownSelected = ({
|
||||
item,
|
||||
onClick,
|
||||
}: MainNavigationDropdownSelectedProps) => {
|
||||
if (item.type === "divider") {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"flex justify-between items-center",
|
||||
"cursor-pointer rounded-docs_sm hover:bg-medusa-bg-hover"
|
||||
)}
|
||||
tabIndex={-1}
|
||||
onClick={onClick}
|
||||
>
|
||||
<MainNavigationDropdownIcon icon={item.icon} />
|
||||
<div className="flex gap-[6px] py-docs_0.25 px-docs_0.5 items-center">
|
||||
<span className="text-medusa-fg-base whitespace-nowrap flex-1">
|
||||
{item.title}
|
||||
</span>
|
||||
<TrianglesMini className="text-medusa-fg-muted" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
"use client"
|
||||
|
||||
import clsx from "clsx"
|
||||
import React, { useMemo, useRef, useState } from "react"
|
||||
import { MainNavigationDropdownSelected } from "./Selected"
|
||||
import { MainNavigationDropdownMenu } from "./Menu"
|
||||
import { useClickOutside, useMainNav } from "../../.."
|
||||
|
||||
export const MainNavigationDropdown = () => {
|
||||
const { navItems: items } = useMainNav()
|
||||
const navigationRef = useRef<HTMLDivElement>(null)
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
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)}
|
||||
/>
|
||||
)}
|
||||
<MainNavigationDropdownMenu
|
||||
items={items}
|
||||
open={menuOpen}
|
||||
onSelect={() => setMenuOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
"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>
|
||||
)
|
||||
}
|
||||
38
www/packages/docs-ui/src/components/MainNav/index.tsx
Normal file
38
www/packages/docs-ui/src/components/MainNav/index.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
"use client"
|
||||
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
import { MainNavigationDropdown } from "./NavigationDropdown"
|
||||
import { MainNavBreadcrumbs } from "./Breadcrumb"
|
||||
import { SearchModalOpener, useMainNav } from "../.."
|
||||
import { MainNavColorMode } from "./ColorMode"
|
||||
import Link from "next/link"
|
||||
import { MainNavDivider } from "./Divider"
|
||||
import { MainNavSidebarOpener } from "./SidebarOpener"
|
||||
|
||||
export const MainNav = () => {
|
||||
const { reportIssueLink } = useMainNav()
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"hidden sm:flex justify-between items-center",
|
||||
"px-docs_1 py-docs_0.75 w-full z-20",
|
||||
"sticky top-0 bg-medusa-bg-base"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-docs_0.25">
|
||||
<MainNavSidebarOpener />
|
||||
<MainNavigationDropdown />
|
||||
<MainNavBreadcrumbs />
|
||||
</div>
|
||||
<div className="flex items-center gap-docs_0.25">
|
||||
<Link href={reportIssueLink} className="text-medusa-fg-muted">
|
||||
Report Issue
|
||||
</Link>
|
||||
<MainNavDivider />
|
||||
<MainNavColorMode />
|
||||
<SearchModalOpener />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
32
www/packages/docs-ui/src/components/Menu/Action/index.tsx
Normal file
32
www/packages/docs-ui/src/components/Menu/Action/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
"use client"
|
||||
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
import { MenuItemAction } from "types"
|
||||
|
||||
export type MenuActionProps = {
|
||||
item: MenuItemAction
|
||||
}
|
||||
|
||||
export const MenuAction = ({ item }: MenuActionProps) => {
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
"flex py-docs_0.25 px-docs_0.5",
|
||||
"gap-docs_0.5 rounded-docs_xs",
|
||||
"hover:bg-medusa-bg-component-hover",
|
||||
"text-medusa-fg-base cursor-pointer"
|
||||
)}
|
||||
tabIndex={-1}
|
||||
onClick={item.action}
|
||||
>
|
||||
<span className="text-medusa-fg-subtle">{item.icon}</span>
|
||||
<span className="text-compact-small flex-1">{item.title}</span>
|
||||
{item.shortcut && (
|
||||
<span className="text-medusa-fg-subtle text-compact-small">
|
||||
{item.shortcut}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
|
||||
export const MenuDivider = () => {
|
||||
return <hr className="bg-medusa-border-menu-top mt-[3px] mb-[3px]" />
|
||||
}
|
||||
27
www/packages/docs-ui/src/components/Menu/Item/index.tsx
Normal file
27
www/packages/docs-ui/src/components/Menu/Item/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
"use client"
|
||||
|
||||
import clsx from "clsx"
|
||||
import Link from "next/link"
|
||||
import React from "react"
|
||||
import { MenuItemLink } from "types"
|
||||
|
||||
export type MenuItemProps = {
|
||||
item: MenuItemLink
|
||||
}
|
||||
|
||||
export const MenuItem = ({ item }: MenuItemProps) => {
|
||||
return (
|
||||
<Link
|
||||
className={clsx(
|
||||
"flex py-docs_0.25 px-docs_0.5",
|
||||
"gap-docs_0.5 rounded-docs_xs",
|
||||
"hover:bg-medusa-bg-component-hover",
|
||||
"text-medusa-fg-base"
|
||||
)}
|
||||
href={item.link}
|
||||
>
|
||||
<span className="text-medusa-fg-subtle">{item.icon}</span>
|
||||
<span className="text-compact-small">{item.title}</span>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
31
www/packages/docs-ui/src/components/Menu/index.tsx
Normal file
31
www/packages/docs-ui/src/components/Menu/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
import { MenuItem as MenuItemType } from "types"
|
||||
import { MenuItem } from "./Item"
|
||||
import { MenuDivider } from "./Divider"
|
||||
import { MenuAction } from "./Action"
|
||||
|
||||
export type MenuProps = {
|
||||
items: MenuItemType[]
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const Menu = ({ items, className }: MenuProps) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"bg-medusa-bg-component p-docs_0.25 rounded-docs_DEFAULT",
|
||||
"shadow-elevation-flyout dark:shadow-elevation-flyout-dark",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{item.type === "link" && <MenuItem item={item} />}
|
||||
{item.type === "action" && <MenuAction item={item} />}
|
||||
{item.type === "divider" && <MenuDivider />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
"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-50 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>
|
||||
)
|
||||
}
|
||||
@@ -88,7 +88,7 @@ export const Modal = ({
|
||||
{...props}
|
||||
className={clsx(
|
||||
"fixed top-0 left-0 flex h-screen w-screen items-center justify-center",
|
||||
"bg-medusa-bg-overlay",
|
||||
"bg-medusa-bg-overlay z-50",
|
||||
"hidden open:flex border-0 p-0",
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { NavbarIconButton, NavbarIconButtonProps } from "../IconButton"
|
||||
import { useColorMode } from "@/providers"
|
||||
import { Moon, Sun } from "@medusajs/icons"
|
||||
|
||||
export type NavbarColorModeToggleProps = {
|
||||
buttonProps?: NavbarIconButtonProps
|
||||
}
|
||||
|
||||
export const NavbarColorModeToggle = ({
|
||||
buttonProps,
|
||||
}: NavbarColorModeToggleProps) => {
|
||||
const { colorMode, toggleColorMode } = useColorMode()
|
||||
|
||||
return (
|
||||
<NavbarIconButton {...buttonProps} onClick={() => toggleColorMode()}>
|
||||
{colorMode === "light" && <Sun className="text-medusa-fg-muted" />}
|
||||
{colorMode === "dark" && <Moon className="text-medusa-fg-muted" />}
|
||||
</NavbarIconButton>
|
||||
)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { Button, ButtonProps } from "@/components"
|
||||
|
||||
export type NavbarIconButtonProps = ButtonProps
|
||||
|
||||
export const NavbarIconButton = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: NavbarIconButtonProps) => {
|
||||
return (
|
||||
<Button
|
||||
className={clsx(
|
||||
"[&>svg]:h-[22px] [&>svg]:w-[22px] btn-secondary-icon",
|
||||
className
|
||||
)}
|
||||
variant="secondary"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { Badge, BadgeProps, Link, LinkProps } from "@/components"
|
||||
|
||||
export type NavbarLinkProps = {
|
||||
href: string
|
||||
label: string
|
||||
className?: string
|
||||
activeValuePattern?: RegExp
|
||||
isActive?: boolean
|
||||
badge?: BadgeProps
|
||||
} & LinkProps
|
||||
|
||||
export const NavbarLink = ({
|
||||
href,
|
||||
label,
|
||||
className,
|
||||
isActive,
|
||||
badge,
|
||||
}: NavbarLinkProps) => {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
className={clsx(
|
||||
isActive && "!text-medusa-fg-base",
|
||||
!isActive && "!text-medusa-fg-subtle",
|
||||
"text-compact-small-plus inline-block",
|
||||
"hover:!text-medusa-fg-base",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{badge && (
|
||||
<Badge {...badge} className={clsx(badge.className, "ml-docs_0.5")} />
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { useColorMode } from "@/providers"
|
||||
import Link from "next/link"
|
||||
import clsx from "clsx"
|
||||
import Image from "next/image"
|
||||
|
||||
export type NavbarLogoProps = {
|
||||
light: string
|
||||
dark?: string
|
||||
className?: string
|
||||
imageClassName?: string
|
||||
}
|
||||
|
||||
export const NavbarLogo = ({
|
||||
light,
|
||||
dark,
|
||||
className,
|
||||
imageClassName,
|
||||
}: NavbarLogoProps) => {
|
||||
const { colorMode } = useColorMode()
|
||||
|
||||
return (
|
||||
<Link href={`/`} className={clsx("flex-1", className)}>
|
||||
<Image
|
||||
src={colorMode === "light" ? light : dark || light}
|
||||
alt="Medusa Logo"
|
||||
height={20}
|
||||
width={20}
|
||||
className={clsx("align-middle", imageClassName)}
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { NavbarIconButton, NavbarIconButtonProps } from "../../IconButton"
|
||||
import clsx from "clsx"
|
||||
import { SidebarLeft, XMark } from "@medusajs/icons"
|
||||
|
||||
export type NavbarMobileMenuButtonProps = {
|
||||
buttonProps?: NavbarIconButtonProps
|
||||
mobileSidebarOpen: boolean
|
||||
setMobileSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
export const NavbarMobileMenuButton = ({
|
||||
buttonProps,
|
||||
mobileSidebarOpen,
|
||||
setMobileSidebarOpen,
|
||||
isLoading = false,
|
||||
}: NavbarMobileMenuButtonProps) => {
|
||||
return (
|
||||
<NavbarIconButton
|
||||
{...buttonProps}
|
||||
className={clsx("mr-docs_1 lg:!hidden", buttonProps?.className)}
|
||||
onClick={() => {
|
||||
if (!isLoading) {
|
||||
setMobileSidebarOpen((prevValue) => !prevValue)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{!mobileSidebarOpen && <SidebarLeft className="text-medusa-fg-muted" />}
|
||||
{mobileSidebarOpen && <XMark className="text-medusa-fg-muted" />}
|
||||
</NavbarIconButton>
|
||||
)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { NavbarMobileMenuButton, NavbarMobileMenuButtonProps } from "./Button"
|
||||
import { NavbarColorModeToggle } from "../ColorModeToggle"
|
||||
import { NavbarSearchModalOpener } from "../SearchModalOpener"
|
||||
import { useMobile } from "@/providers"
|
||||
import clsx from "clsx"
|
||||
import { NavbarLogo, NavbarLogoProps } from "../Logo"
|
||||
|
||||
export type NavbarMobileMenuProps = {
|
||||
menuButton: NavbarMobileMenuButtonProps
|
||||
logo: NavbarLogoProps
|
||||
}
|
||||
|
||||
export const NavbarMobileMenu = ({
|
||||
menuButton,
|
||||
logo,
|
||||
}: NavbarMobileMenuProps) => {
|
||||
const { isMobile } = useMobile()
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center justify-between lg:hidden">
|
||||
{isMobile && (
|
||||
<>
|
||||
<NavbarMobileMenuButton
|
||||
{...menuButton}
|
||||
buttonProps={{
|
||||
...(menuButton.buttonProps || {}),
|
||||
variant: "transparent",
|
||||
}}
|
||||
/>
|
||||
<NavbarLogo
|
||||
{...logo}
|
||||
className="lg:hidden"
|
||||
imageClassName="mx-auto"
|
||||
/>
|
||||
<div className="flex">
|
||||
<NavbarSearchModalOpener />
|
||||
<NavbarColorModeToggle
|
||||
buttonProps={{
|
||||
variant: "transparent",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
|
||||
export const NavbarDivider = () => {
|
||||
return (
|
||||
<div
|
||||
className={clsx("w-px h-1/2 bg-medusa-fg-disabled", "mx-docs_0.5")}
|
||||
></div>
|
||||
)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { SearchModalOpener } from "@/components"
|
||||
import { useMobile } from "@/providers"
|
||||
|
||||
export type NavbarSearchModalOpenerProps = {
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
export const NavbarSearchModalOpener = ({
|
||||
isLoading,
|
||||
}: NavbarSearchModalOpenerProps) => {
|
||||
const { isMobile } = useMobile()
|
||||
|
||||
return <SearchModalOpener isMobile={isMobile} isLoading={isLoading} />
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { NavbarLink, NavbarLinkProps } from "./Link"
|
||||
import { NavbarColorModeToggle } from "./ColorModeToggle"
|
||||
import { NavbarLogo, NavbarLogoProps } from "./Logo"
|
||||
import { NavbarMobileMenu } from "./MobileMenu"
|
||||
import { NavbarSearchModalOpener } from "./SearchModalOpener"
|
||||
import { NavbarMobileMenuButtonProps } from "./MobileMenu/Button"
|
||||
import { NavbarDivider } from "./NavbarDivider"
|
||||
|
||||
export type NavbarItem =
|
||||
| {
|
||||
type: "link"
|
||||
props: NavbarLinkProps
|
||||
}
|
||||
| {
|
||||
type: "divider"
|
||||
props?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type NavbarProps = {
|
||||
logo: NavbarLogoProps
|
||||
items: NavbarItem[]
|
||||
showSearchOpener?: boolean
|
||||
showColorModeToggle?: boolean
|
||||
additionalActionsAfter?: React.ReactNode
|
||||
additionalActionsBefore?: React.ReactNode
|
||||
mobileMenuButton: NavbarMobileMenuButtonProps
|
||||
isLoading?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const Navbar = ({
|
||||
logo,
|
||||
items,
|
||||
showSearchOpener = true,
|
||||
showColorModeToggle = true,
|
||||
additionalActionsBefore,
|
||||
additionalActionsAfter,
|
||||
mobileMenuButton,
|
||||
isLoading,
|
||||
className,
|
||||
}: NavbarProps) => {
|
||||
return (
|
||||
<nav
|
||||
className={clsx(
|
||||
"h-navbar w-full justify-between",
|
||||
"bg-docs-bg dark:bg-docs-bg-dark border-medusa-border-base border-b",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"h-navbar max-w-xxl py-docs_0.75 mx-auto flex w-full justify-between px-docs_1 lg:px-docs_3"
|
||||
)}
|
||||
>
|
||||
<div className="hidden w-full items-center gap-docs_0.5 lg:flex lg:w-auto lg:gap-docs_1.5">
|
||||
<NavbarLogo {...logo} />
|
||||
{items.map(({ type, props }, index) => {
|
||||
switch (type) {
|
||||
case "divider":
|
||||
return <NavbarDivider key={index} />
|
||||
default:
|
||||
return <NavbarLink key={index} {...props} />
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
<div className="hidden min-w-0 flex-1 items-center justify-end gap-docs_0.5 lg:flex">
|
||||
{additionalActionsBefore}
|
||||
{showSearchOpener && (
|
||||
<NavbarSearchModalOpener isLoading={isLoading} />
|
||||
)}
|
||||
{showColorModeToggle && <NavbarColorModeToggle />}
|
||||
{additionalActionsAfter}
|
||||
</div>
|
||||
<NavbarMobileMenu
|
||||
logo={logo}
|
||||
menuButton={{
|
||||
...mobileMenuButton,
|
||||
isLoading,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export const NotificationContainer = () => {
|
||||
return (
|
||||
<TransitionGroup
|
||||
className={clsx(
|
||||
"flex fixed flex-col gap-docs_0.5 right-0",
|
||||
"flex fixed z-40 flex-col gap-docs_0.5 right-0",
|
||||
"md:w-auto w-full overflow-y-auto",
|
||||
"max-h-[50%] md:max-h-[calc(100vh-57px)]",
|
||||
"max-[768px]:max-h-[50%]",
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
|
||||
import React, { MouseEvent, useMemo } from "react"
|
||||
import clsx from "clsx"
|
||||
import { useSearch } from "@/providers"
|
||||
import { Button, InputText, Kbd } from "@/components"
|
||||
import { useMobile, useSearch } from "@/providers"
|
||||
import { Button } from "@/components"
|
||||
import { MagnifyingGlass } from "@medusajs/icons"
|
||||
import { useKeyboardShortcut } from "@/hooks"
|
||||
|
||||
export type SearchModalOpenerProps = {
|
||||
isLoading?: boolean
|
||||
isMobile?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const SearchModalOpener = ({
|
||||
isLoading = false,
|
||||
isMobile = false,
|
||||
className,
|
||||
}: SearchModalOpenerProps) => {
|
||||
const { isMobile } = useMobile()
|
||||
const { setIsOpen } = useSearch()
|
||||
const isApple = useMemo(() => {
|
||||
return typeof navigator !== "undefined"
|
||||
@@ -52,39 +53,19 @@ export const SearchModalOpener = ({
|
||||
</Button>
|
||||
)}
|
||||
{!isMobile && (
|
||||
<div
|
||||
className={clsx("relative w-min hover:cursor-pointer group")}
|
||||
<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
|
||||
className={clsx(
|
||||
"absolute left-docs_0.5 top-[5px]",
|
||||
"text-medusa-fg-muted"
|
||||
)}
|
||||
/>
|
||||
<InputText
|
||||
type="search"
|
||||
className={clsx(
|
||||
"placeholder:text-compact-small",
|
||||
"!py-[5px] !pl-[36px] !pr-docs_0.5",
|
||||
"cursor-pointer select-none"
|
||||
)}
|
||||
placeholder="Find something"
|
||||
onClick={handleOpen}
|
||||
onFocus={(e) => e.target.blur()}
|
||||
tabIndex={-1}
|
||||
addGroupStyling={true}
|
||||
/>
|
||||
<span
|
||||
className={clsx(
|
||||
"gap-docs_0.25 flex",
|
||||
"absolute right-docs_0.5 top-[5px]"
|
||||
)}
|
||||
>
|
||||
<Kbd>{isApple ? "⌘" : "Ctrl"}</Kbd>
|
||||
<Kbd>K</Kbd>
|
||||
</span>
|
||||
</div>
|
||||
<MagnifyingGlass />
|
||||
<span>{isApple ? "⌘" : "Ctrl"}K</span>
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { useSidebar } from "../../../providers"
|
||||
import clsx from "clsx"
|
||||
import { ArrowUturnLeft } from "@medusajs/icons"
|
||||
|
||||
export const SidebarBack = () => {
|
||||
const { goBack } = useSidebar()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"mb-docs_1.5 cursor-pointer",
|
||||
"flex items-center gap-docs_0.5 rounded-docs_sm px-docs_0.5 py-[6px] hover:no-underline",
|
||||
"border border-transparent",
|
||||
"text-medusa-fg-subtle text-medium-plus"
|
||||
)}
|
||||
tabIndex={-1}
|
||||
onClick={goBack}
|
||||
>
|
||||
<ArrowUturnLeft className="mr-docs_0.5" />
|
||||
<span>Back</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
34
www/packages/docs-ui/src/components/Sidebar/Child/index.tsx
Normal file
34
www/packages/docs-ui/src/components/Sidebar/Child/index.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { InteractiveSidebarItem } from "types"
|
||||
import { ArrowUturnLeft } from "@medusajs/icons"
|
||||
import { useSidebar } from "../../.."
|
||||
|
||||
type SidebarTitleProps = {
|
||||
item: InteractiveSidebarItem
|
||||
}
|
||||
|
||||
export const SidebarChild = ({ item }: SidebarTitleProps) => {
|
||||
const { goBack } = useSidebar()
|
||||
|
||||
return (
|
||||
<div className="px-docs_0.75">
|
||||
<div
|
||||
onClick={goBack}
|
||||
className={clsx(
|
||||
"flex items-center justify-start my-docs_0.75 gap-[10px]",
|
||||
"border border-transparent cursor-pointer mx-docs_0.5",
|
||||
"!text-medusa-fg-base !text-compact-small-plus"
|
||||
)}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<ArrowUturnLeft />
|
||||
<span className="truncate flex-1">
|
||||
{item.childSidebarTitle || item.title}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
"use client"
|
||||
|
||||
// @refresh reset
|
||||
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react"
|
||||
import { SidebarItemCategory as SidebarItemCategoryType } from "types"
|
||||
import { Loading, SidebarItem, useSidebar } from "../../../.."
|
||||
import clsx from "clsx"
|
||||
import { MinusMini, PlusMini } from "@medusajs/icons"
|
||||
|
||||
export type SidebarItemCategory = {
|
||||
item: SidebarItemCategoryType
|
||||
expandItems?: boolean
|
||||
} & React.AllHTMLAttributes<HTMLDivElement>
|
||||
|
||||
export const SidebarItemCategory = ({
|
||||
item,
|
||||
expandItems = true,
|
||||
className,
|
||||
}: SidebarItemCategory) => {
|
||||
const [showLoading, setShowLoading] = useState(false)
|
||||
const [open, setOpen] = useState(
|
||||
item.initialOpen !== undefined ? item.initialOpen : expandItems
|
||||
)
|
||||
const childrenRef = useRef<HTMLUListElement>(null)
|
||||
const { isChildrenActive } = useSidebar()
|
||||
|
||||
useEffect(() => {
|
||||
if (open && !item.loaded) {
|
||||
setShowLoading(true)
|
||||
}
|
||||
}, [open])
|
||||
|
||||
useEffect(() => {
|
||||
if (item.loaded && showLoading) {
|
||||
setShowLoading(false)
|
||||
}
|
||||
}, [item])
|
||||
|
||||
useEffect(() => {
|
||||
const isActive = isChildrenActive(item)
|
||||
if (isActive && !open) {
|
||||
setOpen(true)
|
||||
}
|
||||
}, [isChildrenActive])
|
||||
|
||||
const handleOpen = () => {
|
||||
item.onOpen?.()
|
||||
}
|
||||
|
||||
const isTitleOneWord = useMemo(
|
||||
() => item.title.split(" ").length === 1,
|
||||
[item.title]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={clsx("my-docs_0.75 w-full relative", className)}>
|
||||
<div className="px-docs_0.75">
|
||||
<div
|
||||
className={clsx(
|
||||
"py-docs_0.25 px-docs_0.5",
|
||||
"flex justify-between items-center gap-docs_0.5",
|
||||
"text-medusa-fg-muted",
|
||||
"cursor-pointer relative",
|
||||
"z-[2]",
|
||||
!isTitleOneWord && "break-words"
|
||||
)}
|
||||
tabIndex={-1}
|
||||
onClick={() => {
|
||||
if (!open) {
|
||||
handleOpen()
|
||||
}
|
||||
setOpen((prev) => !prev)
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={clsx(
|
||||
"text-compact-x-small-plus",
|
||||
isTitleOneWord && "truncate"
|
||||
)}
|
||||
>
|
||||
{item.title}
|
||||
</span>
|
||||
{item.additionalElms}
|
||||
{!item.additionalElms && (
|
||||
<>
|
||||
{open && <MinusMini />}
|
||||
{!open && <PlusMini />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
className={clsx(
|
||||
"ease-ease",
|
||||
"flex flex-col gap-docs_0.125",
|
||||
"z-[1] relative",
|
||||
!open && "overflow-hidden m-0 h-0"
|
||||
)}
|
||||
ref={childrenRef}
|
||||
>
|
||||
{showLoading && (
|
||||
<Loading
|
||||
count={3}
|
||||
className="!mb-0 !px-docs_0.5"
|
||||
barClassName="h-[20px]"
|
||||
/>
|
||||
)}
|
||||
{item.children?.map((childItem, index) => (
|
||||
<SidebarItem item={childItem} key={index} expandItems={expandItems} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
147
www/packages/docs-ui/src/components/Sidebar/Item/Link/index.tsx
Normal file
147
www/packages/docs-ui/src/components/Sidebar/Item/Link/index.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
"use client"
|
||||
|
||||
// @refresh reset
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from "react"
|
||||
import { SidebarItemLink as SidebarItemlinkType } from "types"
|
||||
import {
|
||||
checkSidebarItemVisibility,
|
||||
SidebarItem,
|
||||
useMobile,
|
||||
useSidebar,
|
||||
} from "../../../.."
|
||||
import clsx from "clsx"
|
||||
import Link from "next/link"
|
||||
|
||||
export type SidebarItemLinkProps = {
|
||||
item: SidebarItemlinkType
|
||||
nested?: boolean
|
||||
} & React.AllHTMLAttributes<HTMLLIElement>
|
||||
|
||||
export const SidebarItemLink = ({
|
||||
item,
|
||||
className,
|
||||
nested = false,
|
||||
}: SidebarItemLinkProps) => {
|
||||
const {
|
||||
isLinkActive,
|
||||
setMobileSidebarOpen: setSidebarOpen,
|
||||
disableActiveTransition,
|
||||
sidebarRef,
|
||||
sidebarTopHeight,
|
||||
} = useSidebar()
|
||||
const { isMobile } = useMobile()
|
||||
const active = useMemo(() => isLinkActive(item, true), [isLinkActive, item])
|
||||
const ref = useRef<HTMLLIElement>(null)
|
||||
|
||||
const newTopCalculator = useCallback(() => {
|
||||
if (!sidebarRef.current || !ref.current) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const sidebarBoundingRect = sidebarRef.current.getBoundingClientRect()
|
||||
const itemBoundingRect = ref.current.getBoundingClientRect()
|
||||
|
||||
return (
|
||||
itemBoundingRect.top -
|
||||
(sidebarBoundingRect.top + sidebarTopHeight) +
|
||||
sidebarRef.current.scrollTop
|
||||
)
|
||||
}, [sidebarTopHeight, sidebarRef, ref])
|
||||
|
||||
useEffect(() => {
|
||||
if (active && ref.current && sidebarRef.current && !isMobile) {
|
||||
const isVisible = checkSidebarItemVisibility(
|
||||
(ref.current.children.item(0) as HTMLElement) || ref.current,
|
||||
!disableActiveTransition
|
||||
)
|
||||
if (isVisible) {
|
||||
return
|
||||
}
|
||||
if (!disableActiveTransition) {
|
||||
ref.current.scrollIntoView({
|
||||
block: "center",
|
||||
})
|
||||
} else {
|
||||
sidebarRef.current.scrollTo({
|
||||
top: newTopCalculator(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [
|
||||
active,
|
||||
sidebarRef.current,
|
||||
disableActiveTransition,
|
||||
isMobile,
|
||||
newTopCalculator,
|
||||
])
|
||||
|
||||
const hasChildren = useMemo(() => {
|
||||
return !item.isChildSidebar && (item.children?.length || 0) > 0
|
||||
}, [item.children])
|
||||
|
||||
const isTitleOneWord = useMemo(
|
||||
() => item.title.split(" ").length === 1,
|
||||
[item.title]
|
||||
)
|
||||
|
||||
return (
|
||||
<li ref={ref}>
|
||||
<span className="block px-docs_0.75">
|
||||
<Link
|
||||
href={item.isPathHref ? item.path : `#${item.path}`}
|
||||
className={clsx(
|
||||
"py-docs_0.25 px-docs_0.5",
|
||||
"block w-full rounded-docs_sm",
|
||||
!isTitleOneWord && "break-words",
|
||||
active && [
|
||||
"bg-medusa-bg-base",
|
||||
"shadow-elevation-card-rest dark:shadow-elevation-card-rest-dark",
|
||||
"text-medusa-fg-base",
|
||||
],
|
||||
!active && [
|
||||
!nested && "text-medusa-fg-subtle",
|
||||
nested && "text-medusa-fg-muted",
|
||||
"hover:bg-medusa-bg-base-hover lg:hover:bg-medusa-bg-subtle-hover",
|
||||
],
|
||||
"text-compact-small-plus",
|
||||
"flex justify-between items-center gap-[6px]",
|
||||
className
|
||||
)}
|
||||
scroll={true}
|
||||
onClick={() => {
|
||||
if (isMobile) {
|
||||
setSidebarOpen(false)
|
||||
}
|
||||
}}
|
||||
replace={!item.isPathHref}
|
||||
shallow={!item.isPathHref}
|
||||
{...item.linkProps}
|
||||
>
|
||||
<span className={clsx(isTitleOneWord && "truncate")}>
|
||||
{item.title}
|
||||
</span>
|
||||
{item.additionalElms}
|
||||
</Link>
|
||||
</span>
|
||||
{hasChildren && (
|
||||
<ul
|
||||
className={clsx(
|
||||
"ease-ease overflow-hidden",
|
||||
"flex flex-col gap-docs_0.125",
|
||||
!item.childrenSameLevel && "pl-docs_1.5",
|
||||
"pt-docs_0.125 pb-docs_0.5"
|
||||
)}
|
||||
>
|
||||
{item.children!.map((childItem, index) => (
|
||||
<SidebarItem
|
||||
item={childItem}
|
||||
key={index}
|
||||
nested={!item.childrenSameLevel}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
"use client"
|
||||
|
||||
// @refresh reset
|
||||
|
||||
import React, { useEffect, useMemo, useRef } from "react"
|
||||
import { SidebarItemSubCategory as SidebarItemSubCategoryType } from "types"
|
||||
import {
|
||||
checkSidebarItemVisibility,
|
||||
SidebarItem,
|
||||
useMobile,
|
||||
useSidebar,
|
||||
} from "../../../.."
|
||||
import clsx from "clsx"
|
||||
|
||||
export type SidebarItemLinkProps = {
|
||||
item: SidebarItemSubCategoryType
|
||||
nested?: boolean
|
||||
} & React.AllHTMLAttributes<HTMLLIElement>
|
||||
|
||||
export const SidebarItemSubCategory = ({
|
||||
item,
|
||||
className,
|
||||
nested = false,
|
||||
}: SidebarItemLinkProps) => {
|
||||
const { isLinkActive, disableActiveTransition, sidebarRef } = useSidebar()
|
||||
const { isMobile } = useMobile()
|
||||
const active = useMemo(
|
||||
() => !isMobile && isLinkActive(item, true),
|
||||
[isLinkActive, item, isMobile]
|
||||
)
|
||||
const ref = useRef<HTMLLIElement>(null)
|
||||
|
||||
/**
|
||||
* Tries to place the item in the center of the sidebar
|
||||
*/
|
||||
const newTopCalculator = (): number => {
|
||||
if (!sidebarRef.current || !ref.current) {
|
||||
return 0
|
||||
}
|
||||
const sidebarBoundingRect = sidebarRef.current.getBoundingClientRect()
|
||||
const sidebarHalf = sidebarBoundingRect.height / 2
|
||||
const itemTop = ref.current.offsetTop
|
||||
const itemBottom =
|
||||
itemTop + (ref.current.children.item(0) as HTMLElement)?.clientHeight
|
||||
|
||||
// try deducting half
|
||||
let newTop = itemTop - sidebarHalf
|
||||
let newBottom = newTop + sidebarBoundingRect.height
|
||||
if (newTop <= itemTop && newBottom >= itemBottom) {
|
||||
return newTop
|
||||
}
|
||||
|
||||
// try adding half
|
||||
newTop = itemTop + sidebarHalf
|
||||
newBottom = newTop + sidebarBoundingRect.height
|
||||
if (newTop <= itemTop && newBottom >= itemBottom) {
|
||||
return newTop
|
||||
}
|
||||
|
||||
//return the item's top minus some top margin
|
||||
return itemTop - sidebarBoundingRect.top
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
active &&
|
||||
ref.current &&
|
||||
sidebarRef.current &&
|
||||
window.innerWidth >= 1025
|
||||
) {
|
||||
if (
|
||||
!disableActiveTransition &&
|
||||
!checkSidebarItemVisibility(
|
||||
(ref.current.children.item(0) as HTMLElement) || ref.current,
|
||||
!disableActiveTransition
|
||||
)
|
||||
) {
|
||||
ref.current.scrollIntoView({
|
||||
block: "center",
|
||||
})
|
||||
} else if (disableActiveTransition) {
|
||||
sidebarRef.current.scrollTo({
|
||||
top: newTopCalculator(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [active, sidebarRef.current, disableActiveTransition])
|
||||
|
||||
const hasChildren = useMemo(() => {
|
||||
return item.children?.length || 0 > 0
|
||||
}, [item.children])
|
||||
|
||||
const isTitleOneWord = useMemo(
|
||||
() => item.title.split(" ").length === 1,
|
||||
[item.title]
|
||||
)
|
||||
|
||||
return (
|
||||
<li ref={ref}>
|
||||
<span className="block px-docs_0.75">
|
||||
<span
|
||||
className={clsx(
|
||||
"py-docs_0.25 px-docs_0.5",
|
||||
"block w-full",
|
||||
!isTitleOneWord && "break-words",
|
||||
active && [
|
||||
"rounded-docs_sm",
|
||||
"shadow-borders-base dark:shadow-borders-base-dark",
|
||||
"text-medusa-fg-base",
|
||||
],
|
||||
!active && [
|
||||
!nested && "text-medusa-fg-subtle",
|
||||
nested && "text-medusa-fg-muted",
|
||||
],
|
||||
"text-compact-small-plus",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<span className={clsx(isTitleOneWord && "truncate")}>
|
||||
{item.title}
|
||||
</span>
|
||||
{item.additionalElms}
|
||||
</span>
|
||||
</span>
|
||||
{hasChildren && (
|
||||
<ul
|
||||
className={clsx(
|
||||
"ease-ease overflow-hidden",
|
||||
"flex flex-col gap-docs_0.125",
|
||||
!item.childrenSameLevel && "pl-docs_1.5",
|
||||
"pb-docs_0.5 pt-docs_0.125"
|
||||
)}
|
||||
>
|
||||
{item.children!.map((childItem, index) => (
|
||||
<SidebarItem
|
||||
item={childItem}
|
||||
key={index}
|
||||
nested={!item.childrenSameLevel}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
@@ -1,204 +1,37 @@
|
||||
"use client"
|
||||
|
||||
// @refresh reset
|
||||
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useSidebar } from "@/providers"
|
||||
import clsx from "clsx"
|
||||
import Link from "next/link"
|
||||
import { checkSidebarItemVisibility } from "@/utils"
|
||||
import { Loading } from "@/components"
|
||||
import { SidebarItemType } from "types"
|
||||
import React from "react"
|
||||
import { SidebarItem as SidebarItemType } from "types"
|
||||
import { SidebarItemCategory } from "./Category"
|
||||
import { SidebarItemLink } from "./Link"
|
||||
import { SidebarItemSubCategory } from "./SubCategory"
|
||||
import { SidebarSeparator } from "../Separator"
|
||||
|
||||
export type SidebarItemProps = {
|
||||
item: SidebarItemType
|
||||
nested?: boolean
|
||||
expandItems?: boolean
|
||||
currentLevel?: number
|
||||
isSidebarTitle?: boolean
|
||||
sidebarHasParent?: boolean
|
||||
isMobile?: boolean
|
||||
} & React.AllHTMLAttributes<HTMLLIElement>
|
||||
hasNextItems?: boolean
|
||||
} & React.AllHTMLAttributes<HTMLElement>
|
||||
|
||||
export const SidebarItem = ({
|
||||
item,
|
||||
nested = false,
|
||||
expandItems = false,
|
||||
className,
|
||||
currentLevel = 1,
|
||||
sidebarHasParent = false,
|
||||
isMobile = false,
|
||||
hasNextItems = false,
|
||||
...props
|
||||
}: SidebarItemProps) => {
|
||||
const [showLoading, setShowLoading] = useState(false)
|
||||
const {
|
||||
isItemActive,
|
||||
setMobileSidebarOpen: setSidebarOpen,
|
||||
disableActiveTransition,
|
||||
noTitleStyling,
|
||||
sidebarRef,
|
||||
} = useSidebar()
|
||||
const active = useMemo(
|
||||
() => !isMobile && isItemActive(item, nested),
|
||||
[isItemActive, item, nested, isMobile]
|
||||
)
|
||||
const collapsed = !expandItems && !isItemActive(item, true)
|
||||
const ref = useRef<HTMLLIElement>(null)
|
||||
|
||||
const itemChildren = useMemo(() => {
|
||||
return item.isChildSidebar ? undefined : item.children
|
||||
}, [item])
|
||||
const canHaveTitleStyling = useMemo(
|
||||
() =>
|
||||
item.hasTitleStyling ||
|
||||
((itemChildren?.length || !item.loaded) && !noTitleStyling && !nested),
|
||||
[itemChildren, noTitleStyling, item, nested]
|
||||
)
|
||||
|
||||
const classNames = useMemo(
|
||||
() =>
|
||||
clsx(
|
||||
"flex items-center justify-between gap-docs_0.5 rounded-docs_xs px-docs_0.5 py-[6px]",
|
||||
"hover:no-underline hover:bg-medusa-bg-component-hover",
|
||||
!canHaveTitleStyling && "text-compact-small-plus text-medusa-fg-subtle",
|
||||
canHaveTitleStyling &&
|
||||
"text-compact-x-small-plus text-medusa-fg-muted uppercase",
|
||||
!item.path && "cursor-default",
|
||||
item.path !== undefined &&
|
||||
active &&
|
||||
"text-medusa-fg-base bg-medusa-bg-component-hover"
|
||||
),
|
||||
[canHaveTitleStyling, active, item.path]
|
||||
)
|
||||
|
||||
/**
|
||||
* Tries to place the item in the center of the sidebar
|
||||
*/
|
||||
const newTopCalculator = (): number => {
|
||||
if (!sidebarRef.current || !ref.current) {
|
||||
return 0
|
||||
}
|
||||
const sidebarBoundingRect = sidebarRef.current.getBoundingClientRect()
|
||||
const sidebarHalf = sidebarBoundingRect.height / 2
|
||||
const itemTop = ref.current.offsetTop
|
||||
const itemBottom =
|
||||
itemTop + (ref.current.children.item(0) as HTMLElement)?.clientHeight
|
||||
|
||||
// try deducting half
|
||||
let newTop = itemTop - sidebarHalf
|
||||
let newBottom = newTop + sidebarBoundingRect.height
|
||||
if (newTop <= itemTop && newBottom >= itemBottom) {
|
||||
return newTop
|
||||
}
|
||||
|
||||
// try adding half
|
||||
newTop = itemTop + sidebarHalf
|
||||
newBottom = newTop + sidebarBoundingRect.height
|
||||
if (newTop <= itemTop && newBottom >= itemBottom) {
|
||||
return newTop
|
||||
}
|
||||
|
||||
//return the item's top minus some top margin
|
||||
return itemTop - sidebarBoundingRect.top
|
||||
switch (item.type) {
|
||||
case "category":
|
||||
return (
|
||||
<>
|
||||
<SidebarItemCategory item={item} {...props} />
|
||||
{hasNextItems && <SidebarSeparator />}
|
||||
</>
|
||||
)
|
||||
case "sub-category":
|
||||
return <SidebarItemSubCategory item={item} {...props} />
|
||||
case "link":
|
||||
return <SidebarItemLink item={item} {...props} />
|
||||
case "separator":
|
||||
return <SidebarSeparator />
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
active &&
|
||||
ref.current &&
|
||||
sidebarRef.current &&
|
||||
window.innerWidth >= 1025
|
||||
) {
|
||||
if (
|
||||
!disableActiveTransition &&
|
||||
!checkSidebarItemVisibility(
|
||||
(ref.current.children.item(0) as HTMLElement) || ref.current,
|
||||
!disableActiveTransition
|
||||
)
|
||||
) {
|
||||
ref.current.scrollIntoView({
|
||||
block: "center",
|
||||
})
|
||||
} else if (disableActiveTransition) {
|
||||
sidebarRef.current.scrollTo({
|
||||
top: newTopCalculator(),
|
||||
})
|
||||
}
|
||||
}
|
||||
if (active) {
|
||||
setShowLoading(true)
|
||||
}
|
||||
}, [active, sidebarRef.current, disableActiveTransition])
|
||||
|
||||
return (
|
||||
<li
|
||||
className={clsx(
|
||||
canHaveTitleStyling &&
|
||||
!collapsed && [
|
||||
!sidebarHasParent && "my-docs_1.5",
|
||||
sidebarHasParent && "[&:not(:first-child)]:my-docs_1.5",
|
||||
],
|
||||
!canHaveTitleStyling &&
|
||||
!nested &&
|
||||
active &&
|
||||
!disableActiveTransition &&
|
||||
"mt-docs_1.5",
|
||||
!expandItems &&
|
||||
((canHaveTitleStyling && !collapsed) ||
|
||||
(!canHaveTitleStyling && !nested && active)) &&
|
||||
"-translate-y-docs_1 transition-transform",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
>
|
||||
{item.path === undefined && (
|
||||
<span className={classNames}>
|
||||
<span>{item.title}</span>
|
||||
{item.additionalElms}
|
||||
</span>
|
||||
)}
|
||||
{item.path !== undefined && (
|
||||
<Link
|
||||
href={item.isPathHref ? item.path : `#${item.path}`}
|
||||
className={classNames}
|
||||
scroll={true}
|
||||
onClick={() => {
|
||||
if (window.innerWidth < 1025) {
|
||||
setSidebarOpen(false)
|
||||
}
|
||||
}}
|
||||
replace={!item.isPathHref}
|
||||
shallow={!item.isPathHref}
|
||||
{...item.linkProps}
|
||||
>
|
||||
<span>{item.title}</span>
|
||||
{item.additionalElms}
|
||||
</Link>
|
||||
)}
|
||||
{itemChildren && (
|
||||
<ul
|
||||
className={clsx("ease-ease overflow-hidden", collapsed && "m-0 h-0")}
|
||||
style={{
|
||||
paddingLeft: noTitleStyling ? `${currentLevel * 6}px` : 0,
|
||||
}}
|
||||
>
|
||||
{showLoading && !item.loaded && (
|
||||
<Loading
|
||||
count={3}
|
||||
className="!mb-0 !px-docs_0.5"
|
||||
barClassName="h-[20px]"
|
||||
/>
|
||||
)}
|
||||
{itemChildren?.map((childItem, index) => (
|
||||
<SidebarItem
|
||||
item={childItem}
|
||||
key={index}
|
||||
nested={true}
|
||||
currentLevel={currentLevel + 1}
|
||||
expandItems={expandItems}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
"use client"
|
||||
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
|
||||
export type SidebarSeparatorProps = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const SidebarSeparator = ({ className }: SidebarSeparatorProps) => {
|
||||
return (
|
||||
<div className="px-docs_0.75">
|
||||
<span
|
||||
className={clsx(
|
||||
"block w-full h-px relative my-docs_0.75 bg-border-dotted",
|
||||
"bg-[length:4px_1px] bg-repeat-x bg-bottom",
|
||||
className
|
||||
)}
|
||||
></span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from "react"
|
||||
import Link from "next/link"
|
||||
import clsx from "clsx"
|
||||
import { SidebarItemType } from "types"
|
||||
|
||||
type SidebarTitleProps = {
|
||||
item: SidebarItemType
|
||||
}
|
||||
|
||||
export const SidebarTitle = ({ item }: SidebarTitleProps) => {
|
||||
return (
|
||||
<Link
|
||||
className={clsx(
|
||||
"flex items-center justify-between gap-docs_0.5 rounded-docs_sm px-docs_0.5 pb-[6px] hover:no-underline",
|
||||
"border border-transparent",
|
||||
"text-medusa-fg-subtle text-large-plus"
|
||||
)}
|
||||
href={item.isPathHref && item.path ? item.path : `#${item.path}`}
|
||||
replace={!item.isPathHref}
|
||||
shallow={!item.isPathHref}
|
||||
{...item.linkProps}
|
||||
>
|
||||
<span>{item.childSidebarTitle || item.title}</span>
|
||||
{item.additionalElms}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
"use client"
|
||||
|
||||
import clsx from "clsx"
|
||||
import React, { useRef, useState } from "react"
|
||||
import {
|
||||
BorderedIcon,
|
||||
getOsShortcut,
|
||||
useClickOutside,
|
||||
useSidebar,
|
||||
} from "../../../.."
|
||||
import {
|
||||
EllipsisHorizontal,
|
||||
SidebarLeft,
|
||||
TimelineVertical,
|
||||
} from "@medusajs/icons"
|
||||
import { MedusaIcon } from "../../../Icons/MedusaLogo"
|
||||
import { HouseIcon } from "../../../Icons/House"
|
||||
import { Menu } from "../../../Menu"
|
||||
|
||||
export const SidebarTopMedusaMenu = () => {
|
||||
const [openMenu, setOpenMenu] = useState(false)
|
||||
const { setDesktopSidebarOpen } = useSidebar()
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
const toggleOpen = () => setOpenMenu((prev) => !prev)
|
||||
|
||||
useClickOutside({
|
||||
elmRef: ref,
|
||||
onClickOutside: () => {
|
||||
setOpenMenu(false)
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={clsx("p-docs_0.75", "relative")} ref={ref}>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex justify-between items-center gap-docs_0.5",
|
||||
"rounded-docs_sm hover:bg-medusa-bg-subtle-hover cursor-pointer",
|
||||
"py-docs_0.125 pl-docs_0.125 pr-docs_0.5"
|
||||
)}
|
||||
tabIndex={-1}
|
||||
onClick={toggleOpen}
|
||||
>
|
||||
<BorderedIcon IconComponent={MedusaIcon} />
|
||||
<span className="text-compact-small-plus text-medusa-fg-base flex-1">
|
||||
Medusa Docs
|
||||
</span>
|
||||
<EllipsisHorizontal className="text-medusa-fg-subtle" />
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"absolute w-[calc(100%-16px)] bottom-[calc(-100%-40px)]",
|
||||
"left-docs_0.5 z-40",
|
||||
!openMenu && "hidden"
|
||||
)}
|
||||
>
|
||||
<Menu
|
||||
items={[
|
||||
{
|
||||
type: "link",
|
||||
icon: <HouseIcon />,
|
||||
title: "Homepage",
|
||||
link: "https://medusajs.com",
|
||||
},
|
||||
{
|
||||
type: "link",
|
||||
icon: <TimelineVertical />,
|
||||
title: "Changelog",
|
||||
link: "https://medusajs.com/changelog",
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
type: "action",
|
||||
title: "Hide Sidebar",
|
||||
icon: <SidebarLeft />,
|
||||
shortcut: `${getOsShortcut()}.`,
|
||||
action: () => {
|
||||
setDesktopSidebarOpen(false)
|
||||
setOpenMenu(false)
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { Button, useSidebar } from "../../../.."
|
||||
import { XMarkMini } from "@medusajs/icons"
|
||||
|
||||
export const SidebarTopMobileClose = () => {
|
||||
const { setMobileSidebarOpen } = useSidebar()
|
||||
|
||||
return (
|
||||
<div className="m-docs_0.75 lg:hidden">
|
||||
<Button
|
||||
variant="transparent-clear"
|
||||
onClick={() => setMobileSidebarOpen(false)}
|
||||
className="!p-0 hover:!bg-transparent"
|
||||
>
|
||||
<XMarkMini className="text-medusa-fg-subtle" />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
32
www/packages/docs-ui/src/components/Sidebar/Top/index.tsx
Normal file
32
www/packages/docs-ui/src/components/Sidebar/Top/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { SidebarChild } from "../Child"
|
||||
import { InteractiveSidebarItem } from "types"
|
||||
import { SidebarSeparator } from "../Separator"
|
||||
import { SidebarTopMobileClose } from "./MobileClose"
|
||||
import { SidebarTopMedusaMenu } from "./MedusaMenu"
|
||||
|
||||
export type SidebarTopProps = {
|
||||
parentItem?: InteractiveSidebarItem
|
||||
}
|
||||
|
||||
export const SidebarTop = React.forwardRef<HTMLDivElement, SidebarTopProps>(
|
||||
function SidebarTop({ parentItem }, ref) {
|
||||
return (
|
||||
<div className="pt-docs_0.25">
|
||||
<SidebarTopMobileClose />
|
||||
<div ref={ref}>
|
||||
<SidebarTopMedusaMenu />
|
||||
{parentItem && (
|
||||
<>
|
||||
<SidebarSeparator />
|
||||
<SidebarChild item={parentItem} />
|
||||
</>
|
||||
)}
|
||||
<SidebarSeparator className="!my-0" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -5,125 +5,153 @@ import { useSidebar } from "@/providers"
|
||||
import clsx from "clsx"
|
||||
import { Loading } from "@/components"
|
||||
import { SidebarItem } from "./Item"
|
||||
import { SidebarTitle } from "./Title"
|
||||
import { SidebarBack } from "./Back"
|
||||
import { CSSTransition, SwitchTransition } from "react-transition-group"
|
||||
import { SidebarTop, SidebarTopProps } from "./Top"
|
||||
import useResizeObserver from "@react-hook/resize-observer"
|
||||
import { SidebarSeparator } from "./Separator"
|
||||
import { useClickOutside, useKeyboardShortcut } from "@/hooks"
|
||||
|
||||
export type SidebarProps = {
|
||||
className?: string
|
||||
expandItems?: boolean
|
||||
banner?: React.ReactNode
|
||||
sidebarTopProps?: Omit<SidebarTopProps, "parentItem">
|
||||
}
|
||||
|
||||
export const Sidebar = ({
|
||||
className = "",
|
||||
expandItems = false,
|
||||
banner,
|
||||
expandItems = true,
|
||||
sidebarTopProps,
|
||||
}: SidebarProps) => {
|
||||
const sidebarWrapperRef = useRef(null)
|
||||
const sidebarTopRef = useRef<HTMLDivElement>(null)
|
||||
const {
|
||||
items,
|
||||
currentItems,
|
||||
mobileSidebarOpen,
|
||||
desktopSidebarOpen,
|
||||
setMobileSidebarOpen,
|
||||
staticSidebarItems,
|
||||
sidebarRef,
|
||||
sidebarTopHeight,
|
||||
setSidebarTopHeight,
|
||||
desktopSidebarOpen,
|
||||
setDesktopSidebarOpen,
|
||||
} = useSidebar()
|
||||
useClickOutside({
|
||||
elmRef: sidebarWrapperRef,
|
||||
onClickOutside: () => {
|
||||
if (mobileSidebarOpen) {
|
||||
setMobileSidebarOpen(false)
|
||||
}
|
||||
},
|
||||
})
|
||||
useKeyboardShortcut({
|
||||
metakey: true,
|
||||
shortcutKeys: ["."],
|
||||
action: () => {
|
||||
setDesktopSidebarOpen((prev) => !prev)
|
||||
},
|
||||
})
|
||||
|
||||
const sidebarItems = useMemo(
|
||||
() => currentItems || items,
|
||||
[items, currentItems]
|
||||
)
|
||||
|
||||
const sidebarHasParent = useMemo(
|
||||
() => sidebarItems.parentItem !== undefined,
|
||||
[sidebarItems]
|
||||
)
|
||||
useResizeObserver(sidebarTopRef, () => {
|
||||
setSidebarTopHeight(sidebarTopRef.current?.clientHeight || 0)
|
||||
})
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={clsx(
|
||||
"clip bg-docs-bg dark:bg-docs-bg-dark block",
|
||||
"border-medusa-border-base border-0 border-r border-solid",
|
||||
"fixed -left-full top-0 h-screen transition-[left] lg:relative lg:left-0 lg:top-auto lg:h-auto",
|
||||
"lg:w-sidebar w-full",
|
||||
mobileSidebarOpen && "!left-0 z-50 top-[57px]",
|
||||
!desktopSidebarOpen && "!absolute !-left-full",
|
||||
className
|
||||
<>
|
||||
{mobileSidebarOpen && (
|
||||
<div
|
||||
className={clsx(
|
||||
"lg:hidden bg-medusa-bg-overlay opacity-70",
|
||||
"fixed top-0 left-0 w-full h-full z-10"
|
||||
)}
|
||||
></div>
|
||||
)}
|
||||
style={{
|
||||
animationFillMode: "forwards",
|
||||
}}
|
||||
>
|
||||
<ul
|
||||
<aside
|
||||
className={clsx(
|
||||
"sticky top-0 h-screen max-h-screen w-full list-none p-0",
|
||||
"px-docs_1.5 pb-[57px] pt-docs_1.5",
|
||||
"flex flex-col"
|
||||
"bg-medusa-bg-base lg:bg-transparent block",
|
||||
"fixed -left-full top-0 h-[calc(100%-16px)] transition-[left] lg:relative lg:h-auto",
|
||||
"max-w-sidebar-xs sm:max-w-sidebar-sm md:max-w-sidebar-md lg:max-w-sidebar-lg",
|
||||
"xl:max-w-sidebar-xl xxl:max-w-sidebar-xxl xxxl:max-w-sidebar-xxxl",
|
||||
mobileSidebarOpen && [
|
||||
"!left-docs_0.5 !top-docs_0.5 z-50 shadow-elevation-modal dark:shadow-elevation-modal-dark",
|
||||
"rounded",
|
||||
"lg:!left-0 lg:!top-0 lg:shadow-none",
|
||||
],
|
||||
desktopSidebarOpen && "lg:left-0",
|
||||
!desktopSidebarOpen && "lg:!absolute lg:!-left-full",
|
||||
className
|
||||
)}
|
||||
id="sidebar"
|
||||
style={{
|
||||
animationFillMode: "forwards",
|
||||
}}
|
||||
ref={sidebarWrapperRef}
|
||||
>
|
||||
{banner && <div className="mb-docs_1">{banner}</div>}
|
||||
{sidebarItems.parentItem && (
|
||||
<div className={clsx("mb-docs_1", !banner && "mt-docs_1.5")}>
|
||||
<SidebarBack />
|
||||
<SidebarTitle item={sidebarItems.parentItem} />
|
||||
</div>
|
||||
)}
|
||||
<SwitchTransition>
|
||||
<CSSTransition
|
||||
key={sidebarItems.parentItem?.title || "home"}
|
||||
nodeRef={sidebarRef}
|
||||
classNames={{
|
||||
enter: "animate-fadeInLeft animate-fast",
|
||||
exit: "animate-fadeOutLeft animate-fast",
|
||||
}}
|
||||
timeout={200}
|
||||
>
|
||||
<div className="overflow-auto" ref={sidebarRef}>
|
||||
<div className={clsx("mb-docs_1.5 lg:hidden")}>
|
||||
{!sidebarItems.mobile.length && !staticSidebarItems && (
|
||||
<Loading className="px-0" />
|
||||
<ul className={clsx("h-full w-full", "flex flex-col")}>
|
||||
<SidebarTop
|
||||
{...sidebarTopProps}
|
||||
parentItem={sidebarItems.parentItem}
|
||||
ref={sidebarTopRef}
|
||||
/>
|
||||
<SwitchTransition>
|
||||
<CSSTransition
|
||||
key={sidebarItems.parentItem?.title || "home"}
|
||||
nodeRef={sidebarRef}
|
||||
classNames={{
|
||||
enter: "animate-fadeInLeft animate-fast",
|
||||
exit: "animate-fadeOutLeft animate-fast",
|
||||
}}
|
||||
timeout={200}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"overflow-y-scroll clip",
|
||||
"py-docs_0.75 flex-1"
|
||||
)}
|
||||
{sidebarItems.mobile.map((item, index) => (
|
||||
<SidebarItem
|
||||
item={item}
|
||||
key={index}
|
||||
expandItems={expandItems}
|
||||
sidebarHasParent={sidebarHasParent}
|
||||
isMobile={true}
|
||||
/>
|
||||
))}
|
||||
ref={sidebarRef}
|
||||
style={{
|
||||
maxHeight: `calc(100vh - ${sidebarTopHeight}px)`,
|
||||
}}
|
||||
id="sidebar"
|
||||
>
|
||||
{/* MOBILE SIDEBAR */}
|
||||
<div className={clsx("lg:hidden")}>
|
||||
{!sidebarItems.mobile.length && !staticSidebarItems && (
|
||||
<Loading className="px-0" />
|
||||
)}
|
||||
{sidebarItems.mobile.map((item, index) => (
|
||||
<SidebarItem
|
||||
item={item}
|
||||
key={index}
|
||||
expandItems={expandItems}
|
||||
hasNextItems={index !== sidebarItems.default.length - 1}
|
||||
/>
|
||||
))}
|
||||
<SidebarSeparator />
|
||||
</div>
|
||||
{/* DESKTOP SIDEBAR */}
|
||||
<div className="mt-docs_0.75 lg:mt-0">
|
||||
{!sidebarItems.default.length && !staticSidebarItems && (
|
||||
<Loading className="px-0" />
|
||||
)}
|
||||
{sidebarItems.default.map((item, index) => (
|
||||
<SidebarItem
|
||||
item={item}
|
||||
key={index}
|
||||
expandItems={expandItems}
|
||||
hasNextItems={index !== sidebarItems.default.length - 1}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={clsx("mb-docs_1.5")}>
|
||||
{!sidebarItems.top.length && !staticSidebarItems && (
|
||||
<Loading className="px-0" />
|
||||
)}
|
||||
{sidebarItems.top.map((item, index) => (
|
||||
<SidebarItem
|
||||
item={item}
|
||||
key={index}
|
||||
expandItems={expandItems}
|
||||
sidebarHasParent={sidebarHasParent}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="mb-docs_1.5">
|
||||
{!sidebarItems.bottom.length && !staticSidebarItems && (
|
||||
<Loading className="px-0" />
|
||||
)}
|
||||
{sidebarItems.bottom.map((item, index) => (
|
||||
<SidebarItem
|
||||
item={item}
|
||||
key={index}
|
||||
expandItems={expandItems}
|
||||
sidebarHasParent={sidebarHasParent}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CSSTransition>
|
||||
</SwitchTransition>
|
||||
</ul>
|
||||
</aside>
|
||||
</CSSTransition>
|
||||
</SwitchTransition>
|
||||
</ul>
|
||||
</aside>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
33
www/packages/docs-ui/src/components/Toc/Item/index.tsx
Normal file
33
www/packages/docs-ui/src/components/Toc/Item/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
"use client"
|
||||
|
||||
import clsx from "clsx"
|
||||
import React, { useMemo } from "react"
|
||||
import { ToCItemUi } from "types"
|
||||
import { TocList } from "../List"
|
||||
|
||||
export type TocItemProps = {
|
||||
item: ToCItemUi
|
||||
activeItem: string
|
||||
}
|
||||
|
||||
export const TocItem = ({ item, activeItem }: TocItemProps) => {
|
||||
const isActive = useMemo(() => item.id === activeItem, [item, activeItem])
|
||||
|
||||
return (
|
||||
<li>
|
||||
<span
|
||||
className={clsx(
|
||||
"h-docs_0.125 rounded-full transition-colors",
|
||||
isActive && "bg-medusa-fg-subtle",
|
||||
!isActive && "bg-medusa-fg-disabled",
|
||||
item.level === 2 && "w-[20px]",
|
||||
item.level === 3 && "w-[10px]",
|
||||
"block"
|
||||
)}
|
||||
></span>
|
||||
{(item.children?.length || 0) > 0 && (
|
||||
<TocList items={item.children!} activeItem={activeItem} />
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
29
www/packages/docs-ui/src/components/Toc/List/index.tsx
Normal file
29
www/packages/docs-ui/src/components/Toc/List/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
import { ToCItemUi } from "types"
|
||||
import { TocItem } from "../Item"
|
||||
|
||||
export type TocListProps = {
|
||||
items: ToCItemUi[]
|
||||
topLevel?: boolean
|
||||
activeItem: string
|
||||
}
|
||||
|
||||
export const TocList = ({
|
||||
items,
|
||||
topLevel = false,
|
||||
activeItem,
|
||||
}: TocListProps) => {
|
||||
return (
|
||||
<ul
|
||||
className={clsx(
|
||||
"flex flex-col gap-docs_0.75 items-end",
|
||||
!topLevel && "mt-docs_0.75"
|
||||
)}
|
||||
>
|
||||
{items.map((item, key) => (
|
||||
<TocItem item={item} key={key} activeItem={activeItem} />
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
80
www/packages/docs-ui/src/components/Toc/Menu/index.tsx
Normal file
80
www/packages/docs-ui/src/components/Toc/Menu/index.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
"use client"
|
||||
|
||||
import { EllipseMiniSolid } from "@medusajs/icons"
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
import { ToCItemUi } from "types"
|
||||
import { Button, useScrollController } from "../../.."
|
||||
|
||||
export type TocMenuProps = {
|
||||
items: ToCItemUi[]
|
||||
activeItem: string
|
||||
show: boolean
|
||||
setShow: (value: boolean) => void
|
||||
}
|
||||
|
||||
export const TocMenu = ({ items, activeItem, show, setShow }: TocMenuProps) => {
|
||||
const { scrollToElement } = useScrollController()
|
||||
|
||||
const getItemElm = (item: ToCItemUi) => {
|
||||
const isActive = item.id === activeItem
|
||||
const hasChildren = item.children?.length || 0 > 0
|
||||
return (
|
||||
<li className={clsx("text-medusa-fg-base w-full")}>
|
||||
<Button
|
||||
variant="transparent-clear"
|
||||
className={clsx(
|
||||
"gap-docs_0.5 flex-1",
|
||||
"cursor-pointer rounded-docs_sm py-docs_0.25",
|
||||
"px-docs_0.5 hover:bg-medusa-bg-component-hover",
|
||||
"!text-inherit max-w-full w-full",
|
||||
"focus:!outline-none focus:!shadow-none focus:dark:!shadow-none",
|
||||
"!flex !justify-start !items-center",
|
||||
isActive && "!text-compact-small-plus",
|
||||
!isActive && "!text-compact-small"
|
||||
)}
|
||||
onClick={() => {
|
||||
history.pushState({}, "", `#${item.id}`)
|
||||
const elm = document.getElementById(item.id) as HTMLElement
|
||||
scrollToElement(elm)
|
||||
}}
|
||||
>
|
||||
<EllipseMiniSolid className={clsx(!isActive && "invisible")} />
|
||||
<span className="truncate flex-1 text-left">{item.title}</span>
|
||||
</Button>
|
||||
{hasChildren && (
|
||||
<ul className="pl-docs_0.5">
|
||||
{item.children!.map((childItem, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{getItemElm(childItem)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"hidden lg:flex relative transition-[width] lg:h-full",
|
||||
"w-0 z-50 bg-medusa-bg-subtle overflow-hidden flex flex-col justify-center",
|
||||
show && "lg:w-toc"
|
||||
)}
|
||||
onMouseLeave={() => setShow(false)}
|
||||
>
|
||||
<ul
|
||||
className={clsx(
|
||||
"p-docs_0.75 lg:w-toc max-h-full overflow-y-scroll",
|
||||
"absolute lg:-right-full transition-[right,opacity] opacity-0",
|
||||
show && "lg:right-0 lg:opacity-100"
|
||||
)}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<React.Fragment key={index}>{getItemElm(item)}</React.Fragment>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
99
www/packages/docs-ui/src/components/Toc/index.tsx
Normal file
99
www/packages/docs-ui/src/components/Toc/index.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
"use client"
|
||||
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { ToCItemUi } from "types"
|
||||
import {
|
||||
ActiveOnScrollItem,
|
||||
isElmWindow,
|
||||
useActiveOnScroll,
|
||||
useIsBrowser,
|
||||
useScrollController,
|
||||
} from "../.."
|
||||
import { TocList } from "./List"
|
||||
import clsx from "clsx"
|
||||
import { TocMenu } from "./Menu"
|
||||
|
||||
export const Toc = () => {
|
||||
const [items, setItems] = useState<ToCItemUi[]>([])
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
const isBrowser = useIsBrowser()
|
||||
const { items: headingItems, activeItemId } = useActiveOnScroll({})
|
||||
const [maxHeight, setMaxHeight] = useState(0)
|
||||
const { scrollableElement } = useScrollController()
|
||||
|
||||
const formatHeadingContent = (content: string | null): string => {
|
||||
return content?.replaceAll(/#$/g, "") || ""
|
||||
}
|
||||
|
||||
const formatHeadingObject = ({
|
||||
heading,
|
||||
children,
|
||||
}: ActiveOnScrollItem): ToCItemUi => {
|
||||
const level = parseInt(heading.tagName.replace("H", ""))
|
||||
return {
|
||||
title: formatHeadingContent(heading.textContent),
|
||||
id: heading.id,
|
||||
level,
|
||||
children: children?.map(formatHeadingObject),
|
||||
associatedHeading: heading as HTMLHeadingElement,
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setItems(headingItems.map(formatHeadingObject))
|
||||
}, [headingItems])
|
||||
|
||||
const handleResize = () => {
|
||||
const offset =
|
||||
(scrollableElement instanceof HTMLElement
|
||||
? scrollableElement.offsetTop
|
||||
: 0) + 56
|
||||
|
||||
setMaxHeight(
|
||||
(isElmWindow(scrollableElement)
|
||||
? scrollableElement.innerHeight
|
||||
: scrollableElement?.clientHeight || 0) - offset
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBrowser) {
|
||||
return
|
||||
}
|
||||
|
||||
handleResize()
|
||||
|
||||
window.addEventListener("resize", handleResize)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize)
|
||||
}
|
||||
}, [isBrowser])
|
||||
|
||||
return (
|
||||
<div className="hidden lg:block" onMouseOver={() => setShowMenu(true)}>
|
||||
<div
|
||||
className={clsx(
|
||||
"fixed top-1/2 right-[20px]",
|
||||
"hidden lg:flex justify-center items-center",
|
||||
"overflow-hidden z-10",
|
||||
showMenu && "lg:hidden",
|
||||
maxHeight < 1000 && "-translate-y-[40%]",
|
||||
maxHeight >= 1000 && "-translate-y-1/2"
|
||||
)}
|
||||
onMouseOver={() => setShowMenu(true)}
|
||||
style={{
|
||||
maxHeight,
|
||||
}}
|
||||
>
|
||||
<TocList items={items} topLevel={true} activeItem={activeItemId} />
|
||||
</div>
|
||||
<TocMenu
|
||||
items={items}
|
||||
activeItem={activeItemId}
|
||||
show={showMenu}
|
||||
setShow={setShowMenu}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -36,7 +36,8 @@ export const TypeList = ({
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"bg-docs-bg-surface shadow-elevation-card-rest rounded my-docs_1",
|
||||
"bg-medusa-bg-subtle rounded my-docs_1",
|
||||
"shadow-elevation-card-rest dark:shadow-elevation-card-rest-dark",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -36,20 +36,14 @@ export * from "./Link"
|
||||
export * from "./Loading"
|
||||
export * from "./Loading/Dots"
|
||||
export * from "./Loading/Spinner"
|
||||
export * from "./MainNav"
|
||||
export * from "./MarkdownContent"
|
||||
export * from "./MDXComponents"
|
||||
export * from "./Menu"
|
||||
export * from "./MermaidDiagram"
|
||||
export * from "./Modal"
|
||||
export * from "./Modal/Header"
|
||||
export * from "./Modal/Footer"
|
||||
export * from "./Navbar"
|
||||
export * from "./Navbar/ColorModeToggle"
|
||||
export * from "./Navbar/IconButton"
|
||||
export * from "./Navbar/Link"
|
||||
export * from "./Navbar/Logo"
|
||||
export * from "./Navbar/MobileMenu"
|
||||
export * from "./Navbar/MobileMenu/Button"
|
||||
export * from "./Navbar/SearchModalOpener"
|
||||
export * from "./Note"
|
||||
export * from "./Notification"
|
||||
export * from "./Notification/Item"
|
||||
|
||||
Reference in New Issue
Block a user