diff --git a/readme.plan.md b/readme.plan.md index dc93288..a9898a9 100644 --- a/readme.plan.md +++ b/readme.plan.md @@ -133,7 +133,7 @@ Introduce CSS variables for consistent spacing: 1. Phase 1: Create DeesInputBase class and update dees-input-text ✅ 2. Phase 2: Update remaining input components ✅ -3. Phase 3: Update documentation and examples +3. Phase 3: Update documentation and examples ✅ 4. Phase 4: Testing and refinement ✅ ## Implementation Status @@ -152,6 +152,12 @@ Introduce CSS variables for consistent spacing: - `dees-input-dropdown`: Now extends DeesInputBase, uses dees-label - `dees-input-checkbox`: Now extends DeesInputBase, uses dees-label (default label position: right) - `dees-input-radio`: Now extends DeesInputBase, uses dees-label (default label position: right) + - `dees-input-phone`: Now extends DeesInputBase with phone formatting functionality + - `dees-input-iban`: Now extends DeesInputBase with IBAN validation + - `dees-input-quantityselector`: Now extends DeesInputBase + - `dees-input-multitoggle`: Now extends DeesInputBase with value property for forms + - `dees-input-typelist`: Now extends DeesInputBase + - `dees-input-fileupload`: Now extends DeesInputBase, uses dees-label 3. **Updated dees-form**: - Added `horizontal-layout` property @@ -162,6 +168,10 @@ Introduce CSS variables for consistent spacing: - Added value property to dropdown for form compatibility - Fixed changeSubject typing - Updated form value type to include dropdown options + - Fixed firstUpdated method signatures (phone, iban, fileupload) + - Fixed CSS-in-JS errors in quantityselector (removed dynamic references) + - Added value property to multitoggle for form compatibility + - Removed duplicate properties in fileupload (label, key, disabled, required) ### Result: @@ -171,4 +181,33 @@ All input components now have: - No margin on last child - Consistent label handling via dees-label - Flexible layout modes -- Better alignment in flexbox containers \ No newline at end of file +- Better alignment in flexbox containers + +## Demo Improvements + +### Created external demo files: +1. **dees-input-text.demo.ts**: Comprehensive demos showing: + - Basic text inputs with descriptions + - Horizontal layout examples + - Label position variations + - Validation states + - Password input features + +2. **dees-input-checkbox.demo.ts**: Interactive demos featuring: + - Basic checkbox usage + - Horizontal layout groups + - Feature selection with batch operations + - Real-world examples + +3. **dees-input-radio.demo.ts**: Radio button demos including: + - Radio groups with proper behavior + - Horizontal yes/no questions + - Survey-style layouts + - Settings examples + +### Updated existing demos: +1. **dees-input-dropdown.demo.ts**: Enhanced with dees-demowrapper and comprehensive examples +2. **dees-form.demo.ts**: Added horizontal form layout examples and advanced form features +3. **dees-simple-appdash.demo.ts**: Enhanced settings view with horizontal forms and radio groups + +All demos now use the `dees-demowrapper` component for consistency and include proper styling for light/dark themes. \ No newline at end of file diff --git a/ts_web/elements/dees-form.demo.ts b/ts_web/elements/dees-form.demo.ts index 26ba90c..2a6929e 100644 --- a/ts_web/elements/dees-form.demo.ts +++ b/ts_web/elements/dees-form.demo.ts @@ -1,69 +1,248 @@ -import { html, domtools, cssManager } from '@design.estate/dees-element'; +import { html, css, domtools, cssManager } from '@design.estate/dees-element'; import type { DeesForm } from './dees-form.js'; +import '@design.estate/dees-wcctools/demotools'; export const demoFunc = () => html` - -
- { - const form: DeesForm = eventArg.currentTarget; - form.setStatus('pending', 'authenticating...'); - await domtools.plugins.smartdelay.delayFor(1000); - form.setStatus('success', 'authenticated!'); - }} - > - - - - - - - - - - - Submit - -
+ + + +
+
+

Complete Form Example

+

A comprehensive form with various input types, validation, and form submission handling

+ +
+ { + const form: DeesForm = eventArg.currentTarget; + form.setStatus('pending', 'Processing...'); + await domtools.plugins.smartdelay.delayFor(2000); + form.setStatus('success', 'Form submitted successfully!'); + await domtools.plugins.smartdelay.delayFor(2000); + form.reset(); + }} + > + + + + + + + + + + + + + + + Create Account + +
+
+ +
+

Horizontal Form Layout

+

Compact form with inputs arranged horizontally - perfect for filters and quick forms

+ +
+ + + + + + + + + +
+
+ +
+

Advanced Form Features

+

Form with specialized input types and complex validation

