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

Implementation of filters in custom search modal. #1560

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
21ec770
Add custom component for search modal
vivekjain23 May 15, 2024
7475323
Add styles for custom search modal
vivekjain23 May 15, 2024
3e05305
Add list of filters for custom search based on facet tags
vivekjain23 May 15, 2024
058ce2f
Add context for search
vivekjain23 May 15, 2024
eb4a6cd
Refactor styles for custom search filter dropdown
vivekjain23 May 17, 2024
b4141e8
Refactor custom search modal
vivekjain23 May 17, 2024
6507c3b
Remove unwanted swizzle component
vivekjain23 May 17, 2024
9e87bb4
Add custom search filter page
vivekjain23 May 17, 2024
5e2c99e
Update list of filters for custom search based on facet tags
vivekjain23 May 17, 2024
4f2aa50
Refactor context for search modal and page
vivekjain23 May 17, 2024
85422c1
lint fix
vivekjain23 May 17, 2024
12110e0
Fix custom dropdown filter name
vivekjain23 May 23, 2024
8e853e2
Refractor custom dropdown filter
vivekjain23 May 23, 2024
607b2df
Update styles for custom dropdown filter
vivekjain23 May 23, 2024
b52cbd5
Add filter constants for custom dropdown filter
vivekjain23 May 23, 2024
5cc1abf
Fix footer alignment in search page
vivekjain23 Jun 6, 2024
ccf24d2
Fix linting errors
vivekjain23 Jun 7, 2024
5829d3b
Fix formatting
vivekjain23 Jun 7, 2024
550bf08
Fix styling
vivekjain23 Jun 12, 2024
c0e589e
Fix filter alignment
vivekjain23 Jun 14, 2024
68aeef9
Merge branch 'main' into feature/search-filters
Dr-Electron Jul 2, 2024
6c24de4
Fix filter build issue
vivekjain23 Jul 4, 2024
9ab4943
Add swizzle version and info
vivekjain23 Jul 5, 2024
31482b1
Add swizzle version and info
vivekjain23 Jul 5, 2024
d3510b6
Update filter select logic
vivekjain23 Jul 22, 2024
fbdf425
Remove unwanted console logs
vivekjain23 Jul 22, 2024
1ddf1d7
Remove search filter from search modal
vivekjain23 Jul 30, 2024
492f1c6
Remove unneeded swizzles
Dr-Electron Aug 5, 2024
a6f1379
Revert footer changes
Dr-Electron Aug 5, 2024
fa062f3
Remove unused dep
Dr-Electron Aug 5, 2024
ff8d16d
Revert change
Dr-Electron Aug 5, 2024
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"humanize-duration": "^3.30.0",
"infima": "^0.2.0-alpha.43",
"plugin-image-zoom": "flexanalytics/plugin-image-zoom",
"prop-types": "^15.8.1",
"raw-loader": "^4.0.2",
"react": "18.2.0",
"react-dom": "18.2.0",
Expand Down
18 changes: 10 additions & 8 deletions src/theme/Footer/index.tsx
vivekjain23 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@

import React from 'react';
import Footer from '@theme-original/Footer';
import type FooterType from '@theme/Footer';
import type { WrapperProps } from '@docusaurus/types';
import Social from '@site/src/components/Social';

type Props = WrapperProps<typeof FooterType>;
type FooterProps = {
footerStyleProps?: React.CSSProperties;
};

export default function FooterWrapper(props: Props): JSX.Element {
const FooterWrapper = ({ footerStyleProps }: FooterProps) => {
return (
<>
<Footer {...props} />
<div style={footerStyleProps}>
<Footer />
<Social />
</>
</div>
);
}
};

export default FooterWrapper;
30 changes: 30 additions & 0 deletions src/theme/Layout/Provider/index.js
vivekjain23 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import { composeProviders } from '@docusaurus/theme-common';
import {
ColorModeProvider,
AnnouncementBarProvider,
DocsPreferredVersionContextProvider,
ScrollControllerProvider,
NavbarProvider,
PluginHtmlClassNameProvider,
} from '@docusaurus/theme-common/internal';

const Provider = composeProviders([
ColorModeProvider,
AnnouncementBarProvider,
ScrollControllerProvider,
DocsPreferredVersionContextProvider,
PluginHtmlClassNameProvider,
NavbarProvider,
]);

