UIElement Docs Version 0.11.0

🍽️ 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

    Max. 20 characters

    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');
    

    Responsive Image Gallery Example

    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');