Skip to content

Commit

Permalink
feat: implement range date filter as standalone component
Browse files Browse the repository at this point in the history
  • Loading branch information
gabriele-ct committed May 24, 2024
1 parent 2f83a52 commit 572aad4
Show file tree
Hide file tree
Showing 5 changed files with 656 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/gatsby-theme-docs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { default as LayoutSidebar } from './src/layouts/internals/layout-sidebar
export { default as LayoutHeader } from './src/layouts/internals/layout-header';
export { default as LayoutPage } from './src/layouts/internals/layout-page';
export { default as Footer } from './src/layouts/internals/footer';
export { default as LayoutReleaseNotesSearch } from './src/layouts/release-notes-search';
export {
BetaTag,
ErrorBoundary,
Expand Down
124 changes: 124 additions & 0 deletions packages/gatsby-theme-docs/src/layouts/release-notes-search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import { LordIcon, Markdown } from '@commercetools-docs/ui-kit';
import SpacingsStack from '@commercetools-uikit/spacings-stack';
import { useInView } from 'react-intersection-observer';
import useLayoutState from '../hooks/use-layout-state';
import { useSiteData } from '../hooks/use-site-data';
import {
PlanTag,
BetaTag,
ContentPagination,
GlobalNotification,
} from '../components';
import PlaceholderPageHeaderSide from '../overrides/page-header-side';
import PlaceholderPageHeaderSideBannerArea from '../overrides/page-header-banner-area';
import LayoutApplication from './internals/layout-application';
import LayoutHeader from './internals/layout-header';
import LayoutSidebar from './internals/layout-sidebar';
import LayoutMain from './internals/layout-main';
import LayoutFooter from './internals/layout-footer';
import LayoutPageWrapper from './internals/layout-page-wrapper';
import LayoutPage from './internals/layout-page';
import LayoutGlobalNotification from './internals/layout-global-notification';
import LayoutPageHeader from './internals/layout-page-header';
import LayoutPageHeaderSide from '../overrides/layout-page-header-side';
import LayoutPageNavigation from './internals/layout-page-navigation';
import LayoutPageContent from './internals/layout-page-content';
import PageContentInset from './internals/page-content-inset';
import PageReadTime from './internals/page-read-time-estimation';
import CourseCompleteModal from '../modules/self-learning/components/course-complete-modal';
import { useCourseInfoByPageSlugs } from '../modules/self-learning/hooks/use-course-pages';
import styled from '@emotion/styled';
import useAiAssistant from '../modules/ai-assistant/hooks/use-ai-assistant';

const PlansWrapper = styled.div`
& > span {
margin-right: 10px;
}
`;

const LayoutReleaseNotesSearch = (props) => {
const { ref, inView, entry } = useInView();
const isSearchBoxInView = !Boolean(entry) || inView;
const layoutState = useLayoutState();
const siteData = useSiteData();
const excludeFromSearchIndex = false;
useAiAssistant();
return (
<LayoutApplication
globalNotification={siteData.siteMetadata.globalNotification}
>
<LayoutSidebar
{...layoutState.sidebar}
{...layoutState.searchDialog}
{...layoutState.topMenu}
siteTitle={siteData.siteMetadata.title}
isGlobalBeta={siteData.siteMetadata.beta}
hasReleaseNotes={false}
/>
<LayoutMain
{...layoutState.topMenu}
preventScroll={
layoutState.topMenu.isTopMenuOpen ||
layoutState.sidebar.isSidebarMenuOpen
}
>
<LayoutHeader
{...layoutState.searchDialog}
{...layoutState.topMenu}
ref={ref}
siteTitle={siteData.siteMetadata.title}
excludeFromSearchIndex={excludeFromSearchIndex}
allowWideContentLayout={false}
/>
<LayoutPageWrapper>
<LayoutPage allowWideContentLayout={false}>
<LayoutGlobalNotification>
{siteData.siteMetadata.globalNotification.active && (
<GlobalNotification
type={
siteData.siteMetadata.globalNotification.notificationType
}
>
{siteData.siteMetadata.globalNotification.content}
</GlobalNotification>
)}
</LayoutGlobalNotification>
<LayoutPageHeader>
<Markdown.H1>Relase notes</Markdown.H1>
</LayoutPageHeader>
<LayoutPageHeaderSide>
<SpacingsStack scale="m">
<PlaceholderPageHeaderSide />
<PlaceholderPageHeaderSideBannerArea />
</SpacingsStack>
</LayoutPageHeaderSide>
<LayoutPageContent>
<PageContentInset id="body-content" showRightBorder>
{props.children}
</PageContentInset>
</LayoutPageContent>
{/* <LayoutPageNavigation
{...layoutState.searchDialog}
isSearchBoxInView={isSearchBoxInView}
excludeFromSearchIndex={excludeFromSearchIndex}
pageTitle={props.pageContext.shortTitle || props.pageData.title}
tableOfContents={props.pageData.tableOfContents}
navLevels={props.pageData.navLevels}
beta={isBeta}
planTags={planTags}
/> */}
</LayoutPage>
</LayoutPageWrapper>
<LayoutFooter />
</LayoutMain>
</LayoutApplication>
);
};
LayoutReleaseNotesSearch.displayName = 'LayoutReleaseNotesSearch';
LayoutReleaseNotesSearch.propTypes = {
children: PropTypes.node.isRequired,
};

export default LayoutReleaseNotesSearch;
6 changes: 5 additions & 1 deletion websites/docs-smoke-test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@
"@commercetools-docs/gatsby-theme-constants": "24.0.3",
"@commercetools-docs/gatsby-theme-docs": "24.1.1",
"@commercetools-docs/ui-kit": "24.0.3",
"@commercetools-uikit/date-range-field": "18.4.0",
"@commercetools-uikit/icons": "18.4.0",
"@commercetools-uikit/radio-input": "18.4.0",
"algoliasearch": "^4.23.3",
"gatsby": "5.12.12",
"gatsby-cli": "5.12.4",
"instantsearch.css": "^8.2.0",
"process-top": "^1.2.0",
"react": "18.2.0",
"react-dom": "18.2.0"
"react-dom": "18.2.0",
"react-instantsearch": "^7.9.0"
},
"devDependencies": {
"@svgr/cli": "8.1.0"
Expand Down
96 changes: 96 additions & 0 deletions websites/docs-smoke-test/src/pages/release-notes-search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { ThemeProvider } from '@commercetools-docs/gatsby-theme-docs';
import LayoutReleaseNotesSearch from '@commercetools-docs/gatsby-theme-docs/src/layouts/release-notes-search';
import algoliasearch from 'algoliasearch/lite';
import {
Hits,
InstantSearch,
RefinementList,
useRange,
} from 'react-instantsearch';
import 'instantsearch.css/themes/satellite.css';
import { IntlProvider } from 'react-intl';
import DateRangeField from '@commercetools-uikit/date-range-field';
import { useEffect, useState } from 'react';
import PropTypes from 'prop-types';

const convertDateToTimestamp = (dateString) => {
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateString || !dateString.match(dateRegex)) {
return null;
}
const date = new Date(dateString);
const timestamp = Math.floor(date.getTime() / 1000);
return timestamp;
};

