Skip to content

Commit

Permalink
feat!: Add Search
Browse files Browse the repository at this point in the history
  • Loading branch information
3y3 committed Sep 24, 2024
1 parent 72cf276 commit 5806618
Show file tree
Hide file tree
Showing 17 changed files with 486 additions and 54 deletions.
2 changes: 1 addition & 1 deletion src/components/App/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function RichNavPage({data, props, controls}: PageProps<WithNavigation>)

const CustomSuggest = useCallback(() => <Suggest />, []);
const CustomControls = useCallback(() => <HeaderControls {...controls} />, [controls]);
const navigation = useNavigation(data, CustomControls, CustomSuggest);
const navigation = useNavigation(data, controls, CustomControls, CustomSuggest);

const CustomPage = useCallback(
() => (
Expand Down
76 changes: 45 additions & 31 deletions src/components/App/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type {NavigationData, PageContent} from '@gravity-ui/page-constructor';
import type {ReactElement} from 'react';
import type {Props as HeaderControlsProps} from '../HeaderControls';
import type {SearchConfig} from '../Search';
import type {RouterConfig} from '../Router';

import React, {useEffect} from 'react';
import React, {useEffect, useMemo} from 'react';
import {ThemeProvider} from '@gravity-ui/uikit';
import {
ConsentPopup,
Expand All @@ -11,11 +13,12 @@ import {
DocLeadingPageData,
DocPageData,
Lang,
Router,
configure,
} from '@diplodoc/components';
import '@diplodoc/transform/dist/js/yfm';

import {SearchProvider} from '../Search';
import {RouterProvider} from '../Router';
import {getDirection, updateRootClassName, updateThemeClassName} from '../../utils';
import {LangProvider} from '../../hooks/useLang';
import '../../interceptors/leading-page-links';
Expand All @@ -38,7 +41,8 @@ export type DocAnalytics = {
export interface AppProps {
lang: Lang;
langs: Lang[];
router: Router;
router: RouterConfig;
search?: SearchConfig;
analytics?: DocAnalytics;
}

Expand All @@ -63,7 +67,7 @@ function hasNavigation(
}

export function App(props: DocInnerProps): ReactElement {
const {data, router, lang, analytics} = props;
const {data, router, lang, search, analytics} = props;

configure({
lang,
Expand All @@ -75,20 +79,26 @@ export function App(props: DocInnerProps): ReactElement {

const {theme, textSize, wideFormat, fullScreen, showMiniToc} = settings;

const page = {
router,

theme,
textSize,
wideFormat,
fullScreen,
showMiniToc,
};
const controls: HeaderControlsProps = {
...settings,
...langs,
mobileView,
};
const page = useMemo(
() => ({
router,

theme,
textSize,
wideFormat,
fullScreen,
showMiniToc,
}),
[router, theme, textSize, wideFormat, fullScreen, showMiniToc],
);
const controls: HeaderControlsProps = useMemo(
() => ({
...settings,
...langs,
mobileView,
}),
[langs, settings, mobileView],
);
const direction = getDirection(lang);

useEffect(() => {
Expand All @@ -100,19 +110,23 @@ export function App(props: DocInnerProps): ReactElement {
<div className="App">
<ThemeProvider theme={theme} direction={direction}>
<LangProvider value={lang}>
{hasNavigation(data) ? (
<RichNavPage data={data} props={page} controls={controls} />
) : (
<LegacyNavPage data={data} props={page} controls={controls} />
)}
{analytics && (
<ConsentPopup
router={router}
gtmId={analytics?.gtm?.id || ''}
consentMode={analytics?.gtm?.mode}
/>
)}
<Runtime />
<RouterProvider value={router}>
<SearchProvider value={search}>
{hasNavigation(data) ? (
<RichNavPage data={data} props={page} controls={controls} />
) : (
<LegacyNavPage data={data} props={page} controls={controls} />
)}
{analytics && (
<ConsentPopup
router={router}
gtmId={analytics?.gtm?.id || ''}
consentMode={analytics?.gtm?.mode}
/>
)}
<Runtime />
</SearchProvider>
</RouterProvider>
</LangProvider>
</ThemeProvider>
</div>
Expand Down
12 changes: 8 additions & 4 deletions src/components/ConstructorPage/useNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ import type {ReactNode} from 'react';
import type {NavigationData} from '@gravity-ui/page-constructor';
import type {DocBasePageData} from '@diplodoc/components';
import type {WithNavigation} from '../App';
import type {Props as HeaderControlsProps} from '../HeaderControls';

import React, {useMemo} from 'react';
import {ControlSizes, CustomNavigation, MobileDropdown} from '@diplodoc/components';

import {HEADER_HEIGHT} from '../../constants';
import {useRouter} from '../';

export const useNavigation = (
data: DocBasePageData<WithNavigation>,
controls: HeaderControlsProps,
CustomControls: () => ReactNode,
CustomSuggest: () => ReactNode,
) => {
const {toc} = data;
const {navigation} = toc;
Expand All @@ -20,7 +24,6 @@ export const useNavigation = (
const withControls = rightItems.some((item: {type: string}) => item.type === 'controls');

const router = useRouter();
const userSettings = useSettings();

const navigationData = useMemo(
() => ({
Expand All @@ -41,9 +44,9 @@ export const useNavigation = (
const mobileControlsData = useMemo(
() => ({
controlSize: ControlSizes.L,
userSettings,
userSettings: controls,
}),
[userSettings],
[controls],
);

const layout = useMemo(
Expand All @@ -67,13 +70,14 @@ export const useNavigation = (
const config = useMemo(
() => ({
custom: {
search: CustomSuggest,
controls: CustomControls,
MobileDropdown: MobileDropdown,
},
layout,
withControls,
}),
[CustomControls, layout, withControls],
[CustomSuggest, CustomControls, layout, withControls],
);

return config;
Expand Down
20 changes: 20 additions & 0 deletions src/components/Router/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type {Router} from '@diplodoc/components';

import {createContext, useContext} from 'react';

export interface RouterConfig extends Router {
depth: number;
}

export const RouterContext = createContext<RouterConfig>({
pathname: '/',
depth: 0,
});

RouterContext.displayName = 'RouterContext';

export const RouterProvider = RouterContext.Provider;

export function useRouter() {
return useContext(RouterContext);
}
5 changes: 5 additions & 0 deletions src/components/Search/Search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export const Search = () => {
return <div>Initial</div>;
};
17 changes: 17 additions & 0 deletions src/components/Search/Suggest.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.Suggest {
margin-right: 20px;
min-width: 200px;
transition: min-width 0.3s;

.dc-root_focused-search & {
min-width: 500px;
}

&__Item {
&__Marker {
background: var(--g-color-base-neutral-medium);
padding: 0 3px 1px;
border-radius: 4px;
}
}
}
41 changes: 41 additions & 0 deletions src/components/Search/Suggest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type {ISearchProvider, SearchSuggestApi} from '@diplodoc/components';

import React, {useCallback, useRef} from 'react';
import {SearchSuggest} from '@diplodoc/components';

import {updateRootClassName} from '../../utils';

import {useProvider} from './useProvider';
import './Suggest.scss';

export function Suggest() {
const provider: ISearchProvider | null = useProvider();
const suggest = useRef<SearchSuggestApi>(null);

const onFocus = useCallback(() => {
updateRootClassName({focusSearch: true});
}, []);

const onBlur = useCallback(() => {
updateRootClassName({focusSearch: false});
setTimeout(() => {
if (suggest.current) {
suggest.current.close();
}
}, 100);
}, []);

if (!provider) {
return null;
}

return (
<SearchSuggest
ref={suggest}
provider={provider}
onFocus={onFocus}
onBlur={onBlur}
classNameContainer={'Suggest'}
/>
);
}
13 changes: 13 additions & 0 deletions src/components/Search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type {SearchConfig, WorkerApi, WorkerConfig} from './types';

import {createContext} from 'react';

export type {SearchConfig, WorkerConfig, WorkerApi};

export const SearchContext = createContext<SearchConfig | null | undefined>(null);

SearchContext.displayName = 'SearchContext';

export const SearchProvider = SearchContext.Provider;

export {Search} from './Search';
86 changes: 86 additions & 0 deletions src/components/Search/provider/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type {ISearchProvider, ISearchResult} from '@diplodoc/components';
import type {SearchConfig, WorkerConfig} from '../types';

export class SearchProvider implements ISearchProvider {
private worker!: Promise<Worker>;

private config: SearchConfig;

constructor(config: SearchConfig) {
this.config = config;
}

init = () => {
this.worker = initWorker({
...this.config,
base: this.base,
mark: 'Suggest__Item__Marker',
});
};

async suggest(query: string) {
return this.request({
type: 'suggest',
query,
}) as Promise<ISearchResult[]>;
}

async search(query: string) {
return this.request({
type: 'search',
query,
}) as Promise<ISearchResult[]>;
}

// Temporary disable link to search page
// TODO: Implement search page
link = () => null;

// link = (query: string) => {
// const params = query ? `?query=${encodeURIComponent(query)}` : '';
//
// return `${this.base}/${this.config.link}${params}`;
// };

private get base() {
return window.location.pathname
.split('/')
.slice(0, -(this.config.depth + 1))
.join('/');
}

private async request(message: object) {
return request(await this.worker, message);
}
}

async function initWorker(config: WorkerConfig) {
const worker = new Worker(new URL('../worker/index.ts', import.meta.url));

await request(worker, {...config, type: 'init'});

return worker;
}

function request(worker: Worker, message: object) {
const channel = new MessageChannel();

return new Promise((resolve, reject) => {
channel.port1.onmessage = (message) => {
if (message.data.error) {
// eslint-disable-next-line no-console
console.error(message.data.error);

reject(message.data.error);
} else {
resolve(message.data.result);
}
};

channel.port1.onmessageerror = (message) => {
reject(message.data.error);
};

worker.postMessage(message, [channel.port2]);
});
}
Loading

0 comments on commit 5806618

Please sign in to comment.