update
This commit is contained in:
@ -11,7 +11,7 @@ import * as domtools from '@design.estate/dees-domtools';
|
||||
import { DeesInputCheckbox } from './dees-input-checkbox.js';
|
||||
import { DeesInputText } from './dees-input-text.js';
|
||||
import { DeesInputQuantitySelector } from './dees-input-quantityselector.js';
|
||||
import { DeesInputRadio } from './dees-input-radio.js';
|
||||
import { DeesInputRadiogroup } from './dees-input-radiogroup.js';
|
||||
import { DeesInputDropdown } from './dees-input-dropdown.js';
|
||||
import { DeesInputFileupload } from './dees-input-fileupload.js';
|
||||
import { DeesInputIban } from './dees-input-iban.js';
|
||||
@ -31,7 +31,7 @@ const FORM_INPUT_TYPES = [
|
||||
DeesInputMultitoggle,
|
||||
DeesInputPhone,
|
||||
DeesInputQuantitySelector,
|
||||
DeesInputRadio,
|
||||
DeesInputRadiogroup,
|
||||
DeesInputText,
|
||||
DeesInputTypelist,
|
||||
DeesTable,
|
||||
@ -45,7 +45,7 @@ export type TFormInputElement =
|
||||
| DeesInputMultitoggle
|
||||
| DeesInputPhone
|
||||
| DeesInputQuantitySelector
|
||||
| DeesInputRadio
|
||||
| DeesInputRadiogroup
|
||||
| DeesInputText
|
||||
| DeesInputTypelist
|
||||
| DeesTable<any>;
|
||||
@ -132,7 +132,6 @@ export class DeesForm extends DeesElement {
|
||||
public async collectFormData() {
|
||||
const children = this.getFormElements();
|
||||
const valueObject: { [key: string]: string | number | boolean | any[] | File[] | { option: string; key: string; payload?: any } } = {};
|
||||
const radioGroups = new Map<string, DeesInputRadio[]>();
|
||||
|
||||
for (const child of children) {
|
||||
if (!child.key) {
|
||||
@ -140,21 +139,7 @@ export class DeesForm extends DeesElement {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle radio buttons specially
|
||||
if (child instanceof DeesInputRadio && child.name) {
|
||||
if (!radioGroups.has(child.name)) {
|
||||
radioGroups.set(child.name, []);
|
||||
}
|
||||
radioGroups.get(child.name).push(child);
|
||||
} else {
|
||||
valueObject[child.key] = child.value;
|
||||
}
|
||||
}
|
||||
|
||||
// Process radio groups - use the name as key and selected radio's key as value
|
||||
for (const [groupName, radios] of radioGroups) {
|
||||
const selectedRadio = radios.find(radio => radio.value === true);
|
||||
valueObject[groupName] = selectedRadio ? selectedRadio.key : null;
|
||||
valueObject[child.key] = child.value;
|
||||
}
|
||||
|
||||
return valueObject;
|
||||
|
@ -50,14 +50,24 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
|
||||
}
|
||||
|
||||
.maincontainer {
|
||||
padding: 5px 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 0px;
|
||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.maincontainer:hover {
|
||||
color: ${cssManager.bdTheme('#000', '#fff')};
|
||||
}
|
||||
|
||||
.maincontainer:hover .checkbox {
|
||||
border-color: ${cssManager.bdTheme('#999', '#888')};
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-bottom: 1px solid #e4002b;
|
||||
@ -72,6 +82,7 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
|
||||
width: 24px;
|
||||
display: inline-block;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#222')};
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.checkbox.selected {
|
||||
@ -118,13 +129,43 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
|
||||
img {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
font-size: 14px;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.maincontainer:hover .checkbox-label {
|
||||
color: ${cssManager.bdTheme('#1a1a1a', '#ffffff')};
|
||||
}
|
||||
|
||||
.maincontainer.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.maincontainer.disabled:hover {
|
||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
||||
}
|
||||
|
||||
.maincontainer.disabled:hover .checkbox {
|
||||
border-color: ${cssManager.bdTheme('#ccc', '#333')};
|
||||
}
|
||||
|
||||
.description-text {
|
||||
font-size: 12px;
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
margin-top: 4px;
|
||||
line-height: 1.4;
|
||||
padding-left: 36px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
<div class="maincontainer" @click="${this.toggleSelected}">
|
||||
<div class="maincontainer ${this.disabled ? 'disabled' : ''}" @click="${this.toggleSelected}">
|
||||
<div class="checkbox ${this.value ? 'selected' : ''} ${this.disabled ? 'disabled' : ''}" tabindex="0">
|
||||
${this.value
|
||||
? html`
|
||||
@ -135,8 +176,11 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
${this.label ? html`<div class="checkbox-label">${this.label}</div>` : ''}
|
||||
</div>
|
||||
<dees-label .label=${this.label}></dees-label>
|
||||
${this.description ? html`
|
||||
<div class="description-text">${this.description}</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -1,267 +0,0 @@
|
||||
import { html, css } from '@design.estate/dees-element';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
import type { DeesInputRadio } from './dees-input-radio.js';
|
||||
|
||||
export const demoFunc = () => html`
|
||||
<dees-demowrapper>
|
||||
<style>
|
||||
${css`
|
||||
.demo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
padding: 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.demo-section {
|
||||
background: #1a1a1a;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-section h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
color: #0069f2;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.demo-section p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.demo-section p {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.radio-group {
|
||||
background: #0a0a0a;
|
||||
}
|
||||
}
|
||||
|
||||
.radio-group-title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.radio-group-title {
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-layout {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
|
||||
<div class="demo-container">
|
||||
<div class="demo-section">
|
||||
<h3>Basic Radio Groups</h3>
|
||||
<p>Radio buttons for single-choice selections</p>
|
||||
|
||||
<div class="radio-group">
|
||||
<div class="radio-group-title">Select your subscription plan:</div>
|
||||
<dees-input-radio
|
||||
.label=${'Basic Plan - $9/month'}
|
||||
.value=${true}
|
||||
.key=${'plan-basic'}
|
||||
.name=${'plan'}
|
||||
></dees-input-radio>
|
||||
<dees-input-radio
|
||||
.label=${'Pro Plan - $29/month'}
|
||||
.key=${'plan-pro'}
|
||||
.name=${'plan'}
|
||||
></dees-input-radio>
|
||||
<dees-input-radio
|
||||
.label=${'Enterprise Plan - $99/month'}
|
||||
.key=${'plan-enterprise'}
|
||||
.name=${'plan'}
|
||||
></dees-input-radio>
|
||||
</div>
|
||||
|
||||
<div class="radio-group">
|
||||
<div class="radio-group-title">Task Priority:</div>
|
||||
<dees-input-radio
|
||||
.label=${'High Priority'}
|
||||
.key=${'priority-high'}
|
||||
.name=${'priority'}
|
||||
></dees-input-radio>
|
||||
<dees-input-radio
|
||||
.label=${'Medium Priority'}
|
||||
.value=${true}
|
||||
.key=${'priority-medium'}
|
||||
.name=${'priority'}
|
||||
></dees-input-radio>
|
||||
<dees-input-radio
|
||||
.label=${'Low Priority'}
|
||||
.key=${'priority-low'}
|
||||
.name=${'priority'}
|
||||
></dees-input-radio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Horizontal Layout</h3>
|
||||
<p>Radio buttons arranged horizontally for yes/no questions</p>
|
||||
|
||||
<div class="radio-group" style="flex-direction: row;">
|
||||
<div style="margin-right: 16px;">Do you agree?</div>
|
||||
<dees-input-radio
|
||||
.label=${'Yes'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.value=${true}
|
||||
.key=${'agree-yes'}
|
||||
.name=${'agreement'}
|
||||
></dees-input-radio>
|
||||
<dees-input-radio
|
||||
.label=${'No'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'agree-no'}
|
||||
.name=${'agreement'}
|
||||
></dees-input-radio>
|
||||
<dees-input-radio
|
||||
.label=${'Maybe'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'agree-maybe'}
|
||||
.name=${'agreement'}
|
||||
></dees-input-radio>
|
||||
</div>
|
||||
|
||||
<div class="radio-group" style="flex-direction: row;">
|
||||
<div style="margin-right: 16px;">Experience Level:</div>
|
||||
<dees-input-radio
|
||||
.label=${'Beginner'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'exp-beginner'}
|
||||
.name=${'experience'}
|
||||
></dees-input-radio>
|
||||
<dees-input-radio
|
||||
.label=${'Intermediate'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.value=${true}
|
||||
.key=${'exp-intermediate'}
|
||||
.name=${'experience'}
|
||||
></dees-input-radio>
|
||||
<dees-input-radio
|
||||
.label=${'Expert'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'exp-expert'}
|
||||
.name=${'experience'}
|
||||
></dees-input-radio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Survey Example</h3>
|
||||
<p>Multiple radio groups in a survey format</p>
|
||||
|
||||
<div class="grid-layout">
|
||||
<div class="radio-group">
|
||||
<div class="radio-group-title">How satisfied are you?</div>
|
||||
<dees-input-radio .label=${'Very Satisfied'} .key=${'sat-very'} .name=${'satisfaction'}></dees-input-radio>
|
||||
<dees-input-radio .label=${'Satisfied'} .value=${true} .key=${'sat-normal'} .name=${'satisfaction'}></dees-input-radio>
|
||||
<dees-input-radio .label=${'Neutral'} .key=${'sat-neutral'} .name=${'satisfaction'}></dees-input-radio>
|
||||
<dees-input-radio .label=${'Dissatisfied'} .key=${'sat-dis'} .name=${'satisfaction'}></dees-input-radio>
|
||||
<dees-input-radio .label=${'Very Dissatisfied'} .key=${'sat-verydis'} .name=${'satisfaction'}></dees-input-radio>
|
||||
</div>
|
||||
|
||||
<div class="radio-group">
|
||||
<div class="radio-group-title">Would you recommend us?</div>
|
||||
<dees-input-radio .label=${'Definitely'} .key=${'rec-def'} .name=${'recommend'}></dees-input-radio>
|
||||
<dees-input-radio .label=${'Probably'} .value=${true} .key=${'rec-prob'} .name=${'recommend'}></dees-input-radio>
|
||||
<dees-input-radio .label=${'Not Sure'} .key=${'rec-unsure'} .name=${'recommend'}></dees-input-radio>
|
||||
<dees-input-radio .label=${'Probably Not'} .key=${'rec-probnot'} .name=${'recommend'}></dees-input-radio>
|
||||
<dees-input-radio .label=${'Definitely Not'} .key=${'rec-defnot'} .name=${'recommend'}></dees-input-radio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>States</h3>
|
||||
<p>Different radio button states</p>
|
||||
|
||||
<div class="radio-group">
|
||||
<dees-input-radio
|
||||
.label=${'Normal Radio'}
|
||||
.key=${'state-normal'}
|
||||
.name=${'states'}
|
||||
></dees-input-radio>
|
||||
<dees-input-radio
|
||||
.label=${'Selected Radio'}
|
||||
.value=${true}
|
||||
.key=${'state-selected'}
|
||||
.name=${'states'}
|
||||
></dees-input-radio>
|
||||
<dees-input-radio
|
||||
.label=${'Disabled Unchecked'}
|
||||
.disabled=${true}
|
||||
.key=${'state-disabled1'}
|
||||
.name=${'states2'}
|
||||
></dees-input-radio>
|
||||
<dees-input-radio
|
||||
.label=${'Disabled Checked'}
|
||||
.disabled=${true}
|
||||
.value=${true}
|
||||
.key=${'state-disabled2'}
|
||||
.name=${'states2'}
|
||||
></dees-input-radio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Settings Example</h3>
|
||||
<p>Common radio button patterns in settings</p>
|
||||
|
||||
<div class="radio-group">
|
||||
<div class="radio-group-title">Theme Preference:</div>
|
||||
<dees-input-radio .label=${'Light Theme'} .key=${'theme-light'} .name=${'theme'}></dees-input-radio>
|
||||
<dees-input-radio .label=${'Dark Theme'} .value=${true} .key=${'theme-dark'} .name=${'theme'}></dees-input-radio>
|
||||
<dees-input-radio .label=${'System Default'} .key=${'theme-system'} .name=${'theme'}></dees-input-radio>
|
||||
</div>
|
||||
|
||||
<div class="radio-group">
|
||||
<div class="radio-group-title">Notification Frequency:</div>
|
||||
<dees-input-radio .label=${'All Notifications'} .key=${'notif-all'} .name=${'notifications'}></dees-input-radio>
|
||||
<dees-input-radio .label=${'Important Only'} .value=${true} .key=${'notif-important'} .name=${'notifications'}></dees-input-radio>
|
||||
<dees-input-radio .label=${'None'} .key=${'notif-none'} .name=${'notifications'}></dees-input-radio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dees-demowrapper>
|
||||
`;
|
@ -1,135 +0,0 @@
|
||||
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-radio.demo.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-input-radio': DeesInputRadio;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-input-radio')
|
||||
export class DeesInputRadio extends DeesInputBase<DeesInputRadio> {
|
||||
public static demo = demoFunc;
|
||||
|
||||
// INSTANCE
|
||||
|
||||
@property()
|
||||
public value: boolean = false;
|
||||
|
||||
@property({ type: String })
|
||||
public name: string = '';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.labelPosition = 'right'; // Radio buttons default to label on the right
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
...DeesInputBase.baseStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:host {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.maincontainer {
|
||||
transition: all 0.3s;
|
||||
padding: 5px 0px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.maincontainer:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-bottom: 1px solid #e4002b;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
transition: all 0.3s;
|
||||
box-sizing: border-box;
|
||||
border-radius: 20px;
|
||||
border: 1px solid #999;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
display: inline-block;
|
||||
background: #222;
|
||||
}
|
||||
|
||||
.checkbox.selected {
|
||||
background: #0050b9;
|
||||
border: 1px solid #0050b9;
|
||||
}
|
||||
|
||||
.maincontainer:hover .checkbox.selected {
|
||||
background: #03A9F4;
|
||||
}
|
||||
|
||||
.innercircle {
|
||||
transition: all 0.3s;
|
||||
margin: 6px 0px 0px 6px;
|
||||
background: #222;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
<div class="maincontainer" @click="${this.toggleSelected}">
|
||||
<div class="checkbox ${this.value ? 'selected' : ''}">
|
||||
${this.value ? html`<div class="innercircle"></div>`: html``}
|
||||
</div>
|
||||
</div>
|
||||
<dees-label .label=${this.label}></dees-label>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
public async toggleSelected () {
|
||||
// Radio buttons can only be selected, not deselected by clicking
|
||||
if (this.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this radio has a name, find and deselect other radios in the same group
|
||||
if (this.name) {
|
||||
// Try to find a form container first, then fall back to document
|
||||
const container = this.closest('dees-form') ||
|
||||
this.closest('dees-demowrapper') ||
|
||||
this.closest('.radio-group')?.parentElement ||
|
||||
document;
|
||||
const allRadios = container.querySelectorAll(`dees-input-radio[name="${this.name}"]`);
|
||||
allRadios.forEach((radio: DeesInputRadio) => {
|
||||
if (radio !== this && radio.value) {
|
||||
radio.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.value = true;
|
||||
this.dispatchEvent(new CustomEvent('newValue', {
|
||||
detail: this.value,
|
||||
bubbles: true
|
||||
}));
|
||||
this.changeSubject.next(this);
|
||||
}
|
||||
|
||||
public getValue(): boolean {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public setValue(value: boolean): void {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
200
ts_web/elements/dees-input-radiogroup.demo.ts
Normal file
200
ts_web/elements/dees-input-radiogroup.demo.ts
Normal file
@ -0,0 +1,200 @@
|
||||
import { html, css } from '@design.estate/dees-element';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
import './dees-panel.js';
|
||||
|
||||
export const demoFunc = () => html`
|
||||
<dees-demowrapper>
|
||||
<style>
|
||||
${css`
|
||||
.demo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
padding: 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
dees-panel {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
dees-panel:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.result-display {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background: rgba(0, 105, 242, 0.1);
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
|
||||
<div class="demo-container">
|
||||
<dees-panel .title=${'1. Basic Radio Groups'} .subtitle=${'Simple string options for common use cases'}>
|
||||
<div class="demo-grid">
|
||||
<dees-input-radiogroup
|
||||
.label=${'Subscription Plan'}
|
||||
.options=${['Basic - $9/month', 'Pro - $29/month', 'Enterprise - $99/month']}
|
||||
.selectedOption=${'Pro - $29/month'}
|
||||
.description=${'Choose your subscription tier'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Priority Level'}
|
||||
.options=${['High', 'Medium', 'Low']}
|
||||
.selectedOption=${'Medium'}
|
||||
.required=${true}
|
||||
></dees-input-radiogroup>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'2. Horizontal Layout'} .subtitle=${'Radio groups with horizontal arrangement'}>
|
||||
<dees-input-radiogroup
|
||||
.label=${'Do you agree with the terms?'}
|
||||
.options=${['Yes', 'No', 'Maybe']}
|
||||
.direction=${'horizontal'}
|
||||
.selectedOption=${'Yes'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Experience Level'}
|
||||
.options=${['Beginner', 'Intermediate', 'Expert']}
|
||||
.direction=${'horizontal'}
|
||||
.selectedOption=${'Intermediate'}
|
||||
.description=${'Select your experience level with web development'}
|
||||
></dees-input-radiogroup>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'3. Advanced Options'} .subtitle=${'Using object format with keys and payloads'}>
|
||||
<dees-input-radiogroup
|
||||
id="advanced-radio"
|
||||
.label=${'Select Region'}
|
||||
.options=${[
|
||||
{ option: 'United States (US East)', key: 'us-east', payload: { region: 'us-east-1', latency: 20 } },
|
||||
{ option: 'Europe (Frankfurt)', key: 'eu-central', payload: { region: 'eu-central-1', latency: 50 } },
|
||||
{ option: 'Asia Pacific (Singapore)', key: 'ap-southeast', payload: { region: 'ap-southeast-1', latency: 120 } }
|
||||
]}
|
||||
.selectedOption=${'eu-central'}
|
||||
.description=${'Choose the closest region for optimal performance'}
|
||||
@change=${(e: CustomEvent) => {
|
||||
const display = document.querySelector('#region-result');
|
||||
if (display) {
|
||||
display.textContent = 'Selected: ' + JSON.stringify(e.detail.value, null, 2);
|
||||
}
|
||||
}}
|
||||
></dees-input-radiogroup>
|
||||
<div id="region-result" class="result-display">Selected: { "region": "eu-central-1", "latency": 50 }</div>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'4. Survey Example'} .subtitle=${'Multiple radio groups for surveys and forms'}>
|
||||
<div class="demo-grid">
|
||||
<dees-input-radiogroup
|
||||
.label=${'How satisfied are you?'}
|
||||
.options=${['Very Satisfied', 'Satisfied', 'Neutral', 'Dissatisfied', 'Very Dissatisfied']}
|
||||
.selectedOption=${'Satisfied'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Would you recommend us?'}
|
||||
.options=${['Definitely', 'Probably', 'Not Sure', 'Probably Not', 'Definitely Not']}
|
||||
.selectedOption=${'Probably'}
|
||||
></dees-input-radiogroup>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'5. States & Validation'} .subtitle=${'Different states and validation examples'}>
|
||||
<div class="demo-grid">
|
||||
<dees-input-radiogroup
|
||||
.label=${'Required Selection'}
|
||||
.options=${['Option A', 'Option B', 'Option C']}
|
||||
.required=${true}
|
||||
.description=${'This field is required'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Disabled State'}
|
||||
.options=${['Disabled Option 1', 'Disabled Option 2', 'Disabled Option 3']}
|
||||
.selectedOption=${'Disabled Option 2'}
|
||||
.disabled=${true}
|
||||
></dees-input-radiogroup>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'6. Settings Example'} .subtitle=${'Common patterns in application settings'}>
|
||||
<dees-input-radiogroup
|
||||
.label=${'Theme Preference'}
|
||||
.options=${[
|
||||
{ option: 'Light Theme', key: 'light', payload: 'light' },
|
||||
{ option: 'Dark Theme', key: 'dark', payload: 'dark' },
|
||||
{ option: 'System Default', key: 'system', payload: 'auto' }
|
||||
]}
|
||||
.selectedOption=${'dark'}
|
||||
.description=${'Choose how the application should appear'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Notification Frequency'}
|
||||
.options=${['All Notifications', 'Important Only', 'None']}
|
||||
.selectedOption=${'Important Only'}
|
||||
.description=${'Control how often you receive notifications'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Language'}
|
||||
.options=${['English', 'German', 'French', 'Spanish', 'Japanese']}
|
||||
.selectedOption=${'English'}
|
||||
.direction=${'horizontal'}
|
||||
></dees-input-radiogroup>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'7. Form Integration'} .subtitle=${'Works seamlessly with dees-form'}>
|
||||
<dees-form>
|
||||
<dees-input-text
|
||||
.label=${'Product Name'}
|
||||
.required=${true}
|
||||
.key=${'productName'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Product Category'}
|
||||
.options=${['Electronics', 'Clothing', 'Books', 'Home & Garden', 'Sports']}
|
||||
.required=${true}
|
||||
.key=${'category'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Condition'}
|
||||
.options=${['New', 'Like New', 'Good', 'Fair', 'Poor']}
|
||||
.direction=${'horizontal'}
|
||||
.key=${'condition'}
|
||||
.selectedOption=${'New'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Shipping Speed'}
|
||||
.options=${[
|
||||
{ option: 'Standard (5-7 days)', key: 'standard', payload: { days: 7, price: 0 } },
|
||||
{ option: 'Express (2-3 days)', key: 'express', payload: { days: 3, price: 10 } },
|
||||
{ option: 'Overnight', key: 'overnight', payload: { days: 1, price: 25 } }
|
||||
]}
|
||||
.selectedOption=${'standard'}
|
||||
.key=${'shipping'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-form-submit .text=${'Submit Product'}></dees-form-submit>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
</div>
|
||||
</dees-demowrapper>
|
||||
`;
|
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import './dees-form.js';
|
||||
import './dees-input-text.js';
|
||||
import './dees-input-checkbox.js';
|
||||
import './dees-input-dropdown.js';
|
||||
import './dees-input-radio.js';
|
||||
import './dees-input-radiogroup.js';
|
||||
import './dees-form-submit.js';
|
||||
import './dees-statsgrid.js';
|
||||
import type { IStatsTile } from './dees-statsgrid.js';
|
||||
@ -230,13 +230,12 @@ class DemoViewSettings extends DeesElement {
|
||||
<div class="settings-section">
|
||||
<h2>Notification Settings</h2>
|
||||
<dees-form>
|
||||
<div style="margin-bottom: 16px;">
|
||||
<div style="font-weight: 500; margin-bottom: 8px;">Email Frequency:</div>
|
||||
<dees-input-radio label="Real-time" value="true" key="email-realtime"></dees-input-radio>
|
||||
<dees-input-radio label="Daily Digest" key="email-daily"></dees-input-radio>
|
||||
<dees-input-radio label="Weekly Summary" key="email-weekly"></dees-input-radio>
|
||||
<dees-input-radio label="Never" key="email-never"></dees-input-radio>
|
||||
</div>
|
||||
<dees-input-radiogroup
|
||||
.label=${'Email Frequency'}
|
||||
.options=${['Real-time', 'Daily Digest', 'Weekly Summary', 'Never']}
|
||||
.selectedOption=${'Real-time'}
|
||||
.key=${'emailFrequency'}
|
||||
></dees-input-radiogroup>
|
||||
<dees-input-checkbox key="pushNotifications" label="Enable Push Notifications" value="true"></dees-input-checkbox>
|
||||
<dees-input-checkbox key="soundAlerts" label="Play Sound for Alerts" value="true"></dees-input-checkbox>
|
||||
<dees-form-submit>Update Notifications</dees-form-submit>
|
||||
|
@ -34,7 +34,7 @@ export * from './dees-input-phone.js';
|
||||
export * from './dees-input-wysiwyg.js';
|
||||
export * from './dees-progressbar.js';
|
||||
export * from './dees-input-quantityselector.js';
|
||||
export * from './dees-input-radio.js';
|
||||
export * from './dees-input-radiogroup.js';
|
||||
export * from './dees-input-richtext.js';
|
||||
export * from './dees-input-text.js';
|
||||
export * from './dees-label.js';
|
||||
|
@ -315,12 +315,12 @@ export const inputShowcase = () => html`
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dees-input-radio
|
||||
<dees-input-radiogroup
|
||||
.label=${'Select Plan'}
|
||||
.options=${['Free', 'Pro', 'Enterprise']}
|
||||
.selectedOption=${'Pro'}
|
||||
.required=${true}
|
||||
></dees-input-radio>
|
||||
></dees-input-radiogroup>
|
||||
</div>
|
||||
</div>
|
||||
</dees-panel>
|
||||
@ -538,12 +538,13 @@ export const inputShowcase = () => html`
|
||||
.key=${'country'}
|
||||
></dees-input-dropdown>
|
||||
|
||||
<dees-input-radio
|
||||
<dees-input-radiogroup
|
||||
.label=${'Account Type'}
|
||||
.options=${['Personal', 'Business']}
|
||||
.required=${true}
|
||||
.key=${'accountType'}
|
||||
></dees-input-radio>
|
||||
.selectedOption=${'Personal'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-richtext
|
||||
.label=${'Bio'}
|
||||
|
Reference in New Issue
Block a user