@razen-core/zenweb
Version:
A minimalist TypeScript framework for building reactive web applications with no virtual DOM
376 lines • 10.7 kB
JavaScript
/**
* ZenWeb UI Components
* Pre-built UI components with common patterns
*/
import { createElement } from '../dom.js';
import { vbox } from './layout.js';
import { svg } from './media.js';
/**
* Badge/Tag component
*/
export function badge(props, content) {
const variant = props.variant || 'primary';
const variantStyles = {
primary: { background: '#00ff88', color: '#000000' },
secondary: { background: '#6c757d', color: '#ffffff' },
success: { background: '#28a745', color: '#ffffff' },
warning: { background: '#ffc107', color: '#000000' },
danger: { background: '#dc3545', color: '#ffffff' }
};
const defaultStyle = {
display: 'inline-block',
padding: '0.25rem 0.75rem',
borderRadius: '9999px',
fontSize: '0.875rem',
fontWeight: '600',
...variantStyles[variant],
...(props.style || {})
};
return createElement('span', { ...props, style: defaultStyle }, content);
}
/**
* Card component
*/
export function card(props, ...children) {
const defaultStyle = {
background: '#0a0a0a',
border: '1px solid #333333',
borderRadius: '0.5rem',
padding: '1.5rem',
boxShadow: '0 1px 2px rgba(0, 255, 136, 0.1)',
...(props.style || {})
};
return vbox({ ...props, style: defaultStyle }, ...children);
}
/**
* Avatar component
*/
export function avatar(props) {
const size = props.size || '40px';
const defaultStyle = {
width: size,
height: size,
borderRadius: '50%',
objectFit: 'cover',
...(props.style || {})
};
return createElement('img', { ...props, style: defaultStyle });
}
/**
* Icon wrapper component - for SVG icons
*/
export function icon(props, svgContent) {
const size = props.size || '24px';
const defaultStyle = {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: size,
height: size,
...(props.style || {})
};
const container = createElement('span', { ...props, style: defaultStyle });
const svgEl = svg({
viewBox: '0 0 24 24',
width: size,
height: size,
fill: 'currentColor',
style: { display: 'block' }
}, svgContent);
container.appendChild(svgEl);
return container;
}
/**
* Tooltip component (simple implementation)
*/
export function tooltip(props, ...children) {
const tooltipText = props.text;
const container = createElement('div', {
...props,
style: {
position: 'relative',
display: 'inline-block',
...(props.style || {})
}
}, ...children);
const tooltipEl = createElement('div', {
style: {
position: 'absolute',
bottom: '100%',
left: '50%',
transform: 'translateX(-50%)',
padding: '0.5rem 0.75rem',
background: '#333333',
color: '#ffffff',
fontSize: '0.875rem',
borderRadius: '0.25rem',
whiteSpace: 'nowrap',
opacity: '0',
pointerEvents: 'none',
transition: 'opacity 0.2s',
marginBottom: '0.5rem',
zIndex: '1000'
}
}, tooltipText);
container.appendChild(tooltipEl);
container.addEventListener('mouseenter', () => {
tooltipEl.style.opacity = '1';
});
container.addEventListener('mouseleave', () => {
tooltipEl.style.opacity = '0';
});
return container;
}
/**
* Modal/Dialog component
*/
export function modal(props, ...children) {
const isOpen = props.open || false;
const overlay = createElement('div', {
style: {
position: 'fixed',
top: '0',
left: '0',
right: '0',
bottom: '0',
background: 'rgba(0, 0, 0, 0.5)',
display: isOpen ? 'flex' : 'none',
alignItems: 'center',
justifyContent: 'center',
zIndex: '9999',
...(props.style || {})
},
onClick: (e) => {
if (e.target === overlay && props.onClose) {
props.onClose();
}
}
});
const content = createElement('div', {
style: {
background: '#0a0a0a',
border: '1px solid #333333',
borderRadius: '0.5rem',
padding: '2rem',
maxWidth: '500px',
width: '90%',
maxHeight: '90vh',
overflow: 'auto'
}
}, ...children);
overlay.appendChild(content);
return overlay;
}
/**
* Dropdown menu component
*/
export function dropdown(props, trigger) {
const items = props.items || [];
let isOpen = false;
const container = createElement('div', {
style: {
position: 'relative',
display: 'inline-block',
...(props.style || {})
}
});
const triggerEl = createElement('div', {
style: { cursor: 'pointer' },
onClick: () => {
isOpen = !isOpen;
menuEl.style.display = isOpen ? 'block' : 'none';
}
}, trigger);
const menuEl = createElement('div', {
style: {
position: 'absolute',
top: '100%',
left: '0',
background: '#0a0a0a',
border: '1px solid #333333',
borderRadius: '0.5rem',
marginTop: '0.5rem',
minWidth: '200px',
display: 'none',
zIndex: '1000',
boxShadow: '0 4px 6px -1px rgba(0, 255, 136, 0.1)'
}
});
items.forEach((item, index) => {
const itemEl = createElement('div', {
style: {
padding: '0.75rem 1rem',
cursor: 'pointer',
borderBottom: index < items.length - 1 ? '1px solid #333333' : 'none',
transition: 'background 0.2s'
},
onClick: () => {
item.onClick();
isOpen = false;
menuEl.style.display = 'none';
},
onMouseEnter: (e) => {
e.target.style.background = '#1a1a1a';
},
onMouseLeave: (e) => {
e.target.style.background = 'transparent';
}
}, item.label);
menuEl.appendChild(itemEl);
});
container.appendChild(triggerEl);
container.appendChild(menuEl);
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!container.contains(e.target)) {
isOpen = false;
menuEl.style.display = 'none';
}
});
return container;
}
/**
* Toggle/Switch component
*/
export function toggle(props) {
let isChecked = props.checked || false;
const container = createElement('label', {
style: {
display: 'inline-flex',
alignItems: 'center',
cursor: 'pointer',
...(props.style || {})
}
});
const track = createElement('div', {
style: {
width: '48px',
height: '24px',
background: isChecked ? '#00ff88' : '#333333',
borderRadius: '9999px',
position: 'relative',
transition: 'background 0.2s'
}
});
const thumb = createElement('div', {
style: {
width: '20px',
height: '20px',
background: '#ffffff',
borderRadius: '50%',
position: 'absolute',
top: '2px',
left: isChecked ? '26px' : '2px',
transition: 'left 0.2s'
}
});
track.appendChild(thumb);
const input = createElement('input', {
type: 'checkbox',
checked: isChecked,
style: { display: 'none' },
onChange: (e) => {
isChecked = e.target.checked;
track.style.background = isChecked ? '#00ff88' : '#333333';
thumb.style.left = isChecked ? '26px' : '2px';
if (props.onChange) {
props.onChange(isChecked);
}
}
});
container.appendChild(input);
container.appendChild(track);
container.addEventListener('click', () => {
isChecked = !isChecked;
input.checked = isChecked;
track.style.background = isChecked ? '#00ff88' : '#333333';
thumb.style.left = isChecked ? '26px' : '2px';
if (props.onChange) {
props.onChange(isChecked);
}
});
return container;
}
/**
* Slider/Range component
*/
export function slider(props) {
const min = props.min || 0;
const max = props.max || 100;
const value = props.value || 50;
return createElement('input', {
type: 'range',
min,
max,
value,
...props,
style: {
width: '100%',
accentColor: '#00ff88',
...(props.style || {})
},
onInput: (e) => {
if (props.onChange) {
props.onChange(Number(e.target.value));
}
}
});
}
/**
* Progress bar component
*/
export function progressBar(props) {
const value = props.value || 0;
const max = props.max || 100;
const percentage = (value / max) * 100;
const container = createElement('div', {
style: {
width: '100%',
height: '8px',
background: '#333333',
borderRadius: '9999px',
overflow: 'hidden',
...(props.style || {})
}
});
const bar = createElement('div', {
style: {
width: `${percentage}%`,
height: '100%',
background: '#00ff88',
transition: 'width 0.3s ease'
}
});
container.appendChild(bar);
return container;
}
/**
* Spinner/Loading component
*/
export function spinner(props) {
const size = props.size || '40px';
return createElement('div', {
...props,
style: {
width: size,
height: size,
border: '3px solid #333333',
borderTop: '3px solid #00ff88',
borderRadius: '50%',
animation: 'spin 1s linear infinite',
...(props.style || {})
}
});
}
// Add keyframes for spinner animation
if (typeof document !== 'undefined') {
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
}
//# sourceMappingURL=components.js.map