UNPKG

tempora-ui

Version:

TemporaUI is a developer-first, lightweight UI component library built with native Web Components.

990 lines (881 loc) 27.7 kB
class CInput extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); const label = this.getAttribute('label') || ''; const isDisabled = this.hasAttribute('disabled'); const hasError = this.hasAttribute('error'); const isReadOnly = this.hasAttribute('readonly'); const value = this.getAttribute('value') || ''; const hint = this.getAttribute('hint') || ''; this.shadowRoot.innerHTML = ` <style> :host { display: flex; flex-direction: column; justify-content: center; position: relative; } .input-container{ display: flex; position: relative; flex-direction: column; row-gap: 5px; } .wrapper{ display: flex; flex-direction: column; justify-content: center; position: relative; min-width: 200px; min-height: 20px; cursor: ${isDisabled || isReadOnly ? ' not-allowed' : 'pointer'}; opacity: ${isDisabled ? '.4' : '1'}; background-color: ${isReadOnly ? 'transparent' : 'var(--white)'}; border: 1px ${hasError ? 'var(--error)' : 'var(--black-32)'} solid; padding: 12px; border-radius: 6px; transition: all .3s ease; outline: solid; outline-width: 2px; outline-color: transparent; } .input-hint{ display: ${hint ? 'flex' : 'none'}; font-size: 12px; color: ${hasError ? 'var(--error)' : 'var(--black-64)'} } label { position: absolute; top: 13px; left: 12px; color: ${hasError ? 'var(--error)' : 'var(--black)'}; font-size: 14px; transition: all 0.3s ease; pointer-events: none; opacity: .64 } input { position: absolute; border: none; top: 20px; left: 10px; outline: none; color: white; font-size: 16px; color: var(--black); background-color: transparent; pointer-events: none } input:focus + label, input:not(:placeholder-shown) + label { top: 3px; font-size: 12px; } .wrapper:focus-within{ outline-color: ${hasError ? 'var(--error-easy)' : 'var(--tempora-primary-300)'}; border-color: ${hasError ? 'var(--error)' : 'var(--tempora-primary)'} } input:focus { top: 20px } </style> <div class="input-container"> <div class="wrapper"> <input type="text" id="c-input-field" placeholder="" value="${value}" ${isDisabled || isReadOnly ? 'disabled="true"' : ''} /> <label for="c-input-field" >${label}</label> </div> <span class="input-hint">${hint}</span> </div> `; this.addEventListener('click', () => { const input = this.shadowRoot.querySelector('input'); input.focus(); }); } } customElements.define("c-input", CInput); class CButton extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); const type = this.getAttribute('type') || 'primary'; const size = this.getAttribute('size') || 'M'; const isDisabled = this.hasAttribute('disabled'); const loading = this.hasAttribute('loading'); this.shadowRoot.innerHTML = ` <style> :host { display: inline-block; } button { all: unset; cursor: ${isDisabled ? 'not-allowed' : 'pointer'}; font-family: var(--tempora-font, sans-serif); border-radius: 8px; font-weight: 500; text-align: center; transition: all 0.3s ease; padding: 0.75rem 1.5rem; border: 2px solid transparent; opacity: ${isDisabled ? '.3' : '1'}; display: flex; flex-direction: row; align-items: center; justify-content: center; column-gap: 5px; } .button-text{ display: ${loading ? 'none' : 'flex'}; flex-direction: row; column-gap: 5px; align-items: center } /* Size variants */ .size-L { font-size: 1.1rem; padding: 1rem 2rem; } .size-M { font-size: 1rem; padding: 0.75rem 1.5rem; } .size-S { font-size: 0.875rem; padding: 0.5rem 1rem; } /* Type variants */ .primary { background-color: var(--tempora-primary); color: white; border: none; } .primary:hover { background-color: ${!isDisabled ? 'var(--tempora-primary-hover)' : 'var(--tempora-primary)'}; } .primary:active { background-color: ${!isDisabled ? 'var(--tempora-primary-active)' : 'var(--tempora-primary)'}; } .flat { background: transparent; color: var(--tempora-primary) !important; } .flat:hover { background: ${!isDisabled ? 'var(--tempora-primary-200)' : 'transparent'}; } .flat:active { background: ${!isDisabled ? 'var(--tempora-primary-300)' : 'transparent'}; } .outline { background: transparent; border: 2px solid var(--tempora-primary); color: var(--tempora-primary); } .outline:hover { background: ${!isDisabled ? 'var(--tempora-primary-200)' : 'transparent'}; } .outline:active { background: ${!isDisabled ? 'var(--tempora-primary-300)' : 'transparent'}; } .spinner { width: 16px; height: 16px; border: 2px solid ${type == 'flat' ? 'var(--tempora-primary)' : type == 'outline' ? 'var(--tempora-primary)' : 'white'}; border-top: 2px solid transparent; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> <button class="${type} size-${size}" ${isDisabled ? 'disabled' : ''}> <span class="button-text" ${loading ? 'hidden' : ''}> <slot></slot> </span> <span class="spinner" ${loading ? '' : 'hidden'}></span> </button> `; } connectedCallback() { this.shadowRoot.querySelector("button").addEventListener("click", () => { this.dispatchEvent(new Event("click", { bubbles: true, composed: true })); }); } } customElements.define('c-button', CButton); class CCheckbox extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); const label = this.getAttribute("label") || ""; const isChecked = this.hasAttribute('checked'); const isDisabled = this.hasAttribute("disabled"); const hasError = this.hasAttribute('error'); this.shadowRoot.innerHTML = ` <style> :host { display: inline-flex; align-items: center; cursor: pointer; gap: 4px; user-select: none; opacity: ${isDisabled ? '0.5' : '1'} } input[type="checkbox"] { appearance: none; width: 20px; height: 20px; border: 2px solid ${hasError ? 'none' : 'var(--tempora-primary-300)'}; border-radius: 4px; display: grid; place-content: center; background-color: ${hasError ? 'var(--error-easy)' : 'var(--tempora-primary-100)'}; transition: all 0.2s ease; } input[type="checkbox"]:checked { background-color: var(--tempora-primary); border-color: var(--tempora-primary) } input[type="checkbox"]::before { content: "✓"; color: var(--white); font-size: 14px; transform: scale(0); transition: transform 0.2s ease; } input:checked + span { color: var(--black) } input[type="checkbox"]:checked::before { transform: scale(1); } input:checked:hover { background-color: ${!isDisabled ? 'var(--tempora-primary-active)' : 'var(--tempora-primary)'}; border-color: ${!isDisabled ? 'var(--tempora-primary-active)' : 'var(--tempora-primary)'}; } input:hover{ background-color: ${hasError ? 'var(--error-easy)' : 'var(--tempora-primary-200)'}; border-color: var(--tempora-primary-300) } label { color: ${hasError ? 'var(--error)' : 'var(--black)'}; font-size: 15px; display: flex; align-items: center; gap: 5px } span{ font-size: 14px } </style> <label> <input type="checkbox" ${isChecked ? 'checked' : ''} ${isDisabled ? 'disabled' : ''}> <span>${label}</span> </label> `; } } customElements.define("c-checkbox", CCheckbox); class CRadioGroup extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } connectedCallback() { const name = this.getAttribute('name') || 'group'; const isSelected = this.hasAttribute('selected'); const isDisabled = this.hasAttribute('disabled'); const options = Array.from(this.querySelectorAll('option')); let radiosHtml = options.map((opt, idx) => ` <label class="radio-wrapper"> <input type="radio" name="${name}" value="${opt.getAttribute('value')}" id="${name}-${idx}" ${isSelected ? 'checked' : ''} ${isDisabled ? 'disabled' : ''} > <span class="custom-dot"></span> <span class="radio-label">${opt.textContent}</span> </label> `).join(''); this.shadowRoot.innerHTML = ` <style> :host { display: flex; flex-direction: column; gap: 0.5rem; } .radio-wrapper { display: flex; flex-direction: row; align-items: center; gap: 4px; cursor: pointer; color: var(--black); position: relative; opacity: ${isDisabled ? '.5' : '1'} } .radio-label{ font-size: 15px } input[type="radio"] { appearance: none; width: 20px; height: 20px; border: 2px solid var(--tempora-primary-300); border-radius: 50%; background: var(--tempora-primary-200); cursor: pointer; position: relative; transition: all 0.3s ease; } input[type="radio"]:checked { background: var(--tempora-primary); border-color: var(--tempora-primary); } input[type="radio"]:not(:checked):hover { background: ${!isDisabled ? 'var(--tempora-primary-100)' : 'var(--tempora-primary-200)'}; border-color: var(--tempora-primary-300); } .custom-dot { position: absolute; left: 9px; top: 7px; width: 12px; height: 12px; border-radius: 50%; background: white; transform: scale(0); transition: transform 0.2s ease; pointer-events: none; } input[type="radio"]:checked + .custom-dot { transform: scale(1); } </style> ${radiosHtml} `; } } customElements.define('c-radio-group', CRadioGroup); class CToggle extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); const isDisabled = this.hasAttribute('disabled'); const isToggled = this.hasAttribute('toggled'); const label = this.getAttribute('label') || ''; const description = this.getAttribute('description') || ''; this.shadowRoot.innerHTML = ` <style> :host { display: flex; gap: 5px; align-items: start; } .toggle { position: relative; width: 48px; height: 24px; background: var(--light-blue-200); border-radius: 50px; cursor: pointer; transition: background 0.3s ease; opacity: ${isDisabled ? '.8' : '1'}; } .toggle:hover{ background-color: ${!isDisabled ? 'var(--light-blue-300)' : 'var(--light-blue-200)'}; } .circle { position: absolute; top: 4px; left: 4px; width: 16px; height: 16px; background: var(--white); border-radius: 50%; transition: transform 0.3s ease; } .off-circle{ position: absolute; top: 6px; right: 6px; width: 10px; border-radius: 10px; height: 10px; border: 1px var(--white) solid; } input { display: none; } input:checked + .toggle { background: var(--tempora-primary); } input:checked + .toggle:hover{ background-color: ${!isDisabled ? 'var(--tempora-primary-hover)' : 'var(--tempora-primary)'} } input:checked + .toggle .circle { transform: translateX(24px); } .label_description{ display: flex; flex-direction: column; padding-top: 4px; color: var(--black) } .description{ color: var(--black-64); font-size: 14px } </style> <label> <input type="checkbox" ${isToggled ? 'checked' : ''} ${isDisabled ? 'disabled' : ''} /> <div class="toggle"> <div class="circle"></div> <span class="off-circle"></span> </div> </label> <div class="label_description"> <span class="label">${label ? label : ''}</span> <span class="description">${label && description ? description : ''}</span> </div> `; this.input = this.shadowRoot.querySelector('input'); } get checked() { return this.input.checked; } set checked(val) { this.input.checked = val; } } customElements.define('c-toggle', CToggle); class CText extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.shadowRoot.innerHTML = ` <style> :host { display: block; margin: 0; } .text { margin: 0; } /* Paragraphs */ .p1 { font-size: 20px; line-height: 28px; font-weight: 400; } .p2 { font-size: 18px; line-height: 26px; font-weight: 400; } .p3 { font-size: 16px; line-height: 24px; font-weight: 400; } /* Semibold override for paragraphs */ .semibold { font-weight: 600; } /* Headings */ .h1 { font-size: 72px; line-height: 80px; font-weight: 700; } .h2 { font-size: 60px; line-height: 68px; font-weight: 700; } .h3 { font-size: 48px; line-height: 56px; font-weight: 700; } .h4 { font-size: 40px; line-height: 48px; font-weight: 700; } .h5 { font-size: 32px; line-height: 40px; font-weight: 700; } .h6 { font-size: 28px; line-height: 36px; font-weight: 700; } .h7 { font-size: 24px; line-height: 32px; font-weight: 700; } @media (max-width: 768px) { .h1 { font-size: 48px; line-height: 56px; } .h2 { font-size: 42px; line-height: 48px; } .h3 { font-size: 36px; line-height: 44px; } .h4 { font-size: 32px; line-height: 40px; } .h5 { font-size: 28px; line-height: 36px; } .h6 { font-size: 26px; line-height: 34px; } .h7 { font-size: 24px; line-height: 32px; } } </style> <span class="text"><slot></slot></span> `; } connectedCallback() { this.updateType(); } static get observedAttributes() { return ["type", "semibold"]; } attributeChangedCallback() { this.updateType(); } updateType() { const type = this.getAttribute("type") || "p1"; const semibold = this.hasAttribute("semibold"); const textElement = this.shadowRoot.querySelector(".text"); // Reset classes textElement.className = "text"; textElement.classList.add(type); if (["p1", "p2", "p3"].includes(type) && semibold) { textElement.classList.add("semibold"); } } } customElements.define("c-text", CText); class CBadge extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); const variant = this.getAttribute("variant") || "primary"; const size = this.getAttribute("size") || "M"; this.shadowRoot.innerHTML = ` <style> :host { display: inline-flex; align-items: center; border-radius: 12px; font-weight: 500; gap: 6px; padding: 0 10px; user-select: none; } .badge { display: inline-flex; align-items: center; gap: 6px; color: ${variant === 'outline' ? 'var(--tempora-primary)' : variant == 'other' ? 'var(--black)' : 'var(--white)'}; background-color: ${variant === 'other' ? 'var(--attention)' : variant === 'blur' ? 'var(--black-48)' : variant === 'outline' ? 'transparent' : 'var(--tempora-primary)'}; border: 1px ${variant === 'outline' ? 'var(--tempora-primary)' : 'transparent'} solid !important; font-size: ${size === 'S' ? '13px' : '16px'}; padding-inline: ${size == "M" ? '16px' : '8px'}; padding-block: ${size == 'M' ? '7px' : '4px'}; border-radius: 100px; backdrop-filter: ${variant == 'blur' ? 'blur(3px)' : 'none'} } ::slotted(img[slot="icon"]) { width: 16px; height: 16px; } </style> <span class="badge"> <slot name="icon"></slot> <slot></slot> </span> `; } } customElements.define("c-badge", CBadge); class CAlert extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); const variant = this.getAttribute("variant") || "info"; this.shadowRoot.innerHTML = ` <style> :host { display: block; font-family: sans-serif; } .alert { display: flex; align-items: flex-start; padding: 12px 16px; border-radius: 8px; gap: 12px; position: relative; font-size: 14px; } .icon { flex-shrink: 0; width: 20px; height: 20px; } .message { flex: 1; } .close { background: none; border: none; cursor: pointer; font-size: 16px; color: inherit; } /* Variants */ .info { background-color: #e6f0ff; color: #004085; border: 1px #00408587 solid; } .success { background-color: #e6ffed; color: var(--success); border: 1px var(--success) solid; } .warning { background-color: #fff3cd; color: #856404; border: 1px #85640487 solid } .error { background-color: #f8d7da; color: #721c24; border: 1px #721c2487 solid } .neutral { background-color: var(--light-blue-100); color: #343a40; border: 1px #343a4030 solid } </style> <div class="alert ${variant}"> <slot name="icon" class="icon"></slot> <div class="message"><slot></slot></div> <button class="close" aria-label="Close" style="display:none;">&times;</button> </div> `; } connectedCallback() { if (this.hasAttribute("dismissable")) { this.shadowRoot.querySelector(".close").style.display = "block"; this.shadowRoot.querySelector(".close").addEventListener("click", () => { this.remove(); }); } } static get observedAttributes() { return ["variant"]; } } customElements.define("c-alert", CAlert); class CProgress extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); } static get observedAttributes() { return ["value", "segments", "variant"]; } attributeChangedCallback() { this.render(); } connectedCallback() { this.render(); } render() { const value = parseInt(this.getAttribute("value") || "0", 10); const variant = this.getAttribute("variant") || "simple"; const color = this.getAttribute('color') || ''; const segments = this.getAttribute("segments"); const segmentData = segments ? JSON.parse(segments) : []; const styles = ` :host { display: block; width: 100%; } .bar { height: 12px; border-radius: 50px; background: var(--black-16); position: relative; overflow: hidden; } .fill { height: 100%; background: ${color ? color : 'var(--tempora-primary)'}; width: ${value}%; display: flex; align-items: center; transition: width 0.3s ease; position: relative; } .inline-label { color: white; font-size: 10px; position: absolute; top: 0; left: 50%; } .multi { display: flex; height: 100%; } .multi-segment { height: 100%; } `; let content = ""; if (variant === "multicolor") { const total = segmentData.reduce((sum, seg) => sum + seg.value, 0); content = ` <div class="bar"> <div class="multi"> ${segmentData .map( (seg) => ` <div class="multi-segment" style="width:${(seg.value / total) * 100}%; background:${seg.color}"></div> ` ) .join("")} </div> </div> `; } else if (variant === "inline") { content = ` <div class="bar"> <div class="fill"> <div class="inline-label">${value}%</div> </div> </div> `; } else { content = ` <div class="bar"> <div class="fill"></div> </div> `; } this.shadowRoot.innerHTML = `<style>${styles}</style>${content}`; } } customElements.define("c-progress", CProgress); function setupPopupTriggers() { document.querySelectorAll('[open-popup]').forEach(trigger => { trigger.addEventListener('click', () => { console.log('[ Custom Button Event Fired ]'); const targetId = trigger.getAttribute('open-popup'); const popup = document.getElementById(targetId); if (popup && popup.open); popup.open(); }); }); document.querySelectorAll('[close-popup]').forEach(trigger => { trigger.addEventListener('click', () => { const targetId = trigger.getAttribute('close-popup'); const popup = document.getElementById(targetId); if (popup && popup.close) popup.close(); }); }); } function iniToolTips(){ console.log("%c ToolTip Initialized 🚀", "color:rgb(107, 205, 244); font-size: 11px;"); const tooltipAttrPrefixes = ["ctool-tip-top", "ctool-tip-right", "ctool-tip-bottom", "ctool-tip-left"]; tooltipAttrPrefixes.forEach(attr => { document.querySelectorAll(`[${attr}]`).forEach(el => { const text = el.getAttribute(attr); const position = attr.split("ctool-tip-")[1]; // Create tooltip element const tooltip = document.createElement("div"); tooltip.className = `dynamic-tooltip ${position}`; tooltip.textContent = text; // Basic styles Object.assign(tooltip.style, { position: "absolute", background: "#333", color: "#fff", padding: "6px 10px", fontSize: "12px", borderRadius: "4px", whiteSpace: "nowrap", opacity: "0", pointerEvents: "none", transition: "opacity 0.2s ease", zIndex: 1000, }); el.style.position = "relative"; el.appendChild(tooltip); el.addEventListener("mouseenter", () => { tooltip.style.opacity = "1"; }); el.addEventListener("mouseleave", () => { tooltip.style.opacity = "0"; }); // Tooltip positioning logic el.addEventListener("mousemove", () => { switch (position) { case "top": tooltip.style.bottom = `${el.offsetHeight + 6}px`; tooltip.style.left = "50%"; tooltip.style.transform = "translateX(-50%)"; break; case "right": tooltip.style.top = "50%"; tooltip.style.left = `${el.offsetWidth + 6}px`; tooltip.style.transform = "translateY(-50%)"; break; case "bottom": tooltip.style.top = `${el.offsetHeight + 6}px`; tooltip.style.left = "50%"; tooltip.style.transform = "translateX(-50%)"; break; case "left": tooltip.style.top = "50%"; tooltip.style.right = `${el.offsetWidth + 6}px`; tooltip.style.transform = "translateY(-50%)"; break; } }); }); }); } const TemporaUI = { version: "1.0.0", init() { console.log("%c TemporaUI Initialized 🚀", "color: #4E41FF; font-size: 20px;"); } }; // Make it global (this fixes your issue) window.TemporaUI = TemporaUI; window.addEventListener('DOMContentLoaded', () => { iniToolTips(); setupPopupTriggers(); }); // Auto-init (optional) TemporaUI.init(); export { TemporaUI as default }; //# sourceMappingURL=tempora.js.map