tempora-ui
Version:
TemporaUI is a developer-first, lightweight UI component library built with native Web Components.
990 lines (881 loc) • 27.7 kB
JavaScript
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;
}
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;
}
(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;">×</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