function LayoutProvider({ children }) {
return <Provider>{children}</Provider>;
}

LayoutProvider.propTypes = {
children: PropTypes.node.isRequired,
};

export default LayoutProvider;
72 changes: 72 additions & 0 deletions src/theme/Layout/index.js
vivekjain23 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* SWIZZLED VERSION: 2.4.3
* REASONS:
* - Ejected to fix the fotter alignment
*/

import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import ErrorBoundary from '@docusaurus/ErrorBoundary';
import {
PageMetadata,
SkipToContentFallbackId,
ThemeClassNames,
} from '@docusaurus/theme-common';
import { useKeyboardNavigation } from '@docusaurus/theme-common/internal';
import SkipToContent from '@theme/SkipToContent';
import AnnouncementBar from '@theme/AnnouncementBar';
import Navbar from '@theme/Navbar';
import Footer from '@theme/Footer';
import LayoutProvider from '@theme/Layout/Provider';
import ErrorPageContent from '@theme/ErrorPageContent';
import styles from './styles.module.css';

export default function Layout(props) {
const {
children,
noFooter,
wrapperClassName,
footerStyleProps,
title,
description,
} = props;

useKeyboardNavigation();

return (
<LayoutProvider>
<PageMetadata title={title} description={description} />

<SkipToContent />

<AnnouncementBar />

<Navbar />

<div
id={SkipToContentFallbackId}
className={clsx(
ThemeClassNames.wrapper.main,
styles.mainWrapper,
wrapperClassName,
)}
>
<ErrorBoundary fallback={(params) => <ErrorPageContent {...params} />}>
{children}
</ErrorBoundary>
</div>

{!noFooter && <Footer footerStyleProps={footerStyleProps} />}
</LayoutProvider>
);
}

Layout.propTypes = {
children: PropTypes.node.isRequired,
noFooter: PropTypes.bool,
wrapperClassName: PropTypes.string,
footerStyleProps: PropTypes.object,
title: PropTypes.string,
description: PropTypes.string,
};
21 changes: 21 additions & 0 deletions src/theme/Layout/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
html,
body {
height: 100%;
}

.mainWrapper {
flex: 1 0 auto;
display: flex;
flex-direction: column;
}

/* Docusaurus-specific utility class */
:global(.docusaurus-mt-lg) {
margin-top: 3rem;
}

:global(#__docusaurus) {
min-height: 100%;
display: flex;
flex-direction: column;
}
17 changes: 17 additions & 0 deletions src/theme/Root.js
vivekjain23 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want this? I think in most cases the search filter state should revert again 🤔

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* SWIZZLED VERSION: 2.4.3
* REASONS:
* - Wrapped the component in context provider inorder to transfer data seamlessly between search bar and search page
*/

/* eslint-disable */
import React from 'react';
import { SearchProvider } from '@site/src/utils/SearchContext';

export default function Root({ children }) {
return (
<SearchProvider>
<div>{children}</div>
</SearchProvider>
);
}
218 changes: 218 additions & 0 deletions src/theme/SearchBar/index.js
vivekjain23 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/**
* SWIZZLED VERSION: 2.4.3
* REASONS:
* - Ejected to add search filters using facets
*/

/* eslint-disable */
import React, {
useCallback,
useMemo,
useRef,
useState,
useContext,
useEffect,
} from 'react';
import { DocSearchButton, useDocSearchKeyboardEvents } from '@docsearch/react';
import Head from '@docusaurus/Head';
import Link from '@docusaurus/Link';
import { useHistory } from '@docusaurus/router';
import {
isRegexpStringMatch,
useSearchLinkCreator,
} from '@docusaurus/theme-common';
import { useSearchResultUrlProcessor } from '@docusaurus/theme-search-algolia/client';
import Translate from '@docusaurus/Translate';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import { createPortal } from 'react-dom';
import translations from '@theme/SearchTranslations';
import { FilterDropdown } from '../SearchPage/FilterDropdown';
import { SearchContext } from '@site/src/utils/SearchContext';
import { allFacets } from '@site/src/utils/searchConstant';

let DocSearchModal = null;
function Hit({ hit, children }) {
return <Link to={hit.url}>{children}</Link>;
}

