1import { BaseTemplate } from './BaseTemplate.js'; 2 3/** 4 * This file defines the <application-list-template> web component, which extends a base layout 5 * (BaseTemplate) to render a structured UI template for application listing views. It provides: 6 * 7 * - A customizable header area with slots for breadcrumb/tabs, title, tutorial link, and action buttons. 8 * - A main content slot for injecting dynamic or templated content. 9 * - Responsive layout behavior for both desktop and mobile breakpoints. 10 * - Optional full-width button behavior on mobile (⤠768px). 11 * - Dynamic rendering of a default label for the tutorial link slot. 12 * - Custom event dispatching for click actions on slotted buttons: 13 * - 'primary-btn-clicked' for create-application 14 * - 'primary-btn-2-clicked' for create-template 15 * - 'secondary-btn-clicked' for share-embed 16 * 17 * Usage: 18 * <application-list-template> 19 * <breadcrumb-tabs-component slot="tabs" tabs-config="..."></breadcrumb-tabs-component> 20 * <h1 slot="header-title">Title</h1> 21 * <a slot="watch-tutorial" href="#">Watch</a> 22 * <button-element slot="create-application" id="app-btn">Create App</button-element> 23 * <button-element slot="create-template" id="template-btn">Create Template</button-element> 24 * <button-element slot="share-embed" id="share-btn">Share</button-element> 25 * <div>Your list or content here</div> 26 * </application-list-template> 27 * 28 * Dependencies: 29 * - Extends: BaseTemplate (imported from './BaseTemplate.js') 30 * - Expects: <button-element> components with slots matching action names. 31 * 32 * @slot tabs - Optional breadcrumb/tabs row (e.g. <breadcrumb-tabs-component>) rendered above the 33 * title row, mirroring the same slot on the sibling ApplicationDetailsTemplate. Renders 34 * nothing when left unslotted, so existing consumers of this template are unaffected. 35 * @slot breadcrumb - Optional breadcrumb nav rendered above the title, inside the left header column. 36 * @slot section-heading - Optional secondary heading rendered below the title. 37 * @slot description - Optional supporting copy rendered below the section heading. 38 * These three collapse when unslotted, so they add no vertical gap. 39 * 40 * @cssprop --template-header-padding - Padding of the header row (tabs/title/actions). Default: 16px 32px. 41 * @cssprop --template-content-padding - Padding of the default-slot content area. Default: 8px 32px 16px. 42 * Set either via the `style` attribute on the host element (e.g. 43 * `<application-list-template style="--template-header-padding: 1rem 1rem .5rem;">`), 44 * same pattern as BaseTemplate's own `--margin-left`. 45 */ 46 47// changes to my template here 48class ApplicationListTemplate extends BaseTemplate { 49 constructor() { 50 super(); 51 } 52 53 connectedCallback() { 54 super.connectedCallback(); 55 56 // Responsive styling on load handler 57 this.updateLeftPosition(); 58 this.updateResponsiveStyles(); 59 this.render(); 60 61 // Responsive styling on resize handler 62 this._handleResizeBound = () => { 63 this.updateLeftPosition(); 64 this.updateResponsiveStyles(); 65 }; 66 67 window.addEventListener('resize', this._handleResizeBound); 68 } 69 70 disconnectedCallback() { 71 window.removeEventListener('resize', this._handleResizeBound); 72 super.disconnectedCallback(); 73 } 74 75 render() { 76 const slotWrapper = this.getContentWrapper(); 77 if(!slotWrapper) return; 78 79 slotWrapper.innerHTML = ` 80 <style> 81 .template-header { 82 display: flex; 83 flex-direction: column; 84 padding: var(--template-header-padding, 16px 32px); 85 } 86 87 /* Toggled in render() -- an unslotted <slot> is still a DOM node, so :empty can't detect it */ 88 .header-tabs.has-content { 89 margin-bottom: 8px; 90 } 91 92 /* Two-column page header: content (left) + help/actions (right) */ 93 .header-row { 94 display: flex; 95 align-items: flex-start; 96 justify-content: space-between; 97 gap: 40px; 98 flex-wrap: wrap; 99 } 100 101 .header-main { 102 display: flex; 103 flex-direction: column; 104 gap: 8px; 105 flex: 1 0 0; 106 min-width: 0; 107 } 108 109 .header-aside { 110 display: flex; 111 flex-direction: column; 112 align-items: flex-end; 113 justify-content: flex-start; 114 gap: 16px; 115 } 116 117 .header-actions { 118 display: flex; 119 justify-content: flex-end; 120 gap: 8px; 121 flex-wrap: wrap; 122 } 123 124 /* An optional slot with no assigned content is collapsed
124so it adds no gap */ 125 slot.is-empty { display: none; } 126 127 ::slotted([slot="breadcrumb"]) { 128 display: flex; 129 align-items: center; 130 gap: 4px; 131 font-size: 14px; 132 line-height: 18px; 133 margin: 0; 134 } 135 136 ::slotted(h1[slot="header-title"]) { 137 color: #038580; 138 font-size: 32px; 139 font-weight: 500; 140 line-height: 40px; 141 margin: 0; 142 } 143 144 ::slotted([slot="section-heading"]) { 145 color: #222222; 146 font-size: 24px; 147 font-weight: 600; 148 line-height: 32px; 149 margin: 0; 150 } 151 152 ::slotted([slot="description"]) { 153 color: #5E6463; 154 font-size: 16px; 155 font-weight: 400; 156 line-height: 24px; 157 margin: 0; 158 } 159 160 ::slotted(a[slot="watch-tutorial"]) { 161 display: inline-flex; 162 align-items: center; 163 font-size: 14px; 164 color: #026E80 !important; 165 text-decoration: none; 166 font-weight: 500; 167 gap: 4px; 168 white-space: nowrap; 169 line-height: 18px; 170 } 171 172 .template-content { 173 padding: var(--template-content-padding, 8px 32px 16px); 174 } 175 176 @media (max-width: 768px) { 177 .template-header { padding: 16px; } 178 .header-row { 179 flex-direction: column; 180 gap: 16px; 181 } 182 .header-aside { 183 align-items: stretch; 184 width: 100%; 185 } 186 .header-actions { 187 flex-direction: column; 188 width: 100%; 189 } 190 .header-actions ::slotted(*) { 191 width: 100%; 192 } 193 .template-content { padding: 8px 16px 16px; } 194 } 195 </style> 196 197 <slot name="banner"></slot> 198 <header class="template-header"> 199 <div class="header-tabs"> 200 <slot name="tabs"></slot> 201 </div> 202 <div class="header-row"> 203 <div class="header-main"> 204 <slot name="breadcrumb"></slot> 205 <slot name="header-title"></slot> 206 <slot name="section-heading"></slot> 207 <slot name="description"></slot> 208 </div> 209 <div class="header-aside"> 210 <slot name="watch-tutorial"></slot> 211 <div class="header-actions"> 212 <slot name="header-actions"></slot> 213 </div> 214 </div> 215 </div> 216 </header> 217 <div class="template-content"> 218 <slot></slot> 219 </div> 220 `; 221 222 // Inject default inner content into the watch link if it's empty 223 requestAnimationFrame(() => { 224 const slottedLink = this.querySelector('a[slot="watch-tutorial"]'); 225 if(slottedLink && slottedLink.innerHTML.trim() === '') { 226 slottedLink.innerHTML = `<i class="uil uil-play-circle size-16"></i> Watch tutorial videos`; 227 } 228 }); 229 230 // Collapse optional header slots that have no assigned content so they add no vertical gap 231 ['breadcrumb', 'section-heading', 'description', 'watch-tutorial'].forEach((name) => { 232 const slot = slotWrapper.querySelector(`slot[name="${name}"]`); 233 if(!slot) return; 234 const syncEmpty = () => { 235 const hasContent = slot.assignedNodes({ flatten: true }).some( 236 (n) => n.nodeType === 1 || (n.nodeType === 3 && n.textContent.trim() !== '') 237 ); 238 slot.classList.toggle('is-empty', !hasContent); 239 }; 240 slot.addEventListener('slotchange', syncEmpty); 241 syncEmpty(); 242 }); 243 244 // Only reserve spacing for the tabs row when something is actually slotted into it. 245 const tabsSlot = slotWrapper.querySelector('slot[name="tabs"]'); 246 const tabsWrapper = slotWrapper.querySelector('.header-tabs'); 247 if(tabsSlot && tabsWrapper) { 248 const updateTabsVisibility = () => { 249 tabsWrapper.classList.toggle('has-content', tabsSlot.assignedElements({ flatten: true }).length > 0); 250 }; 251 tabsSlot.addEventListener('slotchange', updateTabsVisibility); 252 updateTabsVisibility(); 253 } 254 255 this.setupEventHandlers(); 256 } 257 258 updateResponsiveStyles() { 259 //matchMedia keeps this in step with the shadow stylesheet's own 768px query; innerWidth counts the scrollbar, the media query does not 260 const isMobile = window.matchMedia('(max-width: 768px)').matches; 261 const buttons = this.querySelectorAll(`button-element[slot="header-actions"]`); 262 263 buttons.forEach(button => { 264 265 if(button) { 266 if(isMobile) { 267 button.setAttribute('full-width', ''); 268 } else { 269 button.removeAttribute('full-width'); 270 } 271 } 272 }); 273 } 274 275 276 setupEventHandlers() { 277 requestAnimationFrame(() => { 278 const shadow = this.shadowRoot; 279 280 const buttons = this.querySelectorAll(`button-element[slot="header-actions"]`); 281 282 buttons.forEach((button) => { 283 if(button.hasAttribute('dropdown')) return; 284 button.addEventListener('click', () => { 285 const buttonId = button.id || null; 286 this.dispatchEvent(new CustomEvent('click', { 287 detail: { id: buttonId }, 288 bubbles: true, 289 composed: true 290 })); 291 }); 292 293 }); 294 }); 295 } 296} 297 298customElements.define('application-list-template', ApplicationListTemplate);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.