card-container.mjs (6596B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { 6 classMap, 7 html, 8 ifDefined, 9 when, 10 } from "chrome://global/content/vendor/lit.all.mjs"; 11 import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; 12 13 /** 14 * A collapsible card container to be used throughout Firefox View 15 * 16 * @property {string} sectionLabel - The aria-label used for the section landmark if the header is hidden with hideHeader 17 * @property {boolean} hideHeader - Optional property given if the card container should not display a header 18 * @property {boolean} isEmptyState - Optional property given if the card is used within an empty state 19 * @property {boolean} isInnerCard - Optional property given if the card a nested card within another card and given a border rather than box-shadow 20 * @property {boolean} preserveCollapseState - Whether or not the expanded/collapsed state should persist 21 * @property {string} shortPageName - Page name that the 'View all' link will navigate to and the preserveCollapseState pref will use 22 * @property {boolean} showViewAll - True if you need to display a 'View all' header link to navigate 23 * @property {boolean} toggleDisabled - Optional property given if the card container should not be collapsible 24 * @property {boolean} removeBlockEndMargin - True if you need to remove the block end margin on the card container 25 */ 26 class CardContainer extends MozLitElement { 27 constructor() { 28 super(); 29 this.initiallyExpanded = true; 30 this.isExpanded = false; 31 this.visible = false; 32 } 33 34 static properties = { 35 sectionLabel: { type: String }, 36 hideHeader: { type: Boolean }, 37 isExpanded: { type: Boolean }, 38 isEmptyState: { type: Boolean }, 39 isInnerCard: { type: Boolean }, 40 preserveCollapseState: { type: Boolean }, 41 shortPageName: { type: String }, 42 showViewAll: { type: Boolean }, 43 toggleDisabled: { type: Boolean }, 44 removeBlockEndMargin: { type: Boolean }, 45 visible: { type: Boolean }, 46 }; 47 48 static queries = { 49 detailsEl: "details", 50 mainSlot: "slot[name=main]", 51 summaryEl: "summary", 52 viewAllLink: ".view-all-link", 53 }; 54 55 get detailsExpanded() { 56 return this.detailsEl.hasAttribute("open"); 57 } 58 59 get detailsOpenPrefValue() { 60 const prefName = this.shortPageName 61 ? `browser.tabs.firefox-view.ui-state.${this.shortPageName}.open` 62 : null; 63 if (prefName && Services.prefs.prefHasUserValue(prefName)) { 64 return Services.prefs.getBoolPref(prefName); 65 } 66 return null; 67 } 68 69 connectedCallback() { 70 super.connectedCallback(); 71 this.isExpanded = this.detailsOpenPrefValue ?? this.initiallyExpanded; 72 } 73 74 onToggleContainer() { 75 if (this.isExpanded == this.detailsExpanded) { 76 return; 77 } 78 this.isExpanded = this.detailsExpanded; 79 80 this.updateTabLists(); 81 82 if (!this.shortPageName) { 83 return; 84 } 85 86 if (this.preserveCollapseState) { 87 const prefName = this.shortPageName 88 ? `browser.tabs.firefox-view.ui-state.${this.shortPageName}.open` 89 : null; 90 Services.prefs.setBoolPref(prefName, this.isExpanded); 91 } 92 93 // Record telemetry 94 Glean.firefoxviewNext[ 95 `card${this.isExpanded ? "Expanded" : "Collapsed"}CardContainer` 96 ].record({ 97 data_type: this.shortPageName, 98 }); 99 } 100 101 viewAllClicked() { 102 this.dispatchEvent( 103 new CustomEvent("card-container-view-all", { 104 bubbles: true, 105 composed: true, 106 }) 107 ); 108 } 109 110 willUpdate(changes) { 111 if (changes.has("visible")) { 112 this.updateTabLists(); 113 } 114 } 115 116 updateTabLists() { 117 let tabLists = this.querySelectorAll( 118 "fxview-tab-list, opentabs-tab-list, syncedtabs-tab-list" 119 ); 120 if (tabLists) { 121 tabLists.forEach(tabList => { 122 tabList.updatesPaused = !this.visible || !this.isExpanded; 123 }); 124 } 125 } 126 127 render() { 128 return html` 129 <link 130 rel="stylesheet" 131 href="chrome://browser/content/firefoxview/card-container.css" 132 /> 133 ${when( 134 this.toggleDisabled, 135 () => 136 html`<div 137 class=${classMap({ 138 "card-container": true, 139 inner: this.isInnerCard, 140 "empty-state": this.isEmptyState && !this.isInnerCard, 141 })} 142 > 143 <span 144 id="header" 145 class="card-container-header" 146 ?hidden=${ifDefined(this.hideHeader)} 147 toggleDisabled 148 ?withViewAll=${this.showViewAll} 149 > 150 <slot name="header"></slot> 151 <slot name="secondary-header"></slot> 152 </span> 153 <a 154 href="about:firefoxview#${this.shortPageName}" 155 @click=${this.viewAllClicked} 156 class="view-all-link" 157 data-l10n-id="firefoxview-view-all-link" 158 ?hidden=${!this.showViewAll} 159 ></a> 160 <slot name="main"></slot> 161 <slot name="footer" class="card-container-footer"></slot> 162 </div>`, 163 () => 164 html`<details 165 class=${classMap({ 166 "card-container": true, 167 inner: this.isInnerCard, 168 "empty-state": this.isEmptyState && !this.isInnerCard, 169 })} 170 ?open=${this.isExpanded} 171 ?isOpenTabsView=${this.removeBlockEndMargin} 172 @toggle=${this.onToggleContainer} 173 role=${this.isInnerCard ? "presentation" : "group"} 174 > 175 <summary 176 class="card-container-header" 177 ?hidden=${ifDefined(this.hideHeader)} 178 ?withViewAll=${this.showViewAll} 179 > 180 <span 181 class="icon chevron-icon" 182 role="presentation" 183 data-l10n-id=${this.isExpanded 184 ? "firefoxview-collapse-button-hide" 185 : "firefoxview-collapse-button-show"} 186 ></span> 187 <slot name="header"></slot> 188 </summary> 189 <a 190 href="about:firefoxview#${this.shortPageName}" 191 @click=${this.viewAllClicked} 192 class="view-all-link" 193 data-l10n-id="firefoxview-view-all-link" 194 ?hidden=${!this.showViewAll} 195 ></a> 196 <slot name="main"></slot> 197 <slot name="footer" class="card-container-footer"></slot> 198 </details>` 199 )} 200 `; 201 } 202 } 203 customElements.define("card-container", CardContainer);