update
This commit is contained in:
295
ts_web/elements/dees-input-radiogroup.ts
Normal file
295
ts_web/elements/dees-input-radiogroup.ts
Normal file
@ -0,0 +1,295 @@
|
||||
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<string | object> {
|
||||
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`
|
||||
<div class="input-wrapper">
|
||||
${this.label ? html`<div class="label-text">${this.label}</div>` : ''}
|
||||
<div class="maincontainer ${this.direction}">
|
||||
${this.options.map((option) => {
|
||||
const optionKey = this.getOptionKey(option);
|
||||
const optionLabel = this.getOptionLabel(option);
|
||||
const isSelected = this.selectedOption === optionKey;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="radio-option ${isSelected ? 'selected' : ''}"
|
||||
@click="${() => this.selectOption(optionKey)}"
|
||||
>
|
||||
<div class="radio-circle"></div>
|
||||
<div class="radio-label">${optionLabel}</div>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
${this.description ? html`<div class="description-text">${this.description}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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<boolean> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user