+ +
+ { + const form: DeesForm = eventArg.currentTarget; + const data = eventArg.detail.data; + console.log('Form data:', data); + form.setStatus('success', 'Data logged to console!'); + }} + > + + + + + + + + + + + Submit Application + +
+
+
+
`; diff --git a/ts_web/elements/dees-form.ts b/ts_web/elements/dees-form.ts index fea1d25..0ecafa5 100644 --- a/ts_web/elements/dees-form.ts +++ b/ts_web/elements/dees-form.ts @@ -13,29 +13,41 @@ import { DeesInputText } from './dees-input-text.js'; import { DeesInputQuantitySelector } from './dees-input-quantityselector.js'; import { DeesInputRadio } from './dees-input-radio.js'; import { DeesInputDropdown } from './dees-input-dropdown.js'; +import { DeesInputFileupload } from './dees-input-fileupload.js'; +import { DeesInputIban } from './dees-input-iban.js'; +import { DeesInputMultitoggle } from './dees-input-multitoggle.js'; +import { DeesInputPhone } from './dees-input-phone.js'; +import { DeesInputTypelist } from './dees-input-typelist.js'; import { DeesFormSubmit } from './dees-form-submit.js'; import { DeesTable } from './dees-table.js'; import { demoFunc } from './dees-form.demo.js'; -import { DeesInputIban } from './dees-input-iban.js'; // Unified set for form input types const FORM_INPUT_TYPES = [ DeesInputCheckbox, DeesInputDropdown, + DeesInputFileupload, DeesInputIban, - DeesInputText, + DeesInputMultitoggle, + DeesInputPhone, DeesInputQuantitySelector, DeesInputRadio, + DeesInputText, + DeesInputTypelist, DeesTable, ]; export type TFormInputElement = | DeesInputCheckbox | DeesInputDropdown + | DeesInputFileupload | DeesInputIban - | DeesInputText + | DeesInputMultitoggle + | DeesInputPhone | DeesInputQuantitySelector | DeesInputRadio + | DeesInputText + | DeesInputTypelist | DeesTable; declare global { @@ -119,7 +131,7 @@ export class DeesForm extends DeesElement { */ public async collectFormData() { const children = this.getFormElements(); - const valueObject: { [key: string]: string | number | boolean | any[] | { option: string; key: string; payload?: any } } = {}; + const valueObject: { [key: string]: string | number | boolean | any[] | File[] | { option: string; key: string; payload?: any } } = {}; for (const child of children) { if (!child.key) { console.log(`form element with label "${child.label}" has no key. skipping.`); diff --git a/ts_web/elements/dees-input-checkbox.demo.ts b/ts_web/elements/dees-input-checkbox.demo.ts new file mode 100644 index 0000000..ad2c70a --- /dev/null +++ b/ts_web/elements/dees-input-checkbox.demo.ts @@ -0,0 +1,267 @@ +import { html, css } from '@design.estate/dees-element'; +import '@design.estate/dees-wcctools/demotools'; +import type { DeesInputCheckbox } from './dees-input-checkbox.js'; +import './dees-button.js'; + +export const demoFunc = () => html` + { + // Get all checkboxes for demo interactions + const checkboxes = elementArg.querySelectorAll('dees-input-checkbox'); + + // Example of programmatic interaction + const selectAllBtn = elementArg.querySelector('#select-all-btn'); + const clearAllBtn = elementArg.querySelector('#clear-all-btn'); + + if (selectAllBtn && clearAllBtn) { + selectAllBtn.addEventListener('click', () => { + checkboxes.forEach((checkbox: DeesInputCheckbox) => { + if (!checkbox.disabled && checkbox.key?.startsWith('feature')) { + checkbox.value = true; + } + }); + }); + + clearAllBtn.addEventListener('click', () => { + checkboxes.forEach((checkbox: DeesInputCheckbox) => { + if (!checkbox.disabled && checkbox.key?.startsWith('feature')) { + checkbox.value = false; + } + }); + }); + } + }}> + + +
+
+

Basic Checkboxes

+

Standard checkbox inputs for boolean selections

+ + + + + + +
+ +
+

Horizontal Layout

+

Checkboxes arranged horizontally for compact forms

+ +
+ + + + + + + +
+
+ +
+

Feature Selection Example

+

Common use case for feature toggles with batch operations

+ +
+ Select All + Clear All +
+ +
+
+ + + + + + + + + +
+
+
+ +
+

States

+

Different checkbox states and configurations

+ + + + + + +
+ +
+

Real-world Examples

+

Common checkbox patterns in applications

+ +
+ + + + + + + +
+
+
+
+`; \ No newline at end of file diff --git a/ts_web/elements/dees-input-checkbox.ts b/ts_web/elements/dees-input-checkbox.ts index 5c66c8f..289538d 100644 --- a/ts_web/elements/dees-input-checkbox.ts +++ b/ts_web/elements/dees-input-checkbox.ts @@ -6,8 +6,8 @@ import { css, cssManager, } from '@design.estate/dees-element'; -import * as domtools from '@design.estate/dees-domtools'; import { DeesInputBase } from './dees-input-base.js'; +import { demoFunc } from './dees-input-checkbox.demo.js'; declare global { interface HTMLElementTagNameMap { @@ -18,7 +18,7 @@ declare global { @customElement('dees-input-checkbox') export class DeesInputCheckbox extends DeesInputBase { // STATIC - public static demo = () => html``; + public static demo = demoFunc; // INSTANCE diff --git a/ts_web/elements/dees-input-dropdown.demo.ts b/ts_web/elements/dees-input-dropdown.demo.ts index c5f3223..0f0db89 100644 --- a/ts_web/elements/dees-input-dropdown.demo.ts +++ b/ts_web/elements/dees-input-dropdown.demo.ts @@ -1,27 +1,200 @@ -import { html } from '@design.estate/dees-element'; +import { html, css } from '@design.estate/dees-element'; +import '@design.estate/dees-wcctools/demotools'; export const demoFunc = () => html` - - -
- + + + +
+
+

Basic Dropdowns

+

Standard dropdown with search functionality and various options

+ + + + +
+ +
+

Without Search

+

Dropdown with search functionality disabled for simpler selection

+ + +
+ +
+

Horizontal Layout

+

Multiple dropdowns in a horizontal layout for compact forms

+ +
+ + + + + +
+
+ +
+

States

+

Different states and configurations

+ + + + +
+ +
+ (Spacer to test dropdown positioning) +
+ +
+

Bottom Positioning

+

Dropdown that opens upward when near bottom of viewport

+ + +
+
+
` \ No newline at end of file diff --git a/ts_web/elements/dees-input-fileupload.demo.ts b/ts_web/elements/dees-input-fileupload.demo.ts new file mode 100644 index 0000000..8132205 --- /dev/null +++ b/ts_web/elements/dees-input-fileupload.demo.ts @@ -0,0 +1,138 @@ +import { html, css, cssManager } from '@design.estate/dees-element'; + +export const demoFunc = () => html` + + + +
+ + + + + + + +
+
+

Profile Picture

+ +
+ +
+

Cover Image

+ +
+
+
+ + + + + + + + + + + + + + + + +
+

Features:

+
    +
  • Click to select files or drag & drop
  • +
  • Multiple file selection support
  • +
  • Visual feedback for drag operations
  • +
  • Right-click files to remove them
  • +
  • Integrates seamlessly with forms
  • +
+
+
+
+
+`; \ No newline at end of file diff --git a/ts_web/elements/dees-input-fileupload.ts b/ts_web/elements/dees-input-fileupload.ts index e4799de..1a1433a 100644 --- a/ts_web/elements/dees-input-fileupload.ts +++ b/ts_web/elements/dees-input-fileupload.ts @@ -2,6 +2,8 @@ import * as colors from './00colors.js'; import * as plugins from './00plugins.js'; import { DeesContextmenu } from './dees-contextmenu.js'; +import { DeesInputBase } from './dees-input-base.js'; +import { demoFunc } from './dees-input-fileupload.demo.js'; import { customElement, @@ -23,23 +25,9 @@ declare global { } @customElement('dees-input-fileupload') -export class DeesInputFileupload extends DeesElement { - public static demo = () => - html``; +export class DeesInputFileupload extends DeesInputBase { + public static demo = demoFunc; - // INSTANCE - public changeSubject = new domtools.plugins.smartrx.rxjs.Subject(); - - @property({ - type: String, - }) - public label: string = null; - - @property({ - type: String, - reflect: true, - }) - public key: string; @property({ attribute: false, @@ -49,16 +37,6 @@ export class DeesInputFileupload extends DeesElement { @property() public state: 'idle' | 'dragOver' | 'dropped' | 'uploading' | 'completed' = 'idle'; - @property({ - type: Boolean, - }) - public required: boolean = false; - - @property({ - type: Boolean, - }) - public disabled: boolean = false; - @property({ type: String, }) @@ -69,13 +47,12 @@ export class DeesInputFileupload extends DeesElement { } public static styles = [ + ...DeesInputBase.baseStyles, cssManager.defaultStyles, css` :host { position: relative; display: grid; - margin: 10px 0px; - margin-bottom: 24px; color: ${cssManager.bdTheme('#333', '#ccc')}; } @@ -112,11 +89,6 @@ export class DeesInputFileupload extends DeesElement { background: #00000080; } - .label { - font-size: 14px; - margin-bottom: 8px; - } - .uploadButton { position: relative; padding: 8px; @@ -173,11 +145,12 @@ export class DeesInputFileupload extends DeesElement { public render(): TemplateResult { return html` - - - ${this.label ? html`
${this.label}
` : null} -
+
+ + +
${this.value.map( (fileArg) => html`
{ @@ -205,6 +178,7 @@ export class DeesInputFileupload extends DeesElement { ${this.buttonText}
+
`; } @@ -221,7 +195,8 @@ export class DeesInputFileupload extends DeesElement { this.changeSubject.next(this); } - public firstUpdated() { + public firstUpdated(_changedProperties: Map) { + super.firstUpdated(_changedProperties); const inputFile: HTMLInputElement = this.shadowRoot.querySelector('input[type="file"]'); inputFile.addEventListener('change', (event: Event) => { const target = event.target as HTMLInputElement; @@ -263,4 +238,12 @@ export class DeesInputFileupload extends DeesElement { dropArea.addEventListener('dragover', handlerFunction, false); dropArea.addEventListener('drop', handlerFunction, false); } + + public getValue(): File[] { + return this.value; + } + + public setValue(value: File[]): void { + this.value = value; + } } diff --git a/ts_web/elements/dees-input-iban.demo.ts b/ts_web/elements/dees-input-iban.demo.ts index 463a4b0..a82a1c4 100644 --- a/ts_web/elements/dees-input-iban.demo.ts +++ b/ts_web/elements/dees-input-iban.demo.ts @@ -1,3 +1,80 @@ -import { html } from '@design.estate/dees-element'; +import { html, css } from '@design.estate/dees-element'; -export const demoFunc = () => html``; \ No newline at end of file +export const demoFunc = () => html` + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + +
+
+`; \ No newline at end of file diff --git a/ts_web/elements/dees-input-iban.ts b/ts_web/elements/dees-input-iban.ts index 7604796..e095ee1 100644 --- a/ts_web/elements/dees-input-iban.ts +++ b/ts_web/elements/dees-input-iban.ts @@ -1,18 +1,19 @@ import { customElement, - DeesElement, type TemplateResult, state, html, - domtools, property, + css, + cssManager, } from '@design.estate/dees-element'; - +import * as domtools from '@design.estate/dees-domtools'; +import { DeesInputBase } from './dees-input-base.js'; import * as ibantools from 'ibantools'; import { demoFunc } from './dees-input-iban.demo.js'; @customElement('dees-input-iban') -export class DeesInputIban extends DeesElement { +export class DeesInputIban extends DeesInputBase { // STATIC public static demo = demoFunc; @@ -23,60 +24,44 @@ export class DeesInputIban extends DeesElement { @state() enteredIbanIsValid: boolean = false; - @property({ - type: Boolean, - }) - public disabled = false; - - @property({ - type: Boolean, - }) - public required = false; - - @property({ - type: String, - }) - public label = ''; - - @property({ - type: String, - }) - public key = ''; - @property({ type: String, }) public value = ''; - public changeSubject = new domtools.plugins.smartrx.rxjs.Subject(); + public static styles = [ + ...DeesInputBase.baseStyles, + cssManager.defaultStyles, + css` + /* IBAN input specific styles can go here */ + `, + ]; public render(): TemplateResult { return html` - - { - this.validateIban(eventArg); - }} - > +
+ + { + this.validateIban(eventArg); + }} + > +
`; } - public async firstUpdated() { - const deesInputText = this.shadowRoot.querySelector('dees-input-text'); - deesInputText.disabled = this.disabled; - deesInputText.required = this.required; - deesInputText.changeSubject.subscribe(valueArg => { - this.value = valueArg.value; - this.changeSubject.next(this); - }) + public firstUpdated(_changedProperties: Map) { + super.firstUpdated(_changedProperties); + const deesInputText = this.shadowRoot.querySelector('dees-input-text') as any; + if (deesInputText && deesInputText.changeSubject) { + deesInputText.changeSubject.subscribe(() => { + this.changeSubject.next(this); + }); + } } public async validateIban(eventArg: InputEvent): Promise { @@ -95,4 +80,13 @@ export class DeesInputIban extends DeesElement { const deesInputText = this.shadowRoot.querySelector('dees-input-text'); deesInputText.validationText = `IBAN is valid: ${this.enteredIbanIsValid}`; } + + public getValue(): string { + return this.value; + } + + public setValue(value: string): void { + this.value = value; + this.enteredString = ibantools.friendlyFormatIBAN(value) || ''; + } } diff --git a/ts_web/elements/dees-input-multitoggle.demo.ts b/ts_web/elements/dees-input-multitoggle.demo.ts index f975baf..cbfd563 100644 --- a/ts_web/elements/dees-input-multitoggle.demo.ts +++ b/ts_web/elements/dees-input-multitoggle.demo.ts @@ -1,14 +1,128 @@ -import { html } from '@design.estate/dees-element'; +import { html, css } from '@design.estate/dees-element'; export const demoFunc = () => html` - - + + + +
+ + + + + + + + + + + + + +
+ + + + + + + +
+
+ + + + + + + + + + +
+
`; \ No newline at end of file diff --git a/ts_web/elements/dees-input-multitoggle.ts b/ts_web/elements/dees-input-multitoggle.ts index 6d4d971..d65b513 100644 --- a/ts_web/elements/dees-input-multitoggle.ts +++ b/ts_web/elements/dees-input-multitoggle.ts @@ -1,14 +1,14 @@ import { customElement, - DeesElement, type TemplateResult, - state, html, - domtools, property, css, cssManager, } from '@design.estate/dees-element'; +import { DeesInputBase } from './dees-input-base.js'; + +import * as colors from './00colors.js' const { demoFunc } = await import('./dees-input-multitoggle.demo.js'); @@ -19,18 +19,9 @@ declare global { } @customElement('dees-input-multitoggle') -export class DeesInputMultitoggle extends DeesElement { +export class DeesInputMultitoggle extends DeesInputBase { public static demo = demoFunc; - @property({ - type: String, - }) - public label: string; - - @property({ - type: String, - }) - public description: string; @property() type: 'boolean' | 'multi' | 'single' = 'multi'; @@ -49,23 +40,38 @@ export class DeesInputMultitoggle extends DeesElement { @property() selectedOption: string = ''; - @property() + @property({ type: Boolean }) boolValue: boolean = false; + // Add value property for form compatibility + public get value(): string | boolean { + if (this.type === 'boolean') { + return this.selectedOption === this.booleanTrueName; + } + return this.selectedOption; + } + + public set value(val: string | boolean) { + if (this.type === 'boolean' && typeof val === 'boolean') { + this.selectedOption = val ? this.booleanTrueName : this.booleanFalseName; + } else { + this.selectedOption = val as string; + } + // Defer indicator update to next frame if component not yet updated + if (this.hasUpdated) { + this.setIndicator(); + } + } + public static styles = [ + ...DeesInputBase.baseStyles, cssManager.defaultStyles, css` :host { - display: block; color: ${cssManager.bdTheme('#333', '#ccc')}; user-select: none; - margin: 8px 0px 24px 0px; } - .label { - font-size: 14px; - margin-bottom: 8px; - } .selections { position: relative; @@ -76,11 +82,11 @@ export class DeesInputMultitoggle extends DeesElement { width: min-content; border-radius: 20px; height: 32px; - border-top: 1px solid #ffffff10; + border-top: 1px solid ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(255,255,255,0.1)')}; } .option { - color: #ccc; + color: ${cssManager.bdTheme('#666', '#999')}; position: relative; padding: 0px 16px; line-height: 32px; @@ -93,11 +99,11 @@ export class DeesInputMultitoggle extends DeesElement { } .option:hover { - color: #fff; + color: ${cssManager.bdTheme('#333', '#fff')}; } .option.selected { - color: #fff; + color: ${cssManager.bdTheme('#fff', '#fff')}; } .indicator { @@ -107,17 +113,23 @@ export class DeesInputMultitoggle extends DeesElement { left: 4px; top: 3px; border-radius: 16px; - background: #0050b9; - min-width: 36px; + background: ${cssManager.bdTheme(colors.bright.blueActive, colors.dark.blueActive)}; + min-width: 24px; + transition: all 0.1s ease-in-out; + } + + .indicator.no-transition { + transition: none; } `, ]; public render(): TemplateResult { return html` - -
-
+
+ +
+
${this.options.map( (option) => @@ -125,16 +137,31 @@ export class DeesInputMultitoggle extends DeesElement { ${option}
` )} +
`; } - public async firstUpdated() { + public async connectedCallback() { + await super.connectedCallback(); + // Initialize boolean options early + if (this.type === 'boolean' && this.options.length === 0) { + this.options = [this.booleanTrueName || 'true', this.booleanFalseName || 'false']; + } + } + + public async firstUpdated(_changedProperties: Map) { + super.firstUpdated(_changedProperties); + // Update boolean options if they changed if (this.type === 'boolean') { this.options = [this.booleanTrueName || 'true', this.booleanFalseName || 'false']; } - this.setIndicator(); + // Wait for the next frame to ensure DOM is fully rendered + await this.updateComplete; + requestAnimationFrame(() => { + this.setIndicator(); + }); } public async handleSelection(optionArg: string) { @@ -142,18 +169,57 @@ export class DeesInputMultitoggle extends DeesElement { this.setIndicator(); } + private indicatorInitialized = false; + public async setIndicator() { const indicator: HTMLDivElement = this.shadowRoot.querySelector('.indicator'); + const selectedIndex = this.options.indexOf(this.selectedOption); + + // If no valid selection, hide indicator + if (selectedIndex === -1 || !indicator) { + if (indicator) { + indicator.style.opacity = '0'; + } + return; + } + const option: HTMLDivElement = this.shadowRoot.querySelector( - `.option:nth-child(${this.options.indexOf(this.selectedOption) + 2})` + `.option:nth-child(${selectedIndex + 2})` ); + if (indicator && option) { + // Only disable transition for the very first positioning + if (!this.indicatorInitialized) { + indicator.classList.add('no-transition'); + this.indicatorInitialized = true; + + // Remove the no-transition class after a brief delay + setTimeout(() => { + indicator.classList.remove('no-transition'); + }, 50); + } + indicator.style.width = `${option.clientWidth - 8}px`; indicator.style.left = `${option.offsetLeft + 4}px`; indicator.style.opacity = '1'; } - setTimeout(() => { - indicator.style.transition = 'all 0.1s'; - }, 100); + } + + public getValue(): string | boolean { + if (this.type === 'boolean') { + return this.selectedOption === this.booleanTrueName; + } + return this.selectedOption; + } + + public setValue(value: string | boolean): void { + if (this.type === 'boolean' && typeof value === 'boolean') { + this.selectedOption = value ? (this.booleanTrueName || 'true') : (this.booleanFalseName || 'false'); + } else { + this.selectedOption = value as string; + } + if (this.hasUpdated) { + this.setIndicator(); + } } } diff --git a/ts_web/elements/dees-input-phone.demo.ts b/ts_web/elements/dees-input-phone.demo.ts index 64f5002..3b5cb35 100644 --- a/ts_web/elements/dees-input-phone.demo.ts +++ b/ts_web/elements/dees-input-phone.demo.ts @@ -1,3 +1,80 @@ -import { html } from '@design.estate/dees-element'; +import { html, css } from '@design.estate/dees-element'; -export const demoFunc = () => html``; +export const demoFunc = () => html` + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + +
+
+`; \ No newline at end of file diff --git a/ts_web/elements/dees-input-phone.ts b/ts_web/elements/dees-input-phone.ts index 1153042..3c62c57 100644 --- a/ts_web/elements/dees-input-phone.ts +++ b/ts_web/elements/dees-input-phone.ts @@ -1,14 +1,14 @@ import { customElement, - DeesElement, type TemplateResult, property, + state, html, css, - unsafeCSS, cssManager, - type CSSResult, } from '@design.estate/dees-element'; +import * as domtools from '@design.estate/dees-domtools'; +import { DeesInputBase } from './dees-input-base.js'; import { demoFunc } from './dees-input-phone.demo.js'; declare global { @@ -18,12 +18,116 @@ declare global { } @customElement('dees-input-phone') -export class DeesInputPhone extends DeesElement { +export class DeesInputPhone extends DeesInputBase { // STATIC public static demo = demoFunc; // INSTANCE - public render() { - return html`
Phone Input
`; + @state() + protected formattedPhone: string = ''; + + @property({ type: String }) + public value: string = ''; + + @property({ type: String }) + public placeholder: string = '+1 (555) 123-4567'; + + public static styles = [ + ...DeesInputBase.baseStyles, + cssManager.defaultStyles, + css` + /* Phone input specific styles can go here */ + `, + ]; + + public render(): TemplateResult { + return html` +
+ + this.handlePhoneInput(event)} + > +
+ `; + } + + public firstUpdated(_changedProperties: Map) { + super.firstUpdated(_changedProperties); + // Initialize formatted phone from value + if (this.value) { + this.formattedPhone = this.formatPhoneNumber(this.value); + } + + // Subscribe to the inner input's changes + const innerInput = this.shadowRoot.querySelector('dees-input-text') as any; + if (innerInput && innerInput.changeSubject) { + innerInput.changeSubject.subscribe(() => { + this.changeSubject.next(this); + }); + } + } + + private handlePhoneInput(event: InputEvent) { + const input = event.target as HTMLInputElement; + const cleanedValue = this.cleanPhoneNumber(input.value); + const formatted = this.formatPhoneNumber(cleanedValue); + + // Update the input with formatted value + if (input.value !== formatted) { + const cursorPosition = input.selectionStart || 0; + input.value = formatted; + + // Try to maintain cursor position intelligently + const newCursorPos = this.calculateCursorPosition(cleanedValue, formatted, cursorPosition); + input.setSelectionRange(newCursorPos, newCursorPos); + } + + this.formattedPhone = formatted; + this.value = cleanedValue; + this.changeSubject.next(this); + } + + private cleanPhoneNumber(value: string): string { + // Remove all non-numeric characters + return value.replace(/\D/g, ''); + } + + private formatPhoneNumber(value: string): string { + // Basic US phone number formatting + // This can be enhanced to support international formats + const cleaned = this.cleanPhoneNumber(value); + + if (cleaned.length === 0) return ''; + if (cleaned.length <= 3) return cleaned; + if (cleaned.length <= 6) return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3)}`; + if (cleaned.length <= 10) return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)}-${cleaned.slice(6)}`; + + // For numbers longer than 10 digits, format as international + return `+${cleaned.slice(0, cleaned.length - 10)} (${cleaned.slice(-10, -7)}) ${cleaned.slice(-7, -4)}-${cleaned.slice(-4)}`; + } + + private calculateCursorPosition(cleaned: string, formatted: string, oldPos: number): number { + // Simple cursor position calculation + // Count how many formatting characters are before the cursor + let formattingChars = 0; + for (let i = 0; i < oldPos && i < formatted.length; i++) { + if (!/\d/.test(formatted[i])) { + formattingChars++; + } + } + return Math.min(oldPos + formattingChars, formatted.length); + } + + public getValue(): string { + return this.value; + } + + public setValue(value: string): void { + this.value = value; + this.formattedPhone = this.formatPhoneNumber(value); } } \ No newline at end of file diff --git a/ts_web/elements/dees-input-quantityselector.demo.ts b/ts_web/elements/dees-input-quantityselector.demo.ts new file mode 100644 index 0000000..c8d7c12 --- /dev/null +++ b/ts_web/elements/dees-input-quantityselector.demo.ts @@ -0,0 +1,127 @@ +import { html, css, cssManager } from '@design.estate/dees-element'; + +export const demoFunc = () => html` + + + +
+ + + + + + + +
+
+
Premium Headphones
+
$199.99
+ +
+ +
+
Wireless Mouse
+
$49.99
+ +
+ +
+
USB-C Cable
+
$19.99
+ +
+
+
+ + + + + + + + + + + + + + + +
+
+`; \ No newline at end of file diff --git a/ts_web/elements/dees-input-quantityselector.ts b/ts_web/elements/dees-input-quantityselector.ts index 0801552..474b511 100644 --- a/ts_web/elements/dees-input-quantityselector.ts +++ b/ts_web/elements/dees-input-quantityselector.ts @@ -1,5 +1,7 @@ -import { customElement, property, html, type TemplateResult, DeesElement, type CSSResult, } from '@design.estate/dees-element'; +import { customElement, property, html, type TemplateResult, css, cssManager } from '@design.estate/dees-element'; import * as domtools from '@design.estate/dees-domtools'; +import { DeesInputBase } from './dees-input-base.js'; +import { demoFunc } from './dees-input-quantityselector.demo.js'; declare global { interface HTMLElementTagNameMap { @@ -8,67 +10,50 @@ declare global { } @customElement('dees-input-quantityselector') -export class DeesInputQuantitySelector extends DeesElement { - public static demo = () => html``; +export class DeesInputQuantitySelector extends DeesInputBase { + public static demo = demoFunc; // INSTANCE - public changeSubject = new domtools.plugins.smartrx.rxjs.Subject(); - - @property() - public label: string = 'Label'; - - @property({ - type: String, - reflect: true, - }) - public key: string; @property({ type: Number }) public value: number = 1; - @property({ - type: Boolean, - }) - public required: boolean = false; - @property({ - type: Boolean - }) - public disabled: boolean = false; - constructor() { - super(); - } - - public render(): TemplateResult { - return html` - ${domtools.elementBasic.styles} - + `, + ]; -
-
-
+ public render(): TemplateResult { + return html` +
+ +
+
-
${this.value}
-
+
+
+
+
`; } - public increase () { - this.value++; - this.changeSubject.next(this); + public increase() { + if (!this.disabled) { + this.value++; + this.changeSubject.next(this); + } } - public decrease () { - if (this.value > 0) { + public decrease() { + if (!this.disabled && this.value > 0) { this.value--; - } else { - // nothing to do here + this.changeSubject.next(this); } - this.changeSubject.next(this); + } + + public getValue(): number { + return this.value; + } + + public setValue(value: number): void { + this.value = value; } } diff --git a/ts_web/elements/dees-input-radio.demo.ts b/ts_web/elements/dees-input-radio.demo.ts new file mode 100644 index 0000000..c9aa2f7 --- /dev/null +++ b/ts_web/elements/dees-input-radio.demo.ts @@ -0,0 +1,277 @@ +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` + { + // Implement radio group behavior + const radioGroups = new Map(); + + // Group radios by their container + const radioContainers = elementArg.querySelectorAll('.radio-group'); + radioContainers.forEach((container) => { + const radios = Array.from(container.querySelectorAll('dees-input-radio')) as DeesInputRadio[]; + const groupName = container.getAttribute('data-group') || 'default'; + radioGroups.set(groupName, radios); + + // Add click handlers for radio group behavior + radios.forEach((radio) => { + radio.addEventListener('click', () => { + if (!radio.disabled && !radio.value) { + // Uncheck all other radios in the group + radios.forEach((r) => { + if (r !== radio) { + r.value = false; + } + }); + radio.value = true; + } + }); + }); + }); + }}> + + +
+
+

Basic Radio Groups

+

Radio buttons for single-choice selections

+ +
+
Select your subscription plan:
+ + + +
+ +
+
Task Priority:
+ + + +
+
+ +
+

Horizontal Layout

+

Radio buttons arranged horizontally for yes/no questions

+ +
+
Do you agree?
+ + + +
+ +
+
Experience Level:
+ + + +
+
+ +
+

Survey Example

+

Multiple radio groups in a survey format

+ +
+
+
How satisfied are you?
+ + + + + +
+ +
+
Would you recommend us?
+ + + + + +
+
+
+ +
+

States

+

Different radio button states

+ +
+ + + + +
+
+ +
+

Settings Example

+

Common radio button patterns in settings

+ +
+
Theme Preference:
+ + + +
+ +
+
Notification Frequency:
+ + + +
+
+
+
+`; \ No newline at end of file diff --git a/ts_web/elements/dees-input-radio.ts b/ts_web/elements/dees-input-radio.ts index c41689b..910c4b8 100644 --- a/ts_web/elements/dees-input-radio.ts +++ b/ts_web/elements/dees-input-radio.ts @@ -1,6 +1,6 @@ import {customElement, type TemplateResult, property, html, css, cssManager} from '@design.estate/dees-element'; -import * as domtools from '@design.estate/dees-domtools'; import { DeesInputBase } from './dees-input-base.js'; +import { demoFunc } from './dees-input-radio.demo.js'; declare global { interface HTMLElementTagNameMap { @@ -10,7 +10,7 @@ declare global { @customElement('dees-input-radio') export class DeesInputRadio extends DeesInputBase { - public static demo = () => html``; + public static demo = demoFunc; // INSTANCE diff --git a/ts_web/elements/dees-input-text.demo.ts b/ts_web/elements/dees-input-text.demo.ts new file mode 100644 index 0000000..472a076 --- /dev/null +++ b/ts_web/elements/dees-input-text.demo.ts @@ -0,0 +1,199 @@ +import { html, css } from '@design.estate/dees-element'; +import '@design.estate/dees-wcctools/demotools'; + +export const demoFunc = () => html` + + + +
+
+

Basic Text Inputs

+

Standard text inputs with labels and descriptions

+ + + + + + +
+ +
+

Horizontal Layout

+

Multiple inputs arranged horizontally for compact forms

+ +
+ + + + + +
+
+ +
+

Label Positions

+

Different label positioning options for various layouts

+ + + + + +
+ + + +
+
+ +
+

Validation & States

+

Different validation states and input configurations

+ + + + + + +
+ +
+

Advanced Features

+

Password visibility toggle and other advanced features

+ + + + +
+
+
+`; \ No newline at end of file diff --git a/ts_web/elements/dees-input-text.ts b/ts_web/elements/dees-input-text.ts index 25a277d..4d2b49c 100644 --- a/ts_web/elements/dees-input-text.ts +++ b/ts_web/elements/dees-input-text.ts @@ -1,5 +1,6 @@ import * as colors from './00colors.js'; import { DeesInputBase } from './dees-input-base.js'; +import { demoFunc } from './dees-input-text.demo.js'; import { customElement, @@ -18,10 +19,7 @@ declare global { @customElement('dees-input-text') export class DeesInputText extends DeesInputBase { - public static demo = () => html` - - - `; + public static demo = demoFunc; // INSTANCE @property({ diff --git a/ts_web/elements/dees-input-typelist.demo.ts b/ts_web/elements/dees-input-typelist.demo.ts index c616e3b..ecf4192 100644 --- a/ts_web/elements/dees-input-typelist.demo.ts +++ b/ts_web/elements/dees-input-typelist.demo.ts @@ -1,15 +1,118 @@ -import { html } from '@design.estate/dees-element'; +import { html, css } from '@design.estate/dees-element'; export const demoFunc = () => html` - -
- -
+ + + +
+ + + + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + +
+ Tip: Type a value and press Enter to add it to the list. Click on any item to remove it. +
+
+
+
`; \ No newline at end of file diff --git a/ts_web/elements/dees-input-typelist.ts b/ts_web/elements/dees-input-typelist.ts index b08a1f5..13a2ae5 100644 --- a/ts_web/elements/dees-input-typelist.ts +++ b/ts_web/elements/dees-input-typelist.ts @@ -1,44 +1,37 @@ import { customElement, - DeesElement, type TemplateResult, state, html, - domtools, property, css, cssManager, } from '@design.estate/dees-element'; +import * as domtools from '@design.estate/dees-domtools'; +import { DeesInputBase } from './dees-input-base.js'; const { demoFunc } = await import('./dees-input-typelist.demo.js'); @customElement('dees-input-typelist') -export class DeesInputTypelist extends DeesElement { +export class DeesInputTypelist extends DeesInputBase { public static demo = demoFunc; // INSTANCE - @property({ - type: String, - }) - public label: string; + @property({ type: Array }) + public value: string[] = []; + + @state() + private inputValue: string = ''; - constructor() { - super(); - } public static styles = [ + ...DeesInputBase.baseStyles, cssManager.defaultStyles, css` :host { - display: block; color: ${cssManager.bdTheme('#333', '#fff')}; - margin: 8px 0px 24px 0px; - } - .label { - font-size: 14px; - margin-bottom: 8px; } .mainbox { border-radius: 3px; @@ -79,20 +72,89 @@ export class DeesInputTypelist extends DeesElement { input:focus { height: 32px; } + + .tag { + display: inline-block; + background: ${cssManager.bdTheme('#e0e0e0', '#444')}; + color: ${cssManager.bdTheme('#333', '#fff')}; + padding: 4px 8px; + border-radius: 3px; + margin: 2px; + font-size: 12px; + } + + .tag .remove { + margin-left: 6px; + cursor: pointer; + opacity: 0.6; + } + + .tag .remove:hover { + opacity: 1; + } `, ]; public render(): TemplateResult { return html` -
${this.label}
-
-
{ - this.shadowRoot.querySelector('input').focus(); - }}> -
No tags yet
+
+ +
+
{ + this.shadowRoot.querySelector('input').focus(); + }}> + ${this.value.length === 0 + ? html`
No tags yet
` + : this.value.map( + (tag) => html` + + ${tag} + { + e.stopPropagation(); + this.removeTag(tag); + }}>× + + ` + )} +
+ { + this.inputValue = (e.target as HTMLInputElement).value; + }} + @keydown=${(e: KeyboardEvent) => { + if (e.key === 'Enter' && this.inputValue.trim()) { + e.preventDefault(); + this.addTag(this.inputValue.trim()); + } + }} + .disabled=${this.disabled} + />
-
`; } + + private addTag(tag: string) { + if (!this.value.includes(tag)) { + this.value = [...this.value, tag]; + this.inputValue = ''; + this.changeSubject.next(this); + } + } + + private removeTag(tag: string) { + this.value = this.value.filter((t) => t !== tag); + this.changeSubject.next(this); + } + + public getValue(): string[] { + return this.value; + } + + public setValue(value: string[]): void { + this.value = value; + } } diff --git a/ts_web/elements/dees-panel.demo.ts b/ts_web/elements/dees-panel.demo.ts new file mode 100644 index 0000000..d29ebbd --- /dev/null +++ b/ts_web/elements/dees-panel.demo.ts @@ -0,0 +1,81 @@ +import { html, css, cssManager } from '@design.estate/dees-element'; + +export const demoFunc = () => html` + + +
+
+ +

The panel component automatically follows the theme and provides consistent styling for grouped content.

+

It's perfect for creating sections in your application with proper spacing and borders.

+
+ + +

Panels can have both a title and subtitle to provide more context.

+

The subtitle appears in a smaller, muted text below the title.

+
+ +
+ +

Grid layouts work great with panels for creating dashboards and feature sections.

+ Action +
+ + +

Each panel maintains consistent spacing and styling.

+ Another Action +
+
+ + +

Nested Elements

+

Panels can contain any type of content:

+
    +
  • Text and paragraphs
  • +
  • Lists and tables
  • +
  • Form inputs
  • +
  • Other components
  • +
+ + + +
+ Submit +
+
+ + +

Panels work great even without a title for simple content grouping.

+

They provide visual separation and consistent padding.

+
+
+
+`; \ No newline at end of file diff --git a/ts_web/elements/dees-panel.ts b/ts_web/elements/dees-panel.ts new file mode 100644 index 0000000..8979a56 --- /dev/null +++ b/ts_web/elements/dees-panel.ts @@ -0,0 +1,77 @@ +import { + customElement, + DeesElement, + html, + css, + cssManager, + property, + type TemplateResult, +} from '@design.estate/dees-element'; +import { demoFunc } from './dees-panel.demo.js'; + +declare global { + interface HTMLElementTagNameMap { + 'dees-panel': DeesPanel; + } +} + +@customElement('dees-panel') +export class DeesPanel extends DeesElement { + public static demo = demoFunc; + + @property({ type: String }) + public title: string = ''; + + @property({ type: String }) + public subtitle: string = ''; + + public static styles = [ + cssManager.defaultStyles, + css` + :host { + display: block; + background: ${cssManager.bdTheme('#ffffff', '#1a1a1a')}; + border-radius: 8px; + padding: 24px; + box-shadow: 0 2px 4px ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(0,0,0,0.3)')}; + border: 1px solid ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(255,255,255,0.1)')}; + } + + .title { + margin: 0 0 16px 0; + font-size: 18px; + font-weight: 500; + color: ${cssManager.bdTheme('#0069f2', '#0099ff')}; + } + + .subtitle { + margin: -12px 0 16px 0; + font-size: 14px; + color: ${cssManager.bdTheme('#666', '#999')}; + } + + .content { + color: ${cssManager.bdTheme('#333', '#ccc')}; + } + + /* Remove margins from first and last children */ + .content ::slotted(*:first-child) { + margin-top: 0; + } + + .content ::slotted(*:last-child) { + margin-bottom: 0; + } + `, + ]; + + public render(): TemplateResult { + return html` + ${this.title ? html`

${this.title}

` : ''} + ${this.subtitle ? html`

${this.subtitle}

` : ''} +
+ +
+ `; + } +} \ No newline at end of file diff --git a/ts_web/elements/dees-simple-appdash.demo.ts b/ts_web/elements/dees-simple-appdash.demo.ts index 91ddc51..bf227d0 100644 --- a/ts_web/elements/dees-simple-appdash.demo.ts +++ b/ts_web/elements/dees-simple-appdash.demo.ts @@ -3,6 +3,8 @@ import type { IView } from './dees-simple-appdash.js'; 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-form-submit.js'; import './dees-statsgrid.js'; import type { IStatsTile } from './dees-statsgrid.js'; @@ -157,6 +159,12 @@ class DemoViewSettings extends DeesElement { margin: 0 0 15px 0; color: ${cssManager.bdTheme('#333', '#ccc')}; } + .horizontal-form-section { + background: ${cssManager.bdTheme('#f5f5f5', '#1a1a1a')}; + padding: 20px; + border-radius: 8px; + margin: 15px 0; + } ` ]; @@ -170,8 +178,68 @@ class DemoViewSettings extends DeesElement { + - Save Settings + + Save General Settings + +
+ +
+

Display Preferences

+
+

Quick display settings using horizontal layout:

+ + + + + +
+
+ +
+

Notification Settings

+ +
+
Email Frequency:
+ + + + +
+ + + Update Notifications
`; diff --git a/ts_web/elements/index.ts b/ts_web/elements/index.ts index 5d9026b..7a37d75 100644 --- a/ts_web/elements/index.ts +++ b/ts_web/elements/index.ts @@ -39,6 +39,7 @@ export * from './dees-label.js'; export * from './dees-mobilenavigation.js'; export * from './dees-modal.js'; export * from './dees-input-multitoggle.js'; +export * from './dees-panel.js'; export * from './dees-pdf.js'; export * from './dees-searchbar.js'; export * from './dees-simple-appdash.js';