UNPKG

halfcab

Version:

A simple universal JavaScript framework focused on making use of es2015 template strings to build components.

149 lines (138 loc) 3.83 kB
import halfcab, { html, css, injectMarkdown, defineRoute, gotoRoute, updateState, formField, formIsValid, getRouteComponent, state, nextTick } from '../halfcab.mjs' // Some demo styles using the css tag helper const styles = css` .container { max-width: 880px; margin: 0 auto; } .counter { display: inline-flex; align-items: center; gap: 8px; padding: 8px 12px; border-radius: 8px; border: 1px solid #8884; } .navLink.active { font-weight: 600; text-decoration: underline; } .card { padding: 12px; border: 1px solid #8884; border-radius: 8px; background: #00000008; margin: 12px 0; } ` // Demo components const Home = (args) => html` <section class="${styles.container}"> <h2>Welcome to halfcab + lit demo</h2> <div class="${styles.card}"> ${injectMarkdown(` ### Features shown - Client-side routing - Global state + rerender - CSS via css helper - Form helpers and validation - Markdown injection `)} </div> <div class="${styles.card}"> <h3>Counter</h3> <div class="${styles.counter}"> <button onclick=${() => updateState({ count: (state.count || 0) - 1 })} aria-label="decrement"></button> <strong>${state.count || 0}</strong> <button onclick=${() => updateState({ count: (state.count || 0) + 1 })} aria-label="increment">+</button> </div> </div> </section> ` const About = () => html` <section class="${styles.container}"> <h2>About</h2> <p>This demo page is here to help you quickly test halfcab components and interactions.</p> <p>Source: <code>example/</code> directory.</p> </section> ` const Form = () => { // A simple form using formField + validation state.form = state.form || { name: '', email: '' } const bindName = formField(state.form, 'name') const bindEmail = formField(state.form, 'email') const submit = (e) => { e.preventDefault() if (formIsValid(state.form)) { alert(`Submitted: ${state.form.name} <${state.form.email}>`) } else { alert('Form is invalid. Please fix errors.') } } return html` <section class="${styles.container}"> <h2>Form</h2> <form onsubmit=${submit} novalidate> <div class="${styles.card}"> <label> Name <input type="text" required placeholder="Ada Lovelace" oninput=${bindName} value="${state.form.name}" /> </label> </div> <div class="${styles.card}"> <label> Email <input type="email" required placeholder="ada@example.com" oninput=${bindEmail} value="${state.form.email}" /> </label> </div> <button type="submit">Submit</button> </form> </section> ` } // Define routes defineRoute({ path: '/', title: 'Home', component: Home }) defineRoute({ path: '/about', title: 'About', component: About }) defineRoute({ path: '/form', title: 'Form', component: Form }) // Application shell const Shell = () => { const pathname = (state.router && state.router.pathname) || '/' const NavLink = (to, label) => html` <a class="navLink ${pathname === to ? 'active' : ''}" href="${to}" onclick=${(e) => { e.preventDefault(); gotoRoute(to) }}>${label}</a> ` const Current = getRouteComponent(pathname) || Home return html` <header> ${NavLink('/', 'Home')} ${NavLink('/about', 'About')} ${NavLink('/form', 'Form')} </header> <main> ${Current({})} </main> ` } // Boot the app halfcab({ el: '#app', components: Shell }).then(() => { // Ensure initial render after possible async state init nextTick(() => {}) }).catch((err) => { console.error('Failed to start halfcab demo:', err) })