From 049b4201e72b91a49b2ec63ea2577fe3bed6a9e9 Mon Sep 17 00:00:00 2001 From: dmbrooke <38883189+dmbrooke@users.noreply.github.com> Date: Wed, 2 Aug 2023 14:30:18 -0400 Subject: [PATCH] feat(atomic): add skip link to grid results (#3045) * Handle click with JS rather than link https://coveord.atlassian.net/browse/KIT-2597 * Support click target as prop on grid items https://coveord.atlassian.net/browse/KIT-2597 * Add generated files https://coveord.atlassian.net/browse/KIT-2597 * Add basic grid result list E2E tests, leverage noopener https://coveord.atlassian.net/browse/KIT-2597 * Add grid result list quickview tests https://coveord.atlassian.net/browse/KIT-2597 * Add skip link to each result https://coveord.atlassian.net/browse/KIT-2597 * Rename prop, better docs, support cmd + click https://coveord.atlassian.net/browse/KIT-2597 * Change default value to _self https://coveord.atlassian.net/browse/KIT-2597 * Fix result-list-selectors https://coveord.atlassian.net/browse/KIT-2597 * Leverage focus-visible pseudo-class https://coveord.atlassian.net/browse/KIT-2597 * Remove ariaHidden from skip link https://coveord.atlassian.net/browse/KIT-2597 * Add additional props to ResultLink https://coveord.atlassian.net/browse/KIT-2597 --------- Co-authored-by: GitHub Actions Bot <> --- .cspell.json | 10 +- .../src/lib/stencil-generated/components.ts | 8 +- .../result-components/quickview.cypress.ts | 92 ++++++++++++++++++- .../e2e/result-list/result-list-actions.ts | 12 +++ .../e2e/result-list/result-list-selectors.ts | 4 + .../e2e/result-list/result-list.cypress.ts | 14 ++- packages/atomic/src/components.d.ts | 22 ++++- .../atomic/src/components/common/button.tsx | 2 +- .../common/layout/display-options.ts | 1 + .../result-children-common.tsx | 2 +- .../common/result-link/result-link.tsx | 7 ++ .../result-list/grid-display-results.tsx | 19 +++- .../result-list-common-interface.ts | 2 + .../common/result-list/styles/mixins.pcss | 21 +++-- .../atomic-insight-folded-result-list.tsx | 3 + .../atomic-insight-result-list.tsx | 4 +- .../atomic-recs-list/atomic-recs-list.tsx | 8 ++ .../atomic-folded-result-list.tsx | 3 + .../atomic-result-list/atomic-result-list.tsx | 10 ++ .../atomic-quickview/atomic-quickview.tsx | 5 +- 20 files changed, 222 insertions(+), 27 deletions(-) diff --git a/.cspell.json b/.cspell.json index 587641ea493..bb03668cbfd 100644 --- a/.cspell.json +++ b/.cspell.json @@ -4,8 +4,12 @@ "cache": { "useCache": true }, - "ignorePaths": ["CHANGELOG.md"], - "import": ["@cspell/dict-fr-fr/cspell-ext.json"], + "ignorePaths": [ + "CHANGELOG.md" + ], + "import": [ + "@cspell/dict-fr-fr/cspell-ext.json" + ], "words": [ "adjustednumberoflikes", "ARROWUP", @@ -43,10 +47,12 @@ "exchangemdn", "exchangerssfeed", "Expandto", + "genqa", "gmailmessage", "gzipped", "headless", "IIFE", + "inat", "initializable", "inlink", "isloading", diff --git a/packages/atomic-angular/projects/atomic-angular/src/lib/stencil-generated/components.ts b/packages/atomic-angular/projects/atomic-angular/src/lib/stencil-generated/components.ts index bae69a92b6a..5df66062938 100644 --- a/packages/atomic-angular/projects/atomic-angular/src/lib/stencil-generated/components.ts +++ b/packages/atomic-angular/projects/atomic-angular/src/lib/stencil-generated/components.ts @@ -738,14 +738,14 @@ export declare interface AtomicRecsList extends Components.AtomicRecsList {} @ProxyCmp({ defineCustomElementFn: undefined, - inputs: ['density', 'display', 'headingLevel', 'imageSize', 'label', 'numberOfRecommendations', 'numberOfRecommendationsPerPage', 'recommendation'], + inputs: ['density', 'display', 'gridCellLinkTarget', 'headingLevel', 'imageSize', 'label', 'numberOfRecommendations', 'numberOfRecommendationsPerPage', 'recommendation'], methods: ['setRenderFunction', 'previousPage', 'nextPage'] }) @Component({ selector: 'atomic-recs-list', changeDetection: ChangeDetectionStrategy.OnPush, template: '', - inputs: ['density', 'display', 'headingLevel', 'imageSize', 'label', 'numberOfRecommendations', 'numberOfRecommendationsPerPage', 'recommendation'] + inputs: ['density', 'display', 'gridCellLinkTarget', 'headingLevel', 'imageSize', 'label', 'numberOfRecommendations', 'numberOfRecommendationsPerPage', 'recommendation'] }) export class AtomicRecsList { protected el: HTMLElement; @@ -1080,14 +1080,14 @@ export declare interface AtomicResultList extends Components.AtomicResultList {} @ProxyCmp({ defineCustomElementFn: undefined, - inputs: ['density', 'display', 'imageSize'], + inputs: ['density', 'display', 'gridCellLinkTarget', 'imageSize'], methods: ['setRenderFunction'] }) @Component({ selector: 'atomic-result-list', changeDetection: ChangeDetectionStrategy.OnPush, template: '', - inputs: ['density', 'display', 'imageSize'] + inputs: ['density', 'display', 'gridCellLinkTarget', 'imageSize'] }) export class AtomicResultList { protected el: HTMLElement; diff --git a/packages/atomic/cypress/e2e/result-list/result-components/quickview.cypress.ts b/packages/atomic/cypress/e2e/result-list/result-components/quickview.cypress.ts index 6760320922d..0f24923ab9a 100644 --- a/packages/atomic/cypress/e2e/result-list/result-components/quickview.cypress.ts +++ b/packages/atomic/cypress/e2e/result-list/result-components/quickview.cypress.ts @@ -1,4 +1,7 @@ -import {generateComponentHTML} from '../../../fixtures/fixture-common'; +import { + TagProps, + generateComponentHTML, +} from '../../../fixtures/fixture-common'; import {TestFixture} from '../../../fixtures/test-fixture'; import * as CommonAssertions from '../../common-assertions'; import {addFacet} from '../../facets/facet/facet-actions'; @@ -10,11 +13,14 @@ import { QuickviewModalSelectors, } from './quickview-selectors'; -const addQuickviewInResultList = () => +const addQuickviewInResultList = (props: TagProps = {}) => addResultList( - buildTemplateWithSections({ - actions: generateComponentHTML(quickviewComponent), - }) + buildTemplateWithSections( + { + actions: generateComponentHTML(quickviewComponent), + }, + props + ) ); const openModal = () => { @@ -107,6 +113,82 @@ describe('Quickview Component', () => { }); }); + describe('when used on grid display result list', () => { + beforeEach(() => { + new TestFixture() + .with(addFacet({field: 'filetype'})) + .withHash('f-filetype=pdf') + .with( + addQuickviewInResultList({ + display: 'grid', + }) + ) + .init(); + }); + + it('should be accessible', () => { + CommonAssertions.assertAccessibility(QuickviewSelectors.firstInResult); + }); + + it('should not log error to console', () => { + CommonAssertions.assertConsoleError(false); + }); + + it('should display a header title', () => { + openModal(); + + QuickviewModalSelectors.titleLink() + .should('exist') + .should('have.attr', 'href'); + }); + + it('should display a close button', () => { + openModal(); + QuickviewModalSelectors.closeButton() + .should('exist') + .should('have.attr', 'aria-label', 'Close') + .click(); + + cy.get('body').should('not.have.class', 'atomic-modal-opened'); + }); + + it('should display an iframe', () => { + openModal(); + QuickviewModalSelectors.iframe() + .should('exist') + .its('0.contentDocument.body') + .should('not.be.empty'); + }); + + it('should display a pager with next navigation', () => { + openModal(); + QuickviewModalSelectors.pagerSummary() + .should('exist') + .should('have.text', 'Result 1 of 10'); + + QuickviewModalSelectors.nextButton().click(); + QuickviewModalSelectors.pagerSummary().should( + 'have.text', + 'Result 2 of 10' + ); + cy.expectClickEvent('documentQuickview'); + }); + + it('should display a pager with previous navigation', () => { + openModal(); + QuickviewModalSelectors.pagerSummary() + .should('exist') + .should('have.text', 'Result 1 of 10'); + + QuickviewModalSelectors.previousButton().click(); + QuickviewModalSelectors.pagerSummary().should( + 'have.text', + 'Result 10 of 10' + ); + cy.expectClickEvent('documentQuickview'); + }); + }); + describe('when used on pdf file inside a result list with keywords', () => { beforeEach(() => { new TestFixture() diff --git a/packages/atomic/cypress/e2e/result-list/result-list-actions.ts b/packages/atomic/cypress/e2e/result-list/result-list-actions.ts index 5c570d3af75..507249d093c 100644 --- a/packages/atomic/cypress/e2e/result-list/result-list-actions.ts +++ b/packages/atomic/cypress/e2e/result-list/result-list-actions.ts @@ -79,6 +79,18 @@ export const addFoldedResultList = fixture.withElement(foldedResultList); }; +export const addGridResultList = + (template?: HTMLElement, tags?: TagProps) => (fixture: TestFixture) => { + const gridResultList = generateComponentHTML(resultListComponent, { + ...tags, + display: 'grid', + }); + if (template) { + gridResultList.appendChild(template); + } + fixture.withElement(gridResultList); + }; + export const removeResultChildrenFromResponse = (fixture: TestFixture) => { fixture.withCustomResponse((response) => response.results.forEach((result) => { diff --git a/packages/atomic/cypress/e2e/result-list/result-list-selectors.ts b/packages/atomic/cypress/e2e/result-list/result-list-selectors.ts index f7485e04efe..a62b6624315 100644 --- a/packages/atomic/cypress/e2e/result-list/result-list-selectors.ts +++ b/packages/atomic/cypress/e2e/result-list/result-list-selectors.ts @@ -36,6 +36,10 @@ export const ResultListSelectors = { result: () => ResultListSelectors.shadow().find(resultComponent), resultGridClickable: () => ResultListSelectors.shadow().find('[part="result-list-grid-clickable"]'), + resultGridClickableContainer: () => + ResultListSelectors.shadow().find( + '[part="result-list-grid-clickable-container"]' + ), firstResult: () => ResultListSelectors.result().first().shadow(), firstResultRoot: () => ResultListSelectors.firstResult().find(resultRoot), sections: { diff --git a/packages/atomic/cypress/e2e/result-list/result-list.cypress.ts b/packages/atomic/cypress/e2e/result-list/result-list.cypress.ts index 48517e80f2f..a37a16878e4 100644 --- a/packages/atomic/cypress/e2e/result-list/result-list.cypress.ts +++ b/packages/atomic/cypress/e2e/result-list/result-list.cypress.ts @@ -8,6 +8,7 @@ import { } from './folded-result-list-selectors'; import { addFoldedResultList, + addGridResultList, addResultList, buildTemplateWithSections, } from './result-list-actions'; @@ -31,7 +32,18 @@ const resultListConfig = { title: 'Result List Component', }; -const configs = [foldedResultListConfig, resultListConfig] as const; +const gridResultListConfig = { + componentSelectors: ResultListSelectors, + componentTag: resultListComponent, + addResultFn: addGridResultList, + title: 'Grid result List Component', +}; + +const configs = [ + foldedResultListConfig, + resultListConfig, + gridResultListConfig, +] as const; configs.forEach(({componentSelectors, componentTag, addResultFn, title}) => { describe(title, () => { diff --git a/packages/atomic/src/components.d.ts b/packages/atomic/src/components.d.ts index e6bddef1228..2ce5585d2a7 100644 --- a/packages/atomic/src/components.d.ts +++ b/packages/atomic/src/components.d.ts @@ -9,7 +9,7 @@ import { AutomaticFacet, CategoryFacetSortCriterion, FacetSortCriterion, FoldedR import { AnyBindings } from "./components/common/interface/bindings"; import { DateFilter, DateFilterState, NumericFilter, NumericFilterState, RelativeDateUnit } from "./components/common/types"; import { NumberInputType } from "./components/common/facets/facet-number-input/number-input-type"; -import { ResultDisplayBasicLayout, ResultDisplayDensity, ResultDisplayImageSize, ResultDisplayLayout } from "./components/common/layout/display-options"; +import { ResultDisplayBasicLayout, ResultDisplayDensity, ResultDisplayImageSize, ResultDisplayLayout, ResultTarget } from "./components/common/layout/display-options"; import { ResultRenderingFunction } from "./components/common/result-list/result-list-common-interface"; import { InsightEngine, InsightFacetSortCriterion, InsightFoldedResult, InsightInteractiveResult, InsightLogLevel, InsightRangeFacetRangeAlgorithm, InsightRangeFacetSortCriterion, InsightResult, InsightResultTemplate, InsightResultTemplateCondition, PlatformEnvironmentInsight } from "./components/insight"; import { FacetDisplayValues } from "./components/common/facets/facet-common"; @@ -1213,6 +1213,11 @@ export namespace Components { * The layout to apply when displaying results themselves. This does not affect the display of the surrounding list itself. To modify the number of recommendations per column, modify the --atomic-recs-number-of-columns CSS variable. */ "display": ResultDisplayBasicLayout; + /** + * The target location to open the result link (see [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target)). This property is only leveraged when `display` is `grid`. + * @defaultValue `_self` + */ + "gridCellLinkTarget": ResultTarget; /** * The [heading level](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements) to use for the heading label, from 1 to 6. */ @@ -1467,6 +1472,11 @@ export namespace Components { * The desired layout to use when displaying results. Layouts affect how many results to display per row and how visually distinct they are from each other. */ "display": ResultDisplayLayout; + /** + * The target location to open the result link (see [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target)). This property is only leveraged when `display` is `grid`. + * @defaultValue `_self` + */ + "gridCellLinkTarget": ResultTarget; /** * The expected size of the image displayed in the results. */ @@ -4139,6 +4149,11 @@ declare namespace LocalJSX { * The layout to apply when displaying results themselves. This does not affect the display of the surrounding list itself. To modify the number of recommendations per column, modify the --atomic-recs-number-of-columns CSS variable. */ "display"?: ResultDisplayBasicLayout; + /** + * The target location to open the result link (see [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target)). This property is only leveraged when `display` is `grid`. + * @defaultValue `_self` + */ + "gridCellLinkTarget"?: ResultTarget; /** * The [heading level](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements) to use for the heading label, from 1 to 6. */ @@ -4373,6 +4388,11 @@ declare namespace LocalJSX { * The desired layout to use when displaying results. Layouts affect how many results to display per row and how visually distinct they are from each other. */ "display"?: ResultDisplayLayout; + /** + * The target location to open the result link (see [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target)). This property is only leveraged when `display` is `grid`. + * @defaultValue `_self` + */ + "gridCellLinkTarget"?: ResultTarget; /** * The expected size of the image displayed in the results. */ diff --git a/packages/atomic/src/components/common/button.tsx b/packages/atomic/src/components/common/button.tsx index 5f2f14ad889..2096ae2a1b9 100644 --- a/packages/atomic/src/components/common/button.tsx +++ b/packages/atomic/src/components/common/button.tsx @@ -8,7 +8,7 @@ import { export interface ButtonProps { style: ButtonStyle; - onClick?(): void; + onClick?(event?: MouseEvent): void; class?: string; text?: string; part?: string; diff --git a/packages/atomic/src/components/common/layout/display-options.ts b/packages/atomic/src/components/common/layout/display-options.ts index 01da0e32db8..52efd84d242 100644 --- a/packages/atomic/src/components/common/layout/display-options.ts +++ b/packages/atomic/src/components/common/layout/display-options.ts @@ -4,6 +4,7 @@ export type ResultDisplayBasicLayout = 'list' | 'grid'; export type ResultDisplayLayout = ResultDisplayBasicLayout | 'table'; export type ResultDisplayDensity = 'comfortable' | 'normal' | 'compact'; export type ResultDisplayImageSize = 'large' | 'small' | 'icon' | 'none'; +export type ResultTarget = '_self' | '_blank' | '_parent' | '_top'; function getDisplayClass(display: ResultDisplayLayout) { switch (display) { diff --git a/packages/atomic/src/components/common/result-children/result-children-common.tsx b/packages/atomic/src/components/common/result-children/result-children-common.tsx index 02607187454..7032615d1ec 100644 --- a/packages/atomic/src/components/common/result-children/result-children-common.tsx +++ b/packages/atomic/src/components/common/result-children/result-children-common.tsx @@ -112,7 +112,7 @@ export class ResultChildrenCommon { ); } - private renderChildren(children: FoldedResult[]) { + private renderChildren(children: FoldedResult[]): VNode | VNode[] { return this.renderChildrenWrapper( children.map((child, i) => this.props.renderChild(child, i === children.length - 1) diff --git a/packages/atomic/src/components/common/result-link/result-link.tsx b/packages/atomic/src/components/common/result-link/result-link.tsx index d08ce1a2071..44137e39926 100644 --- a/packages/atomic/src/components/common/result-link/result-link.tsx +++ b/packages/atomic/src/components/common/result-link/result-link.tsx @@ -1,5 +1,6 @@ import {FunctionalComponent, h} from '@stencil/core'; import {filterProtocol} from '../../../utils/xss-utils'; +import {ResultTarget} from '../layout/display-options'; export interface ResultLinkEventProps { onSelect: () => void; @@ -18,6 +19,8 @@ export interface ResultLinkProps extends ResultLinkEventProps { attributes?: Attr[]; tabIndex?: number; ariaHidden?: boolean; + target?: ResultTarget; + rel?: string; } export const bindAnalyticsToLink = ( @@ -64,7 +67,9 @@ export const LinkWithResultAnalytics: FunctionalComponent = ( attributes, tabIndex, ariaHidden, + rel, stopPropagation = true, + target = '_self', }, children ) => { @@ -73,7 +78,9 @@ export const LinkWithResultAnalytics: FunctionalComponent = ( class={className} part={part} href={filterProtocol(href)} + target={target} title={title} + rel={rel} ref={(el) => { if (ref) { ref(el); diff --git a/packages/atomic/src/components/common/result-list/grid-display-results.tsx b/packages/atomic/src/components/common/result-list/grid-display-results.tsx index ec9ad4eba70..85ae7e646d1 100644 --- a/packages/atomic/src/components/common/result-list/grid-display-results.tsx +++ b/packages/atomic/src/components/common/result-list/grid-display-results.tsx @@ -14,6 +14,17 @@ export const GridDisplayResults: FunctionalComponent = (
props.setNewResultRef(element!, index)} + onClick={(event) => { + event.preventDefault(); + interactiveResult.select(); + window.open( + unfoldedResult.clickUri, + event.ctrlKey || event.metaKey + ? '_blank' + : props.gridCellLinkTarget, + 'noopener' + ); + }} > = ( onCancelPendingSelect={() => interactiveResult.cancelPendingSelect()} href={unfoldedResult.clickUri} title={unfoldedResult.title} - tabIndex={-1} - ariaHidden={true} - /> + target={props.gridCellLinkTarget} + rel="noopener" + > + {unfoldedResult.title} + {props.renderResult({ key: props.getResultId(result), result: result, diff --git a/packages/atomic/src/components/common/result-list/result-list-common-interface.ts b/packages/atomic/src/components/common/result-list/result-list-common-interface.ts index 806ea4427ef..4fd1617f19f 100644 --- a/packages/atomic/src/components/common/result-list/result-list-common-interface.ts +++ b/packages/atomic/src/components/common/result-list/result-list-common-interface.ts @@ -8,6 +8,7 @@ import { ResultDisplayDensity, ResultDisplayImageSize, ResultDisplayLayout, + ResultTarget, } from '../layout/display-options'; import {ResultTemplateProvider} from './result-template-provider'; @@ -25,6 +26,7 @@ export interface ResultListCommonProps< loadingFlag: string; resultTemplateProvider: ResultTemplateProvider; nextNewResultTarget: FocusTargetController; + gridCellLinkTarget: ResultTarget; getLayoutDisplay(): ResultDisplayLayout; getResultDisplay(): ResultDisplayLayout; getDensity(): ResultDisplayDensity; diff --git a/packages/atomic/src/components/common/result-list/styles/mixins.pcss b/packages/atomic/src/components/common/result-list/styles/mixins.pcss index 1d154c73a7a..00adfbe72d9 100644 --- a/packages/atomic/src/components/common/result-list/styles/mixins.pcss +++ b/packages/atomic/src/components/common/result-list/styles/mixins.pcss @@ -150,18 +150,27 @@ &:hover { border: 1px solid var(--atomic-neutral); box-shadow: 0px 10px 25px var(--atomic-neutral); + cursor: pointer; } } } } - [part='result-list-grid-clickable'] { - content: ''; + [part='result-list-grid-clickable']:focus-visible { + border: 2px solid var(--atomic-primary); + border-radius: 2px; + color: var(--atomic-primary); + cursor: pointer; + display: inline-block; + text-decoration: underline; + text-align: center; position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; + } + [part='result-list-grid-clickable']:not(:focus) { + clip: rect(1px, 1px, 1px, 1px); + overflow: hidden; + position: absolute; + padding: 0; } } diff --git a/packages/atomic/src/components/insight/result-lists/atomic-insight-folded-result-list/atomic-insight-folded-result-list.tsx b/packages/atomic/src/components/insight/result-lists/atomic-insight-folded-result-list/atomic-insight-folded-result-list.tsx index 0211d9c8f46..ad0c6ca85f8 100644 --- a/packages/atomic/src/components/insight/result-lists/atomic-insight-folded-result-list/atomic-insight-folded-result-list.tsx +++ b/packages/atomic/src/components/insight/result-lists/atomic-insight-folded-result-list/atomic-insight-folded-result-list.tsx @@ -33,6 +33,7 @@ import { ResultDisplayDensity, ResultDisplayImageSize, ResultDisplayLayout, + ResultTarget, } from '../../../common/layout/display-options'; import {ResultListCommon} from '../../../common/result-list/result-list-common'; import {ResultRenderingFunction} from '../../../common/result-list/result-list-common-interface'; @@ -58,6 +59,7 @@ export class AtomicInsightFoldedResultList private resultRenderingFunction: ResultRenderingFunction; private loadingFlag = randomID('firstResultLoaded-'); private display: ResultDisplayLayout = 'list'; + private gridCellLinkTarget: ResultTarget = '_self'; @Element() public host!: HTMLDivElement; @@ -154,6 +156,7 @@ export class AtomicInsightFoldedResultList getNumberOfPlaceholders: () => this.resultsPerPageState.numberOfResults, host: this.host, bindings: this.bindings, + gridCellLinkTarget: this.gridCellLinkTarget, getDensity: () => this.density, getResultDisplay: () => this.display, getLayoutDisplay: () => this.display, diff --git a/packages/atomic/src/components/insight/result-lists/atomic-insight-result-list/atomic-insight-result-list.tsx b/packages/atomic/src/components/insight/result-lists/atomic-insight-result-list/atomic-insight-result-list.tsx index 1ae0f333ed5..3a0c07439e2 100644 --- a/packages/atomic/src/components/insight/result-lists/atomic-insight-result-list/atomic-insight-result-list.tsx +++ b/packages/atomic/src/components/insight/result-lists/atomic-insight-result-list/atomic-insight-result-list.tsx @@ -25,6 +25,7 @@ import { ResultDisplayDensity, ResultDisplayImageSize, ResultDisplayLayout, + ResultTarget, } from '../../../common/layout/display-options'; import {ResultListCommon} from '../../../common/result-list/result-list-common'; import {ResultRenderingFunction} from '../../../common/result-list/result-list-common-interface'; @@ -48,6 +49,7 @@ export class AtomicInsightResultList private resultListCommon!: ResultListCommon; private loadingFlag = randomID('firstInsightResultLoaded-'); private display: ResultDisplayLayout = 'list'; + private gridCellLinkTarget: ResultTarget = '_self'; private resultRenderingFunction: ResultRenderingFunction; @Element() public host!: HTMLDivElement; @@ -72,7 +74,6 @@ export class AtomicInsightResultList * The expected size of the image displayed in the results. */ @Prop({reflect: true}) imageSize: ResultDisplayImageSize = 'icon'; - /** * 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. @@ -116,6 +117,7 @@ export class AtomicInsightResultList getNumberOfPlaceholders: () => this.resultsPerPageState.numberOfResults, host: this.host, bindings: this.bindings, + gridCellLinkTarget: this.gridCellLinkTarget, getDensity: () => this.density, getResultDisplay: () => this.display, getLayoutDisplay: () => this.display, diff --git a/packages/atomic/src/components/recommendations/atomic-recs-list/atomic-recs-list.tsx b/packages/atomic/src/components/recommendations/atomic-recs-list/atomic-recs-list.tsx index b232d84e60b..d2c99204a0f 100644 --- a/packages/atomic/src/components/recommendations/atomic-recs-list/atomic-recs-list.tsx +++ b/packages/atomic/src/components/recommendations/atomic-recs-list/atomic-recs-list.tsx @@ -32,6 +32,7 @@ import { ResultDisplayDensity, ResultDisplayImageSize, ResultDisplayBasicLayout, + ResultTarget, } from '../../common/layout/display-options'; import {ResultListCommon} from '../../common/result-list/result-list-common'; import { @@ -90,6 +91,12 @@ export class AtomicRecsList implements InitializableComponent { * To modify the number of recommendations per column, modify the --atomic-recs-number-of-columns CSS variable. */ @Prop({reflect: true}) public display: ResultDisplayBasicLayout = 'list'; + /** + * The target location to open the result link (see [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target)). + * This property is only leveraged when `display` is `grid`. + * @defaultValue `_self` + */ + @Prop() gridCellLinkTarget: ResultTarget = '_self'; /** * The spacing of various elements in the result list, including the gap between results, the gap between parts of a result, and the font sizes of different parts in a result. */ @@ -190,6 +197,7 @@ export class AtomicRecsList implements InitializableComponent { this.numberOfRecommendationsPerPage ?? this.numberOfRecommendations, host: this.host, bindings: this.bindings, + gridCellLinkTarget: this.gridCellLinkTarget, getDensity: () => this.density, getLayoutDisplay: () => 'grid', getResultDisplay: () => this.display, diff --git a/packages/atomic/src/components/search/result-lists/atomic-folded-result-list/atomic-folded-result-list.tsx b/packages/atomic/src/components/search/result-lists/atomic-folded-result-list/atomic-folded-result-list.tsx index 29e2e97c980..2cc44b9c0b6 100644 --- a/packages/atomic/src/components/search/result-lists/atomic-folded-result-list/atomic-folded-result-list.tsx +++ b/packages/atomic/src/components/search/result-lists/atomic-folded-result-list/atomic-folded-result-list.tsx @@ -33,6 +33,7 @@ import { ResultDisplayDensity, ResultDisplayImageSize, ResultDisplayLayout, + ResultTarget, } from '../../../common/layout/display-options'; import {ResultListCommon} from '../../../common/result-list/result-list-common'; import {ResultRenderingFunction} from '../../../common/result-list/result-list-common-interface'; @@ -59,6 +60,7 @@ export class AtomicFoldedResultList implements InitializableComponent { private resultRenderingFunction: ResultRenderingFunction; private loadingFlag = randomID('firstResultLoaded-'); private display: ResultDisplayLayout = 'list'; + private gridCellLinkTarget: ResultTarget = '_self'; @Element() public host!: HTMLDivElement; @@ -161,6 +163,7 @@ export class AtomicFoldedResultList implements InitializableComponent { getImageSize: () => this.imageSize, nextNewResultTarget: this.nextNewResultTarget, loadingFlag: this.loadingFlag, + gridCellLinkTarget: this.gridCellLinkTarget, getResultListState: () => this.foldedResultListState, getResultRenderingFunction: () => this.resultRenderingFunction, renderResult: (props) => , diff --git a/packages/atomic/src/components/search/result-lists/atomic-result-list/atomic-result-list.tsx b/packages/atomic/src/components/search/result-lists/atomic-result-list/atomic-result-list.tsx index dd09d0134e6..a499f879d05 100644 --- a/packages/atomic/src/components/search/result-lists/atomic-result-list/atomic-result-list.tsx +++ b/packages/atomic/src/components/search/result-lists/atomic-result-list/atomic-result-list.tsx @@ -23,6 +23,7 @@ import { ResultDisplayDensity, ResultDisplayImageSize, ResultDisplayLayout, + ResultTarget, } from '../../../common/layout/display-options'; import {ResultListCommon} from '../../../common/result-list/result-list-common'; import {ResultRenderingFunction} from '../../../common/result-list/result-list-common-interface'; @@ -81,6 +82,14 @@ export class AtomicResultList implements InitializableComponent { * The spacing of various elements in the result list, including the gap between results, the gap between parts of a result, and the font sizes of different parts in a result. */ @Prop({reflect: true}) public density: ResultDisplayDensity = 'normal'; + + /** + * The target location to open the result link (see [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target)). + * This property is only leveraged when `display` is `grid`. + * @defaultValue `_self` + */ + @Prop() gridCellLinkTarget: ResultTarget = '_self'; + /** * The expected size of the image displayed in the results. */ @@ -128,6 +137,7 @@ export class AtomicResultList implements InitializableComponent { this.resultListCommon = new ResultListCommon({ resultTemplateProvider, getNumberOfPlaceholders: () => this.resultsPerPageState.numberOfResults, + gridCellLinkTarget: this.gridCellLinkTarget, host: this.host, bindings: this.bindings, getDensity: () => this.density, diff --git a/packages/atomic/src/components/search/result-template-components/atomic-quickview/atomic-quickview.tsx b/packages/atomic/src/components/search/result-template-components/atomic-quickview/atomic-quickview.tsx index e4b54aa17dc..3a5790173d7 100644 --- a/packages/atomic/src/components/search/result-template-components/atomic-quickview/atomic-quickview.tsx +++ b/packages/atomic/src/components/search/result-template-components/atomic-quickview/atomic-quickview.tsx @@ -126,7 +126,8 @@ export class AtomicQuickview implements InitializableComponent { } } - private onClick() { + private onClick(event?: MouseEvent) { + event?.stopPropagation(); this.quickview.fetchResultContent(); } @@ -139,7 +140,7 @@ export class AtomicQuickview implements InitializableComponent { title={this.bindings.i18n.t('quickview')} style="outline-primary" class="p-2" - onClick={() => this.onClick()} + onClick={(event) => this.onClick(event)} ref={this.buttonFocusTarget.setTarget} >