Skip to content

Commit

Permalink
feat(atomic): add tab support for atomic-smart-snippet (#4221)
Browse files Browse the repository at this point in the history
This PR allows `atomic-smart-snippet` to be visible/hidden based on the
currently active tab in the tab manager.

https://coveord.atlassian.net/browse/CDX-1558

---------

Co-authored-by: GitHub Actions Bot <>
Co-authored-by: Alex Prudhomme <[email protected]>
  • Loading branch information
fpbrault and alexprudhomme authored Aug 8, 2024
1 parent 47d9624 commit 9f228d3
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2111,14 +2111,14 @@ export declare interface AtomicSegmentedFacetScrollable extends Components.Atomi


@ProxyCmp({
inputs: ['collapsedHeight', 'headingLevel', 'maximumHeight', 'snippetCollapsedHeight', 'snippetMaximumHeight', 'snippetStyle']
inputs: ['collapsedHeight', 'headingLevel', 'maximumHeight', 'snippetCollapsedHeight', 'snippetMaximumHeight', 'snippetStyle', 'tabsExcluded', 'tabsIncluded']
})
@Component({
selector: 'atomic-smart-snippet',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['collapsedHeight', 'headingLevel', 'maximumHeight', 'snippetCollapsedHeight', 'snippetMaximumHeight', 'snippetStyle'],
inputs: ['collapsedHeight', 'headingLevel', 'maximumHeight', 'snippetCollapsedHeight', 'snippetMaximumHeight', 'snippetStyle', 'tabsExcluded', 'tabsIncluded'],
})
export class AtomicSmartSnippet {
protected el: HTMLElement;
Expand Down
16 changes: 16 additions & 0 deletions packages/atomic/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3262,6 +3262,14 @@ export namespace Components {
* Sets the style of the snippet. Example: ```ts smartSnippet.snippetStyle = ` b { color: blue; } `; ```
*/
"snippetStyle"?: string;
/**
* The tabs on which this smart snippet 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-smart-snippet tabs-excluded='["tabIDA", "tabIDB"]'></atomic-smart-snippet> ``` If you don't set this property, the smart snippet can be displayed on any tab. Otherwise, the smart snippet won't be displayed on any of the specified tabs.
*/
"tabsExcluded": string[] | string;
/**
* The tabs on which the smart snippet 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-smart-snippet tabs-included='["tabIDA", "tabIDB"]'></atomic-smart-snippet snippet> ``` If you don't set this property, the smart snippet can be displayed on any tab. Otherwise, the smart snippet can only be displayed on the specified tabs.
*/
"tabsIncluded": string[] | string;
}
interface AtomicSmartSnippetAnswer {
"htmlContent": string;
Expand Down Expand Up @@ -8778,6 +8786,14 @@ declare namespace LocalJSX {
* Sets the style of the snippet. Example: ```ts smartSnippet.snippetStyle = ` b { color: blue; } `; ```
*/
"snippetStyle"?: string;
/**
* The tabs on which this smart snippet 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-smart-snippet tabs-excluded='["tabIDA", "tabIDB"]'></atomic-smart-snippet> ``` If you don't set this property, the smart snippet can be displayed on any tab. Otherwise, the smart snippet won't be displayed on any of the specified tabs.
*/
"tabsExcluded"?: string[] | string;
/**
* The tabs on which the smart snippet 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-smart-snippet tabs-included='["tabIDA", "tabIDB"]'></atomic-smart-snippet snippet> ``` If you don't set this property, the smart snippet can be displayed on any tab. Otherwise, the smart snippet can only be displayed on the specified tabs.
*/
"tabsIncluded"?: string[] | string;
}
interface AtomicSmartSnippetAnswer {
"htmlContent": string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ test.describe('when there are results', () => {
await noProducts.load({story: 'with-results'});
});

test('should not be visible', async ({noProducts}) => {
await expect(noProducts.ariaLive()).not.toBeVisible();
test('should have aria live before first query', async ({noProducts}) => {
await expect(noProducts.ariaLive()).toBeVisible();
});

test.describe('after executing a search query that yields no results', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import {
buildSmartSnippet,
buildTabManager,
InlineLink,
SmartSnippet,
SmartSnippetState,
TabManager,
TabManagerState,
} from '@coveo/headless';
import {Component, Prop, State, Element, Listen} from '@stencil/core';
import {Component, Prop, State, Element, Listen, h} from '@stencil/core';
import {
InitializableComponent,
InitializeBindings,
BindStateToController,
} from '../../../../utils/initialization-utils';
import {ArrayProp} from '../../../../utils/props-utils';
import {shouldDisplayOnCurrentTab} from '../../../../utils/tab-utils';
import {randomID} from '../../../../utils/utils';
import {Hidden} from '../../../common/hidden';
import {getAttributesFromLinkSlot} from '../../../common/item-link/attributes-slot';
import {SmartSnippetCommon} from '../../../common/smart-snippets/atomic-smart-snippet/smart-snippet-common';
import {Bindings} from '../../atomic-search-interface/atomic-search-interface';
Expand Down Expand Up @@ -65,6 +71,10 @@ export class AtomicSmartSnippet implements InitializableComponent {
@BindStateToController('smartSnippet')
@State()
public smartSnippetState!: SmartSnippetState;
public tabManager!: TabManager;
@BindStateToController('tabManager')
@State()
public tabManagerState!: TabManagerState;
public error!: Error;
@Element() public host!: HTMLElement;
private id = randomID();
Expand Down Expand Up @@ -100,6 +110,32 @@ export class AtomicSmartSnippet implements InitializableComponent {
*/
@Prop({reflect: true}) snippetStyle?: string;

/**
* The tabs on which the smart snippet 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-smart-snippet tabs-included='["tabIDA", "tabIDB"]'></atomic-smart-snippet snippet>
* ```
* If you don't set this property, the smart snippet can be displayed on any tab. Otherwise, the smart snippet can only be displayed on the specified tabs.
*/
@ArrayProp()
@Prop({reflect: true, mutable: true})
public tabsIncluded: string[] | string = '[]';

/**
* The tabs on which this smart snippet 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-smart-snippet tabs-excluded='["tabIDA", "tabIDB"]'></atomic-smart-snippet>
* ```
* If you don't set this property, the smart snippet can be displayed on any tab. Otherwise, the smart snippet won't be displayed on any of the specified tabs.
*/
@ArrayProp()
@Prop({reflect: true, mutable: true})
public tabsExcluded: string[] | string = '[]';

@State() feedbackSent = false;

@Prop({reflect: true}) public snippetMaximumHeight?: number;
Expand Down Expand Up @@ -150,6 +186,7 @@ export class AtomicSmartSnippet implements InitializableComponent {
this.bindings.store.waitUntilAppLoaded(() =>
this.smartSnippetCommon.hideDuringRender(false)
);
this.tabManager = buildTabManager(this.bindings.engine);
}

private setModalRef(ref: HTMLElement) {
Expand All @@ -173,6 +210,15 @@ export class AtomicSmartSnippet implements InitializableComponent {
}

public render() {
if (
!shouldDisplayOnCurrentTab(
[...this.tabsIncluded],
[...this.tabsExcluded],
this.tabManagerState?.activeTab
)
) {
return <Hidden></Hidden>;
}
return this.smartSnippetCommon.render();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
import {parameters} from '@coveo/atomic/storybookUtils/common/common-meta-parameters';
import {renderComponent} from '@coveo/atomic/storybookUtils/common/render-component';
import {wrapInSearchInterface} from '@coveo/atomic/storybookUtils/search/search-interface-wrapper';
import type {Meta, StoryObj as Story} from '@storybook/web-components';
import {html} from 'lit/static-html.js';
import {wrapInSearchInterface} from '../../../../../storybookUtils/search/search-interface-wrapper';

const {decorator, play} = wrapInSearchInterface();
const {decorator, play} = wrapInSearchInterface({
search: {
preprocessSearchResponseMiddleware: (r) => {
const [result] = r.body.results;
result.title = 'Manage the Coveo In-Product Experiences (IPX)';
result.clickUri = 'https://docs.coveo.com/en/3160';
r.body.questionAnswer = {
documentId: {
contentIdKey: 'permanentid',
contentIdValue: result.raw.permanentid!,
},
question: 'Creating an In-Product Experience (IPX)',
answerSnippet: `
<ol>
<li>On the <a href="https://platform.cloud.coveo.com/admin/#/orgid/search/in-app-widgets/">In-Product Experiences</a> page, click Add <b>In-Product Experience</b>.</li>
<li>In the Configuration tab, fill the Basic settings section.</li>
<li>(Optional) Use the Design and Content access tabs to <a href="https://docs.coveo.com/en/3160/#customizing-an-ipx-interface">customize your <b>IPX</b> interface</a>.</li>
<li>Click Save.</li>
<li>In the Loader snippet panel that appears, you may click Copy to save the loader snippet for your <b>IPX</b> to your clipboard, and then click Save. You can Always retrieve the loader snippet later.</li>
</ol>
<p>
You're now ready to <a href="https://docs.coveo.com/en/3160/build-a-search-ui/manage-coveo-in-product-experiences-ipx#embed-your-ipx-interface-in-sites-and-applications">embed your IPX interface</a>. However, we recommend that you <a href="https://docs.coveo.com/en/3160/build-a-search-ui/manage-coveo-in-product-experiences-ipx#configuring-query-pipelines-for-an-ipx-interface-recommended">configure query pipelines for your IPX interface</a> before.
</p>
`,
relatedQuestions: [],
score: 1337,
};
return r;
},
},
});

const meta: Meta = {
component: 'atomic-tab-manager',
Expand All @@ -31,6 +62,12 @@ export const Default: Story = {
decorators: [
(story) => html`
${story()}
<div style="display: flex;">
<atomic-smart-snippet
tabs-included='["article", "documentation"]'
style="padding: 10px;"
></atomic-smart-snippet>
</div>
<div style="display: flex; justify-content: flex-start;">
<atomic-facet field="objecttype" label="Object type"></atomic-facet>
<atomic-facet
Expand Down
Loading

0 comments on commit 9f228d3

Please sign in to comment.