Skip to content

Commit

Permalink
feat(atomic): add tab support for atomic-result-list and atomic-folde…
Browse files Browse the repository at this point in the history
…d-result-list (#4281)

This PR allows atomic-result-list and atomic-folded-result-list to be
visible/hidden based on the currently active tab in the tab manager.

https://coveord.atlassian.net/browse/CDX-1556
  • Loading branch information
fpbrault authored Aug 27, 2024
1 parent 10426ec commit 8f484e0
Show file tree
Hide file tree
Showing 8 changed files with 379 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -584,15 +584,15 @@ export declare interface AtomicFieldCondition extends Components.AtomicFieldCond


@ProxyCmp({
inputs: ['childField', 'collectionField', 'density', 'imageSize', 'numberOfFoldedResults', 'parentField'],
inputs: ['childField', 'collectionField', 'density', 'imageSize', 'numberOfFoldedResults', 'parentField', 'tabsExcluded', 'tabsIncluded'],
methods: ['setRenderFunction']
})
@Component({
selector: 'atomic-folded-result-list',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['childField', 'collectionField', 'density', 'imageSize', 'numberOfFoldedResults', 'parentField'],
inputs: ['childField', 'collectionField', 'density', 'imageSize', 'numberOfFoldedResults', 'parentField', 'tabsExcluded', 'tabsIncluded'],
})
export class AtomicFoldedResultList {
protected el: HTMLElement;
Expand Down Expand Up @@ -1521,15 +1521,15 @@ export declare interface AtomicResultLink extends Components.AtomicResultLink {}


@ProxyCmp({
inputs: ['density', 'display', 'gridCellLinkTarget', 'imageSize'],
inputs: ['density', 'display', 'gridCellLinkTarget', 'imageSize', 'tabsExcluded', 'tabsIncluded'],
methods: ['setRenderFunction']
})
@Component({
selector: 'atomic-result-list',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['density', 'display', 'gridCellLinkTarget', 'imageSize'],
inputs: ['density', 'display', 'gridCellLinkTarget', 'imageSize', 'tabsExcluded', 'tabsIncluded'],
})
export class AtomicResultList {
protected el: HTMLElement;
Expand Down
32 changes: 32 additions & 0 deletions packages/atomic/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,14 @@ export namespace Components {
* Sets a rendering function to bypass the standard HTML template mechanism for rendering results. You can use this function while working with web frameworks that don't use plain HTML syntax, e.g., React, Angular or Vue. Do not use this method if you integrate Atomic in a plain HTML deployment.
*/
"setRenderFunction": (resultRenderingFunction: ItemRenderingFunction) => Promise<void>;
/**
* The tabs on which this folded result list must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html <atomic-folded-result-list tabs-excluded='["tabIDA", "tabIDB"]'></atomic-folded-result-list> ``` If you don't set this property, the folded result list can be displayed on any tab. Otherwise, the folded result list won't be displayed on any of the specified tabs.
*/
"tabsExcluded": string[] | string;
/**
* The tabs on which the folded result list can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html <atomic-folded-result-list tabs-included='["tabIDA", "tabIDB"]'></atomic-folded-result-list snippet> ``` If you don't set this property, the folded result list can be displayed on any tab. Otherwise, the folded result list can only be displayed on the specified tabs.
*/
"tabsIncluded": string[] | string;
}
/**
* The `atomic-format-currency` component is used for formatting currencies.
Expand Down Expand Up @@ -2752,6 +2760,14 @@ export namespace Components {
* @param resultRenderingFunction
*/
"setRenderFunction": (resultRenderingFunction: ItemRenderingFunction) => Promise<void>;
/**
* The tabs on which this result list must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html <atomic-result-list tabs-excluded='["tabIDA", "tabIDB"]'></atomic-result-list> ``` If you don't set this property, the result list can be displayed on any tab. Otherwise, the result list won't be displayed on any of the specified tabs.
*/
"tabsExcluded": string[] | string;
/**
* The tabs on which the result list can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html <atomic-result-list tabs-included='["tabIDA", "tabIDB"]'></atomic-result-list snippet> ``` If you don't set this property, the result list can be displayed on any tab. Otherwise, the result list can only be displayed on the specified tabs.
*/
"tabsIncluded": string[] | string;
}
/**
* The `atomic-result-localized-text` component renders a target i18n localized string using the values of a target field.
Expand Down Expand Up @@ -6682,6 +6698,14 @@ declare namespace LocalJSX {
* @defaultValue `foldingparent`
*/
"parentField"?: string;
/**
* The tabs on which this folded result list must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html <atomic-folded-result-list tabs-excluded='["tabIDA", "tabIDB"]'></atomic-folded-result-list> ``` If you don't set this property, the folded result list can be displayed on any tab. Otherwise, the folded result list won't be displayed on any of the specified tabs.
*/
"tabsExcluded"?: string[] | string;
/**
* The tabs on which the folded result list can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html <atomic-folded-result-list tabs-included='["tabIDA", "tabIDB"]'></atomic-folded-result-list snippet> ``` If you don't set this property, the folded result list can be displayed on any tab. Otherwise, the folded result list can only be displayed on the specified tabs.
*/
"tabsIncluded"?: string[] | string;
}
/**
* The `atomic-format-currency` component is used for formatting currencies.
Expand Down Expand Up @@ -8337,6 +8361,14 @@ declare namespace LocalJSX {
* The expected size of the image displayed in the results.
*/
"imageSize"?: ItemDisplayImageSize;
/**
* The tabs on which this result list must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html <atomic-result-list tabs-excluded='["tabIDA", "tabIDB"]'></atomic-result-list> ``` If you don't set this property, the result list can be displayed on any tab. Otherwise, the result list won't be displayed on any of the specified tabs.
*/
"tabsExcluded"?: string[] | string;
/**
* The tabs on which the result list can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html <atomic-result-list tabs-included='["tabIDA", "tabIDB"]'></atomic-result-list snippet> ``` If you don't set this property, the result list can be displayed on any tab. Otherwise, the result list can only be displayed on the specified tabs.
*/
"tabsIncluded"?: string[] | string;
}
/**
* The `atomic-result-localized-text` component renders a target i18n localized string using the values of a target field.
Expand Down
21 changes: 21 additions & 0 deletions packages/atomic/src/components/common/tab-manager/tab-guard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {Fragment, FunctionalComponent, h} from '@stencil/core';
import {shouldDisplayOnCurrentTab} from '../../../utils/tab-utils';
import {Hidden} from '../hidden';

interface TabGuardProps {
tabsIncluded: string | string[];
tabsExcluded: string | string[];
activeTab: string;
}

export const TabGuard: FunctionalComponent<TabGuardProps> = (
{tabsIncluded, tabsExcluded, activeTab},
children
) => {
if (
!shouldDisplayOnCurrentTab([...tabsIncluded], [...tabsExcluded], activeTab)
) {
return <Hidden></Hidden>;
}
return <Fragment>{...children}</Fragment>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
ResultListProps,
FoldedCollection,
buildInteractiveResult,
TabManager,
TabManagerState,
buildTabManager,
} from '@coveo/headless';
import {
Component,
Expand All @@ -17,15 +20,19 @@ import {
Listen,
Method,
h,
Watch,
} from '@stencil/core';
import {FocusTargetController} from '../../../../utils/accessibility-utils';
import {
BindStateToController,
InitializableComponent,
InitializeBindings,
} from '../../../../utils/initialization-utils';
import {ArrayProp} from '../../../../utils/props-utils';
import {shouldDisplayOnCurrentTab} from '../../../../utils/tab-utils';
import {randomID} from '../../../../utils/utils';
import {ResultsPlaceholdersGuard} from '../../../common/atomic-result-placeholder/placeholders';
import {Hidden} from '../../../common/hidden';
import {extractUnfoldedItem} from '../../../common/interface/item';
import {DisplayWrapper} from '../../../common/item-list/display-wrapper';
import {ItemDisplayGuard} from '../../../common/item-list/item-display-guard';
Expand Down Expand Up @@ -74,6 +81,10 @@ export class AtomicFoldedResultList implements InitializableComponent {
@BindStateToController('resultsPerPage')
@State()
public resultsPerPageState!: ResultsPerPageState;
public tabManager!: TabManager;
@BindStateToController('tabManager')
@State()
public tabManagerState!: TabManagerState;
@State() private resultTemplateRegistered = false;
@State() public error!: Error;
@State() private templateHasError = false;
Expand All @@ -86,6 +97,31 @@ export class AtomicFoldedResultList implements InitializableComponent {
* The expected size of the image displayed in the results.
*/
@Prop({reflect: true}) imageSize: ItemDisplayImageSize = 'icon';
/**
* The tabs on which the folded result list can be displayed. This property should not be used at the same time as `tabs-excluded`.
*
* Set this property as a stringified JSON array, e.g.,
* ```html
* <atomic-folded-result-list tabs-included='["tabIDA", "tabIDB"]'></atomic-folded-result-list snippet>
* ```
* If you don't set this property, the folded result list can be displayed on any tab. Otherwise, the folded result list can only be displayed on the specified tabs.
*/
@ArrayProp()
@Prop({reflect: true, mutable: true})
public tabsIncluded: string[] | string = '[]';

