From f07dc0384fc1031e7efa0608bbdda16243d16dee Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Thu, 24 Aug 2023 18:36:06 +0300 Subject: [PATCH] docs,api-ref: added search filters (#4830) * initial implementation of search modal * added hit and search suggestions * added support for multiple indices * updated sample env * added close when click outside dropdown * test for mobile * added mobile design * added shortcut * dark mode fixes * added search to docs * added plugins filter * added React import * moved filters to configurations * handled error on page load * change suggestion text * removed hits limit * handle select all * open link in current tab * change highlight colors * added support for shortcuts + auto focus * change header and footer * redesigned search ui --- www/api-reference/.env.sample | 3 +- www/api-reference/app/api/[area]/layout.tsx | 43 +- www/api-reference/app/api/algolia/route.tsx | 2 +- www/api-reference/components/Badge/index.tsx | 3 + .../components/Bordered/index.tsx | 23 + .../components/BorderedIcon/index.tsx | 56 ++ www/api-reference/components/Button/index.tsx | 6 +- .../Icons/ArrowDownLeftMini/index.tsx | 37 ++ .../components/Icons/CheckMini/index.tsx | 32 ++ .../components/Icons/ChevronUpDown/index.tsx | 32 ++ .../Icons/DocumentTextSolid/index.tsx | 33 ++ .../Icons/EllipseMiniSolid/index.tsx | 31 ++ .../Icons/ExclamationCircleSolid/index.tsx | 29 + .../Icons/MagnifyingGlass/index.tsx | 27 + .../components/Icons/ToolsSolid/index.tsx | 42 ++ .../components/Icons/TreeNode/index.tsx | 36 ++ .../components/Icons/XMarkMini/index.tsx | 27 + .../components/Input/Text/index.tsx | 2 +- .../components/MDXComponents/Kbd/index.tsx | 24 + .../components/MDXComponents/index.tsx | 2 + .../components/Modal/Footer/index.tsx | 8 +- .../components/Modal/Header/index.tsx | 9 +- www/api-reference/components/Modal/index.tsx | 99 +++- .../components/Navbar/FeedbackModal/index.tsx | 5 + .../components/Navbar/MenuButton/index.tsx | 6 +- .../components/Navbar/MobileMenu/index.tsx | 28 +- www/api-reference/components/Navbar/index.tsx | 8 +- .../Search/EmptyQueryBoundary/index.tsx | 21 + .../Search/Hits/GroupName/index.tsx | 21 + .../components/Search/Hits/index.tsx | 209 +++++++ .../components/Search/Modal/index.tsx | 333 +++++++++++ .../components/Search/ModalOpener/index.tsx | 84 +++ .../components/Search/NoResults/index.tsx | 14 + .../components/Search/Suggestions/index.tsx | 49 ++ .../components/SearchBar/index.tsx | 20 - .../components/Select/Badge/index.tsx | 135 +++++ .../components/Select/Dropdown/index.tsx | 151 +++++ .../components/Select/Input/index.tsx | 127 +++++ www/api-reference/components/Select/types.ts | 9 + .../components/TextArea/index.tsx | 2 +- www/api-reference/css/code-theme.css | 313 ----------- www/api-reference/css/docsearch.css | 232 -------- www/api-reference/css/globals.css | 28 +- www/api-reference/css/tooltip.css | 10 - .../hooks/use-keyboard-shortcut.tsx | 70 +++ www/api-reference/hooks/use-select.tsx | 93 ++++ www/api-reference/package.json | 1 + www/api-reference/providers/area.tsx | 10 +- www/api-reference/providers/mobile.tsx | 65 +++ www/api-reference/providers/modal.tsx | 10 +- www/api-reference/providers/page-loading.tsx | 41 ++ www/api-reference/providers/search.tsx | 48 ++ www/api-reference/providers/sidebar.tsx | 8 + www/api-reference/tailwind.config.js | 6 + www/api-reference/utils/array-same-elms.ts | 10 + www/api-reference/utils/dom-utils.ts | 29 + www/api-reference/utils/get-base-url.ts | 2 +- www/api-reference/yarn.lock | 181 +++++- www/docs/.env.sample | 4 +- www/docs/docusaurus.config.js | 63 ++- www/docs/package.json | 1 + www/docs/src/components/Badge/index.tsx | 18 +- www/docs/src/components/Button/index.tsx | 7 +- www/docs/src/components/Input/Text/index.tsx | 2 +- .../components/LearningPath/Icon/index.tsx | 2 +- .../LearningPath/Steps/Actions/index.tsx | 4 +- .../src/components/Modal/Footer/index.tsx | 29 + .../src/components/Modal/Header/index.tsx | 39 ++ www/docs/src/components/Modal/index.tsx | 138 +++-- www/docs/src/components/Rating/index.tsx | 2 +- .../Search/EmptyQueryBoundary/index.tsx | 22 + .../Search/Hits/GroupName/index.tsx | 22 + www/docs/src/components/Search/Hits/index.tsx | 190 +++++++ .../src/components/Search/Modal/index.tsx | 312 +++++++++++ .../components/Search/ModalOpener/index.tsx | 78 +++ .../src/components/Search/NoResults/index.tsx | 15 + .../components/Search/Suggestions/index.tsx | 69 +++ .../src/components/Select/Badge/index.tsx | 136 +++++ .../src/components/Select/Dropdown/index.tsx | 152 +++++ .../src/components/Select/Input/index.tsx | 128 +++++ www/docs/src/components/Select/types.ts | 9 + .../StructuredData/Searchbox/index.tsx | 33 -- www/docs/src/components/TextArea/index.tsx | 2 +- www/docs/src/css/components/docsearch.css | 197 ------- www/docs/src/css/custom.css | 38 +- www/docs/src/hooks/use-keyboard-shortcut.tsx | 64 +++ www/docs/src/hooks/use-select.tsx | 93 ++++ www/docs/src/providers/Modal/index.tsx | 13 +- www/docs/src/providers/Search/index.tsx | 68 +++ www/docs/src/theme/DocPage/index.tsx | 5 +- .../theme/Icon/ArrowDownLeftMini/index.tsx | 41 ++ www/docs/src/theme/Icon/CheckMini/index.tsx | 31 ++ .../src/theme/Icon/ChevronUpDown/index.tsx | 31 ++ .../src/theme/Icon/EllipseMiniSolid/index.tsx | 30 + .../src/theme/Icon/MagnifyingGlass/index.tsx | 31 ++ www/docs/src/theme/Icon/XMarkMini/index.tsx | 31 ++ www/docs/src/theme/Icon/index.ts | 14 + www/docs/src/theme/Layout/index.tsx | 40 +- www/docs/src/theme/MDXComponents/Kbd.tsx | 27 + www/docs/src/theme/MDXComponents/index.tsx | 2 + www/docs/src/theme/Navbar/Search/index.tsx | 10 + .../src/theme/NavbarItem/SearchNavbarItem.tsx | 18 + www/docs/src/theme/SearchPage/index.tsx | 524 ------------------ www/docs/src/types/index.d.ts | 20 + www/docs/src/utils/array-same-elms.ts | 10 + www/docs/src/utils/dom-utils.ts | 29 + www/docs/tailwind.config.js | 6 + www/docs/yarn.lock | 183 +++++- www/tailwind.config.js | 185 +++---- 109 files changed, 4555 insertions(+), 1648 deletions(-) create mode 100644 www/api-reference/components/Bordered/index.tsx create mode 100644 www/api-reference/components/BorderedIcon/index.tsx create mode 100644 www/api-reference/components/Icons/ArrowDownLeftMini/index.tsx create mode 100644 www/api-reference/components/Icons/CheckMini/index.tsx create mode 100644 www/api-reference/components/Icons/ChevronUpDown/index.tsx create mode 100644 www/api-reference/components/Icons/DocumentTextSolid/index.tsx create mode 100644 www/api-reference/components/Icons/EllipseMiniSolid/index.tsx create mode 100644 www/api-reference/components/Icons/ExclamationCircleSolid/index.tsx create mode 100644 www/api-reference/components/Icons/MagnifyingGlass/index.tsx create mode 100644 www/api-reference/components/Icons/ToolsSolid/index.tsx create mode 100644 www/api-reference/components/Icons/TreeNode/index.tsx create mode 100644 www/api-reference/components/Icons/XMarkMini/index.tsx create mode 100644 www/api-reference/components/MDXComponents/Kbd/index.tsx create mode 100644 www/api-reference/components/Search/EmptyQueryBoundary/index.tsx create mode 100644 www/api-reference/components/Search/Hits/GroupName/index.tsx create mode 100644 www/api-reference/components/Search/Hits/index.tsx create mode 100644 www/api-reference/components/Search/Modal/index.tsx create mode 100644 www/api-reference/components/Search/ModalOpener/index.tsx create mode 100644 www/api-reference/components/Search/NoResults/index.tsx create mode 100644 www/api-reference/components/Search/Suggestions/index.tsx delete mode 100644 www/api-reference/components/SearchBar/index.tsx create mode 100644 www/api-reference/components/Select/Badge/index.tsx create mode 100644 www/api-reference/components/Select/Dropdown/index.tsx create mode 100644 www/api-reference/components/Select/Input/index.tsx create mode 100644 www/api-reference/components/Select/types.ts delete mode 100644 www/api-reference/css/code-theme.css delete mode 100644 www/api-reference/css/docsearch.css create mode 100644 www/api-reference/hooks/use-keyboard-shortcut.tsx create mode 100644 www/api-reference/hooks/use-select.tsx create mode 100644 www/api-reference/providers/mobile.tsx create mode 100644 www/api-reference/providers/page-loading.tsx create mode 100644 www/api-reference/providers/search.tsx create mode 100644 www/api-reference/utils/array-same-elms.ts create mode 100644 www/api-reference/utils/dom-utils.ts create mode 100644 www/docs/src/components/Modal/Footer/index.tsx create mode 100644 www/docs/src/components/Modal/Header/index.tsx create mode 100644 www/docs/src/components/Search/EmptyQueryBoundary/index.tsx create mode 100644 www/docs/src/components/Search/Hits/GroupName/index.tsx create mode 100644 www/docs/src/components/Search/Hits/index.tsx create mode 100644 www/docs/src/components/Search/Modal/index.tsx create mode 100644 www/docs/src/components/Search/ModalOpener/index.tsx create mode 100644 www/docs/src/components/Search/NoResults/index.tsx create mode 100644 www/docs/src/components/Search/Suggestions/index.tsx create mode 100644 www/docs/src/components/Select/Badge/index.tsx create mode 100644 www/docs/src/components/Select/Dropdown/index.tsx create mode 100644 www/docs/src/components/Select/Input/index.tsx create mode 100644 www/docs/src/components/Select/types.ts delete mode 100644 www/docs/src/components/StructuredData/Searchbox/index.tsx delete mode 100644 www/docs/src/css/components/docsearch.css create mode 100644 www/docs/src/hooks/use-keyboard-shortcut.tsx create mode 100644 www/docs/src/hooks/use-select.tsx create mode 100644 www/docs/src/providers/Search/index.tsx create mode 100644 www/docs/src/theme/Icon/ArrowDownLeftMini/index.tsx create mode 100644 www/docs/src/theme/Icon/CheckMini/index.tsx create mode 100644 www/docs/src/theme/Icon/ChevronUpDown/index.tsx create mode 100644 www/docs/src/theme/Icon/EllipseMiniSolid/index.tsx create mode 100644 www/docs/src/theme/Icon/MagnifyingGlass/index.tsx create mode 100644 www/docs/src/theme/Icon/XMarkMini/index.tsx create mode 100644 www/docs/src/theme/MDXComponents/Kbd.tsx create mode 100644 www/docs/src/theme/Navbar/Search/index.tsx create mode 100644 www/docs/src/theme/NavbarItem/SearchNavbarItem.tsx delete mode 100644 www/docs/src/theme/SearchPage/index.tsx create mode 100644 www/docs/src/utils/array-same-elms.ts create mode 100644 www/docs/src/utils/dom-utils.ts diff --git a/www/api-reference/.env.sample b/www/api-reference/.env.sample index 47899803ec..a9a938212a 100644 --- a/www/api-reference/.env.sample +++ b/www/api-reference/.env.sample @@ -1,6 +1,7 @@ NEXT_PUBLIC_SEGMENT_API_KEY= NEXT_PUBLIC_BASE_PATH= -NEXT_PUBLIC_ALGOLIA_INDEX_NAME= +NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME= +NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME= NEXT_PUBLIC_ALGOLIA_API_KEY= NEXT_PUBLIC_ALGOLIA_APP_ID= NEXT_PUBLIC_ENV= diff --git a/www/api-reference/app/api/[area]/layout.tsx b/www/api-reference/app/api/[area]/layout.tsx index 31b226a0be..07493b9958 100644 --- a/www/api-reference/app/api/[area]/layout.tsx +++ b/www/api-reference/app/api/[area]/layout.tsx @@ -10,7 +10,10 @@ import { Roboto_Mono } from "next/font/google" import AnalyticsProvider from "@/providers/analytics" import NavbarProvider from "@/providers/navbar" import ModalProvider from "../../../providers/modal" +import SearchProvider from "../../../providers/search" import { ScrollControllerProvider } from "../../../hooks/scroll-utils" +import MobileProvider from "../../../providers/mobile" +import PageLoadingProvider from "../../../providers/page-loading" export const metadata = { title: "Medusa API Reference", @@ -46,23 +49,29 @@ export default function RootLayout({ - - - - -
- -
- -
- {children} -
-
-
-
-
-
-
+ + + + + + + +
+ +
+ +
+ {children} +
+
+
+
+
+
+
+
+
+
diff --git a/www/api-reference/app/api/algolia/route.tsx b/www/api-reference/app/api/algolia/route.tsx index 0afde69780..72a610100a 100644 --- a/www/api-reference/app/api/algolia/route.tsx +++ b/www/api-reference/app/api/algolia/route.tsx @@ -15,7 +15,7 @@ export async function GET() { process.env.ALGOLIA_WRITE_API_KEY || "" ) const index = algoliaClient.initIndex( - process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME || "" + process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "" ) // retrieve tags and their operations to index them diff --git a/www/api-reference/components/Badge/index.tsx b/www/api-reference/components/Badge/index.tsx index 7351dc8291..4f4ab310c6 100644 --- a/www/api-reference/components/Badge/index.tsx +++ b/www/api-reference/components/Badge/index.tsx @@ -13,6 +13,7 @@ export type BadgeProps = { | "blue" | "blue-dark" | "red" + | "neutral" } & React.HTMLAttributes const Badge: React.FC = ({ className, variant, children }) => { @@ -38,6 +39,8 @@ const Badge: React.FC = ({ className, variant, children }) => { "bg-medusa-tag-blue-bg-dark text-medusa-tag-blue-text-dark border-medusa-tag-blue-border-dark", variant === "red" && "bg-medusa-tag-red-bg dark:bg-medusa-tag-red-bg-dark text-medusa-tag-red-text dark:text-medusa-tag-red-text-dark border-medusa-tag-red-border dark:border-medusa-tag-red-border-dark", + variant === "neutral" && + "bg-medusa-tag-neutral-bg dark:bg-medusa-tag-neutral-bg-dark text-medusa-tag-neutral-text dark:text-medusa-tag-neutral-text-dark border-medusa-tag-neutral-border dark:border-medusa-tag-neutral-border-dark", "badge", className )} diff --git a/www/api-reference/components/Bordered/index.tsx b/www/api-reference/components/Bordered/index.tsx new file mode 100644 index 0000000000..d34577702b --- /dev/null +++ b/www/api-reference/components/Bordered/index.tsx @@ -0,0 +1,23 @@ +import React from "react" +import clsx from "clsx" + +type BorderedProps = { + wrapperClassName?: string +} & React.HTMLAttributes + +const Bordered: React.FC = ({ wrapperClassName, children }) => { + return ( + + {children} + + ) +} + +export default Bordered diff --git a/www/api-reference/components/BorderedIcon/index.tsx b/www/api-reference/components/BorderedIcon/index.tsx new file mode 100644 index 0000000000..a602370263 --- /dev/null +++ b/www/api-reference/components/BorderedIcon/index.tsx @@ -0,0 +1,56 @@ +import React from "react" +import clsx from "clsx" +import Bordered from "../Bordered/index" +import IconProps from "../Icons/types" +import { useColorMode } from "../../providers/color-mode" +import Image from "next/image" + +type BorderedIconProp = { + icon?: { + light: string + dark?: string + } + IconComponent?: React.FC + wrapperClassName?: string + iconWrapperClassName?: string + iconClassName?: string + iconColorClassName?: string +} & React.HTMLAttributes + +const BorderedIcon: React.FC = ({ + icon = null, + IconComponent = null, + wrapperClassName, + iconWrapperClassName, + iconClassName, + iconColorClassName = "", +}) => { + const { colorMode } = useColorMode() + + return ( + + + {!IconComponent && ( + + )} + {IconComponent && ( + + )} + + + ) +} + +export default BorderedIcon diff --git a/www/api-reference/components/Button/index.tsx b/www/api-reference/components/Button/index.tsx index eaea4f7cc1..377e4c6910 100644 --- a/www/api-reference/components/Button/index.tsx +++ b/www/api-reference/components/Button/index.tsx @@ -3,8 +3,8 @@ import clsx from "clsx" export type ButtonProps = { isSelected?: boolean disabled?: boolean - variant?: "primary" | "secondary" - darkVariant?: "primary" | "secondary" + variant?: "primary" | "secondary" | "clear" + darkVariant?: "primary" | "secondary" | "clear" } & React.HTMLAttributes const Button = ({ @@ -19,8 +19,10 @@ const Button = ({ className={clsx( variant === "primary" && "btn-primary", variant === "secondary" && "btn-secondary", + variant === "clear" && "btn-clear", darkVariant && darkVariant === "primary" && "dark:btn-primary", darkVariant && darkVariant === "secondary" && "dark:btn-secondary", + darkVariant && darkVariant === "clear" && "dark:btn-clear", className )} {...props} diff --git a/www/api-reference/components/Icons/ArrowDownLeftMini/index.tsx b/www/api-reference/components/Icons/ArrowDownLeftMini/index.tsx new file mode 100644 index 0000000000..3a9992dca7 --- /dev/null +++ b/www/api-reference/components/Icons/ArrowDownLeftMini/index.tsx @@ -0,0 +1,37 @@ +import type IconProps from "../types" + +const IconArrowDownLeftMini = ({ iconColorClassName, ...props }: IconProps) => { + return ( + + + + + ) +} + +export default IconArrowDownLeftMini diff --git a/www/api-reference/components/Icons/CheckMini/index.tsx b/www/api-reference/components/Icons/CheckMini/index.tsx new file mode 100644 index 0000000000..ceb90c83e6 --- /dev/null +++ b/www/api-reference/components/Icons/CheckMini/index.tsx @@ -0,0 +1,32 @@ +import type IconProps from "../types" + +const IconCheckMini = ({ + iconColorClassName, + containerClassName, + ...props +}: IconProps) => { + return ( + + + + ) +} + +export default IconCheckMini diff --git a/www/api-reference/components/Icons/ChevronUpDown/index.tsx b/www/api-reference/components/Icons/ChevronUpDown/index.tsx new file mode 100644 index 0000000000..6a0de85938 --- /dev/null +++ b/www/api-reference/components/Icons/ChevronUpDown/index.tsx @@ -0,0 +1,32 @@ +import type IconProps from "../types" + +const IconChevronUpDown = ({ + iconColorClassName, + containerClassName, + ...props +}: IconProps) => { + return ( + + + + ) +} + +export default IconChevronUpDown diff --git a/www/api-reference/components/Icons/DocumentTextSolid/index.tsx b/www/api-reference/components/Icons/DocumentTextSolid/index.tsx new file mode 100644 index 0000000000..9195e30121 --- /dev/null +++ b/www/api-reference/components/Icons/DocumentTextSolid/index.tsx @@ -0,0 +1,33 @@ +import IconProps from "../types" + +const IconDocumentTextSolid = ({ iconColorClassName, ...props }: IconProps) => { + return ( + + + + + ) +} + +export default IconDocumentTextSolid diff --git a/www/api-reference/components/Icons/EllipseMiniSolid/index.tsx b/www/api-reference/components/Icons/EllipseMiniSolid/index.tsx new file mode 100644 index 0000000000..5ae9db0965 --- /dev/null +++ b/www/api-reference/components/Icons/EllipseMiniSolid/index.tsx @@ -0,0 +1,31 @@ +import type IconProps from "../types" + +const IconEllipseMiniSolid = ({ + iconColorClassName, + containerClassName, + ...props +}: IconProps) => { + return ( + + + + ) +} + +export default IconEllipseMiniSolid diff --git a/www/api-reference/components/Icons/ExclamationCircleSolid/index.tsx b/www/api-reference/components/Icons/ExclamationCircleSolid/index.tsx new file mode 100644 index 0000000000..4546d52822 --- /dev/null +++ b/www/api-reference/components/Icons/ExclamationCircleSolid/index.tsx @@ -0,0 +1,29 @@ +import IconProps from "../types" + +const IconExclamationCircleSolid = ({ + iconColorClassName, + ...props +}: IconProps) => { + return ( + + + + ) +} + +export default IconExclamationCircleSolid diff --git a/www/api-reference/components/Icons/MagnifyingGlass/index.tsx b/www/api-reference/components/Icons/MagnifyingGlass/index.tsx new file mode 100644 index 0000000000..ddadc6c607 --- /dev/null +++ b/www/api-reference/components/Icons/MagnifyingGlass/index.tsx @@ -0,0 +1,27 @@ +import type IconProps from "../types" + +const IconMagnifyingGlass = ({ iconColorClassName, ...props }: IconProps) => { + return ( + + + + ) +} + +export default IconMagnifyingGlass diff --git a/www/api-reference/components/Icons/ToolsSolid/index.tsx b/www/api-reference/components/Icons/ToolsSolid/index.tsx new file mode 100644 index 0000000000..b89f764118 --- /dev/null +++ b/www/api-reference/components/Icons/ToolsSolid/index.tsx @@ -0,0 +1,42 @@ +import IconProps from "../types" + +const IconToolsSolid = ({ iconColorClassName, ...props }: IconProps) => { + return ( + + + + + + ) +} + +export default IconToolsSolid diff --git a/www/api-reference/components/Icons/TreeNode/index.tsx b/www/api-reference/components/Icons/TreeNode/index.tsx new file mode 100644 index 0000000000..89d0701960 --- /dev/null +++ b/www/api-reference/components/Icons/TreeNode/index.tsx @@ -0,0 +1,36 @@ +import IconProps from "../types" + +const IconTreeNode = ({ iconColorClassName, ...props }: IconProps) => { + return ( + + + + + ) +} + +export default IconTreeNode diff --git a/www/api-reference/components/Icons/XMarkMini/index.tsx b/www/api-reference/components/Icons/XMarkMini/index.tsx new file mode 100644 index 0000000000..5c2a3f37b0 --- /dev/null +++ b/www/api-reference/components/Icons/XMarkMini/index.tsx @@ -0,0 +1,27 @@ +import IconProps from "../types" + +const IconXMarkMini = ({ iconColorClassName, ...props }: IconProps) => { + return ( + + + + ) +} + +export default IconXMarkMini diff --git a/www/api-reference/components/Input/Text/index.tsx b/www/api-reference/components/Input/Text/index.tsx index 940d8b0ec3..b541580bbe 100644 --- a/www/api-reference/components/Input/Text/index.tsx +++ b/www/api-reference/components/Input/Text/index.tsx @@ -12,7 +12,7 @@ const InputText = (props: InputTextProps) => { {...props} className={clsx( "bg-medusa-bg-field dark:bg-medusa-bg-field-dark shadow-button-secondary dark:shadow-button-secondary-dark", - "border-medusa-border-loud-muted dark:border-medusa-border-loud-muted-dark rounded-sm border border-solid", + "border-medusa-border-base dark:border-medusa-border-base-dark rounded-sm border border-solid", "px-0.75 py-[9px]", "hover:bg-medusa-bg-field-hover dark:hover:bg-medusa-bg-field-hover-dark", "focus:border-medusa-border-interactive dark:focus:border-medusa-border-interactive-dark", diff --git a/www/api-reference/components/MDXComponents/Kbd/index.tsx b/www/api-reference/components/MDXComponents/Kbd/index.tsx new file mode 100644 index 0000000000..f4425a7475 --- /dev/null +++ b/www/api-reference/components/MDXComponents/Kbd/index.tsx @@ -0,0 +1,24 @@ +import clsx from "clsx" + +type KbdProps = React.ComponentProps<"kbd"> + +const Kbd = ({ children, className, ...props }: KbdProps) => { + return ( + + {children} + + ) +} + +export default Kbd diff --git a/www/api-reference/components/MDXComponents/index.tsx b/www/api-reference/components/MDXComponents/index.tsx index 4446488c06..fa36a92d7a 100644 --- a/www/api-reference/components/MDXComponents/index.tsx +++ b/www/api-reference/components/MDXComponents/index.tsx @@ -4,6 +4,7 @@ import type { OpenAPIV3 } from "openapi-types" import Link from "./Link" import CodeWrapper from "./CodeWrapper" import H2 from "./H2" +import Kbd from "./Kbd" export type ScopeType = { specs?: OpenAPIV3.Document @@ -16,6 +17,7 @@ const getCustomComponents = (scope?: ScopeType): MDXComponents => { code: CodeWrapper, a: Link, h2: (props) =>

, + kbd: Kbd, } } diff --git a/www/api-reference/components/Modal/Footer/index.tsx b/www/api-reference/components/Modal/Footer/index.tsx index b6cf9b1b7e..8fa85d0f71 100644 --- a/www/api-reference/components/Modal/Footer/index.tsx +++ b/www/api-reference/components/Modal/Footer/index.tsx @@ -2,11 +2,12 @@ import clsx from "clsx" import Button, { ButtonProps } from "../../Button" type ModalFooterProps = { - actions: ButtonProps[] + actions?: ButtonProps[] + children?: React.ReactNode className?: string } -const ModalFooter = ({ actions, className }: ModalFooterProps) => { +const ModalFooter = ({ actions, children, className }: ModalFooterProps) => { return (
{ className )} > - {actions.map((action, index) => ( + {actions?.map((action, index) => (
) } diff --git a/www/api-reference/components/Modal/Header/index.tsx b/www/api-reference/components/Modal/Header/index.tsx index 835a6970f1..41f52672f3 100644 --- a/www/api-reference/components/Modal/Header/index.tsx +++ b/www/api-reference/components/Modal/Header/index.tsx @@ -1,6 +1,7 @@ import clsx from "clsx" import { useModal } from "../../../providers/modal" import IconXMark from "../../Icons/XMark" +import Button from "../../Button" type ModalHeaderProps = { title?: string @@ -23,9 +24,13 @@ const ModalHeader = ({ title }: ModalHeaderProps) => { > {title} - + ) } diff --git a/www/api-reference/components/Modal/index.tsx b/www/api-reference/components/Modal/index.tsx index b1f7ae0df0..de788856fb 100644 --- a/www/api-reference/components/Modal/index.tsx +++ b/www/api-reference/components/Modal/index.tsx @@ -1,70 +1,117 @@ import clsx from "clsx" -import React, { useRef } from "react" +import React, { forwardRef, useCallback, useEffect, useRef } from "react" import { ButtonProps } from "../Button" import { useModal } from "../../providers/modal" import ModalHeader from "./Header" import ModalFooter from "./Footer" +import useKeyboardShortcut from "../../hooks/use-keyboard-shortcut" export type ModalProps = { className?: string title?: string actions?: ButtonProps[] + modalContainerClassName?: string contentClassName?: string -} & React.DetailedHTMLProps< - React.DialogHTMLAttributes, - HTMLDialogElement -> + onClose?: React.ReactEventHandler + open?: boolean + footerContent?: React.ReactNode +} & Omit, "ref"> -const Modal: React.FC = ({ - className, - title, - actions, - children, - contentClassName, - ...props -}) => { +const Modal = forwardRef(function Modal( + { + className, + title, + actions, + children, + contentClassName, + modalContainerClassName, + onClose, + open = true, + footerContent, + ...props + }, + passedRef +) { const { closeModal } = useModal() - const dialogRef = useRef(null) + const ref = useRef(null) + + const setRefs = useCallback( + (node: HTMLDialogElement) => { + // Ref's from useRef needs to have the node assigned to `current` + ref.current = node + if (typeof passedRef === "function") { + passedRef(node) + } else if (passedRef && "current" in passedRef) { + passedRef.current = node + } + }, + [passedRef] + ) + + useKeyboardShortcut({ + metakey: false, + checkEditing: false, + shortcutKeys: ["escape"], + action: () => { + if (open) { + ref.current?.close() + } + }, + }) const handleClick = (e: React.MouseEvent) => { // close modal when the user clicks outside the content - if (e.target === dialogRef.current) { + if (e.target === ref.current) { closeModal() + onClose?.(e) } } + const handleClose = (e: React.SyntheticEvent) => { + onClose?.(e) + closeModal() + } + + useEffect(() => { + if (open) { + document.body.setAttribute("data-modal", "opened") + } else { + document.body.removeAttribute("data-modal") + } + }, [open]) + return (
- -
+ {title && } +
{children}
{actions && actions?.length > 0 && } + {footerContent && {footerContent}}
) -} +}) export default Modal diff --git a/www/api-reference/components/Navbar/FeedbackModal/index.tsx b/www/api-reference/components/Navbar/FeedbackModal/index.tsx index 1b88d97a4c..b7725a079a 100644 --- a/www/api-reference/components/Navbar/FeedbackModal/index.tsx +++ b/www/api-reference/components/Navbar/FeedbackModal/index.tsx @@ -1,13 +1,18 @@ "use client" import { useModal } from "../../../providers/modal" +import { usePageLoading } from "../../../providers/page-loading" import Button from "../../Button" import DetailedFeedback from "../../DetailedFeedback" const FeedbackModal = () => { const { setModalProps } = useModal() + const { isLoading } = usePageLoading() const openModal = () => { + if (isLoading) { + return + } setModalProps({ title: "Send your Feedback", children: , diff --git a/www/api-reference/components/Navbar/MenuButton/index.tsx b/www/api-reference/components/Navbar/MenuButton/index.tsx index 888994d079..0a43a59cce 100644 --- a/www/api-reference/components/Navbar/MenuButton/index.tsx +++ b/www/api-reference/components/Navbar/MenuButton/index.tsx @@ -5,20 +5,22 @@ import { useSidebar } from "@/providers/sidebar" import IconSidebar from "../../Icons/Sidebar" import clsx from "clsx" import IconXMark from "../../Icons/XMark" +import { usePageLoading } from "../../../providers/page-loading" type NavbarMenuButtonProps = { buttonProps?: NavbarIconButtonProps } const NavbarMenuButton = ({ buttonProps }: NavbarMenuButtonProps) => { - const { items, setMobileSidebarOpen, mobileSidebarOpen } = useSidebar() + const { setMobileSidebarOpen, mobileSidebarOpen } = useSidebar() + const { isLoading } = usePageLoading() return ( { - if (items.top.length !== 0 && items.bottom.length !== 0) { + if (!isLoading) { setMobileSidebarOpen((prevValue) => !prevValue) } }} diff --git a/www/api-reference/components/Navbar/MobileMenu/index.tsx b/www/api-reference/components/Navbar/MobileMenu/index.tsx index a122b584e4..65e059bad8 100644 --- a/www/api-reference/components/Navbar/MobileMenu/index.tsx +++ b/www/api-reference/components/Navbar/MobileMenu/index.tsx @@ -1,33 +1,13 @@ "use client" -import { useCallback, useEffect, useState } from "react" import NavbarMenuButton from "../MenuButton" import NavbarMobileLogo from "../MobileLogo" -import SearchBar from "../../SearchBar" import NavbarColorModeToggle from "../ColorModeToggle" +import SearchModalOpener from "../../Search/ModalOpener" +import { useMobile } from "../../../providers/mobile" const MobileMenu = () => { - const [isMobile, setIsMobile] = useState(false) - - const handleResize = useCallback(() => { - if (window.innerWidth < 1025 && !isMobile) { - setIsMobile(true) - } else if (window.innerWidth >= 1025 && isMobile) { - setIsMobile(false) - } - }, [isMobile]) - - useEffect(() => { - window.addEventListener("resize", handleResize) - - return () => { - window.removeEventListener("resize", handleResize) - } - }, [handleResize]) - - useEffect(() => { - handleResize() - }, []) + const { isMobile } = useMobile() return (
@@ -41,7 +21,7 @@ const MobileMenu = () => { />
- + { return ( @@ -44,8 +42,8 @@ const Navbar = () => {
-
- +
+
diff --git a/www/api-reference/components/Search/EmptyQueryBoundary/index.tsx b/www/api-reference/components/Search/EmptyQueryBoundary/index.tsx new file mode 100644 index 0000000000..0f9b78642c --- /dev/null +++ b/www/api-reference/components/Search/EmptyQueryBoundary/index.tsx @@ -0,0 +1,21 @@ +import { useInstantSearch } from "react-instantsearch" + +type SearchEmptyQueryBoundaryProps = { + children: React.ReactNode + fallback: React.ReactNode +} + +const SearchEmptyQueryBoundary = ({ + children, + fallback, +}: SearchEmptyQueryBoundaryProps) => { + const { indexUiState } = useInstantSearch() + + if (!indexUiState.query) { + return fallback + } + + return children +} + +export default SearchEmptyQueryBoundary diff --git a/www/api-reference/components/Search/Hits/GroupName/index.tsx b/www/api-reference/components/Search/Hits/GroupName/index.tsx new file mode 100644 index 0000000000..a325f9e9a6 --- /dev/null +++ b/www/api-reference/components/Search/Hits/GroupName/index.tsx @@ -0,0 +1,21 @@ +import clsx from "clsx" + +type SearchHitGroupNameProps = { + name: string +} + +const SearchHitGroupName = ({ name }: SearchHitGroupNameProps) => { + return ( + + {name} + + ) +} + +export default SearchHitGroupName diff --git a/www/api-reference/components/Search/Hits/index.tsx b/www/api-reference/components/Search/Hits/index.tsx new file mode 100644 index 0000000000..4d4acd4d6d --- /dev/null +++ b/www/api-reference/components/Search/Hits/index.tsx @@ -0,0 +1,209 @@ +import clsx from "clsx" +import Link from "next/link" +import { Fragment, useEffect, useMemo, useState } from "react" +import { + Configure, + ConfigureProps, + Index, + Snippet, + useHits, + useInstantSearch, +} from "react-instantsearch" +import SearchNoResult from "../NoResults" +import SearchHitGroupName from "./GroupName" +import getBaseUrl from "../../../utils/get-base-url" +import { useSearch } from "../../../providers/search" + +type Hierarchy = "lvl0" | "lvl1" | "lvl2" | "lvl3" | "lvl4" | "lvl5" + +export type HitType = { + hierarchy: { + lvl0: string | null + lvl1: string | null + lvl2: string | null + lvl3: string | null + lvl4: string | null + lvl5: string | null + } + _tags: string[] + url: string + type?: "lvl1" | "lvl2" | "lvl3" | "lvl4" | "lvl5" | "content" + content?: string + __position: number + __queryID?: string + objectID: string +} + +type GroupedHitType = { + [k: string]: HitType[] +} + +type SearchHitWrapperProps = { + configureProps: ConfigureProps +} + +type IndexResults = { + [k: string]: boolean +} + +const SearchHitsWrapper = ({ configureProps }: SearchHitWrapperProps) => { + const { status } = useInstantSearch() + const indices = useMemo( + () => [ + process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp", + process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", + ], + [] + ) + const [hasNoResults, setHashNoResults] = useState({ + [indices[0]]: false, + [indices[1]]: false, + }) + const showNoResults = useMemo(() => { + return Object.values(hasNoResults).every((value) => value === true) + }, [hasNoResults]) + + const setNoResults = (index: string, value: boolean) => { + setHashNoResults((prev: IndexResults) => ({ + ...prev, + [index]: value, + })) + } + + return ( +
+ {status !== "loading" && showNoResults && } + {indices.map((indexName, index) => ( + + + + + ))} +
+ ) +} + +type SearchHitsProps = { + indexName: string + setNoResults: (index: string, value: boolean) => void +} + +const SearchHits = ({ indexName, setNoResults }: SearchHitsProps) => { + const { hits } = useHits() + const { status } = useInstantSearch() + const { setIsOpen } = useSearch() + + // group by lvl0 + const grouped = useMemo(() => { + const grouped: GroupedHitType = {} + hits.forEach((hit) => { + if (hit.hierarchy.lvl0) { + if (!grouped[hit.hierarchy.lvl0]) { + grouped[hit.hierarchy.lvl0] = [] + } + grouped[hit.hierarchy.lvl0].push(hit) + } + }) + + return grouped + }, [hits]) + + useEffect(() => { + if (status !== "loading" && status !== "stalled") { + setNoResults(indexName, hits.length === 0) + } + }, [hits, status]) + + const getLastAvailableHeirarchy = (item: HitType) => { + return ( + Object.keys(item.hierarchy) + .reverse() + .find((key) => item.hierarchy[key as Hierarchy] !== null) || "" + ) + } + + const baseUrl = useMemo(() => getBaseUrl(), []) + + const checkIfInternal = (url: string): boolean => { + const testRegex = new RegExp(`^${baseUrl}/api/(admin|store)`) + return testRegex.test(url) + } + + return ( +
+ {Object.keys(grouped).map((groupName, index) => ( + + + {grouped[groupName].map((item, index) => ( +
{ + const target = e.target as Element + if (target.tagName.toLowerCase() === "div") { + target.querySelector("a")?.click() + } + }} + > + + + + {item.type !== "lvl1" && ( + + + + )} + { + if (checkIfInternal(item.url)) { + e.preventDefault() + window.location.href = item.url + setIsOpen(false) + } + }} + /> +
+ ))} +
+ ))} +
+ ) +} + +export default SearchHitsWrapper diff --git a/www/api-reference/components/Search/Modal/index.tsx b/www/api-reference/components/Search/Modal/index.tsx new file mode 100644 index 0000000000..1b59140237 --- /dev/null +++ b/www/api-reference/components/Search/Modal/index.tsx @@ -0,0 +1,333 @@ +"use client" + +import React, { useEffect, useMemo, useRef, useState } from "react" +import algoliasearch, { SearchClient } from "algoliasearch/lite" +import { InstantSearch, SearchBox } from "react-instantsearch" +import Modal from "../../Modal" +import clsx from "clsx" +import IconMagnifyingGlass from "../../Icons/MagnifyingGlass" +import IconXMark from "../../Icons/XMark" +import SearchEmptyQueryBoundary from "../EmptyQueryBoundary" +import SearchSuggestions from "../Suggestions" +import { useSearch } from "../../../providers/search" +import checkArraySameElms from "../../../utils/array-same-elms" +import SearchHitsWrapper from "../Hits" +import Button from "../../Button" +import Kbd from "../../MDXComponents/Kbd" +import { OptionType } from "../../../hooks/use-select" +import SelectBadge from "../../Select/Badge" +import useKeyboardShortcut from "../../../hooks/use-keyboard-shortcut" +import { findNextSibling, findPrevSibling } from "../../../utils/dom-utils" + +const algoliaClient = algoliasearch( + process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || "temp", + process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp" +) + +const searchClient: SearchClient = { + ...algoliaClient, + async search(requests) { + if (requests.every(({ params }) => !params?.query)) { + return Promise.resolve({ + results: requests.map(() => ({ + hits: [], + nbHits: 0, + nbPages: 0, + page: 0, + processingTimeMS: 0, + hitsPerPage: 0, + exhaustiveNbHits: false, + query: "", + params: "", + })), + }) + } + + return algoliaClient.search(requests) + }, +} + +const SearchModal = () => { + const modalRef = useRef(null) + const options: OptionType[] = useMemo(() => { + return [ + { + value: "admin", + label: "Admin API", + }, + { + value: "store", + label: "Store API", + }, + { + value: "docs", + label: "Docs", + }, + { + value: "user-guide", + label: "User Guide", + }, + { + value: "plugins", + label: "Plugins", + }, + { + value: "reference", + label: "References", + }, + { + value: "ui", + label: "UI", + }, + ] + }, []) + const { isOpen, setIsOpen, defaultFilters } = useSearch() + const [filters, setFilters] = useState(defaultFilters) + const formattedFilters: string = useMemo(() => { + let formatted = "" + filters.forEach((filter) => { + const split = filter.split("_") + split.forEach((f) => { + if (formatted.length) { + formatted += " OR " + } + formatted += `_tags:${f}` + }) + }) + return formatted + }, [filters]) + const searchBoxRef = useRef(null) + + const focusSearchInput = () => + searchBoxRef.current?.querySelector("input")?.focus() + + useEffect(() => { + if (!checkArraySameElms(defaultFilters, filters)) { + setFilters(defaultFilters) + } + }, [defaultFilters]) + + useEffect(() => { + if (isOpen && searchBoxRef.current) { + focusSearchInput() + } + }, [isOpen]) + + const handleKeyAction = (e: KeyboardEvent) => { + if (!isOpen) { + return + } + e.preventDefault() + const focusedItem = modalRef.current?.querySelector(":focus") as HTMLElement + if (!focusedItem) { + // focus the first data-hit + const nextItem = modalRef.current?.querySelector( + "[data-hit]" + ) as HTMLElement + nextItem?.focus() + return + } + + const isHit = focusedItem.hasAttribute("data-hit") + const isInput = focusedItem.tagName.toLowerCase() === "input" + + if (!isHit && !isInput) { + // ignore if focused items aren't input/data-hit + return + } + + const lowerPressedKey = e.key.toLowerCase() + + if (lowerPressedKey === "enter") { + if (isHit) { + // trigger click event of the focused element + focusedItem.click() + } + return + } + + if (lowerPressedKey === "arrowup") { + // only hit items has action on arrow up + if (isHit) { + // find if there's a data-hit item before this one + const beforeItem = findPrevSibling(focusedItem, "[data-hit]") + if (!beforeItem) { + // focus the input + focusSearchInput() + } else { + // focus the previous item + beforeItem.focus() + } + } + } else if (lowerPressedKey === "arrowdown") { + // check if item is input or hit + if (isInput) { + // go to the first data-hit item + const nextItem = modalRef.current?.querySelector( + "[data-hit]" + ) as HTMLElement + nextItem?.focus() + } else { + // handle go down for hit items + // find if there's a data-hit item after this one + const afterItem = findNextSibling(focusedItem, "[data-hit]") + if (afterItem) { + // focus the next item + afterItem.focus() + } + } + } + } + + useKeyboardShortcut({ + metakey: false, + shortcutKeys: ["ArrowUp", "ArrowDown", "Enter"], + action: handleKeyAction, + checkEditing: false, + preventDefault: false, + }) + + return ( + setIsOpen(false)} + ref={modalRef} + > + +
+ ( + + )} + resetIconComponent={() => ( + + )} + placeholder="Find something..." + autoFocus + formRef={searchBoxRef} + /> + +
+
+ }> + + +
+
+
+ + 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(options.map((option) => option.value)) + } + }} + /> +
+
+ + Navigation + + + ↑ + ↓ + +
+
+ + Open Result + + ↵ +
+
+
+
+ ) +} + +export default SearchModal diff --git a/www/api-reference/components/Search/ModalOpener/index.tsx b/www/api-reference/components/Search/ModalOpener/index.tsx new file mode 100644 index 0000000000..55d7cb34f9 --- /dev/null +++ b/www/api-reference/components/Search/ModalOpener/index.tsx @@ -0,0 +1,84 @@ +"use client" + +import clsx from "clsx" +import IconMagnifyingGlass from "../../Icons/MagnifyingGlass" +import InputText from "../../Input/Text" +import { MouseEvent, useMemo } from "react" +import Kbd from "../../MDXComponents/Kbd" +import { useSearch } from "../../../providers/search" +import { useMobile } from "../../../providers/mobile" +import Button from "../../Button" +import { usePageLoading } from "../../../providers/page-loading" +import useKeyboardShortcut from "../../../hooks/use-keyboard-shortcut" + +const SearchModalOpener = () => { + const { setIsOpen } = useSearch() + const { isMobile } = useMobile() + const isApple = useMemo(() => { + return typeof navigator !== "undefined" + ? navigator.userAgent.toLowerCase().indexOf("mac") !== 0 + : true + }, []) + const { isLoading } = usePageLoading() + useKeyboardShortcut({ + shortcutKeys: ["k"], + action: () => setIsOpen((prev) => !prev), + }) + + const handleOpen = ( + e: + | MouseEvent + | MouseEvent + | MouseEvent + ) => { + if (isLoading) { + return + } + e.preventDefault() + if ("blur" in e.target && typeof e.target.blur === "function") { + e.target.blur() + } + setIsOpen(true) + } + + return ( + <> + {isMobile && ( + + )} + {!isMobile && ( +
+ + e.target.blur()} + tabIndex={-1} + /> + + {isApple ? "⌘" : "Ctrl"} + K + +
+ )} + + ) +} + +export default SearchModalOpener diff --git a/www/api-reference/components/Search/NoResults/index.tsx b/www/api-reference/components/Search/NoResults/index.tsx new file mode 100644 index 0000000000..980e40cded --- /dev/null +++ b/www/api-reference/components/Search/NoResults/index.tsx @@ -0,0 +1,14 @@ +import IconExclamationCircleSolid from "../../Icons/ExclamationCircleSolid" + +const SearchNoResult = () => { + return ( +
+ + + No results found. Try changing selected filters. + +
+ ) +} + +export default SearchNoResult diff --git a/www/api-reference/components/Search/Suggestions/index.tsx b/www/api-reference/components/Search/Suggestions/index.tsx new file mode 100644 index 0000000000..e2316795b7 --- /dev/null +++ b/www/api-reference/components/Search/Suggestions/index.tsx @@ -0,0 +1,49 @@ +import clsx from "clsx" +import { useInstantSearch } from "react-instantsearch" +import SearchHitGroupName from "../Hits/GroupName" + +const SearchSuggestions = () => { + const { setIndexUiState } = useInstantSearch() + const suggestions = [ + "Authentication", + "Expanding fields", + "Selecting fields", + "Pagination", + "Query parameter types", + ] + return ( +
+ + {suggestions.map((suggestion, index) => ( +
+ setIndexUiState({ + query: suggestion, + }) + } + key={index} + tabIndex={index} + data-hit + > + + {suggestion} + +
+ ))} +
+ ) +} + +export default SearchSuggestions diff --git a/www/api-reference/components/SearchBar/index.tsx b/www/api-reference/components/SearchBar/index.tsx deleted file mode 100644 index c9548ed1ab..0000000000 --- a/www/api-reference/components/SearchBar/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -"use client" - -import { DocSearch } from "@docsearch/react" - -import "@docsearch/css" - -const SearchBar = () => { - return ( - - ) -} - -export default SearchBar diff --git a/www/api-reference/components/Select/Badge/index.tsx b/www/api-reference/components/Select/Badge/index.tsx new file mode 100644 index 0000000000..720b8cd570 --- /dev/null +++ b/www/api-reference/components/Select/Badge/index.tsx @@ -0,0 +1,135 @@ +import { useCallback, useRef, useState } from "react" +import useSelect from "../../../hooks/use-select" +import clsx from "clsx" +import SelectDropdown from "../Dropdown" +import { SelectProps } from "../types" + +const SelectBadge = ({ + value, + options, + setSelected, + addSelected, + removeSelected, + multiple, + className, + addAll = multiple, + handleAddAll, + ...props +}: SelectProps) => { + const [open, setOpen] = useState(false) + const ref = useRef(null) + const dropdownRef = useRef(null) + const { isValueSelected, isAllSelected, handleChange, handleSelectAll } = + useSelect({ + value, + options, + multiple, + setSelected, + removeSelected, + addSelected, + handleAddAll, + }) + + const getSelectedText = useCallback(() => { + let str = "" + const selectedOptions = options.filter((option) => + value.includes(option.value) + ) + + if (isAllSelected) { + str = "All Areas" + } else { + if ( + (!Array.isArray(value) && !value) || + (Array.isArray(value) && !value.length) + ) { + str = "None selected" + } else { + str = selectedOptions[0].label + } + } + + return ( + <> + + {str} + + {!isAllSelected && selectedOptions.length > 1 && ( + + {" "} + + {selectedOptions.length} + + )} + + ) + }, [isAllSelected, options, value]) + + return ( +
+
{ + if (!dropdownRef.current?.contains(e.target as Element)) { + setOpen((prev) => !prev) + } + }} + > + + Show results from:{" "} + + {getSelectedText()} +
+ + +
+ ) +} + +export default SelectBadge diff --git a/www/api-reference/components/Select/Dropdown/index.tsx b/www/api-reference/components/Select/Dropdown/index.tsx new file mode 100644 index 0000000000..a21e842edd --- /dev/null +++ b/www/api-reference/components/Select/Dropdown/index.tsx @@ -0,0 +1,151 @@ +import clsx from "clsx" +import IconCheckMini from "../../Icons/CheckMini" +import IconEllipseMiniSolid from "../../Icons/EllipseMiniSolid" +import { OptionType } from "../../../hooks/use-select" +import { forwardRef, useCallback, useEffect, useRef } from "react" + +type SelectDropdownProps = { + options: OptionType[] + open: boolean + setOpen: React.Dispatch> + addAll?: boolean + multiple?: boolean + isAllSelected: boolean + isValueSelected: (val: string) => boolean + handleSelectAll: () => void + handleChange?: (selectedValue: string, wasSelected: boolean) => void + parentRef?: React.RefObject + className?: string +} + +const SelectDropdown = forwardRef( + function SelectDropdown( + { + open, + setOpen, + options, + addAll, + multiple = false, + isAllSelected, + isValueSelected, + handleSelectAll, + handleChange: handleSelectChange, + parentRef, + className, + }, + passedRef + ) { + const ref = useRef(null) + const setRefs = useCallback( + (node: HTMLDivElement) => { + // Ref's from useRef needs to have the node assigned to `current` + ref.current = node + if (typeof passedRef === "function") { + passedRef(node) + } else if (passedRef && "current" in passedRef) { + passedRef.current = node + } + }, + [passedRef] + ) + + const handleChange = (clickedValue: string, wasSelected: boolean) => { + handleSelectChange?.(clickedValue, wasSelected) + if (!multiple) { + setOpen(false) + } + } + + const handleOutsideClick = useCallback( + (e: MouseEvent) => { + if ( + open && + !ref.current?.contains(e.target as Element) && + !parentRef?.current?.contains(e.target as Element) + ) { + setOpen(false) + } + }, + [open, parentRef, setOpen] + ) + + useEffect(() => { + document.body.addEventListener("click", handleOutsideClick) + + return () => { + document.body.removeEventListener("click", handleOutsideClick) + } + }, [handleOutsideClick]) + + const getSelectOption = (option: OptionType, index: number) => { + const isSelected = option.isAllOption + ? isAllSelected + : isValueSelected(option.value) + + return ( +
  • svg]:left-0.75 cursor-pointer [&>svg]:absolute [&>svg]:top-0.5", + !isSelected && "text-compact-small", + isSelected && "text-compact-small-plus" + )} + onClick={() => { + if (option.isAllOption) { + handleSelectAll() + } else { + handleChange(option.value, isSelected) + } + }} + > + {isSelected && ( + <> + {multiple && ( + + )} + {!multiple && ( + + )} + + )} + {option.label} +
  • + ) + } + + return ( +
    +
      + {addAll && + getSelectOption( + { + value: "all", + label: "All Areas", + isAllOption: true, + }, + -1 + )} + {options.map(getSelectOption)} +
    +
    + ) + } +) + +export default SelectDropdown diff --git a/www/api-reference/components/Select/Input/index.tsx b/www/api-reference/components/Select/Input/index.tsx new file mode 100644 index 0000000000..721adba8b9 --- /dev/null +++ b/www/api-reference/components/Select/Input/index.tsx @@ -0,0 +1,127 @@ +import clsx from "clsx" +import IconChevronUpDown from "../../Icons/ChevronUpDown" +import { useRef, useState } from "react" +import Badge from "../../Badge" +import IconXMarkMini from "../../Icons/XMarkMini" +import useSelect from "../../../hooks/use-select" +import SelectDropdown from "../Dropdown" +import { SelectProps } from "../types" + +const SelectInput = ({ + value, + options, + setSelected, + addSelected, + removeSelected, + multiple, + className, + addAll = multiple, + handleAddAll, + showClearButton = true, + ...props +}: SelectProps) => { + const [open, setOpen] = useState(false) + const ref = useRef(null) + const dropdownRef = useRef(null) + const { + isValueSelected, + hasSelectedValue, + hasSelectedValues, + selectedValues, + isAllSelected, + handleChange, + handleSelectAll, + } = useSelect({ + value, + options, + multiple, + setSelected, + removeSelected, + addSelected, + handleAddAll, + }) + + return ( +
    { + if (!dropdownRef.current?.contains(e.target as Element)) { + setOpen((prev) => !prev) + } + }} + > + {hasSelectedValues && ( + + + {(value as string[]).length} + + {showClearButton && ( + { + e.stopPropagation() + setSelected?.([]) + }} + /> + )} + + )} + + {!multiple && hasSelectedValue && selectedValues.length + ? selectedValues[0].label + : props.placeholder} + + + + +
    + ) +} + +export default SelectInput diff --git a/www/api-reference/components/Select/types.ts b/www/api-reference/components/Select/types.ts new file mode 100644 index 0000000000..85369f2bd0 --- /dev/null +++ b/www/api-reference/components/Select/types.ts @@ -0,0 +1,9 @@ +import { OptionType, SelectOptions } from "../../hooks/use-select" + +export type SelectProps = { + options: OptionType[] + multiple?: boolean + addAll?: boolean + showClearButton?: boolean +} & SelectOptions & + React.ComponentProps<"input"> diff --git a/www/api-reference/components/TextArea/index.tsx b/www/api-reference/components/TextArea/index.tsx index d5fcaa471e..069ae76cb0 100644 --- a/www/api-reference/components/TextArea/index.tsx +++ b/www/api-reference/components/TextArea/index.tsx @@ -14,7 +14,7 @@ const TextArea = (props: TextAreaProps) => { {...props} className={clsx( "bg-medusa-bg-field dark:bg-medusa-bg-field-dark shadow-button-secondary dark:shadow-button-secondary-dark", - "border-medusa-border-loud-muted dark:border-medusa-border-loud-muted-dark rounded-sm border border-solid", + "border-medusa-border-base dark:border-medusa-border-base-dark rounded-sm border border-solid", "pt-0.4 px-0.75 text-medium font-base pb-[9px]", "hover:bg-medusa-bg-field-hover dark:hover:bg-medusa-bg-field-hover-dark", "focus:border-medusa-border-interactive dark:focus:border-medusa-border-interactive-dark", diff --git a/www/api-reference/css/code-theme.css b/www/api-reference/css/code-theme.css deleted file mode 100644 index d484e515b5..0000000000 --- a/www/api-reference/css/code-theme.css +++ /dev/null @@ -1,313 +0,0 @@ -/** VS Code Dark Theme: https://github.com/PrismJS/prism-themes/blob/master/themes/prism-vsc-dark-plus.css **/ - -/* .section-content pre { - @apply rounded border border-transparent dark:border-medusa-code-border; -} */ - -/* .code-block { - @apply relative; - @apply xs:after:content-[''] xs:after:rounded xs:after:absolute xs:after:right-0 xs:after:top-0 xs:after:w-[calc(10%+24px)] xs:after:h-full xs:after:bg-code-fade; -} */ - -/* .prism-code { - @apply relative xs:max-w-[90%]; -} - -.prism-code *::selection, .code-header::selection { - @apply bg-medusa-code-text-highlight; -} - -.prism-code:not(:hover)::-webkit-scrollbar-thumb, -.prism-code:not(:hover)::-webkit-scrollbar-track { - @apply xs:invisible; -} - -.prism-code:hover::-webkit-scrollbar-thumb, -.prism-code:hover::-webkit-scrollbar-track { - @apply xs:opacity-100 -} */ - -/* .prism-code { - @apply bg-transparent break-words !outline-none; -} - -.prism-code div { - @apply !outline-none focus:!outline-none active:!outline-none; -} */ - -/* pre[class*="language-"], -code[class*="language-"] { - color: #d4d4d4; - font-size: 13px; - text-shadow: none; - font-family: Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace; - direction: ltr; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - line-height: 1.5; - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; - position: relative; -} - -pre[class*="language-"]::selection, -code[class*="language-"]::selection, -pre[class*="language-"] *::selection, -code[class*="language-"] *::selection { - text-shadow: none; - background: #264F78; -} - -@media print { - pre[class*="language-"], - code[class*="language-"] { - text-shadow: none; - } -} - -pre[class*="language-"] { - padding: 1em; - margin: .5em 0; - overflow: auto; - background: #1e1e1e; -} - -:not(pre) > code[class*="language-"] { - padding: .1em .3em; - border-radius: .3em; - color: #db4c69; - background: #1e1e1e; -} */ -/********************************************************* -* Tokens -*/ -/* .namespace { - opacity: .7; -} - -.token.doctype .token.doctype-tag { - color: #569CD6; -} - -.token.doctype .token.name { - color: #9cdcfe; -} - -.token.comment, -.token.prolog { - color: #6a9955; -} - -.token.punctuation, -.language-html .language-css .token.punctuation, -.language-html .language-javascript .token.punctuation { - color: #d4d4d4; -} - -.token.property, -.token.tag, -.token.boolean, -.token.number, -.token.constant, -.token.symbol, -.token.inserted, -.token.unit { - color: #b5cea8; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.char, -.token.builtin, -.token.deleted { - color: #ce9178; -} - -.language-css .token.string.url { - text-decoration: underline; -} - -.token.operator, -.token.entity { - color: #d4d4d4; -} - -.token.operator.arrow { - color: #569CD6; -} - -.token.atrule { - color: #ce9178; -} - -.token.atrule .token.rule { - color: #c586c0; -} - -.token.atrule .token.url { - color: #9cdcfe; -} - -.token.atrule .token.url .token.function { - color: #dcdcaa; -} - -.token.atrule .token.url .token.punctuation { - color: #d4d4d4; -} - -.token.keyword { - color: #569CD6; -} - -.token.keyword.module, -.token.keyword.control-flow { - color: #c586c0; -} - -.token.function, -.token.function .token.maybe-class-name { - color: #dcdcaa; -} - -.token.regex { - color: #d16969; -} - -.token.important { - color: #569cd6; -} - -.token.italic { - font-style: italic; -} - -.token.constant { - color: #9cdcfe; -} - -.token.class-name, -.token.maybe-class-name { - color: #4ec9b0; -} - -.token.console { - color: #9cdcfe; -} - -.token.parameter { - color: #9cdcfe; -} - -.token.interpolation { - color: #9cdcfe; -} - -.token.punctuation.interpolation-punctuation { - color: #569cd6; -} - -.token.boolean { - color: #569cd6; -} - -.token.property, -.token.variable, -.token.imports .token.maybe-class-name, -.token.exports .token.maybe-class-name { - color: #9cdcfe; -} - -.token.selector { - color: #d7ba7d; -} - -.token.escape { - color: #d7ba7d; -} - -.token.tag { - color: #569cd6; -} - -.token.tag .token.punctuation { - color: #808080; -} - -.token.cdata { - color: #808080; -} - -.token.attr-name { - color: #9cdcfe; -} - -.token.attr-value, -.token.attr-value .token.punctuation { - color: #ce9178; -} - -.token.attr-value .token.punctuation.attr-equals { - color: #d4d4d4; -} - -.token.entity { - color: #569cd6; -} - -.token.namespace { - color: #4ec9b0; -} */ -/********************************************************* -* Language Specific -*/ - -/* pre[class*="language-javascript"], -code[class*="language-javascript"], -pre[class*="language-jsx"], -code[class*="language-jsx"], -pre[class*="language-typescript"], -code[class*="language-typescript"], -pre[class*="language-tsx"], -code[class*="language-tsx"] { - color: #9cdcfe; -} - -pre[class*="language-css"], -code[class*="language-css"] { - color: #ce9178; -} - -pre[class*="language-html"], -code[class*="language-html"] { - color: #d4d4d4; -} - -.language-regex .token.anchor { - color: #dcdcaa; -} - -.language-html .token.punctuation { - color: #808080; -} */ -/********************************************************* -* Line highlighting -*/ -/* pre[class*="language-"] > code[class*="language-"] { - position: relative; - z-index: 1; -} - -.line-highlight.line-highlight { - background: #f7ebc6; - box-shadow: inset 5px 0 0 #f7d87c; - z-index: 0; -} */ \ No newline at end of file diff --git a/www/api-reference/css/docsearch.css b/www/api-reference/css/docsearch.css deleted file mode 100644 index 09c94bd596..0000000000 --- a/www/api-reference/css/docsearch.css +++ /dev/null @@ -1,232 +0,0 @@ -.DocSearch-Modal { - @apply border border-solid !border-medusa-border-base dark:!border-medusa-border-base-dark; - @apply !rounded !relative; - @apply md:!m-[unset] md:w-[560px]; -} - -.DocSearch-SearchBar { - @apply !p-0; -} - -.DocSearch-Form { - --docsearch-spacing: theme(margin[1.5]); - --docsearch-searchbox-height: 56px; - --docsearch-searchbox-focus-background: var(--docsearch-modal-background) !important; - - @apply !rounded-t !rounded-b-none border-0 border-b border-solid !border-medusa-border-base dark:!border-medusa-border-base-dark; -} - -.DocSearch-LoadingIndicator svg, .DocSearch-MagnifierLabel svg { - @apply !w-[20px] !h-[20px]; -} - -.DocSearch-Input { - @apply !text-compact-large lg:!text-medium !pl-1; - @apply placeholder:text-medusa-fg-muted dark:placeholder:text-medusa-fg-muted-dark; -} - -.DocSearch-Dropdown { - @apply !pt-0 !pb-2.5 !px-0; - @apply !max-h-[416px]; -} - -.DocSearch-Hit-source { - @apply !m-0 !text-compact-small-plus uppercase text-medusa-fg-muted dark:text-medusa-fg-muted-dark; - @apply border-0 border-b border-solid border-medusa-border-base dark:border-medusa-border-base-dark; - @apply !py-[10px] !px-1.5; -} - -.DocSearch-Footer { - @apply !hidden; -} - -.DocSearch-Hit { - @apply !p-0; -} - -.DocSearch-Hit:not(.DocSearch-Hit--Child) .DocSearch-Hit-icon { - @apply !w-2.5 !h-2.5 p-0.125 border border-solid border-medusa-border-strong dark:border-medusa-border-strong-dark; - @apply rounded flex justify-center items-center; - @apply before:content-[''] before:w-2 before:h-2 before:bg-no-repeat before:bg-center before:bg-contain before:bg-search-hit dark:before:bg-search-hit-dark; -} - -.DocSearch-Hit-icon svg { - @apply hidden; -} - -.DocSearch-Hit--Child .DocSearch-Hit-icon { - @apply hidden; -} - -.DocSearch-Hit a { - @apply !py-0.75 !px-1.5 !shadow-none !rounded-none; -} - -.DocSearch-Hit:not(:last-of-type) a { - @apply border-0 border-b border-solid border-medusa-border-base dark:border-medusa-border-base-dark; -} - -.DocSearch-Hit-content-wrapper { - @apply !mt-0 !mx-1; -} - -.DocSearch-Hit-title, -.DocSearch-Hit-title mark { - @apply !text-medusa-fg-base dark:!text-medusa-fg-base-dark; - @apply !text-compact-small-plus; -} - -.DocSearch-Hit-path { - @apply !text-medusa-fg-subtle dark:!text-medusa-fg-subtle-dark !text-compact-small; -} - -.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-path { - --docsearch-hit-active-color: theme(colors.medusa.fg.subtle.DEFAULT); -} - -html[data-theme="dark"] .DocSearch-Hit[aria-selected=true] .DocSearch-Hit-path { - --docsearch-hit-active-color: theme(colors.medusa.fg.subtle.dark); -} - -.DocSearch-Hit[aria-selected=true] a { - @apply !bg-medusa-bg-base-hover dark:!bg-medusa-bg-base-hover-dark; -} - -.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-content-wrapper + .DocSearch-Hit-action:last-child { - @apply h-1.5 w-1.5 border border-solid border-medusa-border-strong dark:border-medusa-border-strong-dark; - @apply rounded bg-medusa-bg-base dark:bg-medusa-bg-base-dark p-0.125 flex justify-center items-center; - @apply before:content-[''] before:w-[20px] before:h-[20px] before:bg-search-arrow dark:before:bg-search-arrow-dark before:bg-no-repeat before:bg-center; -} - -.DocSearch-Hit-content-wrapper + .DocSearch-Hit-action:last-child svg, -.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-content-wrapper + .DocSearch-Hit-action:last-child svg { - @apply hidden; -} - -.DocSearch-Hit[aria-selected=true] mark { - @apply !no-underline; -} - -.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Tree { - @apply text-medusa-border-strong dark:text-medusa-border-strong-dark; -} - -.DocSearch-Hit-Tree { - @apply text-medusa-border-base dark:text-medusa-border-base-dark; -} - -.DocSearch-HitsFooter { - @apply z-[100] absolute bottom-0 left-0 w-full h-2.5 !bg-medusa-bg-base dark:!bg-medusa-bg-base-dark; - @apply border-0 border-t border-solid border-medusa-border-base dark:border-medusa-border-base-dark; -} - -.DocSearch-HitsFooter a { - @apply !border-b-0; -} - -.DocSearch-Reset { - @apply !rounded transition-all duration-200 ease-ease; - @apply hover:bg-medusa-bg-base-hover dark:hover:bg-medusa-bg-base-hover-dark; -} - -.DocSearch-NoResults .DocSearch-Screen-Icon { - @apply w-[20px] h-[20px] !p-0 flex justify-center items-center mt-0 mb-1 mx-auto; - @apply before:content-[''] before:w-full before:h-full before:bg-no-repeat before:bg-center before:bg-contain; - @apply before:bg-search-no-result dark:before:bg-search-no-result-dark; -} - -.DocSearch-NoResults .DocSearch-Screen-Icon svg { - @apply hidden; -} - -.DocSearch-NoResults .DocSearch-Title { - @apply !text-compact-small !pl-1; -} - -.DocSearch-NoResults-Prefill-List { - @apply text-center !text-compact-small-plus !pl-1; -} - -.DocSearch-NoResults-Prefill-List li::marker { - @apply content-['']; -} - -.DocSearch-NoResults-Prefill-List li button { - @apply text-medusa-fg-base dark:text-medusa-fg-base-dark; -} -.DocSearch-Button { - @apply w-full !h-full !rounded lg:!border lg:!border-solid lg:!border-medusa-border-loud-muted lg:dark:!border-medusa-border-base-dark border-0; - @apply lg:!bg-medusa-bg-field lg:dark:!bg-medusa-bg-field-dark !bg-transparent; - @apply lg:hover:!bg-medusa-bg-field-hover lg:dark:hover:!bg-medusa-bg-field-hover-dark; - @apply lg:disabled:!bg-medusa-bg-disabled lg:dark:disabled:!bg-medusa-bg-disabled-dark; - @apply p-0 lg:!py-[5px] lg:!px-0.5 relative ml-0; - @apply hover:!border-medusa-border-loud-muted hover:dark:!border-medusa-border-loud-muted-dark; - @apply active:!border-medusa-border-interactive active:dark:!border-medusa-border-interactive-dark; - @apply focus:!border-medusa-border-interactive focus:dark:!border-medusa-border-interactive-dark; - @apply disabled:!border-medusa-border-base disabled:dark:!border-medusa-border-base-dark; - @apply lg:!shadow-button-secondary lg:dark:!shadow-button-secondary-dark; -} - -.DocSearch-Container { - @apply !z-[1001] md:flex md:justify-center md:items-center; -} - -.DocSearch-Button .DocSearch-Search-Icon { - @apply invisible; -} - -.DocSearch-Button-Container { - @apply before:content-[''] before:h-[20px] before:w-[20px] before:absolute before:left-0.5 before:top-[5px]; - @apply before:bg-magnifying-glass dark:before:bg-magnifying-glass-dark before:bg-no-repeat; -} - -.DocSearch-Button-Placeholder { - @apply text-medusa-fg-muted dark:text-medusa-fg-muted-dark; - @apply !pl-0.5 !text-compact-small lg:!block !hidden; -} - -.DocSearch-Button-Keys { - @apply w-fit !min-w-[unset] lg:!flex !hidden; -} - -.DocSearch-Button-Key { - @apply !shadow-none !rounded-md !text-compact-x-small-plus !font-base align-middle !p-0.25; - @apply !border !border-solid !border-medusa-tag-neutral-border dark:!border-medusa-tag-neutral-border-dark; - @apply [&span]:hidden [&:not(:last-child)]:!mr-0.25 last:!mr-0; -} - -[class*=searchBox] { - @apply lg:w-[280px] lg:max-w-[280px] lg:!h-2 lg:!p-0; -} - - -:root { - --docsearch-searchbox-background: theme(colors.medusa.bg.field.DEFAULT) !important; - --docsearch-searchbox-focus-background: theme(colors.medusa.bg.field.hover.DEFAULT) !important; - --docsearch-searchbox-shadow: none !important; - --docsearch-modal-height: 472px !important; - --docsearch-modal-background: theme(colors.medusa.bg.base.DEFAULT) !important; - --docsearch-modal-shadow: theme(boxShadow.modal) !important; - --docsearch-container-background: theme(colors.medusa.bg.overlay.DEFAULT) !important; - --docsearch-key-gradient: theme(colors.medusa.tag.neutral.bg.DEFAULT) !important; - --docsearch-muted-color: theme(colors.medusa.tag.neutral.text.DEFAULT) !important; - --docsearch-spacing: 12px theme(spacing[1.5]) !important; - --docsearch-highlight-color: theme(colors.medusa.fg.muted.DEFAULT) !important; - --docsearch-text-color: theme(colors.medusa.fg.base.DEFAULT) !important; - --docsearch-hit-background: var(--docsearch-modal-background) !important; - --docsearch-hit-height: auto !important; - --docsearch-hit-active-color: var(--docsearch-text-color) !important; - --docsearch-footer-height: 40px !important; -} - -html[data-theme="dark"] { - --docsearch-searchbox-background: theme(colors.medusa.bg.field.dark) !important; - --docsearch-searchbox-focus-background: theme(colors.medusa.bg.field.hover.dark) !important; - --docsearch-modal-background: theme(colors.medusa.bg.base.dark) !important; - --docsearch-modal-shadow: theme(boxShadow.modal-dark) !important; - --docsearch-container-background: theme(colors.medusa.bg.overlay.dark) !important; - --docsearch-key-gradient: theme(colors.medusa.tag.neutral.bg.dark) !important; - --docsearch-muted-color: theme(colors.medusa.tag.neutral.text.dark) !important; - --docsearch-highlight-color: theme(colors.medusa.fg.muted.dark) !important; - --docsearch-text-color: theme(colors.medusa.fg.base.dark) !important; -} \ No newline at end of file diff --git a/www/api-reference/css/globals.css b/www/api-reference/css/globals.css index 49d74e8737..202121129b 100644 --- a/www/api-reference/css/globals.css +++ b/www/api-reference/css/globals.css @@ -47,8 +47,13 @@ @apply !bg-medusa-code-text-highlight; } - body[data-modal="opened"] { - overflow: hidden; + body[data-modal="opened"] { + @apply !overflow-hidden; + } + + mark { + @apply bg-medusa-bg-highlight dark:bg-medusa-bg-highlight-dark; + @apply text-medusa-fg-interactive dark:text-medusa-fg-interactive-dark; } } @@ -243,9 +248,9 @@ @apply inline-flex flex-row justify-center items-center; @apply py-[5px] px-0.75 rounded-sm cursor-pointer; @apply bg-button-neutral bg-medusa-button-neutral dark:bg-button-neutral-dark dark:bg-medusa-button-neutral-dark; - @apply hover:bg-medusa-button-neutral-hover hover:bg-button-neutral-hover dark:hover:bg-medusa-button-neutral-hover-dark dark:hover:bg-button-neutral-hover-dark hover:no-underline; - @apply active:bg-medusa-button-neutral-pressed active:bg-button-neutral-pressed dark:active:bg-medusa-button-neutral-pressed-dark dark:active:bg-button-neutral-pressed-dark; - @apply focus:bg-medusa-button-neutral-pressed focus:bg-button-neutral-pressed dark:focus:bg-medusa-button-neutral-pressed-dark dark:focus:bg-button-neutral-pressed-dark; + @apply hover:bg-medusa-button-neutral-hover hover:bg-no-image dark:hover:bg-medusa-button-neutral-hover-dark hover:no-underline; + @apply active:bg-medusa-button-neutral-pressed active:bg-no-image dark:active:bg-medusa-button-neutral-pressed-dark; + @apply focus:bg-medusa-button-neutral-pressed focus:bg-no-image dark:focus:bg-medusa-button-neutral-pressed-dark; @apply disabled:!bg-no-image disabled:bg-medusa-bg-disabled dark:disabled:bg-medusa-bg-disabled-dark; @apply disabled:cursor-not-allowed; @apply border border-solid border-medusa-border-base dark:border-medusa-border-base-dark; @@ -263,12 +268,12 @@ @apply inline-flex flex-row justify-center items-center; @apply py-[5px] px-0.75 rounded-sm cursor-pointer; @apply bg-button-inverted bg-medusa-button-inverted dark:bg-button-inverted-dark dark:bg-medusa-button-inverted-dark; - @apply hover:bg-medusa-button-inverted-hover hover:bg-button-inverted-hover dark:hover:bg-medusa-button-inverted-hover-dark dark:hover:bg-button-inverted-hover-dark hover:no-underline; - @apply active:bg-medusa-button-inverted-pressed active:bg-button-inverted-pressed dark:active:bg-medusa-button-inverted-pressed-dark dark:active:bg-button-inverted-pressed-dark; - @apply focus:bg-medusa-button-inverted-pressed focus:bg-button-inverted-pressed dark:focus:bg-medusa-button-inverted-pressed-dark dark:focus:bg-button-inverted-pressed-dark; + @apply hover:bg-medusa-button-inverted-hover hover:bg-no-image dark:hover:bg-medusa-button-inverted-hover-dark hover:no-underline; + @apply active:bg-medusa-button-inverted-pressed active:bg-no-image dark:active:bg-medusa-button-inverted-pressed-dark; + @apply focus:bg-medusa-button-inverted-pressed focus:bg-no-image dark:focus:bg-medusa-button-inverted-pressed-dark; @apply shadow-button-colored active:shadow-button-colored-focused focus:shadow-button-colored-focused transition-shadow; @apply dark:shadow-button-colored-dark dark:active:shadow-button-colored-focused-dark dark:focus:shadow-button-colored-focused-dark; - @apply disabled:!bg-no-image disabled:bg-medusa-button-disabled dark:disabled:bg-medusa-button-disabled-dark; + @apply disabled:!bg-no-image disabled:bg-medusa-bg-disabled dark:disabled:bg-medusa-bg-disabled-dark; @apply disabled:cursor-not-allowed disabled:border-medusa-border-base dark:disabled:border-medusa-border-base-dark; @apply text-compact-small-plus text-medusa-fg-on-inverted dark:text-medusa-fg-on-inverted-dark; @apply disabled:text-medusa-fg-disabled dark:disabled:text-medusa-fg-disabled-dark; @@ -277,9 +282,8 @@ } .btn-clear { - @apply bg-transparent shadow-none border-0 outline-none; + @apply bg-transparent shadow-none border-0 outline-none cursor-pointer; } } -@import url('./tooltip.css'); -@import url('./docsearch.css'); \ No newline at end of file +@import url('./tooltip.css'); \ No newline at end of file diff --git a/www/api-reference/css/tooltip.css b/www/api-reference/css/tooltip.css index b3b3d50c26..6a6314bda8 100644 --- a/www/api-reference/css/tooltip.css +++ b/www/api-reference/css/tooltip.css @@ -1,13 +1,3 @@ -/* .react-tooltip { - @apply !border !border-solid !border-medusa-border-base dark:!border-medusa-border-base-dark; - @apply !rounded !text-compact-x-small-plus !shadow-tooltip dark:!shadow-tooltip-dark; - @apply !py-0.4 !px-1 lg:block hidden; -} */ - -/* .react-tooltip-arrow { - @apply hidden; -} */ - :root { --rt-opacity: theme(opacity.100) !important; --rt-color-dark: theme(colors.medusa.bg.base.DEFAULT) !important; diff --git a/www/api-reference/hooks/use-keyboard-shortcut.tsx b/www/api-reference/hooks/use-keyboard-shortcut.tsx new file mode 100644 index 0000000000..f90c949815 --- /dev/null +++ b/www/api-reference/hooks/use-keyboard-shortcut.tsx @@ -0,0 +1,70 @@ +import { useCallback, useEffect } from "react" +import { usePageLoading } from "../providers/page-loading" + +type useKeyboardShortcutOptions = { + metakey?: boolean + shortcutKeys: string[] + action: (e: KeyboardEvent) => void + checkEditing?: boolean + preventDefault?: boolean +} + +const useKeyboardShortcut = ({ + metakey = true, + shortcutKeys, + action, + checkEditing = true, + preventDefault = true, +}: useKeyboardShortcutOptions) => { + const { isLoading } = usePageLoading() + + function isEditingContent(event: KeyboardEvent) { + const element = event.target as HTMLElement + const tagName = element.tagName + return ( + element.isContentEditable || + tagName === "INPUT" || + tagName === "SELECT" || + tagName === "TEXTAREA" + ) + } + + const checkKeysPressed = useCallback( + (pressedKey: string) => { + const lowerPressedKey = pressedKey.toLowerCase() + return shortcutKeys.some( + (value) => lowerPressedKey === value.toLowerCase() + ) + }, + [shortcutKeys] + ) + + const sidebarShortcut = useCallback( + (e: KeyboardEvent) => { + if (isLoading) { + return + } + if ( + (!metakey || e.metaKey || e.ctrlKey) && + checkKeysPressed(e.key) && + (!checkEditing || !isEditingContent(e)) + ) { + if (preventDefault) { + e.preventDefault() + } + action(e) + } + }, + [isLoading, metakey, checkKeysPressed, checkEditing, action, preventDefault] + ) + + useEffect(() => { + window.addEventListener("keydown", sidebarShortcut) + + return () => { + window.removeEventListener("keydown", sidebarShortcut) + } + }, [sidebarShortcut]) +} + +export default useKeyboardShortcut diff --git a/www/api-reference/hooks/use-select.tsx b/www/api-reference/hooks/use-select.tsx new file mode 100644 index 0000000000..d685d80db2 --- /dev/null +++ b/www/api-reference/hooks/use-select.tsx @@ -0,0 +1,93 @@ +import { useCallback, useMemo } from "react" + +export type OptionType = { + value: string + label: string + index?: string + isAllOption?: boolean +} + +export type SelectOptions = { + value: string | string[] + multiple?: boolean + options: OptionType[] + setSelected?: (value: string | string[]) => void + addSelected?: (value: string) => void + removeSelected?: (value: string) => void + handleAddAll?: (isAllSelected: boolean) => void +} + +const useSelect = ({ + value, + options, + multiple = false, + setSelected, + addSelected, + removeSelected, + handleAddAll, +}: SelectOptions) => { + const isValueSelected = useCallback( + (val: string) => { + return ( + (typeof value === "string" && val === value) || + (Array.isArray(value) && value.includes(val)) + ) + }, + [value] + ) + + // checks if there are multiple selected values + const hasSelectedValues = useMemo(() => { + return multiple && Array.isArray(value) && value.length > 0 + }, [value, multiple]) + + // checks if there are any selected values, + // whether multiple or one + const hasSelectedValue = useMemo(() => { + return hasSelectedValues || (typeof value === "string" && value.length) + }, [hasSelectedValues, value]) + + const selectedValues: OptionType[] = useMemo(() => { + if (typeof value === "string") { + const selectedValue = options.find((option) => option.value === value) + return selectedValue ? [selectedValue] : [] + } else if (Array.isArray(value)) { + return options.filter((option) => value.includes(option.value)) + } + return [] + }, [options, value]) + + const isAllSelected = useMemo(() => { + return Array.isArray(value) && value.length === options.length + }, [options, value]) + + const handleChange = (selectedValue: string, wasSelected: boolean) => { + if (multiple) { + wasSelected + ? removeSelected?.(selectedValue) + : addSelected?.(selectedValue) + } else { + setSelected?.(selectedValue) + } + } + + const handleSelectAll = () => { + if (handleAddAll) { + handleAddAll(isAllSelected) + } else { + setSelected?.(options.map((option) => option.value)) + } + } + + return { + isValueSelected, + hasSelectedValue, + hasSelectedValues, + selectedValues, + isAllSelected, + handleChange, + handleSelectAll, + } +} + +export default useSelect diff --git a/www/api-reference/package.json b/www/api-reference/package.json index 3e2978962e..61d405a7be 100644 --- a/www/api-reference/package.json +++ b/www/api-reference/package.json @@ -42,6 +42,7 @@ "prism-react-renderer": "^2.0.6", "react": "latest", "react-dom": "latest", + "react-instantsearch": "^7.0.1", "react-intersection-observer": "^9.5.2", "react-tooltip": "^5.19.0", "react-transition-group": "^4.4.5", diff --git a/www/api-reference/providers/area.tsx b/www/api-reference/providers/area.tsx index 214a36e45b..825879549e 100644 --- a/www/api-reference/providers/area.tsx +++ b/www/api-reference/providers/area.tsx @@ -1,7 +1,8 @@ "use client" import type { Area } from "@/types/openapi" -import { createContext, useContext, useState } from "react" +import { createContext, useContext, useEffect, useState } from "react" +import { useSearch } from "./search" type AreaContextType = { area: Area @@ -17,6 +18,13 @@ type AreaProviderProps = { const AreaProvider = ({ area: passedArea, children }: AreaProviderProps) => { const [area, setArea] = useState(passedArea) + const { defaultFilters, setDefaultFilters } = useSearch() + + useEffect(() => { + if (!defaultFilters.includes(area)) { + setDefaultFilters([area]) + } + }, [area, defaultFilters, setDefaultFilters]) return ( (null) + +type MobileProviderProps = { + children: React.ReactNode +} + +const MobileProvider = ({ children }: MobileProviderProps) => { + const [isMobile, setIsMobile] = useState(false) + + const handleResize = useCallback(() => { + if (window.innerWidth < 1025 && !isMobile) { + setIsMobile(true) + } else if (window.innerWidth >= 1025 && isMobile) { + setIsMobile(false) + } + }, [isMobile]) + + useEffect(() => { + window.addEventListener("resize", handleResize) + + return () => { + window.removeEventListener("resize", handleResize) + } + }, [handleResize]) + + useEffect(() => { + handleResize() + }, []) + + return ( + + {children} + + ) +} + +export default MobileProvider + +export const useMobile = () => { + const context = useContext(MobileContext) + + if (!context) { + throw new Error("useMobile must be used within a MobileProvider") + } + + return context +} diff --git a/www/api-reference/providers/modal.tsx b/www/api-reference/providers/modal.tsx index c0d6c2a0a3..a52004696a 100644 --- a/www/api-reference/providers/modal.tsx +++ b/www/api-reference/providers/modal.tsx @@ -1,6 +1,6 @@ "use client" -import React, { useContext, useEffect, useState } from "react" +import React, { useContext, useState } from "react" import { createContext } from "react" import Modal, { ModalProps } from "../components/Modal" @@ -23,14 +23,6 @@ const ModalProvider = ({ children }: ModalProviderProps) => { setModalProps(null) } - useEffect(() => { - if (modalProps) { - document.body.setAttribute("data-modal", "opened") - } else { - document.body.removeAttribute("data-modal") - } - }, [modalProps]) - return ( void +} + +const PageLoadingContext = createContext(null) + +type PageLoadingProviderProps = { + children?: React.ReactNode +} + +const PageLoadingProvider = ({ children }: PageLoadingProviderProps) => { + const [isLoading, setIsLoading] = useState(true) + + return ( + + {children} + + ) +} + +export default PageLoadingProvider + +export const usePageLoading = (): PageLoadingContextType => { + const context = useContext(PageLoadingContext) + + if (!context) { + throw new Error("usePageLoading must be used inside a PageLoadingProvider") + } + + return context +} diff --git a/www/api-reference/providers/search.tsx b/www/api-reference/providers/search.tsx new file mode 100644 index 0000000000..5721630216 --- /dev/null +++ b/www/api-reference/providers/search.tsx @@ -0,0 +1,48 @@ +"use client" + +import { createContext, useContext, useState } from "react" +import SearchModal from "../components/Search/Modal" + +type SearchContextType = { + isOpen: boolean + setIsOpen: React.Dispatch> + defaultFilters: string[] + setDefaultFilters: (value: string[]) => void +} + +const SearchContext = createContext(null) + +type SearchProviderProps = { + children: React.ReactNode +} + +const SearchProvider = ({ children }: SearchProviderProps) => { + const [isOpen, setIsOpen] = useState(false) + const [defaultFilters, setDefaultFilters] = useState([]) + + return ( + + {children} + + + ) +} + +export default SearchProvider + +export const useSearch = (): SearchContextType => { + const context = useContext(SearchContext) + + if (!context) { + throw new Error("useSearch must be used inside a SearchProvider") + } + + return context +} diff --git a/www/api-reference/providers/sidebar.tsx b/www/api-reference/providers/sidebar.tsx index 3f8db934e4..d0129f7bf7 100644 --- a/www/api-reference/providers/sidebar.tsx +++ b/www/api-reference/providers/sidebar.tsx @@ -10,6 +10,7 @@ import { useReducer, useState, } from "react" +import { usePageLoading } from "./page-loading" export enum SidebarItemSections { TOP = "top", @@ -167,6 +168,7 @@ const SidebarProvider = ({ children }: SidebarProviderProps) => { const [activePath, setActivePath] = useState("") const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false) const [desktopSidebarOpen, setDesktopSidebarOpen] = useState(true) + const { isLoading, setIsLoading } = usePageLoading() const findItemInSection = useCallback( ( @@ -288,6 +290,12 @@ const SidebarProvider = ({ children }: SidebarProviderProps) => { } }, [handleHashChange]) + useEffect(() => { + if (isLoading && items.top.length && items.bottom.length) { + setIsLoading(false) + } + }, [items, isLoading, setIsLoading]) + return ( { + addVariant("search-cancel", "&::-webkit-search-cancel-button") + }), + ], } diff --git a/www/api-reference/utils/array-same-elms.ts b/www/api-reference/utils/array-same-elms.ts new file mode 100644 index 0000000000..a8f6810917 --- /dev/null +++ b/www/api-reference/utils/array-same-elms.ts @@ -0,0 +1,10 @@ +export default function checkArraySameElms( + arr1: Array, + arr2: Array +): boolean { + if (arr1.length !== arr2.length) { + return false + } + + return arr1.every((value, index) => value === arr2[index]) +} diff --git a/www/api-reference/utils/dom-utils.ts b/www/api-reference/utils/dom-utils.ts new file mode 100644 index 0000000000..cb2ef70b12 --- /dev/null +++ b/www/api-reference/utils/dom-utils.ts @@ -0,0 +1,29 @@ +export function findPrevSibling( + element: HTMLElement, + selector: string +): HTMLElement | null { + let prevElement = element.previousElementSibling + while (prevElement !== null) { + if (prevElement.matches(selector)) { + return prevElement as HTMLElement + } + prevElement = prevElement.previousElementSibling + } + + return null +} + +export function findNextSibling( + element: HTMLElement, + selector: string +): HTMLElement | null { + let nextElement = element.nextElementSibling + while (nextElement !== null) { + if (nextElement.matches(selector)) { + return nextElement as HTMLElement + } + nextElement = nextElement.nextElementSibling + } + + return null +} diff --git a/www/api-reference/utils/get-base-url.ts b/www/api-reference/utils/get-base-url.ts index 73348a78c8..0c54e1ac45 100644 --- a/www/api-reference/utils/get-base-url.ts +++ b/www/api-reference/utils/get-base-url.ts @@ -1,3 +1,3 @@ export default function getBaseUrl() { - return process.env.NEXT_PUBLIC_BASE_URL || "http://locahost:3000" + return process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000" } diff --git a/www/api-reference/yarn.lock b/www/api-reference/yarn.lock index 9f58a27e24..9acdb7e2eb 100644 --- a/www/api-reference/yarn.lock +++ b/www/api-reference/yarn.lock @@ -135,6 +135,13 @@ __metadata: languageName: node linkType: hard +"@algolia/events@npm:^4.0.1": + version: 4.0.1 + resolution: "@algolia/events@npm:4.0.1" + checksum: f398d815c6ed21ac08f6caadf1e9155add74ac05d99430191c3b1f1335fd91deaf468c6b304e6225c9885d3d44c06037c53def101e33d9c22daff175b2a65ca9 + languageName: node + linkType: hard + "@algolia/logger-common@npm:4.19.1": version: 4.19.1 resolution: "@algolia/logger-common@npm:4.19.1" @@ -187,6 +194,23 @@ __metadata: languageName: node linkType: hard +"@algolia/ui-components-highlight-vdom@npm:^1.2.1": + version: 1.2.1 + resolution: "@algolia/ui-components-highlight-vdom@npm:1.2.1" + dependencies: + "@algolia/ui-components-shared": 1.2.1 + "@babel/runtime": ^7.0.0 + checksum: cb768905ac19cde9b491e9d3ed9aa0cd9bd1c2db5a3fb723cbc6668e00917b3ca98864eb37dc5d9736fcfcd0951a695201f81b3994651d25eaa122a124549851 + languageName: node + linkType: hard + +"@algolia/ui-components-shared@npm:1.2.1, @algolia/ui-components-shared@npm:^1.2.1": + version: 1.2.1 + resolution: "@algolia/ui-components-shared@npm:1.2.1" + checksum: 0ebbe1411a257efcd5b4535302ad85037c2b7de658fd05d0da681ed9eaebf66734a2327e08b20a9700a4cd6b2396463a5f91981aa35dc5b0adbb8d20aca96320 + languageName: node + linkType: hard + "@alloc/quick-lru@npm:^5.2.0": version: 5.2.0 resolution: "@alloc/quick-lru@npm:5.2.0" @@ -236,7 +260,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": version: 7.22.10 resolution: "@babel/runtime@npm:7.22.10" dependencies: @@ -2047,6 +2071,13 @@ __metadata: languageName: node linkType: hard +"@types/dom-speech-recognition@npm:^0.0.1": + version: 0.0.1 + resolution: "@types/dom-speech-recognition@npm:0.0.1" + checksum: 1df9283e40476f82b15cc7691c2f1177a185bf98af63d068f9333fbf4e334d2584b70babe2b9c69fcbe3c74293fcc0d47ce98c5717d8db361e70d88a8fbf9490 + languageName: node + linkType: hard + "@types/estree-jsx@npm:^1.0.0": version: 1.0.0 resolution: "@types/estree-jsx@npm:1.0.0" @@ -2063,6 +2094,13 @@ __metadata: languageName: node linkType: hard +"@types/google.maps@npm:^3.45.3": + version: 3.53.6 + resolution: "@types/google.maps@npm:3.53.6" + checksum: 08c43dc680edcc7f9f66262fe3598b2c7e250fa0240ec005daece1b16c125344e909c5cb865d22450d6e6cad2cd4f28cbb1e51f038627d78c21cc76768077f15 + languageName: node + linkType: hard + "@types/hast@npm:*": version: 3.0.0 resolution: "@types/hast@npm:3.0.0" @@ -2081,6 +2119,13 @@ __metadata: languageName: node linkType: hard +"@types/hogan.js@npm:^3.0.0": + version: 3.0.1 + resolution: "@types/hogan.js@npm:3.0.1" + checksum: 8d2dc2809063852710a559746c8275af97bf5c84be1ad40fe240d8b5c91d266b0f841dc9774b5d1461ea42679b4dfeb4f1865a94995fc61a8e4081d3f11dc266 + languageName: node + linkType: hard + "@types/js-yaml@npm:^4.0.0": version: 4.0.5 resolution: "@types/js-yaml@npm:4.0.5" @@ -2174,6 +2219,13 @@ __metadata: languageName: node linkType: hard +"@types/qs@npm:^6.5.3": + version: 6.9.7 + resolution: "@types/qs@npm:6.9.7" + checksum: 157eb05f4c75790b0ebdcf7b0547ff117feabc8cda03c3cac3d3ea82bb19a1912e76a411df3eb0bdd01026a9770f07bc0e7e3fbe39ebb31c1be4564c16be35f1 + languageName: node + linkType: hard + "@types/react-dom@npm:18.2.7": version: 18.2.7 resolution: "@types/react-dom@npm:18.2.7" @@ -2319,7 +2371,7 @@ __metadata: languageName: node linkType: hard -"abbrev@npm:^1.0.0": +"abbrev@npm:1, abbrev@npm:^1.0.0": version: 1.1.1 resolution: "abbrev@npm:1.1.1" checksum: 3f762677702acb24f65e813070e306c61fafe25d4b2583f9dfc935131f774863f3addd5741572ed576bd69cabe473c5af18e1e108b829cb7b6b4747884f726e6 @@ -2408,6 +2460,17 @@ __metadata: languageName: node linkType: hard +"algoliasearch-helper@npm:3.14.0": + version: 3.14.0 + resolution: "algoliasearch-helper@npm:3.14.0" + dependencies: + "@algolia/events": ^4.0.1 + peerDependencies: + algoliasearch: ">= 3.1 < 6" + checksum: 8c60aae2bcaa3f8eb547fd48cec0089a329dc5fec05e6c7364642fb2353256f11e4402ea3cec58c4a2bdad6a1720980fbd7dbab51be0b37b13a26b78705ddcc9 + languageName: node + linkType: hard + "algoliasearch@npm:^4.0.0, algoliasearch@npm:^4.19.1": version: 4.19.1 resolution: "algoliasearch@npm:4.19.1" @@ -2523,6 +2586,7 @@ __metadata: prism-react-renderer: ^2.0.6 react: latest react-dom: latest + react-instantsearch: ^7.0.1 react-intersection-observer: ^9.5.2 react-tooltip: ^5.19.0 react-transition-group: ^4.4.5 @@ -4289,6 +4353,25 @@ __metadata: languageName: node linkType: hard +"hogan.js@npm:^3.0.2": + version: 3.0.2 + resolution: "hogan.js@npm:3.0.2" + dependencies: + mkdirp: 0.3.0 + nopt: 1.0.10 + bin: + hulk: ./bin/hulk + checksum: fa5c9d2eaf3fa712e72e67cce5e3435a1c5823282b81051514aefdca7d4b706cc4dbef7a34be19ee320c6ebaf3687d5781f12bc0aac04d3d902aa26861493679 + languageName: node + linkType: hard + +"htm@npm:^3.0.0": + version: 3.1.1 + resolution: "htm@npm:3.1.1" + checksum: 0de4c8fff2b8e76c162235ae80dbf93ca5eef1575bd50596a06ce9bebf1a6da5efc467417c53034a9ffa2ab9ecff819cbec041dc9087894b2b900ad4de26c7e7 + languageName: node + linkType: hard + "html-encoding-sniffer@npm:^3.0.0": version: 3.0.0 resolution: "html-encoding-sniffer@npm:3.0.0" @@ -4399,6 +4482,29 @@ __metadata: languageName: node linkType: hard +"instantsearch.js@npm:4.56.9": + version: 4.56.9 + resolution: "instantsearch.js@npm:4.56.9" + dependencies: + "@algolia/events": ^4.0.1 + "@algolia/ui-components-highlight-vdom": ^1.2.1 + "@algolia/ui-components-shared": ^1.2.1 + "@types/dom-speech-recognition": ^0.0.1 + "@types/google.maps": ^3.45.3 + "@types/hogan.js": ^3.0.0 + "@types/qs": ^6.5.3 + algoliasearch-helper: 3.14.0 + hogan.js: ^3.0.2 + htm: ^3.0.0 + preact: ^10.10.0 + qs: ^6.5.1 < 6.10 + search-insights: ^2.6.0 + peerDependencies: + algoliasearch: ">= 3.1 < 6" + checksum: acde452a6da992a01148cdcba89217292e624e2dce77682236b36f7d119586afbcc9f0c8f74cae49e81652ba1e79d72fed0a1aba29bbce0463e115fd1223e49d + languageName: node + linkType: hard + "internal-slot@npm:^1.0.3, internal-slot@npm:^1.0.5": version: 1.0.5 resolution: "internal-slot@npm:1.0.5" @@ -5613,6 +5719,13 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:0.3.0": + version: 0.3.0 + resolution: "mkdirp@npm:0.3.0" + checksum: cd9e54878490571df79770de1cdceba48ab6682c004616666d23a38315feaf5822d443aeb500ac298a12d7f6f5e11dc05cea3207d500e547d938218bf22d8629 + languageName: node + linkType: hard + "mkdirp@npm:^1.0.3": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" @@ -5806,6 +5919,17 @@ __metadata: languageName: node linkType: hard +"nopt@npm:1.0.10": + version: 1.0.10 + resolution: "nopt@npm:1.0.10" + dependencies: + abbrev: 1 + bin: + nopt: ./bin/nopt.js + checksum: ddfbd892116a125fd68849ef564dd5b1f0a5ba0dbbf18782e9499e2efad8f4d3790635b47c6b5d3f7e014069e7b3ce5b8112687e9ae093fcd2678188c866fe28 + languageName: node + linkType: hard + "nopt@npm:^6.0.0": version: 6.0.0 resolution: "nopt@npm:6.0.0" @@ -6229,6 +6353,13 @@ __metadata: languageName: node linkType: hard +"preact@npm:^10.10.0": + version: 10.17.0 + resolution: "preact@npm:10.17.0" + checksum: 43279a28cd1240c8692ef48751d32ec17548f2678a47cdb006e786ecbcb3b4db9ded97afb72f16248d56d91fbcf3974ae787cd9d186acc4f874fc58a32b894c0 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -6290,6 +6421,13 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.5.1 < 6.10": + version: 6.9.7 + resolution: "qs@npm:6.9.7" + checksum: d0274b3c2daa9e7b350fb695fc4b5f7a1e65e266d5798a07936975f0848bdca6d7ad41cded19ad4af6a6253b97e43b497e988e728eab7a286f277b6dddfbade4 + languageName: node + linkType: hard + "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" @@ -6316,6 +6454,36 @@ __metadata: languageName: node linkType: hard +"react-instantsearch-core@npm:7.0.1": + version: 7.0.1 + resolution: "react-instantsearch-core@npm:7.0.1" + dependencies: + "@babel/runtime": ^7.1.2 + algoliasearch-helper: 3.14.0 + instantsearch.js: 4.56.9 + use-sync-external-store: ^1.0.0 + peerDependencies: + algoliasearch: ">= 3.1 < 5" + react: ">= 16.8.0 < 19" + checksum: 7796a0c8c9b7105aa31dc64762b37c72bccd8342d6cbdd6b55c3cb740f48af2fac5d8ba6b735b4739531720cf0ca48a478b7829728a237cd434abf4ab8fd2ff6 + languageName: node + linkType: hard + +"react-instantsearch@npm:^7.0.1": + version: 7.0.1 + resolution: "react-instantsearch@npm:7.0.1" + dependencies: + "@babel/runtime": ^7.1.2 + instantsearch.js: 4.56.9 + react-instantsearch-core: 7.0.1 + peerDependencies: + algoliasearch: ">= 3.1 < 5" + react: ">= 16.8.0 < 19" + react-dom: ">= 16.8.0 < 19" + checksum: 05cdafd17f6cf0523d29ebd9542b81a054fe0ef426a507b9d91653b35f5f567c3d0fb31b47feee600939f4feb561ae992cc4398e7d1aaa120029a01583088843 + languageName: node + linkType: hard + "react-intersection-observer@npm:^9.5.2": version: 9.5.2 resolution: "react-intersection-observer@npm:9.5.2" @@ -6634,6 +6802,13 @@ __metadata: languageName: node linkType: hard +"search-insights@npm:^2.6.0": + version: 2.7.0 + resolution: "search-insights@npm:2.7.0" + checksum: b21701b88f9fcfc6e9c13c5b48aa61a5be2ac73d0a3fc5a9c50eea04b0465936d8badb5093fdba0310997b58b5d3d562a2e4bd681825c74199a4314846052064 + languageName: node + linkType: hard + "semver@npm:^6.3.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" @@ -7432,7 +7607,7 @@ __metadata: languageName: node linkType: hard -"use-sync-external-store@npm:^1.2.0": +"use-sync-external-store@npm:^1.0.0, use-sync-external-store@npm:^1.2.0": version: 1.2.0 resolution: "use-sync-external-store@npm:1.2.0" peerDependencies: diff --git a/www/docs/.env.sample b/www/docs/.env.sample index fd2fa8ec27..dc7c9c36e2 100644 --- a/www/docs/.env.sample +++ b/www/docs/.env.sample @@ -1 +1,3 @@ -API_URL= \ No newline at end of file +API_URL= +NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME= +NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME= \ No newline at end of file diff --git a/www/docs/docusaurus.config.js b/www/docs/docusaurus.config.js index 61d5d3c776..7451217736 100644 --- a/www/docs/docusaurus.config.js +++ b/www/docs/docusaurus.config.js @@ -3,9 +3,6 @@ require("dotenv").config() const fs = require("fs") const reverseSidebar = require("./src/utils/reverseSidebar") -const algoliaAppId = process.env.ALGOLIA_APP_ID || "temp" -const algoliaApiKey = process.env.ALGOLIA_API_KEY || "temp" - const announcementBar = JSON.parse(fs.readFileSync("./announcement.json")) /** @type {import('@medusajs/docs').MedusaDocusaurusConfig} */ @@ -46,16 +43,58 @@ const config = { disableSwitch: false, respectPrefersColorScheme: true, }, - algolia: { - apiKey: algoliaApiKey, - indexName: "medusa-commerce", - placeholder: "Search docs...", - appId: algoliaAppId, - contextualSearch: false, - externalUrlRegex: "https://medusajs.com,https://docs.medusajs.com/api/", - searchParameters: { - tagFilters: "-reference", + algoliaConfig: { + appId: process.env.ALGOLIA_APP_ID || "temp", + apiKey: process.env.ALGOLIA_API_KEY || "temp", + indexNames: { + docs: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME, + api: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME, }, + filters: [ + { + value: "docs", + label: "Docs", + }, + { + value: "user-guide", + label: "User Guide", + }, + { + value: "admin", + label: "Admin API", + }, + { + value: "store", + label: "Store API", + }, + { + value: "plugins", + label: "Plugins", + }, + { + value: "reference", + label: "References", + }, + { + value: "ui", + label: "UI", + }, + ], + defaultFiltersByPath: [ + { + path: "/user-guide", + filters: ["user-guide"], + }, + { + path: "/references", + filters: ["reference"], + }, + { + path: "/plugins", + filters: ["plugins"], + }, + ], + defaultFilters: ["docs"], }, prism: { defaultLanguage: "js", diff --git a/www/docs/package.json b/www/docs/package.json index 2b14c96217..83f83010b7 100644 --- a/www/docs/package.json +++ b/www/docs/package.json @@ -37,6 +37,7 @@ "prism-react-renderer": "^1.3.1", "react": "^17.0.1", "react-dom": "^17.0.1", + "react-instantsearch": "^7.0.1", "react-tooltip": "5.7.4", "react-transition-group": "^4.4.5", "react-uuid": "^2.0.0", diff --git a/www/docs/src/components/Badge/index.tsx b/www/docs/src/components/Badge/index.tsx index 417b74b20e..4f4ab310c6 100644 --- a/www/docs/src/components/Badge/index.tsx +++ b/www/docs/src/components/Badge/index.tsx @@ -3,14 +3,24 @@ import clsx from "clsx" export type BadgeProps = { className?: string - variant: string + variant: + | "purple" + | "purple-dark" + | "orange" + | "orange-dark" + | "green" + | "green-dark" + | "blue" + | "blue-dark" + | "red" + | "neutral" } & React.HTMLAttributes const Badge: React.FC = ({ className, variant, children }) => { return ( = ({ className, variant, children }) => { "bg-medusa-tag-blue-bg dark:bg-medusa-tag-blue-bg-dark text-medusa-tag-blue-text dark:text-medusa-tag-blue-text-dark border-medusa-tag-blue-border dark:border-medusa-tag-blue-border-dark", variant === "blue-dark" && "bg-medusa-tag-blue-bg-dark text-medusa-tag-blue-text-dark border-medusa-tag-blue-border-dark", + variant === "red" && + "bg-medusa-tag-red-bg dark:bg-medusa-tag-red-bg-dark text-medusa-tag-red-text dark:text-medusa-tag-red-text-dark border-medusa-tag-red-border dark:border-medusa-tag-red-border-dark", + variant === "neutral" && + "bg-medusa-tag-neutral-bg dark:bg-medusa-tag-neutral-bg-dark text-medusa-tag-neutral-text dark:text-medusa-tag-neutral-text-dark border-medusa-tag-neutral-border dark:border-medusa-tag-neutral-border-dark", "badge", className )} diff --git a/www/docs/src/components/Button/index.tsx b/www/docs/src/components/Button/index.tsx index 02c0360c88..e3e8db8b52 100644 --- a/www/docs/src/components/Button/index.tsx +++ b/www/docs/src/components/Button/index.tsx @@ -2,6 +2,7 @@ import React from "react" import clsx from "clsx" export type ButtonProps = { + variant?: "secondary" | "primary" | "clear" btnTypeClassName?: string className?: string onClick?: React.MouseEventHandler @@ -9,6 +10,7 @@ export type ButtonProps = { } & React.HTMLAttributes const Button: React.FC = ({ + variant, className = "", btnTypeClassName, onClick, @@ -18,7 +20,10 @@ const Button: React.FC = ({ return ( {hasNextStep() && ( - )} {!hasNextStep() && ( - )} diff --git a/www/docs/src/components/Modal/Footer/index.tsx b/www/docs/src/components/Modal/Footer/index.tsx new file mode 100644 index 0000000000..e627594b38 --- /dev/null +++ b/www/docs/src/components/Modal/Footer/index.tsx @@ -0,0 +1,29 @@ +import React from "react" +import clsx from "clsx" +import Button, { ButtonProps } from "../../Button" + +type ModalFooterProps = { + actions?: ButtonProps[] + children?: React.ReactNode + className?: string +} + +const ModalFooter = ({ actions, className, children }: ModalFooterProps) => { + return ( +
    + {actions?.map((action, index) => ( +
    + ) +} + +export default ModalFooter diff --git a/www/docs/src/components/Modal/Header/index.tsx b/www/docs/src/components/Modal/Header/index.tsx new file mode 100644 index 0000000000..5d152b7c85 --- /dev/null +++ b/www/docs/src/components/Modal/Header/index.tsx @@ -0,0 +1,39 @@ +import React from "react" +import clsx from "clsx" +import { useModal } from "../../../providers/Modal" +import Button from "../../Button" +import IconXMark from "../../../theme/Icon/XMark" + +type ModalHeaderProps = { + title?: string +} + +const ModalHeader = ({ title }: ModalHeaderProps) => { + const { closeModal } = useModal() + + return ( +
    + + {title} + + +
    + ) +} + +export default ModalHeader diff --git a/www/docs/src/components/Modal/index.tsx b/www/docs/src/components/Modal/index.tsx index 8cb273c36a..255d368dbc 100644 --- a/www/docs/src/components/Modal/index.tsx +++ b/www/docs/src/components/Modal/index.tsx @@ -1,73 +1,117 @@ import clsx from "clsx" -import React from "react" -import Button, { ButtonProps } from "../Button" -import IconXMark from "../../theme/Icon/XMark" +import React, { forwardRef, useCallback, useEffect, useRef } from "react" +import { ButtonProps } from "../Button" +import ModalHeader from "./Header" +import ModalFooter from "./Footer" +import useKeyboardShortcut from "../../hooks/use-keyboard-shortcut" +import { useModal } from "../../providers/Modal" export type ModalProps = { className?: string title?: string actions?: ButtonProps[] -} & React.DetailedHTMLProps< - React.DialogHTMLAttributes, - HTMLDialogElement -> + modalContainerClassName?: string + contentClassName?: string + onClose?: React.ReactEventHandler + open?: boolean + footerContent?: React.ReactNode +} & Omit, "ref"> + +const Modal = forwardRef(function Modal( + { + className, + title, + actions, + children, + contentClassName, + modalContainerClassName, + onClose, + open = true, + footerContent, + ...props + }, + passedRef +) { + const { closeModal } = useModal() + const ref = useRef(null) + + const setRefs = useCallback( + (node: HTMLDialogElement) => { + // Ref's from useRef needs to have the node assigned to `current` + ref.current = node + if (typeof passedRef === "function") { + passedRef(node) + } else if (passedRef && "current" in passedRef) { + passedRef.current = node + } + }, + [passedRef] + ) + + useKeyboardShortcut({ + metakey: false, + checkEditing: false, + shortcutKeys: ["escape"], + action: () => { + if (open) { + ref.current?.close() + } + }, + }) + + const handleClick = (e: React.MouseEvent) => { + // close modal when the user clicks outside the content + if (e.target === ref.current) { + closeModal() + onClose?.(e) + } + } + + const handleClose = (e: React.SyntheticEvent) => { + onClose?.(e) + closeModal() + } + + useEffect(() => { + if (open) { + document.body.setAttribute("data-modal", "opened") + } else { + document.body.removeAttribute("data-modal") + } + }, [open]) -const Modal: React.FC = ({ - className, - title, - actions, - children, - ...props -}) => { return (
    -
    - - {title} - - + {title && } +
    + {children}
    -
    {children}
    - {actions && actions?.length > 0 && ( -
    - {actions.map((action, index) => ( -
    - )} + {actions && actions?.length > 0 && } + {footerContent && {footerContent}}
    ) -} +}) export default Modal diff --git a/www/docs/src/components/Rating/index.tsx b/www/docs/src/components/Rating/index.tsx index a079da9ee3..9f48299d0d 100644 --- a/www/docs/src/components/Rating/index.tsx +++ b/www/docs/src/components/Rating/index.tsx @@ -47,7 +47,7 @@ const Rating: React.FC = ({ (hoverRating !== 0 && hoverRating - 1 >= i) return ( +
    +
    + }> + + +
    + +
    + + 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(options.map((option) => option.value)) + } + }} + /> +
    +
    + + Navigation + + + ↑ + ↓ + +
    +
    + + Open Result + + ↵ +
    +
    +
    + + ) +} + +export default SearchModal diff --git a/www/docs/src/components/Search/ModalOpener/index.tsx b/www/docs/src/components/Search/ModalOpener/index.tsx new file mode 100644 index 0000000000..fbf54e4a34 --- /dev/null +++ b/www/docs/src/components/Search/ModalOpener/index.tsx @@ -0,0 +1,78 @@ +import React from "react" +import clsx from "clsx" +import InputText from "../../Input/Text" +import { MouseEvent, useMemo } from "react" +import { useSearch } from "../../../providers/Search" +import { useWindowSize } from "@docusaurus/theme-common" +import Button from "../../Button" +import IconMagnifyingGlass from "../../../theme/Icon/MagnifyingGlass" +import Kbd from "../../../theme/MDXComponents/Kbd" +import useKeyboardShortcut from "../../../hooks/use-keyboard-shortcut" + +const SearchModalOpener = () => { + const { setIsOpen } = useSearch() + const windowSize = useWindowSize() + const isApple = useMemo(() => { + return typeof navigator !== "undefined" + ? navigator.userAgent.toLowerCase().indexOf("mac") !== 0 + : true + }, []) + useKeyboardShortcut({ + shortcutKeys: ["k"], + action: () => setIsOpen((prev) => !prev), + }) + + const handleOpen = ( + e: + | MouseEvent + | MouseEvent + | MouseEvent + ) => { + e.preventDefault() + if ("blur" in e.target && typeof e.target.blur === "function") { + e.target.blur() + } + setIsOpen(true) + } + + return ( + <> + {windowSize !== "desktop" && ( + + )} + {windowSize === "desktop" && ( +
    + + e.target.blur()} + tabIndex={-1} + /> + + {isApple ? "⌘" : "Ctrl"} + K + +
    + )} + + ) +} + +export default SearchModalOpener diff --git a/www/docs/src/components/Search/NoResults/index.tsx b/www/docs/src/components/Search/NoResults/index.tsx new file mode 100644 index 0000000000..6bf0bf8f5e --- /dev/null +++ b/www/docs/src/components/Search/NoResults/index.tsx @@ -0,0 +1,15 @@ +import React from "react" +import IconExclamationCircleSolid from "../../../theme/Icon/ExclamationCircleSolid" + +const SearchNoResult = () => { + return ( +
    + + + No results found. Try changing selected filters. + +
    + ) +} + +export default SearchNoResult diff --git a/www/docs/src/components/Search/Suggestions/index.tsx b/www/docs/src/components/Search/Suggestions/index.tsx new file mode 100644 index 0000000000..c7750cc45e --- /dev/null +++ b/www/docs/src/components/Search/Suggestions/index.tsx @@ -0,0 +1,69 @@ +import React from "react" +import clsx from "clsx" +import { useInstantSearch } from "react-instantsearch" +import SearchHitGroupName from "../Hits/GroupName" + +const SearchSuggestions = () => { + const { setIndexUiState } = useInstantSearch() + const suggestions = [ + { + title: "Getting started? Try one of the following terms.", + items: [ + "Install Medusa with create-medusa-app", + "Next.js quickstart", + "Admin dashboard quickstart", + "Commerce modules", + "Medusa architecture", + ], + }, + { + title: "Developing with Medusa", + items: [ + "Recipes", + "How to create endpoints", + "How to create an entity", + "How to create a plugin", + "How to create an admin widget", + ], + }, + ] + return ( +
    + {suggestions.map((suggestion, index) => ( + + + {suggestion.items.map((item, itemIndex) => ( +
    + setIndexUiState({ + query: item, + }) + } + key={itemIndex} + tabIndex={itemIndex} + data-hit + > + + {item} + +
    + ))} +
    + ))} +
    + ) +} + +export default SearchSuggestions diff --git a/www/docs/src/components/Select/Badge/index.tsx b/www/docs/src/components/Select/Badge/index.tsx new file mode 100644 index 0000000000..b21a8cf857 --- /dev/null +++ b/www/docs/src/components/Select/Badge/index.tsx @@ -0,0 +1,136 @@ +import React from "react" +import { useCallback, useRef, useState } from "react" +import useSelect from "../../../hooks/use-select" +import clsx from "clsx" +import SelectDropdown from "../Dropdown" +import { SelectProps } from "../types" + +const SelectBadge = ({ + value, + options, + setSelected, + addSelected, + removeSelected, + multiple, + className, + addAll = multiple, + handleAddAll, + ...props +}: SelectProps) => { + const [open, setOpen] = useState(false) + const ref = useRef(null) + const dropdownRef = useRef(null) + const { isValueSelected, isAllSelected, handleChange, handleSelectAll } = + useSelect({ + value, + options, + multiple, + setSelected, + removeSelected, + addSelected, + handleAddAll, + }) + + const getSelectedText = useCallback(() => { + let str = "" + const selectedOptions = options.filter((option) => + value.includes(option.value) + ) + + if (isAllSelected) { + str = "All Areas" + } else { + if ( + (!Array.isArray(value) && !value) || + (Array.isArray(value) && !value.length) + ) { + str = "None selected" + } else { + str = selectedOptions[0].label + } + } + + return ( + <> + + {str} + + {!isAllSelected && selectedOptions.length > 1 && ( + + {" "} + + {selectedOptions.length} + + )} + + ) + }, [isAllSelected, options, value]) + + return ( +
    +
    { + if (!dropdownRef.current?.contains(e.target as Element)) { + setOpen((prev) => !prev) + } + }} + > + + Show results from:{" "} + + {getSelectedText()} +
    + + +
    + ) +} + +export default SelectBadge diff --git a/www/docs/src/components/Select/Dropdown/index.tsx b/www/docs/src/components/Select/Dropdown/index.tsx new file mode 100644 index 0000000000..65c3e20ea0 --- /dev/null +++ b/www/docs/src/components/Select/Dropdown/index.tsx @@ -0,0 +1,152 @@ +import React from "react" +import clsx from "clsx" +import { OptionType } from "../../../hooks/use-select" +import { forwardRef, useCallback, useEffect, useRef } from "react" +import IconCheckMini from "../../../theme/Icon/CheckMini" +import IconEllipseMiniSolid from "../../../theme/Icon/EllipseMiniSolid" + +type SelectDropdownProps = { + options: OptionType[] + open: boolean + setOpen: React.Dispatch> + addAll?: boolean + multiple?: boolean + isAllSelected: boolean + isValueSelected: (val: string) => boolean + handleSelectAll: () => void + handleChange?: (selectedValue: string, wasSelected: boolean) => void + parentRef?: React.RefObject + className?: string +} + +const SelectDropdown = forwardRef( + function SelectDropdown( + { + open, + setOpen, + options, + addAll, + multiple = false, + isAllSelected, + isValueSelected, + handleSelectAll, + handleChange: handleSelectChange, + parentRef, + className, + }, + passedRef + ) { + const ref = useRef(null) + const setRefs = useCallback( + (node: HTMLDivElement) => { + // Ref's from useRef needs to have the node assigned to `current` + ref.current = node + if (typeof passedRef === "function") { + passedRef(node) + } else if (passedRef && "current" in passedRef) { + passedRef.current = node + } + }, + [passedRef] + ) + + const handleChange = (clickedValue: string, wasSelected: boolean) => { + handleSelectChange?.(clickedValue, wasSelected) + if (!multiple) { + setOpen(false) + } + } + + const handleOutsideClick = useCallback( + (e: MouseEvent) => { + if ( + open && + !ref.current?.contains(e.target as Element) && + !parentRef?.current?.contains(e.target as Element) + ) { + setOpen(false) + } + }, + [open, parentRef, setOpen] + ) + + useEffect(() => { + document.body.addEventListener("click", handleOutsideClick) + + return () => { + document.body.removeEventListener("click", handleOutsideClick) + } + }, [handleOutsideClick]) + + const getSelectOption = (option: OptionType, index: number) => { + const isSelected = option.isAllOption + ? isAllSelected + : isValueSelected(option.value) + + return ( +
  • svg]:left-0.75 cursor-pointer [&>svg]:absolute [&>svg]:top-0.5", + !isSelected && "text-compact-small", + isSelected && "text-compact-small-plus" + )} + onClick={() => { + if (option.isAllOption) { + handleSelectAll() + } else { + handleChange(option.value, isSelected) + } + }} + > + {isSelected && ( + <> + {multiple && ( + + )} + {!multiple && ( + + )} + + )} + {option.label} +
  • + ) + } + + return ( +
    +
      + {addAll && + getSelectOption( + { + value: "all", + label: "All Areas", + isAllOption: true, + }, + -1 + )} + {options.map(getSelectOption)} +
    +
    + ) + } +) + +export default SelectDropdown diff --git a/www/docs/src/components/Select/Input/index.tsx b/www/docs/src/components/Select/Input/index.tsx new file mode 100644 index 0000000000..06fc3eb0aa --- /dev/null +++ b/www/docs/src/components/Select/Input/index.tsx @@ -0,0 +1,128 @@ +import React from "react" +import clsx from "clsx" +import { useRef, useState } from "react" +import Badge from "../../Badge" +import useSelect from "../../../hooks/use-select" +import SelectDropdown from "../Dropdown" +import { SelectProps } from "../types" +import IconChevronUpDown from "../../../theme/Icon/ChevronUpDown" +import IconXMarkMini from "../../../theme/Icon/XMarkMini" + +const SelectInput = ({ + value, + options, + setSelected, + addSelected, + removeSelected, + multiple, + className, + addAll = multiple, + handleAddAll, + showClearButton = true, + ...props +}: SelectProps) => { + const [open, setOpen] = useState(false) + const ref = useRef(null) + const dropdownRef = useRef(null) + const { + isValueSelected, + hasSelectedValue, + hasSelectedValues, + selectedValues, + isAllSelected, + handleChange, + handleSelectAll, + } = useSelect({ + value, + options, + multiple, + setSelected, + removeSelected, + addSelected, + handleAddAll, + }) + + return ( +
    { + if (!dropdownRef.current?.contains(e.target as Element)) { + setOpen((prev) => !prev) + } + }} + > + {hasSelectedValues && ( + + + {(value as string[]).length} + + {showClearButton && ( + { + e.stopPropagation() + setSelected?.([]) + }} + /> + )} + + )} + + {!multiple && hasSelectedValue && selectedValues.length + ? selectedValues[0].label + : props.placeholder} + + + + +
    + ) +} + +export default SelectInput diff --git a/www/docs/src/components/Select/types.ts b/www/docs/src/components/Select/types.ts new file mode 100644 index 0000000000..85369f2bd0 --- /dev/null +++ b/www/docs/src/components/Select/types.ts @@ -0,0 +1,9 @@ +import { OptionType, SelectOptions } from "../../hooks/use-select" + +export type SelectProps = { + options: OptionType[] + multiple?: boolean + addAll?: boolean + showClearButton?: boolean +} & SelectOptions & + React.ComponentProps<"input"> diff --git a/www/docs/src/components/StructuredData/Searchbox/index.tsx b/www/docs/src/components/StructuredData/Searchbox/index.tsx deleted file mode 100644 index 5fdb8d608b..0000000000 --- a/www/docs/src/components/StructuredData/Searchbox/index.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from "react" -import useDocusaurusContext from "@docusaurus/useDocusaurusContext" -import Head from "@docusaurus/Head" - -type StructuredDataSearchboxProps = React.HTMLAttributes - -const StructuredDataSearchbox: React.FC = () => { - const { - siteConfig: { url }, - } = useDocusaurusContext() - - return ( - - - - ) -} - -export default StructuredDataSearchbox diff --git a/www/docs/src/components/TextArea/index.tsx b/www/docs/src/components/TextArea/index.tsx index 40d40d3866..6120a16a97 100644 --- a/www/docs/src/components/TextArea/index.tsx +++ b/www/docs/src/components/TextArea/index.tsx @@ -14,7 +14,7 @@ const TextArea: React.FC = (props) => { {...props} className={clsx( "bg-medusa-bg-field dark:bg-medusa-bg-field-dark shadow-button-secondary dark:shadow-button-secondary-dark", - "rounded border-medusa-border-loud-muted dark:border-medusa-border-loud-muted-dark border border-solid", + "rounded border-medusa-border-base dark:border-medusa-border-base-dark border border-solid", "px-0.75 pt-0.4 pb-[9px]", "hover:bg-medusa-bg-field-hover dark:hover:bg-medusa-bg-field-hover-dark", "focus:border-medusa-border-interactive dark:focus:border-medusa-border-interactive-dark", diff --git a/www/docs/src/css/components/docsearch.css b/www/docs/src/css/components/docsearch.css deleted file mode 100644 index fbdec112a6..0000000000 --- a/www/docs/src/css/components/docsearch.css +++ /dev/null @@ -1,197 +0,0 @@ -.DocSearch-Modal { - @apply border border-solid !border-medusa-border-base dark:!border-medusa-border-base-dark; - @apply lg:!rounded !relative; - @apply md:!m-[unset] md:w-[560px]; -} - -.DocSearch-SearchBar { - @apply !p-0; -} - -.DocSearch-Form { - --docsearch-spacing: theme(margin[1.5]); - --docsearch-searchbox-height: 56px; - --docsearch-searchbox-focus-background: var(--docsearch-modal-background) !important; - - @apply !rounded-t !rounded-b-none border-0 border-b border-solid !border-medusa-border-base dark:!border-medusa-border-base-dark; -} - -.DocSearch-LoadingIndicator svg, .DocSearch-MagnifierLabel svg { - @apply !w-[20px] !h-[20px]; -} - -.DocSearch-Input { - @apply !text-medium !pl-1; - @apply placeholder:text-medusa-fg-muted dark:placeholder:text-medusa-fg-muted-dark; -} - -.DocSearch-Dropdown { - @apply !pt-0 !pb-2.5 !px-0; - @apply !max-h-[416px]; -} - -.DocSearch-Hit-source { - @apply !m-0 !text-compact-small-plus uppercase text-medusa-fg-muted dark:text-medusa-fg-muted-dark; - @apply border-0 border-b border-solid border-medusa-border-base dark:border-medusa-border-base-dark; - @apply !py-[10px] !px-1.5; -} - -.DocSearch-Footer { - @apply !hidden; -} - -.DocSearch-Hit { - @apply !p-0; -} - -.DocSearch-Hit:not(.DocSearch-Hit--Child) .DocSearch-Hit-icon { - @apply !w-2.5 !h-2.5 p-0.125 border border-solid border-medusa-border-strong dark:border-medusa-border-strong-dark; - @apply rounded flex justify-center items-center; - @apply before:content-[''] before:w-2 before:h-2 before:bg-no-repeat before:bg-center before:bg-contain before:bg-search-hit dark:before:bg-search-hit-dark; -} - -.DocSearch-Hit-icon svg { - @apply hidden; -} - -.DocSearch-Hit--Child .DocSearch-Hit-icon { - @apply hidden; -} - -.DocSearch-Hit a { - @apply !py-0.75 !px-1.5 !shadow-none !rounded-none; -} - -.DocSearch-Hit:not(:last-of-type) a { - @apply border-0 border-b border-solid border-medusa-border-base dark:border-medusa-border-base-dark; -} - -.DocSearch-Hit-content-wrapper { - @apply !mt-0 !mx-1; -} - -.DocSearch-Hit-title, -.DocSearch-Hit-title mark { - @apply !text-medusa-fg-base dark:!text-medusa-fg-base-dark; - @apply !text-compact-small-plus; -} - -.DocSearch-Hit-path { - @apply !text-medusa-fg-subtle dark:!text-medusa-fg-subtle-dark !text-compact-small; -} - -.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-path { - --docsearch-hit-active-color: theme(colors.medusa.fg.subtle.DEFAULT); -} - -html[data-theme="dark"] .DocSearch-Hit[aria-selected=true] .DocSearch-Hit-path { - --docsearch-hit-active-color: theme(colors.medusa.fg.subtle.dark); -} - -.DocSearch-Hit[aria-selected=true] a { - @apply !bg-medusa-bg-base-hover dark:!bg-medusa-bg-base-hover-dark; -} - -.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-content-wrapper + .DocSearch-Hit-action:last-child { - @apply h-1.5 w-1.5 border border-solid border-medusa-border-strong dark:border-medusa-border-strong-dark; - @apply rounded bg-medusa-bg-base dark:bg-medusa-bg-base-dark p-0.125 flex justify-center items-center; - @apply before:content-[''] before:w-[20px] before:h-[20px] before:bg-search-arrow dark:before:bg-search-arrow-dark before:bg-no-repeat before:bg-center; -} - -.DocSearch-Hit-content-wrapper + .DocSearch-Hit-action:last-child svg, -.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-content-wrapper + .DocSearch-Hit-action:last-child svg { - @apply hidden; -} - -.DocSearch-Hit[aria-selected=true] mark { - @apply !no-underline; -} - -.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Tree { - @apply text-medusa-border-strong dark:text-medusa-border-strong-dark; -} - -.DocSearch-Hit-Tree { - @apply text-medusa-border-base dark:text-medusa-border-base-dark; -} - -.DocSearch-HitsFooter { - @apply z-[100] absolute bottom-0 left-0 w-full h-2.5 !bg-medusa-bg-base dark:!bg-medusa-bg-base-dark; - @apply border-0 border-t border-solid border-medusa-border-base dark:border-medusa-border-base-dark; -} - -.DocSearch-HitsFooter a { - @apply !border-b-0; -} - -.DocSearch-Reset { - @apply !rounded transition-all duration-200 ease-ease; - @apply hover:bg-medusa-bg-base-hover dark:hover:bg-medusa-bg-base-hover-dark; -} - -.DocSearch-NoResults .DocSearch-Screen-Icon { - @apply w-[20px] h-[20px] !p-0 flex justify-center items-center mt-0 mb-1 mx-auto; - @apply before:content-[''] before:w-full before:h-full before:bg-no-repeat before:bg-center before:bg-contain; - @apply before:bg-search-no-result dark:before:bg-search-no-result-dark; -} - -.DocSearch-NoResults .DocSearch-Screen-Icon svg { - @apply hidden; -} - -.DocSearch-NoResults .DocSearch-Title { - @apply !text-compact-small !pl-1; -} - -.DocSearch-NoResults-Prefill-List { - @apply text-center !text-compact-small-plus !pl-1; -} - -.DocSearch-NoResults-Prefill-List li::marker { - @apply content-['']; -} - -.DocSearch-NoResults-Prefill-List li button { - @apply text-medusa-fg-base dark:text-medusa-fg-base-dark; -} -.DocSearch-Button { - @apply w-full !h-full !rounded lg:!border lg:!border-solid lg:!border-medusa-border-base lg:dark:!border-medusa-border-base-dark border-0; - @apply lg:!bg-medusa-bg-field lg:dark:!bg-medusa-bg-field-dark !bg-transparent; - @apply lg:hover:!bg-medusa-bg-field-hover lg:dark:hover:!bg-medusa-bg-field-hover-dark; - @apply !py-[6px] !pl-0.5 relative; - @apply hover:!border-medusa-border-base hover:dark:!border-medusa-border-base-dark; - @apply active:!border-medusa-border-base active:dark:!border-medusa-border-base-dark; - @apply focus:!border-medusa-border-base focus:dark:!border-medusa-border-base-dark; -} - -.DocSearch-Container { - @apply !z-[1001] md:flex md:justify-center md:items-center; -} - -.DocSearch-Button .DocSearch-Search-Icon { - @apply invisible; -} - -.DocSearch-Button-Container { - @apply before:content-[''] before:h-[20px] before:w-[20px] before:absolute before:left-0.5 before:top-[5px]; - @apply before:bg-magnifying-glass dark:before:bg-magnifying-glass-dark before:bg-no-repeat; -} - -.DocSearch-Button-Placeholder { - @apply text-medusa-fg-muted dark:text-medusa-fg-muted-dark; - @apply !pl-0.5 !text-compact-small lg:!block !hidden; -} - -.DocSearch-Button-Keys { - @apply w-fit !min-w-[unset] lg:!flex !hidden; -} - -.DocSearch-Button-Key { - @apply !shadow-none !rounded-md !text-compact-x-small-plus !font-base align-middle !p-0.125; - @apply !border !border-solid !border-medusa-tag-neutral-border dark:!border-medusa-tag-neutral-border-dark; - @apply [&span]:hidden [&:not(:last-child)]:!mr-0.25 last:!mr-0; -} - -[class*=searchBox] { - @apply lg:w-[280px] lg:max-w-[280px] lg:!h-2 lg:!p-0 !static; -} diff --git a/www/docs/src/css/custom.css b/www/docs/src/css/custom.css index 801f2fbc39..b4fb6bcbb2 100644 --- a/www/docs/src/css/custom.css +++ b/www/docs/src/css/custom.css @@ -2,6 +2,17 @@ @tailwind components; @tailwind utilities; +@layer base { + body[data-modal="opened"] { + @apply !overflow-hidden; + } + + mark { + @apply bg-medusa-bg-highlight dark:bg-medusa-bg-highlight-dark; + @apply text-medusa-fg-interactive dark:text-medusa-fg-interactive-dark; + } +} + @layer components { .sidebar-title { @apply !m-0 !py-1 !px-0; @@ -116,13 +127,14 @@ @apply inline-flex flex-row justify-center items-center; @apply py-[5px] px-0.75 rounded-sm cursor-pointer; @apply bg-button-neutral bg-medusa-button-neutral dark:bg-button-neutral-dark dark:bg-medusa-button-neutral-dark; - @apply hover:bg-medusa-button-neutral-hover hover:bg-button-neutral-hover dark:hover:bg-medusa-button-neutral-hover-dark dark:hover:bg-button-neutral-hover-dark hover:no-underline; - @apply active:bg-medusa-button-neutral-pressed active:bg-button-neutral-pressed dark:active:bg-medusa-button-neutral-pressed-dark dark:active:bg-button-neutral-pressed-dark; - @apply focus:bg-medusa-button-neutral-pressed focus:bg-button-neutral-pressed dark:focus:bg-medusa-button-neutral-pressed-dark dark:focus:bg-button-neutral-pressed-dark; + @apply hover:bg-medusa-button-neutral-hover hover:bg-no-image dark:hover:bg-medusa-button-neutral-hover-dark hover:no-underline; + @apply active:bg-medusa-button-neutral-pressed active:bg-no-image dark:active:bg-medusa-button-neutral-pressed-dark; + @apply focus:bg-medusa-button-neutral-pressed focus:bg-no-image dark:focus:bg-medusa-button-neutral-pressed-dark; @apply disabled:!bg-no-image disabled:bg-medusa-bg-disabled dark:disabled:bg-medusa-bg-disabled-dark; @apply disabled:cursor-not-allowed; @apply border border-solid border-medusa-border-base dark:border-medusa-border-base-dark; @apply text-compact-small-plus text-medusa-fg-base dark:text-medusa-fg-base-dark; + @apply hover:text-medusa-fg-base hover:dark:text-medusa-fg-base-dark; @apply shadow-button-neutral focus:shadow-button-neutral-focused active:shadow-button-neutral-focused transition-shadow; @apply dark:shadow-button-neutral dark:focus:shadow-button-neutral-focused dark:active:shadow-button-neutral-focused; @apply select-none; @@ -132,9 +144,9 @@ @apply inline-flex flex-row justify-center items-center; @apply py-[5px] px-0.75 rounded-sm cursor-pointer; @apply bg-button-inverted bg-medusa-button-inverted dark:bg-button-inverted-dark dark:bg-medusa-button-inverted-dark; - @apply hover:bg-medusa-button-inverted-hover hover:bg-button-inverted-hover dark:hover:bg-medusa-button-inverted-hover-dark dark:hover:bg-button-inverted-hover-dark hover:no-underline; - @apply active:bg-medusa-button-inverted-pressed active:bg-button-inverted-pressed dark:active:bg-medusa-button-inverted-pressed-dark dark:active:bg-button-inverted-pressed-dark; - @apply focus:bg-medusa-button-inverted-pressed focus:bg-button-inverted-pressed dark:focus:bg-medusa-button-inverted-pressed-dark dark:focus:bg-button-inverted-pressed-dark; + @apply hover:bg-medusa-button-inverted-hover hover:bg-no-image dark:hover:bg-medusa-button-inverted-hover-dark hover:no-underline; + @apply active:bg-medusa-button-inverted-pressed active:bg-no-image dark:active:bg-medusa-button-inverted-pressed-dark; + @apply focus:bg-medusa-button-inverted-pressed focus:bg-no-image dark:focus:bg-medusa-button-inverted-pressed-dark; @apply shadow-button-colored active:shadow-button-colored-focused focus:shadow-button-colored-focused transition-shadow; @apply dark:shadow-button-colored-dark dark:active:shadow-button-colored-focused-dark dark:focus:shadow-button-colored-focused-dark; @apply disabled:!bg-no-image disabled:bg-medusa-button-disabled dark:disabled:bg-medusa-button-disabled-dark; @@ -146,15 +158,16 @@ } .navbar-action-icon-item { - @apply lg:bg-docs-button-neutral lg:dark:bg-docs-button-neutral-dark hover:!bg-no-image lg:active:!bg-no-image hover:bg-medusa-button-neutral-hover dark:hover:bg-medusa-button-neutral-hover-dark; - @apply lg:active:bg-medusa-button-neutral-pressed lg:dark:active:bg-medusa-button-neutral-pressed-dark; - @apply lg:focus:shadow-button-secondary-focus lg:dark:focus:shadow-button-secondary-focus-dark; - @apply lg:border lg:border-solid lg:border-medusa-border-loud-muted lg:dark:border-medusa-border-loud-muted-dark rounded; + @apply lg:bg-button-neutral lg:bg-medusa-button-neutral lg:dark:bg-button-neutral-dark lg:dark:bg-medusa-button-neutral-dark; + @apply lg:hover:bg-medusa-button-neutral-hover lg:hover:bg-no-image lg:dark:hover:bg-medusa-button-neutral-hover-dark lg:hover:no-underline; + @apply lg:active:bg-medusa-button-neutral-pressed lg:active:bg-no-image lg:dark:active:bg-medusa-button-neutral-pressed-dark; + @apply lg:focus:bg-medusa-button-neutral-pressed lg:focus:bg-no-image lg:dark:focus:bg-medusa-button-neutral-pressed-dark; + @apply lg:lg:border lg:border-solid lg:border-medusa-border-base lg:dark:border-medusa-border-base-dark rounded; @apply w-2 h-2 flex justify-center items-center cursor-pointer; } - .transparent-button { - @apply bg-transparent border-0 text-inherit cursor-pointer p-0; + .btn-clear { + @apply bg-transparent shadow-none border-0 outline-none cursor-pointer; } } @@ -167,7 +180,6 @@ @import url('./_variables.css'); @import url('./_docusaurus.css'); -@import url('./components/docsearch.css'); @import url('./components/sidebar.css'); @import url('./components/toc.css'); @import url('./components/tooltip.css'); \ No newline at end of file diff --git a/www/docs/src/hooks/use-keyboard-shortcut.tsx b/www/docs/src/hooks/use-keyboard-shortcut.tsx new file mode 100644 index 0000000000..02b91c9865 --- /dev/null +++ b/www/docs/src/hooks/use-keyboard-shortcut.tsx @@ -0,0 +1,64 @@ +import { useCallback, useEffect } from "react" + +type useKeyboardShortcutOptions = { + metakey?: boolean + shortcutKeys: string[] + action: (e: KeyboardEvent) => void + checkEditing?: boolean + preventDefault?: boolean +} + +const useKeyboardShortcut = ({ + metakey = true, + shortcutKeys, + action, + checkEditing = true, + preventDefault = true, +}: useKeyboardShortcutOptions) => { + function isEditingContent(event: KeyboardEvent) { + const element = event.target as HTMLElement + const tagName = element.tagName + return ( + element.isContentEditable || + tagName === "INPUT" || + tagName === "SELECT" || + tagName === "TEXTAREA" + ) + } + + const checkKeysPressed = useCallback( + (pressedKey: string) => { + const lowerPressedKey = pressedKey.toLowerCase() + return shortcutKeys.some( + (value) => lowerPressedKey === value.toLowerCase() + ) + }, + [shortcutKeys] + ) + + const sidebarShortcut = useCallback( + (e: KeyboardEvent) => { + if ( + (!metakey || e.metaKey || e.ctrlKey) && + checkKeysPressed(e.key) && + (!checkEditing || !isEditingContent(e)) + ) { + if (preventDefault) { + e.preventDefault() + } + action(e) + } + }, + [metakey, checkKeysPressed, checkEditing, action, preventDefault] + ) + + useEffect(() => { + window.addEventListener("keydown", sidebarShortcut) + + return () => { + window.removeEventListener("keydown", sidebarShortcut) + } + }, [sidebarShortcut]) +} + +export default useKeyboardShortcut diff --git a/www/docs/src/hooks/use-select.tsx b/www/docs/src/hooks/use-select.tsx new file mode 100644 index 0000000000..d685d80db2 --- /dev/null +++ b/www/docs/src/hooks/use-select.tsx @@ -0,0 +1,93 @@ +import { useCallback, useMemo } from "react" + +export type OptionType = { + value: string + label: string + index?: string + isAllOption?: boolean +} + +export type SelectOptions = { + value: string | string[] + multiple?: boolean + options: OptionType[] + setSelected?: (value: string | string[]) => void + addSelected?: (value: string) => void + removeSelected?: (value: string) => void + handleAddAll?: (isAllSelected: boolean) => void +} + +const useSelect = ({ + value, + options, + multiple = false, + setSelected, + addSelected, + removeSelected, + handleAddAll, +}: SelectOptions) => { + const isValueSelected = useCallback( + (val: string) => { + return ( + (typeof value === "string" && val === value) || + (Array.isArray(value) && value.includes(val)) + ) + }, + [value] + ) + + // checks if there are multiple selected values + const hasSelectedValues = useMemo(() => { + return multiple && Array.isArray(value) && value.length > 0 + }, [value, multiple]) + + // checks if there are any selected values, + // whether multiple or one + const hasSelectedValue = useMemo(() => { + return hasSelectedValues || (typeof value === "string" && value.length) + }, [hasSelectedValues, value]) + + const selectedValues: OptionType[] = useMemo(() => { + if (typeof value === "string") { + const selectedValue = options.find((option) => option.value === value) + return selectedValue ? [selectedValue] : [] + } else if (Array.isArray(value)) { + return options.filter((option) => value.includes(option.value)) + } + return [] + }, [options, value]) + + const isAllSelected = useMemo(() => { + return Array.isArray(value) && value.length === options.length + }, [options, value]) + + const handleChange = (selectedValue: string, wasSelected: boolean) => { + if (multiple) { + wasSelected + ? removeSelected?.(selectedValue) + : addSelected?.(selectedValue) + } else { + setSelected?.(selectedValue) + } + } + + const handleSelectAll = () => { + if (handleAddAll) { + handleAddAll(isAllSelected) + } else { + setSelected?.(options.map((option) => option.value)) + } + } + + return { + isValueSelected, + hasSelectedValue, + hasSelectedValues, + selectedValues, + isAllSelected, + handleChange, + handleSelectAll, + } +} + +export default useSelect diff --git a/www/docs/src/providers/Modal/index.tsx b/www/docs/src/providers/Modal/index.tsx index d6b5a07db1..1b8b10b789 100644 --- a/www/docs/src/providers/Modal/index.tsx +++ b/www/docs/src/providers/Modal/index.tsx @@ -5,6 +5,7 @@ import Modal, { ModalProps } from "../../components/Modal" type ModalContextType = { modalProps: ModalProps | null setModalProps: (value: ModalProps | null) => void + closeModal: () => void } const ModalContext = createContext(null) @@ -13,10 +14,10 @@ type ModalProviderProps = { children?: React.ReactNode } -const ModalProvider: React.FC = ({ children }) => { +const ModalProvider = ({ children }: ModalProviderProps) => { const [modalProps, setModalProps] = useState(null) - const handleClose = () => { + const closeModal = () => { setModalProps(null) } @@ -25,10 +26,16 @@ const ModalProvider: React.FC = ({ children }) => { value={{ modalProps, setModalProps, + closeModal, }} > {children} - {modalProps && } + {modalProps && ( + <> +
    + + + )} ) } diff --git a/www/docs/src/providers/Search/index.tsx b/www/docs/src/providers/Search/index.tsx new file mode 100644 index 0000000000..13dd440f50 --- /dev/null +++ b/www/docs/src/providers/Search/index.tsx @@ -0,0 +1,68 @@ +import React, { useEffect } from "react" +import { createContext, useContext, useState } from "react" +import SearchModal from "../../components/Search/Modal" +import { useLocalPathname } from "@docusaurus/theme-common/internal" +import { useThemeConfig } from "@docusaurus/theme-common" +import { ThemeConfig } from "@medusajs/docs" +import checkArraySameElms from "../../utils/array-same-elms" + +type SearchContextType = { + isOpen: boolean + setIsOpen: React.Dispatch> + defaultFilters: string[] + setDefaultFilters: (value: string[]) => void +} + +const SearchContext = createContext(null) + +type SearchProviderProps = { + children: React.ReactNode +} + +const SearchProvider = ({ children }: SearchProviderProps) => { + const [isOpen, setIsOpen] = useState(false) + const [defaultFilters, setDefaultFilters] = useState([]) + const currentPath = useLocalPathname() + const { algoliaConfig: algolia } = useThemeConfig() as ThemeConfig + + useEffect(() => { + let resultFilters = [] + algolia.defaultFiltersByPath.some((filtersByPath) => { + if (currentPath.startsWith(filtersByPath.path)) { + resultFilters = filtersByPath.filters + } + }) + if (!resultFilters.length && algolia.defaultFilters) { + resultFilters = algolia.defaultFilters + } + if (!checkArraySameElms(defaultFilters, resultFilters)) { + setDefaultFilters(resultFilters) + } + }, [currentPath]) + + return ( + + {children} + + + ) +} + +export default SearchProvider + +export const useSearch = (): SearchContextType => { + const context = useContext(SearchContext) + + if (!context) { + throw new Error("useSearch must be used inside a SearchProvider") + } + + return context +} diff --git a/www/docs/src/theme/DocPage/index.tsx b/www/docs/src/theme/DocPage/index.tsx index 33844596ab..b140feb904 100644 --- a/www/docs/src/theme/DocPage/index.tsx +++ b/www/docs/src/theme/DocPage/index.tsx @@ -20,6 +20,7 @@ import SidebarProvider from "@site/src/providers/Sidebar" import NotificationProvider from "@site/src/providers/Notification" import UserProvider from "@site/src/providers/User" import ModalProvider from "../../providers/Modal" +import SearchProvider from "../../providers/Search" function DocPageMetadata(props: Props): JSX.Element { const { versionMetadata } = props @@ -67,9 +68,7 @@ export default function DocPage(props: Props): JSX.Element { - - {docElement} - + {docElement} diff --git a/www/docs/src/theme/Icon/ArrowDownLeftMini/index.tsx b/www/docs/src/theme/Icon/ArrowDownLeftMini/index.tsx new file mode 100644 index 0000000000..af3c3dbc0c --- /dev/null +++ b/www/docs/src/theme/Icon/ArrowDownLeftMini/index.tsx @@ -0,0 +1,41 @@ +import React from "react" +import { IconProps } from ".." + +const IconArrowDownLeftMini: React.FC = ({ + iconColorClassName, + ...props +}) => { + return ( + + + + + ) +} + +export default IconArrowDownLeftMini diff --git a/www/docs/src/theme/Icon/CheckMini/index.tsx b/www/docs/src/theme/Icon/CheckMini/index.tsx new file mode 100644 index 0000000000..a6c6aa1a35 --- /dev/null +++ b/www/docs/src/theme/Icon/CheckMini/index.tsx @@ -0,0 +1,31 @@ +import React from "react" +import { IconProps } from ".." + +const IconCheckMini: React.FC = ({ + iconColorClassName, + ...props +}) => { + return ( + + + + ) +} + +export default IconCheckMini diff --git a/www/docs/src/theme/Icon/ChevronUpDown/index.tsx b/www/docs/src/theme/Icon/ChevronUpDown/index.tsx new file mode 100644 index 0000000000..49d23c48d9 --- /dev/null +++ b/www/docs/src/theme/Icon/ChevronUpDown/index.tsx @@ -0,0 +1,31 @@ +import React from "react" +import { IconProps } from ".." + +const IconChevronUpDown: React.FC = ({ + iconColorClassName, + ...props +}) => { + return ( + + + + ) +} + +export default IconChevronUpDown diff --git a/www/docs/src/theme/Icon/EllipseMiniSolid/index.tsx b/www/docs/src/theme/Icon/EllipseMiniSolid/index.tsx new file mode 100644 index 0000000000..6b2dfa2c8c --- /dev/null +++ b/www/docs/src/theme/Icon/EllipseMiniSolid/index.tsx @@ -0,0 +1,30 @@ +import React from "react" +import { IconProps } from ".." + +const IconEllipseMiniSolid: React.FC = ({ + iconColorClassName, + ...props +}) => { + return ( + + + + ) +} + +export default IconEllipseMiniSolid diff --git a/www/docs/src/theme/Icon/MagnifyingGlass/index.tsx b/www/docs/src/theme/Icon/MagnifyingGlass/index.tsx new file mode 100644 index 0000000000..52eaa76d69 --- /dev/null +++ b/www/docs/src/theme/Icon/MagnifyingGlass/index.tsx @@ -0,0 +1,31 @@ +import React from "react" +import { IconProps } from ".." + +const IconMagnifyingGlass: React.FC = ({ + iconColorClassName, + ...props +}) => { + return ( + + + + ) +} + +export default IconMagnifyingGlass diff --git a/www/docs/src/theme/Icon/XMarkMini/index.tsx b/www/docs/src/theme/Icon/XMarkMini/index.tsx new file mode 100644 index 0000000000..68039b4859 --- /dev/null +++ b/www/docs/src/theme/Icon/XMarkMini/index.tsx @@ -0,0 +1,31 @@ +import React from "react" +import { IconProps } from ".." + +const IconXMarkMini: React.FC = ({ + iconColorClassName, + ...props +}) => { + return ( + + + + ) +} + +export default IconXMarkMini diff --git a/www/docs/src/theme/Icon/index.ts b/www/docs/src/theme/Icon/index.ts index e9f74a2972..ca67554fea 100644 --- a/www/docs/src/theme/Icon/index.ts +++ b/www/docs/src/theme/Icon/index.ts @@ -1,6 +1,7 @@ import IconAcademicCapSolid from "./AcademicCapSolid" import IconAdjustments from "./Adjustments" import IconAlert from "./Alert" +import IconArrowDownLeftMini from "./ArrowDownLeftMini" import IconArrowDownTray from "./ArrowDownTray" import IconBackArrow from "./BackArrow" import IconBarsThree from "./BarsThree" @@ -19,7 +20,9 @@ import IconCashSolid from "./CashSolid" import IconChannels from "./Channels" import IconChannelsSolid from "./ChannelsSolid" import IconCheckCircleSolid from "./CheckCircleSolid" +import IconCheckMini from "./CheckMini" import IconChevronDoubleLeftMiniSolid from "./ChevronDoubleLeftMiniSolid" +import IconChevronUpDown from "./ChevronUpDown" import IconCircleDottedLine from "./CircleDottedLine" import IconCircleMiniSolid from "./CircleMiniSolid" import IconCircleStack from "./CircleStack" @@ -43,6 +46,7 @@ import IconDarkMode from "./DarkMode" import IconDiscord from "./Discord" import IconDocumentText from "./DocumentText" import IconDocumentTextSolid from "./DocumentTextSolid" +import IconEllipseMiniSolid from "./EllipseMiniSolid" import IconExclamationCircleSolid from "./ExclamationCircleSolid" import IconExternalLink from "./ExternalLink" import IconFlyingBox from "./FlyingBox" @@ -61,6 +65,7 @@ import IconLightBulb from "./LightBulb" import IconLightBulbSolid from "./LightBulbSolid" import IconLightMode from "./LightMode" import IconLinkedIn from "./LinkedIn" +import IconMagnifyingGlass from "./MagnifyingGlass" import IconMap from "./Map" import IconNewspaper from "./Newspaper" import IconNextjs from "./Nextjs" @@ -93,6 +98,8 @@ import IconTwitter from "./Twitter" import IconUser from "./User" import IconUsersSolid from "./UsersSolid" import IconXCircleSolid from "./XCircleSolid" +import IconXMark from "./XMark" +import IconXMarkMini from "./XMarkMini" export type IconProps = { width?: number @@ -105,6 +112,7 @@ export default { "academic-cap-solid": IconAcademicCapSolid, adjustments: IconAdjustments, alert: IconAlert, + "arrow-down-left-mini": IconArrowDownLeftMini, "arrow-down-tray": IconArrowDownTray, "back-arrow": IconBackArrow, "bars-three": IconBarsThree, @@ -123,7 +131,9 @@ export default { "channels-solid": IconChannelsSolid, channels: IconChannels, "check-circle-solid": IconCheckCircleSolid, + "check-mini": IconCheckMini, "chevron-double-left-mini-solid": IconChevronDoubleLeftMiniSolid, + "chevron-up-down": IconChevronUpDown, "circle-dotted-line": IconCircleDottedLine, "circle-mini-solid": IconCircleMiniSolid, "circle-stack": IconCircleStack, @@ -147,6 +157,7 @@ export default { discord: IconDiscord, "document-text": IconDocumentText, "document-text-solid": IconDocumentTextSolid, + "ellipse-mini-solid": IconEllipseMiniSolid, "exclamation-circle-solid": IconExclamationCircleSolid, "external-link": IconExternalLink, "flying-box": IconFlyingBox, @@ -165,6 +176,7 @@ export default { "light-bulb-solid": IconLightBulbSolid, "light-mode": IconLightMode, linkedin: IconLinkedIn, + "magnifying-glass": IconMagnifyingGlass, map: IconMap, newspaper: IconNewspaper, nextjs: IconNextjs, @@ -197,4 +209,6 @@ export default { user: IconUser, "users-solid": IconUsersSolid, "x-circle-solid": IconXCircleSolid, + "x-mark": IconXMark, + "x-mark-mini": IconXMarkMini, } diff --git a/www/docs/src/theme/Layout/index.tsx b/www/docs/src/theme/Layout/index.tsx index 7b8016d5b0..e6b25b9e59 100644 --- a/www/docs/src/theme/Layout/index.tsx +++ b/www/docs/src/theme/Layout/index.tsx @@ -15,8 +15,9 @@ import type { Props } from "@theme/Layout" import useIsBrowser from "@docusaurus/useIsBrowser" import { useLocation } from "@docusaurus/router" import "animate.css" -import StructuredDataSearchbox from "@site/src/components/StructuredData/Searchbox" import { useUser } from "@site/src/providers/User" +import SearchProvider from "../../providers/Search" +import ModalProvider from "../../providers/Modal" export default function Layout(props: Props): JSX.Element { const { @@ -54,24 +55,29 @@ export default function Layout(props: Props): JSX.Element { return ( - - {isBrowser && location.pathname === "/" && } - + + + + - + -
    - }> - {children} - -
    +
    + } + > + {children} + +
    +
    +
    ) } diff --git a/www/docs/src/theme/MDXComponents/Kbd.tsx b/www/docs/src/theme/MDXComponents/Kbd.tsx new file mode 100644 index 0000000000..0b9e58962a --- /dev/null +++ b/www/docs/src/theme/MDXComponents/Kbd.tsx @@ -0,0 +1,27 @@ +import React from "react" +import clsx from "clsx" + +type KbdProps = { + className?: string +} & React.ComponentProps<"kbd"> + +const Kbd: React.FC = ({ children, className, ...props }) => { + return ( + + {children} + + ) +} + +export default Kbd diff --git a/www/docs/src/theme/MDXComponents/index.tsx b/www/docs/src/theme/MDXComponents/index.tsx index a76ae38bb7..10fe3c3853 100644 --- a/www/docs/src/theme/MDXComponents/index.tsx +++ b/www/docs/src/theme/MDXComponents/index.tsx @@ -5,6 +5,7 @@ import CloudinaryImage from "@site/src/components/CloudinaryImage" import MDXDetails from "./Details" import MDXSummary from "./Summary" import MDXA from "./A" +import Kbd from "./Kbd" export default { // Re-use the default mapping @@ -14,4 +15,5 @@ export default { details: MDXDetails, summary: MDXSummary, a: MDXA, + kbd: Kbd, } diff --git a/www/docs/src/theme/Navbar/Search/index.tsx b/www/docs/src/theme/Navbar/Search/index.tsx new file mode 100644 index 0000000000..a93dcda8fb --- /dev/null +++ b/www/docs/src/theme/Navbar/Search/index.tsx @@ -0,0 +1,10 @@ +import React from "react" +import clsx from "clsx" +import type { Props } from "@theme/Navbar/Search" + +export default function NavbarSearch({ + children, + className, +}: Props): JSX.Element { + return
    {children}
    +} diff --git a/www/docs/src/theme/NavbarItem/SearchNavbarItem.tsx b/www/docs/src/theme/NavbarItem/SearchNavbarItem.tsx new file mode 100644 index 0000000000..f002859646 --- /dev/null +++ b/www/docs/src/theme/NavbarItem/SearchNavbarItem.tsx @@ -0,0 +1,18 @@ +import React from "react" +import NavbarSearch from "@theme/Navbar/Search" +import type { Props } from "@theme/NavbarItem/SearchNavbarItem" +import SearchModalOpener from "../../components/Search/ModalOpener" + +export default function SearchNavbarItem({ + mobile, +}: Props): JSX.Element | null { + if (mobile) { + return null + } + + return ( + + + + ) +} diff --git a/www/docs/src/theme/SearchPage/index.tsx b/www/docs/src/theme/SearchPage/index.tsx deleted file mode 100644 index 185c89b911..0000000000 --- a/www/docs/src/theme/SearchPage/index.tsx +++ /dev/null @@ -1,524 +0,0 @@ -import React, { useEffect, useReducer, useRef, useState } from "react" -import clsx from "clsx" - -import algoliaSearchHelper from "algoliasearch-helper" -import algoliaSearch from "algoliasearch/lite" - -import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment" -import Head from "@docusaurus/Head" -import Link from "@docusaurus/Link" -import { useAllDocsData } from "@docusaurus/plugin-content-docs/client" -import { - HtmlClassNameProvider, - useEvent, - usePluralForm, - useSearchQueryString, -} from "@docusaurus/theme-common" -import { useTitleFormatter } from "@docusaurus/theme-common/internal" -import Translate, { translate } from "@docusaurus/Translate" -import useDocusaurusContext from "@docusaurus/useDocusaurusContext" -import { useSearchResultUrlProcessor } from "@docusaurus/theme-search-algolia/client" -import Layout from "@theme/Layout" -import { ThemeConfig } from "@medusajs/docs" -import UserProvider from "@site/src/providers/User" - -// Very simple pluralization: probably good enough for now -function useDocumentsFoundPlural() { - const { selectMessage } = usePluralForm() - return (count: number) => - selectMessage( - count, - translate( - { - id: "theme.SearchPage.documentsFound.plurals", - description: `Pluralized label for "{count} documents found". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)`, - message: "One document found|{count} documents found", - }, - { count } - ) - ) -} - -function useDocsSearchVersionsHelpers() { - const allDocsData = useAllDocsData() - - // State of the version select menus / algolia facet filters - // docsPluginId -> versionName map - const [searchVersions, setSearchVersions] = useState<{ - [pluginId: string]: string - }>(() => - Object.entries(allDocsData).reduce( - (acc, [pluginId, pluginData]) => ({ - ...acc, - [pluginId]: pluginData.versions[0]!.name, - }), - {} - ) - ) - - // Set the value of a single select menu - const setSearchVersion = (pluginId: string, searchVersion: string) => - setSearchVersions((s) => ({ ...s, [pluginId]: searchVersion })) - - const versioningEnabled = Object.values(allDocsData).some( - (docsData) => docsData.versions.length > 1 - ) - - return { - allDocsData, - versioningEnabled, - searchVersions, - setSearchVersion, - } -} - -// We want to display one select per versioned docs plugin instance -function SearchVersionSelectList({ - docsSearchVersionsHelpers, -}: { - docsSearchVersionsHelpers: ReturnType -}) { - const versionedPluginEntries = Object.entries( - docsSearchVersionsHelpers.allDocsData - ) - // Do not show a version select for unversioned docs plugin instances - .filter(([, docsData]) => docsData.versions.length > 1) - - return ( -
    - {versionedPluginEntries.map(([pluginId, docsData]) => { - const labelPrefix = - versionedPluginEntries.length > 1 ? `${pluginId}: ` : "" - return ( - - ) - })} -
    - ) -} - -type ResultDispatcherState = { - items: { - title: string - url: string - summary: string - breadcrumbs: string[] - }[] - query: string | null - totalResults: number | null - totalPages: number | null - lastPage: number | null - hasMore: boolean | null - loading: boolean | null -} - -type ResultDispatcher = - | { type: "reset"; value?: undefined } - | { type: "loading"; value?: undefined } - | { type: "update"; value: ResultDispatcherState } - | { type: "advance"; value?: undefined } - -function SearchPageContent(): JSX.Element { - const { - siteConfig: { themeConfig }, - } = useDocusaurusContext() - const { - algolia: { appId, apiKey, indexName }, - } = themeConfig as ThemeConfig - const processSearchResultUrl = useSearchResultUrlProcessor() - const documentsFoundPlural = useDocumentsFoundPlural() - - const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers() - const [searchQuery, setSearchQuery] = useSearchQueryString() - const initialSearchResultState: ResultDispatcherState = { - items: [], - query: null, - totalResults: null, - totalPages: null, - lastPage: null, - hasMore: null, - loading: null, - } - const [searchResultState, searchResultStateDispatcher] = useReducer( - (prevState: ResultDispatcherState, data: ResultDispatcher) => { - switch (data.type) { - case "reset": { - return initialSearchResultState - } - case "loading": { - return { ...prevState, loading: true } - } - case "update": { - if (searchQuery !== data.value.query) { - return prevState - } - - return { - ...data.value, - items: - data.value.lastPage === 0 - ? data.value.items - : prevState.items.concat(data.value.items), - } - } - case "advance": { - const hasMore = prevState.totalPages! > prevState.lastPage! + 1 - - return { - ...prevState, - lastPage: hasMore ? prevState.lastPage! + 1 : prevState.lastPage, - hasMore, - } - } - default: - return prevState - } - }, - initialSearchResultState - ) - - const algoliaClient = algoliaSearch(appId, apiKey) - const algoliaHelper = algoliaSearchHelper(algoliaClient, indexName, { - hitsPerPage: 15, - advancedSyntax: true, - disjunctiveFacets: ["language", "docusaurus_tag"], - }) - - algoliaHelper.on( - "result", - ({ results: { query, hits, page, nbHits, nbPages } }) => { - if (query === "" || !Array.isArray(hits)) { - searchResultStateDispatcher({ type: "reset" }) - return - } - - const sanitizeValue = (value: string) => - value.replace( - /algolia-docsearch-suggestion--highlight/g, - "search-result-match" - ) - - const items = hits.map( - ({ - url, - _highlightResult: { hierarchy }, - _snippetResult: snippet = {}, - }: { - url: string - _highlightResult: { hierarchy: { [key: string]: { value: string } } } - _snippetResult: { content?: { value: string } } - }) => { - const titles = Object.keys(hierarchy).map((key) => - sanitizeValue(hierarchy[key]!.value) - ) - return { - title: titles.pop()!, - url: processSearchResultUrl(url), - summary: snippet.content - ? `${sanitizeValue(snippet.content.value)}...` - : "", - breadcrumbs: titles, - } - } - ) - - searchResultStateDispatcher({ - type: "update", - value: { - items, - query, - totalResults: nbHits, - totalPages: nbPages, - lastPage: page, - hasMore: nbPages > page + 1, - loading: false, - }, - }) - } - ) - - const [loaderRef, setLoaderRef] = useState(null) - const prevY = useRef(0) - const observer = useRef( - ExecutionEnvironment.canUseIntersectionObserver && - new IntersectionObserver( - (entries) => { - const { - isIntersecting, - boundingClientRect: { y: currentY }, - } = entries[0]! - - if (isIntersecting && prevY.current > currentY) { - searchResultStateDispatcher({ type: "advance" }) - } - - prevY.current = currentY - }, - { threshold: 1 } - ) - ) - - const getTitle = () => { - return searchQuery - ? translate( - { - id: "theme.SearchPage.existingResultsTitle", - message: `Search results for "{query}"`, - description: "The search page title for non-empty query", - }, - { - query: searchQuery, - } - ) - : translate({ - id: "theme.SearchPage.emptyResultsTitle", - message: "Search the documentation", - description: "The search page title for empty query", - }) - } - - const makeSearch = useEvent((page?: number) => { - // These commented out line are from algolia's implementation - // we might need them in the future - // algoliaHelper.addDisjunctiveFacetRefinement("docusaurus_tag", "default") - // algoliaHelper.addDisjunctiveFacetRefinement("language", currentLocale) - - // Object.entries(docsSearchVersionsHelpers.searchVersions).forEach( - // ([pluginId, searchVersion]) => { - // algoliaHelper.addDisjunctiveFacetRefinement( - // "docusaurus_tag", - // `docs-${pluginId}-${searchVersion}` - // ) - // } - // ) - - algoliaHelper - .setQuery(searchQuery) - .setPage(page || 0) - .search() - }) - - useEffect(() => { - if (!loaderRef) { - return undefined - } - const currentObserver = observer.current - if (currentObserver) { - currentObserver.observe(loaderRef) - return () => currentObserver.unobserve(loaderRef) - } - return () => true - }, [loaderRef]) - - useEffect(() => { - searchResultStateDispatcher({ type: "reset" }) - - if (searchQuery) { - searchResultStateDispatcher({ type: "loading" }) - - setTimeout(() => { - makeSearch() - }, 300) - } - }, [searchQuery, docsSearchVersionsHelpers.searchVersions, makeSearch]) - - useEffect(() => { - if (!searchResultState.lastPage || searchResultState.lastPage === 0) { - return - } - - makeSearch(searchResultState.lastPage) - }, [makeSearch, searchResultState.lastPage]) - - return ( - - - {useTitleFormatter(getTitle())} - {/* - We should not index search pages - See https://github.com/facebook/docusaurus/pull/3233 - */} - - - -
    -

    {getTitle()}

    - -
    e.preventDefault()}> -
    - setSearchQuery(e.target.value)} - value={searchQuery} - autoComplete="off" - autoFocus - /> -
    - - {docsSearchVersionsHelpers.versioningEnabled && ( - - )} - - -
    -
    - {!!searchResultState.totalResults && - documentsFoundPlural(searchResultState.totalResults)} -
    -
    - - {searchResultState.items.length > 0 ? ( -
    - {searchResultState.items.map( - ({ title, url, summary, breadcrumbs }, i) => ( -
    -

    - -

    - - {breadcrumbs.length > 0 && ( - - )} - - {summary && ( -

    - )} -

    - ) - )} -
    - ) : ( - [ - searchQuery && !searchResultState.loading && ( -

    - - No results were found - -

    - ), - !!searchResultState.loading && ( -
    - ), - ] - )} - - {searchResultState.hasMore && ( -
    - - Fetching new results... - -
    - )} -
    - - ) -} - -export default function SearchPage(): JSX.Element { - return ( - - - - - - ) -} diff --git a/www/docs/src/types/index.d.ts b/www/docs/src/types/index.d.ts index 3772644316..c80ca65a48 100644 --- a/www/docs/src/types/index.d.ts +++ b/www/docs/src/types/index.d.ts @@ -131,6 +131,12 @@ declare module "@medusajs/docs" { export declare type NavbarAction = NavbarActionLink | NavbarActionButton + export declare type OptionType = { + value: string + label: string + isAllOption?: boolean + } + export declare type ThemeConfig = { reportCodeLinkPrefix?: string footerFeedback: { @@ -154,6 +160,20 @@ declare module "@medusajs/docs" { magicComments: MagicCommentConfig[] } mobileLogo: NavbarLogo + algoliaConfig?: { + apiKey: string + indexNames: { + docs: string + api: string + } + appId: string + filters: OptionType[] + defaultFilters: string[] + defaultFiltersByPath: { + path: string + filters: string[] + }[] + } } & DocusaurusThemeConfig export declare type MedusaDocusaurusConfig = { diff --git a/www/docs/src/utils/array-same-elms.ts b/www/docs/src/utils/array-same-elms.ts new file mode 100644 index 0000000000..a8f6810917 --- /dev/null +++ b/www/docs/src/utils/array-same-elms.ts @@ -0,0 +1,10 @@ +export default function checkArraySameElms( + arr1: Array, + arr2: Array +): boolean { + if (arr1.length !== arr2.length) { + return false + } + + return arr1.every((value, index) => value === arr2[index]) +} diff --git a/www/docs/src/utils/dom-utils.ts b/www/docs/src/utils/dom-utils.ts new file mode 100644 index 0000000000..cb2ef70b12 --- /dev/null +++ b/www/docs/src/utils/dom-utils.ts @@ -0,0 +1,29 @@ +export function findPrevSibling( + element: HTMLElement, + selector: string +): HTMLElement | null { + let prevElement = element.previousElementSibling + while (prevElement !== null) { + if (prevElement.matches(selector)) { + return prevElement as HTMLElement + } + prevElement = prevElement.previousElementSibling + } + + return null +} + +export function findNextSibling( + element: HTMLElement, + selector: string +): HTMLElement | null { + let nextElement = element.nextElementSibling + while (nextElement !== null) { + if (nextElement.matches(selector)) { + return nextElement as HTMLElement + } + nextElement = nextElement.nextElementSibling + } + + return null +} diff --git a/www/docs/tailwind.config.js b/www/docs/tailwind.config.js index 41d4769531..65f6cd6c3e 100644 --- a/www/docs/tailwind.config.js +++ b/www/docs/tailwind.config.js @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ import coreConfig from "../tailwind.config" /** @type {import('tailwindcss').Config} */ @@ -26,4 +27,9 @@ module.exports = { }, }, }, + plugins: [ + require("tailwindcss/plugin")(({ addVariant }) => { + addVariant("search-cancel", "&::-webkit-search-cancel-button") + }), + ], } diff --git a/www/docs/yarn.lock b/www/docs/yarn.lock index 460d803410..9295ad07ce 100644 --- a/www/docs/yarn.lock +++ b/www/docs/yarn.lock @@ -172,6 +172,23 @@ __metadata: languageName: node linkType: hard +"@algolia/ui-components-highlight-vdom@npm:^1.2.1": + version: 1.2.1 + resolution: "@algolia/ui-components-highlight-vdom@npm:1.2.1" + dependencies: + "@algolia/ui-components-shared": 1.2.1 + "@babel/runtime": ^7.0.0 + checksum: cb768905ac19cde9b491e9d3ed9aa0cd9bd1c2db5a3fb723cbc6668e00917b3ca98864eb37dc5d9736fcfcd0951a695201f81b3994651d25eaa122a124549851 + languageName: node + linkType: hard + +"@algolia/ui-components-shared@npm:1.2.1, @algolia/ui-components-shared@npm:^1.2.1": + version: 1.2.1 + resolution: "@algolia/ui-components-shared@npm:1.2.1" + checksum: 0ebbe1411a257efcd5b4535302ad85037c2b7de658fd05d0da681ed9eaebf66734a2327e08b20a9700a4cd6b2396463a5f91981aa35dc5b0adbb8d20aca96320 + languageName: node + linkType: hard + "@alloc/quick-lru@npm:^5.2.0": version: 5.2.0 resolution: "@alloc/quick-lru@npm:5.2.0" @@ -1610,6 +1627,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.0.0": + version: 7.22.10 + resolution: "@babel/runtime@npm:7.22.10" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: d3a006fe2cbaf4048b935fb18f55d9ed52c26292182537b442cee57bf524dbb483367c57f464b1a5a96648d9d8d0fdcda848d58a8a09e18ed3f8971dcd684c6c + languageName: node + linkType: hard + "@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.3, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": version: 7.21.0 resolution: "@babel/runtime@npm:7.21.0" @@ -2963,6 +2989,13 @@ __metadata: languageName: node linkType: hard +"@types/dom-speech-recognition@npm:^0.0.1": + version: 0.0.1 + resolution: "@types/dom-speech-recognition@npm:0.0.1" + checksum: 1df9283e40476f82b15cc7691c2f1177a185bf98af63d068f9333fbf4e334d2584b70babe2b9c69fcbe3c74293fcc0d47ce98c5717d8db361e70d88a8fbf9490 + languageName: node + linkType: hard + "@types/eslint-scope@npm:^3.7.3": version: 3.7.4 resolution: "@types/eslint-scope@npm:3.7.4" @@ -3013,6 +3046,13 @@ __metadata: languageName: node linkType: hard +"@types/google.maps@npm:^3.45.3": + version: 3.53.6 + resolution: "@types/google.maps@npm:3.53.6" + checksum: 08c43dc680edcc7f9f66262fe3598b2c7e250fa0240ec005daece1b16c125344e909c5cb865d22450d6e6cad2cd4f28cbb1e51f038627d78c21cc76768077f15 + languageName: node + linkType: hard + "@types/hast@npm:^2.0.0": version: 2.3.4 resolution: "@types/hast@npm:2.3.4" @@ -3029,6 +3069,13 @@ __metadata: languageName: node linkType: hard +"@types/hogan.js@npm:^3.0.0": + version: 3.0.1 + resolution: "@types/hogan.js@npm:3.0.1" + checksum: 8d2dc2809063852710a559746c8275af97bf5c84be1ad40fe240d8b5c91d266b0f841dc9774b5d1461ea42679b4dfeb4f1865a94995fc61a8e4081d3f11dc266 + languageName: node + linkType: hard + "@types/html-minifier-terser@npm:^6.0.0": version: 6.1.0 resolution: "@types/html-minifier-terser@npm:6.1.0" @@ -3169,7 +3216,7 @@ __metadata: languageName: node linkType: hard -"@types/qs@npm:*": +"@types/qs@npm:*, @types/qs@npm:^6.5.3": version: 6.9.7 resolution: "@types/qs@npm:6.9.7" checksum: 157eb05f4c75790b0ebdcf7b0547ff117feabc8cda03c3cac3d3ea82bb19a1912e76a411df3eb0bdd01026a9770f07bc0e7e3fbe39ebb31c1be4564c16be35f1 @@ -3627,7 +3674,7 @@ __metadata: languageName: node linkType: hard -"abbrev@npm:^1.0.0": +"abbrev@npm:1, abbrev@npm:^1.0.0": version: 1.1.1 resolution: "abbrev@npm:1.1.1" checksum: 3f762677702acb24f65e813070e306c61fafe25d4b2583f9dfc935131f774863f3addd5741572ed576bd69cabe473c5af18e1e108b829cb7b6b4747884f726e6 @@ -3773,6 +3820,17 @@ __metadata: languageName: node linkType: hard +"algoliasearch-helper@npm:3.14.0": + version: 3.14.0 + resolution: "algoliasearch-helper@npm:3.14.0" + dependencies: + "@algolia/events": ^4.0.1 + peerDependencies: + algoliasearch: ">= 3.1 < 6" + checksum: 8c60aae2bcaa3f8eb547fd48cec0089a329dc5fec05e6c7364642fb2353256f11e4402ea3cec58c4a2bdad6a1720980fbd7dbab51be0b37b13a26b78705ddcc9 + languageName: node + linkType: hard + "algoliasearch-helper@npm:^3.10.0, algoliasearch-helper@npm:^3.11.3": version: 3.12.0 resolution: "algoliasearch-helper@npm:3.12.0" @@ -5419,6 +5477,7 @@ __metadata: prism-react-renderer: ^1.3.1 react: ^17.0.1 react-dom: ^17.0.1 + react-instantsearch: ^7.0.1 react-tooltip: 5.7.4 react-transition-group: ^4.4.5 react-uuid: ^2.0.0 @@ -7024,6 +7083,18 @@ __metadata: languageName: node linkType: hard +"hogan.js@npm:^3.0.2": + version: 3.0.2 + resolution: "hogan.js@npm:3.0.2" + dependencies: + mkdirp: 0.3.0 + nopt: 1.0.10 + bin: + hulk: ./bin/hulk + checksum: fa5c9d2eaf3fa712e72e67cce5e3435a1c5823282b81051514aefdca7d4b706cc4dbef7a34be19ee320c6ebaf3687d5781f12bc0aac04d3d902aa26861493679 + languageName: node + linkType: hard + "hoist-non-react-statics@npm:^3.1.0": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" @@ -7045,6 +7116,13 @@ __metadata: languageName: node linkType: hard +"htm@npm:^3.0.0": + version: 3.1.1 + resolution: "htm@npm:3.1.1" + checksum: 0de4c8fff2b8e76c162235ae80dbf93ca5eef1575bd50596a06ce9bebf1a6da5efc467417c53034a9ffa2ab9ecff819cbec041dc9087894b2b900ad4de26c7e7 + languageName: node + linkType: hard + "html-entities@npm:^2.3.2": version: 2.3.3 resolution: "html-entities@npm:2.3.3" @@ -7376,6 +7454,29 @@ __metadata: languageName: node linkType: hard +"instantsearch.js@npm:4.56.9": + version: 4.56.9 + resolution: "instantsearch.js@npm:4.56.9" + dependencies: + "@algolia/events": ^4.0.1 + "@algolia/ui-components-highlight-vdom": ^1.2.1 + "@algolia/ui-components-shared": ^1.2.1 + "@types/dom-speech-recognition": ^0.0.1 + "@types/google.maps": ^3.45.3 + "@types/hogan.js": ^3.0.0 + "@types/qs": ^6.5.3 + algoliasearch-helper: 3.14.0 + hogan.js: ^3.0.2 + htm: ^3.0.0 + preact: ^10.10.0 + qs: ^6.5.1 < 6.10 + search-insights: ^2.6.0 + peerDependencies: + algoliasearch: ">= 3.1 < 6" + checksum: acde452a6da992a01148cdcba89217292e624e2dce77682236b36f7d119586afbcc9f0c8f74cae49e81652ba1e79d72fed0a1aba29bbce0463e115fd1223e49d + languageName: node + linkType: hard + "internal-slot@npm:^1.0.3, internal-slot@npm:^1.0.5": version: 1.0.5 resolution: "internal-slot@npm:1.0.5" @@ -8616,6 +8717,13 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:0.3.0": + version: 0.3.0 + resolution: "mkdirp@npm:0.3.0" + checksum: cd9e54878490571df79770de1cdceba48ab6682c004616666d23a38315feaf5822d443aeb500ac298a12d7f6f5e11dc05cea3207d500e547d938218bf22d8629 + languageName: node + linkType: hard + "mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" @@ -8794,6 +8902,17 @@ __metadata: languageName: node linkType: hard +"nopt@npm:1.0.10": + version: 1.0.10 + resolution: "nopt@npm:1.0.10" + dependencies: + abbrev: 1 + bin: + nopt: ./bin/nopt.js + checksum: ddfbd892116a125fd68849ef564dd5b1f0a5ba0dbbf18782e9499e2efad8f4d3790635b47c6b5d3f7e014069e7b3ce5b8112687e9ae093fcd2678188c866fe28 + languageName: node + linkType: hard + "nopt@npm:^6.0.0": version: 6.0.0 resolution: "nopt@npm:6.0.0" @@ -9855,6 +9974,13 @@ __metadata: languageName: node linkType: hard +"preact@npm:^10.10.0": + version: 10.17.1 + resolution: "preact@npm:10.17.1" + checksum: 6a264832afacb2e5c6ffd3bdbbb85cfbb1c5f8e33c616885cdbd8d07474819b6740c834dc3a749f6986366a57cdc33feba10680b2a65dc51d5e40a9c68c7b641 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -10042,6 +10168,13 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.5.1 < 6.10": + version: 6.9.7 + resolution: "qs@npm:6.9.7" + checksum: d0274b3c2daa9e7b350fb695fc4b5f7a1e65e266d5798a07936975f0848bdca6d7ad41cded19ad4af6a6253b97e43b497e988e728eab7a286f277b6dddfbade4 + languageName: node + linkType: hard + "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -10194,6 +10327,36 @@ __metadata: languageName: node linkType: hard +"react-instantsearch-core@npm:7.0.1": + version: 7.0.1 + resolution: "react-instantsearch-core@npm:7.0.1" + dependencies: + "@babel/runtime": ^7.1.2 + algoliasearch-helper: 3.14.0 + instantsearch.js: 4.56.9 + use-sync-external-store: ^1.0.0 + peerDependencies: + algoliasearch: ">= 3.1 < 5" + react: ">= 16.8.0 < 19" + checksum: 7796a0c8c9b7105aa31dc64762b37c72bccd8342d6cbdd6b55c3cb740f48af2fac5d8ba6b735b4739531720cf0ca48a478b7829728a237cd434abf4ab8fd2ff6 + languageName: node + linkType: hard + +"react-instantsearch@npm:^7.0.1": + version: 7.0.1 + resolution: "react-instantsearch@npm:7.0.1" + dependencies: + "@babel/runtime": ^7.1.2 + instantsearch.js: 4.56.9 + react-instantsearch-core: 7.0.1 + peerDependencies: + algoliasearch: ">= 3.1 < 5" + react: ">= 16.8.0 < 19" + react-dom: ">= 16.8.0 < 19" + checksum: 05cdafd17f6cf0523d29ebd9542b81a054fe0ef426a507b9d91653b35f5f567c3d0fb31b47feee600939f4feb561ae992cc4398e7d1aaa120029a01583088843 + languageName: node + linkType: hard + "react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" @@ -10433,6 +10596,13 @@ __metadata: languageName: node linkType: hard +"regenerator-runtime@npm:^0.14.0": + version: 0.14.0 + resolution: "regenerator-runtime@npm:0.14.0" + checksum: e25f062c1a183f81c99681691a342760e65c55e8d3a4d4fe347ebe72433b123754b942b70b622959894e11f8a9131dc549bd3c9a5234677db06a4af42add8d12 + languageName: node + linkType: hard + "regenerator-transform@npm:^0.15.1": version: 0.15.1 resolution: "regenerator-transform@npm:0.15.1" @@ -10877,6 +11047,13 @@ __metadata: languageName: node linkType: hard +"search-insights@npm:^2.6.0": + version: 2.7.0 + resolution: "search-insights@npm:2.7.0" + checksum: b21701b88f9fcfc6e9c13c5b48aa61a5be2ac73d0a3fc5a9c50eea04b0465936d8badb5093fdba0310997b58b5d3d562a2e4bd681825c74199a4314846052064 + languageName: node + linkType: hard + "section-matter@npm:^1.0.0": version: 1.0.0 resolution: "section-matter@npm:1.0.0" @@ -12215,7 +12392,7 @@ __metadata: languageName: node linkType: hard -"use-sync-external-store@npm:^1.2.0": +"use-sync-external-store@npm:^1.0.0, use-sync-external-store@npm:^1.2.0": version: 1.2.0 resolution: "use-sync-external-store@npm:1.2.0" peerDependencies: diff --git a/www/tailwind.config.js b/www/tailwind.config.js index 68f2f7cf85..544e48092b 100644 --- a/www/tailwind.config.js +++ b/www/tailwind.config.js @@ -102,38 +102,38 @@ module.exports = { bg: { subtle: { DEFAULT: "#F9FAFB", - dark: "#0A0A0A", + dark: "#18181A", hover: { DEFAULT: "#F3F4F6", - dark: "#171717", + dark: "#1B1B1F", }, pressed: { DEFAULT: "#E5E7EB", - dark: "#262626", + dark: "#27282D", }, }, base: { DEFAULT: "#FFFFFF", - dark: "#171717", + dark: "#1B1B1F", hover: { DEFAULT: "#F9FAFB", - dark: "#262626", + dark: "#27282D", }, pressed: { DEFAULT: "#F3F4F6", - dark: "#303030", + dark: "#2E3035", }, }, component: { DEFAULT: "#F1F3F5", - dark: "#262626", + dark: "#27282D", }, - "toggle-off": { + "switch-off": { DEFAULT: "#E5E7EB", - dark: "#262626", + dark: "#35373C", hover: { DEFAULT: "#E5E7EB", - dark: "#303030" + dark: "#464B50" } }, interactive: { @@ -142,54 +142,54 @@ module.exports = { }, overlay: { DEFAULT: "rgba(3, 7, 18, 0.4)", - dark: "rgba(10, 10, 10, 0.70)", + dark: "rgba(24, 24, 26, 0.7)", }, disabled: { DEFAULT: "#F3F4F6", - dark: "#262626" + dark: "#27282D" }, highlight: { DEFAULT: "#EFF6FF", dark: "#172554", hover: { DEFAULT: "#DBEAFE", - dark: "#1E3A8A" // TODO change + dark: "#1E3A8A" } }, field: { DEFAULT: "#F9FAFB", - dark: "#262626", + dark: "#27282D", hover: { DEFAULT: "#F3F4F6", - dark: "#303030" + dark: "#2E3035" } } }, fg: { base: { DEFAULT: "#030712", - dark: "#EEEEEE" + dark: "#EDEEF0" }, subtle: { DEFAULT: "#4B5563", - dark: "#A3A3A3" + dark: "#ADB1B8" }, muted: { DEFAULT: "#9CA3AF", - dark: "#6E6E6E" + dark: "#696E77" }, disabled: { DEFAULT: "#D1D5DB", - dark: "#3F3F3F" + dark: "#3C3F44" }, on: { color: { DEFAULT: "#FFFFFF", - dark: "#EEEEEE" + dark: "#FFFFFF" }, inverted: { DEFAULT: "#FFFFFF", - dark: "#0A0A0A" + dark: "#18181A" } }, interactive: { @@ -202,29 +202,21 @@ module.exports = { }, error: { DEFAULT: "#E11D48", - dark: "#E11D48" + dark: "#FB7185" } }, border: { base: { DEFAULT: "#E5E7EB", - dark: "#303030", + dark: "#2E3035", }, strong: { DEFAULT: "#D1D5DB", - dark: "#373737", + dark: "#35373C", }, loud: { DEFAULT: "#030712", - dark: "#EEEEEE", - muted: { - DEFAULT: "rgba(3, 7, 18, 0.1)", - dark: "rgba(238, 238, 238, 0.1)" - }, - transparent: { - DEFAULT: "rgba(3, 7, 18, 0)", - dark: "rgba(238, 238, 238, 0)" - } + dark: "#EDEEF0", }, interactive: { DEFAULT: "#3B82F6", @@ -236,33 +228,48 @@ module.exports = { }, danger: { DEFAULT: "#BE123C", - dark: "#E11D48" + dark: "#BE123C" }, + transparent: { + DEFAULT: "rgba(3, 7, 18, 0)", + dark: "rgba(238, 238, 238, 0)" + } }, button: { inverted: { + DEFAULT: "#030712", + dark: "#EDEEF0", hover: { - DEFAULT: "#0A0A0A", - dark: "#EEEEEE" + DEFAULT: "#1F2937", + dark: "#FFFFFF" }, pressed: { - DEFAULT: "#0A0A0A", - dark: "#EEEDEF" + DEFAULT: "#374151", + dark: "#EDEEF0" + }, + }, + neutral: { + DEFAULT: "#FFFFFF", + dark: "#27282D", + hover: { + DEFAULT: "#F9FAFB", + dark: "#35373C", + }, + pressed: { + DEFAULT: "#F3F4F6", + dark: "#3C3F44", }, - // TODO remove if not updated - DEFAULT: "#0A0A0A", - dark: "#EEEEEE", }, danger: { DEFAULT: "#E11D48", dark: "#9F1239", hover: { DEFAULT: "#E11D48", - dark: "#9F1239", + dark: "#BE123C", }, pressed: { - DEFAULT: "#E11D48", - dark: "#E5484D", + DEFAULT: "#BE123C", + dark: "#E11D48", }, }, transparent: { @@ -270,23 +277,11 @@ module.exports = { dark: "rgba(0, 0, 0, 0)", hover: { DEFAULT: "#F9FAFB", - dark: "#262626", + dark: "#27282D", }, pressed: { DEFAULT: "#F3F4F6", - dark: "#303030", - }, - }, - neutral: { - DEFAULT: "#FFFFFF", - dark: "#262626", - hover: { - DEFAULT: "#FFFFFF", - dark: "#262626", - }, - pressed: { - DEFAULT: "#FFFFFF", - dark: "#262626", + dark: "#2E3035", }, }, disabled: { @@ -298,23 +293,23 @@ module.exports = { neutral: { bg: { DEFAULT: "#F3F4F6", - dark: "#262626", + dark: "#2E3035", hover: { DEFAULT: "#E5E7EB", - dark: "#303030", + dark: "#35373C", }, }, text: { DEFAULT: "#4B5563", - dark: "#818181", + dark: "#ADB1B8", }, icon: { DEFAULT: "#6B7280", - dark: "#6E6E6E", + dark: "#7D828A", }, border: { DEFAULT: "#E5E7EB", - dark: "#34343A", + dark: "#3C3F44", }, }, purple: { @@ -416,11 +411,11 @@ module.exports = { }, text: { DEFAULT: "#BE123C", - dark: "#F43F5E", + dark: "#FB7185", }, icon: { DEFAULT: "#E11D48", - dark: "#BE123C", + dark: "#F43F5E", }, border: { DEFAULT: "#FECDD3", @@ -432,31 +427,31 @@ module.exports = { text: { base: { DEFAULT: "#F9FAFB", - dark: "#EEEEEE" + dark: "#EDEEF0" }, subtle: { DEFAULT: "#9CA3AF", - dark: "#6E6E6E" + dark: "#696E77" }, highlight: "#102A4C" }, icon: { DEFAULT: "#6B7280", - dark: "#4A4A4A" + dark: "#464B50" }, bg: { base: { DEFAULT: "#111827", - dark: "#1E1E1E" + dark: "#1B1B1F" }, header: { DEFAULT: "#1F2937", - dark: "#171717" + dark: "#18181A" } }, border: { DEFAULT: "#374151", - dark: "#303030" + dark: "#2E3035" }, }, }, @@ -494,14 +489,14 @@ module.exports = { "modal-dark": "0px 2px 24px 0px rgba(0, 0, 0, 0.32), 0px 16px 32px 0px rgba(0, 0, 0, 0.32), 0px 0px 0px 1px rgba(255, 255, 255, 0.10)", "navbar-dark": "0px 1px 0px 0px #2E2E32", - "button-colored": "0px 1px 0px 0px rgba(0, 0, 0, 0.12), 0px 0.5px 0px 0px rgba(0, 0, 0, 0.12), 0px 2px 0px 0px rgba(255, 255, 255, 0.16) inset", - "button-colored-dark": "0px 1px 0px 0px rgba(0, 0, 0, 0.40), 0px 0.5px 0px 0px rgba(0, 0, 0, 0.40)", - "button-colored-focused": "0px 0px 0px 3px rgba(59, 130, 246, 0.60), 0px 0px 0px 1px #FFF, 0px 1px 0px 0px rgba(0, 0, 0, 0.12), 0px 0.5px 0px 0px rgba(0, 0, 0, 0.12), 0px 2px 0px 0px rgba(255, 255, 255, 0.16) inset", - "button-colored-focused-dark": "0px 0px 0px 3px rgba(96, 165, 250, 0.80), 0px 0px 0px 1px #171717, 0px 1px 0px 0px rgba(0, 0, 0, 0.40), 0px 0.5px 0px 0px rgba(0, 0, 0, 0.40)", - "button-neutral": "0px 1px 0px 0px rgba(0, 0, 0, 0.12), 0px 0.5px 0px 0px rgba(0, 0, 0, 0.12)", - "button-neutral-dark": "0px 1px 0px 0px rgba(0, 0, 0, 0.40), 0px 0.5px 0px 0px rgba(0, 0, 0, 0.40), 0px 1.5px 0px 0px rgba(255, 255, 255, 0.10) inset", - "button-neutral-focused": "0px 0px 0px 3px rgba(59, 130, 246, 0.60), 0px 0px 0px 1px #FFF, 0px 1px 0px 0px rgba(0, 0, 0, 0.12), 0px 0.5px 0px 0px rgba(0, 0, 0, 0.12)", - "button-neutral-focused-dark": "0px 0px 0px 3px rgba(96, 165, 250, 0.80), 0px 0px 0px 1px #171717, 0px 1px 0px 0px rgba(0, 0, 0, 0.40), 0px 0.5px 0px 0px rgba(0, 0, 0, 0.40), 0px 1.5px 0px 0px rgba(255, 255, 255, 0.10) inset", + "button-colored": "0px 0.5px 0px 0px rgba(3, 7, 18, 0.16), 0px 0.25px 0px 0px rgba(3, 7, 18, 0.16), 0px 1.75px 0px 0px rgba(255, 255, 255, 0.16) inset", + "button-colored-dark": "0px 0.5px 0px 0px rgba(0, 0, 0, 0.60), 0px 0.25px 0px 0px rgba(0, 0, 0, 0.60)", + "button-colored-focused": "0px 0px 0px 3px rgba(59, 130, 246, 0.60), 0px 0px 0px 1px #FFF, 0px 0.5px 0px 0px rgba(3, 7, 18, 0.20), 0px 0.25px 0px 0px rgba(3, 7, 18, 0.20), 0px 1.75px 0px 0px rgba(255, 255, 255, 0.16) inset", + "button-colored-focused-dark": "0px 0px 0px 3px rgba(96, 165, 250, 0.80), 0px 0px 0px 1px #1B1B1F, 0px 0.5px 0px 0px rgba(0, 0, 0, 0.60), 0px 0.25px 0px 0px rgba(0, 0, 0, 0.60)", + "button-neutral": "0px 0.5px 0px 0px rgba(3, 7, 18, 0.16), 0px 0.25px 0px 0px rgba(3, 7, 18, 0.16)", + "button-neutral-dark": "0px 0.5px 0px 0px rgba(0, 0, 0, 0.60), 0px 0.25px 0px 0px rgba(0, 0, 0, 0.60), 0px 1.5px 0px 0px rgba(255, 255, 255, 0.10) inset", + "button-neutral-focused": "0px 0px 0px 3px rgba(59, 130, 246, 0.60), 0px 0px 0px 1px #FFF, 0px 0.5px 0px 0px rgba(3, 7, 18, 0.16), 0px 0.25px 0px 0px rgba(3, 7, 18, 0.16)", + "button-neutral-focused-dark": "0px 0px 0px 3px rgba(96, 165, 250, 0.80), 0px 0px 0px 1px #1B1B1F, 0px 0.5px 0px 0px rgba(0, 0, 0, 0.60), 0px 0.25px 0px 0px rgba(0, 0, 0, 0.60), 0px 1.5px 0px 0px rgba(255, 255, 255, 0.10) inset", "button-secondary": "0px 1px 1px 0px rgba(3, 7, 18, 0.06), 0px -1px 0px 0px rgba(3, 7, 18, 0.08) inset", "button-secondary-dark": "0px 1px 1px 0px rgba(3, 7, 18, 0.06), 0px -1px 0px 0px rgba(3, 7, 18, 0.08) inset", // TODO change "button-secondary-focus": "0px 0px 0px 3px rgba(59, 130, 246, 0.60), 0px 0px 0px 1px #FFF, 0px 1px 1px 0px rgba(3, 7, 18, 0.06), 0px -1px 0px 0px rgba(3, 7, 18, 0.08) inset", @@ -510,36 +505,28 @@ module.exports = { active: "0px 0px 0px 3px #E1F0FF", "active-dark": "0px 0px 0px 3px #2C2250", navbar: "0px 1px 0px 0px #E6E8EB", - "button-primary": "0px 1px 1px 0px rgba(3, 7, 18, 0.20), 0px 2px 0px 0px rgba(255, 255, 255, 0.16) inset", - "button-primary-focus": "0px 0px 0px 3px rgba(59, 130, 246, 0.60), 0px 0px 0px 1px #FFF, 0px 1px 1px 0px rgba(3, 7, 18, 0.20), 0px 2px 0px 0px rgba(255, 255, 255, 0.16) inset", }, borderRadius: { - DEFAULT: "8px", - sm: "6px", - xs: "4px", xxs: "2px", - lg: "16px", + xs: "4px", + sm: "6px", + DEFAULT: "8px", + md: "8px", + lg: "12px", + xl: "16px", }, lineHeight: { DEFAULT: "24px", }, backgroundImage: { "button-neutral": - "linear-gradient(180deg, rgba(3, 7, 18, 0.00) 0%, rgba(3, 7, 18, 0.02) 100%)", + "linear-gradient(180deg, rgba(3, 7, 18, 0.00) 0%, rgba(3, 7, 18, 0.03) 100%)", "button-neutral-dark": - "linear-gradient(180deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.00) 100%)", - "button-neutral-hover": "linear-gradient(180deg, rgba(3, 7, 18, 0.00) 0%, rgba(3, 7, 18, 0.06) 100%)", - "button-neutral-hover-dark": "linear-gradient(180deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.00) 100%)", - "button-neutral-pressed": "linear-gradient(180deg, rgba(3, 7, 18, 0.06) 0%, rgba(3, 7, 18, 0.00) 100%)", - "button-neutral-pressed-dark": "linear-gradient(180deg, rgba(255, 255, 255, 0.00) 0%, rgba(255, 255, 255, 0.08) 100%)", + "linear-gradient(180deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.00) 100%)", "no-image": "none", - "button-inverted": "linear-gradient(180deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.00) 100%)", + "button-inverted": "linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0.00) 100%)", "button-inverted-dark": "linear-gradient(180deg, rgba(0, 0, 0, 0.00) 0%, rgba(0, 0, 0, 0.12) 100%)", - "button-inverted-hover": "linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0.00) 100%)", - "button-inverted-hover-dark": "linear-gradient(180deg, rgba(0, 0, 0, 0.00) 0%, rgba(0, 0, 0, 0.16) 100%)", - "button-inverted-pressed": "linear-gradient(180deg, rgba(255, 255, 255, 0.00) 0%, rgba(255, 255, 255, 0.16) 100%)", - "button-inverted-pressed-dark": "linear-gradient(180deg, rgba(0, 0, 0, 0.16) 0%, rgba(0, 0, 0, 0.00) 100%)", "button-danger": "linear-gradient(180deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.00) 100%)", "button-danger-dark": "linear-gradient(180deg, rgba(255, 255, 255, 0.10) 0%, rgba(255, 255, 255, 0.00) 100%)", "button-danger-hover": "linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0.00) 100%)", @@ -548,9 +535,8 @@ module.exports = { "button-danger-pressed-dark": "linear-gradient(180deg, rgba(255, 255, 255, 0.00) 0%, rgba(255, 255, 255, 0.14) 100%)", "code-fade": "linear-gradient(90deg, #11182700, #111827 24px)", "code-fade-dark": "linear-gradient(90deg, #1E1E1E00, #1E1E1E 24px)", - // TODO remove if not used - "docs-button-neutral": "linear-gradient(180deg, #FFF 30.10%, #F8F9FA 100%)", - "docs-button-neutral-dark": "linear-gradient(180deg, #2E2E32 0%, #28282C 32.67%)", + "fade": "linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0))", + "fade-dark": "linear-gradient(to top, rgba(27, 27, 31, 1), rgba(27, 27, 31, 0))", }, screens: { xs: "576px", @@ -799,5 +785,4 @@ module.exports = { "toc-dark": "url('/img/side-menu.svg')", }, }, - plugins: [], }