-
} />
+
} />
Learn how to build Medusa projects. Explore our guides.
diff --git a/www/apps/resources/app/medusa-workflows-reference/page.mdx b/www/apps/resources/app/medusa-workflows-reference/page.mdx
index 11c0140621..89d89edd45 100644
--- a/www/apps/resources/app/medusa-workflows-reference/page.mdx
+++ b/www/apps/resources/app/medusa-workflows-reference/page.mdx
@@ -20,4 +20,8 @@ Through this reference, you'll learn about the available workflows and steps in
All workflows and steps in this reference are exported by the `@medusajs/medusa/core-flows` package.
-
+
diff --git a/www/packages/docs-ui/package.json b/www/packages/docs-ui/package.json
index 1550855c25..e8e4243d4b 100644
--- a/www/packages/docs-ui/package.json
+++ b/www/packages/docs-ui/package.json
@@ -67,6 +67,7 @@
"algoliasearch": "^5.2.1",
"framer-motion": "^11.11.9",
"mermaid": "^10.9.0",
+ "minisearch": "^7.1.1",
"npm-to-yarn": "^2.1.0",
"prism-react-renderer": "2.4.0",
"react": "rc",
diff --git a/www/packages/docs-ui/src/components/Card/Layout/Default/index.tsx b/www/packages/docs-ui/src/components/Card/Layout/Default/index.tsx
index de31304980..21303adf19 100644
--- a/www/packages/docs-ui/src/components/Card/Layout/Default/index.tsx
+++ b/www/packages/docs-ui/src/components/Card/Layout/Default/index.tsx
@@ -17,9 +17,35 @@ export const CardDefaultLayout = ({
children,
badge,
rightIcon: RightIconComponent,
+ highlightText = [],
}: CardProps) => {
const isExternal = useIsExternalLink({ href })
+ const getHighlightedText = (textToHighlight: string) => {
+ if (!highlightText.length) {
+ return textToHighlight
+ }
+
+ const parts = textToHighlight.split(
+ new RegExp(`(${highlightText.join("|")})`, "gi")
+ )
+ return parts.map((part, index) => {
+ const isHighlighted = highlightText.some((highlight) => {
+ return part.toLowerCase() === highlight.toLowerCase()
+ })
+ return isHighlighted ? (
+
+ {part}
+
+ ) : (
+ part
+ )
+ })
+ }
+
return (
{title && (
- {title}
+ {getHighlightedText(title)}
)}
{text && (
-
{text}
+
+ {getHighlightedText(text)}
+
)}
{children}
diff --git a/www/packages/docs-ui/src/components/Card/index.tsx b/www/packages/docs-ui/src/components/Card/index.tsx
index c710ea177a..102b339555 100644
--- a/www/packages/docs-ui/src/components/Card/index.tsx
+++ b/www/packages/docs-ui/src/components/Card/index.tsx
@@ -23,6 +23,7 @@ export type CardProps = {
iconClassName?: string
children?: React.ReactNode
badge?: BadgeProps
+ highlightText?: string[]
}
export const Card = ({ type = "default", ...props }: CardProps) => {
diff --git a/www/packages/docs-ui/src/components/Icons/Book/index.tsx b/www/packages/docs-ui/src/components/Icons/Book/index.tsx
deleted file mode 100644
index 11ac7b0518..0000000000
--- a/www/packages/docs-ui/src/components/Icons/Book/index.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import React from "react"
-import { IconProps } from "@medusajs/icons/dist/types"
-
-export const BookIcon = (props: IconProps) => {
- return (
-
- )
-}
diff --git a/www/packages/docs-ui/src/components/Icons/index.tsx b/www/packages/docs-ui/src/components/Icons/index.tsx
index 8df127ecc2..d33254ed11 100644
--- a/www/packages/docs-ui/src/components/Icons/index.tsx
+++ b/www/packages/docs-ui/src/components/Icons/index.tsx
@@ -1,5 +1,4 @@
export * from "./AiAssistant"
-export * from "./Book"
export * from "./CalendarRefresh"
export * from "./ChefHat"
export * from "./CircleDottedLine"
diff --git a/www/packages/docs-ui/src/components/Input/Search/index.tsx b/www/packages/docs-ui/src/components/Input/Search/index.tsx
new file mode 100644
index 0000000000..a9a69b45af
--- /dev/null
+++ b/www/packages/docs-ui/src/components/Input/Search/index.tsx
@@ -0,0 +1,64 @@
+"use client"
+
+import { MagnifyingGlass, XMark } from "@medusajs/icons"
+import clsx from "clsx"
+import React from "react"
+import { useKeyboardShortcut } from "../../../hooks"
+import { Kbd } from "../../Kbd"
+
+type SearchInputProps = {
+ onChange: (value: string) => void
+} & Omit
, "onChange">
+
+export const SearchInput = ({
+ value,
+ onChange,
+ className,
+ placeholder = "Search...",
+ ...props
+}: SearchInputProps) => {
+ useKeyboardShortcut({
+ metakey: false,
+ shortcutKeys: ["escape"],
+ action: () => onChange(""),
+ checkEditing: false,
+ preventDefault: true,
+ })
+
+ return (
+
+
+
+ onChange(e.target.value)}
+ {...props}
+ />
+ {value && (
+
+ )}
+
+
+ esc
+ Clear Search
+
+
+ )
+}
diff --git a/www/packages/docs-ui/src/components/Kbd/index.tsx b/www/packages/docs-ui/src/components/Kbd/index.tsx
index 60acc5cece..155f79722d 100644
--- a/www/packages/docs-ui/src/components/Kbd/index.tsx
+++ b/www/packages/docs-ui/src/components/Kbd/index.tsx
@@ -1,9 +1,16 @@
import React from "react"
import clsx from "clsx"
-export type KbdProps = React.ComponentProps<"kbd">
+export type KbdProps = React.ComponentProps<"kbd"> & {
+ variant?: "default" | "small"
+}
-export const Kbd = ({ children, className, ...props }: KbdProps) => {
+export const Kbd = ({
+ children,
+ className,
+ variant = "default",
+ ...props
+}: KbdProps) => {
return (
{
"py-0 px-docs_0.25",
"bg-medusa-bg-field",
"text-medusa-fg-subtle",
- "text-compact-x-small-plus font-base shadow-none",
+ "font-base shadow-none",
+ variant === "small"
+ ? "text-compact-x-small"
+ : "text-compact-x-small-plus",
className
)}
{...props}
diff --git a/www/packages/docs-ui/src/components/MainNav/DesktopMenu/index.tsx b/www/packages/docs-ui/src/components/MainNav/DesktopMenu/index.tsx
index b6e5cfb18a..7b70a47e90 100644
--- a/www/packages/docs-ui/src/components/MainNav/DesktopMenu/index.tsx
+++ b/www/packages/docs-ui/src/components/MainNav/DesktopMenu/index.tsx
@@ -2,13 +2,13 @@
import {
BarsThree,
+ Book,
QuestionMarkCircle,
SidebarLeft,
TimelineVertical,
} from "@medusajs/icons"
import React, { useMemo, useRef, useState } from "react"
import {
- BookIcon,
Button,
getOsShortcut,
Menu,
@@ -41,7 +41,7 @@ export const MainNavDesktopMenu = () => {
},
{
type: "link",
- icon: ,
+ icon: ,
title: "Medusa v1",
link: "https://docs.medusajs.com/v1",
},
diff --git a/www/packages/docs-ui/src/components/index.ts b/www/packages/docs-ui/src/components/index.ts
index 10562fb049..32fbe2e413 100644
--- a/www/packages/docs-ui/src/components/index.ts
+++ b/www/packages/docs-ui/src/components/index.ts
@@ -31,6 +31,7 @@ export * from "./InlineIcon"
export * from "./InlineThemeImage"
export * from "./InlineCode"
export * from "./Input/Text"
+export * from "./Input/Search"
export * from "./Kbd"
export * from "./Label"
export * from "./LearningPath"
diff --git a/www/packages/docs-ui/src/hooks/use-child-docs/index.tsx b/www/packages/docs-ui/src/hooks/use-child-docs/index.tsx
index ca693560ad..dda0b50ae9 100644
--- a/www/packages/docs-ui/src/hooks/use-child-docs/index.tsx
+++ b/www/packages/docs-ui/src/hooks/use-child-docs/index.tsx
@@ -1,21 +1,25 @@
"use client"
-import React, { useCallback, useMemo } from "react"
+import React, { useCallback, useEffect, useMemo, useState } from "react"
import {
Card,
CardList,
+ getLocalSearch,
H2,
H3,
H4,
Hr,
isSidebarItemLink,
+ LocalSearch,
MarkdownContent,
+ SearchInput,
+ useIsBrowser,
useSidebar,
} from "../.."
import { InteractiveSidebarItem, SidebarItem, SidebarItemLink } from "types"
import slugify from "slugify"
import { MDXComponents } from "../.."
-import { ChevronDoubleRight } from "@medusajs/icons"
+import { ChevronDoubleRight, ExclamationCircle } from "@medusajs/icons"
type HeadingComponent = (
props: React.HTMLAttributes
@@ -32,6 +36,11 @@ export type UseChildDocsProps = {
childLevel?: number
itemsPerRow?: number
defaultItemsPerRow?: number
+ search?: {
+ enable: boolean
+ storageKey?: string
+ placeholder?: string
+ }
}
export const useChildDocs = ({
@@ -45,8 +54,18 @@ export const useChildDocs = ({
childLevel = 1,
itemsPerRow,
defaultItemsPerRow,
+ search: {
+ enable: enableSearch = false,
+ storageKey = "child-docs",
+ ...searchProps
+ } = { enable: false },
}: UseChildDocsProps) => {
const { currentItems, activeItem } = useSidebar()
+ const { isBrowser } = useIsBrowser()
+ const [searchQuery, setSearchQuery] = useState("")
+ const [localSearch, setLocalSearch] = useState<
+ LocalSearch | undefined
+ >()
const TitleHeaderComponent = useCallback(
(level: number): HeadingComponent => {
switch (level) {
@@ -92,16 +111,11 @@ export const useChildDocs = ({
}
}
- const filterItems = (items: SidebarItem[]): SidebarItem[] => {
- return items
- .filter(filterCondition)
+ const filterItems = (items: SidebarItem[]): InteractiveSidebarItem[] => {
+ return (items.filter(filterCondition) as InteractiveSidebarItem[])
.map((item) => Object.assign({}, item))
.map((item) => {
- if (
- item.type !== "separator" &&
- item.children &&
- filterType === "hide"
- ) {
+ if (item.children && filterType === "hide") {
item.children = filterItems(item.children)
}
@@ -109,25 +123,6 @@ export const useChildDocs = ({
})
}
- const filteredItems = useMemo(() => {
- const targetItems =
- type === "sidebar"
- ? currentItems
- ? Object.assign({}, currentItems)
- : undefined
- : {
- default: [...(activeItem?.children || [])],
- }
- if (filterType === "all" || !targetItems) {
- return targetItems
- }
-
- return {
- ...targetItems,
- default: filterItems(targetItems.default),
- }
- }, [currentItems, type, activeItem, filterItems])
-
const filterNonInteractiveItems = (
items: SidebarItem[] | undefined
): InteractiveSidebarItem[] => {
@@ -164,11 +159,115 @@ export const useChildDocs = ({
return childrenResult
}
- const getTopLevelElms = (items?: SidebarItem[]) => {
+ const filteredItems = useMemo(() => {
+ let targetItems =
+ type === "sidebar"
+ ? currentItems
+ ? Object.assign({}, currentItems)
+ : undefined
+ : {
+ default: [...(activeItem?.children || [])],
+ }
+ if (filterType !== "all" && targetItems) {
+ targetItems = {
+ ...targetItems,
+ default: filterItems(targetItems.default),
+ }
+ }
+
+ return targetItems
+ ? {
+ ...targetItems,
+ default: filterNonInteractiveItems(targetItems?.default),
+ }
+ : undefined
+ }, [currentItems, type, activeItem, filterType])
+
+ const searchableItems = useMemo(() => {
+ const searchableItems: SidebarItemLink[] = []
+ if (!enableSearch) {
+ return searchableItems
+ }
+ if (onlyTopLevel) {
+ filteredItems?.default?.forEach((item) => {
+ if (isSidebarItemLink(item)) {
+ searchableItems.push(item)
+ } else {
+ const firstChild = item.children?.find((child) =>
+ isSidebarItemLink(child)
+ )
+ if (firstChild) {
+ searchableItems.push(firstChild as SidebarItemLink)
+ }
+ }
+ })
+ } else {
+ filteredItems?.default?.forEach((item) => {
+ const childItems: SidebarItemLink[] =
+ (getChildrenForLevel(item)?.filter((childItem) => {
+ return isSidebarItemLink(childItem)
+ }) as SidebarItemLink[]) || []
+ searchableItems.push(...childItems)
+ })
+ }
+
+ return searchableItems
+ }, [filteredItems, onlyTopLevel, enableSearch])
+
+ useEffect(() => {
+ if (!enableSearch && localSearch) {
+ setLocalSearch(undefined)
+ return
+ }
+ if (!enableSearch || !searchableItems?.length || localSearch) {
+ return
+ }
+
+ setLocalSearch(
+ getLocalSearch({
+ docs: searchableItems,
+ searchableFields: ["title", "description"],
+ options: {
+ storeFields: ["title", "description", "path", "type"],
+ searchOptions: {
+ boost: { title: 2 },
+ prefix: true,
+ fuzzy: 0.2,
+ },
+ idField: "path",
+ },
+ })
+ )
+ }, [searchableItems, enableSearch, localSearch])
+
+ const searchResult = useMemo(() => {
+ return localSearch?.search(searchQuery) || []
+ }, [localSearch, searchQuery])
+
+ useEffect(() => {
+ if (!isBrowser || !enableSearch) {
+ return
+ }
+
+ const storedQuery = localStorage.getItem(`${storageKey}-query`)
+ if (storedQuery) {
+ setSearchQuery(storedQuery)
+ }
+ }, [isBrowser, storageKey, enableSearch])
+
+ useEffect(() => {
+ if (!isBrowser || !enableSearch) {
+ return
+ }
+
+ localStorage.setItem(`${storageKey}-query`, searchQuery)
+ }, [isBrowser, searchQuery, storageKey, enableSearch])
+
+ const getTopLevelElms = (items?: InteractiveSidebarItem[]) => {
return (
{
+ items?.map((childItem) => {
const href = isSidebarItemLink(childItem)
? childItem.path
: childItem.children?.length
@@ -193,11 +292,10 @@ export const useChildDocs = ({
}
const getAllLevelsElms = (
- items?: SidebarItem[],
+ items?: InteractiveSidebarItem[],
headerLevel = titleLevel
) => {
- const filteredItems = filterNonInteractiveItems(items)
- return filteredItems.map((item, key) => {
+ return items?.map((item, key) => {
const itemChildren = getChildrenForLevel(item)
const HeadingComponent = itemChildren?.length
? TitleHeaderComponent(headerLevel)
@@ -243,7 +341,7 @@ export const useChildDocs = ({
defaultItemsPerRow={defaultItemsPerRow}
/>
)}
- {key !== filteredItems.length - 1 && headerLevel === 2 &&
}
+ {key !== items.length - 1 && headerLevel === 2 &&
}
>
)}
{!HeadingComponent && isSidebarItemLink(item) && (
@@ -259,12 +357,64 @@ export const useChildDocs = ({
})
}
- const getElms = (items?: SidebarItem[]) => {
- return onlyTopLevel ? getTopLevelElms(items) : getAllLevelsElms(items)
+ const getSearchResultElms = () => {
+ const Heading = TitleHeaderComponent(titleLevel)
+ return (
+ <>
+ Search Results
+ {searchResult.length > 0 && (
+ ({
+ title: item.title,
+ href: item.path,
+ text: item.description,
+ rightIcon: item.type === "ref" ? ChevronDoubleRight : undefined,
+ highlightText: item.terms,
+ }))}
+ itemsPerRow={itemsPerRow}
+ defaultItemsPerRow={defaultItemsPerRow}
+ className="my-docs_2"
+ />
+ )}
+ {!searchResult.length && (
+
+
+
+ No results found matching your query.
+
+
+ Try searching with another term or clearing the search.
+
+
+ )}
+ >
+ )
+ }
+
+ const getElms = () => {
+ return (
+ <>
+ {enableSearch && (
+
+ )}
+ {searchQuery && getSearchResultElms()}
+ {!searchQuery && (
+ <>
+ {onlyTopLevel
+ ? getTopLevelElms(filteredItems?.default)
+ : getAllLevelsElms(filteredItems?.default)}
+ >
+ )}
+ >
+ )
}
return {
items: filteredItems,
- component: getElms(filteredItems?.default),
+ component: getElms(),
}
}
diff --git a/www/packages/docs-ui/src/utils/get-local-search.ts b/www/packages/docs-ui/src/utils/get-local-search.ts
new file mode 100644
index 0000000000..00c753f524
--- /dev/null
+++ b/www/packages/docs-ui/src/utils/get-local-search.ts
@@ -0,0 +1,32 @@
+import MiniSearch, { Options as MiniSearchOptions } from "minisearch"
+
+type BaseSearchRecord = Record
+
+type GetLocalSearchInput = {
+ docs: T[]
+ searchableFields: string[]
+ options?: Omit
+}
+
+type SearchResult = (T & {
+ terms?: string[]
+})[]
+
+export type LocalSearch =
+ MiniSearch & {
+ search: (query: string) => SearchResult
+ }
+
+export const getLocalSearch = ({
+ docs,
+ searchableFields,
+ options,
+}: GetLocalSearchInput): LocalSearch => {
+ const miniSearch = new MiniSearch({
+ fields: searchableFields,
+ ...options,
+ })
+ miniSearch.addAll(docs)
+
+ return miniSearch as LocalSearch
+}
diff --git a/www/packages/docs-ui/src/utils/index.ts b/www/packages/docs-ui/src/utils/index.ts
index ab26e6101e..54988860ad 100644
--- a/www/packages/docs-ui/src/utils/index.ts
+++ b/www/packages/docs-ui/src/utils/index.ts
@@ -4,6 +4,7 @@ export * from "./check-sidebar-item-visibility"
export * from "./decode-str"
export * from "./dom-utils"
export * from "./get-link-with-base-path"
+export * from "./get-local-search"
export * from "./get-navbar-items"
export * from "./os-browser-utils"
export * from "./get-scrolled-top"
diff --git a/www/yarn.lock b/www/yarn.lock
index a64ba78fe4..83c6850c5b 100644
--- a/www/yarn.lock
+++ b/www/yarn.lock
@@ -8445,6 +8445,7 @@ __metadata:
eslint: ^9.13.0
framer-motion: ^11.11.9
mermaid: ^10.9.0
+ minisearch: ^7.1.1
next: 15.0.4
npm-to-yarn: ^2.1.0
prism-react-renderer: 2.4.0
@@ -13011,6 +13012,13 @@ __metadata:
languageName: node
linkType: hard
+"minisearch@npm:^7.1.1":
+ version: 7.1.1
+ resolution: "minisearch@npm:7.1.1"
+ checksum: a601963ae5fa3b2e884278c92f614187651f2734e248cb564236258cb307cbe6aab2f985962f77939a6255da123d625e38ff6d72fa9c4164ac3e49477fbad9f5
+ languageName: node
+ linkType: hard
+
"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2":
version: 2.1.2
resolution: "minizlib@npm:2.1.2"