/**
* The tabs on which this folded result list must not be displayed. This property should not be used at the same time as `tabs-included`.
*
* Set this property as a stringified JSON array, e.g.,
* ```html
* <atomic-folded-result-list tabs-excluded='["tabIDA", "tabIDB"]'></atomic-folded-result-list>
* ```
* If you don't set this property, the folded result list can be displayed on any tab. Otherwise, the folded result list won't be displayed on any of the specified tabs.
*/
@ArrayProp()
@Prop({reflect: true, mutable: true})
public tabsExcluded: string[] | string = '[]';
/**
* The name of the field on which to do the folding. The folded result list component will use the values of this field to resolve the collections of result items.
*
Expand Down Expand Up @@ -182,6 +218,7 @@ export class AtomicFoldedResultList implements InitializableComponent {
nextNewItemTarget: this.focusTarget,
store: this.bindings.store,
});
this.tabManager = buildTabManager(this.bindings.engine);
}

private initFolding(
Expand All @@ -200,7 +237,26 @@ export class AtomicFoldedResultList implements InitializableComponent {
});
}

@Watch('tabManagerState')
watchTabManagerState(
newValue: {activeTab: string},
oldValue: {activeTab: string}
) {
if (newValue?.activeTab !== oldValue?.activeTab) {
this.bindings.store.unsetLoadingFlag(this.loadingFlag);
}
}

public render() {
if (
!shouldDisplayOnCurrentTab(
[...this.tabsIncluded],
[...this.tabsExcluded],
this.tabManagerState.activeTab
)
) {
return <Hidden></Hidden>;
}
this.itemListCommon.updateBreakpoints();
const listClasses = this.computeListDisplayClasses();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ import {
buildResultsPerPage,
buildInteractiveResult,
Result,
TabManager,
TabManagerState,
buildTabManager,
} from '@coveo/headless';
import {Component, Element, State, Prop, Method, h} from '@stencil/core';
import {Component, Element, State, Prop, Method, h, Watch} from '@stencil/core';
import {FocusTargetController} from '../../../../utils/accessibility-utils';
import {
BindStateToController,
InitializableComponent,
InitializeBindings,
} from '../../../../utils/initialization-utils';
import {ArrayProp} from '../../../../utils/props-utils';
import {randomID} from '../../../../utils/utils';
import {ResultsPlaceholdersGuard} from '../../../common/atomic-result-placeholder/placeholders';
import {DisplayGrid} from '../../../common/item-list/display-grid';
Expand All @@ -38,6 +42,7 @@ import {
ItemTarget,
getItemListDisplayClasses,
} from '../../../common/layout/display-options';
import {TabGuard} from '../../../common/tab-manager/tab-guard';
import {Bindings} from '../../atomic-search-interface/atomic-search-interface';

/**
Expand Down Expand Up @@ -81,7 +86,11 @@ export class AtomicResultList implements InitializableComponent {
@BindStateToController('resultsPerPage')
@State()
private resultsPerPageState!: ResultsPerPageState;
@State() private resultTemplateRegistered = false;
public tabManager!: TabManager;
@BindStateToController('tabManager')
@State()
public tabManagerState!: TabManagerState;
@State() private resultTemplateRegistered = true;
@State() public error!: Error;
@State() private templateHasError = false;

Expand All @@ -108,6 +117,32 @@ export class AtomicResultList implements InitializableComponent {
@Prop({reflect: true, mutable: true})
public imageSize: ItemDisplayImageSize = 'icon';

/**
* The tabs on which the result list can be displayed. This property should not be used at the same time as `tabs-excluded`.
*
* Set this property as a stringified JSON array, e.g.,
* ```html
* <atomic-result-list tabs-included='["tabIDA", "tabIDB"]'></atomic-result-list snippet>
* ```
* If you don't set this property, the result list can be displayed on any tab. Otherwise, the result list can only be displayed on the specified tabs.
*/
@ArrayProp()
@Prop({reflect: true, mutable: true})
public tabsIncluded: string[] | string = '[]';