function ResultsFooter({ state, onClose }) {
const createSearchLink = useSearchLinkCreator();
return (
<Link to={createSearchLink(state.query)} onClick={onClose}>
<Translate
id='theme.SearchBar.seeAll'
values={{ count: state.context.nbHits }}
>
{'See all {count} results'}
</Translate>
</Link>
);
}

function DocSearch({ contextualSearch, externalUrlRegex, ...props }) {
const { selectedFacets, setSelectedFacets } = useContext(SearchContext);
const { siteMetadata } = useDocusaurusContext();
const processSearchResultUrl = useSearchResultUrlProcessor();

const searchParameters = {
...props.searchParameters,
facetFilters: selectedFacets,
};
const history = useHistory();
const searchContainer = useRef(null);
const searchButtonRef = useRef(null);
const filterDropdownMenuRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
const [initialQuery, setInitialQuery] = useState(undefined);

const importDocSearchModalIfNeeded = useCallback(async (event) => {
if (DocSearchModal) {
return Promise.resolve();
}
const [{ DocSearchModal: Modal }] = await Promise.all([
import('@docsearch/react/modal'),
import('@docsearch/react/style'),
import('./styles.css'),
]);
DocSearchModal = Modal;
}, []);

const onOpen = useCallback(() => {
importDocSearchModalIfNeeded().then(() => {
searchContainer.current = document.createElement('div');
document.body.insertBefore(
searchContainer.current,
document.body.firstChild,
);
setIsOpen(true);
});
}, [importDocSearchModalIfNeeded, setIsOpen]);

const onClose = useCallback(() => {
setIsOpen(false);
searchContainer.current?.remove();
}, [setIsOpen]);

const onInput = useCallback(
(event) => {
importDocSearchModalIfNeeded().then(() => {
setIsOpen(true);
setInitialQuery(event.key);
});
},
[importDocSearchModalIfNeeded, setIsOpen, setInitialQuery],
);

const navigator = useRef({
navigate({ itemUrl }) {
if (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
window.location.href = itemUrl;
} else {
history.push(itemUrl);
}
},
}).current;

const transformItems = useRef((items) =>
props.transformItems
? props.transformItems(items)
: items.map((item) => ({
...item,
url: processSearchResultUrl(item.url),
})),
).current;

const resultsFooterComponent = useMemo(
() => (footerProps) => <ResultsFooter {...footerProps} onClose={onClose} />,
[onClose],
);

const transformSearchClient = useCallback(
(searchClient) => {
searchClient.addAlgoliaAgent(
'docusaurus',
siteMetadata.docusaurusVersion,
);
return searchClient;
},
[siteMetadata.docusaurusVersion],
);

useDocSearchKeyboardEvents({
isOpen,
onOpen,
onClose,
onInput,
searchButtonRef,
});

useEffect(() => {
if (!isOpen) return;

const handleClickOutside = (event) => {
const filterDropdownMenu = document.querySelector('.dropdown__menu');
if (filterDropdownMenu && filterDropdownMenu.contains(event.target))
return;
if (
searchContainer.current &&
!searchContainer.current.contains(event.target)
) {
setSelectedFacets(allFacets);
}
};

document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen, onClose, setSelectedFacets]);

return (
<>
<Head>
<link
rel='preconnect'
href={`https://${props.appId}-dsn.algolia.net`}
crossOrigin='anonymous'
/>
</Head>
<DocSearchButton
onTouchStart={importDocSearchModalIfNeeded}
onFocus={importDocSearchModalIfNeeded}
onMouseOver={importDocSearchModalIfNeeded}
onClick={onOpen}
ref={searchButtonRef}
translations={translations.button}
/>
{isOpen &&
DocSearchModal &&
searchContainer.current &&
createPortal(
<React.Fragment>
<DocSearchModal
onClose={onClose}
initialScrollY={window.scrollY}
initialQuery={initialQuery}
navigator={navigator}
transformItems={transformItems}
hitComponent={Hit}
transformSearchClient={transformSearchClient}
{...(props.searchPagePath && {
resultsFooterComponent,
})}
{...props}
searchParameters={searchParameters}
placeholder={translations.placeholder}
translations={translations.modal}
/>
</React.Fragment>,
searchContainer.current,
)}
</>
);
}

export default function SearchBar() {
const { siteConfig } = useDocusaurusContext();
return <DocSearch {...siteConfig.themeConfig.algolia} />;
}
Loading
Loading