Last active
May 26, 2026 18:12
-
-
Save mrclay/c0d4a629b6c2de6a4d132968428de264 to your computer and use it in GitHub Desktop.
React/web components to demo a layout at various widths
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
| (() => { | |
| /** | |
| * Provides a centered block container whose max-width can easily | |
| * be set to given CSS lengths via a button click. | |
| * | |
| * Example component demo: | |
| * | |
| * <break-points sizes="1200px,40rem" initSize="40rem"> | |
| * ...My component here | |
| * </break-points> | |
| */ | |
| class BreakPointsElement extends HTMLElement { | |
| constructor() { | |
| super(); | |
| this.attachShadow({ mode: "open" }); | |
| } | |
| connectedCallback() { | |
| this.render(); | |
| const initSize = this.getAttribute("init-size") || ""; | |
| if (initSize) { | |
| this.shadowRoot.querySelector(".container").style.maxWidth = initSize; | |
| } | |
| } | |
| render() { | |
| const sizesAttr = this.getAttribute("sizes") || ""; | |
| const sizes = sizesAttr | |
| .split(",") | |
| .map((s) => s.trim()) | |
| .filter(Boolean); | |
| const style = ` | |
| :host { | |
| display: block; | |
| } | |
| .buttons { | |
| align-items: center; | |
| display: flex; | |
| flex-wrap: wrap; | |
| font: 14px/17px system-ui, sans-serif; | |
| justify-content: center; | |
| button, label { | |
| align-items: center; | |
| display: flex; | |
| font: inherit; | |
| height: 2rem; | |
| padding: 0 1rem; | |
| cursor: pointer; | |
| background: #f0f0f0; | |
| border: 1px solid #ccc; | |
| border-radius: 4px; | |
| &:hover { | |
| background: #e0e0e0; | |
| } | |
| } | |
| label { | |
| padding-inline: 0.5rem; | |
| } | |
| } | |
| .container { | |
| transition: max-width 0.3s ease; | |
| margin: 0 auto; | |
| max-width: 100%; | |
| &.show-outline { | |
| outline: 1px dashed #f99; | |
| } | |
| &[data-max-width="unset"] { | |
| margin-inline: 2rem; | |
| } | |
| } | |
| `; | |
| this.shadowRoot.innerHTML = ` | |
| <style>${style}</style> | |
| <div class="buttons"> | |
| <button data-size="unset">unset</button> | |
| ${sizes.map((size) => `<button data-size="${size}">${size}</button>`).join("")} | |
| <label><input type="checkbox" id="outline-toggle"> Outline</label> | |
| </div> | |
| <div class="container"> | |
| <slot></slot> | |
| </div> | |
| `; | |
| this.shadowRoot.querySelectorAll("button").forEach((btn) => { | |
| btn.addEventListener("click", (e) => { | |
| const width = e.target.getAttribute("data-size"); | |
| const div = this.shadowRoot.querySelector(".container"); | |
| if (div instanceof HTMLDivElement) { | |
| div.style.maxWidth = width; | |
| div.dataset.maxWidth = width; | |
| } | |
| }); | |
| }); | |
| this.shadowRoot | |
| .querySelector("#outline-toggle") | |
| .addEventListener("change", (e) => { | |
| const container = this.shadowRoot.querySelector(".container"); | |
| container.classList[e.target.checked ? "add" : "remove"]( | |
| "show-outline", | |
| ); | |
| }); | |
| } | |
| } | |
| if (!customElements.get("break-points")) { | |
| customElements.define("break-points", BreakPointsElement); | |
| } | |
| })(); |
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
| import React, { CSSProperties, useState } from 'react'; | |
| interface BreakPointsProps { | |
| /** Comma-separated sizes, e.g., "1200px, 40rem" */ | |
| sizes?: string; | |
| /** The initial max-width of the container */ | |
| initSize?: string; | |
| children?: React.ReactNode; | |
| } | |
| /** | |
| * Provides a centered block container whose max-width can easily | |
| * be set to given CSS lengths via a button click. | |
| * | |
| * Example component demo: | |
| * | |
| * <BreakPoints sizes="1200px,40rem" initSize="40rem"> | |
| * ...My component here | |
| * </BreakPoints> | |
| */ | |
| export const BreakPoints: React.FC<BreakPointsProps> = ({ | |
| sizes = '', | |
| initSize = 'unset', | |
| children, | |
| }) => { | |
| const [currentSize, setCurrentSize] = useState(initSize); | |
| const [showOutline, setShowOutline] = useState(false); | |
| const sizeList = sizes | |
| .split(',') | |
| .map((s) => s.trim()) | |
| .filter(Boolean); | |
| const hostStyle: React.CSSProperties = { | |
| display: 'block', | |
| }; | |
| const styles: Record<string, CSSProperties> = { | |
| buttonsContainerStyle: { | |
| alignItems: 'center', | |
| display: 'flex', | |
| flexWrap: 'wrap', | |
| font: '14px/17px system-ui, sans-serif', | |
| justifyContent: 'center', | |
| gap: '0.5rem', | |
| marginBottom: '1rem', | |
| }, | |
| controlStyle: { | |
| alignItems: 'center', | |
| display: 'flex', | |
| font: 'inherit', | |
| height: '2rem', | |
| margin: '0', | |
| padding: '0 1rem', | |
| cursor: 'pointer', | |
| background: '#f0f0f0', | |
| border: '1px solid #ccc', | |
| borderRadius: '4px', | |
| transition: 'background 0.2s ease', | |
| }, | |
| containerStyle: { | |
| transition: 'max-width 0.3s ease', | |
| margin: currentSize === 'unset' ? '0 2rem' : '0 auto', | |
| maxWidth: currentSize, | |
| outline: showOutline ? '1px dashed #f99' : 'none', | |
| }, | |
| }; | |
| const labelStyle: React.CSSProperties = { | |
| ...styles.controlStyle, | |
| paddingInline: '0.5rem', | |
| }; | |
| return ( | |
| <div style={hostStyle}> | |
| {/* Scoped hover styles that inline style objects don't natively support */} | |
| <style>{` | |
| .BreakPoints-control:hover { | |
| background: #e0e0e0 !important; | |
| } | |
| `}</style> | |
| <div style={styles.buttonsContainerStyle}> | |
| <button | |
| className="BreakPoints-control" | |
| style={styles.controlStyle} | |
| onClick={() => setCurrentSize('unset')} | |
| > | |
| unset | |
| </button> | |
| {sizeList.map((size) => ( | |
| <button | |
| key={size} | |
| className="BreakPoints-control" | |
| style={styles.controlStyle} | |
| onClick={() => setCurrentSize(size)} | |
| > | |
| {size} | |
| </button> | |
| ))} | |
| <label className="BreakPoints-control" style={labelStyle}> | |
| <input | |
| type="checkbox" | |
| checked={showOutline} | |
| onChange={(e) => setShowOutline(e.target.checked)} | |
| style={{ marginRight: '0.5rem', cursor: 'pointer' }} | |
| /> | |
| Outline | |
| </label> | |
| </div> | |
| <div style={styles.containerStyle}>{children}</div> | |
| </div> | |
| ); | |
| }; | |
| export default BreakPoints; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment