From 3d69e976a380c8f71b9a6720be19920cc7fe4447 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Tue, 25 Jun 2024 15:50:17 -0400 Subject: [PATCH] feat(atomic): add alt text field on atomic-result-image (#4056) This PR adds a field on atomic-result-image that will be used as the value of the alt text, similar to atomic-product-image. https://coveord.atlassian.net/browse/KIT-3243 --- .../src/lib/stencil-generated/components.ts | 4 +- packages/atomic/src/components.d.ts | 12 ++- .../atomic-product-image.tsx | 22 +++--- .../atomic-result-image.tsx | 30 +++++++- packages/atomic/src/locales.json | 77 +++++++++++++------ 5 files changed, 104 insertions(+), 41 deletions(-) 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 21f348cd36f..dd7cc6fe25c 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 @@ -1226,14 +1226,14 @@ export declare interface AtomicResultIcon extends Components.AtomicResultIcon {} @ProxyCmp({ - inputs: ['fallback', 'field'] + inputs: ['fallback', 'field', 'imageAltField'] }) @Component({ selector: 'atomic-result-image', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['fallback', 'field'], + inputs: ['fallback', 'field', 'imageAltField'], }) export class AtomicResultImage { protected el: HTMLElement; diff --git a/packages/atomic/src/components.d.ts b/packages/atomic/src/components.d.ts index fd9be0da422..e1e8f898cc2 100644 --- a/packages/atomic/src/components.d.ts +++ b/packages/atomic/src/components.d.ts @@ -1890,7 +1890,7 @@ export namespace Components { /** * The product field that contains the alt text for the images. This will look for the field in the product object first, then in the product.additionalFields object. The field can be a string or an array of strings. If the value of the field is a string, it will be used as the alt text for all the images. If the value of the field is an array of strings, the alt text will be used in the order of the images. If the field is not specified, or does not contain a valid value, the alt text will be set to "Image {index} out of {totalImages} for {productName}". */ - "imagesAltField"?: string; + "imageAltField"?: string; /** * Navigates to the specified image index. * @param index - The index of the image to navigate to. @@ -2547,6 +2547,10 @@ export namespace Components { * The result field which the component should use. This will look for the field in the Result object first, then in the Result.raw object. It is important to include the necessary field in the `atomic-search-interface` component. */ "field": string; + /** + * The result field that contains the alt text for the image. This will look for the field in the Result object first, then in the Result.raw object If the field is not specified, or does not contain a valid value, the alt text will be set to "Image for {productName}". + */ + "imageAltField"?: string; } /** * The `atomic-result-link` component automatically transforms a search result title into a clickable link that points to the original item. @@ -7234,7 +7238,7 @@ declare namespace LocalJSX { /** * The product field that contains the alt text for the images. This will look for the field in the product object first, then in the product.additionalFields object. The field can be a string or an array of strings. If the value of the field is a string, it will be used as the alt text for all the images. If the value of the field is an array of strings, the alt text will be used in the order of the images. If the field is not specified, or does not contain a valid value, the alt text will be set to "Image {index} out of {totalImages} for {productName}". */ - "imagesAltField"?: string; + "imageAltField"?: string; } interface AtomicProductLink { /** @@ -7842,6 +7846,10 @@ declare namespace LocalJSX { * The result field which the component should use. This will look for the field in the Result object first, then in the Result.raw object. It is important to include the necessary field in the `atomic-search-interface` component. */ "field": string; + /** + * The result field that contains the alt text for the image. This will look for the field in the Result object first, then in the Result.raw object If the field is not specified, or does not contain a valid value, the alt text will be set to "Image for {productName}". + */ + "imageAltField"?: string; } /** * The `atomic-result-link` component automatically transforms a search result title into a clickable link that points to the original item. diff --git a/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/atomic-product-image.tsx b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/atomic-product-image.tsx index 76dad827127..774f8310859 100644 --- a/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/atomic-product-image.tsx +++ b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/atomic-product-image.tsx @@ -48,7 +48,7 @@ export class AtomicProductImage implements InitializableComponent { * * If the field is not specified, or does not contain a valid value, the alt text will be set to "Image {index} out of {totalImages} for {productName}". */ - @Prop({reflect: true}) imagesAltField?: string; + @Prop({reflect: true}) imageAltField?: string; /** * An fallback image URL that will be used in case the specified image is not available or an error is encountered. @@ -128,7 +128,7 @@ export class AtomicProductImage implements InitializableComponent { (image) => typeof image === 'string' ); - const validImagesAlt = this.imagesAlt; + const validImageAlt = this.imageAlt; this.images = validImages.map((url, index) => { const finalUrl = this.useFallback ? this.fallback : url; @@ -136,15 +136,15 @@ export class AtomicProductImage implements InitializableComponent { this.validateUrl(finalUrl); let altText; - if (Array.isArray(validImagesAlt) && validImagesAlt[index]) { - altText = validImagesAlt[index]; - } else if (typeof validImagesAlt === 'string') { - altText = validImagesAlt; + if (Array.isArray(validImageAlt) && validImageAlt[index]) { + altText = validImageAlt[index]; + } else if (typeof validImageAlt === 'string') { + altText = validImageAlt; } else { - altText = this.bindings.i18n.t('image-alt-fallback', { + altText = this.bindings.i18n.t('image-alt-fallback-multiple', { count: index + 1, max: validImages?.length, - productName: this.product.ec_name, + itemName: this.product.ec_name, }); } @@ -164,11 +164,11 @@ export class AtomicProductImage implements InitializableComponent { return Array.isArray(value) ? value : [value]; } - private get imagesAlt() { - if (this.imagesAltField) { + private get imageAlt() { + if (this.imageAltField) { const value = ProductTemplatesHelpers.getProductProperty( this.product, - this.imagesAltField + this.imageAltField ); if (Array.isArray(value)) { diff --git a/packages/atomic/src/components/search/result-template-components/atomic-result-image/atomic-result-image.tsx b/packages/atomic/src/components/search/result-template-components/atomic-result-image/atomic-result-image.tsx index 0dfc5b6e39a..81ed1996d5f 100644 --- a/packages/atomic/src/components/search/result-template-components/atomic-result-image/atomic-result-image.tsx +++ b/packages/atomic/src/components/search/result-template-components/atomic-result-image/atomic-result-image.tsx @@ -27,6 +27,13 @@ export class AtomicResultImage implements InitializableComponent { */ @Prop({reflect: true}) field!: string; + /** + * The result field that contains the alt text for the image. This will look for the field in the Result object first, then in the Result.raw object + * + * If the field is not specified, or does not contain a valid value, the alt text will be set to "Image for {productName}". + */ + @Prop({reflect: true}) imageAltField?: string; + /** * An optional fallback image URL that will be used in case the specified image field is not available or encounters an error. */ @@ -42,6 +49,27 @@ export class AtomicResultImage implements InitializableComponent { return Array.isArray(value) ? value[0] : value; } + private get altText(): string { + if (this.imageAltField) { + const value = ResultTemplatesHelpers.getResultProperty( + this.result, + this.field + ); + + if (Array.isArray(value) && typeof value[0] === 'string') { + return value[0]; + } + + if (typeof value === 'string') { + return value; + } + } + + return this.bindings.i18n.t('image-alt-fallback', { + itemName: this.result.title, + }); + } + private logWarning(message: string) { this.bindings.engine.logger.warn(message, this.host); } @@ -87,7 +115,7 @@ export class AtomicResultImage implements InitializableComponent { return ( {`${this.field} this.handleImageError()} loading="lazy" diff --git a/packages/atomic/src/locales.json b/packages/atomic/src/locales.json index 9b8704b61d8..0628fd5ea76 100644 --- a/packages/atomic/src/locales.json +++ b/packages/atomic/src/locales.json @@ -7426,32 +7426,59 @@ "zh-CN": "可用于:", "zh-TW": "可用於:" }, + "image-alt-fallback-multiple": { + "en": "Image {{count}} out of {{max}} for {{itemName}}", + "fr": "Image {{count}} sur {{max}} pour {{itemName}}", + "cs": "Obrázek {{count}} z {{max}} pro {{itemName}}", + "da": "Billede {{count}} ud af {{max}} for {{itemName}}", + "de": "Bild {{count}} von {{max}} für {{itemName}}", + "el": "Εικόνα {{count}} από {{max}} για {{itemName}}", + "es": "Imagen {{count}} de {{max}} para {{itemName}}", + "fi": "Kuva {{count}} / {{max}} tuotteelle {{itemName}}", + "hu": "Kép {{count}} a {{max}}-ból a(z) {{itemName}} termékhez", + "id": "Gambar {{count}} dari {{max}} untuk {{itemName}}", + "it": "Immagine {{count}} su {{max}} per {{itemName}}", + "ja": "{{max}}のうちのイメージ{{count}}、{{itemName}}", + "ko": "{{max}}개 중 이미지 {{count}} for {{itemName}}", + "nl": "Afbeelding {{count}} van {{max}} voor {{itemName}}", + "no": "Bilde {{count}} av {{max}} for {{itemName}}", + "pl": "Obraz {{count}} z {{max}} dla {{itemName}}", + "pt": "Imagem {{count}} de {{max}} para {{itemName}}", + "pt-BR": "Imagem {{count}} de {{max}} para {{itemName}}", + "ru": "Изображение {{count}} из {{max}} для {{itemName}}", + "sv": "Bild {{count}} av {{max}} för {{itemName}}", + "th": "รูปภาพ {{count}} จาก {{max}} สำหรับ {{itemName}}", + "tr": "{{max}} üzerinden Resim {{count}} için {{itemName}}", + "zh": "第{{count}}张图片,共{{max}}张,适用于{{itemName}}", + "zh-CN": "第{{count}}张图片,共{{max}}张,适用于{{itemName}}", + "zh-TW": "第{{count}}張圖片,共{{max}}張,適用於{{itemName}}" + }, "image-alt-fallback": { - "en": "Image {{count}} out of {{max}} for {{productName}}", - "fr": "Image {{count}} sur {{max}} pour {{productName}}", - "cs": "Obrázek {{count}} z {{max}} pro {{productName}}", - "da": "Billede {{count}} ud af {{max}} for {{productName}}", - "de": "Bild {{count}} von {{max}} für {{productName}}", - "el": "Εικόνα {{count}} από {{max}} για {{productName}}", - "es": "Imagen {{count}} de {{max}} para {{productName}}", - "fi": "Kuva {{count}} / {{max}} tuotteelle {{productName}}", - "hu": "Kép {{count}} a {{max}}-ból a(z) {{productName}} termékhez", - "id": "Gambar {{count}} dari {{max}} untuk {{productName}}", - "it": "Immagine {{count}} su {{max}} per {{productName}}", - "ja": "{{max}}のうちのイメージ{{count}}、{{productName}}", - "ko": "{{max}}개 중 이미지 {{count}} for {{productName}}", - "nl": "Afbeelding {{count}} van {{max}} voor {{productName}}", - "no": "Bilde {{count}} av {{max}} for {{productName}}", - "pl": "Obraz {{count}} z {{max}} dla {{productName}}", - "pt": "Imagem {{count}} de {{max}} para {{productName}}", - "pt-BR": "Imagem {{count}} de {{max}} para {{productName}}", - "ru": "Изображение {{count}} из {{max}} для {{productName}}", - "sv": "Bild {{count}} av {{max}} för {{productName}}", - "th": "รูปภาพ {{count}} จาก {{max}} สำหรับ {{productName}}", - "tr": "{{max}} üzerinden Resim {{count}} için {{productName}}", - "zh": "第{{count}}张图片,共{{max}}张,适用于{{productName}}", - "zh-CN": "第{{count}}张图片,共{{max}}张,适用于{{productName}}", - "zh-TW": "第{{count}}張圖片,共{{max}}張,適用於{{productName}}" + "en": "Image for {{itemName}}", + "fr": "Image pour {{itemName}}", + "cs": "Obrázek pro {{itemName}}", + "da": "Billede for {{itemName}}", + "de": "Bild für {{itemName}}", + "el": "Εικόνα για {{itemName}}", + "es": "Imagen para {{itemName}}", + "fi": "Kuva tuotteelle {{itemName}}", + "hu": "Kép a(z) {{itemName}} termékhez", + "id": "Gambar untuk {{itemName}}", + "it": "Immagine per {{itemName}}", + "ja": "{{itemName}}の画像", + "ko": "{{itemName}}의 이미지", + "nl": "Afbeelding voor {{itemName}}", + "no": "Bilde for {{itemName}}", + "pl": "Obraz dla {{itemName}}", + "pt": "Imagem para {{itemName}}", + "pt-BR": "Imagem para {{itemName}}", + "ru": "Изображение для {{itemName}}", + "sv": "Bild för {{itemName}}", + "th": "รูปภาพสำหรับ {{itemName}}", + "tr": "{{itemName}} için resim", + "zh": "{{itemName}}的图片", + "zh-CN": "{{itemName}}的图片", + "zh-TW": "{{itemName}}的圖片" }, "image-not-found-alt": { "en": "No image available.",