Last active
October 3, 2025 17:32
-
-
Save krhoyt/8276d7a8350f1ba9a4d4ce6d7c07b934 to your computer and use it in GitHub Desktop.
Three panel responsive layout with fourth panel that slides into relevant area based on viewport.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export default class HoytPrimary extends HTMLElement { | |
| constructor() { | |
| super(); | |
| const template = document.createElement( 'template' ); | |
| template.innerHTML = /* template */ ` | |
| <style> | |
| :host { | |
| background: #ecf0f1; | |
| box-sizing: border-box; | |
| display: flex; | |
| flex-basis: 0; | |
| flex-direction: column; | |
| flex-grow: 1; | |
| margin: 0; | |
| padding: 0; | |
| position: relative; | |
| } | |
| </style> | |
| <section> | |
| <slot></slot> | |
| </section> | |
| `; | |
| // Root | |
| this.attachShadow( {mode: 'open'} ); | |
| this.shadowRoot.appendChild( template.content.cloneNode( true ) ); | |
| } | |
| // When attributes change | |
| _render() {;} | |
| // Promote properties | |
| // Values may be set before module load | |
| _upgrade( property ) { | |
| if( this.hasOwnProperty( property ) ) { | |
| const value = this[property]; | |
| delete this[property]; | |
| this[property] = value; | |
| } | |
| } | |
| // Setup | |
| connectedCallback() { | |
| this._render(); | |
| } | |
| // Watched attributes | |
| static get observedAttributes() { | |
| return []; | |
| } | |
| // Observed attribute has changed | |
| // Update render | |
| attributeChangedCallback( name, old, value ) { | |
| this._render(); | |
| } | |
| } | |
| window.customElements.define( 'hoyt-primary', HoytPrimary ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export default class HoytSecondary extends HTMLElement { | |
| constructor() { | |
| super(); | |
| const template = document.createElement( 'template' ); | |
| template.innerHTML = /* template */ ` | |
| <style> | |
| :host { | |
| box-sizing: border-box; | |
| display: flex; | |
| flex-basis: 0; | |
| flex-direction: column; | |
| flex-grow: 1; | |
| margin: 0; | |
| overflow: hidden; | |
| padding: 0; | |
| position: relative; | |
| } | |
| section[part=placeholder] { | |
| box-sizing: border-box; | |
| height: 100%; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| section[part=detail] { | |
| background: #f8f9fa; | |
| box-sizing: border-box; | |
| height: 100%; | |
| left: 0; | |
| margin: 0; | |
| padding: 0; | |
| position: absolute; | |
| transform: translateY( 100% ); | |
| transition: transform 300ms ease-in-out; | |
| top: 0; | |
| width: 100%; | |
| } | |
| :host( [open] ) section[part=detail] { | |
| transform: translateY( 0 ); | |
| } | |
| @media ( max-width: 430px ) { | |
| :host { | |
| bottom: 0; | |
| height: 100%; | |
| left: 0; | |
| position: fixed; | |
| transform: translateY( 100% ); | |
| transition: transform 300ms ease-in-out; | |
| width: 100%; | |
| z-index: 100; | |
| } | |
| :host( [open] ) { | |
| transform: translateY( 0 ); | |
| } | |
| section[part=placeholder] { | |
| display: none; | |
| } | |
| section[part=detail] { | |
| height: 100%; | |
| position: static; | |
| transform: none; | |
| } | |
| } | |
| </style> | |
| <section part="placeholder"> | |
| <slot name="placeholder"></slot> | |
| </section> | |
| <section part="detail"> | |
| <slot></slot> | |
| </section> | |
| `; | |
| // Root | |
| this.attachShadow( {mode: 'open'} ); | |
| this.shadowRoot.appendChild( template.content.cloneNode( true ) ); | |
| } | |
| // When attributes change | |
| _render() {;} | |
| // Promote properties | |
| // Values may be set before module load | |
| _upgrade( property ) { | |
| if( this.hasOwnProperty( property ) ) { | |
| const value = this[property]; | |
| delete this[property]; | |
| this[property] = value; | |
| } | |
| } | |
| // Setup | |
| connectedCallback() { | |
| this._upgrade( 'open' ); | |
| this._render(); | |
| } | |
| // Watched attributes | |
| static get observedAttributes() { | |
| return [ | |
| 'open' | |
| ]; | |
| } | |
| // Observed attribute has changed | |
| // Update render | |
| attributeChangedCallback( name, old, value ) { | |
| this._render(); | |
| } | |
| // Attributes | |
| // Reflected | |
| // Boolean, Number, String, null | |
| get open() { | |
| return this.hasAttribute( 'open' ); | |
| } | |
| set open( value ) { | |
| if( value !== null ) { | |
| if( typeof value === 'boolean' ) { | |
| value = value.toString(); | |
| } | |
| if( value === 'false' ) { | |
| this.removeAttribute( 'open' ); | |
| } else { | |
| this.setAttribute( 'open', '' ); | |
| } | |
| } else { | |
| this.removeAttribute( 'open' ); | |
| } | |
| } | |
| } | |
| window.customElements.define( 'hoyt-secondary', HoytSecondary ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="theme-color" content="#5fb2ff"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> | |
| <meta name="description" content="Responsive three panel user interface."> | |
| <title>Three Panel</title> | |
| <link rel="icon" type="image/x-icon" href="three-panel.svg" /> | |
| <style> | |
| body, html { | |
| height: 100%; | |
| margin: 0; | |
| overflow: hidden; | |
| padding: 0; | |
| } | |
| body { | |
| box-sizing: border-box; | |
| color: #161616; | |
| display: flex; | |
| flex-direction: row; | |
| font-family: sans-serif; | |
| font-weight: 400; | |
| } | |
| hoyt-navigation p { | |
| color: #ffffff; | |
| } | |
| hoyt-navigation button[id=menu_close], | |
| hoyt-primary button[id=menu_open] { | |
| display: none; | |
| } | |
| @media ( max-width: 1024px ) { | |
| hoyt-navigation button[id=menu_close], | |
| hoyt-primary button[id=menu_open] { | |
| display: inline-flex; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <hoyt-navigation> | |
| <button id="menu_close" type="button">Close Menu</button> | |
| <p>Navigation Menu</p> | |
| </hoyt-navigation> | |
| <hoyt-primary> | |
| <button id="menu_open" type="button">Open Menu</button> | |
| <p>Primary</p> | |
| <button id="detail_open" type="button">Open Detail</button> | |
| </hoyt-primary> | |
| <hoyt-secondary> | |
| <p slot="placeholder">Secondary</p> | |
| <p>Detail</p> | |
| <button id="detail_close" type="button">Close Detail</button> | |
| </hoyt-secondary> | |
| <script src="navigation.js" type="module"></script> | |
| <script src="primary.js" type="module"></script> | |
| <script src="secondary.js" type="module"></script> | |
| <script> | |
| const detail_close = document.querySelector( '#detail_close' ); | |
| detail_close.addEventListener( 'click', () => { | |
| secondary.open = false; | |
| } ); | |
| const detail_open = document.querySelector( '#detail_open' ); | |
| detail_open.addEventListener( 'click', () => { | |
| secondary.open = true; | |
| } ); | |
| const menu_close = document.querySelector( '#menu_close' ); | |
| menu_close.addEventListener( 'click', () => { | |
| navigation.open = false; | |
| } ); | |
| const menu_open = document.querySelector( '#menu_open' ); | |
| menu_open.addEventListener( 'click', () => { | |
| navigation.open = true; | |
| } ); | |
| const navigation = document.querySelector( 'hoyt-navigation' ); | |
| const secondary = document.querySelector( 'hoyt-secondary' ); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment