docs: redesign search (#12628)
* docs: redesign search * add shadow in dark mode
This commit is contained in:
@@ -1,10 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import {
|
import { usePageLoading, SearchProvider as UiSearchProvider } from "docs-ui"
|
||||||
usePageLoading,
|
|
||||||
SearchProvider as UiSearchProvider,
|
|
||||||
searchFilters,
|
|
||||||
} from "docs-ui"
|
|
||||||
import { config } from "../config"
|
import { config } from "../config"
|
||||||
import basePathUrl from "../utils/base-path-url"
|
import basePathUrl from "../utils/base-path-url"
|
||||||
|
|
||||||
@@ -20,17 +16,18 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
appId: process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || "temp",
|
appId: process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || "temp",
|
||||||
apiKey: process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp",
|
apiKey: process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp",
|
||||||
mainIndexName: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
mainIndexName: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
||||||
indices: [
|
|
||||||
{
|
|
||||||
name: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
|
||||||
title: "Store & Admin API",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
|
||||||
title: "Docs",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}}
|
}}
|
||||||
|
indices={[
|
||||||
|
{
|
||||||
|
value: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
||||||
|
title: "Docs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
||||||
|
title: "Store & Admin API",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
defaultIndex={process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp"}
|
||||||
searchProps={{
|
searchProps={{
|
||||||
isLoading,
|
isLoading,
|
||||||
suggestions: [
|
suggestions: [
|
||||||
@@ -55,7 +52,6 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
checkInternalPattern: new RegExp(
|
checkInternalPattern: new RegExp(
|
||||||
`^${config.baseUrl}${basePathUrl(`/(admin|store)`)}`
|
`^${config.baseUrl}${basePathUrl(`/(admin|store)`)}`
|
||||||
),
|
),
|
||||||
filterOptions: searchFilters,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { SearchProvider as UiSearchProvider, searchFilters } from "docs-ui"
|
import { SearchProvider as UiSearchProvider } from "docs-ui"
|
||||||
import { config } from "../config"
|
import { config } from "../config"
|
||||||
|
|
||||||
type SearchProviderProps = {
|
type SearchProviderProps = {
|
||||||
@@ -15,17 +15,18 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
apiKey: process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp",
|
apiKey: process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp",
|
||||||
mainIndexName:
|
mainIndexName:
|
||||||
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
||||||
indices: [
|
|
||||||
{
|
|
||||||
name: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
|
||||||
title: "Docs",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
|
||||||
title: "Store & Admin API",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}}
|
}}
|
||||||
|
indices={[
|
||||||
|
{
|
||||||
|
value: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
||||||
|
title: "Docs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
||||||
|
title: "Store & Admin API",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
defaultIndex={process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp"}
|
||||||
searchProps={{
|
searchProps={{
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
suggestions: [
|
suggestions: [
|
||||||
@@ -51,7 +52,6 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
checkInternalPattern: new RegExp(
|
checkInternalPattern: new RegExp(
|
||||||
`^${config.baseUrl}/([^(resources)])*`
|
`^${config.baseUrl}/([^(resources)])*`
|
||||||
),
|
),
|
||||||
filterOptions: searchFilters,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { SearchProvider as UiSearchProvider, searchFilters } from "docs-ui"
|
import { SearchProvider as UiSearchProvider } from "docs-ui"
|
||||||
import { config } from "../config"
|
import { config } from "../config"
|
||||||
|
|
||||||
type SearchProviderProps = {
|
type SearchProviderProps = {
|
||||||
@@ -15,17 +15,18 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
apiKey: process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp",
|
apiKey: process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp",
|
||||||
mainIndexName:
|
mainIndexName:
|
||||||
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
||||||
indices: [
|
|
||||||
{
|
|
||||||
name: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
|
||||||
title: "Docs",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
|
||||||
title: "Store & Admin API",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}}
|
}}
|
||||||
|
indices={[
|
||||||
|
{
|
||||||
|
value: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
||||||
|
title: "Docs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
||||||
|
title: "Store & Admin API",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
defaultIndex={process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp"}
|
||||||
searchProps={{
|
searchProps={{
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
suggestions: [
|
suggestions: [
|
||||||
@@ -40,7 +41,6 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
checkInternalPattern: new RegExp(`^${config.baseUrl}/resources/.*`),
|
checkInternalPattern: new RegExp(`^${config.baseUrl}/resources/.*`),
|
||||||
filterOptions: searchFilters,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { SearchProvider as UiSearchProvider, searchFilters } from "docs-ui"
|
import { SearchProvider as UiSearchProvider } from "docs-ui"
|
||||||
import { absoluteUrl } from "../lib/absolute-url"
|
import { absoluteUrl } from "../lib/absolute-url"
|
||||||
|
|
||||||
type SearchProviderProps = {
|
type SearchProviderProps = {
|
||||||
@@ -15,17 +15,18 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
apiKey: process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp",
|
apiKey: process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp",
|
||||||
mainIndexName:
|
mainIndexName:
|
||||||
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
||||||
indices: [
|
|
||||||
{
|
|
||||||
name: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
|
||||||
title: "Docs",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
|
||||||
title: "Store & Admin API",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}}
|
}}
|
||||||
|
indices={[
|
||||||
|
{
|
||||||
|
value: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
||||||
|
title: "Docs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
||||||
|
title: "Store & Admin API",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
defaultIndex={process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp"}
|
||||||
searchProps={{
|
searchProps={{
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
suggestions: [
|
suggestions: [
|
||||||
@@ -35,7 +36,6 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
checkInternalPattern: new RegExp(`^${absoluteUrl()}/ui`),
|
checkInternalPattern: new RegExp(`^${absoluteUrl()}/ui`),
|
||||||
filterOptions: searchFilters,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { SearchProvider as UiSearchProvider, searchFilters } from "docs-ui"
|
import { SearchProvider as UiSearchProvider } from "docs-ui"
|
||||||
import { config } from "../config"
|
import { config } from "../config"
|
||||||
|
|
||||||
type SearchProviderProps = {
|
type SearchProviderProps = {
|
||||||
@@ -15,17 +15,18 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
apiKey: process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp",
|
apiKey: process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp",
|
||||||
mainIndexName:
|
mainIndexName:
|
||||||
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
||||||
indices: [
|
|
||||||
{
|
|
||||||
name: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
|
||||||
title: "Docs",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
|
||||||
title: "Store & Admin API",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}}
|
}}
|
||||||
|
indices={[
|
||||||
|
{
|
||||||
|
value: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
||||||
|
title: "Docs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
||||||
|
title: "Store & Admin API",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
defaultIndex={process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp"}
|
||||||
searchProps={{
|
searchProps={{
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
suggestions: [
|
suggestions: [
|
||||||
@@ -43,7 +44,6 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
checkInternalPattern: new RegExp(`^${config.baseUrl}/user-guide`),
|
checkInternalPattern: new RegExp(`^${config.baseUrl}/user-guide`),
|
||||||
filterOptions: searchFilters,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
31
www/packages/docs-ui/src/components/Search/Filters/index.tsx
Normal file
31
www/packages/docs-ui/src/components/Search/Filters/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import clsx from "clsx"
|
||||||
|
import React from "react"
|
||||||
|
import { useSearch } from "../../../providers"
|
||||||
|
|
||||||
|
export const SearchFilters = () => {
|
||||||
|
const { indices, selectedIndex, setSelectedIndex } = useSearch()
|
||||||
|
return (
|
||||||
|
<div className="pt-docs_0.75 px-docs_0.5 justify-center items-center w-full">
|
||||||
|
<div className="flex flex-wrap bg-medusa-bg-disabled rounded-docs_DEFAULT p-docs_0.125">
|
||||||
|
{indices.map((index) => (
|
||||||
|
<button
|
||||||
|
key={index.value}
|
||||||
|
className={clsx(
|
||||||
|
"text-compact-small-plus flex-1 p-docs_0.25",
|
||||||
|
selectedIndex === index.value && [
|
||||||
|
"rounded-docs_sm text-medusa-fg-base bg-medusa-bg-base",
|
||||||
|
"shadow-elevation-card-rest dark:shadow-elevation-card-rest-dark",
|
||||||
|
],
|
||||||
|
selectedIndex !== index.value && "text-medusa-fg-muted"
|
||||||
|
)}
|
||||||
|
onClick={() => setSelectedIndex(index.value)}
|
||||||
|
>
|
||||||
|
{index.title}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -8,13 +8,16 @@ export const SearchFooter = () => {
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
"py-docs_0.75 hidden md:flex items-center justify-end px-docs_1",
|
"py-docs_0.75 hidden md:flex items-center justify-end px-docs_1",
|
||||||
"border-medusa-border-base border-t",
|
"border-medusa-border-base border-t",
|
||||||
"bg-medusa-bg-field"
|
"bg-medusa-bg-field z-10"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-docs_0.75">
|
<div className="flex items-center gap-docs_0.75">
|
||||||
<div className="flex items-center gap-docs_0.5">
|
<div className="flex items-center gap-docs_0.5">
|
||||||
<span
|
<span
|
||||||
className={clsx("text-medusa-fg-subtle", "text-compact-x-small")}
|
className={clsx(
|
||||||
|
"text-medusa-fg-subtle",
|
||||||
|
"text-compact-x-small-plus"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
Navigation
|
Navigation
|
||||||
</span>
|
</span>
|
||||||
@@ -40,7 +43,10 @@ export const SearchFooter = () => {
|
|||||||
<div className={clsx("h-docs_0.75 w-px bg-medusa-border-strong")}></div>
|
<div className={clsx("h-docs_0.75 w-px bg-medusa-border-strong")}></div>
|
||||||
<div className="flex items-center gap-docs_0.5">
|
<div className="flex items-center gap-docs_0.5">
|
||||||
<span
|
<span
|
||||||
className={clsx("text-medusa-fg-subtle", "text-compact-x-small")}
|
className={clsx(
|
||||||
|
"text-medusa-fg-subtle",
|
||||||
|
"text-compact-x-small-plus"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
Open Result
|
Open Result
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import React, { Fragment, useEffect, useMemo, useState } from "react"
|
import React, { useEffect, useMemo, useState } from "react"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import {
|
import {
|
||||||
Configure,
|
Configure,
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
useInstantSearch,
|
useInstantSearch,
|
||||||
} from "react-instantsearch"
|
} from "react-instantsearch"
|
||||||
import { SearchNoResult } from "../NoResults"
|
import { SearchNoResult } from "../NoResults"
|
||||||
import { AlgoliaIndex, useSearch } from "@/providers"
|
import { useSearch } from "@/providers"
|
||||||
import { Link, SearchHitGroupName } from "@/components"
|
import { Link, SearchHitGroupName } from "@/components"
|
||||||
|
|
||||||
export type Hierarchy = "lvl0" | "lvl1" | "lvl2" | "lvl3" | "lvl4" | "lvl5"
|
export type Hierarchy = "lvl0" | "lvl1" | "lvl2" | "lvl3" | "lvl4" | "lvl5"
|
||||||
@@ -42,7 +42,6 @@ export type GroupedHitType = {
|
|||||||
|
|
||||||
export type SearchHitWrapperProps = {
|
export type SearchHitWrapperProps = {
|
||||||
configureProps: ConfigureProps
|
configureProps: ConfigureProps
|
||||||
indices: AlgoliaIndex[]
|
|
||||||
} & Omit<SearchHitsProps, "indexName" | "setNoResults">
|
} & Omit<SearchHitsProps, "indexName" | "setNoResults">
|
||||||
|
|
||||||
export type IndexResults = {
|
export type IndexResults = {
|
||||||
@@ -51,41 +50,46 @@ export type IndexResults = {
|
|||||||
|
|
||||||
export const SearchHitsWrapper = ({
|
export const SearchHitsWrapper = ({
|
||||||
configureProps,
|
configureProps,
|
||||||
indices,
|
|
||||||
...rest
|
...rest
|
||||||
}: SearchHitWrapperProps) => {
|
}: SearchHitWrapperProps) => {
|
||||||
const { status } = useInstantSearch()
|
const { status } = useInstantSearch()
|
||||||
const [hasNoResults, setHashNoResults] = useState<IndexResults>({
|
const { selectedIndex, indices } = useSearch()
|
||||||
[indices[0].name]: false,
|
const [hasNoResults, setHasNoResults] = useState<IndexResults>({
|
||||||
[indices[1].name]: false,
|
[indices[0].value]: false,
|
||||||
|
[indices[1].value]: false,
|
||||||
})
|
})
|
||||||
const showNoResults = useMemo(() => {
|
|
||||||
return Object.values(hasNoResults).every((value) => value === true)
|
|
||||||
}, [hasNoResults])
|
|
||||||
|
|
||||||
const setNoResults = (index: string, value: boolean) => {
|
const setNoResults = (index: string, value: boolean) => {
|
||||||
setHashNoResults((prev: IndexResults) => ({
|
setHasNoResults((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[index]: value,
|
[index]: value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
const showNoResults = useMemo(() => {
|
||||||
|
return Object.values(hasNoResults).every((val) => val)
|
||||||
|
}, [hasNoResults])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full overflow-auto px-docs_0.5">
|
<div className="overflow-auto px-docs_0.5 flex-1">
|
||||||
{status !== "loading" && showNoResults && <SearchNoResult />}
|
{status !== "loading" && showNoResults && <SearchNoResult />}
|
||||||
{indices.map((index, key) => (
|
{indices.map((index) => (
|
||||||
// @ts-expect-error React v19 doesn't see this type as a React element
|
<div
|
||||||
<Index indexName={index.name} key={key}>
|
className={clsx(index.value !== selectedIndex && "hidden")}
|
||||||
{!hasNoResults[index.name] && (
|
key={index.value}
|
||||||
<SearchHitGroupName name={index.title} />
|
data-index
|
||||||
)}
|
>
|
||||||
<SearchHits
|
{/* @ts-expect-error React v19 doesn't see this type as a React element */}
|
||||||
indexName={index.name}
|
<Index indexName={index.value}>
|
||||||
setNoResults={setNoResults}
|
{!hasNoResults[index.value] && (
|
||||||
{...rest}
|
<SearchHitGroupName name={index.title} />
|
||||||
/>
|
)}
|
||||||
<Configure {...configureProps} />
|
<SearchHits
|
||||||
</Index>
|
indexName={index.value}
|
||||||
|
setNoResults={setNoResults}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
<Configure {...configureProps} />
|
||||||
|
</Index>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import React, { useEffect, useMemo, useRef, useState } from "react"
|
import React, { useEffect, useRef } from "react"
|
||||||
import { InstantSearch, SearchBox } from "react-instantsearch"
|
import { InstantSearch, SearchBox } from "react-instantsearch"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { SearchEmptyQueryBoundary } from "./EmptyQueryBoundary"
|
import { SearchEmptyQueryBoundary } from "./EmptyQueryBoundary"
|
||||||
import { SearchSuggestions, type SearchSuggestionType } from "./Suggestions"
|
import { SearchSuggestions, type SearchSuggestionType } from "./Suggestions"
|
||||||
import { AlgoliaProps, useSearch } from "@/providers"
|
import { AlgoliaProps, useSearch } from "@/providers"
|
||||||
import { checkArraySameElms } from "@/utils"
|
|
||||||
import { SearchHitsWrapper } from "./Hits"
|
import { SearchHitsWrapper } from "./Hits"
|
||||||
import { SelectBadge, SpinnerLoading } from "@/components"
|
import { SpinnerLoading } from "@/components"
|
||||||
import { useSearchNavigation, type OptionType } from "@/hooks"
|
import { useSearchNavigation, type OptionType } from "@/hooks"
|
||||||
import { SearchFooter } from "./Footer"
|
import { SearchFooter } from "./Footer"
|
||||||
|
import { SearchFilters } from "./Filters"
|
||||||
|
|
||||||
export type SearchProps = {
|
export type SearchProps = {
|
||||||
algolia: AlgoliaProps
|
algolia: AlgoliaProps
|
||||||
@@ -25,21 +25,13 @@ export const Search = ({
|
|||||||
suggestions,
|
suggestions,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
checkInternalPattern,
|
checkInternalPattern,
|
||||||
filterOptions = [],
|
|
||||||
}: SearchProps) => {
|
}: SearchProps) => {
|
||||||
const { isOpen, defaultFilters, searchClient, modalRef } = useSearch()
|
const { isOpen, searchClient, modalRef } = useSearch()
|
||||||
const [filters, setFilters] = useState<string[]>(defaultFilters)
|
|
||||||
const searchBoxRef = useRef<HTMLFormElement>(null)
|
const searchBoxRef = useRef<HTMLFormElement>(null)
|
||||||
|
|
||||||
const focusSearchInput = () =>
|
const focusSearchInput = () =>
|
||||||
searchBoxRef.current?.querySelector("input")?.focus()
|
searchBoxRef.current?.querySelector("input")?.focus()
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!checkArraySameElms(defaultFilters, filters)) {
|
|
||||||
setFilters(defaultFilters)
|
|
||||||
}
|
|
||||||
}, [defaultFilters])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen && searchBoxRef.current) {
|
if (isOpen && searchBoxRef.current) {
|
||||||
focusSearchInput()
|
focusSearchInput()
|
||||||
@@ -57,14 +49,6 @@ export const Search = ({
|
|||||||
}
|
}
|
||||||
}, [isOpen])
|
}, [isOpen])
|
||||||
|
|
||||||
const facetFilters = useMemo(() => {
|
|
||||||
const filtersToUse =
|
|
||||||
!filters.length || filters[0] === "all"
|
|
||||||
? filterOptions.map((option) => option.value)
|
|
||||||
: filters
|
|
||||||
return filtersToUse.map((filter) => "_tags:" + filter)
|
|
||||||
}, [filters, defaultFilters])
|
|
||||||
|
|
||||||
useSearchNavigation({
|
useSearchNavigation({
|
||||||
getInputElm: () =>
|
getInputElm: () =>
|
||||||
searchBoxRef.current?.querySelector("input") as HTMLInputElement,
|
searchBoxRef.current?.querySelector("input") as HTMLInputElement,
|
||||||
@@ -76,30 +60,6 @@ export const Search = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
{filterOptions.length > 0 && (
|
|
||||||
<SelectBadge
|
|
||||||
multiple
|
|
||||||
options={filterOptions}
|
|
||||||
value={filters}
|
|
||||||
setSelected={(value) =>
|
|
||||||
setFilters(Array.isArray(value) ? [...value] : [value])
|
|
||||||
}
|
|
||||||
addSelected={(value) => setFilters((prev) => [...prev, value])}
|
|
||||||
removeSelected={(value) =>
|
|
||||||
setFilters((prev) => prev.filter((v) => v !== value))
|
|
||||||
}
|
|
||||||
showClearButton={false}
|
|
||||||
placeholder="Filters"
|
|
||||||
handleAddAll={(isAllSelected: boolean) => {
|
|
||||||
if (isAllSelected) {
|
|
||||||
setFilters(defaultFilters)
|
|
||||||
} else {
|
|
||||||
setFilters(filterOptions.map((option) => option.value))
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="px-docs_1 pt-docs_1 bg-medusa-bg-base z-10"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{/* @ts-expect-error React v19 doesn't see this type as a React element */}
|
{/* @ts-expect-error React v19 doesn't see this type as a React element */}
|
||||||
<InstantSearch
|
<InstantSearch
|
||||||
indexName={algolia.mainIndexName}
|
indexName={algolia.mainIndexName}
|
||||||
@@ -143,26 +103,16 @@ export const Search = ({
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"md:flex-initial",
|
"md:flex-initial flex flex-col",
|
||||||
filterOptions.length > 0 &&
|
"h-[calc(100%-75px)] lg:max-h-[calc(100%-100px)] lg:min-h-[calc(100%-100px)]"
|
||||||
"h-[calc(100%-95px)] lg:max-h-[calc(100%-140px)] lg:min-h-[calc(100%-140px)]",
|
|
||||||
filterOptions.length === 0 &&
|
|
||||||
"h-[calc(100%-75px)] lg:max-h-[calc(100%-100px)] lg:min-h-[calc(100%-100px)]"
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<SearchFilters />
|
||||||
<SearchEmptyQueryBoundary
|
<SearchEmptyQueryBoundary
|
||||||
fallback={<SearchSuggestions suggestions={suggestions} />}
|
fallback={<SearchSuggestions suggestions={suggestions} />}
|
||||||
>
|
>
|
||||||
<SearchHitsWrapper
|
<SearchHitsWrapper
|
||||||
configureProps={{
|
configureProps={{}}
|
||||||
// filters array has to be wrapped
|
|
||||||
// in another array for an OR condition
|
|
||||||
// to be applied between the items.
|
|
||||||
facetFilters: [facetFilters],
|
|
||||||
getRankingInfo: true,
|
|
||||||
hitsPerPage: 3,
|
|
||||||
}}
|
|
||||||
indices={algolia.indices}
|
|
||||||
checkInternalPattern={checkInternalPattern}
|
checkInternalPattern={checkInternalPattern}
|
||||||
/>
|
/>
|
||||||
</SearchEmptyQueryBoundary>
|
</SearchEmptyQueryBoundary>
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export const VerticalCodeTabs = ({
|
|||||||
{...tabs[selectedTabIndex].code}
|
{...tabs[selectedTabIndex].code}
|
||||||
noCopy={true}
|
noCopy={true}
|
||||||
noReport={true}
|
noReport={true}
|
||||||
|
noAskAi={true}
|
||||||
forceNoTitle={true}
|
forceNoTitle={true}
|
||||||
wrapperClassName="h-full !rounded-docs_DEFAULT"
|
wrapperClassName="h-full !rounded-docs_DEFAULT"
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { OptionType } from "@/hooks"
|
|
||||||
import { NavigationItem } from "types"
|
import { NavigationItem } from "types"
|
||||||
|
|
||||||
export const GITHUB_ISSUES_LINK =
|
export const GITHUB_ISSUES_LINK =
|
||||||
@@ -356,31 +355,3 @@ export const navDropdownItems: NavigationItem[] = [
|
|||||||
link: "/user-guide",
|
link: "/user-guide",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const searchFilters: OptionType[] = [
|
|
||||||
{
|
|
||||||
value: "concepts-guides",
|
|
||||||
label: "Concepts & Guides",
|
|
||||||
hitsPerPage: 8,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "references",
|
|
||||||
label: "References",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "admin-v2",
|
|
||||||
label: "Admin API",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "store-v2",
|
|
||||||
label: "Store API",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "user-guide",
|
|
||||||
label: "User Guide",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "troubleshooting",
|
|
||||||
label: "Troubleshooting",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const useSearchNavigation = ({
|
|||||||
if (isInput) {
|
if (isInput) {
|
||||||
// go to the first data-hit item
|
// go to the first data-hit item
|
||||||
const nextItem = modalRef.current?.querySelector(
|
const nextItem = modalRef.current?.querySelector(
|
||||||
"[data-hit]"
|
"[data-index]:not(.hidden) [data-hit]"
|
||||||
) as HTMLElement
|
) as HTMLElement
|
||||||
nextItem?.focus()
|
nextItem?.focus()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
} from "react"
|
} from "react"
|
||||||
import { BadgeProps, Modal, Search, SearchProps } from "@/components"
|
import { BadgeProps, Modal, Search, SearchProps } from "@/components"
|
||||||
import { checkArraySameElms } from "../../utils"
|
|
||||||
import {
|
import {
|
||||||
liteClient as algoliasearch,
|
liteClient as algoliasearch,
|
||||||
LiteClient as SearchClient,
|
LiteClient as SearchClient,
|
||||||
@@ -30,20 +29,21 @@ export type SearchCommand = {
|
|||||||
export type SearchContextType = {
|
export type SearchContextType = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>
|
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
defaultFilters: string[]
|
|
||||||
setDefaultFilters: (value: string[]) => void
|
|
||||||
searchClient: SearchClient
|
searchClient: SearchClient
|
||||||
commands: SearchCommand[]
|
commands: SearchCommand[]
|
||||||
command: SearchCommand | null
|
command: SearchCommand | null
|
||||||
setCommand: React.Dispatch<React.SetStateAction<SearchCommand | null>>
|
setCommand: React.Dispatch<React.SetStateAction<SearchCommand | null>>
|
||||||
setCommands: React.Dispatch<React.SetStateAction<SearchCommand[]>>
|
setCommands: React.Dispatch<React.SetStateAction<SearchCommand[]>>
|
||||||
modalRef: React.MutableRefObject<HTMLDialogElement | null>
|
modalRef: React.RefObject<HTMLDialogElement | null>
|
||||||
|
indices: AlgoliaIndex[]
|
||||||
|
selectedIndex: string
|
||||||
|
setSelectedIndex: (value: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchContext = createContext<SearchContextType | null>(null)
|
const SearchContext = createContext<SearchContextType | null>(null)
|
||||||
|
|
||||||
export type AlgoliaIndex = {
|
export type AlgoliaIndex = {
|
||||||
name: string
|
value: string
|
||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,12 +51,12 @@ export type AlgoliaProps = {
|
|||||||
appId: string
|
appId: string
|
||||||
apiKey: string
|
apiKey: string
|
||||||
mainIndexName: string
|
mainIndexName: string
|
||||||
indices: AlgoliaIndex[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SearchProviderProps = {
|
export type SearchProviderProps = {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
initialDefaultFilters?: string[]
|
indices: AlgoliaIndex[]
|
||||||
|
defaultIndex: string
|
||||||
algolia: AlgoliaProps
|
algolia: AlgoliaProps
|
||||||
searchProps: Omit<SearchProps, "algolia">
|
searchProps: Omit<SearchProps, "algolia">
|
||||||
commands?: SearchCommand[]
|
commands?: SearchCommand[]
|
||||||
@@ -65,16 +65,16 @@ export type SearchProviderProps = {
|
|||||||
|
|
||||||
export const SearchProvider = ({
|
export const SearchProvider = ({
|
||||||
children,
|
children,
|
||||||
initialDefaultFilters = [],
|
defaultIndex: initialDefaultIndex,
|
||||||
searchProps,
|
searchProps,
|
||||||
algolia,
|
algolia,
|
||||||
commands: initialCommands = [],
|
commands: initialCommands = [],
|
||||||
modalClassName,
|
modalClassName,
|
||||||
|
indices,
|
||||||
}: SearchProviderProps) => {
|
}: SearchProviderProps) => {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const [defaultFilters, setDefaultFilters] = useState<string[]>(
|
const [selectedIndex, setSelectedIndex] =
|
||||||
initialDefaultFilters
|
useState<string>(initialDefaultIndex)
|
||||||
)
|
|
||||||
const [commands, setCommands] = useState<SearchCommand[]>(initialCommands)
|
const [commands, setCommands] = useState<SearchCommand[]>(initialCommands)
|
||||||
const [command, setCommand] = useState<SearchCommand | null>(null)
|
const [command, setCommand] = useState<SearchCommand | null>(null)
|
||||||
|
|
||||||
@@ -88,13 +88,10 @@ export const SearchProvider = ({
|
|||||||
}, [algolia.appId, algolia.apiKey])
|
}, [algolia.appId, algolia.apiKey])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (initialDefaultIndex !== selectedIndex) {
|
||||||
initialDefaultFilters.length &&
|
setSelectedIndex(initialDefaultIndex)
|
||||||
!checkArraySameElms(defaultFilters, initialDefaultFilters)
|
|
||||||
) {
|
|
||||||
setDefaultFilters(initialDefaultFilters)
|
|
||||||
}
|
}
|
||||||
}, [initialDefaultFilters])
|
}, [initialDefaultIndex])
|
||||||
|
|
||||||
const componentWrapperRef = useRef(null)
|
const componentWrapperRef = useRef(null)
|
||||||
|
|
||||||
@@ -107,14 +104,15 @@ export const SearchProvider = ({
|
|||||||
value={{
|
value={{
|
||||||
isOpen,
|
isOpen,
|
||||||
setIsOpen,
|
setIsOpen,
|
||||||
defaultFilters,
|
|
||||||
setDefaultFilters,
|
|
||||||
searchClient,
|
searchClient,
|
||||||
commands,
|
commands,
|
||||||
command,
|
command,
|
||||||
setCommand,
|
setCommand,
|
||||||
modalRef,
|
modalRef,
|
||||||
setCommands,
|
setCommands,
|
||||||
|
indices,
|
||||||
|
selectedIndex,
|
||||||
|
setSelectedIndex,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
Reference in New Issue
Block a user