feat: add theme prop (#2651)
Co-authored-by: Dylan Tientcheu <dylantientcheu@gmail.com>
This commit is contained in:
parent
29fde53320
commit
85568ce095
8 changed files with 76 additions and 6 deletions
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
1
packages/docsearch-react/src/types/DocSearchTheme.ts
Normal file
1
packages/docsearch-react/src/types/DocSearchTheme.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type DocSearchTheme = 'dark' | 'light';
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
export * from './DocSearchHit';
|
||||
export * from './DocSearchState';
|
||||
export * from './DocSearchTheme';
|
||||
export * from './InternalDocSearchHit';
|
||||
export * from './StoredDocSearchHit';
|
||||
|
|
|
|||
23
packages/docsearch-react/src/useTheme.tsx
Normal file
23
packages/docsearch-react/src/useTheme.tsx
Normal 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]);
|
||||
};
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue