docs: redesign search + re-introduce ai assistant in v2 docs (#8678)

* docs: redesign search + re-introduce ai assistant in v2

* change version in ui

* show icon in case of error

* fixes based on feedback
This commit is contained in:
Shahed Nasser
2024-08-23 10:42:37 +03:00
committed by GitHub
parent c63a08fb03
commit 320b01f45d
23 changed files with 553 additions and 347 deletions
+25 -2
View File
@@ -4,6 +4,8 @@ import {
usePageLoading, usePageLoading,
SearchProvider as UiSearchProvider, SearchProvider as UiSearchProvider,
searchFiltersV2, searchFiltersV2,
AiAssistantIcon,
AiAssistantProvider,
} from "docs-ui" } 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"
@@ -33,15 +35,14 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
items: [ items: [
"Install Medusa with create-medusa-app", "Install Medusa with create-medusa-app",
"What is an API route?", "What is an API route?",
"What is a Module?",
"What is a Workflow?", "What is a Workflow?",
], ],
}, },
{ {
title: "Developing with Medusa", title: "Developing with Medusa",
items: [ items: [
"How to create a Module",
"How to create an API route", "How to create an API route",
"How to create a module",
"How to create a data model", "How to create a data model",
"How to create an admin widget", "How to create an admin widget",
], ],
@@ -52,6 +53,28 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
), ),
filterOptions: searchFiltersV2, filterOptions: searchFiltersV2,
}} }}
commands={[
{
name: "ai-assistant",
icon: <AiAssistantIcon />,
component: (
<AiAssistantProvider
apiUrl={process.env.NEXT_PUBLIC_AI_ASSISTANT_URL || "temp"}
websiteId={process.env.NEXT_PUBLIC_AI_WEBSITE_ID || "temp"}
recaptchaSiteKey={
process.env.NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY ||
"temp"
}
/>
),
title: "AI Assistant",
badge: {
variant: "blue",
badgeType: "shaded",
children: "Beta",
},
},
]}
> >
{children} {children}
</UiSearchProvider> </UiSearchProvider>
+28 -1
View File
@@ -1,6 +1,11 @@
"use client" "use client"
import { SearchProvider as UiSearchProvider, searchFiltersV2 } from "docs-ui" import {
AiAssistantIcon,
AiAssistantProvider,
SearchProvider as UiSearchProvider,
searchFiltersV2,
} from "docs-ui"
import { config } from "../config" import { config } from "../config"
type SearchProviderProps = { type SearchProviderProps = {
@@ -47,6 +52,28 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
), ),
filterOptions: searchFiltersV2, filterOptions: searchFiltersV2,
}} }}
commands={[
{
name: "ai-assistant",
icon: <AiAssistantIcon />,
component: (
<AiAssistantProvider
apiUrl={process.env.NEXT_PUBLIC_AI_ASSISTANT_URL || "temp"}
websiteId={process.env.NEXT_PUBLIC_AI_WEBSITE_ID || "temp"}
recaptchaSiteKey={
process.env.NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY ||
"temp"
}
/>
),
title: "AI Assistant",
badge: {
variant: "blue",
badgeType: "shaded",
children: "Beta",
},
},
]}
initialDefaultFilters={["book"]} initialDefaultFilters={["book"]}
> >
{children} {children}
+30 -4
View File
@@ -1,6 +1,11 @@
"use client" "use client"
import { SearchProvider as UiSearchProvider, searchFiltersV2 } from "docs-ui" import {
AiAssistantIcon,
AiAssistantProvider,
SearchProvider as UiSearchProvider,
searchFiltersV2,
} from "docs-ui"
import { config } from "../config" import { config } from "../config"
type SearchProviderProps = { type SearchProviderProps = {
@@ -28,15 +33,36 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
items: [ items: [
"Medusa Configurations", "Medusa Configurations",
"Commerce Modules", "Commerce Modules",
"Medusa JS Reference", "Medusa Workflows Reference",
"Medusa React Reference", "Storefront Development",
"Workflows API Reference",
], ],
}, },
], ],
checkInternalPattern: new RegExp(`^${config.baseUrl}/v2/resources/.*`), checkInternalPattern: new RegExp(`^${config.baseUrl}/v2/resources/.*`),
filterOptions: searchFiltersV2, filterOptions: searchFiltersV2,
}} }}
commands={[
{
name: "ai-assistant",
icon: <AiAssistantIcon />,
component: (
<AiAssistantProvider
apiUrl={process.env.NEXT_PUBLIC_AI_ASSISTANT_URL || "temp"}
websiteId={process.env.NEXT_PUBLIC_AI_WEBSITE_ID || "temp"}
recaptchaSiteKey={
process.env.NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY ||
"temp"
}
/>
),
title: "AI Assistant",
badge: {
variant: "blue",
badgeType: "shaded",
children: "Beta",
},
},
]}
initialDefaultFilters={["resources"]} initialDefaultFilters={["resources"]}
> >
{children} {children}
+1
View File
@@ -49,6 +49,7 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
process.env.NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY || process.env.NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY ||
"temp" "temp"
} }
version="v1"
/> />
), ),
title: "AI Assistant", title: "AI Assistant",
@@ -1,13 +1,8 @@
import React, { useState } from "react" import React, { useState } from "react"
import { ThreadType } from "../.." import { ThreadType } from "../.."
import clsx from "clsx" import clsx from "clsx"
import { import { Button, type ButtonProps } from "@/components"
Button, import { Check, SquareTwoStackMini, ThumbDown, ThumbUp } from "@medusajs/icons"
type ButtonProps,
ThumbDownIcon,
ThumbUpIcon,
} from "@/components"
import { Check, SquareTwoStack } from "@medusajs/icons"
import { useCopy } from "@/hooks" import { useCopy } from "@/hooks"
import { AiAssistantFeedbackType, useAiAssistant } from "@/providers" import { AiAssistantFeedbackType, useAiAssistant } from "@/providers"
@@ -43,21 +38,17 @@ export const AiAssistantThreadItemActions = ({
return ( return (
<div <div
className={clsx( className={clsx("hidden md:flex gap-docs_0.25", "text-medusa-fg-muted")}
"hidden md:flex gap-docs_0.25",
"text-medusa-fg-muted",
"sticky top-docs_1"
)}
> >
<ActionButton onClick={handleCopy}> <ActionButton onClick={handleCopy}>
{isCopied ? <Check /> : <SquareTwoStack />} {isCopied ? <Check /> : <SquareTwoStackMini />}
</ActionButton> </ActionButton>
{(feedback === null || feedback === "upvote") && ( {(feedback === null || feedback === "upvote") && (
<ActionButton <ActionButton
onClick={async () => handleFeedback("upvote", item.question_id)} onClick={async () => handleFeedback("upvote", item.question_id)}
className={clsx(feedback === "upvote" && "!text-medusa-fg-subtle")} className={clsx(feedback === "upvote" && "!text-medusa-fg-subtle")}
> >
<ThumbUpIcon /> <ThumbUp />
</ActionButton> </ActionButton>
)} )}
{(feedback === null || feedback === "downvote") && ( {(feedback === null || feedback === "downvote") && (
@@ -65,7 +56,7 @@ export const AiAssistantThreadItemActions = ({
onClick={async () => handleFeedback("downvote", item.question_id)} onClick={async () => handleFeedback("downvote", item.question_id)}
className={clsx(feedback === "downvote" && "!text-medusa-fg-subtle")} className={clsx(feedback === "downvote" && "!text-medusa-fg-subtle")}
> >
<ThumbDownIcon /> <ThumbDown />
</ActionButton> </ActionButton>
)} )}
</div> </div>
@@ -79,7 +70,7 @@ const ActionButton = ({ children, className, ...props }: ButtonProps) => {
className={clsx( className={clsx(
"text-medusa-fg-muted hover:text-medusa-fg-subtle", "text-medusa-fg-muted hover:text-medusa-fg-subtle",
"hover:bg-medusa-bg-subtle-hover", "hover:bg-medusa-bg-subtle-hover",
"p-docs_0.125 rounded-docs_sm", "!p-[4.5px] rounded-docs_sm",
className className
)} )}
{...props} {...props}
@@ -1,8 +1,7 @@
import clsx from "clsx" import clsx from "clsx"
import React from "react" import React from "react"
import { ThreadType } from ".." import { ThreadType } from ".."
import { DotsLoading, MarkdownContent, QuestionMarkIcon } from "@/components" import { AiAssistantIcon, DotsLoading, MarkdownContent } from "@/components"
import { ExclamationCircle, Sparkles } from "@medusajs/icons"
import { AiAssistantThreadItemActions } from "./Actions" import { AiAssistantThreadItemActions } from "./Actions"
export type AiAssistantThreadItemProps = { export type AiAssistantThreadItemProps = {
@@ -13,39 +12,37 @@ export const AiAssistantThreadItem = ({ item }: AiAssistantThreadItemProps) => {
return ( return (
<div <div
className={clsx( className={clsx(
"p-docs_1 flex justify-start gap-docs_1 items-start", "p-docs_0.5 flex gap-docs_0.75 items-start",
"text-medusa-fg-subtle border-solid border-0 border-b", item.type === "question" && "justify-end",
"border-medusa-border-base text-medium relative", item.type === "answer" && "!pr-[20px]"
item.type === "question" && "bg-medusa-bg-base",
item.type === "answer" && "bg-medusa-bg-subtle"
)} )}
> >
<span {item.type !== "question" && <AiAssistantIcon />}
<div
className={clsx( className={clsx(
"border border-solid border-medusa-border-base", "txt-small text-medusa-fg-subtle",
"rounded-docs_sm p-docs_0.125 bg-medusa-bg-component", item.type === "question" && [
"text-medusa-fg-muted flex" "rounded-docs_xl bg-medusa-tag-neutral-bg",
"px-docs_0.75 py-docs_0.5",
],
item.type !== "question" && "flex-1",
item.type === "answer" && "text-pretty"
)} )}
> >
{item.type === "question" && <QuestionMarkIcon />}
{item.type === "answer" && <Sparkles />}
{item.type === "error" && <ExclamationCircle />}
</span>
<div className="md:max-w-[calc(100%-134px)] md:w-[calc(100%-134px)]">
{item.type === "question" && <>{item.content}</>} {item.type === "question" && <>{item.content}</>}
{item.type === "answer" && ( {item.type === "answer" && (
<> <div className="flex flex-col gap-docs_0.75">
{!item.question_id && item.content.length === 0 && <DotsLoading />} {!item.question_id && item.content.length === 0 && <DotsLoading />}
<MarkdownContent>{item.content}</MarkdownContent> <MarkdownContent className="[&>*:last-child]:mb-0">
</> {item.content}
</MarkdownContent>
{item.question_id && <AiAssistantThreadItemActions item={item} />}
</div>
)} )}
{item.type === "error" && ( {item.type === "error" && (
<span className="text-medusa-fg-error">{item.content}</span> <span className="text-medusa-fg-error">{item.content}</span>
)} )}
</div> </div>
{item.type === "answer" && item.question_id && (
<AiAssistantThreadItemActions item={item} />
)}
</div> </div>
) )
} }
@@ -11,10 +11,9 @@ import {
SearchHitGroupName, SearchHitGroupName,
Tooltip, Tooltip,
Link, Link,
AiAssistantIcon,
} from "@/components" } from "@/components"
import { useAiAssistant, useSearch } from "@/providers" import { useAiAssistant, useSearch } from "@/providers"
import { ArrowUturnLeft, XMarkMini } from "@medusajs/icons" import { ArrowUturnLeft } from "@medusajs/icons"
import clsx from "clsx" import clsx from "clsx"
import { useSearchNavigation } from "@/hooks" import { useSearchNavigation } from "@/hooks"
import { AiAssistantThreadItem } from "./ThreadItem" import { AiAssistantThreadItem } from "./ThreadItem"
@@ -77,26 +76,50 @@ export const AiAssistant = () => {
const [answer, setAnswer] = useState("") const [answer, setAnswer] = useState("")
const [identifiers, setIdentifiers] = useState<IdentifierType | null>(null) const [identifiers, setIdentifiers] = useState<IdentifierType | null>(null)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const { getAnswer } = useAiAssistant() const { getAnswer, version } = useAiAssistant()
const { setCommand } = useSearch() const { setCommand } = useSearch()
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement>(null)
const contentRef = useRef<HTMLDivElement>(null) const contentRef = useRef<HTMLDivElement>(null)
const suggestions: SearchSuggestionType[] = [ const suggestions: SearchSuggestionType[] = useMemo(() => {
{ return version === "v2"
title: "FAQ", ? [
items: [ {
"What is Medusa?", title: "FAQ",
"How can I create an ecommerce store with Medusa?", items: [
"How can I build a marketplace with Medusa?", "What is Medusa?",
"How can I build subscription-based purchases with Medusa?", "How can I create a module?",
"How can I build digital products with Medusa?", "How can I create a data model?",
"What can I build with Medusa?", "How do I create a workflow?",
"What is Medusa Admin?", "How can I extend a data model in the Product Module?",
"How do I configure the database in Medusa?", ],
], },
}, {
] title: "Recipes",
items: [
"How do I build a marketplace with Medusa?",
"How do I build digital products with Medusa?",
"How do I build subscription-based purchases with Medusa?",
"What other recipes are available in the Medusa documentation?",
],
},
]
: [
{
title: "FAQ",
items: [
"What is Medusa?",
"How can I create an ecommerce store with Medusa?",
"How can I build a marketplace with Medusa?",
"How can I build subscription-based purchases with Medusa?",
"How can I build digital products with Medusa?",
"What can I build with Medusa?",
"What is Medusa Admin?",
"How do I configure the database in Medusa?",
],
},
]
}, [version])
const handleSubmit = (selectedQuestion?: string) => { const handleSubmit = (selectedQuestion?: string) => {
if (!selectedQuestion?.length && !question.length) { if (!selectedQuestion?.length && !question.length) {
@@ -292,6 +315,22 @@ export const AiAssistant = () => {
return ( return (
<div className="h-full"> <div className="h-full">
<div className={clsx("px-docs_1 pt-docs_1")}>
<Tooltip
tooltipChildren={
<>
This site is protected by reCAPTCHA and the{" "}
<Link href="https://policies.google.com/privacy">
Google Privacy Policy
</Link>{" "}
and <Link href="https://policies.google.com/terms">ToS</Link>{" "}
apply
</>
}
>
<Badge variant="neutral">AI Assistant</Badge>
</Tooltip>
</div>
<div <div
className={clsx( className={clsx(
"flex gap-docs_1 px-docs_1 py-docs_0.75", "flex gap-docs_1 px-docs_1 py-docs_0.75",
@@ -302,7 +341,7 @@ export const AiAssistant = () => {
<Button <Button
variant="transparent" variant="transparent"
onClick={() => setCommand(null)} onClick={() => setCommand(null)}
className="text-medusa-fg-subtle p-[5px]" className="text-medusa-fg-muted p-[6.5px]"
> >
<ArrowUturnLeft /> <ArrowUturnLeft />
</Button> </Button>
@@ -311,7 +350,7 @@ export const AiAssistant = () => {
onChange={(e) => setQuestion(e.target.value)} onChange={(e) => setQuestion(e.target.value)}
className={clsx( className={clsx(
"bg-transparent border-0 focus:outline-none hover:!bg-transparent", "bg-transparent border-0 focus:outline-none hover:!bg-transparent",
"shadow-none flex-1 text-medusa-fg-base", "!shadow-none flex-1 text-medusa-fg-base",
"disabled:!bg-transparent disabled:cursor-not-allowed" "disabled:!bg-transparent disabled:cursor-not-allowed"
)} )}
placeholder="Ask me a question about Medusa..." placeholder="Ask me a question about Medusa..."
@@ -319,23 +358,22 @@ export const AiAssistant = () => {
passedRef={inputRef} passedRef={inputRef}
disabled={loading} disabled={loading}
/> />
<Button <span
variant="transparent"
onClick={() => { onClick={() => {
setQuestion("") setQuestion("")
inputRef.current?.focus() inputRef.current?.focus()
}} }}
className={clsx( className={clsx(
"text-medusa-fg-subtle p-[5px]", "text-medusa-fg-muted hover:text-medusa-fg-subtle",
"absolute top-docs_0.75 right-docs_1", "absolute top-docs_0.75 right-docs_1",
"hover:bg-medusa-bg-base-hover rounded-docs_sm", "cursor-pointer",
question.length === 0 && "hidden" question.length === 0 && "hidden"
)} )}
> >
<XMarkMini /> Clear
</Button> </span>
</div> </div>
<div className="h-[calc(100%-120px)] md:h-[calc(100%-114px)] lg:max-h-[calc(100%-114px)] lg:min-h-[calc(100%-114px)] overflow-auto"> <div className="h-[calc(100%-95px)] lg:max-h-[calc(100%-140px)] lg:min-h-[calc(100%-140px)] overflow-auto">
<div ref={contentRef}> <div ref={contentRef}>
{!thread.length && ( {!thread.length && (
<div className="mx-docs_0.5"> <div className="mx-docs_0.5">
@@ -372,35 +410,12 @@ export const AiAssistant = () => {
</div> </div>
<div <div
className={clsx( className={clsx(
"py-docs_0.75 flex items-center justify-between px-docs_1", "py-docs_0.75 hidden md:flex items-center justify-end px-docs_1",
"border-0 border-solid",
"border-medusa-border-base border-t", "border-medusa-border-base border-t",
"bg-medusa-bg-base h-[57px]" "bg-medusa-bg-field-component"
)} )}
> >
<Tooltip <div className="flex items-center gap-docs_0.75">
tooltipChildren={
<>
This site is protected by reCAPTCHA and the{" "}
<Link href="https://policies.google.com/privacy">
Google Privacy Policy
</Link>{" "}
and <Link href="https://policies.google.com/terms">ToS</Link>{" "}
apply
</>
}
>
<div
className={clsx(
"flex items-center gap-docs_0.75 text-compact-small-plus"
)}
>
<AiAssistantIcon />
<span className="text-medusa-fg-subtle">Medusa AI Assistant</span>
<Badge variant="purple">Beta</Badge>
</div>
</Tooltip>
<div className="hidden items-center gap-docs_1 md:flex">
<div className="flex items-center gap-docs_0.5"> <div className="flex items-center gap-docs_0.5">
{thread.length === 0 && ( {thread.length === 0 && (
<> <>
@@ -412,9 +427,23 @@ export const AiAssistant = () => {
> >
Navigate FAQ Navigate FAQ
</span> </span>
<span className="gap-docs_0.25 flex"> <span className="gap-[5px] flex">
<Kbd></Kbd> <Kbd
<Kbd></Kbd> className={clsx(
"!bg-medusa-bg-field-component !border-medusa-border-strong",
"!text-medusa-fg-subtle h-[18px] w-[18px] p-0"
)}
>
</Kbd>
<Kbd
className={clsx(
"!bg-medusa-bg-field-component !border-medusa-border-strong",
"!text-medusa-fg-subtle h-[18px] w-[18px] p-0"
)}
>
</Kbd>
</span> </span>
</> </>
)} )}
@@ -426,13 +455,23 @@ export const AiAssistant = () => {
</span> </span>
)} )}
</div> </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")}
> >
Ask Question Ask Question
</span> </span>
<Kbd></Kbd> <Kbd
className={clsx(
"!bg-medusa-bg-field-component !border-medusa-border-strong",
"!text-medusa-fg-subtle h-[18px] w-[18px] p-0"
)}
>
</Kbd>
</div> </div>
</div> </div>
</div> </div>
@@ -11,27 +11,38 @@ export const AiAssistantIcon = (props: IconProps) => {
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
{...props} {...props}
> >
<g filter="url(#filter0_diii_7480_20594)"> <g filter="url(#filter0_diii_10448_46450)">
<circle <circle
cx="9.5" cx="9.50002"
cy="9.00002" cy="8.99989"
r="7.11" r="7.11"
fill="url(#paint0_linear_7480_20594)" fill="url(#paint0_linear_10448_46450)"
/> />
<circle <circle
cx="9.5" cx="9.50002"
cy="9.00002" cy="8.99989"
r="7.11" r="7.11"
fill="url(#paint1_linear_7480_20594)" fill="url(#paint1_linear_10448_46450)"
/> />
</g> </g>
<g filter="url(#filter1_f_7480_20594)"> <g
<circle cx="9.5" cy="6.5" r="4" fill="url(#paint2_linear_7480_20594)" /> style={{
mixBlendMode: "plus-lighter",
}}
opacity="0.8"
filter="url(#filter1_f_10448_46450)"
>
<circle
cx="9.5"
cy="6.5"
r="4"
fill="url(#paint2_linear_10448_46450)"
/>
<circle <circle
cx="9.5" cx="9.5"
cy="6.5" cy="6.5"
r="3.9" r="3.9"
stroke="url(#paint3_linear_7480_20594)" stroke="url(#paint3_linear_10448_46450)"
strokeWidth="0.2" strokeWidth="0.2"
/> />
</g> </g>
@@ -39,14 +50,15 @@ export const AiAssistantIcon = (props: IconProps) => {
style={{ style={{
mixBlendMode: "plus-lighter", mixBlendMode: "plus-lighter",
}} }}
filter="url(#filter2_f_7480_20594)" opacity="0.8"
filter="url(#filter2_f_10448_46450)"
> >
<circle cx="12" cy="10" r="4" fill="url(#paint4_linear_7480_20594)" /> <circle cx="12" cy="10" r="4" fill="url(#paint4_linear_10448_46450)" />
<circle <circle
cx="12" cx="12"
cy="10" cy="10"
r="3.9" r="3.9"
stroke="url(#paint5_linear_7480_20594)" stroke="url(#paint5_linear_10448_46450)"
strokeWidth="0.2" strokeWidth="0.2"
/> />
</g> </g>
@@ -54,21 +66,22 @@ export const AiAssistantIcon = (props: IconProps) => {
style={{ style={{
mixBlendMode: "plus-lighter", mixBlendMode: "plus-lighter",
}} }}
filter="url(#filter3_f_7480_20594)" opacity="0.8"
filter="url(#filter3_f_10448_46450)"
> >
<circle <circle
cx="4" cx="4"
cy="4" cy="4"
r="4" r="4"
transform="matrix(-1 0 0 1 11 4)" transform="matrix(-1 0 0 1 11 4)"
fill="url(#paint6_linear_7480_20594)" fill="url(#paint6_linear_10448_46450)"
/> />
<circle <circle
cx="4" cx="4"
cy="4" cy="4"
r="3.9" r="3.9"
transform="matrix(-1 0 0 1 11 4)" transform="matrix(-1 0 0 1 11 4)"
stroke="url(#paint7_linear_7480_20594)" stroke="url(#paint7_linear_10448_46450)"
strokeWidth="0.2" strokeWidth="0.2"
/> />
</g> </g>
@@ -76,46 +89,53 @@ export const AiAssistantIcon = (props: IconProps) => {
style={{ style={{
mixBlendMode: "plus-lighter", mixBlendMode: "plus-lighter",
}} }}
filter="url(#filter4_f_7480_20594)" opacity="0.8"
filter="url(#filter4_f_10448_46450)"
> >
<circle <circle
cx="4" cx="4"
cy="4" cy="4"
r="4" r="4"
transform="matrix(0 -1 -1 0 13.5 13)" transform="matrix(0 -1 -1 0 13.5 13)"
fill="url(#paint8_linear_7480_20594)" fill="url(#paint8_linear_10448_46450)"
/> />
<circle <circle
cx="4" cx="4"
cy="4" cy="4"
r="3.9" r="3.9"
transform="matrix(0 -1 -1 0 13.5 13)" transform="matrix(0 -1 -1 0 13.5 13)"
stroke="url(#paint9_linear_7480_20594)" stroke="url(#paint9_linear_10448_46450)"
strokeWidth="0.2" strokeWidth="0.2"
/> />
</g> </g>
<g filter="url(#filter5_f_7480_20594)"> <g
style={{
mixBlendMode: "plus-lighter",
}}
opacity="0.8"
filter="url(#filter5_f_10448_46450)"
>
<circle <circle
cx="4" cx="4"
cy="4" cy="4"
r="4" r="4"
transform="matrix(-1 0 0 1 13.5 7.5)" transform="matrix(-1 0 0 1 13.5 7.5)"
fill="url(#paint10_linear_7480_20594)" fill="url(#paint10_linear_10448_46450)"
/> />
<circle <circle
cx="4" cx="4"
cy="4" cy="4"
r="3.9" r="3.9"
transform="matrix(-1 0 0 1 13.5 7.5)" transform="matrix(-1 0 0 1 13.5 7.5)"
stroke="url(#paint11_linear_7480_20594)" stroke="url(#paint11_linear_10448_46450)"
strokeWidth="0.2" strokeWidth="0.2"
/> />
</g> </g>
<defs> <defs>
<filter <filter
id="filter0_diii_7480_20594" id="filter0_diii_10448_46450"
x="0.389999" x="0.390015"
y="0.890015" y="0.889893"
width="18.22" width="18.22"
height="18.22" height="18.22"
filterUnits="userSpaceOnUse" filterUnits="userSpaceOnUse"
@@ -138,12 +158,12 @@ export const AiAssistantIcon = (props: IconProps) => {
<feBlend <feBlend
mode="normal" mode="normal"
in2="BackgroundImageFix" in2="BackgroundImageFix"
result="effect1_dropShadow_7480_20594" result="effect1_dropShadow_10448_46450"
/> />
<feBlend <feBlend
mode="normal" mode="normal"
in="SourceGraphic" in="SourceGraphic"
in2="effect1_dropShadow_7480_20594" in2="effect1_dropShadow_10448_46450"
result="shape" result="shape"
/> />
<feColorMatrix <feColorMatrix
@@ -161,7 +181,7 @@ export const AiAssistantIcon = (props: IconProps) => {
<feBlend <feBlend
mode="normal" mode="normal"
in2="shape" in2="shape"
result="effect2_innerShadow_7480_20594" result="effect2_innerShadow_10448_46450"
/> />
<feColorMatrix <feColorMatrix
in="SourceAlpha" in="SourceAlpha"
@@ -173,12 +193,12 @@ export const AiAssistantIcon = (props: IconProps) => {
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /> <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix <feColorMatrix
type="matrix" type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.4 0"
/> />
<feBlend <feBlend
mode="normal" mode="normal"
in2="effect2_innerShadow_7480_20594" in2="effect2_innerShadow_10448_46450"
result="effect3_innerShadow_7480_20594" result="effect3_innerShadow_10448_46450"
/> />
<feColorMatrix <feColorMatrix
in="SourceAlpha" in="SourceAlpha"
@@ -195,12 +215,12 @@ export const AiAssistantIcon = (props: IconProps) => {
/> />
<feBlend <feBlend
mode="normal" mode="normal"
in2="effect3_innerShadow_7480_20594" in2="effect3_innerShadow_10448_46450"
result="effect4_innerShadow_7480_20594" result="effect4_innerShadow_10448_46450"
/> />
</filter> </filter>
<filter <filter
id="filter1_f_7480_20594" id="filter1_f_10448_46450"
x="5" x="5"
y="2" y="2"
width="9" width="9"
@@ -217,11 +237,11 @@ export const AiAssistantIcon = (props: IconProps) => {
/> />
<feGaussianBlur <feGaussianBlur
stdDeviation="0.25" stdDeviation="0.25"
result="effect1_foregroundBlur_7480_20594" result="effect1_foregroundBlur_10448_46450"
/> />
</filter> </filter>
<filter <filter
id="filter2_f_7480_20594" id="filter2_f_10448_46450"
x="7.5" x="7.5"
y="5.5" y="5.5"
width="9" width="9"
@@ -238,11 +258,11 @@ export const AiAssistantIcon = (props: IconProps) => {
/> />
<feGaussianBlur <feGaussianBlur
stdDeviation="0.25" stdDeviation="0.25"
result="effect1_foregroundBlur_7480_20594" result="effect1_foregroundBlur_10448_46450"
/> />
</filter> </filter>
<filter <filter
id="filter3_f_7480_20594" id="filter3_f_10448_46450"
x="2.5" x="2.5"
y="3.5" y="3.5"
width="9" width="9"
@@ -259,11 +279,11 @@ export const AiAssistantIcon = (props: IconProps) => {
/> />
<feGaussianBlur <feGaussianBlur
stdDeviation="0.25" stdDeviation="0.25"
result="effect1_foregroundBlur_7480_20594" result="effect1_foregroundBlur_10448_46450"
/> />
</filter> </filter>
<filter <filter
id="filter4_f_7480_20594" id="filter4_f_10448_46450"
x="5" x="5"
y="4.5" y="4.5"
width="9" width="9"
@@ -280,11 +300,11 @@ export const AiAssistantIcon = (props: IconProps) => {
/> />
<feGaussianBlur <feGaussianBlur
stdDeviation="0.25" stdDeviation="0.25"
result="effect1_foregroundBlur_7480_20594" result="effect1_foregroundBlur_10448_46450"
/> />
</filter> </filter>
<filter <filter
id="filter5_f_7480_20594" id="filter5_f_10448_46450"
x="5" x="5"
y="7" y="7"
width="9" width="9"
@@ -301,44 +321,44 @@ export const AiAssistantIcon = (props: IconProps) => {
/> />
<feGaussianBlur <feGaussianBlur
stdDeviation="0.25" stdDeviation="0.25"
result="effect1_foregroundBlur_7480_20594" result="effect1_foregroundBlur_10448_46450"
/> />
</filter> </filter>
<linearGradient <linearGradient
id="paint0_linear_7480_20594" id="paint0_linear_10448_46450"
x1="9.5" x1="9.50001"
y1="1.89002" y1="1.88989"
x2="9.5" x2="9.50001"
y2="16.11" y2="16.1099"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop offset="0.2" stopColor="#8952FD" /> <stop offset="0.2" stopColor="#C686FF" />
<stop offset="0.8" stopColor="#48EAEB" /> <stop offset="0.8" stopColor="#8D99FF" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint1_linear_7480_20594" id="paint1_linear_10448_46450"
x1="5.11519" x1="5.1152"
y1="8.96784" y1="8.96772"
x2="13.4627" x2="13.4627"
y2="12.0584" y2="12.0583"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="#5B53FF" stopOpacity="0.8" /> <stop stopColor="#FF6778" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.1" /> <stop offset="1" stopColor="white" stopOpacity="0.1" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint2_linear_7480_20594" id="paint2_linear_10448_46450"
x1="9.5" x1="9.5"
y1="6.5" y1="6.5"
x2="13.5" x2="13.5"
y2="6.5" y2="6.5"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="#5B53FF" stopOpacity="0.1" /> <stop stopColor="#8D99FF" stopOpacity="0.1" />
<stop offset="1" stopColor="white" stopOpacity="0.3" /> <stop offset="1" stopColor="white" stopOpacity="0.3" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint3_linear_7480_20594" id="paint3_linear_10448_46450"
x1="13.5" x1="13.5"
y1="6.5" y1="6.5"
x2="9.5" x2="9.5"
@@ -349,84 +369,84 @@ export const AiAssistantIcon = (props: IconProps) => {
<stop offset="1" stopColor="white" stopOpacity="0" /> <stop offset="1" stopColor="white" stopOpacity="0" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint4_linear_7480_20594" id="paint4_linear_10448_46450"
x1="12" x1="12"
y1="10" y1="10"
x2="16" x2="16"
y2="10" y2="10"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="#5B53FF" stopOpacity="0.1" /> <stop stopColor="#8D99FF" stopOpacity="0.1" />
<stop offset="1" stopColor="white" stopOpacity="0.3" /> <stop offset="1" stopColor="white" stopOpacity="0.3" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint5_linear_7480_20594" id="paint5_linear_10448_46450"
x1="16" x1="16"
y1="10" y1="10"
x2="12" x2="12"
y2="10" y2="10"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="#3B82F6" /> <stop stopColor="#8D99FF" />
<stop offset="1" stopColor="#3B82F6" stopOpacity="0" /> <stop offset="1" stopColor="#8D99FF" stopOpacity="0" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint6_linear_7480_20594" id="paint6_linear_10448_46450"
x1="4" x1="4"
y1="4" y1="4"
x2="8" x2="8"
y2="4" y2="4"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="#5B53FF" stopOpacity="0.1" /> <stop stopColor="#8D99FF" stopOpacity="0.1" />
<stop offset="1" stopColor="white" stopOpacity="0.3" /> <stop offset="1" stopColor="white" stopOpacity="0.3" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint7_linear_7480_20594" id="paint7_linear_10448_46450"
x1="8" x1="8"
y1="4" y1="4"
x2="4" x2="4"
y2="4" y2="4"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="#48EAEB" /> <stop stopColor="#8D99FF" />
<stop offset="1" stopColor="#48EAEB" stopOpacity="0" /> <stop offset="1" stopColor="#8D99FF" stopOpacity="0" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint8_linear_7480_20594" id="paint8_linear_10448_46450"
x1="4" x1="4"
y1="4" y1="4"
x2="8" x2="8"
y2="4" y2="4"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="#5B53FF" stopOpacity="0.1" /> <stop stopColor="#8D99FF" stopOpacity="0.1" />
<stop offset="1" stopColor="white" stopOpacity="0.3" /> <stop offset="1" stopColor="white" stopOpacity="0.3" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint9_linear_7480_20594" id="paint9_linear_10448_46450"
x1="8" x1="8"
y1="4" y1="4"
x2="4" x2="4"
y2="4" y2="4"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="#3B82F6" /> <stop stopColor="#8D99FF" />
<stop offset="1" stopColor="#3B82F6" stopOpacity="0" /> <stop offset="1" stopColor="#8D99FF" stopOpacity="0" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint10_linear_7480_20594" id="paint10_linear_10448_46450"
x1="4" x1="4"
y1="4" y1="4"
x2="8" x2="8"
y2="4" y2="4"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="#5B53FF" stopOpacity="0.1" /> <stop stopColor="#8D99FF" stopOpacity="0.1" />
<stop offset="1" stopColor="white" stopOpacity="0.3" /> <stop offset="1" stopColor="white" stopOpacity="0.3" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint11_linear_7480_20594" id="paint11_linear_10448_46450"
x1="8" x1="8"
y1="4" y1="4"
x2="4" x2="4"
@@ -16,17 +16,17 @@ export const ShadedBgIcon = ({
switch (variant) { switch (variant) {
case "neutral": case "neutral":
case "code": case "code":
return "#E4E4E7" return "var(--docs-tags-neutral-border)"
case "blue": case "blue":
return "#BFDBFE" return "var(--docs-tags-blue-border)"
case "purple": case "purple":
return "#DDD6FE" return "var(--docs-tags-purple-border)"
case "green": case "green":
return "#A7F3D0" return "var(--docs-tags-green-border)"
case "orange": case "orange":
return "#FED7AA" return "var(--docs-tags-orange-border)"
case "red": case "red":
return "#FECDD3" return "var(--docs-tags-red-border)"
} }
}, [variant]) }, [variant])
@@ -89,7 +89,7 @@ export const Modal = ({
className={clsx( className={clsx(
"fixed top-0 left-0 flex h-screen w-screen items-center justify-center", "fixed top-0 left-0 flex h-screen w-screen items-center justify-center",
"bg-medusa-bg-overlay z-50", "bg-medusa-bg-overlay z-50",
"hidden open:flex border-0 p-0", "hidden open:flex border-0 px-docs_0.5 lg:p-0",
className className
)} )}
onClick={handleClick} onClick={handleClick}
@@ -99,17 +99,17 @@ export const Modal = ({
> >
<div <div
className={clsx( className={clsx(
"bg-medusa-bg-base rounded-docs_sm", "bg-medusa-bg-base rounded-docs_lg",
"border-medusa-border-base border border-solid",
"shadow-elevation-modal dark:shadow-elevation-modal-dark", "shadow-elevation-modal dark:shadow-elevation-modal-dark",
"w-[90%] md:h-auto md:w-[75%] lg:w-[560px]", "max-w-full sm:max-w-modal-sm md:max-w-modal-md lg:max-w-modal-lg",
"h-auto w-full",
modalContainerClassName modalContainerClassName
)} )}
> >
{title && <ModalHeader title={title} />} {title && <ModalHeader title={title} />}
<div <div
className={clsx( className={clsx(
"overflow-auto py-docs_1.5 px-docs_2", "overflow-auto py-docs_1.5 px-docs_2 rounded-docs_lg",
contentClassName contentClassName
)} )}
> >
@@ -0,0 +1,59 @@
import clsx from "clsx"
import React from "react"
import { Kbd } from "../../.."
export const SearchFooter = () => {
return (
<div
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"
)}
>
<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")}
>
Navigation
</span>
<span className="gap-[5px] flex">
<Kbd
className={clsx(
"!bg-medusa-bg-field-component !border-medusa-border-strong",
"!text-medusa-fg-subtle h-[18px] w-[18px] p-0"
)}
>
</Kbd>
<Kbd
className={clsx(
"!bg-medusa-bg-field-component !border-medusa-border-strong",
"!text-medusa-fg-subtle h-[18px] w-[18px] p-0"
)}
>
</Kbd>
</span>
</div>
<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")}
>
Open Result
</span>
<Kbd
className={clsx(
"!bg-medusa-bg-field-component !border-medusa-border-strong",
"!text-medusa-fg-subtle h-[18px] w-[18px] p-0"
)}
>
</Kbd>
</div>
</div>
</div>
)
}
@@ -9,7 +9,7 @@ export const SearchHitGroupName = ({ name }: SearchHitGroupNameProps) => {
return ( return (
<span <span
className={clsx( className={clsx(
"pb-docs_0.25 flex px-docs_0.5 pt-docs_1", "pb-docs_0.25 flex px-docs_0.5 pt-docs_0.75",
"text-medusa-fg-muted", "text-medusa-fg-muted",
"text-compact-x-small-plus" "text-compact-x-small-plus"
)} )}
@@ -70,7 +70,7 @@ export const SearchHitsWrapper = ({
} }
return ( return (
<div className="h-full overflow-auto"> <div className="h-full overflow-auto px-docs_0.5">
{status !== "loading" && showNoResults && <SearchNoResult />} {status !== "loading" && showNoResults && <SearchNoResult />}
{indices.map((indexName, index) => ( {indices.map((indexName, index) => (
<Index indexName={indexName} key={index}> <Index indexName={indexName} key={index}>
@@ -1,4 +1,4 @@
import { ExclamationCircleSolid } from "@medusajs/icons" import { MagnifierAlert } from "@medusajs/icons"
import clsx from "clsx" import clsx from "clsx"
import React from "react" import React from "react"
@@ -6,14 +6,24 @@ export const SearchNoResult = () => {
return ( return (
<div <div
className={clsx( className={clsx(
"flex h-full w-full flex-col items-center justify-center gap-docs_1", "flex h-full w-full flex-col items-center justify-center gap-docs_0.75"
"text-medusa-fg-muted"
)} )}
> >
<ExclamationCircleSolid /> <MagnifierAlert className="text-medusa-fg-base" />
<span className="text-compact-small"> <div
No results found. Try changing selected filters. className={clsx(
</span> "flex flex-col justify-center items-center gap-docs_0.25",
"max-w-[360px]"
)}
>
<span className="text-compact-small-plus text-medusa-fg-subtle">
No results found.
</span>
<span className="text-medusa-fg-muted txt-small text-center">
We couldn&apos;t find any matches for your search. Please try changing
the filters or using different keywords.
</span>
</div>
</div> </div>
) )
} }
@@ -14,7 +14,7 @@ export const SearchSuggestionItem = ({
return ( return (
<div <div
className={clsx( className={clsx(
"flex items-center", "flex items-center justify-between gap-docs_0.75",
"cursor-pointer rounded-docs_sm p-docs_0.5", "cursor-pointer rounded-docs_sm p-docs_0.5",
"hover:bg-medusa-bg-base-hover", "hover:bg-medusa-bg-base-hover",
"focus:bg-medusa-bg-base-hover", "focus:bg-medusa-bg-base-hover",
@@ -21,7 +21,7 @@ export const SearchSuggestions = ({ suggestions }: SearchSuggestionsProps) => {
const { commands, setCommand } = useSearch() const { commands, setCommand } = useSearch()
return ( return (
<div className="h-full overflow-auto"> <div className="h-full overflow-auto px-docs_0.5">
{commands.length > 0 && ( {commands.length > 0 && (
<> <>
<SearchHitGroupName name={"Commands"} /> <SearchHitGroupName name={"Commands"} />
@@ -30,12 +30,9 @@ export const SearchSuggestions = ({ suggestions }: SearchSuggestionsProps) => {
onClick={() => setCommand(command)} onClick={() => setCommand(command)}
key={index} key={index}
tabIndex={index} tabIndex={index}
className="justify-between"
> >
<span className="flex gap-docs_0.75"> {command.icon}
{command.icon} <span className="flex-1">{command.title}</span>
<span>{command.title}</span>
</span>
{command.badge && <Badge {...command.badge} />} {command.badge && <Badge {...command.badge} />}
</SearchSuggestionItem> </SearchSuggestionItem>
))} ))}
@@ -8,9 +8,10 @@ import { SearchSuggestions, type SearchSuggestionType } from "./Suggestions"
import { AlgoliaProps, useSearch } from "@/providers" import { AlgoliaProps, useSearch } from "@/providers"
import { checkArraySameElms } from "@/utils" import { checkArraySameElms } from "@/utils"
import { SearchHitsWrapper } from "./Hits" import { SearchHitsWrapper } from "./Hits"
import { Button, Kbd, SelectBadge } from "@/components" import { Button, Kbd, SelectBadge, SpinnerLoading } from "@/components"
import { MagnifyingGlass, XMark } from "@medusajs/icons" import { MagnifyingGlass, XMark } from "@medusajs/icons"
import { useSearchNavigation, type OptionType } from "@/hooks" import { useSearchNavigation, type OptionType } from "@/hooks"
import { SearchFooter } from "./Footer"
export type SearchProps = { export type SearchProps = {
algolia: AlgoliaProps algolia: AlgoliaProps
@@ -68,7 +69,31 @@ export const Search = ({
}) })
return ( return (
<div className="h-full"> <div className="h-full flex flex-col">
{filterOptions.length && (
<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"
/>
)}
<InstantSearch <InstantSearch
indexName={algolia.mainIndexName} indexName={algolia.mainIndexName}
searchClient={searchClient} searchClient={searchClient}
@@ -86,27 +111,25 @@ export const Search = ({
), ),
form: clsx("h-full md:rounded-t-docs_xl bg-transparent"), form: clsx("h-full md:rounded-t-docs_xl bg-transparent"),
input: clsx( input: clsx(
"w-full h-full pl-docs_3 text-medusa-fg-base", "w-full h-full px-docs_1 py-docs_0.75 text-medusa-fg-base",
"placeholder:text-medusa-fg-muted", "placeholder:text-medusa-fg-muted bg-medusa-bg-base",
"md:rounded-t-docs_xl text-compact-medium bg-transparent", "md:rounded-t-docs_xl text-compact-medium",
"appearance-none search-cancel:hidden border-0 active:outline-none focus:outline-none" "appearance-none search-cancel:hidden border-0 active:outline-none focus:outline-none"
), ),
submit: clsx("absolute top-[18px] left-docs_1 btn-clear p-0"), submit: clsx("absolute top-[18px] left-docs_1 btn-clear p-0"),
reset: clsx( reset: clsx("absolute top-[18px] right-docs_1", "btn-clear"),
"absolute top-docs_0.75 right-docs_1 hover:bg-medusa-bg-base-hover",
"p-[5px] md:rounded-docs_DEFAULT btn-clear"
),
loadingIndicator: clsx("absolute top-[18px] right-docs_1"), loadingIndicator: clsx("absolute top-[18px] right-docs_1"),
}} }}
submitIconComponent={() => ( submitIconComponent={() => <></>}
<MagnifyingGlass className="text-medusa-fg-muted" />
)}
resetIconComponent={() => ( resetIconComponent={() => (
<XMark className="hidden md:block text-medusa-fg-subtle" /> <span className="text-medusa-fg-muted text-compact-small-plus hover:text-medusa-fg-subtle">
Clear
</span>
)} )}
placeholder="Find something..." placeholder="Find something..."
autoFocus autoFocus
formRef={searchBoxRef} formRef={searchBoxRef}
loadingIconComponent={() => <SpinnerLoading />}
/> />
<Button <Button
variant="transparent" variant="transparent"
@@ -121,7 +144,7 @@ export const Search = ({
<XMark className="text-medusa-fg-muted" /> <XMark className="text-medusa-fg-muted" />
</Button> </Button>
</div> </div>
<div className="mx-docs_0.5 md:flex-initial h-[calc(100%-120px)] md:h-[calc(100%-114px)] lg:max-h-[calc(100%-114px)] lg:min-h-[calc(100%-114px)]"> <div className="md:flex-initial h-[calc(100%-95px)] lg:max-h-[calc(100%-140px)] lg:min-h-[calc(100%-140px)]">
<SearchEmptyQueryBoundary <SearchEmptyQueryBoundary
fallback={<SearchSuggestions suggestions={suggestions} />} fallback={<SearchSuggestions suggestions={suggestions} />}
> >
@@ -135,59 +158,7 @@ export const Search = ({
</SearchEmptyQueryBoundary> </SearchEmptyQueryBoundary>
</div> </div>
</InstantSearch> </InstantSearch>
<div <SearchFooter />
className={clsx(
"py-docs_0.75 flex items-center justify-between px-docs_1",
"border-0 border-solid h-[57px]",
"border-medusa-border-base border-t",
"bg-medusa-bg-base"
)}
>
{filterOptions.length && (
<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))
}
}}
/>
)}
<div className="hidden items-center gap-docs_1 md:flex">
<div className="flex items-center gap-docs_0.5">
<span
className={clsx("text-medusa-fg-subtle", "text-compact-x-small")}
>
Navigation
</span>
<span className="gap-docs_0.25 flex">
<Kbd></Kbd>
<Kbd></Kbd>
</span>
</div>
<div className="flex items-center gap-docs_0.5">
<span
className={clsx("text-medusa-fg-subtle", "text-compact-x-small")}
>
Open Result
</span>
<Kbd></Kbd>
</div>
</div>
</div>
</div> </div>
) )
} }
@@ -4,6 +4,7 @@ import React, { useCallback, useRef, useState } from "react"
import { useSelect } from "@/hooks" import { useSelect } from "@/hooks"
import clsx from "clsx" import clsx from "clsx"
import { SelectDropdown, SelectProps } from ".." import { SelectDropdown, SelectProps } from ".."
import { TriangleDownMini } from "@medusajs/icons"
export const SelectBadge = ({ export const SelectBadge = ({
value, value,
@@ -20,16 +21,21 @@ export const SelectBadge = ({
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const ref = useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
const dropdownRef = useRef<HTMLDivElement>(null) const dropdownRef = useRef<HTMLDivElement>(null)
const { isValueSelected, isAllSelected, handleChange, handleSelectAll } = const {
useSelect({ isValueSelected,
value, isAllSelected,
options, handleChange,
multiple, handleSelectAll,
setSelected, setSelectedValues,
removeSelected, } = useSelect({
addSelected, value,
handleAddAll, options,
}) multiple,
setSelected,
removeSelected,
addSelected,
handleAddAll,
})
const getSelectedText = useCallback(() => { const getSelectedText = useCallback(() => {
let str = "" let str = ""
@@ -38,7 +44,7 @@ export const SelectBadge = ({
) )
if (isAllSelected) { if (isAllSelected) {
str = "All Areas" str = "All areas"
} else { } else {
if ( if (
(!Array.isArray(value) && !value) || (!Array.isArray(value) && !value) ||
@@ -54,37 +60,28 @@ export const SelectBadge = ({
<> <>
<span <span
className={clsx( className={clsx(
"text-medusa-fg-base", "text-compact-x-small-plus text-medusa-tag-neutral-text"
"text-compact-x-small-plus",
"inline-block max-w-[60px] overflow-hidden text-ellipsis"
)} )}
> >
{str} {str}
{!isAllSelected && selectedOptions.length > 1 && (
<> + {selectedOptions.length - 1}</>
)}
</span> </span>
{!isAllSelected && selectedOptions.length > 1 && (
<span
className={clsx("text-medusa-fg-subtle", "text-compact-x-small")}
>
{" "}
+ {selectedOptions.length}
</span>
)}
</> </>
) )
}, [isAllSelected, options, value]) }, [isAllSelected, options, value])
return ( return (
<div className={clsx("relative", className)}> <div className={clsx("relative w-fit", className)}>
<div <div
className={clsx( className={clsx(
"border-medusa-border-base rounded-docs_sm border border-solid", "border-medusa-tag-neutral-border rounded-docs_sm border border-solid",
"hover:bg-medusa-bg-subtle-hover", "h-fit cursor-pointer pl-docs_0.25 pr-[3px] text-medusa-tag-neutral-text",
"py-docs_0.25 h-fit cursor-pointer px-docs_0.5", "bg-medusa-tag-neutral-bg",
"flex items-center gap-[6px] whitespace-nowrap", "flex items-center gap-[3px] whitespace-nowrap",
"text-medusa-fg-subtle", "text-medusa-fg-subtle",
!open && "bg-medusa-bg-subtle", open && "bg-medusa-tag-neutral-bg-hover"
open && "bg-medusa-bg-subtle-hover",
className
)} )}
ref={ref} ref={ref}
onClick={(e) => { onClick={(e) => {
@@ -93,10 +90,8 @@ export const SelectBadge = ({
} }
}} }}
> >
<span className={clsx("text-medusa-fg-subtle", "text-compact-x-small")}>
Show results from:{" "}
</span>
{getSelectedText()} {getSelectedText()}
<TriangleDownMini className="text-medusa-tag-neutral-icon" />
</div> </div>
<input <input
type="hidden" type="hidden"
@@ -115,10 +110,7 @@ export const SelectBadge = ({
handleChange={handleChange} handleChange={handleChange}
parentRef={ref} parentRef={ref}
passedRef={dropdownRef} passedRef={dropdownRef}
className={clsx( setSelectedValues={setSelectedValues}
"!top-[unset] !bottom-full",
open && "!-translate-y-docs_0.5"
)}
/> />
</div> </div>
) )
@@ -19,6 +19,7 @@ export type SelectDropdownProps = {
parentRef?: React.RefObject<HTMLDivElement> parentRef?: React.RefObject<HTMLDivElement>
className?: string className?: string
passedRef?: Ref<HTMLDivElement> passedRef?: Ref<HTMLDivElement>
setSelectedValues?: (values: string[]) => void
} }
export const SelectDropdown = ({ export const SelectDropdown = ({
@@ -34,6 +35,7 @@ export const SelectDropdown = ({
parentRef, parentRef,
className, className,
passedRef, passedRef,
setSelectedValues,
}: SelectDropdownProps) => { }: SelectDropdownProps) => {
const ref = useRef<HTMLDivElement | null>(null) const ref = useRef<HTMLDivElement | null>(null)
const setRefs = useCallback( const setRefs = useCallback(
@@ -50,7 +52,11 @@ export const SelectDropdown = ({
) )
const handleChange = (clickedValue: string, wasSelected: boolean) => { const handleChange = (clickedValue: string, wasSelected: boolean) => {
handleSelectChange?.(clickedValue, wasSelected) if (isAllSelected && setSelectedValues) {
setSelectedValues([clickedValue])
} else {
handleSelectChange?.(clickedValue, wasSelected)
}
if (!multiple) { if (!multiple) {
setOpen(false) setOpen(false)
} }
@@ -78,43 +84,75 @@ export const SelectDropdown = ({
}, [handleOutsideClick]) }, [handleOutsideClick])
const getSelectOption = (option: OptionType, index: number) => { const getSelectOption = (option: OptionType, index: number) => {
const isSelected = option.isAllOption const originalIsSelected = isValueSelected(option.value)
? isAllSelected const isSelected = isAllSelected
: isValueSelected(option.value) ? option.isAllOption || false
: originalIsSelected
return ( return (
<li <li
key={index} key={index}
className={clsx( className={clsx(
"pr-docs_0.75 relative rounded-docs_sm py-docs_0.5 pl-docs_2.5", "px-docs_0.25",
"hover:bg-medusa-bg-base-hover", index <= 0 && "rounded-t-docs_DEFAULT",
"[&>svg]:left-docs_0.75 cursor-pointer [&>svg]:absolute [&>svg]:top-docs_0.5", index === options.length - 1 && "rounded-b-docs_DEFAULT"
!isSelected && "text-compact-small",
isSelected && "text-compact-small-plus"
)} )}
onClick={() => { onClick={() => {
if (option.isAllOption) { if (option.isAllOption) {
handleSelectAll() handleSelectAll()
} else { } else {
handleChange(option.value, isSelected) handleChange(option.value, originalIsSelected)
} }
}} }}
> >
{isSelected && ( <div
<> className={clsx(
{multiple && <CheckMini className="text-medusa-fg-base" />} "px-docs_0.5 py-docs_0.25 flex-1 min-w-max rounded-docs_xs",
{!multiple && <EllipseMiniSolid className="text-medusa-fg-base" />} "hover:bg-medusa-bg-component-hover cursor-pointer",
</> "flex gap-docs_0.5 text-medusa-fg-base items-center",
)} !isSelected && "text-compact-small",
{option.label} isSelected && "text-compact-small-plus"
)}
>
<span>
{isSelected && (
<>
{option.isAllOption && <EllipseMiniSolid />}
{!option.isAllOption && (
<>
{multiple && <CheckMini />}
{!multiple && <EllipseMiniSolid />}
</>
)}
</>
)}
{!isSelected && <EllipseMiniSolid className="invisible" />}
</span>
<span className="flex-1">{option.label}</span>
</div>
</li> </li>
) )
} }
const getDivider = () => (
<svg
width="176"
height="8"
viewBox="0 0 176 8"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="168" height="8" transform="translate(4)" fill="#FAFAFA" />
<rect y="4" width="176" height="1" fill="white" />
<rect y="3" width="176" height="1" fill="#E4E4E7" />
</svg>
)
return ( return (
<div <div
className={clsx( className={clsx(
"absolute top-full left-0 w-full", "absolute left-0 md:left-docs_1",
"z-10",
"h-0 translate-y-0 overflow-hidden transition-transform", "h-0 translate-y-0 overflow-hidden transition-transform",
open && "h-auto translate-y-docs_0.5 !overflow-visible", open && "h-auto translate-y-docs_0.5 !overflow-visible",
className className
@@ -123,20 +161,25 @@ export const SelectDropdown = ({
> >
<ul <ul
className={clsx( className={clsx(
"p-docs_0.25 mb-0 w-full overflow-auto rounded-docs_DEFAULT", "mb-0 py-docs_0.25 overflow-auto rounded-docs_DEFAULT",
"bg-medusa-bg-base text-medusa-fg-base", "bg-medusa-bg-component text-medusa-fg-base",
"shadow-elevation-flyout dark:shadow-elevation-flyout-dark list-none" "shadow-elevation-flyout dark:shadow-elevation-flyout-dark list-none",
"flex flex-col"
)} )}
> >
{addAll && {addAll && (
getSelectOption( <>
{ {getSelectOption(
value: "all", {
label: "All Areas", value: "all",
isAllOption: true, label: "All Areas",
}, isAllOption: true,
-1 },
)} -1
)}
{getDivider()}
</>
)}
{options.map(getSelectOption)} {options.map(getSelectOption)}
</ul> </ul>
</div> </div>
@@ -79,6 +79,10 @@ export const useSelect = ({
} }
} }
const setSelectedValues = (values: string[]) => {
setSelected?.(values)
}
return { return {
isValueSelected, isValueSelected,
hasSelectedValue, hasSelectedValue,
@@ -87,5 +91,6 @@ export const useSelect = ({
isAllSelected, isAllSelected,
handleChange, handleChange,
handleSelectAll, handleSelectAll,
setSelectedValues,
} }
} }
@@ -13,6 +13,7 @@ export type AiAssistantContextType = {
questionId: string, questionId: string,
reaction: AiAssistantFeedbackType reaction: AiAssistantFeedbackType
) => Promise<Response> ) => Promise<Response>
version: "v1" | "v2"
} }
const AiAssistantContext = createContext<AiAssistantContextType | null>(null) const AiAssistantContext = createContext<AiAssistantContextType | null>(null)
@@ -22,12 +23,14 @@ export type AiAssistantProviderProps = {
apiUrl: string apiUrl: string
recaptchaSiteKey: string recaptchaSiteKey: string
websiteId: string websiteId: string
version?: "v1" | "v2"
} }
export const AiAssistantProvider = ({ export const AiAssistantProvider = ({
apiUrl, apiUrl,
recaptchaSiteKey, recaptchaSiteKey,
websiteId, websiteId,
version = "v2",
children, children,
}: AiAssistantProviderProps) => { }: AiAssistantProviderProps) => {
const { analytics } = useAnalytics() const { analytics } = useAnalytics()
@@ -90,6 +93,7 @@ export const AiAssistantProvider = ({
value={{ value={{
getAnswer, getAnswer,
sendFeedback, sendFeedback,
version,
}} }}
> >
{children} {children}
@@ -131,14 +131,9 @@ export const SearchProvider = ({
<Modal <Modal
contentClassName={clsx( contentClassName={clsx(
"!p-0 overflow-hidden relative h-full", "!p-0 overflow-hidden relative h-full",
"rounded-none md:rounded-docs_lg flex flex-col justify-between" "flex flex-col justify-between"
)}
modalContainerClassName={clsx(
"!rounded-none md:!rounded-docs_lg",
"md:!h-[480px] h-screen",
"md:!w-[640px] w-screen",
"bg-medusa-bg-base"
)} )}
modalContainerClassName="sm:h-[480px] sm:max-h-[480px]"
open={isOpen} open={isOpen}
onClose={() => setIsOpen(false)} onClose={() => setIsOpen(false)}
passedRef={modalRef} passedRef={modalRef}
@@ -156,7 +151,7 @@ export const SearchProvider = ({
? "animate-fadeOutLeft animate-fast" ? "animate-fadeOutLeft animate-fast"
: "animate-fadeOutRight animate-fast", : "animate-fadeOutRight animate-fast",
}} }}
timeout={300} timeout={250}
key={command?.name || "search"} key={command?.name || "search"}
> >
<> <>
@@ -310,8 +310,14 @@ module.exports = {
"inner-content-xxl": "640px", "inner-content-xxl": "640px",
"inner-content-xxxl": "640px", "inner-content-xxxl": "640px",
// wide layout // wide layout
"wide-content": "1112px",
"xl-wide-content": "1112px", "xl-wide-content": "1112px",
"lg-wide-content": "800px", "lg-wide-content": "800px",
// modal
"modal-xs": "304px",
"modal-sm": "624px",
"modal-md": "752px",
"modal-lg": "640px",
}, },
minWidth: { minWidth: {
xl: "1419px", xl: "1419px",