Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial code to add the search functionnality onto the a small viewport #6966

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions packages/docs/src/components/docsearch/doc-search-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const DocSearchModal = component$(
state,
transformItems$ = identity,
aiResultOpen,
isOpen,
disableUserPersonalization = false,
}: DocSearchModalProps) => {
const containerRef = useSignal<Element>();
Expand All @@ -44,7 +45,7 @@ export const DocSearchModal = component$(
const onSelectItem = noSerialize(({ item, event }: any) => {
if (event) {
if (!event.shiftKey && !event.ctrlKey && !event.metaKey) {
state.isOpen = false;
isOpen.value = false;
}
}
}) as any;
Expand Down Expand Up @@ -167,7 +168,8 @@ export const DocSearchModal = component$(
tabIndex={0}
onMouseDown$={(event) => {
if (event.target === containerRef.value) {
state.isOpen = false;
isOpen.value = false;
//state.isOpen.value = false;
}
}}
>
Expand All @@ -178,7 +180,8 @@ export const DocSearchModal = component$(
autoFocus={true}
inputRef={inputRef as any}
onClose$={() => {
state.isOpen = false;
//state.isOpen.value = false;
isOpen.value = false;
}}
/>
</header>
Expand Down
50 changes: 25 additions & 25 deletions packages/docs/src/components/docsearch/doc-search.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { SearchClient } from 'algoliasearch/lite';
import { SearchIcon } from './icons/SearchIcon';
import {
component$,
useStore,
Expand All @@ -9,7 +10,9 @@ import {
type Signal,
$,
sync$,
useTask$,
} from '@builder.io/qwik';
import { Modal } from '@qwik-ui/headless';
import type { DocSearchHit, InternalDocSearchHit } from './types';
import { type ButtonTranslations, DocSearchButton } from './doc-search-button';
import { DocSearchModal, type ModalTranslations } from './doc-search-modal';
Expand All @@ -21,7 +24,6 @@ export type DocSearchTranslations = Partial<{
}>;

export type DocSearchState = {
isOpen: boolean;
query: string;
collections: {
items: InternalDocSearchHit[];
Expand All @@ -34,10 +36,10 @@ export type DocSearchState = {
status: 'idle' | 'loading' | 'stalled' | 'error';
initialQuery?: string;
};

export interface DocSearchProps {
appId: string;
apiKey: string;
isOpen: Signal<boolean>;
indexName: string;
transformItems$?: (items: DocSearchHit[]) => DocSearchHit[];
transformSearchClient?: (searchClient: SearchClient) => SearchClient;
Expand All @@ -55,12 +57,12 @@ export const AiResultOpenContext = createContextId<Signal<boolean>>('aiResultOpe

export const DocSearch = component$((props: DocSearchProps) => {
useStyles$(styles);

const aiResultOpen = useSignal(false);

useContextProvider(AiResultOpenContext, aiResultOpen);

const state = useStore<DocSearchState>({
isOpen: false,
initialQuery: '',
query: '',
collections: [],
Expand All @@ -73,7 +75,6 @@ export const DocSearch = component$((props: DocSearchProps) => {
});

const searchButtonRef = useSignal<Element>();

return (
<div
class={{ docsearch: true, 'ai-result-open': aiResultOpen.value }}
Expand All @@ -88,49 +89,48 @@ export const DocSearch = component$((props: DocSearchProps) => {
// We check that no other DocSearch modal is showing before opening
// another one.
if (!document.body.classList.contains('DocSearch--active')) {
state.isOpen = true;
props.isOpen.value = true;
}
}
if (
(event.key === 'Escape' && state.isOpen) ||
(event.key === 'Escape' && props.isOpen.value) ||
// The `Cmd+K` shortcut both opens and closes the modal.
(event.key === 'k' && (event.metaKey || event.ctrlKey)) ||
// The `/` shortcut opens but doesn't close the modal because it's
// a character.
(!isEditingContent(event) && event.key === '/' && !state.isOpen)
(!isEditingContent(event) && event.key === '/' && !props.isOpen.value)
) {
event.preventDefault();
if (state.isOpen) {
state.isOpen = false;
if (props.isOpen.value) {
props.isOpen.value = false;
} else if (!document.body.classList.contains('DocSearch--active')) {
open();
}
}

if (searchButtonRef && searchButtonRef.value === document.activeElement) {
if (/[a-zA-Z0-9]/.test(String.fromCharCode(event.keyCode))) {
state.isOpen = true;
props.isOpen.value = true;
state.initialQuery = event.key;
}
}
}),
]}
>
<DocSearchButton
ref={searchButtonRef}
onClick$={() => {
state.isOpen = true;
}}
/>
{state.isOpen && (
<DocSearchModal
aiResultOpen={aiResultOpen.value}
indexName={props.indexName}
apiKey={props.apiKey}
appId={props.appId}
state={state}
/>
)}
<Modal.Root bind:show={props.isOpen}>
<Modal.Panel class="w-full h-full">
{props.isOpen.value && (
<DocSearchModal
isOpen={props.isOpen}
aiResultOpen={aiResultOpen.value}
indexName={props.indexName}
apiKey={props.apiKey}
appId={props.appId}
state={state}
/>
)}
</Modal.Panel>
</Modal.Root>
</div>
);
});
2 changes: 1 addition & 1 deletion packages/docs/src/components/docsearch/search-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const SearchBox = component$((props: SearchBoxProps) => {
}
}
if (event.key === 'Escape') {
props.state.isOpen = false;
props.state.isOpen.value = false;
}
if (event.key === 'Enter') {
if (props.state.activeItemId !== null) {
Expand Down
45 changes: 39 additions & 6 deletions packages/docs/src/components/header/header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useLocation } from '@builder.io/qwik-city';
import { component$, useStyles$, useContext, useVisibleTask$ } from '@builder.io/qwik';
import { component$, useStyles$, useContext, useVisibleTask$, useSignal } from '@builder.io/qwik';
import { DocSearch } from '../docsearch/doc-search';
import { CloseIcon } from '../svgs/close-icon';
import { DiscordLogo } from '../svgs/discord-logo';
Expand All @@ -15,9 +15,28 @@ import {
setPreference,
ThemeToggle,
} from '../theme-toggle/theme-toggle';
import { SearchIcon } from '../docsearch/icons/SearchIcon';

export const SearchButton = component$((props: { onClick$: () => void; className?: string }) => {
return (
<button
onClick$={props.onClick$}
class={`flex flex-row justify-between items-center py-0 px-2 my-0 mx-4 h-8 font-medium
text-center normal-case border-2 border-solid cursor-pointer select-none
border-sky-500 bg-neutral-900 text-sky-500 rounded-2xl ${props.className || ''}`}
type="button"
title="Search"
aria-label="Search"
>
<span class="mr-2 md:inline-block sm:hiddenmr-2 hidden sm:visible">Search</span>
<SearchIcon />
</button>
);
});

export const Header = component$(() => {
useStyles$(styles);
const shouldActivate = useSignal(false);
const globalStore = useContext(GlobalStore);
const pathname = useLocation().url.pathname;

Expand All @@ -44,6 +63,14 @@ export const Header = component$(() => {
<QwikLogo width={130} height={44} />
</a>
</div>
<div class="flex items-center">
<SearchButton
onClick$={() => {
shouldActivate.value = true;
}}
className=" absolute right-10 lg:hidden"
/>
</div>
<button
onClick$={() => {
globalStore.headerMenuOpen = !globalStore.headerMenuOpen;
Expand Down Expand Up @@ -99,11 +126,11 @@ export const Header = component$(() => {
<span>Shop</span>
</a>
</li>
<li>
<DocSearch
appId={import.meta.env.VITE_ALGOLIA_APP_ID}
apiKey={import.meta.env.VITE_ALGOLIA_SEARCH_KEY}
indexName={import.meta.env.VITE_ALGOLIA_INDEX}
<li class="hidden lg:flex">
<SearchButton
onClick$={() => {
shouldActivate.value = true;
}}
/>
</li>
<li>
Expand Down Expand Up @@ -135,6 +162,12 @@ export const Header = component$(() => {
</li>
</ul>
</div>
<DocSearch
isOpen={shouldActivate}
appId={import.meta.env.VITE_ALGOLIA_APP_ID}
apiKey={import.meta.env.VITE_ALGOLIA_SEARCH_KEY}
indexName={import.meta.env.VITE_ALGOLIA_INDEX}
/>
</header>
</>
);
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/src/routes/api/qwik/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -1760,7 +1760,7 @@
}
],
"kind": "Function",
"content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\nLoad the prefetch graph for the container.\n\nEach Qwik container needs to include its own prefetch graph.\n\n\n```typescript\nPrefetchGraph: (opts?: {\n base?: string;\n manifestHash?: string;\n manifestURL?: string;\n nonce?: string;\n}) => JSXNode<\"script\">\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nopts\n\n\n</td><td>\n\n{ base?: string; manifestHash?: string; manifestURL?: string; nonce?: string; }\n\n\n</td><td>\n\n_(Optional)_ Options for the loading prefetch graph.\n\n- `base` - Base of the graph. For a default installation this will default to the q:base value `/build/`<!-- -->. But if more than one MFE is installed on the page, then each MFE needs to have its own base. - `manifestHash` - Hash of the manifest file to load. If not provided the hash will be extracted from the container attribute `q:manifest-hash` and assume the default build file `${base}/q-bundle-graph-${manifestHash}.json`<!-- -->. - `manifestURL` - URL of the manifest file to load if non-standard bundle graph location name.\n\n\n</td></tr>\n</tbody></table>\n**Returns:**\n\n[JSXNode](#jsxnode)<!-- -->&lt;\"script\"&gt;",
"content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\nLoad the prefetch graph for the container.\n\nEach Qwik container needs to include its own prefetch graph.\n\n\n```typescript\nPrefetchGraph: (opts?: {\n base?: string;\n manifestHash?: string;\n manifestURL?: string;\n nonce?: string;\n}) => JSXNode<\"script\">\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nopts\n\n\n</td><td>\n\n{ base?: string; manifestHash?: string; manifestURL?: string; nonce?: string; }\n\n\n</td><td>\n\n_(Optional)_ Options for the loading prefetch graph.\n\n- `base` - Base of the graph. For a default installation this will default to the q:base value `/build/`<!-- -->. But if more than one MFE is installed on the page, then each MFE needs to have its own base. - `manifestHash` - Hash of the manifest file to load. If not provided the hash will be extracted from the container attribute `q:manifest-hash` and assume the default build file `${base}/q-bundle-graph-${manifestHash}.json`<!-- -->. - `manifestURL` - URL of the manifest file to load if non-standard bundle graph location name.\n\n\n</td></tr>\n</tbody></table>\n**Returns:**\n\nJSXNode&lt;\"script\"&gt;",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts",
"mdFile": "qwik.prefetchgraph.md"
},
Expand All @@ -1774,7 +1774,7 @@
}
],
"kind": "Function",
"content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\nInstall a service worker which will prefetch the bundles.\n\nThere can only be one service worker per page. Because there can be many separate Qwik Containers on the page each container needs to load its prefetch graph using `PrefetchGraph` component.\n\n\n```typescript\nPrefetchServiceWorker: (opts: {\n base?: string;\n scope?: string;\n path?: string;\n verbose?: boolean;\n fetchBundleGraph?: boolean;\n nonce?: string;\n}) => JSXNode<'script'>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nopts\n\n\n</td><td>\n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n</td><td>\n\nOptions for the prefetch service worker.\n\n- `base` - Base URL for the service worker `import.meta.env.BASE_URL` or `/`<!-- -->. Default is `import.meta.env.BASE_URL` - `scope` - Base URL for when the service-worker will activate. Default is `/` - `path` - Path to the service worker. Default is `qwik-prefetch-service-worker.js` unless you pass a path that starts with a `/` then the base is ignored. Default is `qwik-prefetch-service-worker.js` - `verbose` - Verbose logging for the service worker installation. Default is `false` - `nonce` - Optional nonce value for security purposes, defaults to `undefined`<!-- -->.\n\n\n</td></tr>\n</tbody></table>\n**Returns:**\n\n[JSXNode](#jsxnode)<!-- -->&lt;'script'&gt;",
"content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\nInstall a service worker which will prefetch the bundles.\n\nThere can only be one service worker per page. Because there can be many separate Qwik Containers on the page each container needs to load its prefetch graph using `PrefetchGraph` component.\n\n\n```typescript\nPrefetchServiceWorker: (opts: {\n base?: string;\n scope?: string;\n path?: string;\n verbose?: boolean;\n fetchBundleGraph?: boolean;\n nonce?: string;\n}) => JSXNode<'script'>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nopts\n\n\n</td><td>\n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n</td><td>\n\nOptions for the prefetch service worker.\n\n- `base` - Base URL for the service worker `import.meta.env.BASE_URL` or `/`<!-- -->. Default is `import.meta.env.BASE_URL` - `scope` - Base URL for when the service-worker will activate. Default is `/` - `path` - Path to the service worker. Default is `qwik-prefetch-service-worker.js` unless you pass a path that starts with a `/` then the base is ignored. Default is `qwik-prefetch-service-worker.js` - `verbose` - Verbose logging for the service worker installation. Default is `false` - `nonce` - Optional nonce value for security purposes, defaults to `undefined`<!-- -->.\n\n\n</td></tr>\n</tbody></table>\n**Returns:**\n\nJSXNode&lt;'script'&gt;",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts",
"mdFile": "qwik.prefetchserviceworker.md"
},
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/src/routes/api/qwik/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3625,7 +3625,7 @@ _(Optional)_ Options for the loading prefetch graph.
</tbody></table>
**Returns:**

[JSXNode](#jsxnode)&lt;"script"&gt;
JSXNode&lt;"script"&gt;

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts)

Expand Down Expand Up @@ -3679,7 +3679,7 @@ Options for the prefetch service worker.
</tbody></table>
**Returns:**

[JSXNode](#jsxnode)&lt;'script'&gt;
JSXNode&lt;'script'&gt;

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts)

Expand Down