UNPKG

rynex

Version:

A minimalist TypeScript framework for building reactive web applications with no virtual DOM

562 lines 17 kB
/** * Rynex 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 || {}) } }); } /** * Tabs component */ export function tabs(props) { const { tabs: tabsData, defaultIndex = 0, onChange, ...restProps } = props; let activeIndex = defaultIndex; const container = createElement('div', { ...restProps, class: `tabs ${restProps.class || ''}`, style: { display: 'flex', flexDirection: 'column', ...(restProps.style || {}) } }); // Tab headers const tabHeaders = createElement('div', { class: 'tab-headers', style: { display: 'flex', borderBottom: '1px solid #333333', gap: '0.5rem' } }); // Tab content container const tabContent = createElement('div', { class: 'tab-content', style: { padding: '1rem' } }); const updateActiveTab = (index) => { activeIndex = index; tabContent.innerHTML = ''; tabContent.appendChild(tabsData[index].content); // Update header styles Array.from(tabHeaders.children).forEach((header, i) => { const headerEl = header; if (i === index) { headerEl.style.borderBottom = '2px solid #00ff88'; headerEl.style.color = '#00ff88'; } else { headerEl.style.borderBottom = '2px solid transparent'; headerEl.style.color = '#b0b0b0'; } }); if (onChange) { onChange(index); } }; // Create tab headers tabsData.forEach((tab, index) => { const header = createElement('button', { class: 'tab-header', style: { padding: '0.75rem 1rem', background: 'transparent', border: 'none', borderBottom: index === activeIndex ? '2px solid #00ff88' : '2px solid transparent', color: index === activeIndex ? '#00ff88' : '#b0b0b0', cursor: 'pointer', fontSize: '1rem', transition: 'all 0.2s' }, onClick: () => updateActiveTab(index), onMouseEnter: (e) => { if (index !== activeIndex) { e.target.style.color = '#ffffff'; } }, onMouseLeave: (e) => { if (index !== activeIndex) { e.target.style.color = '#b0b0b0'; } } }); header.textContent = tab.label; tabHeaders.appendChild(header); }); // Set initial content tabContent.appendChild(tabsData[activeIndex].content); container.appendChild(tabHeaders); container.appendChild(tabContent); return container; } /** * Accordion component */ export function accordion(props) { const { items, allowMultiple = false, defaultOpen = [], ...restProps } = props; const openIndices = new Set(defaultOpen); const container = createElement('div', { ...restProps, class: `accordion ${restProps.class || ''}`, style: { display: 'flex', flexDirection: 'column', gap: '0.5rem', ...(restProps.style || {}) } }); const toggleItem = (index, itemContent, icon) => { const isOpen = openIndices.has(index); if (isOpen) { openIndices.delete(index); itemContent.style.display = 'none'; icon.style.transform = 'rotate(0deg)'; } else { if (!allowMultiple) { // Close all other items openIndices.clear(); Array.from(container.children).forEach((child, i) => { const content = child.querySelector('.accordion-content'); const itemIcon = child.querySelector('.accordion-icon'); if (content && itemIcon) { content.style.display = 'none'; itemIcon.style.transform = 'rotate(0deg)'; } }); } openIndices.add(index); itemContent.style.display = 'block'; icon.style.transform = 'rotate(180deg)'; } }; items.forEach((item, index) => { const itemContainer = createElement('div', { class: 'accordion-item', style: { border: '1px solid #333333', borderRadius: '0.5rem', overflow: 'hidden' } }); const icon = createElement('span', { class: 'accordion-icon', style: { transition: 'transform 0.2s', transform: openIndices.has(index) ? 'rotate(180deg)' : 'rotate(0deg)' } }); icon.textContent = '▼'; const header = createElement('button', { class: 'accordion-header', style: { width: '100%', padding: '1rem', background: '#0a0a0a', border: 'none', color: '#ffffff', cursor: 'pointer', display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: '1rem', textAlign: 'left' }, onMouseEnter: (e) => { e.currentTarget.style.background = '#1a1a1a'; }, onMouseLeave: (e) => { e.currentTarget.style.background = '#0a0a0a'; } }); const title = createElement('span'); title.textContent = item.title; header.appendChild(title); header.appendChild(icon); const content = createElement('div', { class: 'accordion-content', style: { padding: '1rem', display: openIndices.has(index) ? 'block' : 'none', background: '#000000' } }); content.appendChild(item.content); header.addEventListener('click', () => toggleItem(index, content, icon)); itemContainer.appendChild(header); itemContainer.appendChild(content); container.appendChild(itemContainer); }); return container; } // 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