UNPKG

@zeix/ui-element

Version:

UIElement - minimal reactive framework based on Web Components

276 lines (255 loc) โ€ข 10.9 kB
<!doctype html> <html> <head> <title>UIElement Docs โ€“ Best Practices & Patterns</title> <link rel="stylesheet" href="assets/main.css"> <script type="module" src="assets/main.js"></script> </head> <body> <header class="content-grid"> <h1 class="content">UIElement Docs <small>Version 0.9.4</small></h1> <nav class="breakout"> <ol> <li> <a href="index.html"> <span class="icon">๐Ÿ“˜</span> <strong>Introduction</strong> <small>Overview and key benefits of UIElement</small> </a> </li> <li> <a href="installation-setup.html"> <span class="icon">โš™๏ธ</span> <strong>Installation & Setup</strong> <small>How to install and set up the library</small> </a> </li> <li> <a href="core-concepts.html"> <span class="icon">๐Ÿงฉ</span> <strong>Core Concepts</strong> <small>Learn about signals, state, and reactivity</small> </a> </li> <li> <a href="detailed-walkthrough.html"> <span class="icon">๐Ÿ“‹</span> <strong>Detailed Walkthrough</strong> <small>Step-by-step guide to creating components</small> </a> </li> <li class="active"> <a href="best-practices-patterns.html"> <span class="icon">๐Ÿ’ก</span> <strong>Best Practices & Patterns</strong> <small>Tips for effective and scalable usage</small> </a> </li> <li> <a href="advanced-topics.html"> <span class="icon">๐Ÿš€</span> <strong>Advanced Topics</strong> <small>Diving deeper into contexts and performance</small> </a> </li> <li> <a href="examples-recipes.html"> <span class="icon">๐Ÿงช</span> <strong>Examples & Recipes</strong> <small>Sample components and practical use cases</small> </a> </li> <li> <a href="troubleshooting-faqs.html"> <span class="icon">โ“</span> <strong>Troubleshooting & FAQs</strong> <small>Common issues and frequently asked questions</small> </a> </li> <li> <a href="api-reference.html"> <span class="icon">๐Ÿ“š</span> <strong>API Reference</strong> <small>Detailed documentation of classes and methods</small> </a> </li> <li> <a href="contributing-development.html"> <span class="icon">๐Ÿค</span> <strong>Contributing & Development</strong> <small>How to contribute and set up the dev environment</small> </a> </li> <li> <a href="changelog-versioning.html"> <span class="icon">๐Ÿ“</span> <strong>Changelog & Versioning</strong> <small>Track changes and understand versioning</small> </a> </li> <li> <a href="licensing-credits.html"> <span class="icon">โš–๏ธ</span> <strong>Licensing & Credits</strong> <small>License details and acknowledgments</small> </a> </li> </ol> </nav> </header> <main> <section class="hero"> <h1>๐Ÿ’ก Best Practices & Patterns</h1> <p class="lead">Learn the best practices for building loosely coupled UIElement components, focusing on managing styles, states, and inter-component communication in a controlled and predictable way.</p> </section> <section> <h2>Composability Principle</h2> <p>Each component should be self-contained, managing its own state and styles, without relying directly on other components for its internal logic or presentation.</p> <h3>Self-Managed State & Styles</h3> <p>Components are responsible for their internal state and appearance, making them reusable and predictable. Parent components should not modify or style the internal DOM of their child components directly.</p> <p>Components should not access elements higher up in the DOM tree or in a different branch thereof.</p> </section> <section> <h2>Styling Components</h2> <p>Avoid styling inner elements of sub-components directly from parent components, as this would make the appearance of the inner component dependent on the styles of the outer component. Parent components may style only the wrapper of child components for layout purposes (margins, gap, flex and grid properties).</p> <h3>Scope Styles via Custom Element Name</h3> <p>Each component should have scoped styles via their custom element name, ensuring its styles don't leak out. Custom element names are unique within the document, making them ideal for scoping purposes. Aim for low specificity selectors like tag names, so it's easy to override with a single class when you need to differentiate.</p> <code-block language="css" copy-success="Copied!" copy-error="Error trying to copy to clipboard!"> <p class="meta"> <span class="language">css</span> </p> <pre class="language-css"><code>my-component { padding: 1rem; /* Only divs that are immediate children of my-component will be styled */ > div { background-color: lightgray; } }</code></pre> <input-button class="copy"> <button type="button" class="secondary small">Copy</button> </input-button> </code-block> <h3>Customize via Class or CSS Custom Properties</h3> <p>Components should allow reasonable variants via defined classes on the wrapper element or customizations via CSS custom properties.</p> <p>Classes allow parent components to choose between certain given variants.</p> <p>CSS custom properties allow parent components to influence the appearance of sub-components without directly styling their DOM internals.</p> <code-block language="css" copy-success="Copied!" copy-error="Error trying to copy to clipboard!"> <p class="meta"> <span class="language">css</span> </p> <pre class="language-css"><code>parent-component { --box-bg-color: red; --box-text-color: white; } /* Base message box appearance can be influenced using CSS custom properties */ message-box { background-color: var(--box-bg-color, lightgray); color: var(--box-text-color, black); /* While pre-defined variant with class "success" always comes with a fixed color scheme */ &.success { background-color: green; color: white; } }</code></pre> <input-button class="copy"> <button type="button" class="secondary small">Copy</button> </input-button> </code-block> </section> <section> <h2>Passing State Down</h2> <p>Parent components can control sub-components by setting their publicly accessible signals. Use the <code>pass()</code> function to pass state directly and share signals.</p> <h3>Passing State with pass()</h3> <p>Use the <code>pass()</code> function to pass a state from a parent component to a child component, keeping the state synchronized.</p> <code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!"> <p class="meta"> <span class="language">js</span> </p> <pre class="language-js"><code>class ParentComponent extends UIElement { connectedCallback() { this.set('parentColor', 'blue'); this.pass('parentColor', 'child-component', 'color'); } } ParentComponent.define('parent-component');</code></pre> <input-button class="copy"> <button type="button" class="secondary small">Copy</button> </input-button> </code-block> <code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!"> <p class="meta"> <span class="language">js</span> </p> <pre class="language-js"><code>class ChildComponent extends UIElement { connectedCallback() { this.first('.box').sync(setStyle('background-color', 'color')); } } ChildComponent.define('child-component');</code></pre> </code-block> </section> <section> <h2>Bubbling Up State with Custom Events</h2> <p>When a child component doesn't have full context for handling state changes, it can dispatch custom events that bubble up to parent components using <code>emit()</code>.</p> <h3>Dispatching Custom Events with <code>emit()</code></h3> <p>Use the <code>emit()</code> method to dispatch custom events to notify parent components of changes.</p> <code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!"> <p class="meta"> <span class="language">js</span> </p> <pre class="language-js"><code>// In child component this.emit('change', { detail: { value: this.get('value') } });</code></pre> </code-block> <h3>Handling Custom Events in Parent Components</h3> <p>Parent components can listen for custom events from child components and respond accordingly.</p> <code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!"> <p class="meta"> <span class="language">js</span> </p> <pre class="language-js"><code>// In parent component this.first('child-component').on('change', (event) => { console.log('Received change event:', event.detail.value); // Handle state changes });</code></pre> </code-block> <h3>Practical Example</h3> <p>The child component emits a <code>change</code> event whenever an internal signal changes, and the parent listens and handles it.</p> <code-block> <pre><code>class ChildComponent extends UIElement { connectedCallback() { this.first('input').on('input', (event) => { this.set('value', event.target.value); this.emit('change', { detail: { value: event.target.value } }); }); } }</code></pre> </code-block> <code-block> <pre><code>class ParentComponent extends UIElement { connectedCallback() { this.first('child-component').on('change', (event) => { console.log('Child value changed:', event.detail.value); // Update parent state or perform an action }); } }</code></pre> </code-block> <code-block> <pre><code>&lt;parent-component&gt; &lt;child-component&gt;&lt;/child-component&gt; &lt;/parent-component&gt;</code></pre> </code-block> <h3>Best Practices for Custom Events</h3> <ul> <li><strong>Emit only when necessary</strong>: Emit events to notify parents of significant state changes.</li> <li><strong>Consistent event names</strong>: Use clear, meaningful names for custom events.</li> <li><strong>Use bubbling carefully</strong>: Understand the scope of event bubbling and which ancestor components may handle the event.</li> </ul> </section> <section> <h2>Conclusion & Next Steps</h2> <p>By adhering to best practices for composability, styling, and state management, you can build efficient and loosely coupled <code>UIElement</code> components. Explore "Advanced Topics" to delve deeper into context and more complex patterns.</p> </section> </main> </body> </html>