diff --git a/components/AlgoliaSearch.tsx b/components/AlgoliaSearch.tsx index be8a5c6bb340..c35dca6e9c63 100644 --- a/components/AlgoliaSearch.tsx +++ b/components/AlgoliaSearch.tsx @@ -1,13 +1,30 @@ /* eslint-disable no-underscore-dangle */ -import type { DocSearchHit, InternalDocSearchHit, StoredDocSearchHit } from '@docsearch/react'; -import { DocSearchModal } from '@docsearch/react'; +import type { + DocSearchHit, + InternalDocSearchHit, + StoredDocSearchHit, +} from '@docsearch/react'; import clsx from 'clsx'; +import dynamic from 'next/dynamic'; import Head from 'next/head'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'; +import React, { + createContext, + useCallback, + useContext, + useEffect, + useRef, + useState, +} from 'react'; import { createPortal } from 'react-dom'; +const DocSearchModal = dynamic( + () => + import('@docsearch/react').then((mod) => ({ default: mod.DocSearchModal })), + { ssr: false }, +); + export const INDEX_NAME = 'asyncapi'; export const DOCS_INDEX_NAME = 'asyncapi-docs'; const APP_ID = 'Z621OGRI9Y'; @@ -46,8 +63,17 @@ interface IUseDocSearchKeyboardEvents { onInput?: (e: React.KeyboardEvent) => void; } -type ISearchButtonProps = Omit, 'children'> & { - children?: React.ReactNode | (({ actionKey }: { actionKey: { shortKey: string; key: string } }) => React.ReactNode); +type ISearchButtonProps = Omit< + React.ButtonHTMLAttributes, + 'children' +> & { + children?: + | React.ReactNode + | (({ + actionKey, + }: { + actionKey: { shortKey: string; key: string }; + }) => React.ReactNode); indexName?: string; }; @@ -64,7 +90,8 @@ function transformItems(items: DocSearchHit[]) { a.href = item.url; - const hash = a.hash === '#content-wrapper' || a.hash === '#header' ? '' : a.hash; + const hash = + a.hash === '#content-wrapper' || a.hash === '#header' ? '' : a.hash; if (item.hierarchy?.lvl0) { // eslint-disable-next-line no-param-reassign @@ -75,10 +102,15 @@ function transformItems(items: DocSearchHit[]) { ...item, url: `${a.pathname}${hash}`, __is_result: () => true, - __is_parent: () => item.type === 'lvl1' && items.length > 1 && index === 0, - __is_child: () => item.type !== 'lvl1' && items.length > 1 && items[0].type === 'lvl1' && index !== 0, + __is_parent: () => + item.type === 'lvl1' && items.length > 1 && index === 0, + __is_child: () => + item.type !== 'lvl1' && + items.length > 1 && + items[0].type === 'lvl1' && + index !== 0, __is_first: () => index === 1, - __is_last: () => index === items.length - 1 && index !== 0 + __is_last: () => index === items.length - 1 && index !== 0, }; }); } @@ -97,7 +129,7 @@ function Hit({ hit, children }: IHitProps) { 'DocSearch-Hit--Parent': hit.__is_parent?.(), 'DocSearch-Hit--FirstChild': hit.__is_first?.(), 'DocSearch-Hit--LastChild': hit.__is_last?.(), - 'DocSearch-Hit--Child': hit.__is_child?.() + 'DocSearch-Hit--Child': hit.__is_child?.(), })} > {children} @@ -114,14 +146,18 @@ function AlgoliaModal({ onClose, initialQuery, indexName }: AlgoliaModalProps) { const router = useRouter(); return createPortal( -
+
, - document.body + document.body, ); } @@ -153,7 +189,10 @@ function isEditingContent(event: KeyboardEvent) { const { tagName } = element as HTMLElement; return ( - (element as HTMLElement).isContentEditable || tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA' + (element as HTMLElement).isContentEditable || + tagName === 'INPUT' || + tagName === 'SELECT' || + tagName === 'TEXTAREA' ); } @@ -163,22 +202,24 @@ function isEditingContent(event: KeyboardEvent) { */ function getActionKey() { if (typeof navigator !== 'undefined') { - if (/(Mac|iPhone|iPod|iPad)/i.test(navigator.userAgent || navigator.platform)) { + if ( + /(Mac|iPhone|iPod|iPad)/i.test(navigator.userAgent || navigator.platform) + ) { return { shortKey: '⌘', - key: 'Command' + key: 'Command', }; } return { shortKey: 'Ctrl', - key: 'Control' + key: 'Control', }; } return { shortKey: 'Ctrl', - key: 'Control' + key: 'Control', }; } @@ -187,7 +228,11 @@ function getActionKey() { * @description The hook used for the Algolia search keyboard events * @param {IUseDocSearchKeyboardEvents} props - The props of the useDocSearchKeyboardEvents hook */ -function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }: IUseDocSearchKeyboardEvents) { +function useDocSearchKeyboardEvents({ + isOpen, + onOpen, + onClose, +}: IUseDocSearchKeyboardEvents) { useEffect(() => { /** * @description The function used to handle the keyboard event. @@ -213,7 +258,9 @@ function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }: IUseDocSearchKe if (typeof document !== 'undefined') { const loc = document.location; - indexName = loc.pathname.startsWith('/docs') ? DOCS_INDEX_NAME : INDEX_NAME; + indexName = loc.pathname.startsWith('/docs') + ? DOCS_INDEX_NAME + : INDEX_NAME; } onOpen(indexName); } @@ -233,7 +280,11 @@ function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }: IUseDocSearchKe * @description The Algolia search component used for searching the website * @param {React.ReactNode} children - The content of the page */ -export default function AlgoliaSearch({ children }: { children: React.ReactNode }) { +export default function AlgoliaSearch({ + children, +}: { + children: React.ReactNode; +}) { const [isOpen, setIsOpen] = useState(false); const [indexName, setIndexName] = useState(INDEX_NAME); const [initialQuery, setInitialQuery] = useState(); @@ -245,7 +296,7 @@ export default function AlgoliaSearch({ children }: { children: React.ReactNode } setIsOpen(true); }, - [setIsOpen, setIndexName] + [setIsOpen, setIndexName], ); const onClose = useCallback(() => { @@ -257,23 +308,51 @@ export default function AlgoliaSearch({ children }: { children: React.ReactNode setIsOpen(true); setInitialQuery(e.key); }, - [setIsOpen, setInitialQuery] + [setIsOpen, setInitialQuery], ); useDocSearchKeyboardEvents({ isOpen, onOpen, onClose, - onInput + onInput, }); + useEffect(() => { + if (isOpen) { + // Load Algolia CSS on demand when modal first opens + const linkId = 'docsearch-css'; + + if (!document.getElementById(linkId)) { + const link = document.createElement('link'); + + link.id = linkId; + link.rel = 'stylesheet'; + link.href = 'https://cdn.jsdelivr.net/npm/@docsearch/css@3'; + document.head.appendChild(link); + } + } + }, [isOpen]); + return ( <> - + - {children} - {isOpen && } + + {children} + + {isOpen && ( + + )} ); } @@ -283,7 +362,11 @@ export default function AlgoliaSearch({ children }: { children: React.ReactNode * @description The search button component used for opening the Algolia search * @param {ISearchButtonProps} props - The props of the search button */ -export function SearchButton({ children, indexName = INDEX_NAME, ...props }: ISearchButtonProps) { +export function SearchButton({ + children, + indexName = INDEX_NAME, + ...props +}: ISearchButtonProps) { const { onOpen, onInput } = useContext(SearchContext); const searchButtonRef = useRef(null); const actionKey = getActionKey(); @@ -296,7 +379,11 @@ export function SearchButton({ children, indexName = INDEX_NAME, ...props }: ISe * @returns {void} */ function onKeyDown(event: KeyboardEvent) { - if (searchButtonRef && searchButtonRef.current === document.activeElement && onInput) { + if ( + searchButtonRef && + searchButtonRef.current === document.activeElement && + onInput + ) { if (/[a-zA-Z0-9]/.test(event.key)) { onInput(event as unknown as React.KeyboardEvent); } @@ -310,17 +397,18 @@ export function SearchButton({ children, indexName = INDEX_NAME, ...props }: ISe }; }, [onInput, searchButtonRef]); - const childContent = typeof children === 'function' ? children({ actionKey }) : children; + const childContent = + typeof children === 'function' ? children({ actionKey }) : children; return ( diff --git a/styles/globals.css b/styles/globals.css index b5308ef4777c..22a879bc2e35 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -1,11 +1,11 @@ @import url(https://cdn.jsdelivr.net/gh/tonsky/FiraCode@4/distr/fira_code.css); -@import url(https://cdn.jsdelivr.net/npm/@docsearch/css@3); +/* Algolia DocSearch CSS is loaded on demand in components/AlgoliaSearch.tsx */ -@import "swiper/css"; -@import "swiper/css/a11y"; -@import "swiper/css/navigation"; -@import "swiper/css/pagination"; -@import "swiper/css/scrollbar"; +@import 'swiper/css'; +@import 'swiper/css/a11y'; +@import 'swiper/css/navigation'; +@import 'swiper/css/pagination'; +@import 'swiper/css/scrollbar'; @tailwind base; @tailwind components; @@ -67,9 +67,9 @@ html:not(.transitions-enabled) *::after { } /* Smooth theme transitions - only enabled after page loads */ -html.transitions-enabled [class*="bg-"], -html.transitions-enabled [class*="text-"], -html.transitions-enabled [class*="border-"], +html.transitions-enabled [class*='bg-'], +html.transitions-enabled [class*='text-'], +html.transitions-enabled [class*='border-'], html.transitions-enabled svg, html.transitions-enabled path, html.transitions-enabled circle, @@ -91,7 +91,7 @@ html.transitions-enabled polygon { } } -li>p { +li > p { display: inline; } @@ -118,11 +118,11 @@ abbr[title] { @keyframes GradientAnimation { 0% { - background-position: 0% 50% + background-position: 0% 50%; } 100% { - background-position: 200% 50% + background-position: 200% 50%; } } @@ -177,7 +177,8 @@ abbr[title] { padding: 0; } -.DocSearch-Help > a:focus, .DocSearch-Help > a:hover { +.DocSearch-Help > a:focus, +.DocSearch-Help > a:hover { outline: none; text-decoration: underline; } @@ -197,8 +198,8 @@ abbr[title] { } } -@media (max-width: 767px){ - .orbits{ +@media (max-width: 767px) { + .orbits { display: none; } } @@ -225,7 +226,6 @@ abbr[title] { cursor: pointer; } - .orbit > div > img { border-radius: 100%; } @@ -415,11 +415,11 @@ abbr[title] { color: white; } -.meeting-card:hover > div:nth-child(2) > p { +.meeting-card:hover > div:nth-child(2) > p { color: white; } -.explorer-menu-wrapper > div > div{ +.explorer-menu-wrapper > div > div { border: 1px solid rgb(228, 228, 228); border-radius: 10px; padding: 10px; @@ -435,13 +435,13 @@ abbr[title] { } select { - cursor:pointer; + cursor: pointer; } .dark .gradient-border { border: 2px solid transparent; - border-image: linear-gradient(225deg, #AD20E2 0%, #2DCCFD 100%); + border-image: linear-gradient(225deg, #ad20e2 0%, #2dccfd 100%); border-image-slice: 1; - border-radius: 16px; + border-radius: 16px; } /* Line clamp utilities for text truncation */ @@ -468,7 +468,8 @@ select { /* Smooth gradient animation for CTA section */ @keyframes gradient-shift { - 0%, 100% { + 0%, + 100% { background-position: 0% 50%; } 50% { @@ -491,7 +492,7 @@ select { } .featured-case-studies-swiper .swiper-pagination-bullet { - background: #8851FB; + background: #8851fb; opacity: 0.5; width: 10px; height: 10px; @@ -510,7 +511,7 @@ select { /* Dark mode pagination bullets */ .dark .featured-case-studies-swiper .swiper-pagination-bullet { - background: #A87EFC; + background: #a87efc; } /* Custom Swiper Styles for Use Cases Carousel */ @@ -523,7 +524,7 @@ select { } .case-studies-swiper .swiper-pagination-bullet { - background: #8851FB; + background: #8851fb; opacity: 0.5; width: 10px; height: 10px; @@ -542,12 +543,12 @@ select { /* Dark mode pagination bullets */ .dark .case-studies-swiper .swiper-pagination-bullet { - background: #A87EFC; + background: #a87efc; } /* Invert documentation diagrams in dark mode */ -.dark img[src*="SNS-SQS-Pub-Sub.png"], -.dark img[src*="SNS-HTTP.png"], -.dark img[src*="SQS-Point-To-Point.png"] { +.dark img[src*='SNS-SQS-Pub-Sub.png'], +.dark img[src*='SNS-HTTP.png'], +.dark img[src*='SQS-Point-To-Point.png'] { filter: invert(100%) hue-rotate(180deg); }