From 5a2769b69387028c42b6d60e27ea6081b7ef8b2c Mon Sep 17 00:00:00 2001 From: Si Taggart Date: Mon, 2 Oct 2023 18:52:02 -0700 Subject: [PATCH 1/9] feat: site search --- biome.json | 4 +- cypress/integration/site-search/index.spec.ts | 14 +- .../engineering/developing-locally.md | 18 -- .../engineering/doc-site/docsearch.md | 22 +- .../engineering/environment-variables.md | 3 - internal-docs/engineering/technologies.md | 2 +- package.json | 13 +- packages/paste-website/package.json | 3 +- .../src/components/ContactUsMenu.tsx | 6 +- .../site-search/SearchEmptyIllustration.tsx | 126 ++++++++++ .../site-search/SearchEmptyState.tsx | 21 ++ .../src/components/site-search/SearchForm.tsx | 75 ++++++ .../components/site-search/SearchModal.tsx | 33 +++ .../site-search/SearchResultDocs.tsx | 106 +++++++++ .../components/site-search/SearchResults.tsx | 145 +++++++++++ .../site-search/SearchResultsLoading.tsx | 48 ++++ .../src/components/site-search/index.tsx | 87 +++++++ .../src/components/site-search/types.ts | 24 ++ .../src/components/site-wrapper/SiteBody.tsx | 5 - .../site-header/DarkModeToggle.tsx | 10 +- .../site-header/SiteHeaderSearch.tsx | 108 +++++---- .../site-wrapper/site-header/index.tsx | 51 ++-- packages/paste-website/src/constants.ts | 9 - .../src/pages/api/docs-search.ts | 12 +- .../paste-website/src/styles/docSearch.ts | 132 ---------- .../stories/SiteSearch.stories.tsx | 214 +++++++++++++++++ packages/paste-website/types/index.d.ts | 1 - yarn.lock | 225 +----------------- 28 files changed, 1023 insertions(+), 494 deletions(-) create mode 100644 packages/paste-website/src/components/site-search/SearchEmptyIllustration.tsx create mode 100644 packages/paste-website/src/components/site-search/SearchEmptyState.tsx create mode 100644 packages/paste-website/src/components/site-search/SearchForm.tsx create mode 100644 packages/paste-website/src/components/site-search/SearchModal.tsx create mode 100644 packages/paste-website/src/components/site-search/SearchResultDocs.tsx create mode 100644 packages/paste-website/src/components/site-search/SearchResults.tsx create mode 100644 packages/paste-website/src/components/site-search/SearchResultsLoading.tsx create mode 100644 packages/paste-website/src/components/site-search/index.tsx create mode 100644 packages/paste-website/src/components/site-search/types.ts delete mode 100644 packages/paste-website/src/styles/docSearch.ts create mode 100644 packages/paste-website/stories/SiteSearch.stories.tsx diff --git a/biome.json b/biome.json index 5f24deb744..076fcfec65 100644 --- a/biome.json +++ b/biome.json @@ -28,8 +28,8 @@ ".codesandbox/ci.json", "build.icon-list.js", "**/*.hbs", - "docs", - "apps/backend/supabase/schema.gen.ts" + "apps/backend/supabase/schema.gen.ts", + "package.json" ] }, "organizeImports": { diff --git a/cypress/integration/site-search/index.spec.ts b/cypress/integration/site-search/index.spec.ts index 9107fa017c..e3f08791a4 100644 --- a/cypress/integration/site-search/index.spec.ts +++ b/cypress/integration/site-search/index.spec.ts @@ -5,7 +5,7 @@ describe("Docs website search", () => { }); beforeEach(() => { - cy.intercept({ url: "https://**.algolia.net/**", method: "POST" }).as("searchRequest"); + cy.intercept({ url: "/api/docs-search", method: "POST" }).as("searchRequest"); }); beforeEach(() => { @@ -14,10 +14,14 @@ describe("Docs website search", () => { it("should handle a search string", () => { cy.get("@searchButtonEl").scrollIntoView().should("be.visible").click({ force: true }); - cy.get(".DocSearch-Input").should("be.visible").should("be.focused").type("checkbox"); + cy.get('[data-cy="paste-docsearch-input"]') + .should("be.visible") + .should("be.focused") + .type("checkbox") + .type("{enter}"); cy.wait("@searchRequest"); - cy.get(".DocSearch-Hits").should("have.length.above", 0); - cy.get('.DocSearch-Hits [role="listbox"]').should("have.length.above", 0); - cy.get('.DocSearch-Hits [role="option"]').should("have.length.above", 0); + cy.get('[data-cy="paste-docsearch-hits"] h2').should("have.length.above", 0); + cy.get('[data-cy="paste-docsearch-hits"] ul').should("have.length.above", 0); + cy.get('[data-cy="paste-docsearch-hits"] li').should("have.length.above", 0); }); }); diff --git a/internal-docs/engineering/developing-locally.md b/internal-docs/engineering/developing-locally.md index 55e73ae2ea..8a0b79973d 100644 --- a/internal-docs/engineering/developing-locally.md +++ b/internal-docs/engineering/developing-locally.md @@ -180,24 +180,6 @@ DATADOG_CLIENT_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxx ID of the Airtable that contains our roadmap - - NEXT_PUBLIC_DOCSEARCHV3_APIKEY - - API key for powering our docs search - - - - NEXT_PUBLIC_DOCSEARCHV3_APPID - - Docs search app ID - - - - NEXT_PUBLIC_DOCSEARCHV3_INDEXNAME - - twilio_paste - - ## Building packages diff --git a/internal-docs/engineering/doc-site/docsearch.md b/internal-docs/engineering/doc-site/docsearch.md index 0a06a4631d..6cdde540c1 100644 --- a/internal-docs/engineering/doc-site/docsearch.md +++ b/internal-docs/engineering/doc-site/docsearch.md @@ -1,17 +1,23 @@ # Docsearch -The search is provided by [Algolia Docsearch v3](https://docsearch.algolia.com/docs/DocSearch-v3/). +The search is provided a custom index hosted on Supabase and created by us on a nightly Cron Github Action. -The docsearch app login is stored in OnePassword, where you can [login here](https://www.algolia.com/). +The Supabase login is stored in OnePassword. -The dashboard will give you analytics and some configurations and insights into the index that is created. +## Creating Embeddings -## Crawler +We use the same technology that powers our GPT4 powered Discussions and Chat bot, to create our Site Search. -Docsearch crawls the website automatically via the crawler. It is recrawled every day. To configure the website crawler you must log into the [crawler application](https://crawler.algolia.com/). +On a nightly basis we index new or updated content in our MDX files and Github Discussions. -The crawler is configured via the editor, and a configuration object. It controls where the sitemap live, how often to crawl, and how to extract data from the website pages into the search index. +For MDX files we create "sections" of "pages" by chunking mdx files by headings. Those sections are then converted to OPENAI Vector embeds and stored in the Supabase Vector DB. -Use the Crawler app to visualize what Algolia can extract from our web pages as it crawls, and test any configuration changes you make. +The same happens with Github discussions. The discussion is a page and is split into two sections, the initial post and the answer. -Crawler documentation can be found [here](https://www.algolia.com/doc/tools/crawler/apis/configuration/). +We can then perform a similarity search using the user input as the query. + +Embeddings are cerated using the the [embedding script](../../../packages/paste-website/scripts/search/). + +## Returning results + +We have a [NextJS API route](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) `/api/documentation-search`. It will return the top 10 most similar page sections across MDX and Github discussions based on a user `prompt`. diff --git a/internal-docs/engineering/environment-variables.md b/internal-docs/engineering/environment-variables.md index faee8a89bb..6387aee33c 100644 --- a/internal-docs/engineering/environment-variables.md +++ b/internal-docs/engineering/environment-variables.md @@ -12,9 +12,6 @@ Full list of Environment variables and where they are needed. | DATADOG_CLIENT_TOKEN | Datadog client token for remix RUM tracking | ✅ | | | ✅ | | NEXT_PUBLIC_DATADOG_APPLICATION_ID | Datadog application ID for docsite RUM tracking | ✅ | | ✅ | | | NEXT_PUBLIC_DATADOG_CLIENT_TOKEN | Datadog client token for docsite RUM tracking | ✅ | | ✅ | | -| NEXT_PUBLIC_DOCSEARCHV3_APIKEY | Algolia API key | ✅ | | ✅ | | -| NEXT_PUBLIC_DOCSEARCHV3_APPID | Algolia app ID | ✅ | | ✅ | | -| NEXT_PUBLIC_DOCSEARCHV3_INDEXNAME | Algolia Index name | ✅ | | ✅ | | | NEXT_PUBLIC_ENVIRONMENT_CONTEXT | Informs Next which deployment environment its in | ✅ | | ✅ | | | NETLIFY_SITE_ID | Docsite site ID for Netlify deployment wait in GH actions | | ✅ | | | | NETLIFY_TOKEN | Docsite token for Netlify deployment wait in GH actions | | ✅ | | | diff --git a/internal-docs/engineering/technologies.md b/internal-docs/engineering/technologies.md index 7ae5600799..c94e76009a 100644 --- a/internal-docs/engineering/technologies.md +++ b/internal-docs/engineering/technologies.md @@ -96,7 +96,7 @@ The documentation site is a [NextJS](https://nextjs.org/) website using the late The content is largely written in [MDX](https://mdxjs.com/) pages. -The search is provided by [Algolia Docsearch](https://docsearch.algolia.com/). +The search is custom built based on OpenAI embeddings, a vector DB and a similarity search that allows us to return results across mdx pages and Github discussions. The [roadmap](https://paste.twilio.design/roadmap/) is stored in an [Airtable](https://airtable.com/) and imported upon building the website via the [Airtable JS client](https://github.com/Airtable/airtable.js). diff --git a/package.json b/package.json index 41ef9e98ee..4efc409c62 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,12 @@ "author": "Twilio Inc.", "license": "MIT", "workspaces": { - "packages": ["apps/**/*", "packages/**/*", "templates/**/*", "!packages/paste-core/core-bundle/**/*"] + "packages": [ + "apps/**/*", + "packages/**/*", + "templates/**/*", + "!packages/paste-core/core-bundle/**/*" + ] }, "types": "./types/index.d.ts", "engines": { @@ -228,5 +233,9 @@ } }, "packageManager": "yarn@3.6.3", - "browserslist": ["last 2 versions", "not dead", "not IE 11"] + "browserslist": [ + "last 2 versions", + "not dead", + "not IE 11" + ] } diff --git a/packages/paste-website/package.json b/packages/paste-website/package.json index b09333d31d..51e5238bb7 100644 --- a/packages/paste-website/package.json +++ b/packages/paste-website/package.json @@ -20,8 +20,6 @@ }, "dependencies": { "@datadog/browser-rum": "^4.46.0", - "@docsearch/css": "^3.3.3", - "@docsearch/react": "^3.3.3", "@hookform/error-message": "2.0.0", "@mdx-js/loader": "^1.6.22", "@mdx-js/mdx": "^1.6.22", @@ -170,6 +168,7 @@ "react-dom": "^18.0.0", "react-github-button": "^0.1.11", "react-hook-form": "^7.30.0", + "react-hotkeys-hook": "^4.4.1", "react-live": "^3.1.1", "react-scrollspy": "^3.4.0", "react-visibility-sensor": "5.1.1", diff --git a/packages/paste-website/src/components/ContactUsMenu.tsx b/packages/paste-website/src/components/ContactUsMenu.tsx index 0003124285..5b343a6058 100644 --- a/packages/paste-website/src/components/ContactUsMenu.tsx +++ b/packages/paste-website/src/components/ContactUsMenu.tsx @@ -20,7 +20,9 @@ export const ContactUsMenu: React.FC = () => { <> event({ @@ -30,7 +32,7 @@ export const ContactUsMenu: React.FC = () => { }) } > - + ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/packages/paste-website/src/components/site-search/SearchEmptyState.tsx b/packages/paste-website/src/components/site-search/SearchEmptyState.tsx new file mode 100644 index 0000000000..75715ddb93 --- /dev/null +++ b/packages/paste-website/src/components/site-search/SearchEmptyState.tsx @@ -0,0 +1,21 @@ +import { Box } from "@twilio-paste/box"; +import { Heading } from "@twilio-paste/heading"; +import { Paragraph } from "@twilio-paste/paragraph"; +import * as React from "react"; + +import { SearchEmptyIllustration } from "./SearchEmptyIllustration"; + +export const SearchEmptyState: React.FC<{ searchQuery: string }> = ({ searchQuery }) => { + return ( + + + ); +}; diff --git a/packages/paste-website/src/components/site-search/SearchForm.tsx b/packages/paste-website/src/components/site-search/SearchForm.tsx new file mode 100644 index 0000000000..14fa795468 --- /dev/null +++ b/packages/paste-website/src/components/site-search/SearchForm.tsx @@ -0,0 +1,75 @@ +import { Box } from "@twilio-paste/box"; +import { Button } from "@twilio-paste/button"; +import { Form, FormControl, FormSection } from "@twilio-paste/form"; +import { ClearIcon } from "@twilio-paste/icons/esm/ClearIcon"; +import { SearchIcon } from "@twilio-paste/icons/esm/SearchIcon"; +import { Input } from "@twilio-paste/input"; +import { useUID } from "@twilio-paste/uid-library"; +import * as React from "react"; + +export interface SearchFormProps { + onSubmit: () => void; + onClear: () => void; + hasResults: boolean; + ariaLabelledBy: string; + inputValue: string; + onChange: (event: React.ChangeEvent) => void; + inputRef: React.RefObject; +} + +const SearchForm: React.FC> = ({ + ariaLabelledBy, + onClear, + onSubmit, + hasResults, + inputValue, + onChange, + inputRef, +}) => { + const inputID = useUID(); + return ( + +
{ + e.preventDefault(); + onSubmit(); + }} + > + + + } + insertAfter={ + hasResults && ( + + ) + } + aria-labelledby={ariaLabelledBy} + type="text" + id={inputID} + name="search-input" + placeholder="Enter a question or keyword" + value={inputValue} + onChange={onChange} + data-cy="paste-docsearch-input" + ref={inputRef} + /> + + +
+
+ ); +}; + +SearchForm.displayName = "SearchForm"; + +export { SearchForm }; diff --git a/packages/paste-website/src/components/site-search/SearchModal.tsx b/packages/paste-website/src/components/site-search/SearchModal.tsx new file mode 100644 index 0000000000..8e004b4ba6 --- /dev/null +++ b/packages/paste-website/src/components/site-search/SearchModal.tsx @@ -0,0 +1,33 @@ +import { Box } from "@twilio-paste/box"; +import { Modal, ModalBody, ModalHeader, ModalHeading, type ModalProps } from "@twilio-paste/modal"; +import * as React from "react"; + +type SearchModal = Pick & { + children: NonNullable; +}; +export const SearchModal: React.FC> = ({ + isOpen, + ariaLabelledby, + onDismiss, + initialFocusRef, + children, +}) => { + return ( + + + + + Search documentation and discussions + + + + {children} + + ); +}; diff --git a/packages/paste-website/src/components/site-search/SearchResultDocs.tsx b/packages/paste-website/src/components/site-search/SearchResultDocs.tsx new file mode 100644 index 0000000000..c68291b740 --- /dev/null +++ b/packages/paste-website/src/components/site-search/SearchResultDocs.tsx @@ -0,0 +1,106 @@ +import { Anchor } from "@twilio-paste/anchor"; +import { Box } from "@twilio-paste/box"; +import { InlineCode } from "@twilio-paste/inline-code"; +import Markdown from "markdown-to-jsx"; +import Link from "next/link"; +import * as React from "react"; + +import { type SearchItem } from "./types"; + +export const FakeHeading: React.FC = ({ children }) => { + return ( + + {children} + + ); +}; + +export const FakeParagraph: React.FC = ({ children }) => { + return ( + + {children} + + ); +}; + +export const FakeList: React.FC = ({ children }) => { + return ( + + {children} + + ); +}; + +export const FakeListItem: React.FC = ({ children }) => { + return ( + + {children} + + ); +}; + +export const SearchResultDocs: React.FC<{ searchItem: SearchItem }> = ({ searchItem }) => { + return ( + + + {/* trim the content string to just 140 characters */} + {`${searchItem.content.slice(0, 120)}...`} + + + ); +}; diff --git a/packages/paste-website/src/components/site-search/SearchResults.tsx b/packages/paste-website/src/components/site-search/SearchResults.tsx new file mode 100644 index 0000000000..43e02c7ab1 --- /dev/null +++ b/packages/paste-website/src/components/site-search/SearchResults.tsx @@ -0,0 +1,145 @@ +import { Box } from "@twilio-paste/box"; +import { Heading } from "@twilio-paste/heading"; +import { DocumentationIcon } from "@twilio-paste/icons/esm/DocumentationIcon"; +import Link from "next/link"; +import * as React from "react"; + +import { sentenceCase } from "../../utils/SentenceCase"; +import GithubIcon from "../icons/GithubIcon"; +import { SearchResultDocs } from "./SearchResultDocs"; +import { type GroupedSearchResults } from "./types"; + +export interface SearchResultsProps { + results?: GroupedSearchResults; +} + +const DiscussionHeading: React.FC<{ title: string; path: string }> = ({ title, path }) => { + return ( + + + + + + + {title} + + + + ); +}; + +const DocumentationHeading: React.FC<{ title: string }> = ({ title }) => { + return ( + + + + {title} + + + ); +}; + +const SearchResultsList: React.FC = ({ results }) => { + if (!results) { + return null; + } + return ( + <> + {Object.keys(results).map((path) => { + const resultSections = results[path]; + const resultParent = { + path: resultSections[0].path, + title: resultSections[0].meta.title, + description: resultSections[0].meta.description, + slug: resultSections[0].meta.slug, + }; + const resultType = resultSections[0].type; + return ( + + {resultType === "github-discussions" ? ( + + ) : ( + + )} + {resultParent.description && ( + + {resultParent.description} + + )} + {resultParent.slug && ( + + {sentenceCase(resultParent.slug.split("/")[1])} + + )} + {resultType === "markdown" && ( + + + {resultSections.map((result) => { + return ( + + + + ); + })} + + + )} + + ); + })} + + ); +}; + +SearchResultsList.displayName = "SearchResultsList"; + +export { SearchResultsList }; diff --git a/packages/paste-website/src/components/site-search/SearchResultsLoading.tsx b/packages/paste-website/src/components/site-search/SearchResultsLoading.tsx new file mode 100644 index 0000000000..ec68fb3ad0 --- /dev/null +++ b/packages/paste-website/src/components/site-search/SearchResultsLoading.tsx @@ -0,0 +1,48 @@ +import { Box } from "@twilio-paste/box"; +import { SkeletonLoader } from "@twilio-paste/skeleton-loader"; +import * as React from "react"; + +export const SearchResultsLoading: React.FC = () => { + return ( + + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/paste-website/src/components/site-search/index.tsx b/packages/paste-website/src/components/site-search/index.tsx new file mode 100644 index 0000000000..742af7cf62 --- /dev/null +++ b/packages/paste-website/src/components/site-search/index.tsx @@ -0,0 +1,87 @@ +/* eslint-disable react/jsx-max-depth */ +import { type ModalProps } from "@twilio-paste/modal"; +import { useUID } from "@twilio-paste/uid-library"; +import groupBy from "lodash/groupBy"; +import * as React from "react"; + +import { SearchEmptyState } from "./SearchEmptyState"; +import { SearchForm } from "./SearchForm"; +import { SearchModal } from "./SearchModal"; +import { SearchResultsList } from "./SearchResults"; +import { SearchResultsLoading } from "./SearchResultsLoading"; +import { type GroupedSearchResults, type SearchItem, type SearchResults } from "./types"; + +const groupResults = (results: SearchItem[]): GroupedSearchResults => groupBy(results, "path"); + +export type SiteSearchProps = Pick; + +const SiteSearch: React.FC> = ({ isOpen, onDismiss }) => { + const modalHeadingId = useUID(); + + const [searchQuery, setSearchQuery] = React.useState(""); + const [results, setResults] = React.useState({}); + const [loading, setLoading] = React.useState(false); + const [searchInitialState, setSearchInitialState] = React.useState(true); + + const inputRef = React.useRef(null); + + React.useEffect(() => { + if (searchQuery === "") { + // reset the search state when we empty the search query to start again + setSearchInitialState(true); + } + }, [searchQuery]); + + const performSearch = async (): Promise => { + setResults({}); + setLoading(true); + try { + const response = await fetch("/api/docs-search", { + method: "POST", + body: JSON.stringify({ prompt: searchQuery }), + }); + const json = (await response.json()) as SearchResults; + const groupedResults = groupResults(json.data); + setResults(groupedResults); + } catch (error) { + // eslint-disable-next-line no-console + console.error(error); + } + setSearchInitialState(false); + setLoading(false); + }; + + const handleOnChange = (event: React.ChangeEvent): void => { + setSearchQuery(event.target.value); + }; + + const clearResults = (): void => { + setResults({}); + setSearchQuery(""); + inputRef.current?.focus(); + }; + + return ( + + 0} + onClear={clearResults} + ariaLabelledBy={modalHeadingId} + inputValue={searchQuery} + onChange={handleOnChange} + inputRef={inputRef} + /> + {loading && } + {searchQuery && !searchInitialState && !loading && Object.keys(results).length === 0 && ( + + )} + + + ); +}; + +SiteSearch.displayName = "SiteSearch"; + +export { SiteSearch }; +/* eslint-enable react/jsx-max-depth */ diff --git a/packages/paste-website/src/components/site-search/types.ts b/packages/paste-website/src/components/site-search/types.ts new file mode 100644 index 0000000000..f57c7e303c --- /dev/null +++ b/packages/paste-website/src/components/site-search/types.ts @@ -0,0 +1,24 @@ +export type SearchItem = { + type: string; + source: string; + path: string; + similarity: number; + slug: string; + content: string; + heading: string; + meta: { + id?: string; + updatedAt?: string; + title: string; + description?: string; + package?: string; + slug?: string; + }; +}; + +export type SearchResults = { + data: SearchItem[]; +}; +export type GroupedSearchResults = { + [key: string]: SearchItem[]; +}; diff --git a/packages/paste-website/src/components/site-wrapper/SiteBody.tsx b/packages/paste-website/src/components/site-wrapper/SiteBody.tsx index bd5cf885cb..8681c2ecbe 100644 --- a/packages/paste-website/src/components/site-wrapper/SiteBody.tsx +++ b/packages/paste-website/src/components/site-wrapper/SiteBody.tsx @@ -11,7 +11,6 @@ import { SidebarPushContentWrapper, } from "@twilio-paste/sidebar"; import { type CSSObject, StylingGlobals } from "@twilio-paste/styling-library"; -import { useTheme } from "@twilio-paste/theme"; import { useWindowSize } from "@twilio-paste/utils"; import { useRouter } from "next/router"; import * as React from "react"; @@ -24,7 +23,6 @@ import { TOKEN_LIST_PAGE_REGEX, TOKEN_STICKY_FILTER_HEIGHT, } from "../../constants"; -import { docSearchStyles, docSearchVariable } from "../../styles/docSearch"; import { SiteMain } from "./SiteMain"; import { SidebarNavigation } from "./sidebar/SidebarNavigation"; import { SiteFooter } from "./site-footer"; @@ -43,7 +41,6 @@ const GlobalScrollBehaviourStyles = (scrollOffset = defaultScrollOffset): CSSObj export const SiteBody: React.FC = ({ children }) => { const { breakpointIndex } = useWindowSize(); - const themeObject = useTheme(); const router = useRouter(); // sidebar is not collapsed by default, most common use case for desktop viewing const [sidebarCollapsed, setSidebarCollapsed] = React.useState(false); @@ -93,8 +90,6 @@ export const SiteBody: React.FC = ({ children }) => { {/** diff --git a/packages/paste-website/src/components/site-wrapper/site-header/DarkModeToggle.tsx b/packages/paste-website/src/components/site-wrapper/site-header/DarkModeToggle.tsx index e9b2884627..b14275ff7b 100644 --- a/packages/paste-website/src/components/site-wrapper/site-header/DarkModeToggle.tsx +++ b/packages/paste-website/src/components/site-wrapper/site-header/DarkModeToggle.tsx @@ -24,7 +24,9 @@ export const DarkModeToggle = (): JSX.Element => { <> event({ @@ -34,7 +36,11 @@ export const DarkModeToggle = (): JSX.Element => { }) } > - {theme === "twilio" ? : } + {theme === "twilio" ? ( + + ) : ( + + )} Switch the site theme diff --git a/packages/paste-website/src/components/site-wrapper/site-header/SiteHeaderSearch.tsx b/packages/paste-website/src/components/site-wrapper/site-header/SiteHeaderSearch.tsx index 0e2b8b45c4..adfd3ed279 100644 --- a/packages/paste-website/src/components/site-wrapper/site-header/SiteHeaderSearch.tsx +++ b/packages/paste-website/src/components/site-wrapper/site-header/SiteHeaderSearch.tsx @@ -1,72 +1,70 @@ -import "@docsearch/css"; -import { DocSearchButton, useDocSearchKeyboardEvents } from "@docsearch/react"; -import type { DocSearchModal as DocSearchModalType } from "@docsearch/react"; -import Head from "next/head"; +import { Box } from "@twilio-paste/box"; +import { Button } from "@twilio-paste/button"; +import { SearchIcon } from "@twilio-paste/icons/esm/SearchIcon"; +import { InlineCode } from "@twilio-paste/inline-code"; +import { Text } from "@twilio-paste/text"; import * as React from "react"; +import { useHotkeys } from "react-hotkeys-hook"; -import { DOCSEARCHV3_APIKEY, DOCSEARCHV3_APPID, DOCSEARCHV3_INDEXNAME } from "../../../constants"; - -// https://github.com/facebook/docusaurus/blob/main/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx -let DocSearchModal: typeof DocSearchModalType | null = null; +import { SiteSearch } from "../../site-search"; const SiteHeaderSearch: React.FC = () => { const [isOpen, setIsOpen] = React.useState(false); - const searchButtonRef = React.useRef(null); - - const importDocSearch = React.useCallback(async () => { - if (DocSearchModal) { - return; - } - const { DocSearchModal: Modal }: typeof import("@docsearch/react") = await import( - /* webpackChunkName: 'DocSearchModal' */ "@docsearch/react/modal" - ); - DocSearchModal = Modal; - }, []); - const onOpen = React.useCallback(async () => { - await importDocSearch(); + const onOpen = (): void => { setIsOpen(true); - }, [importDocSearch, setIsOpen]); + }; - const onClose = React.useCallback(() => { + const onClose = (): void => { setIsOpen(false); - }, [setIsOpen]); - - const onInput = React.useCallback(async () => { - await importDocSearch(); - setIsOpen(true); - }, [importDocSearch, setIsOpen]); + }; - useDocSearchKeyboardEvents({ - isOpen, - onOpen, - onClose, - onInput, - searchButtonRef, - }); + useHotkeys("mod+k", onOpen); return ( <> - - - - - {isOpen && DocSearchModal ? ( - - ) : null} + _hover={{ + boxShadow: "shadowBorderPrimary", + }} + _focus={{ + boxShadow: "shadowFocusShadowBorder", + }} + _active={{ + boxShadow: "shadowBorderPrimaryStronger", + }} + > + + + + + Search + + + + + K + + + + ); }; diff --git a/packages/paste-website/src/components/site-wrapper/site-header/index.tsx b/packages/paste-website/src/components/site-wrapper/site-header/index.tsx index 2f5fcd0cd1..74dad17f5b 100644 --- a/packages/paste-website/src/components/site-wrapper/site-header/index.tsx +++ b/packages/paste-website/src/components/site-wrapper/site-header/index.tsx @@ -1,6 +1,7 @@ import { Box } from "@twilio-paste/box"; import { Button } from "@twilio-paste/button"; import { Topbar, TopbarActions } from "@twilio-paste/topbar"; +import { useWindowSize } from "@twilio-paste/utils"; import * as React from "react"; import GitHubButton from "react-github-button"; import "react-github-button/assets/style.css"; @@ -17,34 +18,38 @@ export const SiteHeader: React.FC<{ sidebarMobileCollapsed: boolean; setSidebarMobileCollapsed: (collapsed: boolean) => void; }> = ({ sidebarMobileCollapsed, setSidebarMobileCollapsed }): JSX.Element => { + const { breakpointIndex } = useWindowSize(); return ( - - - - - - - - - - - - - - - + {breakpointIndex === 0 ? ( + + + + + + + ) : ( + + + + + + + + + + )} ); }; diff --git a/packages/paste-website/src/constants.ts b/packages/paste-website/src/constants.ts index 10ab223261..0726e8a15e 100644 --- a/packages/paste-website/src/constants.ts +++ b/packages/paste-website/src/constants.ts @@ -3,11 +3,6 @@ export const TWILIO_BLUE = "#0D122B"; export const PSA_ALERT_HEIGHT = 40; export const PASTE_THEME_ALERT_HEIGHT = 54; export const SITE_TOPBAR_HEIGHT = 77; -/* - * Note: - * Changing the mobile breakpoint should be reflected - * in styles/docSearch.ts - */ export const SITE_BREAKPOINTS = ["768px", "1024px", "1220px", "1880px"]; export const SITE_CONTENT_MAX_WIDTH = "1440px"; @@ -33,10 +28,6 @@ export const TOKEN_LIST_PAGE_REGEX = /^\/tokens\/list$/; // env variables export const DATADOG_APPLICATION_ID = process.env.NEXT_PUBLIC_DATADOG_APPLICATION_ID || "no env variable"; export const DATADOG_CLIENT_TOKEN = process.env.NEXT_PUBLIC_DATADOG_CLIENT_TOKEN || "no env variable"; -export const DOCSEARCH_APIKEY = process.env.NEXT_PUBLIC_DOCSEARCH_APIKEY || "no env variable"; -export const DOCSEARCHV3_APIKEY = process.env.NEXT_PUBLIC_DOCSEARCHV3_APIKEY || "no env variable"; -export const DOCSEARCHV3_INDEXNAME = process.env.NEXT_PUBLIC_DOCSEARCHV3_INDEXNAME || "no env variable"; -export const DOCSEARCHV3_APPID = process.env.NEXT_PUBLIC_DOCSEARCHV3_APPID || "no env variable"; /* * Netlify provides an environment variable called CONTEXT which reflects their build context https://docs.netlify.com/site-deploys/overview/#deploy-contexts * We need to use this to know where the Next site is being run for metrics tracking. Next env variables all need diff --git a/packages/paste-website/src/pages/api/docs-search.ts b/packages/paste-website/src/pages/api/docs-search.ts index e4e9cadd2d..2373bd23a2 100644 --- a/packages/paste-website/src/pages/api/docs-search.ts +++ b/packages/paste-website/src/pages/api/docs-search.ts @@ -16,7 +16,6 @@ class ApplicationError extends Error { class UserError extends ApplicationError {} const openAiKey = process.env.OPENAI_API_KEY; -const openAiSecret = process.env.OPENAI_API_SECRET; const supabaseUrl = process.env.SUPABASE_URL; const supabaseServiceKey = process.env.SUPABASE_KEY; @@ -32,9 +31,6 @@ export default async function handler(req: NextRequest): Promise { - return { - ":root": { - "--docsearch-primary-color": theme.backgroundColors.colorBackgroundPrimaryStronger, - "--docsearch-text-color": theme.textColors.colorText, - "--docsearch-spacing": theme.space.space50, - "--docsearch-icon-stroke-width": theme.borderWidths.borderWidth20, - "--docsearch-highlight-color": theme.backgroundColors.colorBackgroundPrimaryStronger, - "--docsearch-muted-color": theme.textColors.colorTextWeak, - "--docsearch-container-background": theme.backgroundColors.colorBackgroundOverlay, - "--docsearch-logo-color": theme.textColors.colorTextWeak, - "--docsearch-modal-width": theme.sizes.size70, - "--docsearch-modal-height": "600px", - "--docsearch-modal-background": theme.backgroundColors.colorBackgroundBody, - "--docsearch-modal-shadow": theme.shadows.shadowCard, - "--docsearch-searchbox-height": "46px", - "--docsearch-searchbox-background": theme.backgroundColors.colorBackgroundInverse, - "--docsearch-searchbox-focus-background": theme.backgroundColors.colorBackgroundBody, - "--docsearch-searchbox-shadow": theme.shadows.shadowFocus, - "--docsearch-hit-height": "56px", - "--docsearch-hit-color": theme.textColors.colorText, - "--docsearch-hit-active-color": theme.textColors.colorTextWeakest, - "--docsearch-hit-background": theme.backgroundColors.colorBackgroundBody, - "--docsearch-hit-shadow": "none", - "--docsearch-key-gradient": theme.backgroundColors.colorBackground, - "--docsearch-key-shadow": "none", - "--docsearch-footer-height": "44px", - "--docsearch-footer-background": theme.backgroundColors.colorBackgroundBody, - "--docsearch-footer-shadow": "none", - }, - }; -}; -export const docSearchStyles = css({ - ".DocSearch-Button": { - backgroundColor: "colorBackgroundBody", - borderRadius: "borderRadius20", - boxShadow: "shadowBorder", - color: "colorTextWeak", - paddingX: "space40", - margin: "0", - width: "100%", - "&:hover": { - boxShadow: "shadowBorderPrimary", - }, - "&:active, &:focus": { - boxShadow: "shadowFocusShadowBorder", - }, - ".DocSearch-Search-Icon": { - color: "colorTextIcon", - size: "sizeIcon10", - }, - }, - ".DocSearch-Button-Placeholder": { - fontSize: "fontSize30", - fontFamily: "fontFamilyText", - fontWeight: "fontWeightMedium", - fontStyle: "italic", - paddingX: "space40", - }, - ".DocSearch-Button-Key": { - top: 0, - paddingBottom: 0, - }, - ".DocSearch-Container": { - ...pasteBaseStyles(), - }, - ".DocSearch-Dropdown": { - a: { - color: "colorTextLink", - textDecoration: "underline", - ":hover": { - textDecoration: "none", - }, - }, - }, - ".DocSearch-SearchBar": { - paddingBottom: "space20", - }, - ".DocSearch-Search-Icon": { - color: "colorTextIcon", - }, - ".DocSearch-Input": { - paddingLeft: "space50", - }, - ".DocSearch-LoadingIndicator svg, .DocSearch-MagnifierLabel svg": { - size: "sizeIcon10", - }, - ".DocSearch-Hits mark": { - color: "colorTextLink", - }, - ".DocSearch-Hit": { - paddingBottom: "space30", - a: { - borderColor: "colorBorderWeaker", - borderStyle: "solid", - borderWidth: "borderWidth20", - textDecoration: "none", - }, - "&[aria-selected=true] a": { - borderColor: "colorBorderPrimaryStronger", - }, - }, - ".DocSearch-Hit-title": { - fontSize: "fontSize30", - }, - ".DocSearch-Hit-path": { - fontSize: "fontSize20", - }, - ".DocSearch-Hit-source": { - color: "colorText", - fontSize: "fontSize30", - fontWeight: "fontWeightMedium", - }, - ".DocSearch-Hit-icon": { - display: "none", - }, - ".DocSearch-Prefill": { - color: "colorTextLink", - textDecoration: "underline", - "&:hover": { - textDecoration: "none", - }, - }, - ".DocSearch-Commands-Key": { - paddingBottom: "0", - }, -}); diff --git a/packages/paste-website/stories/SiteSearch.stories.tsx b/packages/paste-website/stories/SiteSearch.stories.tsx new file mode 100644 index 0000000000..c541db8231 --- /dev/null +++ b/packages/paste-website/stories/SiteSearch.stories.tsx @@ -0,0 +1,214 @@ +import { type StoryFn } from "@storybook/react"; +import { useUID } from "@twilio-paste/uid-library"; +import * as React from "react"; + +import { SearchEmptyState } from "../src/components/site-search/SearchEmptyState"; +import { SearchForm } from "../src/components/site-search/SearchForm"; +import { SearchModal } from "../src/components/site-search/SearchModal"; +import { SearchResultsList } from "../src/components/site-search/SearchResults"; +import { SearchResultsLoading } from "../src/components/site-search/SearchResultsLoading"; +import { type GroupedSearchResults } from "../src/components/site-search/types"; +import { SiteHeaderSearch } from "../src/components/site-wrapper/site-header/SiteHeaderSearch"; + +const mockResults = { + "src/pages/components/in-page-navigation/index": [ + { + path: "src/pages/components/in-page-navigation/index", + content: + "### About In Page Navigation\n\nThe In Page Navigation component allows users to navigate to different pages using a styled group of links. Each In Page Navigation Item is an anchor that links to a related page. Each page within the In Page Navigation should also have an In Page Navigation component so that users can easily navigate back and forth within a set of related pages.\n", + similarity: 0.856512844562531, + source: "docs", + type: "markdown", + meta: { + slug: "/components/in-page-navigation/", + title: "In Page Navigation", + package: "@twilio-paste/in-page-navigation", + description: "An In Page Navigation is a group of styled links that lets users navigate between related pages.", + }, + heading: "About In Page Navigation", + slug: "about-in-page-navigation", + }, + { + path: "src/pages/components/in-page-navigation/index", + content: + "### In Page Navigation vs Tabs\n\n[Tabs](/components/tabs) are used to layer related pieces of information together and display one layer at a time on the same URL. Use Tabs to alternate between views within the same context. In Page Navigation is a collection of anchors, rather than buttons. Use In Page Navigation to switch between different, related pages. Tabs replace the entire view based on the selected tab. In Page Navigation links navigate the user to a new page.\n", + similarity: 0.853896617889404, + source: "docs", + type: "markdown", + meta: { + slug: "/components/in-page-navigation/", + title: "In Page Navigation", + package: "@twilio-paste/in-page-navigation", + description: "An In Page Navigation is a group of styled links that lets users navigate between related pages.", + }, + heading: "In Page Navigation vs Tabs", + slug: "in-page-navigation-vs-tabs", + }, + { + path: "src/pages/components/in-page-navigation/index", + content: + "### Accessibility\n\n* Each In Page Navigation must have a unique label. To add the label, add the `aria-label` prop to the `` tag.\n Omit the term 'navigation'- it is redundant since the role is already defined as 'navigation'.\n* To interact with In Page Navigation using the keyboard, use the tab key.\n* Each In Page Navigation must have an In Page Navigation Item which is the currently selected page. To specify which page is current, add the `currentPage` prop to the respective ``. Doing so will set `aria-current=\"page\"` on that link.\n", + similarity: 0.824576377868652, + source: "docs", + type: "markdown", + meta: { + slug: "/components/in-page-navigation/", + title: "In Page Navigation", + package: "@twilio-paste/in-page-navigation", + description: "An In Page Navigation is a group of styled links that lets users navigate between related pages.", + }, + heading: "Accessibility", + slug: "accessibility", + }, + ], + "src/pages/components/tabs/index": [ + { + path: "src/pages/components/tabs/index", + content: + "### Tabs vs. In Page Navigation\n\nTabs are used to layer related pieces of information together and display one layer at a time on the same URL. Use Tabs to alternate between views within the same context. [In Page Navigation](/components/in-page-navigation) is a collection of anchors, rather than buttons. Use In Page Navigation to switch between different, related pages. Tabs replace the entire view based on the selected tab. In Page Navigation links navigate the user to a new page.\n", + similarity: 0.845560073852539, + source: "docs", + type: "markdown", + meta: { + slug: "/components/tabs/", + title: "Tabs", + package: "@twilio-paste/tabs", + description: "Tabs are labeled controls that allow users to switch between multiple views within a page.", + }, + heading: "Tabs vs. In Page Navigation", + slug: "tabs-vs-in-page-navigation", + }, + ], + "https://github.com/twilio-labs/paste/discussions/3356": [ + { + path: "https://github.com/twilio-labs/paste/discussions/3356", + content: + '# switching pages in the breadcrumbs\nhey, we are trying to reproduce patter that is being used in Evergreen. It\'s a breadcrumb combined with minimal button which - once we click - opens a list and we can jump into the another page. \r\n\r\nAny suggestions how to handle this type of case?\r\n\r\nZrzut ekranu 2023-07-27 o 09 11 40\r\n', + similarity: 0.840333878993988, + source: "twilio-labs/paste", + type: "github-discussions", + meta: { + id: "D_kwDOC9UAQs4AUxlu", + title: "switching pages in the breadcrumbs", + updatedAt: "2023-08-03T16:48:57Z", + }, + heading: "switching pages in the breadcrumbs", + slug: "discussion-5445998", + }, + ], + "https://github.com/twilio-labs/paste/discussions/3222": [ + { + path: "https://github.com/twilio-labs/paste/discussions/3222", + content: + "# Page interaction on click for pagination - Answer\nHi @tonge495 \n\nI don't think we have a particular preference, it's largely based on user expectations based on what you're actually doing, aligning to default browser behaviours.\n\nThere are largely two approaches:\n\n1) Each page in the pagination takes the user to a new route/url. When visiting that route the table of results will always be the same. That way you can share your search query and results (Preferred). In this case the action is a page navigation, just like navigating to any other page, and should act accordingly. This usually takes a user to the very top of the page.\n\n2) Each page in the pagination just re-renders the table above. No route change. In this case you must manually place the user above the table of results to inform them that something on the page has happened. \n\nIn both cases, whether you jump or soft scroll to the new location is up to you, and potentially what action is being taken in the browser, will dictate what you _can_ do.\n\nHope that helps a little.", + similarity: 0.832261383533478, + source: "twilio-labs/paste", + type: "github-discussions", + meta: { + id: "D_kwDOC9UAQs4ATwEp", + title: "Page interaction on click for pagination", + updatedAt: "2023-05-10T20:22:07Z", + }, + heading: "Page interaction on click for pagination - Answer", + slug: "discussion-answer-5177641", + }, + ], + "https://github.com/twilio-labs/paste/discussions/2337": [ + { + path: "https://github.com/twilio-labs/paste/discussions/2337", + content: + '# Buttons with link functionality\nHi Team!\r\n\r\nI have some questions about buttons with link functionality.\r\nThe paste guidelines suggest to use buttons with link functionality when we want to navigate the user, the addition of the icons help indicate the action performed on click is a navigation.\r\nIs it possible to get more context around the term "navigate"?\r\n\r\nDoes navigate mean:\r\n- going somewhere else on the same page\r\n- going to a completely new page\r\n- going from one dedicated place in the side navigation to another\r\n- displaying new UI\r\n\r\nI\'ve looked at other parts of console, and navigate means that you are going from one dedicated place in the side navigation to another. The A2P homepage currently uses buttons with link functionality and I just want to make sure we are utilizing them in the correct way.\r\n\r\nThank you so much for all your help! ', + similarity: 0.829035758972168, + source: "twilio-labs/paste", + type: "github-discussions", + meta: { + id: "D_kwDOC9UAQs4APLzh", + title: "Buttons with link functionality", + updatedAt: "2022-07-20T09:29:56Z", + }, + heading: "Buttons with link functionality", + slug: "discussion-3980513", + }, + { + path: "https://github.com/twilio-labs/paste/discussions/2337", + content: + '# Buttons with link functionality - Answer\nHello again, @jkiga 👋 \r\n\r\nWe define **navigate** to mean any **route based URL change**.\r\n\r\nSo taking a look at your list of examples 👇 \r\n> going somewhere else on the same page ✅\r\n\r\nThis is only navigation when that change in position (e.g. "going somewhere") is caused by a change in location (e.g the URL). For example, in-page navigation using `id` attributes and matching hashed `href` attributes on the anchor.\r\n
\r\n> going to a completely new page ✅\r\n\r\nIf the change in page implies a change in location (URL), then yes.\r\n
\r\n> going from one dedicated place in the side navigation to another ✅\r\n\r\nClicking on a navigation item and being redirected to a new location (e.g, new page new URL), is in fact navigation.\r\n
\r\n> displaying new UI 🟨 \r\n\r\nNot necessarily. You can couple page content/UI to location, but they are not necessarily always coupled.\r\n', + similarity: 0.826784372329712, + source: "twilio-labs/paste", + type: "github-discussions", + meta: { + id: "D_kwDOC9UAQs4APLzh", + title: "Buttons with link functionality", + updatedAt: "2022-07-20T09:29:56Z", + }, + heading: "Buttons with link functionality - Answer", + slug: "discussion-answer-3980513", + }, + ], + "https://github.com/twilio-labs/paste/discussions/3307": [ + { + path: "https://github.com/twilio-labs/paste/discussions/3307", + content: + '# No vertical in-page navigation\nHello team, \r\n\r\nPer paste guidance we should be using in-page navigation instead of tabs if we are navigating user to a different URL per tab. \r\nFor Segment-> Unify settings we need both a horizontal in-page navigation and a vertical in-page navigation. However it seems like we don\'t have a vertical in-page navigation per my ENG team\'s investigation. Could you confirm if that\'s true?\r\n\r\nIf that\'s the case, should we use vertical tabs instead or use 2 horizontal in-page navigation? I personally think two horizontal in-page navigation is bad UX. \r\n\r\nThank you so much for your help!\r\n\r\nScreenshot 2023-07-05 at 1 38 05 PM\r\n', + similarity: 0.827467799186707, + source: "twilio-labs/paste", + type: "github-discussions", + meta: { + id: "D_kwDOC9UAQs4AUfk9", + title: "No vertical in-page navigation", + updatedAt: "2023-08-02T17:18:19Z", + }, + heading: "No vertical in-page navigation", + slug: "discussion-5372221", + }, + ], +}; + +const MockSearchModal: React.FC<{ + results: GroupedSearchResults; + searchQuery: string; + loading?: boolean; + showEmptyState?: boolean; +}> = ({ results, searchQuery, loading, showEmptyState }) => { + const modalHeadingId = useUID(); + const inputRef = React.useRef(null); + return ( + {}} initialFocusRef={inputRef}> + {}} + hasResults={Object.keys(results).length > 0} + onClear={() => {}} + ariaLabelledBy={modalHeadingId} + inputValue={searchQuery} + onChange={() => {}} + inputRef={inputRef} + /> + {loading && Object.keys(results).length === 0 && } + {showEmptyState && Object.keys(results).length === 0 && } + + + ); +}; +export const SiteSearchTrigger: StoryFn = () => { + return ; +}; + +export const SiteSearchModalInitialState: StoryFn = () => { + return ; +}; + +export const SiteSearchModalLoading: StoryFn = () => { + return ; +}; + +export const SiteSearchModalEmptyState: StoryFn = () => { + return ; +}; + +export const SiteSearchModalResultsState: StoryFn = () => { + return ; +}; + +export default { + title: "Website/SiteSearch", +}; diff --git a/packages/paste-website/types/index.d.ts b/packages/paste-website/types/index.d.ts index 37b8fed319..ec312c71b6 100644 --- a/packages/paste-website/types/index.d.ts +++ b/packages/paste-website/types/index.d.ts @@ -19,4 +19,3 @@ declare module '*.mdx' { } } -declare module '@docsearch/react/modal'; diff --git a/yarn.lock b/yarn.lock index 1dea887faa..3c65fe2053 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,166 +12,6 @@ __metadata: languageName: node linkType: hard -"@algolia/autocomplete-core@npm:1.7.4": - version: 1.7.4 - resolution: "@algolia/autocomplete-core@npm:1.7.4" - dependencies: - "@algolia/autocomplete-shared": 1.7.4 - checksum: cd7c0badec2dd7f32eb1c567e740473df41d0b5cfdc009efc2b44d2c72e30d90a05882ca0616d6dc29326177d5183a7fd9c6189e5eab3abe26936e232ac5f43a - languageName: node - linkType: hard - -"@algolia/autocomplete-preset-algolia@npm:1.7.4": - version: 1.7.4 - resolution: "@algolia/autocomplete-preset-algolia@npm:1.7.4" - dependencies: - "@algolia/autocomplete-shared": 1.7.4 - peerDependencies: - "@algolia/client-search": ">= 4.9.1 < 6" - algoliasearch: ">= 4.9.1 < 6" - checksum: 4ea134757d611d1b7489f34b4366d103fb981dde3f75f39762fb71142f23bd024825f7541ab756ead9c87e223184616fd74b7762982054c96927fecd5a6e6e3e - languageName: node - linkType: hard - -"@algolia/autocomplete-shared@npm:1.7.4": - version: 1.7.4 - resolution: "@algolia/autocomplete-shared@npm:1.7.4" - checksum: d304b1e3523ccf36a4a21ef9c116c83360fc1bffc595e888f05c35ab00de293104184dafebd9b9ed8ac5ffa5c416ddd4b1139e9794a253f52863c1ae544c2c9c - languageName: node - linkType: hard - -"@algolia/cache-browser-local-storage@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/cache-browser-local-storage@npm:4.13.0" - dependencies: - "@algolia/cache-common": 4.13.0 - checksum: ad02bf64342f5df1c14713566e060afaf3a7c272f8e66cf19d09628ed2deb6621119f92347aa3f914528bc0d50561786e36e5e519b0ae274de6b39de518b313e - languageName: node - linkType: hard - -"@algolia/cache-common@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/cache-common@npm:4.13.0" - checksum: 04520b56579e0b67ba53e7fbb77d51a9edc1e85b7bbafbb114ec84dd104ec3f29f87e54d5e8c348dcf22f00c224ee69d4323acd262e5c1b7444b4036b0441634 - languageName: node - linkType: hard - -"@algolia/cache-in-memory@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/cache-in-memory@npm:4.13.0" - dependencies: - "@algolia/cache-common": 4.13.0 - checksum: 3e1357d679cc665a375fc3752946fbe951af67b9a66df0fa4f14e68522f6ce45f2849f3cddf9cf64a8bac1d734f0b6efb1a90209a6589f4a65d837b080dd6729 - languageName: node - linkType: hard - -"@algolia/client-account@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/client-account@npm:4.13.0" - dependencies: - "@algolia/client-common": 4.13.0 - "@algolia/client-search": 4.13.0 - "@algolia/transporter": 4.13.0 - checksum: ccb4e98b9ea0bbe56a9f4e633c409b62a8be151e550a86abe3bf19266f5e9313a0e9aa16c4da1122deff2bebdc174e1a33e2d807111dd07113afad81304855e4 - languageName: node - linkType: hard - -"@algolia/client-analytics@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/client-analytics@npm:4.13.0" - dependencies: - "@algolia/client-common": 4.13.0 - "@algolia/client-search": 4.13.0 - "@algolia/requester-common": 4.13.0 - "@algolia/transporter": 4.13.0 - checksum: 315d4a26e261fa868ecf3869f204ee712cb03ab36c33c9a0a181405485d639305623dfd683b5d2e290cd3511315bee449541a512b9e4375592d0aa9838514328 - languageName: node - linkType: hard - -"@algolia/client-common@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/client-common@npm:4.13.0" - dependencies: - "@algolia/requester-common": 4.13.0 - "@algolia/transporter": 4.13.0 - checksum: 00b467b58f884cf8c037acb4f473e4a0ae97af0a357741004d3241e07f63aa2b3a1a736e474fe98c85c2a03a4772903c5da2843b3364dbbb566d482aa4e47d31 - languageName: node - linkType: hard - -"@algolia/client-personalization@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/client-personalization@npm:4.13.0" - dependencies: - "@algolia/client-common": 4.13.0 - "@algolia/requester-common": 4.13.0 - "@algolia/transporter": 4.13.0 - checksum: e93afa1036bb5b5f2f3e78d11cef55436e21e9f8efdd9d191e96e6be5cc078bda5fedcffa79037b6b1874f1582f17ae14e11e4bca13d254dfcee76966f0be6ae - languageName: node - linkType: hard - -"@algolia/client-search@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/client-search@npm:4.13.0" - dependencies: - "@algolia/client-common": 4.13.0 - "@algolia/requester-common": 4.13.0 - "@algolia/transporter": 4.13.0 - checksum: 0a14029d2ea6b0fbb0337eee63268e8014d3075c590c17812df4fadafa3c3f5a7daa8dee5adb5258fcc3d9c0855cf54b1925b3af4ce8b771369911d121acd40a - languageName: node - linkType: hard - -"@algolia/logger-common@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/logger-common@npm:4.13.0" - checksum: 11a6ee5d380b4f1f1c09971e9ef9796e328959ddf23a0bb5e66867a31efe0337f0c1666f062d5ff9f287efdec44c00f799df24fb6cbd182773fb1e7b3e94ff16 - languageName: node - linkType: hard - -"@algolia/logger-console@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/logger-console@npm:4.13.0" - dependencies: - "@algolia/logger-common": 4.13.0 - checksum: ee5bae8b5165bd9c4c90b3c4c0560f04f836494a4cee1be61725235598707ef22a8173283673613c93d211ba25b7602c693060bea313aea3aecf47fd5979ed94 - languageName: node - linkType: hard - -"@algolia/requester-browser-xhr@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/requester-browser-xhr@npm:4.13.0" - dependencies: - "@algolia/requester-common": 4.13.0 - checksum: cc1baf68ef5b30db584c07f07a9a3ebde67fc9fca4d565cb8567603f67861d515fa7f301be1c33929bb20f338d412a8d4c82319cdb7b7e8c4e68898389faa650 - languageName: node - linkType: hard - -"@algolia/requester-common@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/requester-common@npm:4.13.0" - checksum: 3c12613b2b31c7b67406904c919ad746653033a2d330eaaa37664c37bda764362d2370ed3e12d9ef9257c4f3bfc0ede92d2aaf3b25aa68023f44f487bcb23926 - languageName: node - linkType: hard - -"@algolia/requester-node-http@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/requester-node-http@npm:4.13.0" - dependencies: - "@algolia/requester-common": 4.13.0 - checksum: b708a96ba56e56155b6167ee40ea24de382d7e3148207da576fccd39dfd7f48bbf01a40a16899038d436a870e7f3a46863fc69e2ef84155ae54e37b9299cd01b - languageName: node - linkType: hard - -"@algolia/transporter@npm:4.13.0": - version: 4.13.0 - resolution: "@algolia/transporter@npm:4.13.0" - dependencies: - "@algolia/cache-common": 4.13.0 - "@algolia/logger-common": 4.13.0 - "@algolia/requester-common": 4.13.0 - checksum: a9e342872f2234ca50aadb513b938bd10e864f1c718190ac1c2b721389dbff8de64bd3e60a484b341a9307cbc97e1fc7802db481558f7663312c70a209947862 - languageName: node - linkType: hard - "@ampproject/remapping@npm:^2.2.0": version: 2.2.1 resolution: "@ampproject/remapping@npm:2.2.1" @@ -2772,36 +2612,6 @@ __metadata: languageName: node linkType: hard -"@docsearch/css@npm:3.3.3, @docsearch/css@npm:^3.3.3": - version: 3.3.3 - resolution: "@docsearch/css@npm:3.3.3" - checksum: c3e678dd5e05a962d3e29b4c953632a013af3a352ad99d0e630546409e665684e122265034bca1619d9bd659e42d35c7cc90ee373836fcfb2614aae2057c5dc1 - languageName: node - linkType: hard - -"@docsearch/react@npm:^3.3.3": - version: 3.3.3 - resolution: "@docsearch/react@npm:3.3.3" - dependencies: - "@algolia/autocomplete-core": 1.7.4 - "@algolia/autocomplete-preset-algolia": 1.7.4 - "@docsearch/css": 3.3.3 - algoliasearch: ^4.0.0 - peerDependencies: - "@types/react": ">= 16.8.0 < 19.0.0" - react: ">= 16.8.0 < 19.0.0" - react-dom: ">= 16.8.0 < 19.0.0" - peerDependenciesMeta: - "@types/react": - optional: true - react: - optional: true - react-dom: - optional: true - checksum: 8a31c175853b61ee80748abc0cebdc33d247483643c4151a430e05d37f159bf59ea08cb69f878cff7787d3ca122b664701575543914d3c3692b448b63d3ad716 - languageName: node - linkType: hard - "@emotion/babel-plugin-jsx-pragmatic@npm:^0.1.5": version: 0.1.5 resolution: "@emotion/babel-plugin-jsx-pragmatic@npm:0.1.5" @@ -13778,8 +13588,6 @@ __metadata: resolution: "@twilio-paste/website@workspace:packages/paste-website" dependencies: "@datadog/browser-rum": ^4.46.0 - "@docsearch/css": ^3.3.3 - "@docsearch/react": ^3.3.3 "@hookform/error-message": 2.0.0 "@mdx-js/loader": ^1.6.22 "@mdx-js/mdx": ^1.6.22 @@ -13930,6 +13738,7 @@ __metadata: react-dom: ^18.0.0 react-github-button: ^0.1.11 react-hook-form: ^7.30.0 + react-hotkeys-hook: ^4.4.1 react-live: ^3.1.1 react-scrollspy: ^3.4.0 react-visibility-sensor: 5.1.1 @@ -16150,28 +15959,6 @@ __metadata: languageName: node linkType: hard -"algoliasearch@npm:^4.0.0": - version: 4.13.0 - resolution: "algoliasearch@npm:4.13.0" - dependencies: - "@algolia/cache-browser-local-storage": 4.13.0 - "@algolia/cache-common": 4.13.0 - "@algolia/cache-in-memory": 4.13.0 - "@algolia/client-account": 4.13.0 - "@algolia/client-analytics": 4.13.0 - "@algolia/client-common": 4.13.0 - "@algolia/client-personalization": 4.13.0 - "@algolia/client-search": 4.13.0 - "@algolia/logger-common": 4.13.0 - "@algolia/logger-console": 4.13.0 - "@algolia/requester-browser-xhr": 4.13.0 - "@algolia/requester-common": 4.13.0 - "@algolia/requester-node-http": 4.13.0 - "@algolia/transporter": 4.13.0 - checksum: 58b9deacb5c9b3b0cd045d519dde805b8e069ce8b524a280a858e06d848ddf6ef8be1871d0323bf87e0b4e939ca469ee489be84926d2fcf387a2561ee74ddbec - languageName: node - linkType: hard - "ansi-align@npm:^2.0.0": version: 2.0.0 resolution: "ansi-align@npm:2.0.0" @@ -36630,6 +36417,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"react-hotkeys-hook@npm:^4.4.1": + version: 4.4.1 + resolution: "react-hotkeys-hook@npm:4.4.1" + peerDependencies: + react: ">=16.8.1" + react-dom: ">=16.8.1" + checksum: c03d5d013ec41f3a63a3dd7d704d2fc98fba227811f7a1222af8651705f1cef983b7ccc4325f9910fec920156b14b4f05101882dc7d0bde1601ba56c01d5f162 + languageName: node + linkType: hard + "react-inspector@npm:^6.0.0": version: 6.0.1 resolution: "react-inspector@npm:6.0.1" From 97f86ce2464426b754efc3ab080d8c7d0b9e5a9b Mon Sep 17 00:00:00 2001 From: Si Taggart Date: Thu, 12 Oct 2023 15:33:29 -0700 Subject: [PATCH 2/9] chore: pr feedback --- .../src/components/site-search/SearchForm.tsx | 2 +- .../site-search/SearchResultDocs.tsx | 6 +-- .../components/site-search/SearchResults.tsx | 39 ++++++++++++------- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/packages/paste-website/src/components/site-search/SearchForm.tsx b/packages/paste-website/src/components/site-search/SearchForm.tsx index 14fa795468..f68dfd39f4 100644 --- a/packages/paste-website/src/components/site-search/SearchForm.tsx +++ b/packages/paste-website/src/components/site-search/SearchForm.tsx @@ -57,7 +57,7 @@ const SearchForm: React.FC> = ({ type="text" id={inputID} name="search-input" - placeholder="Enter a question or keyword" + placeholder={'Try "button" or "what is a design token?"'} value={inputValue} onChange={onChange} data-cy="paste-docsearch-input" diff --git a/packages/paste-website/src/components/site-search/SearchResultDocs.tsx b/packages/paste-website/src/components/site-search/SearchResultDocs.tsx index c68291b740..9eb6a9cbe3 100644 --- a/packages/paste-website/src/components/site-search/SearchResultDocs.tsx +++ b/packages/paste-website/src/components/site-search/SearchResultDocs.tsx @@ -50,19 +50,15 @@ export const SearchResultDocs: React.FC<{ searchItem: SearchItem }> = ({ searchI href={`${searchItem.meta.slug}#${searchItem.slug}`} paddingY="space30" paddingX="space40" + borderRadius="borderRadius30" outline="none" - borderLeftWidth="borderWidth20" - borderLeftStyle="solid" - borderLeftColor="transparent" _hover={{ color: "colorTextPrimary", backgroundColor: "colorBackgroundPrimaryWeakest", - borderColor: "colorBorderPrimary", }} _focus={{ color: "colorTextPrimary", backgroundColor: "colorBackgroundPrimaryWeakest", - borderColor: "colorBorderPrimary", }} > = ({ title, p padding="space20" > - - + + {title} @@ -39,12 +39,28 @@ const DiscussionHeading: React.FC<{ title: string; path: string }> = ({ title, p ); }; -const DocumentationHeading: React.FC<{ title: string }> = ({ title }) => { +const DocumentationHeading: React.FC<{ title: string; slug: string }> = ({ title, slug }) => { return ( - + - - {title} + + + + {sentenceCase(slug.split("/")[1])} + + + {title} + + ); @@ -70,20 +86,15 @@ const SearchResultsList: React.FC = ({ results }) => { {resultType === "github-discussions" ? ( ) : ( - + )} {resultParent.description && ( - + {resultParent.description} )} - {resultParent.slug && ( - - {sentenceCase(resultParent.slug.split("/")[1])} - - )} {resultType === "markdown" && ( - + {resultSections.map((result) => { return ( From 13852015f5404628f1bb077afc9ff244ef1c860e Mon Sep 17 00:00:00 2001 From: Si Taggart Date: Thu, 12 Oct 2023 17:35:41 -0700 Subject: [PATCH 3/9] chore: types fixes --- .../site-search/SearchResultDocs.tsx | 4 +-- .../components/site-search/SearchResults.tsx | 17 +++++----- .../src/components/site-search/types.ts | 31 +++++++++++++------ 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/packages/paste-website/src/components/site-search/SearchResultDocs.tsx b/packages/paste-website/src/components/site-search/SearchResultDocs.tsx index 9eb6a9cbe3..b8ec9d1983 100644 --- a/packages/paste-website/src/components/site-search/SearchResultDocs.tsx +++ b/packages/paste-website/src/components/site-search/SearchResultDocs.tsx @@ -5,7 +5,7 @@ import Markdown from "markdown-to-jsx"; import Link from "next/link"; import * as React from "react"; -import { type SearchItem } from "./types"; +import { type DocSearchItem } from "./types"; export const FakeHeading: React.FC = ({ children }) => { return ( @@ -39,7 +39,7 @@ export const FakeListItem: React.FC = ({ children }) => ); }; -export const SearchResultDocs: React.FC<{ searchItem: SearchItem }> = ({ searchItem }) => { +export const SearchResultDocs: React.FC<{ searchItem: DocSearchItem }> = ({ searchItem }) => { return ( = ({ results }) => { <> {Object.keys(results).map((path) => { const resultSections = results[path]; + const resultType = resultSections[0].type; const resultParent = { path: resultSections[0].path, title: resultSections[0].meta.title, - description: resultSections[0].meta.description, - slug: resultSections[0].meta.slug, + ...(resultType === "markdown" && { + description: resultSections[0].meta.description, + slug: resultSections[0].meta.slug, + }), }; - const resultType = resultSections[0].type; return ( - {resultType === "github-discussions" ? ( + {resultType === "github-discussions" && ( - ) : ( + )} + {resultType === "markdown" && resultParent.slug != null && ( )} {resultParent.description && ( @@ -137,7 +140,7 @@ const SearchResultsList: React.FC = ({ results }) => { }, }} > - + ); })} diff --git a/packages/paste-website/src/components/site-search/types.ts b/packages/paste-website/src/components/site-search/types.ts index f57c7e303c..5e12f0d5d5 100644 --- a/packages/paste-website/src/components/site-search/types.ts +++ b/packages/paste-website/src/components/site-search/types.ts @@ -1,20 +1,31 @@ -export type SearchItem = { - type: string; - source: string; +type CommonSearchItem = { + content: string; + heading: string; path: string; similarity: number; + source: string; slug: string; - content: string; - heading: string; meta: { - id?: string; - updatedAt?: string; title: string; - description?: string; - package?: string; - slug?: string; }; }; +export type DocSearchItem = CommonSearchItem & { + type: "markdown"; + meta: { + description: string; + package: string; + slug: string; + }; +}; +export type DiscussionSearchItem = CommonSearchItem & { + type: "github-discussions"; + meta: { + id: string; + updatedAt: string; + }; +}; + +export type SearchItem = DocSearchItem | DiscussionSearchItem; export type SearchResults = { data: SearchItem[]; From 31da30595c0178a5f93d0cb7d21cf7bfc5f678fd Mon Sep 17 00:00:00 2001 From: Si Taggart Date: Fri, 13 Oct 2023 13:57:17 -0700 Subject: [PATCH 4/9] fix: pr changes and a search cache --- .../site-search/SearchResultDocs.tsx | 24 ++++++------ .../components/site-search/SearchResults.tsx | 10 +++-- .../src/components/site-search/index.tsx | 39 ++++++++++++------- 3 files changed, 44 insertions(+), 29 deletions(-) diff --git a/packages/paste-website/src/components/site-search/SearchResultDocs.tsx b/packages/paste-website/src/components/site-search/SearchResultDocs.tsx index b8ec9d1983..e22771f1a6 100644 --- a/packages/paste-website/src/components/site-search/SearchResultDocs.tsx +++ b/packages/paste-website/src/components/site-search/SearchResultDocs.tsx @@ -7,7 +7,7 @@ import * as React from "react"; import { type DocSearchItem } from "./types"; -export const FakeHeading: React.FC = ({ children }) => { +export const SearchResultHeading: React.FC = ({ children }) => { return ( {children} @@ -15,7 +15,7 @@ export const FakeHeading: React.FC = ({ children }) => ); }; -export const FakeParagraph: React.FC = ({ children }) => { +export const SearchResultParagraph: React.FC = ({ children }) => { return ( {children} @@ -23,7 +23,7 @@ export const FakeParagraph: React.FC = ({ children }) = ); }; -export const FakeList: React.FC = ({ children }) => { +export const SearchResultList: React.FC = ({ children }) => { return ( {children} @@ -31,7 +31,7 @@ export const FakeList: React.FC = ({ children }) => { ); }; -export const FakeListItem: React.FC = ({ children }) => { +export const SearchResultListItem: React.FC = ({ children }) => { return ( {children} @@ -42,7 +42,7 @@ export const FakeListItem: React.FC = ({ children }) => export const SearchResultDocs: React.FC<{ searchItem: DocSearchItem }> = ({ searchItem }) => { return ( = ({ sear component: Anchor, }, h1: { - component: FakeHeading, + component: SearchResultHeading, }, h2: { - component: FakeHeading, + component: SearchResultHeading, }, h3: { - component: FakeHeading, + component: SearchResultHeading, }, h4: { - component: FakeHeading, + component: SearchResultHeading, }, p: { - component: FakeParagraph, + component: SearchResultParagraph, }, ul: { - component: FakeList, + component: SearchResultList, }, li: { - component: FakeListItem, + component: SearchResultListItem, }, }, }} diff --git a/packages/paste-website/src/components/site-search/SearchResults.tsx b/packages/paste-website/src/components/site-search/SearchResults.tsx index ff49f38f41..f4d0c6152b 100644 --- a/packages/paste-website/src/components/site-search/SearchResults.tsx +++ b/packages/paste-website/src/components/site-search/SearchResults.tsx @@ -1,6 +1,8 @@ +import { secureExternalLink } from "@twilio-paste/anchor"; import { Box } from "@twilio-paste/box"; import { Heading } from "@twilio-paste/heading"; import { DocumentationIcon } from "@twilio-paste/icons/esm/DocumentationIcon"; +import { LinkExternalIcon } from "@twilio-paste/icons/esm/LinkExternalIcon"; import Link from "next/link"; import * as React from "react"; @@ -17,7 +19,7 @@ const DiscussionHeading: React.FC<{ title: string; path: string }> = ({ title, p return ( = ({ title, p outline="none" display="block" padding="space20" + {...secureExternalLink(path)} > {title} + @@ -49,7 +53,7 @@ const DocumentationHeading: React.FC<{ title: string; slug: string }> = ({ title {sentenceCase(slug.split("/")[1])} = ({ results }) => { height: "100%", }} _last={{ - // @ts-expect-error we technically only allow design tokens + // @ts-expect-error we technically only allow design tokens but we need access to override the after pseudo element "&:after": { display: "none", }, diff --git a/packages/paste-website/src/components/site-search/index.tsx b/packages/paste-website/src/components/site-search/index.tsx index 742af7cf62..ed10084ae2 100644 --- a/packages/paste-website/src/components/site-search/index.tsx +++ b/packages/paste-website/src/components/site-search/index.tsx @@ -25,31 +25,42 @@ const SiteSearch: React.FC> = ({ isOpen const inputRef = React.useRef(null); - React.useEffect(() => { + const performSearch = React.useCallback(async (): Promise => { if (searchQuery === "") { - // reset the search state when we empty the search query to start again - setSearchInitialState(true); + return; } - }, [searchQuery]); - const performSearch = async (): Promise => { - setResults({}); setLoading(true); + try { - const response = await fetch("/api/docs-search", { - method: "POST", - body: JSON.stringify({ prompt: searchQuery }), - }); - const json = (await response.json()) as SearchResults; - const groupedResults = groupResults(json.data); - setResults(groupedResults); + const cachedResults = sessionStorage.getItem(searchQuery); + if (cachedResults) { + setResults(JSON.parse(cachedResults)); + } else { + const response = await fetch("/api/docs-search", { + method: "POST", + body: JSON.stringify({ prompt: searchQuery }), + }); + const json = (await response.json()) as SearchResults; + const groupedResults = groupResults(json.data); + setResults(groupedResults); + sessionStorage.setItem(searchQuery, JSON.stringify(groupedResults)); + } } catch (error) { // eslint-disable-next-line no-console console.error(error); } + setSearchInitialState(false); setLoading(false); - }; + }, [searchQuery]); + + React.useEffect(() => { + if (searchQuery === "") { + // reset the search state when we empty the search query to start again + setSearchInitialState(true); + } + }, [searchQuery]); const handleOnChange = (event: React.ChangeEvent): void => { setSearchQuery(event.target.value); From 0bf9d62c6a0a61a572d894aaae634818cfe26ce6 Mon Sep 17 00:00:00 2001 From: Simon Taggart Date: Fri, 13 Oct 2023 14:05:04 -0700 Subject: [PATCH 5/9] chore: Apply suggestions from code review Co-authored-by: TheSisb --- internal-docs/engineering/doc-site/docsearch.md | 4 ++-- internal-docs/engineering/technologies.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal-docs/engineering/doc-site/docsearch.md b/internal-docs/engineering/doc-site/docsearch.md index 6cdde540c1..9992eecb1f 100644 --- a/internal-docs/engineering/doc-site/docsearch.md +++ b/internal-docs/engineering/doc-site/docsearch.md @@ -1,6 +1,6 @@ # Docsearch -The search is provided a custom index hosted on Supabase and created by us on a nightly Cron Github Action. +The search is provided a custom index hosted on Supabase and created by us on a [nightly Cron Github Action](https://github.com/twilio-labs/paste/blob/main/.github/workflows/update_docs_embed.yml). The Supabase login is stored in OnePassword. @@ -16,7 +16,7 @@ The same happens with Github discussions. The discussion is a page and is split We can then perform a similarity search using the user input as the query. -Embeddings are cerated using the the [embedding script](../../../packages/paste-website/scripts/search/). +Embeddings are generated using the the [embedding script](../../../packages/paste-website/scripts/search/). ## Returning results diff --git a/internal-docs/engineering/technologies.md b/internal-docs/engineering/technologies.md index c94e76009a..034633755c 100644 --- a/internal-docs/engineering/technologies.md +++ b/internal-docs/engineering/technologies.md @@ -96,7 +96,7 @@ The documentation site is a [NextJS](https://nextjs.org/) website using the late The content is largely written in [MDX](https://mdxjs.com/) pages. -The search is custom built based on OpenAI embeddings, a vector DB and a similarity search that allows us to return results across mdx pages and Github discussions. +The search is custom built based on OpenAI embeddings, a vector DB and a similarity search that allows us to return results across mdx pages and Github discussions. [Learn more](https://github.com/twilio-labs/paste/blob/main/internal-docs/engineering/doc-site/docsearch.md). The [roadmap](https://paste.twilio.design/roadmap/) is stored in an [Airtable](https://airtable.com/) and imported upon building the website via the [Airtable JS client](https://github.com/Airtable/airtable.js). From d3adb7b1ac6448c9fa75b1cd574730cc93c97786 Mon Sep 17 00:00:00 2001 From: Si Taggart Date: Fri, 13 Oct 2023 14:31:43 -0700 Subject: [PATCH 6/9] chore: unique id for svg --- .../site-search/SearchEmptyIllustration.tsx | 217 +++++++++--------- 1 file changed, 111 insertions(+), 106 deletions(-) diff --git a/packages/paste-website/src/components/site-search/SearchEmptyIllustration.tsx b/packages/paste-website/src/components/site-search/SearchEmptyIllustration.tsx index 4d996cd35a..83411a511f 100644 --- a/packages/paste-website/src/components/site-search/SearchEmptyIllustration.tsx +++ b/packages/paste-website/src/components/site-search/SearchEmptyIllustration.tsx @@ -1,126 +1,131 @@ /* DISCLAIMER: this is an example, not meant to be used in production */ +import { useUID } from "@twilio-paste/uid-library"; + export const SearchEmptyIllustration = ({ width, height, ...props -}: { width: number; height: number }): JSX.Element => ( - - - - - - - - - - - - - - - - - - - - - - - - +}: { width: number; height: number }): JSX.Element => { + const maskID = useUID(); + return ( + + + + + + - - + + + + + + + - - - -); + d="M110.93 47.93c-.52-.1-7.51-.18-7.95-.18-.57 0-.74.81.04.81.44 0 7.62-.07 7.91-.12.32-.05.31-.46 0-.52v.01Z" + /> + + + + + + + + + + + + + + + + + ); +}; From 30ed3f399e88ee40b2b1f8af7565a4d254e954a6 Mon Sep 17 00:00:00 2001 From: Si Taggart Date: Fri, 13 Oct 2023 14:46:55 -0700 Subject: [PATCH 7/9] chore: final changes --- packages/paste-website/package.json | 1 + .../stories/SiteSearch.stories.tsx | 155 +----------------- .../__fixtures__/searchresults.fixture.ts | 153 +++++++++++++++++ yarn.lock | 1 + 4 files changed, 156 insertions(+), 154 deletions(-) create mode 100644 packages/paste-website/stories/__fixtures__/searchresults.fixture.ts diff --git a/packages/paste-website/package.json b/packages/paste-website/package.json index 51e5238bb7..186c9007e7 100644 --- a/packages/paste-website/package.json +++ b/packages/paste-website/package.json @@ -185,6 +185,7 @@ }, "devDependencies": { "@next/eslint-plugin-next": "^13.1.6", + "@storybook/react": "7.0.6", "@testing-library/react": "^13.4.0", "tsx": "^3.12.10" }, diff --git a/packages/paste-website/stories/SiteSearch.stories.tsx b/packages/paste-website/stories/SiteSearch.stories.tsx index c541db8231..8d57c16535 100644 --- a/packages/paste-website/stories/SiteSearch.stories.tsx +++ b/packages/paste-website/stories/SiteSearch.stories.tsx @@ -9,160 +9,7 @@ import { SearchResultsList } from "../src/components/site-search/SearchResults"; import { SearchResultsLoading } from "../src/components/site-search/SearchResultsLoading"; import { type GroupedSearchResults } from "../src/components/site-search/types"; import { SiteHeaderSearch } from "../src/components/site-wrapper/site-header/SiteHeaderSearch"; - -const mockResults = { - "src/pages/components/in-page-navigation/index": [ - { - path: "src/pages/components/in-page-navigation/index", - content: - "### About In Page Navigation\n\nThe In Page Navigation component allows users to navigate to different pages using a styled group of links. Each In Page Navigation Item is an anchor that links to a related page. Each page within the In Page Navigation should also have an In Page Navigation component so that users can easily navigate back and forth within a set of related pages.\n", - similarity: 0.856512844562531, - source: "docs", - type: "markdown", - meta: { - slug: "/components/in-page-navigation/", - title: "In Page Navigation", - package: "@twilio-paste/in-page-navigation", - description: "An In Page Navigation is a group of styled links that lets users navigate between related pages.", - }, - heading: "About In Page Navigation", - slug: "about-in-page-navigation", - }, - { - path: "src/pages/components/in-page-navigation/index", - content: - "### In Page Navigation vs Tabs\n\n[Tabs](/components/tabs) are used to layer related pieces of information together and display one layer at a time on the same URL. Use Tabs to alternate between views within the same context. In Page Navigation is a collection of anchors, rather than buttons. Use In Page Navigation to switch between different, related pages. Tabs replace the entire view based on the selected tab. In Page Navigation links navigate the user to a new page.\n", - similarity: 0.853896617889404, - source: "docs", - type: "markdown", - meta: { - slug: "/components/in-page-navigation/", - title: "In Page Navigation", - package: "@twilio-paste/in-page-navigation", - description: "An In Page Navigation is a group of styled links that lets users navigate between related pages.", - }, - heading: "In Page Navigation vs Tabs", - slug: "in-page-navigation-vs-tabs", - }, - { - path: "src/pages/components/in-page-navigation/index", - content: - "### Accessibility\n\n* Each In Page Navigation must have a unique label. To add the label, add the `aria-label` prop to the `` tag.\n Omit the term 'navigation'- it is redundant since the role is already defined as 'navigation'.\n* To interact with In Page Navigation using the keyboard, use the tab key.\n* Each In Page Navigation must have an In Page Navigation Item which is the currently selected page. To specify which page is current, add the `currentPage` prop to the respective ``. Doing so will set `aria-current=\"page\"` on that link.\n", - similarity: 0.824576377868652, - source: "docs", - type: "markdown", - meta: { - slug: "/components/in-page-navigation/", - title: "In Page Navigation", - package: "@twilio-paste/in-page-navigation", - description: "An In Page Navigation is a group of styled links that lets users navigate between related pages.", - }, - heading: "Accessibility", - slug: "accessibility", - }, - ], - "src/pages/components/tabs/index": [ - { - path: "src/pages/components/tabs/index", - content: - "### Tabs vs. In Page Navigation\n\nTabs are used to layer related pieces of information together and display one layer at a time on the same URL. Use Tabs to alternate between views within the same context. [In Page Navigation](/components/in-page-navigation) is a collection of anchors, rather than buttons. Use In Page Navigation to switch between different, related pages. Tabs replace the entire view based on the selected tab. In Page Navigation links navigate the user to a new page.\n", - similarity: 0.845560073852539, - source: "docs", - type: "markdown", - meta: { - slug: "/components/tabs/", - title: "Tabs", - package: "@twilio-paste/tabs", - description: "Tabs are labeled controls that allow users to switch between multiple views within a page.", - }, - heading: "Tabs vs. In Page Navigation", - slug: "tabs-vs-in-page-navigation", - }, - ], - "https://github.com/twilio-labs/paste/discussions/3356": [ - { - path: "https://github.com/twilio-labs/paste/discussions/3356", - content: - '# switching pages in the breadcrumbs\nhey, we are trying to reproduce patter that is being used in Evergreen. It\'s a breadcrumb combined with minimal button which - once we click - opens a list and we can jump into the another page. \r\n\r\nAny suggestions how to handle this type of case?\r\n\r\nZrzut ekranu 2023-07-27 o 09 11 40\r\n', - similarity: 0.840333878993988, - source: "twilio-labs/paste", - type: "github-discussions", - meta: { - id: "D_kwDOC9UAQs4AUxlu", - title: "switching pages in the breadcrumbs", - updatedAt: "2023-08-03T16:48:57Z", - }, - heading: "switching pages in the breadcrumbs", - slug: "discussion-5445998", - }, - ], - "https://github.com/twilio-labs/paste/discussions/3222": [ - { - path: "https://github.com/twilio-labs/paste/discussions/3222", - content: - "# Page interaction on click for pagination - Answer\nHi @tonge495 \n\nI don't think we have a particular preference, it's largely based on user expectations based on what you're actually doing, aligning to default browser behaviours.\n\nThere are largely two approaches:\n\n1) Each page in the pagination takes the user to a new route/url. When visiting that route the table of results will always be the same. That way you can share your search query and results (Preferred). In this case the action is a page navigation, just like navigating to any other page, and should act accordingly. This usually takes a user to the very top of the page.\n\n2) Each page in the pagination just re-renders the table above. No route change. In this case you must manually place the user above the table of results to inform them that something on the page has happened. \n\nIn both cases, whether you jump or soft scroll to the new location is up to you, and potentially what action is being taken in the browser, will dictate what you _can_ do.\n\nHope that helps a little.", - similarity: 0.832261383533478, - source: "twilio-labs/paste", - type: "github-discussions", - meta: { - id: "D_kwDOC9UAQs4ATwEp", - title: "Page interaction on click for pagination", - updatedAt: "2023-05-10T20:22:07Z", - }, - heading: "Page interaction on click for pagination - Answer", - slug: "discussion-answer-5177641", - }, - ], - "https://github.com/twilio-labs/paste/discussions/2337": [ - { - path: "https://github.com/twilio-labs/paste/discussions/2337", - content: - '# Buttons with link functionality\nHi Team!\r\n\r\nI have some questions about buttons with link functionality.\r\nThe paste guidelines suggest to use buttons with link functionality when we want to navigate the user, the addition of the icons help indicate the action performed on click is a navigation.\r\nIs it possible to get more context around the term "navigate"?\r\n\r\nDoes navigate mean:\r\n- going somewhere else on the same page\r\n- going to a completely new page\r\n- going from one dedicated place in the side navigation to another\r\n- displaying new UI\r\n\r\nI\'ve looked at other parts of console, and navigate means that you are going from one dedicated place in the side navigation to another. The A2P homepage currently uses buttons with link functionality and I just want to make sure we are utilizing them in the correct way.\r\n\r\nThank you so much for all your help! ', - similarity: 0.829035758972168, - source: "twilio-labs/paste", - type: "github-discussions", - meta: { - id: "D_kwDOC9UAQs4APLzh", - title: "Buttons with link functionality", - updatedAt: "2022-07-20T09:29:56Z", - }, - heading: "Buttons with link functionality", - slug: "discussion-3980513", - }, - { - path: "https://github.com/twilio-labs/paste/discussions/2337", - content: - '# Buttons with link functionality - Answer\nHello again, @jkiga 👋 \r\n\r\nWe define **navigate** to mean any **route based URL change**.\r\n\r\nSo taking a look at your list of examples 👇 \r\n> going somewhere else on the same page ✅\r\n\r\nThis is only navigation when that change in position (e.g. "going somewhere") is caused by a change in location (e.g the URL). For example, in-page navigation using `id` attributes and matching hashed `href` attributes on the anchor.\r\n
\r\n> going to a completely new page ✅\r\n\r\nIf the change in page implies a change in location (URL), then yes.\r\n
\r\n> going from one dedicated place in the side navigation to another ✅\r\n\r\nClicking on a navigation item and being redirected to a new location (e.g, new page new URL), is in fact navigation.\r\n
\r\n> displaying new UI 🟨 \r\n\r\nNot necessarily. You can couple page content/UI to location, but they are not necessarily always coupled.\r\n', - similarity: 0.826784372329712, - source: "twilio-labs/paste", - type: "github-discussions", - meta: { - id: "D_kwDOC9UAQs4APLzh", - title: "Buttons with link functionality", - updatedAt: "2022-07-20T09:29:56Z", - }, - heading: "Buttons with link functionality - Answer", - slug: "discussion-answer-3980513", - }, - ], - "https://github.com/twilio-labs/paste/discussions/3307": [ - { - path: "https://github.com/twilio-labs/paste/discussions/3307", - content: - '# No vertical in-page navigation\nHello team, \r\n\r\nPer paste guidance we should be using in-page navigation instead of tabs if we are navigating user to a different URL per tab. \r\nFor Segment-> Unify settings we need both a horizontal in-page navigation and a vertical in-page navigation. However it seems like we don\'t have a vertical in-page navigation per my ENG team\'s investigation. Could you confirm if that\'s true?\r\n\r\nIf that\'s the case, should we use vertical tabs instead or use 2 horizontal in-page navigation? I personally think two horizontal in-page navigation is bad UX. \r\n\r\nThank you so much for your help!\r\n\r\nScreenshot 2023-07-05 at 1 38 05 PM\r\n', - similarity: 0.827467799186707, - source: "twilio-labs/paste", - type: "github-discussions", - meta: { - id: "D_kwDOC9UAQs4AUfk9", - title: "No vertical in-page navigation", - updatedAt: "2023-08-02T17:18:19Z", - }, - heading: "No vertical in-page navigation", - slug: "discussion-5372221", - }, - ], -}; +import { mockResults } from "./__fixtures__/searchresults.fixture"; const MockSearchModal: React.FC<{ results: GroupedSearchResults; diff --git a/packages/paste-website/stories/__fixtures__/searchresults.fixture.ts b/packages/paste-website/stories/__fixtures__/searchresults.fixture.ts new file mode 100644 index 0000000000..04e95221d9 --- /dev/null +++ b/packages/paste-website/stories/__fixtures__/searchresults.fixture.ts @@ -0,0 +1,153 @@ +export const mockResults = { + "src/pages/components/in-page-navigation/index": [ + { + path: "src/pages/components/in-page-navigation/index", + content: + "### About In Page Navigation\n\nThe In Page Navigation component allows users to navigate to different pages using a styled group of links. Each In Page Navigation Item is an anchor that links to a related page. Each page within the In Page Navigation should also have an In Page Navigation component so that users can easily navigate back and forth within a set of related pages.\n", + similarity: 0.856512844562531, + source: "docs", + type: "markdown", + meta: { + slug: "/components/in-page-navigation/", + title: "In Page Navigation", + package: "@twilio-paste/in-page-navigation", + description: "An In Page Navigation is a group of styled links that lets users navigate between related pages.", + }, + heading: "About In Page Navigation", + slug: "about-in-page-navigation", + }, + { + path: "src/pages/components/in-page-navigation/index", + content: + "### In Page Navigation vs Tabs\n\n[Tabs](/components/tabs) are used to layer related pieces of information together and display one layer at a time on the same URL. Use Tabs to alternate between views within the same context. In Page Navigation is a collection of anchors, rather than buttons. Use In Page Navigation to switch between different, related pages. Tabs replace the entire view based on the selected tab. In Page Navigation links navigate the user to a new page.\n", + similarity: 0.853896617889404, + source: "docs", + type: "markdown", + meta: { + slug: "/components/in-page-navigation/", + title: "In Page Navigation", + package: "@twilio-paste/in-page-navigation", + description: "An In Page Navigation is a group of styled links that lets users navigate between related pages.", + }, + heading: "In Page Navigation vs Tabs", + slug: "in-page-navigation-vs-tabs", + }, + { + path: "src/pages/components/in-page-navigation/index", + content: + "### Accessibility\n\n* Each In Page Navigation must have a unique label. To add the label, add the `aria-label` prop to the `` tag.\n Omit the term 'navigation'- it is redundant since the role is already defined as 'navigation'.\n* To interact with In Page Navigation using the keyboard, use the tab key.\n* Each In Page Navigation must have an In Page Navigation Item which is the currently selected page. To specify which page is current, add the `currentPage` prop to the respective ``. Doing so will set `aria-current=\"page\"` on that link.\n", + similarity: 0.824576377868652, + source: "docs", + type: "markdown", + meta: { + slug: "/components/in-page-navigation/", + title: "In Page Navigation", + package: "@twilio-paste/in-page-navigation", + description: "An In Page Navigation is a group of styled links that lets users navigate between related pages.", + }, + heading: "Accessibility", + slug: "accessibility", + }, + ], + "src/pages/components/tabs/index": [ + { + path: "src/pages/components/tabs/index", + content: + "### Tabs vs. In Page Navigation\n\nTabs are used to layer related pieces of information together and display one layer at a time on the same URL. Use Tabs to alternate between views within the same context. [In Page Navigation](/components/in-page-navigation) is a collection of anchors, rather than buttons. Use In Page Navigation to switch between different, related pages. Tabs replace the entire view based on the selected tab. In Page Navigation links navigate the user to a new page.\n", + similarity: 0.845560073852539, + source: "docs", + type: "markdown", + meta: { + slug: "/components/tabs/", + title: "Tabs", + package: "@twilio-paste/tabs", + description: "Tabs are labeled controls that allow users to switch between multiple views within a page.", + }, + heading: "Tabs vs. In Page Navigation", + slug: "tabs-vs-in-page-navigation", + }, + ], + "https://github.com/twilio-labs/paste/discussions/3356": [ + { + path: "https://github.com/twilio-labs/paste/discussions/3356", + content: + '# switching pages in the breadcrumbs\nhey, we are trying to reproduce patter that is being used in Evergreen. It\'s a breadcrumb combined with minimal button which - once we click - opens a list and we can jump into the another page. \r\n\r\nAny suggestions how to handle this type of case?\r\n\r\nZrzut ekranu 2023-07-27 o 09 11 40\r\n', + similarity: 0.840333878993988, + source: "twilio-labs/paste", + type: "github-discussions", + meta: { + id: "D_kwDOC9UAQs4AUxlu", + title: "switching pages in the breadcrumbs", + updatedAt: "2023-08-03T16:48:57Z", + }, + heading: "switching pages in the breadcrumbs", + slug: "discussion-5445998", + }, + ], + "https://github.com/twilio-labs/paste/discussions/3222": [ + { + path: "https://github.com/twilio-labs/paste/discussions/3222", + content: + "# Page interaction on click for pagination - Answer\nHi @tonge495 \n\nI don't think we have a particular preference, it's largely based on user expectations based on what you're actually doing, aligning to default browser behaviours.\n\nThere are largely two approaches:\n\n1) Each page in the pagination takes the user to a new route/url. When visiting that route the table of results will always be the same. That way you can share your search query and results (Preferred). In this case the action is a page navigation, just like navigating to any other page, and should act accordingly. This usually takes a user to the very top of the page.\n\n2) Each page in the pagination just re-renders the table above. No route change. In this case you must manually place the user above the table of results to inform them that something on the page has happened. \n\nIn both cases, whether you jump or soft scroll to the new location is up to you, and potentially what action is being taken in the browser, will dictate what you _can_ do.\n\nHope that helps a little.", + similarity: 0.832261383533478, + source: "twilio-labs/paste", + type: "github-discussions", + meta: { + id: "D_kwDOC9UAQs4ATwEp", + title: "Page interaction on click for pagination", + updatedAt: "2023-05-10T20:22:07Z", + }, + heading: "Page interaction on click for pagination - Answer", + slug: "discussion-answer-5177641", + }, + ], + "https://github.com/twilio-labs/paste/discussions/2337": [ + { + path: "https://github.com/twilio-labs/paste/discussions/2337", + content: + '# Buttons with link functionality\nHi Team!\r\n\r\nI have some questions about buttons with link functionality.\r\nThe paste guidelines suggest to use buttons with link functionality when we want to navigate the user, the addition of the icons help indicate the action performed on click is a navigation.\r\nIs it possible to get more context around the term "navigate"?\r\n\r\nDoes navigate mean:\r\n- going somewhere else on the same page\r\n- going to a completely new page\r\n- going from one dedicated place in the side navigation to another\r\n- displaying new UI\r\n\r\nI\'ve looked at other parts of console, and navigate means that you are going from one dedicated place in the side navigation to another. The A2P homepage currently uses buttons with link functionality and I just want to make sure we are utilizing them in the correct way.\r\n\r\nThank you so much for all your help! ', + similarity: 0.829035758972168, + source: "twilio-labs/paste", + type: "github-discussions", + meta: { + id: "D_kwDOC9UAQs4APLzh", + title: "Buttons with link functionality", + updatedAt: "2022-07-20T09:29:56Z", + }, + heading: "Buttons with link functionality", + slug: "discussion-3980513", + }, + { + path: "https://github.com/twilio-labs/paste/discussions/2337", + content: + '# Buttons with link functionality - Answer\nHello again, @jkiga 👋 \r\n\r\nWe define **navigate** to mean any **route based URL change**.\r\n\r\nSo taking a look at your list of examples 👇 \r\n> going somewhere else on the same page ✅\r\n\r\nThis is only navigation when that change in position (e.g. "going somewhere") is caused by a change in location (e.g the URL). For example, in-page navigation using `id` attributes and matching hashed `href` attributes on the anchor.\r\n
\r\n> going to a completely new page ✅\r\n\r\nIf the change in page implies a change in location (URL), then yes.\r\n
\r\n> going from one dedicated place in the side navigation to another ✅\r\n\r\nClicking on a navigation item and being redirected to a new location (e.g, new page new URL), is in fact navigation.\r\n
\r\n> displaying new UI 🟨 \r\n\r\nNot necessarily. You can couple page content/UI to location, but they are not necessarily always coupled.\r\n', + similarity: 0.826784372329712, + source: "twilio-labs/paste", + type: "github-discussions", + meta: { + id: "D_kwDOC9UAQs4APLzh", + title: "Buttons with link functionality", + updatedAt: "2022-07-20T09:29:56Z", + }, + heading: "Buttons with link functionality - Answer", + slug: "discussion-answer-3980513", + }, + ], + "https://github.com/twilio-labs/paste/discussions/3307": [ + { + path: "https://github.com/twilio-labs/paste/discussions/3307", + content: + '# No vertical in-page navigation\nHello team, \r\n\r\nPer paste guidance we should be using in-page navigation instead of tabs if we are navigating user to a different URL per tab. \r\nFor Segment-> Unify settings we need both a horizontal in-page navigation and a vertical in-page navigation. However it seems like we don\'t have a vertical in-page navigation per my ENG team\'s investigation. Could you confirm if that\'s true?\r\n\r\nIf that\'s the case, should we use vertical tabs instead or use 2 horizontal in-page navigation? I personally think two horizontal in-page navigation is bad UX. \r\n\r\nThank you so much for your help!\r\n\r\nScreenshot 2023-07-05 at 1 38 05 PM\r\n', + similarity: 0.827467799186707, + source: "twilio-labs/paste", + type: "github-discussions", + meta: { + id: "D_kwDOC9UAQs4AUfk9", + title: "No vertical in-page navigation", + updatedAt: "2023-08-02T17:18:19Z", + }, + heading: "No vertical in-page navigation", + slug: "discussion-5372221", + }, + ], +}; diff --git a/yarn.lock b/yarn.lock index 3c65fe2053..4414a7b3fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13598,6 +13598,7 @@ __metadata: "@octokit/core": ^5.0.1 "@octokit/plugin-paginate-graphql": ^4.0.0 "@sparticuz/chromium": ^110.0.0 + "@storybook/react": 7.0.6 "@supabase/supabase-js": ^2.36.0 "@testing-library/react": ^13.4.0 "@twilio-paste/account-switcher": ^3.0.0 From 6ae0c62733663df8c4d41e408cc57f68ec85bc69 Mon Sep 17 00:00:00 2001 From: Si Taggart Date: Tue, 17 Oct 2023 11:11:17 -0700 Subject: [PATCH 8/9] fix: storybook runner tests and vrt --- .../site-header/SiteHeaderSearch.tsx | 4 +++- .../stories/SiteSearch.stories.tsx | 20 ++++++++++++++++--- .../__fixtures__/searchresults.fixture.ts | 4 +++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/paste-website/src/components/site-wrapper/site-header/SiteHeaderSearch.tsx b/packages/paste-website/src/components/site-wrapper/site-header/SiteHeaderSearch.tsx index adfd3ed279..edbe7aa374 100644 --- a/packages/paste-website/src/components/site-wrapper/site-header/SiteHeaderSearch.tsx +++ b/packages/paste-website/src/components/site-wrapper/site-header/SiteHeaderSearch.tsx @@ -6,6 +6,7 @@ import { Text } from "@twilio-paste/text"; import * as React from "react"; import { useHotkeys } from "react-hotkeys-hook"; +import { ScreenReaderOnly } from "@twilio-paste/screen-reader-only"; import { SiteSearch } from "../../site-search"; const SiteHeaderSearch: React.FC = () => { @@ -58,10 +59,11 @@ const SiteHeaderSearch: React.FC = () => { Search
- + + Keyboard shortcut: Command / Control K diff --git a/packages/paste-website/stories/SiteSearch.stories.tsx b/packages/paste-website/stories/SiteSearch.stories.tsx index 8d57c16535..89b89ccd17 100644 --- a/packages/paste-website/stories/SiteSearch.stories.tsx +++ b/packages/paste-website/stories/SiteSearch.stories.tsx @@ -1,10 +1,10 @@ import { type StoryFn } from "@storybook/react"; +import { Box } from "@twilio-paste/box"; import { useUID } from "@twilio-paste/uid-library"; import * as React from "react"; import { SearchEmptyState } from "../src/components/site-search/SearchEmptyState"; import { SearchForm } from "../src/components/site-search/SearchForm"; -import { SearchModal } from "../src/components/site-search/SearchModal"; import { SearchResultsList } from "../src/components/site-search/SearchResults"; import { SearchResultsLoading } from "../src/components/site-search/SearchResultsLoading"; import { type GroupedSearchResults } from "../src/components/site-search/types"; @@ -20,7 +20,8 @@ const MockSearchModal: React.FC<{ const modalHeadingId = useUID(); const inputRef = React.useRef(null); return ( - {}} initialFocusRef={inputRef}> + // we don't need to VRT a modal, we know what they look like else where, we only care about the content + {}} hasResults={Object.keys(results).length > 0} @@ -33,12 +34,25 @@ const MockSearchModal: React.FC<{ {loading && Object.keys(results).length === 0 && } {showEmptyState && Object.keys(results).length === 0 && } - +
); }; export const SiteSearchTrigger: StoryFn = () => { return ; }; +SiteSearchTrigger.parameters = { + a11y: { + config: { + rules: [ + { + id: "color-contrast", + // This uses a symbol as text content and even though it's ignored aXe can't cope with figuring out if it's accessible or not + enabled: false, + }, + ], + }, + }, +}; export const SiteSearchModalInitialState: StoryFn = () => { return ; diff --git a/packages/paste-website/stories/__fixtures__/searchresults.fixture.ts b/packages/paste-website/stories/__fixtures__/searchresults.fixture.ts index 04e95221d9..dd4acb82b9 100644 --- a/packages/paste-website/stories/__fixtures__/searchresults.fixture.ts +++ b/packages/paste-website/stories/__fixtures__/searchresults.fixture.ts @@ -1,3 +1,5 @@ +import type { GroupedSearchResults } from "../../src/components/site-search/types"; + export const mockResults = { "src/pages/components/in-page-navigation/index": [ { @@ -150,4 +152,4 @@ export const mockResults = { slug: "discussion-5372221", }, ], -}; +} as GroupedSearchResults; From ae628e87f692429e628e587936291c8d1ef81fd8 Mon Sep 17 00:00:00 2001 From: Si Taggart Date: Tue, 17 Oct 2023 11:39:20 -0700 Subject: [PATCH 9/9] chore: lint --- .../components/site-wrapper/site-header/SiteHeaderSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/paste-website/src/components/site-wrapper/site-header/SiteHeaderSearch.tsx b/packages/paste-website/src/components/site-wrapper/site-header/SiteHeaderSearch.tsx index edbe7aa374..a9022fba50 100644 --- a/packages/paste-website/src/components/site-wrapper/site-header/SiteHeaderSearch.tsx +++ b/packages/paste-website/src/components/site-wrapper/site-header/SiteHeaderSearch.tsx @@ -2,11 +2,11 @@ import { Box } from "@twilio-paste/box"; import { Button } from "@twilio-paste/button"; import { SearchIcon } from "@twilio-paste/icons/esm/SearchIcon"; import { InlineCode } from "@twilio-paste/inline-code"; +import { ScreenReaderOnly } from "@twilio-paste/screen-reader-only"; import { Text } from "@twilio-paste/text"; import * as React from "react"; import { useHotkeys } from "react-hotkeys-hook"; -import { ScreenReaderOnly } from "@twilio-paste/screen-reader-only"; import { SiteSearch } from "../../site-search"; const SiteHeaderSearch: React.FC = () => {