/**
* The tabs on which this result list must not be displayed. This property should not be used at the same time as `tabs-included`.
*
* Set this property as a stringified JSON array, e.g.,
* ```html
* <atomic-result-list tabs-excluded='["tabIDA", "tabIDB"]'></atomic-result-list>
* ```
* If you don't set this property, the result list can be displayed on any tab. Otherwise, the result list won't be displayed on any of the specified tabs.
*/
@ArrayProp()
@Prop({reflect: true, mutable: true})
public tabsExcluded: string[] | string = '[]';

/**
* Sets a rendering function to bypass the standard HTML template mechanism for rendering results.
* You can use this function while working with web frameworks that don't use plain HTML syntax, e.g., React, Angular or Vue.
Expand Down Expand Up @@ -135,6 +170,7 @@ export class AtomicResultList implements InitializableComponent {
'Folded results will not render any children for the "atomic-result-list". Please use "atomic-folded-result-list" instead.'
);
}
this.tabManager = buildTabManager(this.bindings.engine);
this.resultList = buildResultList(this.bindings.engine);
this.resultsPerPage = buildResultsPerPage(this.bindings.engine);
this.itemTemplateProvider = new ItemTemplateProvider(
Expand Down Expand Up @@ -167,38 +203,54 @@ export class AtomicResultList implements InitializableComponent {
});
}

@Watch('tabManagerState')
watchTabManagerState(
newValue: {activeTab: string},
oldValue: {activeTab: string}
) {
if (newValue?.activeTab !== oldValue?.activeTab) {
this.bindings.store.unsetLoadingFlag(this.loadingFlag);
}
}

public render() {
this.resultListCommon.updateBreakpoints();
const listClasses = this.computeListDisplayClasses();

return (
<ItemListGuard
hasError={this.resultListState.hasError}
hasTemplate={this.resultTemplateRegistered}
templateHasError={this.itemTemplateProvider.hasError}
firstRequestExecuted={this.resultListState.firstSearchExecuted}
hasItems={this.resultListState.hasResults}
<TabGuard
tabsIncluded={this.tabsIncluded}
tabsExcluded={this.tabsExcluded}
activeTab={this.tabManagerState.activeTab}
>
<DisplayWrapper display={this.display} listClasses={listClasses}>
<ResultsPlaceholdersGuard
density={this.density}
display={this.display}
imageSize={this.imageSize}
displayPlaceholders={!this.bindings.store.isAppLoaded()}
numberOfPlaceholders={this.resultsPerPageState.numberOfResults}
></ResultsPlaceholdersGuard>
<ItemDisplayGuard
firstRequestExecuted={this.resultListState.firstSearchExecuted}
hasItems={this.resultListState.hasResults}
>
{this.display === 'table'
? this.renderAsTable()
: this.display === 'grid'
? this.renderAsGrid()
: this.renderAsList()}
</ItemDisplayGuard>
</DisplayWrapper>
</ItemListGuard>
<ItemListGuard
hasError={this.resultListState.hasError}
hasTemplate={this.resultTemplateRegistered}
templateHasError={this.itemTemplateProvider.hasError}
firstRequestExecuted={this.resultListState.firstSearchExecuted}
hasItems={this.resultListState.hasResults}
>
<DisplayWrapper display={this.display} listClasses={listClasses}>
<ResultsPlaceholdersGuard
density={this.density}
display={this.display}
imageSize={this.imageSize}
displayPlaceholders={!this.bindings.store.isAppLoaded()}
numberOfPlaceholders={this.resultsPerPageState.numberOfResults}
></ResultsPlaceholdersGuard>
<ItemDisplayGuard
firstRequestExecuted={this.resultListState.firstSearchExecuted}
hasItems={this.resultListState.hasResults}
>
{this.display === 'table'
? this.renderAsTable()
: this.display === 'grid'
? this.renderAsGrid()
: this.renderAsList()}
</ItemDisplayGuard>
</DisplayWrapper>
</ItemListGuard>
</TabGuard>
);
}

Expand Down
Loading

0 comments on commit 8f484e0

Please sign in to comment.