summaryrefslogtreecommitdiffstats
path: root/browser/components/shopping/content/shopping-card.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/shopping/content/shopping-card.mjs')
-rw-r--r--browser/components/shopping/content/shopping-card.mjs204
1 files changed, 204 insertions, 0 deletions
diff --git a/browser/components/shopping/content/shopping-card.mjs b/browser/components/shopping/content/shopping-card.mjs
new file mode 100644
index 0000000000..7b2448df36
--- /dev/null
+++ b/browser/components/shopping/content/shopping-card.mjs
@@ -0,0 +1,204 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
+import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
+
+const MIN_SHOW_MORE_HEIGHT = 200;
+/**
+ * A card container to be used in the shopping sidebar. There are three card types.
+ * The default type where no type attribute is required and the card will have no extra functionality.
+ * The "accordion" type will initially not show any content. The card will contain a arrow to expand the
+ * card so all of the content is visible.
+ *
+ * @property {string} label - The label text that will be used for the card header
+ * @property {string} type - (optional) The type of card. No type specified
+ * will be the default card. The other available types are "accordion" and "show-more".
+ */
+class ShoppingCard extends MozLitElement {
+ static properties = {
+ label: { type: String },
+ type: { type: String },
+ _isExpanded: { type: Boolean },
+ };
+
+ static get queries() {
+ return {
+ detailsEl: "#shopping-details",
+ contentEl: "#content",
+ };
+ }
+
+ labelTemplate() {
+ if (this.label) {
+ if (this.type === "accordion") {
+ return html`
+ <div id="label-wrapper">
+ <h2 id="header">${this.label}</h2>
+ <button
+ tabindex="-1"
+ class="icon chevron-icon ghost-button"
+ aria-labelledby="header"
+ @click=${this.handleChevronButtonClick}
+ ></button>
+ </div>
+ `;
+ }
+ return html`
+ <div id="label-wrapper">
+ <h2 id="header">${this.label}</h2>
+ <slot name="rating"></slot>
+ </div>
+ `;
+ }
+ return "";
+ }
+
+ cardTemplate() {
+ if (this.type === "accordion") {
+ return html`
+ <details id="shopping-details" @toggle=${this.onCardToggle}>
+ <summary>${this.labelTemplate()}</summary>
+ <div id="content"><slot name="content"></slot></div>
+ </details>
+ `;
+ } else if (this.type === "show-more") {
+ return html`
+ ${this.labelTemplate()}
+ <article
+ id="content"
+ class="show-more"
+ aria-describedby="content"
+ expanded="false"
+ >
+ <slot name="content"></slot>
+
+ <footer>
+ <button
+ aria-controls="content"
+ class="small-button shopping-button"
+ data-l10n-id="shopping-show-more-button"
+ @click=${this.handleShowMoreButtonClick}
+ ></button>
+ </footer>
+ </article>
+ `;
+ }
+ return html`
+ ${this.labelTemplate()}
+ <div id="content" aria-describedby="content">
+ <slot name="content"></slot>
+ </div>
+ `;
+ }
+
+ onCardToggle() {
+ const action = this.detailsEl.open ? "expanded" : "collapsed";
+ let l10nId = this.getAttribute("data-l10n-id");
+ switch (l10nId) {
+ case "shopping-settings-label":
+ Glean.shopping.surfaceSettingsExpandClicked.record({ action });
+ break;
+ case "shopping-analysis-explainer-label":
+ Glean.shopping.surfaceShowQualityExplainerClicked.record({
+ action,
+ });
+ break;
+ }
+ }
+
+ handleShowMoreButtonClick(e) {
+ this._isExpanded = !this._isExpanded;
+ // toggle show more/show less text
+ e.target.setAttribute(
+ "data-l10n-id",
+ this._isExpanded
+ ? "shopping-show-less-button"
+ : "shopping-show-more-button"
+ );
+ // toggle content expanded attribute
+ this.contentEl.attributes.expanded.value = this._isExpanded;
+
+ let action = this._isExpanded ? "expanded" : "collapsed";
+ Glean.shopping.surfaceShowMoreReviewsButtonClicked.record({
+ action,
+ });
+ }
+
+ enableShowMoreButton() {
+ this._isExpanded = false;
+ this.toggleAttribute("showMoreButtonDisabled", false);
+ this.contentEl.attributes.expanded.value = false;
+ }
+
+ disableShowMoreButton() {
+ this._isExpanded = true;
+ this.toggleAttribute("showMoreButtonDisabled", true);
+ this.contentEl.attributes.expanded.value = true;
+ }
+
+ handleChevronButtonClick() {
+ this.detailsEl.open = !this.detailsEl.open;
+ }
+
+ firstUpdated() {
+ if (this.type !== "show-more") {
+ return;
+ }
+
+ let contentSlot = this.shadowRoot.querySelector("slot[name='content']");
+ let contentSlotEls = contentSlot.assignedElements();
+ if (!contentSlotEls.length) {
+ return;
+ }
+
+ let slottedDiv = contentSlotEls[0];
+
+ this.handleContentSlotResize = this.handleContentSlotResize.bind(this);
+ this.contentResizeObserver = new ResizeObserver(
+ this.handleContentSlotResize
+ );
+ this.contentResizeObserver.observe(slottedDiv);
+ }
+
+ disconnectedCallback() {
+ this.contentResizeObserver?.disconnect();
+ }
+
+ handleContentSlotResize(entries) {
+ for (let entry of entries) {
+ if (entry.contentRect.height === 0) {
+ return;
+ }
+
+ if (entry.contentRect.height < MIN_SHOW_MORE_HEIGHT) {
+ this.disableShowMoreButton();
+ } else if (this.hasAttribute("showMoreButtonDisabled")) {
+ this.enableShowMoreButton();
+ }
+ }
+ }
+
+ render() {
+ return html`
+ <link
+ rel="stylesheet"
+ href="chrome://browser/content/shopping/shopping-card.css"
+ />
+ <link
+ rel="stylesheet"
+ href="chrome://browser/content/shopping/shopping-page.css"
+ />
+ <article
+ class="shopping-card"
+ aria-labelledby="header"
+ aria-label=${ifDefined(this.label)}
+ >
+ ${this.cardTemplate()}
+ </article>
+ `;
+ }
+}
+customElements.define("shopping-card", ShoppingCard);