lightview
Version:
A reactive UI library with features of Bau, Juris, and HTMX plus safe LLM UI generation
165 lines (138 loc) • 5.91 kB
JavaScript
/**
* Lightview Button Component (DaisyUI)
* @see https://daisyui.com/components/button/
*/
import '../daisyui.js';
/**
* Button Component
* @param {Object} props
* @param {string} props.color - 'primary' | 'secondary' | 'accent' | 'neutral' | 'info' | 'success' | 'warning' | 'error' | 'ghost' | 'link'
* @param {string} props.size - 'xs' | 'sm' | 'md' | 'lg'
* @param {string} props.variant - 'outline' | 'soft' | 'dash' | 'wide' | 'block' | 'square' | 'circle'
* @param {boolean} props.disabled - Disable the button
* @param {boolean} props.loading - Show loading state
* @param {boolean} props.active - Force active state
* @param {boolean} props.glass - Glass morphism effect
* @param {boolean} props.noAnimation - Disable click animation
* @param {boolean} props.useShadow - Render in Shadow DOM with isolated DaisyUI styles
* @param {...children} children - Button content
*/
const Button = (props = {}, ...children) => {
const { tags } = globalThis.Lightview || {};
const LVX = globalThis.LightviewX || {};
if (!tags) {
console.error('Lightview not found');
return null;
}
const { button, div, span, shadowDOM } = tags;
const {
color,
size,
variant,
disabled = false,
loading = false,
active = false,
glass = false,
noAnimation = false,
useShadow,
theme, // Explicit theme override
class: className = '',
...rest
} = props;
const getClassList = () => {
const classes = ['btn'];
// Color
if (['primary', 'secondary', 'accent', 'neutral', 'info', 'success', 'warning', 'error', 'ghost', 'link'].includes(color)) classes.push(`btn-${color}`);
// Size
if (size) classes.push(`btn-${size}`);
// Variant
if (variant === 'outline') classes.push('btn-outline');
else if (variant === 'soft') classes.push('btn-soft');
else if (variant === 'dash') classes.push('btn-dash');
else if (variant === 'wide') classes.push('btn-wide');
else if (variant === 'block') classes.push('btn-block');
else if (variant === 'square') classes.push('btn-square');
else if (variant === 'circle') classes.push('btn-circle');
// States
const isDisabled = typeof disabled === 'function' ? disabled() : disabled;
const isLoading = typeof loading === 'function' ? loading() : loading;
const isActive = typeof active === 'function' ? active() : active;
if (isDisabled) classes.push('btn-disabled');
if (isActive) classes.push('btn-active');
if (glass) classes.push('glass');
if (noAnimation) classes.push('no-animation');
if (className) classes.push(className);
return classes.join(' ');
};
const buildContent = () => {
const isLoading = typeof loading === 'function' ? loading() : loading;
if (isLoading) {
return [
tags.span({ class: 'loading loading-spinner' }),
...children
];
}
return children;
};
if (color && !['primary', 'secondary', 'accent', 'neutral', 'info', 'success', 'warning', 'error', 'ghost', 'link'].includes(color)) {
// set the background color using style
rest.style = rest.style || '';
if (typeof rest.style === 'object') {
rest.style.backgroundColor = color;
} else {
rest.style += `;background-color: ${color};`;
}
}
const buttonEl = button({
class: typeof disabled === 'function' || typeof loading === 'function' || typeof active === 'function'
? () => getClassList()
: getClassList(),
disabled: typeof disabled === 'function'
? () => disabled() || (typeof loading === 'function' ? loading() : loading)
: disabled || loading,
...rest
}, ...(typeof loading === 'function' ? [() => buildContent()] : buildContent()));
// Check if we should use shadow DOM
let usesShadow = false;
if (LVX.shouldUseShadow) {
usesShadow = LVX.shouldUseShadow(useShadow);
} else {
usesShadow = useShadow === true;
}
if (usesShadow) {
const adoptedStyleSheets = LVX.getAdoptedStyleSheets ? LVX.getAdoptedStyleSheets() : [];
// If theme is explicitly provided, wrap in a div with data-theme
// Otherwise, return button directly in shadow root to allow inheritance from host
// Use reactive theme signal if available, otherwise fallback to explicit 'theme' prop or default
const themeValue = theme || (LVX.themeSignal ? () => LVX.themeSignal.value : 'light');
return div({ class: 'content', style: 'display: inline-block' },
shadowDOM({ mode: 'open', adoptedStyleSheets },
div({ 'data-theme': themeValue },
buttonEl
)
)
);
}
return buttonEl;
};
globalThis.Lightview.tags.Button = Button;
// Register as Custom Element using customElementWrapper
if (globalThis.LightviewX && typeof customElements !== 'undefined') {
const ButtonElement = globalThis.LightviewX.customElementWrapper(Button, {
attributeMap: {
color: String,
size: String,
variant: String,
disabled: Boolean,
loading: Boolean,
active: Boolean,
glass: Boolean,
noAnimation: Boolean
},
childElements: {} // No child components for Button, uses slot
});
if (!customElements.get('lv-button')) {
customElements.define('lv-button', ButtonElement);
}
}
export default Button;