const DateRangeInput = (props) => {
// Two important concept to keep in mind:
// - `range` is the maximum and minimum selectable values, they don't make much sense in the context of a date picker
// - `start` (stupid name) is the actual selected range, it's an array of two values. It must always be defined, meaning that
// when date picker is cleared the values must be set to undefined which automatically sets the start to the value of `range`
const { canRefine, refine } = useRange(props);
const [value, setValue] = useState([]);

const handleDateChange = (event) => {
const changeValue = event.target.value;
if (!changeValue || changeValue.length !== 2) {
setValue([]);
}
setValue(changeValue);
};

useEffect(() => {
if (value.length !== 2) {
refine(undefined);
}
refine([
convertDateToTimestamp(value[0]),
convertDateToTimestamp(value[1]),
]);
}, [value, refine]);

return (
<DateRangeField
horizontalConstraint={7}
title="Release Date"
value={value}
onChange={handleDateChange}
isDisabled={!canRefine}
/>
);
};

const searchClient = algoliasearch();
// REDACTED: App ID
// REDACTED: API key

const ReleaseNotesSearch = () => {
return (
<IntlProvider locale="en">
<ThemeProvider>
<LayoutReleaseNotesSearch>
<InstantSearch searchClient={searchClient} indexName="docs-releases">
<RefinementList attribute="product" />
<DateRangeInput
attribute="dateTimestamp"
min={0} // min and max are required as they define the selected range and it must always be defined.
max={2537011284}
/>
<Hits hitComponent={Hit}></Hits>
</InstantSearch>
</LayoutReleaseNotesSearch>
</ThemeProvider>
</IntlProvider>
);
};
const Hit = ({ hit }) => {
return <p>{hit.date}</p>;
};

Hit.propTypes = {
hit: PropTypes.shape({
date: PropTypes.string.isRequired,
}).isRequired,
};

export default ReleaseNotesSearch;
Loading

0 comments on commit 572aad4

Please sign in to comment.