UIElement Docs Version 0.12.1

🔗 🍽️ Examples & Recipes

Discover practical examples and patterns for building reactive, modular components with UIElement. Each example focuses on showcasing a specific feature or best practice, guiding you through real-world use cases.

🔗 What You'll Learn

This collection of examples demonstrates a range of scenarios, from simple state updates in a single component to managing complex interactions across multiple components. Here's an overview of what you'll find:

Whether you're getting started with a basic component or building a full-featured application, these examples will help you understand how to use UIElement effectively to build reactive Web Components.

🔗 TabList and AccordionPanel Example

  • Tab 1
    Content for Tab 1
    Tab 2
    Content for Tab 2
    Tab 3
    Content for Tab 3
    TabList Source Code

    Loading...

    AccordionPanel Source Code

    Loading...

    🔗 CodeBlock Example

    code-block.html html

    <code-block collapsed language="html" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
    	<p class="meta">
    		<span class="file">code-block.html</span>
    		<span class="language">html</span>
    	</p>
    	<pre><code class="language-html"><code-block collapsed language="html" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
    	<p class="meta">
    		<span class="file">code-block.html</span>
    		<span class="language">html</span>
    	</p>
    	<pre><code class="language-html"></code></pre>
    	<input-button class="copy">
    		<button type="button" class="secondary small">Copy</button>
    	</input-button>
    	<button type="button" class="overlay">Expand</button>
    </code-block></code></pre>
    	<input-button class="copy">
    		<button type="button" class="secondary small">Copy</button>
    	</input-button>
    	<button type="button" class="overlay">Expand</button>
    </code-block>
    CodeBlock Source Code

    Loading...

    InputButton Source Code

    Loading...

    🔗 LazyLoad Example

    Loading...

    LazyLoad Source Code

    Loading...

    🔗 InputField Example

    20 characters remaining

    InputField Source Code

    Loading...

    🔗 MediaContext Example

    The MediaContext component provides global state to any sub-components in its DOM tree by exposing context for responsive and adaptive features. It tracks the following:

    🔗 Configuring Breakpoints

    The viewport sizes can be customized by providing attributes on the <media-context> element:

    For example, to set a small breakpoint at 40em and a medium breakpoint at 60em, use:

    html

    <media-context sm="40em" md="60em"></media-context>
    

    Source code:

    js

    import { UIElement, maybe } from '@zeix/ui-element'
    
    const VIEWPORT_XS = 'xs';
    const VIEWPORT_SM = 'sm';
    const VIEWPORT_MD = 'md';
    const VIEWPORT_LG = 'lg';
    const VIEWPORT_XL = 'xl';
    const ORIENTATION_LANDSCAPE = 'landscape';
    const ORIENTATION_PORTRAIT = 'portrait';
    
    class MediaContext extends UIElement {
    static providedContexts = ['media-motion', 'media-theme', 'media-viewport', 'media-orientation'];
    
    connectedCallback() {
    	const getBreakpoints = () => {
    	const parseBreakpoint = (breakpoint) => {
    		const attr = this.getAttribute(breakpoint)?.trim();
    		if (!attr) return null;
    		const unit = attr.match(/em$/) ? 'em' : 'px';
    		const value = maybe(parseFloat(attr)).filter(Number.isFinite)[0];
    		return value ? value + unit : null;
    	};
    
    	const sm = parseBreakpoint(VIEWPORT_SM) || '32em';
    	const md = parseBreakpoint(VIEWPORT_MD) || '48em';
    	const lg = parseBreakpoint(VIEWPORT_LG) || '72em';
    	const xl = parseBreakpoint(VIEWPORT_XL) || '108em';
    	return { sm, md, lg, xl };
    	};
    	
    	const breakpoints = getBreakpoints();
    
    	const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');
    	const darkMode = matchMedia('(prefers-color-scheme: dark)');
    	const screenSmall = matchMedia(`(min-width: ${breakpoints.sm})`);
    	const screenMedium = matchMedia(`(min-width: ${breakpoints.md})`);
    	const screenLarge = matchMedia(`(min-width: ${breakpoints.lg})`);
    	const screenXLarge = matchMedia(`(min-width: ${breakpoints.xl})`);
    	const screenOrientation = matchMedia('(orientation: landscape)');
    
    	const getViewport = () => {
    	if (screenXLarge.matches) return VIEWPORT_XL;
    	if (screenLarge.matches) return VIEWPORT_LG;
    	if (screenMedium.matches) return VIEWPORT_MD;
    	if (screenSmall.matches) return VIEWPORT_SM;
    	return VIEWPORT_XS;
    	};
    
    	this.set('media-motion', reducedMotion.matches);
    	this.set('media-theme', darkMode.matches);
    	this.set('media-viewport', getViewport());
    	this.set('media-orientation', screenOrientation.matches ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT);
    
    	reducedMotion.onchange = (e) => this.set('media-motion', e.matches);
    	darkMode.onchange = (e) => this.set('media-theme', e.matches);
    	screenSmall.onchange = () => this.set('media-viewport', getViewport());
    	screenMedium.onchange = () => this.set('media-viewport', getViewport());
    	screenLarge.onchange = () => this.set('media-viewport', getViewport());
    	screenXLarge.onchange = () => this.set('media-viewport', getViewport());
    	screenOrientation.onchange = (e) => this.set('media-orientation', e.matches ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT);
    }
    }
    
    MediaContext.define('media-context');
    

    🔗 ThemedComponent Example

    This component changes its background based on the theme!
    Source Code

    🔗 HTML

    html

    <media-context>
    	<themed-component>
    		This component changes its background based on the theme!
    	</themed-component>
    </media-context>
    

    🔗 CSS

    css

    themed-component {
    	display: block;
    	padding: 20px;
    	color: white;
    	transition: background-color 0.3s ease;
    
    	&.dark {
    		background-color: black;
    	}
    
    	&.light {
    		background-color: lightgray;
    	}
    }
    

    🔗 JavaScript

    js

    import { UIElement, toggleClass } from '@zeix/ui-element';
    
    class ThemedComponent extends UIElement {
    	static consumedContexts = ['media-theme'];
    
    	connectedCallback() {
    		// Toggle the class based on 'media-theme' signal
    		this.self.sync(toggleClass('dark', () => this.get('media-theme')));
    		this.self.sync(toggleClass('light', () => !this.get('media-theme')));
    	}
    }
    ThemedComponent.define('themed-component');
    

    🔗 AnimatedComponent Example

    Box 1
    Box 2
    Box 3
    Source Code

    🔗 HTML

    html

    <media-context>
    	<animated-component>
    		<div class="animated-box">Box 1</div>
    		<div class="animated-box">Box 2</div>
    		<div class="animated-box">Box 3</div>
    	</animated-component>
    </media-context>
    

    🔗 CSS

    css

    animated-component {
    	display: block;
    	padding: 20px;
    	overflow: hidden;
    
    	.animated-box {
    		width: 50px;
    		height: 50px;
    		margin: 10px;
    		background-color: lightblue;
    		text-align: center;
    		line-height: 50px;
    		font-weight: bold;
    		color: white;
    	}
    
    	&.no-motion .animated-box {
    		opacity: 0;
    		transition: opacity 1s ease-in;
    	}
    
    	&.motion .animated-box {
    		animation: moveAndFlash 2s infinite ease-in-out alternate;
    	}
    
    	@keyframes moveAndFlash {
    		0% {
    			transform: translateX(0);
    			background-color: lightblue;
    		}
    		100% {
    			transform: translateX(100px);
    			background-color: lightcoral;
    		}
    	}
    }
    

    🔗 JavaScript

    js

    import { UIElement, toggleClass } from '@zeix/ui-element';
    
    class AnimatedComponent extends UIElement {
    	static consumedContexts = ['media-motion'];
    
    	connectedCallback() {
    		// Toggle classes based on 'media-motion' context
    		this.self.sync(toggleClass('motion', () => !this.get('media-motion')));
    		this.self.sync(toggleClass('no-motion', 'media-motion'));
    	}
    }
    
    AnimatedComponent.define('animated-component');
    

    🔗 Responsive TabList Example

    Content for Tab 1
    Content for Tab 2
    Content for Tab 3
    Source Code

    🔗 HTML


    🔗 CSS

    css

    			  tab-list {
    				display: flex;
    				flex-direction: column;
    			  
    				&.accordion .tab-button {
    				  display: none; /* Hide tab buttons in accordion mode */
    				}
    			  
    				.tab-button {
    				  cursor: pointer;
    				  padding: 10px;
    				  border: none;
    				  background: lightgray;
    				  transition: background-color 0.2s ease;
    				}
    			  
    				.tab-button.active {
    				  background-color: gray;
    				}
    			  }
    			  
    			  tab-panel {
    				display: none;
    			  
    				&.active {
    				  display: block;
    				}
    			  
    				&.collapsible {
    				  .panel-header {
    					cursor: pointer;
    					padding: 10px;
    					background-color: lightgray;
    					border: none;
    					outline: none;
    				  }
    			  
    				  .panel-header:hover {
    					background-color: darkgray;
    				  }
    			  
    				  .panel-header.active {
    					background-color: gray;
    				  }
    			  
    				  .panel-content {
    					display: none;
    					padding: 10px;
    				  }
    			  
    				  &.active .panel-content {
    					display: block;
    				  }
    				}
    			  }
    

    🔗 JavaScript

    js

    import { UIElement, toggleClass } from '@zeix/ui-element';
    
    // TabList Component
    class TabList extends UIElement {
    	static consumedContexts = ['media-viewport'];
    
    	connectedCallback() {
    		super.connectedCallback(); // Necessary to consume context
    
    		// Set 'accordion' signal based on viewport size
    		this.set('accordion', () => ['xs', 'sm'].includes(this.get('media-viewport')));
    
    		// Toggle 'accordion' class based on the signal
    		this.self.sync(toggleClass('accordion'));
    
    		// Pass 'collapsible' state to tab-panels based on 'accordion' state
    		this.all('tab-panel').pass({
    			collapsible: 'accordion'
    		});
    
    		// Handle tab clicks in normal tabbed mode
    		this.all('.tab-button').on('click', () => this.set('activeIndex', index))
    
    		// Set active tab-panel based on 'activeIndex'
    		this.all('tab-panel').sync((host, target, index) => {
    			toggleClass(
    				'active',
    				() => index === this.get('activeIndex')
    			)(host, target)
    		});
    	}
    }
    TabList.define('tab-list');
    
    // TabPanel Component
    class TabPanel extends UIElement {
    	static observedAttributes = ['collapsible'];
    
    	connectedCallback() {
    		super.connectedCallback(); // Ensure correct setup with context
    
    		// Handle expanding/collapsing if 'collapsible' is true
    		this.self.sync(toggleClass('collapsible', 'collapsible'));
    
    		if (this.get('collapsible')) {
    		const header = this.querySelector('.panel-header');
    		header.addEventListener('click', () => {
    			this.set('expanded', !this.get('expanded'));
    		});
    
    		this.self.sync(toggleClass('active', 'expanded'));
    		}
    	}
    }
    TabPanel.define('tab-panel');
    
    Image 1 Image 2 Image 3 Image 4 Image 5
    Source Code

    🔗 HTML

    html

    <media-context>
    	<responsive-image-gallery>
    	<img src="image1.jpg" alt="Image 1">
    	<img src="image2.jpg" alt="Image 2">
    	<img src="image3.jpg" alt="Image 3">
    	<img src="image4.jpg" alt="Image 4">
    	<img src="image5.jpg" alt="Image 5">
    	</responsive-image-gallery>
    </media-context>
    

    🔗 CSS

    css

    responsive-image-gallery {
    	display: flex;
    	flex-wrap: wrap;
    	gap: 10px;
    	padding: 10px;
    	transition: all 0.3s ease;
    	
    	&.landscape {
    		flex-direction: row;
    		justify-content: space-between;
    	}
    	
    	&.portrait {
    		flex-direction: column;
    	}
    	
    	img {
    		flex: 1 1 calc(20% - 10px); /* Creates a grid with up to 5 images per row */
    		max-width: calc(20% - 10px);
    		height: auto;
    		border: 2px solid transparent;
    		border-radius: 5px;
    		cursor: pointer;
    	}
    	
    	&.portrait img {
    		flex: 0 0 100%; /* Each image takes full width in slider mode */
    		max-width: 100%;
    		margin-bottom: 10px;
    	}
    }
    

    🔗 JavaScript

    js

    import { UIElement, toggleClass, effect, enqueue } from '@zeix/ui-element';
    import { MySlider } from './my-slider.js'; // Assume this is the existing MySlider component
    
    class ResponsiveImageGallery extends UIElement {
    static consumedContexts = ['media-orientation'];
    
    	connectedCallback() {
    		super.connectedCallback(); // Ensure correct setup with context
    
    		// Toggle classes based on orientation
    		this.self.sync(toggleClass('landscape', () => this.get('media-orientation') === 'landscape'));
    		this.self.sync(toggleClass('portrait', () => this.get('media-orientation') === 'portrait'));
    
    		// Dynamically wrap images in <my-slider> for portrait mode
    		effect(() => {
    			if (this.get('media-orientation') === 'portrait') {
    				if (!this.slider) {
    					this.slider = document.createElement('my-slider');
    					while (this.firstChild) {
    						this.slider.appendChild(this.firstChild);
    					}
    					enqueue(() => this.appendChild(this.slider), [this, 'add-slider']);
    				}
    			} else {
    				// Remove <my-slider> and display images as a grid in landscape mode
    				if (this.slider) enqueue(() => this.slider.remove(), [this.slider, 'remove-slider']);
    			}
    		});
    	}
    }
    ResponsiveImageGallery.define('responsive-image-gallery');