docs: redesign search (#12628)

* docs: redesign search

* add shadow in dark mode
This commit is contained in:
Shahed Nasser
2025-05-28 10:01:04 +03:00
committed by GitHub
parent d155f492be
commit 9b3218885c
13 changed files with 157 additions and 200 deletions

View 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>
)
}

View File

@@ -8,13 +8,16 @@ export const SearchFooter = () => {
className={clsx(
"py-docs_0.75 hidden md:flex items-center justify-end px-docs_1",
"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.5">
<span
className={clsx("text-medusa-fg-subtle", "text-compact-x-small")}
className={clsx(
"text-medusa-fg-subtle",
"text-compact-x-small-plus"
)}
>
Navigation
</span>
@@ -40,7 +43,10 @@ export const SearchFooter = () => {
<div className={clsx("h-docs_0.75 w-px bg-medusa-border-strong")}></div>
<div className="flex items-center gap-docs_0.5">
<span
className={clsx("text-medusa-fg-subtle", "text-compact-x-small")}
className={clsx(
"text-medusa-fg-subtle",
"text-compact-x-small-plus"
)}
>
Open Result
</span>

View File

@@ -1,6 +1,6 @@
"use client"
import React, { Fragment, useEffect, useMemo, useState } from "react"
import React, { useEffect, useMemo, useState } from "react"
import clsx from "clsx"
import {
Configure,
@@ -11,7 +11,7 @@ import {
useInstantSearch,
} from "react-instantsearch"
import { SearchNoResult } from "../NoResults"
import { AlgoliaIndex, useSearch } from "@/providers"
import { useSearch } from "@/providers"
import { Link, SearchHitGroupName } from "@/components"
export type Hierarchy = "lvl0" | "lvl1" | "lvl2" | "lvl3" | "lvl4" | "lvl5"
@@ -42,7 +42,6 @@ export type GroupedHitType = {
export type SearchHitWrapperProps = {
configureProps: ConfigureProps
indices: AlgoliaIndex[]
} & Omit<SearchHitsProps, "indexName" | "setNoResults">
export type IndexResults = {
@@ -51,41 +50,46 @@ export type IndexResults = {
export const SearchHitsWrapper = ({
configureProps,
indices,
...rest
}: SearchHitWrapperProps) => {
const { status } = useInstantSearch()
const [hasNoResults, setHashNoResults] = useState<IndexResults>({
[indices[0].name]: false,
[indices[1].name]: false,
const { selectedIndex, indices } = useSearch()
const [hasNoResults, setHasNoResults] = useState<IndexResults>({
[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) => {
setHashNoResults((prev: IndexResults) => ({
setHasNoResults((prev) => ({
...prev,
[index]: value,
}))
}
const showNoResults = useMemo(() => {
return Object.values(hasNoResults).every((val) => val)
}, [hasNoResults])
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 />}
{indices.map((index, key) => (
// @ts-expect-error React v19 doesn't see this type as a React element
<Index indexName={index.name} key={key}>
{!hasNoResults[index.name] && (
<SearchHitGroupName name={index.title} />
)}
<SearchHits
indexName={index.name}
setNoResults={setNoResults}
{...rest}
/>
<Configure {...configureProps} />
</Index>
{indices.map((index) => (
<div
className={clsx(index.value !== selectedIndex && "hidden")}
key={index.value}
data-index
>
{/* @ts-expect-error React v19 doesn't see this type as a React element */}
<Index indexName={index.value}>
{!hasNoResults[index.value] && (
<SearchHitGroupName name={index.title} />
)}
<SearchHits
indexName={index.value}
setNoResults={setNoResults}
{...rest}
/>
<Configure {...configureProps} />
</Index>
</div>
))}
</div>
)

View File

@@ -1,16 +1,16 @@
"use client"
import React, { useEffect, useMemo, useRef, useState } from "react"
import React, { useEffect, useRef } from "react"
import { InstantSearch, SearchBox } from "react-instantsearch"
import clsx from "clsx"
import { SearchEmptyQueryBoundary } from "./EmptyQueryBoundary"
import { SearchSuggestions, type SearchSuggestionType } from "./Suggestions"
import { AlgoliaProps, useSearch } from "@/providers"
import { checkArraySameElms } from "@/utils"
import { SearchHitsWrapper } from "./Hits"
import { SelectBadge, SpinnerLoading } from "@/components"
import { SpinnerLoading } from "@/components"
import { useSearchNavigation, type OptionType } from "@/hooks"
import { SearchFooter } from "./Footer"
import { SearchFilters } from "./Filters"
export type SearchProps = {
algolia: AlgoliaProps
@@ -25,21 +25,13 @@ export const Search = ({
suggestions,
isLoading = false,
checkInternalPattern,
filterOptions = [],
}: SearchProps) => {
const { isOpen, defaultFilters, searchClient, modalRef } = useSearch()
const [filters, setFilters] = useState<string[]>(defaultFilters)
const { isOpen, searchClient, modalRef } = useSearch()
const searchBoxRef = useRef<HTMLFormElement>(null)
const focusSearchInput = () =>
searchBoxRef.current?.querySelector("input")?.focus()
useEffect(() => {
if (!checkArraySameElms(defaultFilters, filters)) {
setFilters(defaultFilters)
}
}, [defaultFilters])
useEffect(() => {
if (isOpen && searchBoxRef.current) {
focusSearchInput()
@@ -57,14 +49,6 @@ export const Search = ({
}
}, [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({
getInputElm: () =>
searchBoxRef.current?.querySelector("input") as HTMLInputElement,
@@ -76,30 +60,6 @@ export const Search = ({
return (
<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 */}
<InstantSearch
indexName={algolia.mainIndexName}
@@ -143,26 +103,16 @@ export const Search = ({
</div>
<div
className={clsx(
"md:flex-initial",
filterOptions.length > 0 &&
"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)]"
"md:flex-initial flex flex-col",
"h-[calc(100%-75px)] lg:max-h-[calc(100%-100px)] lg:min-h-[calc(100%-100px)]"
)}
>
<SearchFilters />
<SearchEmptyQueryBoundary
fallback={<SearchSuggestions suggestions={suggestions} />}
>
<SearchHitsWrapper
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}
configureProps={{}}
checkInternalPattern={checkInternalPattern}
/>
</SearchEmptyQueryBoundary>

View File

@@ -82,6 +82,7 @@ export const VerticalCodeTabs = ({
{...tabs[selectedTabIndex].code}
noCopy={true}
noReport={true}
noAskAi={true}
forceNoTitle={true}
wrapperClassName="h-full !rounded-docs_DEFAULT"
className={clsx(