docs: redesign search (#12628)
* docs: redesign search * add shadow in dark mode
This commit is contained in:
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(
|
||||
"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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user