From a31bad0d775e35d3a33f67adfb683cde5f022622 Mon Sep 17 00:00:00 2001 From: Thomas Baxter Date: Mon, 19 Jan 2026 14:49:41 -0500 Subject: [PATCH 1/2] chore(atomic): applying documentation text standards to atomic --- .../src/components/commerce/Introduction.mdx | 2 +- .../atomic-commerce-product-list.ts | 4 +- .../atomic-product-multi-value-text.ts | 2 +- .../stencil-product-template-decorators.tsx | 2 +- .../atomic-layout-section.ts | 4 +- .../common/atomic-modal/atomic-modal.ts | 2 +- .../validate-props-controller.ts | 2 +- .../atomic-insight-timeframe-facet.tsx | 2 +- .../atomic-ipx-button/atomic-ipx-button.tsx | 187 ++++++++++++++++++ .../src/components/search/Introduction.mdx | 4 +- .../atomic-field-condition.ts | 8 +- .../atomic-result-multi-value-text.ts | 2 +- .../atomic-result-section-bottom-metadata.ts | 2 +- .../quickview-word-highlight/iframe-parser.ts | 2 +- 14 files changed, 206 insertions(+), 19 deletions(-) create mode 100644 packages/atomic/src/components/ipx/atomic-ipx-button/atomic-ipx-button.tsx diff --git a/packages/atomic/src/components/commerce/Introduction.mdx b/packages/atomic/src/components/commerce/Introduction.mdx index 01a148909e7..dd4c56f46b1 100644 --- a/packages/atomic/src/components/commerce/Introduction.mdx +++ b/packages/atomic/src/components/commerce/Introduction.mdx @@ -24,7 +24,7 @@ You can also find examples of full implementations below: These stories provide example usages of the Coveo Atomic web components for Commerce. Each of the components must be placed correctly within the component hierarchy in order to function. The top level of the tree must match the interface being initialized by the Headless engine. -For example, most Coveo Commerce interfaces must start have `atomic-commerce-interface` at the top the component hierarchy. +For example, most Coveo Commerce interfaces must have `atomic-commerce-interface` at the top of the component hierarchy. ### Initialization Here is an example of initializing a `atomic-commerce-interface`. diff --git a/packages/atomic/src/components/commerce/atomic-commerce-product-list/atomic-commerce-product-list.ts b/packages/atomic/src/components/commerce/atomic-commerce-product-list/atomic-commerce-product-list.ts index 2d78a94b7d7..c3b66b6044b 100644 --- a/packages/atomic/src/components/commerce/atomic-commerce-product-list/atomic-commerce-product-list.ts +++ b/packages/atomic/src/components/commerce/atomic-commerce-product-list/atomic-commerce-product-list.ts @@ -62,12 +62,12 @@ import {randomID} from '@/src/utils/utils'; * @part result-table - The table element when the display prop is set to "table". * @part result-table-heading - The thead element when the display prop is set to "table". * @part result-table-heading-row - The tr element nested under the thead when the display prop is set to "table". - * @part result-table-heading-cell - The th elements nested under thead > tr when the display prop is set to "table". + * @part result-table-heading-cell - The `th` elements nested under `thead` > `tr` when the display prop is set to "table". * @part result-table-body - The tbody element when the display prop is set to "table". * @part result-table-row - All tr elements nested under tbody when the display prop is set to "table". * @part result-table-row-even - The even tr elements nested under tbody when the display prop is set to "table". * @part result-table-row-odd - The odd tr elements nested under tbody when the display prop is set to "table". - * @part result-table-cell - The td elements nested under each tbody > tr when the display prop is set to "table". + * @part result-table-cell - The `td` elements nested under each `tbody` > `tr` when the display prop is set to "table". * * @slot default - The default slot where the product templates are defined. */ diff --git a/packages/atomic/src/components/commerce/atomic-product-multi-value-text/atomic-product-multi-value-text.ts b/packages/atomic/src/components/commerce/atomic-product-multi-value-text/atomic-product-multi-value-text.ts index 97726831c08..f76039dd247 100644 --- a/packages/atomic/src/components/commerce/atomic-product-multi-value-text/atomic-product-multi-value-text.ts +++ b/packages/atomic/src/components/commerce/atomic-product-multi-value-text/atomic-product-multi-value-text.ts @@ -53,7 +53,7 @@ export class AtomicProductMultiValueText /** * The maximum number of field values to display. - * If there are _n_ more values than the specified maximum, the last displayed value will be "_n_ more...". + * If there are `n` more values than the specified maximum, the last displayed value will be "`n` more...". */ @property({reflect: true, type: Number, attribute: 'max-values-to-display'}) public maxValuesToDisplay = 3; diff --git a/packages/atomic/src/components/commerce/product-template-component-utils/context/stencil-product-template-decorators.tsx b/packages/atomic/src/components/commerce/product-template-component-utils/context/stencil-product-template-decorators.tsx index ab6b7129c97..ae005c5b270 100644 --- a/packages/atomic/src/components/commerce/product-template-component-utils/context/stencil-product-template-decorators.tsx +++ b/packages/atomic/src/components/commerce/product-template-component-utils/context/stencil-product-template-decorators.tsx @@ -6,7 +6,7 @@ import {itemContext} from '@/src/components/common/item-list/stencil-item-decora * * This method is useful for building custom product template elements, see [Create a Product List](https://docs.coveo.com/en/atomic/latest/cc-search/create-custom-components/native-components/#custom-product-template-component-example) for more information. * - * You should use the method in the [connectedCallback lifecycle method](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks). + * You should use the method in the [`connectedCallback` lifecycle method](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks). * * @param element - The element that the event is dispatched to, which must be the child of a rendered "atomic-product". * @returns A promise that resolves on initialization of the parent "atomic-product" element, or rejects when there is no parent "atomic-product" element. diff --git a/packages/atomic/src/components/common/atomic-layout-section/atomic-layout-section.ts b/packages/atomic/src/components/common/atomic-layout-section/atomic-layout-section.ts index 55c3501a8ec..b70fa614132 100644 --- a/packages/atomic/src/components/common/atomic-layout-section/atomic-layout-section.ts +++ b/packages/atomic/src/components/common/atomic-layout-section/atomic-layout-section.ts @@ -17,14 +17,14 @@ export class AtomicLayoutSection extends LightDomMixin(LitElement) { /** * For column sections, the minimum horizontal space it should take. - * E.g. '300px' + * For example, '300px' */ @property({type: String, reflect: true, attribute: 'min-width'}) minWidth?: string; /** * For column sections, the maximum horizontal space it should take. - * E.g. '300px' + * For example, '300px' */ @property({type: String, reflect: true, attribute: 'max-width'}) maxWidth?: string; diff --git a/packages/atomic/src/components/common/atomic-modal/atomic-modal.ts b/packages/atomic/src/components/common/atomic-modal/atomic-modal.ts index bb59c88d035..8dcd4c188ba 100644 --- a/packages/atomic/src/components/common/atomic-modal/atomic-modal.ts +++ b/packages/atomic/src/components/common/atomic-modal/atomic-modal.ts @@ -17,7 +17,7 @@ import type {AtomicFocusTrap} from '../atomic-focus-trap/atomic-focus-trap.js'; import type {AnyBindings} from '../interface/bindings.js'; /** - * When the modal is opened, the class `atomic-modal-opened` is added to the interfaceElement and the body, allowing further customization. + * When the modal is opened, the class `atomic-modal-opened` is added to the `interfaceElement` and the body, allowing further customization. * * @part backdrop - The transparent backdrop hiding the content behind the modal. * @part container - The modal's outermost container with the outline and background. diff --git a/packages/atomic/src/components/common/validate-props-controller/validate-props-controller.ts b/packages/atomic/src/components/common/validate-props-controller/validate-props-controller.ts index 056118850ea..da1a9fdef6e 100644 --- a/packages/atomic/src/components/common/validate-props-controller/validate-props-controller.ts +++ b/packages/atomic/src/components/common/validate-props-controller/validate-props-controller.ts @@ -7,7 +7,7 @@ import {deepEqual} from '@/src/utils/compare-utils'; * provided Bueno schema. * * It validates the props when the host is connected to the DOM and whenever - * the host updates, revalidating only if the props have changed since the last + * the host updates, re-validating only if the props have changed since the last * validation. * * If validation fails, the controller either sets the `error` property on the host diff --git a/packages/atomic/src/components/insight/atomic-insight-timeframe-facet/atomic-insight-timeframe-facet.tsx b/packages/atomic/src/components/insight/atomic-insight-timeframe-facet/atomic-insight-timeframe-facet.tsx index e0ad14ea49d..a2ec7e5acf6 100644 --- a/packages/atomic/src/components/insight/atomic-insight-timeframe-facet/atomic-insight-timeframe-facet.tsx +++ b/packages/atomic/src/components/insight/atomic-insight-timeframe-facet/atomic-insight-timeframe-facet.tsx @@ -78,7 +78,7 @@ export class AtomicInsightTimeframeFacet */ @Prop({reflect: true}) public field = 'date'; /** - * Whether this facet should contain an datepicker allowing users to set custom ranges. + * Whether this facet should contain a date picker allowing users to set custom ranges. */ @Prop({reflect: true}) public withDatePicker = false; /** diff --git a/packages/atomic/src/components/ipx/atomic-ipx-button/atomic-ipx-button.tsx b/packages/atomic/src/components/ipx/atomic-ipx-button/atomic-ipx-button.tsx new file mode 100644 index 00000000000..9f3d74768c8 --- /dev/null +++ b/packages/atomic/src/components/ipx/atomic-ipx-button/atomic-ipx-button.tsx @@ -0,0 +1,187 @@ +import {loadRecommendationActions} from '@coveo/headless/recommendation'; +import {Component, Element, Fragment, h, Prop, State} from '@stencil/core'; +import CloseIcon from '../../../images/close.svg'; +import SearchIcon from '../../../images/search.svg'; +import { + InitializableComponent, + InitializeBindings, +} from '../../../utils/initialization-utils'; +import {Button} from '../../common/stencil-button'; +import {Bindings} from '../../search/atomic-search-interface/atomic-search-interface'; + +const numberOrPixelValuePattern = new RegExp(/^(?=.*(?:\d+|px)$).*$/); + +/** + * @internal + */ +@Component({ + tag: 'atomic-ipx-button', + styleUrl: './atomic-ipx-button.pcss', + shadow: true, +}) +export class AtomicIPXButton implements InitializableComponent { + @InitializeBindings() public bindings!: Bindings; + + @State() public error!: Error; + + @Element() public host!: HTMLElement; + + /** + * The label that will be shown to the user. + */ + @Prop({reflect: true}) public label?: string; + + /** + * The close icon of the button. + */ + @Prop({reflect: true}) public closeIcon = CloseIcon; + + /** + * The open icon of the button. + */ + @Prop({reflect: true}) public openIcon = SearchIcon; + + /** + * Whether the IPX modal is open. + */ + @Prop({mutable: true, reflect: true}) public isModalOpen = false; + + private recommendationsLoaded = false; + + private async getRecommendations() { + const recsEngine = this.bindings.interfaceElement.querySelector( + 'atomic-recs-interface' + )?.engine; + if (recsEngine) { + this.recommendationsLoaded = true; + recsEngine.dispatch( + loadRecommendationActions(recsEngine).getRecommendations() + ); + } + } + + private async onClick() { + if (!this.recommendationsLoaded) { + this.getRecommendations(); + } + this.isModalOpen ? this.close() : this.open(); + this.render(); + } + + private renderIPXButton() { + return ( + + ); + } + + public render() { + const [displayedIcon, hiddenIcon] = this.isModalOpen + ? ['ipx-close-icon', 'ipx-open-icon'] + : ['ipx-open-icon', 'ipx-close-icon']; + if (this.isModalOpen && !this.recommendationsLoaded) { + this.getRecommendations(); + } + + return ( + + { + + } +
+ {this.renderIPXButton()} +
+
+ ); + } + + private get ipxModal() { + return this.bindings.interfaceElement.querySelector('atomic-ipx-modal')!; + } + + private open() { + this.isModalOpen = true; + this.host.classList.add('btn-open'); + this.ipxModal.setAttribute('is-open', 'true'); + } + + private close() { + this.isModalOpen = false; + this.host.classList.remove('btn-open'); + this.ipxModal.setAttribute('is-open', 'false'); + } + + private getIcon(icon: string) { + const initialDiv = document.createElement('div')!; + initialDiv.innerHTML = icon; + const iconElement = initialDiv.querySelector('svg'); + if (!iconElement) { + return initialDiv.innerHTML; + } + // here, we grab the icon width and height to set a `viewbox` (which keeps the svg looking normal), then remove styles from the icon to let the icon stretch into the space it is given. + const iconWidth = this.getIconWidth(iconElement); + const iconHeight = this.getIconHeight(iconElement); + this.cleanupSVGStyles(iconElement); + if (iconWidth && iconHeight) { + iconElement.setAttribute('viewBox', `0 0 ${iconWidth} ${iconHeight}`); + } + return initialDiv.innerHTML; + } + + private cleanupSVGStyles(iconElement: SVGSVGElement) { + iconElement.removeAttribute('style'); + iconElement.removeAttribute('width'); + iconElement.removeAttribute('height'); + } + + private getIconWidth(icon: SVGSVGElement) { + const width = icon.getAttribute('width') ?? ''; + if (numberOrPixelValuePattern.test(width)) { + return width; + } + return null; + } + + private getIconHeight(icon: SVGSVGElement) { + const height = icon.getAttribute('height') ?? ''; + if (numberOrPixelValuePattern.test(height)) { + return height; + } + return null; + } +} diff --git a/packages/atomic/src/components/search/Introduction.mdx b/packages/atomic/src/components/search/Introduction.mdx index 74d49c1cf0a..c2523887f75 100644 --- a/packages/atomic/src/components/search/Introduction.mdx +++ b/packages/atomic/src/components/search/Introduction.mdx @@ -42,7 +42,6 @@ For a detailed breakdown of how to configure a Search engine, please refer to th getSampleSearchEngineConfiguration } = await import('https://static.cloud.coveo.com/headless/v3/headless.esm.js'); - await customElements.whenDefined('atomic-search-interface'); const searchInterface = document.querySelector('atomic-search-interface'); await searchInterface.initialize(getSampleSearchEngineConfiguration()); @@ -107,4 +106,5 @@ If you are looking to implement Coveo in a framework, you can find reference cod * [NextJS - Search (using App router)](https://github.com/coveo/ui-kit/tree/main/samples/atomic/search-nextjs-app-router) * [NextJS - Search (using Pages router)](https://github.com/coveo/ui-kit/tree/main/samples/atomic/search-nextjs-pages-router) * [Stencil - Search](https://github.com/coveo/ui-kit/tree/main/samples/atomic/search-stencil) -* [Vue - Search](https://github.com/coveo/ui-kit/tree/main/samples/atomic/search-vuejs) \ No newline at end of file +* [Vue - Search](https://github.com/coveo/ui-kit/tree/main/samples/atomic/search-vuejs) + diff --git a/packages/atomic/src/components/search/atomic-field-condition/atomic-field-condition.ts b/packages/atomic/src/components/search/atomic-field-condition/atomic-field-condition.ts index a608f795962..2d74a544c87 100644 --- a/packages/atomic/src/components/search/atomic-field-condition/atomic-field-condition.ts +++ b/packages/atomic/src/components/search/atomic-field-condition/atomic-field-condition.ts @@ -32,24 +32,24 @@ export class AtomicFieldCondition private resultController = createResultContextController(this); /** - * A condition that is satisfied when the specified field is defined on a result (e.g., `if-defined="author"` is satisfied when a result has the `author` field). + * A condition that is satisfied when the specified field is defined on a result (for example, `if-defined="author"` is satisfied when a result has the `author` field). */ @property({type: String, attribute: 'if-defined'}) ifDefined?: string; /** - * A condition that is satisfied when the specified field is not defined on a result (e.g., `if-not-defined="author"` is satisfied when a result does not have the `author` field). + * A condition that is satisfied when the specified field is not defined on a result (for example, `if-not-defined="author"` is satisfied when a result does not have the `author` field). */ @property({type: String, attribute: 'if-not-defined'}) ifNotDefined?: string; /** - * A condition that is satisfied when the specified field matches one of the specified values on a result (e.g., `must-match-filetype="pdf,docx"` is satisfied when a result has a filetype of either pdf or docx). + * A condition that is satisfied when the specified field matches one of the specified values on a result (for example, `must-match-filetype="pdf,docx"` is satisfied when a result has a filetype of either pdf or docx). * @type {Record} */ @mapProperty({splitValues: true, attributePrefix: 'must-match'}) mustMatch!: Record; /** - * A condition that is satisfied when the specified field does not match any of the specified values on a result (e.g., `must-not-match-filetype="pdf"` is satisfied when a result does not have a filetype of pdf). + * A condition that is satisfied when the specified field does not match any of the specified values on a result (for example, `must-not-match-filetype="pdf"` is satisfied when a result does not have a filetype of pdf). * @type {Record} */ @mapProperty({splitValues: true, attributePrefix: 'must-not-match'}) diff --git a/packages/atomic/src/components/search/atomic-result-multi-value-text/atomic-result-multi-value-text.ts b/packages/atomic/src/components/search/atomic-result-multi-value-text/atomic-result-multi-value-text.ts index 8300ab4511e..f37ded1e274 100644 --- a/packages/atomic/src/components/search/atomic-result-multi-value-text/atomic-result-multi-value-text.ts +++ b/packages/atomic/src/components/search/atomic-result-multi-value-text/atomic-result-multi-value-text.ts @@ -51,7 +51,7 @@ export class AtomicResultMultiValueText /** * The maximum number of field values to display. - * If there are _n_ more values than the specified maximum, the last displayed value will be "_n_ more...". + * If there are `n` more values than the specified maximum, the last displayed value will be "`n` more...". */ @property({type: Number, reflect: true, attribute: 'max-values-to-display'}) public maxValuesToDisplay = 3; diff --git a/packages/atomic/src/components/search/atomic-result-section-bottom-metadata/atomic-result-section-bottom-metadata.ts b/packages/atomic/src/components/search/atomic-result-section-bottom-metadata/atomic-result-section-bottom-metadata.ts index 9f568cf72cc..31828b51851 100644 --- a/packages/atomic/src/components/search/atomic-result-section-bottom-metadata/atomic-result-section-bottom-metadata.ts +++ b/packages/atomic/src/components/search/atomic-result-section-bottom-metadata/atomic-result-section-bottom-metadata.ts @@ -7,7 +7,7 @@ import {ItemSectionMixin} from '@/src/mixins/item-section-mixin'; * * Behavior: * * Has a maximum height of two lines. - * ** We recommend that you use `atomic-result-fields-list` to ensure that the fields in this section don’t overflow. + * ** We recommend that you use `atomic-result-fields-list` to ensure that the fields in this section don't overflow. * * Exposes the `--line-height` variable so child elements can adjust to the current line height. * * Has a defined CSS `color` property for text. * * Has a font weight. diff --git a/packages/atomic/src/components/search/result-template-components/quickview-word-highlight/iframe-parser.ts b/packages/atomic/src/components/search/result-template-components/quickview-word-highlight/iframe-parser.ts index e23bd3db504..1b4f1872b78 100644 --- a/packages/atomic/src/components/search/result-template-components/quickview-word-highlight/iframe-parser.ts +++ b/packages/atomic/src/components/search/result-template-components/quickview-word-highlight/iframe-parser.ts @@ -10,7 +10,7 @@ function findHighlightedElements(iframe?: HTMLIFrameElement): HTMLElement[] { // Collect tagged word elements as well as elements whose id starts with // the highlight prefix. Tagged words (``) must be - // considered so that invalid identifiers (e.g. id="invalid") are + // considered so that invalid identifiers (for example id="invalid") are // discovered and cause the appropriate error in the parser. const selector = `[id^="${HIGHLIGHT_PREFIX}"]`; const query = root.querySelectorAll(selector); From 43d177e84110216195ae53731df4d5f61531effb Mon Sep 17 00:00:00 2001 From: "developer-experience-bot[bot]" <91079284+developer-experience-bot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 19:59:48 +0000 Subject: [PATCH 2/2] Add generated files --- packages/atomic/src/components.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/atomic/src/components.d.ts b/packages/atomic/src/components.d.ts index bba8979675c..2d06b594407 100644 --- a/packages/atomic/src/components.d.ts +++ b/packages/atomic/src/components.d.ts @@ -404,7 +404,7 @@ export namespace Components { */ "sortCriteria": InsightRangeFacetSortCriterion; /** - * Whether this facet should contain an datepicker allowing users to set custom ranges. + * Whether this facet should contain a date picker allowing users to set custom ranges. */ "withDatePicker": boolean; } @@ -1459,7 +1459,7 @@ declare namespace LocalJSX { */ "sortCriteria"?: InsightRangeFacetSortCriterion; /** - * Whether this facet should contain an datepicker allowing users to set custom ranges. + * Whether this facet should contain a date picker allowing users to set custom ranges. */ "withDatePicker"?: boolean; }