import { customElement, type TemplateResult, property, html, css, cssManager, } from '@design.estate/dees-element'; import { DeesInputBase } from './dees-input-base.js'; import { demoFunc } from './dees-input-radiogroup.demo.js'; declare global { interface HTMLElementTagNameMap { 'dees-input-radiogroup': DeesInputRadiogroup; } } type RadioOption = string | { option: string; key: string; payload?: any }; @customElement('dees-input-radiogroup') export class DeesInputRadiogroup extends DeesInputBase { public static demo = demoFunc; // INSTANCE @property({ type: Array }) public options: RadioOption[] = []; @property() public selectedOption: string = ''; @property({ type: String }) public direction: 'vertical' | 'horizontal' = 'vertical'; @property({ type: String, reflect: true }) public validationState: 'valid' | 'invalid' | 'warn' | 'pending' = null; // Form compatibility public get value() { const option = this.getOptionByKey(this.selectedOption); if (typeof option === 'object' && option.payload !== undefined) { return option.payload; } return this.selectedOption; } public set value(val: string | any) { if (typeof val === 'string') { this.selectedOption = val; } else { // Try to find option by payload const option = this.options.find(opt => typeof opt === 'object' && opt.payload === val ); if (option && typeof option === 'object') { this.selectedOption = option.key; } } } public static styles = [ ...DeesInputBase.baseStyles, cssManager.defaultStyles, css` * { box-sizing: border-box; } :host { display: block; position: relative; } .maincontainer { display: flex; flex-direction: column; gap: 8px; } .maincontainer.horizontal { flex-direction: row; flex-wrap: wrap; gap: 16px; } .radio-option { display: flex; align-items: center; gap: 12px; padding: 8px 0; cursor: pointer; transition: all 0.2s ease; user-select: none; position: relative; } .maincontainer.horizontal .radio-option { padding: 8px 16px 8px 0; } .radio-option:hover .radio-circle { border-color: ${cssManager.bdTheme('#0050b9', '#0084ff')}; } .radio-option:hover .radio-label { color: ${cssManager.bdTheme('#1a1a1a', '#ffffff')}; } .radio-circle { width: 20px; height: 20px; border-radius: 50%; border: 2px solid ${cssManager.bdTheme('#999', '#666')}; background: ${cssManager.bdTheme('#fff', '#1a1a1a')}; transition: all 0.2s ease; position: relative; flex-shrink: 0; } .radio-option.selected .radio-circle { border-color: ${cssManager.bdTheme('#0050b9', '#0084ff')}; background: ${cssManager.bdTheme('#0050b9', '#0084ff')}; } .radio-option.selected .radio-circle::after { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 8px; height: 8px; border-radius: 50%; background: white; } .radio-label { font-size: 14px; color: ${cssManager.bdTheme('#666', '#999')}; transition: color 0.2s ease; } .radio-option.selected .radio-label { color: ${cssManager.bdTheme('#1a1a1a', '#ffffff')}; font-weight: 500; } :host([disabled]) .radio-option { cursor: not-allowed; opacity: 0.5; } :host([disabled]) .radio-option:hover .radio-circle { border-color: ${cssManager.bdTheme('#999', '#666')}; } :host([disabled]) .radio-option:hover .radio-label { color: ${cssManager.bdTheme('#666', '#999')}; } .label-text { font-size: 14px; font-weight: 500; color: ${cssManager.bdTheme('#333', '#ccc')}; margin-bottom: 8px; } .description-text { font-size: 12px; color: ${cssManager.bdTheme('#666', '#999')}; margin-top: 8px; line-height: 1.4; } /* Validation styles */ :host([validationState="invalid"]) .radio-circle { border-color: #e74c3c; } :host([validationState="valid"]) .radio-option.selected .radio-circle { border-color: #27ae60; background: #27ae60; } :host([validationState="warn"]) .radio-option.selected .radio-circle { border-color: #f39c12; background: #f39c12; } /* Override base grid layout for radiogroup to prevent large gaps */ :host([label-position="left"]) .input-wrapper { grid-template-columns: auto auto; } :host([label-position="right"]) .input-wrapper { grid-template-columns: auto auto; } `, ]; public render(): TemplateResult { return html`
${this.label ? html`
${this.label}
` : ''}
${this.options.map((option) => { const optionKey = this.getOptionKey(option); const optionLabel = this.getOptionLabel(option); const isSelected = this.selectedOption === optionKey; return html`
${optionLabel}
`; })}
${this.description ? html`
${this.description}
` : ''}
`; } private getOptionKey(option: RadioOption): string { if (typeof option === 'string') { return option; } return option.key; } private getOptionLabel(option: RadioOption): string { if (typeof option === 'string') { return option; } return option.option; } private getOptionByKey(key: string): RadioOption | undefined { return this.options.find(opt => this.getOptionKey(opt) === key); } private selectOption(key: string): void { if (this.disabled) { return; } const oldValue = this.selectedOption; this.selectedOption = key; if (oldValue !== key) { this.dispatchEvent(new CustomEvent('change', { detail: { value: this.value }, bubbles: true, composed: true, })); this.dispatchEvent(new CustomEvent('input', { detail: { value: this.value }, bubbles: true, composed: true, })); this.changeSubject.next(this); } } public getValue(): string | any { return this.value; } public setValue(val: string | any): void { this.value = val; } public async validate(): Promise { if (this.required && !this.selectedOption) { this.validationState = 'invalid'; return false; } this.validationState = 'valid'; return true; } public async firstUpdated() { // Auto-select first option if none selected and not required if (!this.selectedOption && this.options.length > 0 && !this.required) { const firstOption = this.options[0]; this.selectedOption = this.getOptionKey(firstOption); } } }