1
0
Fork 0

feat: add theme prop (#2651)

Co-authored-by: Dylan Tientcheu <dylantientcheu@gmail.com>
This commit is contained in:
NiteshSingh17 2025-07-08 23:35:25 +05:30 committed by GitHub
parent 29fde53320
commit 85568ce095
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 76 additions and 6 deletions

View file

@ -1,12 +1,13 @@
import type { AutocompleteState, AutocompleteOptions } from '@algolia/autocomplete-core';
import type { AutocompleteOptions, AutocompleteState } from '@algolia/autocomplete-core';
import type { LiteClient, SearchParamsObject } from 'algoliasearch/lite';
import React, { type JSX } from 'react';
import { createPortal } from 'react-dom';
import { DocSearchButton } from './DocSearchButton';
import { DocSearchModal } from './DocSearchModal';
import type { DocSearchHit, InternalDocSearchHit, StoredDocSearchHit } from './types';
import type { DocSearchHit, DocSearchTheme, InternalDocSearchHit, StoredDocSearchHit } from './types';
import { useDocSearchKeyboardEvents } from './useDocSearchKeyboardEvents';
import { useTheme } from './useTheme';
import type { ButtonTranslations, ModalTranslations } from '.';
@ -26,6 +27,7 @@ export interface DocSearchProps {
appId: string;
apiKey: string;
indexName: string;
theme?: DocSearchTheme;
placeholder?: string;
searchParameters?: SearchParamsObject;
maxResultsPerGroup?: number;
@ -41,7 +43,7 @@ export interface DocSearchProps {
insights?: AutocompleteOptions<InternalDocSearchHit>['insights'];
}
export function DocSearch(props: DocSearchProps): JSX.Element {
export function DocSearch({ ...props }: DocSearchProps): JSX.Element {
const searchButtonRef = React.useRef<HTMLButtonElement>(null);
const [isOpen, setIsOpen] = React.useState(false);
const [initialQuery, setInitialQuery] = React.useState<string | undefined>(props?.initialQuery || undefined);
@ -70,6 +72,7 @@ export function DocSearch(props: DocSearchProps): JSX.Element {
onInput,
searchButtonRef,
});
useTheme({ theme: props.theme });
return (
<>

View file

@ -1,7 +1,9 @@
import React, { type JSX, useEffect, useState } from 'react';
import React, { useEffect, useState, type JSX } from 'react';
import { ControlKeyIcon } from './icons/ControlKeyIcon';
import { SearchIcon } from './icons/SearchIcon';
import type { DocSearchTheme } from './types';
import { useTheme } from './useTheme';
export type ButtonTranslations = Partial<{
buttonText: string;
@ -9,6 +11,7 @@ export type ButtonTranslations = Partial<{
}>;
export type DocSearchButtonProps = React.ComponentProps<'button'> & {
theme?: DocSearchTheme;
translations?: ButtonTranslations;
};
@ -24,7 +27,7 @@ export const DocSearchButton = React.forwardRef<HTMLButtonElement, DocSearchButt
const { buttonText = 'Search', buttonAriaLabel = 'Search' } = translations;
const [key, setKey] = useState<typeof ACTION_KEY_APPLE | typeof ACTION_KEY_DEFAULT | null>(null);
useTheme({ theme: props.theme });
useEffect(() => {
if (typeof navigator !== 'undefined') {
isAppleDevice() ? setKey(ACTION_KEY_APPLE) : setKey(ACTION_KEY_DEFAULT);

View file

@ -14,9 +14,10 @@ import { SearchBox } from './SearchBox';
import { createStoredSearches } from './stored-searches';
import type { DocSearchHit, DocSearchState, InternalDocSearchHit, StoredDocSearchHit } from './types';
import { useSearchClient } from './useSearchClient';
import { useTheme } from './useTheme';
import { useTouchEvents } from './useTouchEvents';
import { useTrapFocus } from './useTrapFocus';
import { groupBy, identity, noop, removeHighlightTags, isModifierEvent } from './utils';
import { groupBy, identity, isModifierEvent, noop, removeHighlightTags } from './utils';
export type ModalTranslations = Partial<{
searchBox: SearchBoxTranslations;
@ -37,6 +38,7 @@ export function DocSearchModal({
placeholder = 'Search docs',
searchParameters,
maxResultsPerGroup,
theme,
onClose = noop,
transformItems = identity,
hitComponent = Hit,
@ -335,6 +337,7 @@ export function DocSearchModal({
inputElement: inputRef.current,
});
useTrapFocus({ container: containerRef.current });
useTheme({ theme });
React.useEffect(() => {
document.body.classList.add('DocSearch--active');

View file

@ -261,4 +261,16 @@ describe('api', () => {
expect(link?.getAttribute('href')).toBe('https://github.com/algolia/docsearch/issues/new?title=q');
});
});
describe('Theme', () => {
const html = document.documentElement;
it('light theme', () => {
render(<DocSearch theme="light" />);
expect(html.getAttribute('data-theme')).toBe('light');
});
it('dark theme', () => {
render(<DocSearch theme="dark" />);
expect(html.getAttribute('data-theme')).toBe('dark');
});
});
});

View file

@ -0,0 +1 @@
export type DocSearchTheme = 'dark' | 'light';

View file

@ -1,4 +1,5 @@
export * from './DocSearchHit';
export * from './DocSearchState';
export * from './DocSearchTheme';
export * from './InternalDocSearchHit';
export * from './StoredDocSearchHit';

View file

@ -0,0 +1,23 @@
import { useEffect } from 'react';
import type { DocSearchTheme } from './types';
export interface UseThemeProps {
theme?: DocSearchTheme;
}
export const useTheme = ({ theme }: UseThemeProps): void => {
useEffect(() => {
if (theme) {
const previousTheme = document.documentElement.dataset.theme;
if (theme !== previousTheme) {
document.documentElement.dataset.theme = theme;
return (): void => {
if (previousTheme === undefined) delete document.documentElement.dataset.theme;
else document.documentElement.dataset.theme = previousTheme;
};
}
}
return undefined;
}, [theme]);
};

View file

@ -349,6 +349,30 @@ docsearch({
</Tabs>
## `theme`
> `type: DocSearchTheme` | `default: "light"` | **optional**
Theme for DocSearch. Can be either `light` or `dark`.
<Tabs
groupId="language"
defaultValue="js"
values={[
{ label: 'JavaScript', value: 'js', }
]
}>
<TabItem value="js">
```js
docsearch({
// ...
theme: 'light',
});
```
</TabItem>
</Tabs>
[1]: https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete/
[2]: https://github.com/algolia/docsearch/
[3]: https://github.com/algolia/docsearch/tree/master