Skip to content

Commit

Permalink
docs(headless SSR): add tabs to ssr samples (#4303)
Browse files Browse the repository at this point in the history
  • Loading branch information
fpbrault authored Sep 19, 2024
1 parent a2f167b commit 70c0450
Show file tree
Hide file tree
Showing 15 changed files with 269 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ResultList from '@/common/components/react/result-list';
import SearchBox from '@/common/components/react/search-box';
import {SearchPageProvider} from '@/common/components/react/search-page';
import SearchParameterManager from '@/common/components/react/search-parameter-manager';
import TabManager from '@/common/components/react/tab-manager';
import {
fetchStaticState,
setNavigatorContextProvider,
Expand Down Expand Up @@ -62,6 +63,7 @@ export default async function Search(url: {
>
<SearchParameterManager />
<SearchBox />
<TabManager />
<ResultList />
<AuthorFacet />
</SearchPageProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
defineSearchBox,
defineContext,
defineSearchParameterManager,
defineTabManager,
defineTab,
} from '@coveo/headless/ssr';

export const config = {
Expand All @@ -23,7 +25,27 @@ export const config = {
context: defineContext(),
searchBox: defineSearchBox(),
resultList: defineResultList(),
authorFacet: defineFacet({options: {facetId: 'author-1', field: 'author'}}),
tabManager: defineTabManager(),
tabAll: defineTab({
options: {id: 'all', expression: ''},
initialState: {isActive: true},
}),
tabCountries: defineTab({
options: {
id: 'countries',
expression: '@source="Coveo Sample - Atlas"',
},
}),
tabVideos: defineTab({
options: {id: 'videos', expression: '@filetype=YouTubeVideo'},
}),
authorFacet: defineFacet({
options: {
facetId: 'author-1',
field: 'author',
tabs: {included: ['all', 'videos']},
},
}),
searchParameterManager: defineSearchParameterManager(),
},
} satisfies SearchEngineDefinitionOptions<
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {TabManager as TabManagerController} from '@coveo/headless/ssr';

interface TabManagerCommonProps {
controller: Omit<TabManagerController, 'state' | 'subscribe'> | undefined;
value: string;
children: React.ReactNode;
}

export default function TabManagerCommon({
controller,
value,
children,
}: TabManagerCommonProps) {
return <div role="tablist">{children}</div>;
}
32 changes: 32 additions & 0 deletions packages/samples/headless-ssr/common/components/common/tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {Tab, TabManager, TabState} from '@coveo/headless/ssr';

Check warning on line 1 in packages/samples/headless-ssr/common/components/common/tab.tsx

View workflow job for this annotation

GitHub Actions / Check with linter

'TabManager' is defined but never used

interface TabCommonProps {
state: TabState;
methods: Omit<Tab, 'state' | 'subscribe'> | undefined;
activeTab: string;
tabName: string;
tabLabel: string;
}

export default function TabCommon({
state,
methods,
activeTab,
tabName,
tabLabel,
}: TabCommonProps) {
function handleClickTab() {
if (activeTab !== tabName) methods?.select();
}

return (
<button
role="tab"
aria-selected={state.isActive}
key={tabName}
onClick={() => handleClickTab()}
>
{state.isActive ? <strong>{tabLabel}</strong> : tabLabel}
</button>
);
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import {FacetState, Facet as FacetController} from '@coveo/headless/ssr';
import {
FacetState,
Facet as FacetController,
TabManager,
} from '@coveo/headless/ssr';
import {useEffect, useState, FunctionComponent} from 'react';
import FacetCommon from '../common/facet';

interface FacetProps {
title: string;
staticState: FacetState;
tabManager?: TabManager;
controller?: FacetController;
}

export const Facet: FunctionComponent<FacetProps> = ({
title,
staticState,
tabManager,
controller,
}) => {
const [state, setState] = useState(staticState);
Expand All @@ -20,6 +26,10 @@ export const Facet: FunctionComponent<FacetProps> = ({
[controller]
);

if (!state.enabled) {
return;
}

return (
<FacetCommon
title={title}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {HydrationMetadata} from '../common/hydration-metadata';
import {Facet} from './facet';
import {ResultList} from './result-list';
import {SearchBox} from './search-box';
import {Tab} from './tab';
import {TabManager} from './tabs-manager';

export default function SearchPage({
staticState,
Expand Down Expand Up @@ -59,10 +61,37 @@ export default function SearchPage({
staticState={staticState.controllers.searchBox.state}
controller={hydratedState?.controllers.searchBox}
/>
<TabManager
staticState={staticState.controllers.tabManager.state}
controller={hydratedState?.controllers.tabManager}
>
<Tab
staticState={staticState.controllers.tabAll.state}
controller={hydratedState?.controllers.tabAll}
tabManager={hydratedState?.controllers.tabManager}
tabName={'all'}
tabLabel={'All'}
></Tab>
<Tab
staticState={staticState.controllers.tabCountries.state}
controller={hydratedState?.controllers.tabCountries}
tabManager={hydratedState?.controllers.tabManager}
tabName={'countries'}
tabLabel={'Countries'}
></Tab>
<Tab
staticState={staticState.controllers.tabVideos.state}
controller={hydratedState?.controllers.tabVideos}
tabManager={hydratedState?.controllers.tabManager}
tabName={'videos'}
tabLabel={'Videos'}
></Tab>
</TabManager>
<Facet
title="Author"
staticState={staticState.controllers.authorFacet.state}
controller={hydratedState?.controllers.authorFacet}
tabManager={hydratedState?.controllers.tabManager}
/>
<ResultList
staticState={staticState.controllers.resultList.state}
Expand Down
36 changes: 36 additions & 0 deletions packages/samples/headless-ssr/common/components/generic/tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {Tab as TabController, TabManager, TabState} from '@coveo/headless/ssr';
import {useEffect, useState, FunctionComponent} from 'react';
import TabCommon from '../common/tab';

interface TabProps {
staticState: TabState;
controller?: TabController;
tabManager?: TabManager;
tabName: string;
tabLabel: string;
}

export const Tab: FunctionComponent<TabProps> = ({
staticState,
controller,
tabManager,
tabName,
tabLabel,
}) => {
const [state, setState] = useState(staticState);

useEffect(
() => controller?.subscribe(() => setState({...controller.state})),
[controller]
);

return (
<TabCommon
state={state}
methods={controller}
activeTab={tabManager?.state.activeTab ?? ''}
tabName={tabName}
tabLabel={tabLabel}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
TabManagerState,
TabManager as TabManagerController,
} from '@coveo/headless-react/ssr';
import {useEffect, useState, FunctionComponent} from 'react';
import TabManagerCommon from '../common/tab-manager';

interface TabManagerProps {
staticState: TabManagerState;
controller?: TabManagerController;
children: React.ReactNode;
}

export const TabManager: FunctionComponent<TabManagerProps> = ({
staticState,
controller,
children,
}: TabManagerProps) => {
const [state, setState] = useState(staticState);

useEffect(
() => controller?.subscribe?.(() => setState({...controller.state})),
[controller]
);

return (
<TabManagerCommon
controller={controller}
value={state.activeTab}
children={children}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import FacetCommon from '../common/facet';
export const AuthorFacet = () => {
const {state, methods} = useAuthorFacet();

if (!state.enabled) {
return;
}

return (
<FacetCommon
title="Author"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use client';

import {useTabManager} from '../../lib/react/engine';
import TabManagerCommon from '../common/tab-manager';
import Tab from './tab';

export default function TabManager() {
const {state, methods} = useTabManager();

return (
<TabManagerCommon controller={methods} value={state.activeTab}>
<Tab tabName={'all'} tabLabel="All"></Tab>
<Tab tabName={'countries'} tabLabel="Countries"></Tab>
<Tab tabName={'videos'} tabLabel="Videos"></Tab>
</TabManagerCommon>
);
}
44 changes: 44 additions & 0 deletions packages/samples/headless-ssr/common/components/react/tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';

import {TabManager} from '@coveo/headless-react/ssr';

Check warning on line 3 in packages/samples/headless-ssr/common/components/react/tab.tsx

View workflow job for this annotation

GitHub Actions / Check with linter

'TabManager' is defined but never used
import {
useTabAll,
useTabCountries,
useTabManager,
useTabVideos,
} from '../../lib/react/engine';
import TabCommon from '../common/tab';

export default function Tab({
tabName,
tabLabel,
}: {
tabName: string;
tabLabel: string;
}) {
const tabManager = useTabManager();

let controller;

if (tabName === 'all') {
controller = useTabAll;
} else if (tabName === 'countries') {
controller = useTabCountries;
} else if (tabName === 'videos') {
controller = useTabVideos;
} else {
throw new Error(`Unknown tab: ${tabName}`);
}

const {state, methods} = controller();

return (
<TabCommon
state={state}
methods={methods}
activeTab={tabManager.state.activeTab}
tabName={tabName}
tabLabel={tabLabel}
/>
);
}
4 changes: 4 additions & 0 deletions packages/samples/headless-ssr/common/lib/react/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export const {
export const {
useResultList,
useSearchBox,
useTabManager,
useTabAll,
useTabCountries,
useTabVideos,
useAuthorFacet,
useSearchParameterManager,
} = engineDefinition.controllers;
6 changes: 3 additions & 3 deletions packages/samples/headless-ssr/cypress/e2e/smoke.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ const msgSelector = '#hydrated-msg';
const timestampSelector = '#timestamp';
const resultListSelector = '.result-list li';
const searchBoxSelector = '.search-box input';
const routes = ['generic', 'react'] as const;
const routes = ['generic?tab=all', 'react?tab=all'] as const;

const isPageDev =
process.env.NODE_ENV === 'development' &&
Cypress.env('NEXTJS_ROUTER') === 'pages';

// Note: Thresholds might need to be adjusted as the page tested changes (e.g. more components are added etc)
const vitals: Record<(typeof routes)[number], Cypress.ReportWebVitalsConfig> = {
generic: {
'generic?tab=all': {
thresholds: {
fcp: isPageDev ? 2000 : 200,
lcp: isPageDev ? 2000 : 200,
Expand All @@ -30,7 +30,7 @@ const vitals: Record<(typeof routes)[number], Cypress.ReportWebVitalsConfig> = {
inp: 400,
},
},
react: {
'react?tab=all': {
thresholds: {
fcp: isPageDev ? 2000 : 400,
lcp: isPageDev ? 2000 : 400,
Expand Down
Loading

0 comments on commit 70c0450

Please sign in to comment.