feat: Enhance demo components with new input types and layout options

- Added dropdown and radio input components to the demo for application settings.
- Introduced horizontal layout for display preferences and notification settings.
- Implemented checkbox demo with programmatic selection and clear functionality.
- Created file upload and quantity selector demos with various states and configurations.
- Added comprehensive radio input demo showcasing group behavior and various states.
- Developed text input demo with validation states and advanced features like password visibility.
- Introduced a new panel component for better content organization in demos.
This commit is contained in:
Juergen Kunz
2025-06-19 11:39:16 +00:00
parent 79b1a4ea9f
commit 03315db863
26 changed files with 2547 additions and 333 deletions

View File

@ -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:
@ -172,3 +182,32 @@ All input components now have:
- Consistent label handling via dees-label
- Flexible layout modes
- 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.

View File

@ -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`
<dees-demowrapper>
<style>
.demoContainer {
max-width: 400px;
margin: 24px auto;
padding: 16px;
background: ${cssManager.bdTheme('#eeeeeb', '#111')};
box-shadow: 0px 1px 3px #00000030;
${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;
}
}
.form-container {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 24px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
@media (prefers-color-scheme: dark) {
.form-container {
background: #222;
border-color: #333;
}
}
.horizontal-form {
display: flex;
align-items: flex-start;
gap: 16px;
flex-wrap: wrap;
}
`}
</style>
<div class="demoContainer">
<div class="demo-container">
<div class="demo-section">
<h3>Complete Form Example</h3>
<p>A comprehensive form with various input types, validation, and form submission handling</p>
<div class="form-container">
<dees-form
style="display: block; margin:auto; max-width: 500px; padding: 20px"
@formData=${async (eventArg) => {
const form: DeesForm = eventArg.currentTarget;
form.setStatus('pending', 'authenticating...');
await domtools.plugins.smartdelay.delayFor(1000);
form.setStatus('success', 'authenticated!');
form.setStatus('pending', 'Processing...');
await domtools.plugins.smartdelay.delayFor(2000);
form.setStatus('success', 'Form submitted successfully!');
await domtools.plugins.smartdelay.delayFor(2000);
form.reset();
}}
>
<dees-input-text
.required=${true}
key="firstName"
label="First Name"
.description=${'Your given name'}
></dees-input-text>
<dees-input-text
.required=${true}
key="lastName"
label="Last Name"
></dees-input-text>
<dees-input-text
.required=${true}
key="email"
label="Email Address"
.description=${'We will use this to contact you'}
></dees-input-text>
<dees-input-dropdown
.label=${'title'}
.required=${true}
key="country"
.label=${'Country'}
.options=${[
{ option: 'option 1', key: 'option1' },
{ option: 'option 2', key: 'option2' },
{ option: 'option 3', key: 'option3' },
{ option: 'United States', key: 'us' },
{ option: 'Canada', key: 'ca' },
{ option: 'Germany', key: 'de' },
{ option: 'France', key: 'fr' },
{ option: 'United Kingdom', key: 'uk' },
]}
></dees-input-dropdown>
<dees-input-multiselect
.label=${'title'}
.options=${[
{ option: 'option 1', key: 'option1' },
{ option: 'option 2', key: 'option2' },
{ option: 'option 3', key: 'option3' },
]}></dees-input-multiselect>
<dees-input-typelist
.label=${'a type list'}
></dees-input-typelist>
<dees-input-text .required="${true}" key="hello1" label="a text" .description=${`
This is an awesome description.
`}></dees-input-text>
<dees-input-text .required="${true}" key="hello2" label="also a text"></dees-input-text>
<dees-input-text
.required="${true}"
key="hello3"
label="a password"
.required=${true}
key="password"
label="Password"
isPasswordBool
.description=${'Minimum 8 characters'}
></dees-input-text>
<dees-input-checkbox
.required="${true}"
key="hello3"
label="another text"
.required=${true}
key="terms"
label="I agree to the Terms and Conditions"
></dees-input-checkbox>
<dees-input-iban></dees-input-iban>
<dees-input-multitoggle
.label=${'multi select'}
.options=${['option 1', 'option 2', 'option 3']}
.selectedOption=${'option 1'}
></dees-input-multitoggle>
<dees-input-fileupload
.label=${'attachments'}
></dees-input-fileupload>
<dees-form-submit>Submit</dees-form-submit>
<dees-input-checkbox
key="newsletter"
label="Send me promotional emails"
.value=${true}
></dees-input-checkbox>
<dees-form-submit>Create Account</dees-form-submit>
</dees-form>
</div>
</div>
<div class="demo-section">
<h3>Horizontal Form Layout</h3>
<p>Compact form with inputs arranged horizontally - perfect for filters and quick forms</p>
<div class="form-container">
<dees-form horizontal-layout>
<dees-input-text
key="search"
label="Search"
></dees-input-text>
<dees-input-dropdown
key="category"
.label=${'Category'}
.enableSearch=${false}
.options=${[
{ option: 'All', key: 'all' },
{ option: 'Products', key: 'products' },
{ option: 'Services', key: 'services' },
{ option: 'Support', key: 'support' },
]}
></dees-input-dropdown>
<dees-input-dropdown
key="sort"
.label=${'Sort By'}
.enableSearch=${false}
.options=${[
{ option: 'Newest', key: 'newest' },
{ option: 'Popular', key: 'popular' },
{ option: 'Price: Low to High', key: 'price_asc' },
{ option: 'Price: High to Low', key: 'price_desc' },
]}
></dees-input-dropdown>
<dees-input-checkbox
key="inStock"
label="In Stock Only"
.value=${true}
></dees-input-checkbox>
</dees-form>
</div>
</div>
<div class="demo-section">
<h3>Advanced Form Features</h3>
<p>Form with specialized input types and complex validation</p>
<div class="form-container">
<dees-form
@formData=${async (eventArg) => {
const form: DeesForm = eventArg.currentTarget;
const data = eventArg.detail.data;
console.log('Form data:', data);
form.setStatus('success', 'Data logged to console!');
}}
>
<dees-input-iban
key="iban"
label="IBAN"
.required=${true}
></dees-input-iban>
<dees-input-phone
key="phone"
label="Phone Number"
.required=${true}
></dees-input-phone>
<dees-input-multitoggle
key="preferences"
.label=${'Notification Preferences'}
.options=${['Email', 'SMS', 'Push', 'In-App']}
.selectedOption=${'Email'}
></dees-input-multitoggle>
<dees-input-multiselect
key="interests"
.label=${'Areas of Interest'}
.options=${[
{ option: 'Technology', key: 'tech' },
{ option: 'Design', key: 'design' },
{ option: 'Business', key: 'business' },
{ option: 'Marketing', key: 'marketing' },
{ option: 'Sales', key: 'sales' },
]}
></dees-input-multiselect>
<dees-input-fileupload
key="documents"
.label=${'Upload Documents'}
.description=${'PDF, DOC, or DOCX files up to 10MB'}
></dees-input-fileupload>
<dees-form-submit>Submit Application</dees-form-submit>
</dees-form>
</div>
</div>
</div>
</dees-demowrapper>
`;

View File

@ -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<any>;
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.`);

View File

@ -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`
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
// 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;
}
});
});
}
}}>
<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;
}
.checkbox-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.feature-list {
background: #f0f0f0;
border-radius: 4px;
padding: 16px;
margin-bottom: 16px;
}
@media (prefers-color-scheme: dark) {
.feature-list {
background: #0a0a0a;
}
}
.button-group {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
`}
</style>
<div class="demo-container">
<div class="demo-section">
<h3>Basic Checkboxes</h3>
<p>Standard checkbox inputs for boolean selections</p>
<dees-input-checkbox
.label=${'I agree to the Terms and Conditions'}
.value=${true}
.key=${'terms'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Subscribe to newsletter'}
.value=${false}
.key=${'newsletter'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Enable notifications'}
.required=${true}
.key=${'notifications'}
></dees-input-checkbox>
</div>
<div class="demo-section">
<h3>Horizontal Layout</h3>
<p>Checkboxes arranged horizontally for compact forms</p>
<div class="horizontal-group">
<dees-input-checkbox
.label=${'Option A'}
.layoutMode=${'horizontal'}
.key=${'optionA'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Option B'}
.layoutMode=${'horizontal'}
.value=${true}
.key=${'optionB'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Option C'}
.layoutMode=${'horizontal'}
.key=${'optionC'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Option D'}
.layoutMode=${'horizontal'}
.value=${true}
.key=${'optionD'}
></dees-input-checkbox>
</div>
</div>
<div class="demo-section">
<h3>Feature Selection Example</h3>
<p>Common use case for feature toggles with batch operations</p>
<div class="button-group">
<dees-button id="select-all-btn" type="secondary">Select All</dees-button>
<dees-button id="clear-all-btn" type="secondary">Clear All</dees-button>
</div>
<div class="feature-list">
<div class="checkbox-group">
<dees-input-checkbox
.label=${'Dark Mode Support'}
.value=${true}
.key=${'feature1'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Email Notifications'}
.value=${true}
.key=${'feature2'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Two-Factor Authentication'}
.value=${false}
.key=${'feature3'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'API Access'}
.value=${true}
.key=${'feature4'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Advanced Analytics'}
.value=${false}
.key=${'feature5'}
></dees-input-checkbox>
</div>
</div>
</div>
<div class="demo-section">
<h3>States</h3>
<p>Different checkbox states and configurations</p>
<dees-input-checkbox
.label=${'Disabled Unchecked'}
.disabled=${true}
.key=${'disabled1'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Disabled Checked'}
.disabled=${true}
.value=${true}
.key=${'disabled2'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Required Checkbox'}
.required=${true}
.key=${'required'}
></dees-input-checkbox>
</div>
<div class="demo-section">
<h3>Real-world Examples</h3>
<p>Common checkbox patterns in applications</p>
<div class="checkbox-group">
<dees-input-checkbox
.label=${'Remember me on this device'}
.value=${true}
.key=${'rememberMe'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Make my profile public'}
.value=${false}
.key=${'publicProfile'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Allow others to find me by email'}
.value=${false}
.key=${'findByEmail'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Send me product updates and announcements'}
.value=${true}
.key=${'productUpdates'}
></dees-input-checkbox>
</div>
</div>
</div>
</dees-demowrapper>
`;

View File

@ -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<DeesInputCheckbox> {
// STATIC
public static demo = () => html`<dees-input-checkbox></dees-input-checkbox>`;
public static demo = demoFunc;
// INSTANCE

View File

@ -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`
<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;
position: relative;
}
@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;
}
.spacer {
height: 200px;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 14px;
}
`}
</style>
<div class="demo-container">
<div class="demo-section">
<h3>Basic Dropdowns</h3>
<p>Standard dropdown with search functionality and various options</p>
<dees-input-dropdown
.label=${'Select Country'}
.options=${[
{option: 'option 1', key: 'option1'},
{option: 'option 2', key: 'option2'},
{option: 'option 3', key: 'option3'}
{ option: 'United States', key: 'us' },
{ option: 'Canada', key: 'ca' },
{ option: 'Germany', key: 'de' },
{ option: 'France', key: 'fr' },
{ option: 'United Kingdom', key: 'uk' },
{ option: 'Australia', key: 'au' },
{ option: 'Japan', key: 'jp' },
{ option: 'Brazil', key: 'br' }
]}
.selectedOption=${{ option: 'United States', key: 'us' }}
></dees-input-dropdown>
<dees-input-dropdown
.label=${'Select Role'}
.options=${[
{ option: 'Administrator', key: 'admin' },
{ option: 'Editor', key: 'editor' },
{ option: 'Viewer', key: 'viewer' },
{ option: 'Guest', key: 'guest' }
]}
></dees-input-dropdown>
</div>
<div class="demo-section">
<h3>Without Search</h3>
<p>Dropdown with search functionality disabled for simpler selection</p>
<dees-input-dropdown
.label=${'Priority Level'}
.enableSearch=${false}
.options=${[
{option: 'option 1', key: 'option1'},
{option: 'option 2', key: 'option2'},
{option: 'option 3', key: 'option3'}
{ option: 'High', key: 'high' },
{ option: 'Medium', key: 'medium' },
{ option: 'Low', key: 'low' }
]}
.selectedOption=${{ option: 'Medium', key: 'medium' }}
></dees-input-dropdown>
<div style="height: 300px"></div>
</div>
<div class="demo-section">
<h3>Horizontal Layout</h3>
<p>Multiple dropdowns in a horizontal layout for compact forms</p>
<div class="horizontal-group">
<dees-input-dropdown
.label=${'Department'}
.layoutMode=${'horizontal'}
.options=${[
{option: 'option 1', key: 'option1'},
{option: 'option 2', key: 'option2'},
{option: 'option 3', key: 'option3'}
{ option: 'Engineering', key: 'eng' },
{ option: 'Design', key: 'design' },
{ option: 'Marketing', key: 'marketing' },
{ option: 'Sales', key: 'sales' }
]}
></dees-input-dropdown>
<dees-input-dropdown
.label=${'Team Size'}
.layoutMode=${'horizontal'}
.enableSearch=${false}
.options=${[
{ option: '1-5', key: 'small' },
{ option: '6-20', key: 'medium' },
{ option: '21-50', key: 'large' },
{ option: '50+', key: 'xlarge' }
]}
></dees-input-dropdown>
<dees-input-dropdown
.label=${'Location'}
.layoutMode=${'horizontal'}
.options=${[
{ option: 'Remote', key: 'remote' },
{ option: 'On-site', key: 'onsite' },
{ option: 'Hybrid', key: 'hybrid' }
]}
></dees-input-dropdown>
</div>
</div>
<div class="demo-section">
<h3>States</h3>
<p>Different states and configurations</p>
<dees-input-dropdown
.label=${'Required Field'}
.required=${true}
.options=${[
{ option: 'Option A', key: 'a' },
{ option: 'Option B', key: 'b' },
{ option: 'Option C', key: 'c' }
]}
></dees-input-dropdown>
<dees-input-dropdown
.label=${'Disabled Dropdown'}
.disabled=${true}
.options=${[
{ option: 'Cannot Select', key: 'disabled' }
]}
.selectedOption=${{ option: 'Cannot Select', key: 'disabled' }}
></dees-input-dropdown>
</div>
<div class="spacer">
(Spacer to test dropdown positioning)
</div>
<div class="demo-section">
<h3>Bottom Positioning</h3>
<p>Dropdown that opens upward when near bottom of viewport</p>
<dees-input-dropdown
.label=${'Opens Upward'}
.options=${[
{ option: 'First Option', key: 'first' },
{ option: 'Second Option', key: 'second' },
{ option: 'Third Option', key: 'third' },
{ option: 'Fourth Option', key: 'fourth' },
{ option: 'Fifth Option', key: 'fifth' }
]}
></dees-input-dropdown>
</div>
</div>
</dees-demowrapper>
`

View File

@ -0,0 +1,138 @@
import { html, css, cssManager } from '@design.estate/dees-element';
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;
}
.upload-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media (max-width: 768px) {
.upload-grid {
grid-template-columns: 1fr;
}
}
.upload-box {
padding: 16px;
background: ${cssManager.bdTheme('#fff', '#2a2a2a')};
border-radius: 4px;
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#444')};
}
.upload-box h4 {
margin-top: 0;
margin-bottom: 16px;
color: ${cssManager.bdTheme('#333', '#fff')};
font-size: 16px;
}
.info-section {
margin-top: 32px;
padding: 16px;
background: ${cssManager.bdTheme('#fff3cd', '#332701')};
border: 1px solid ${cssManager.bdTheme('#ffeaa7', '#664400')};
border-radius: 4px;
color: ${cssManager.bdTheme('#856404', '#ffecb5')};
}
`}
</style>
<div class="demo-container">
<dees-panel .title=${'Basic File Upload'} .subtitle=${'Simple file upload with drag and drop support'}>
<dees-input-fileupload
.label=${'Attachments'}
.description=${'Upload files by clicking or dragging'}
></dees-input-fileupload>
<dees-input-fileupload
.label=${'Resume'}
.description=${'Upload your CV in PDF format'}
.buttonText=${'Choose Resume...'}
></dees-input-fileupload>
</dees-panel>
<dees-panel .title=${'Multiple Upload Areas'} .subtitle=${'Different upload zones for various file types'}>
<div class="upload-grid">
<div class="upload-box">
<h4>Profile Picture</h4>
<dees-input-fileupload
.label=${'Avatar'}
.description=${'JPG, PNG or GIF'}
.buttonText=${'Select Image...'}
></dees-input-fileupload>
</div>
<div class="upload-box">
<h4>Cover Image</h4>
<dees-input-fileupload
.label=${'Banner'}
.description=${'Recommended: 1200x400px'}
.buttonText=${'Select Banner...'}
></dees-input-fileupload>
</div>
</div>
</dees-panel>
<dees-panel .title=${'Required & Disabled States'} .subtitle=${'Different upload states for validation'}>
<dees-input-fileupload
.label=${'Identity Document'}
.description=${'Required for verification'}
.required=${true}
.buttonText=${'Upload Document...'}
></dees-input-fileupload>
<dees-input-fileupload
.label=${'System Files'}
.description=${'File upload is disabled'}
.disabled=${true}
.value=${[]}
></dees-input-fileupload>
</dees-panel>
<dees-panel .title=${'Application Form'} .subtitle=${'Complete form with file upload integration'}>
<dees-form>
<dees-input-text .label=${'Full Name'} .required=${true}></dees-input-text>
<dees-input-text .label=${'Email'} .inputType=${'email'} .required=${true}></dees-input-text>
<dees-input-fileupload
.label=${'Resume'}
.description=${'Upload your CV (PDF preferred)'}
.required=${true}
></dees-input-fileupload>
<dees-input-fileupload
.label=${'Portfolio'}
.description=${'Optional: Upload work samples'}
></dees-input-fileupload>
<dees-input-text
.label=${'Cover Letter'}
.inputType=${'textarea'}
.description=${'Tell us why you would be a great fit'}
></dees-input-text>
</dees-form>
<div class="info-section">
<h4>Features:</h4>
<ul>
<li>Click to select files or drag & drop</li>
<li>Multiple file selection support</li>
<li>Visual feedback for drag operations</li>
<li>Right-click files to remove them</li>
<li>Integrates seamlessly with forms</li>
</ul>
</div>
</dees-panel>
</div>
</dees-demowrapper>
`;

View File

@ -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`<dees-input-fileupload .label=${'Attachments'}></dees-input-fileupload>`;
export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
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,10 +145,11 @@ export class DeesInputFileupload extends DeesElement {
public render(): TemplateResult {
return html`
<div class="input-wrapper">
<dees-label .label=${this.label} .description=${this.description}></dees-label>
<div class="hidden">
<input type="file"></div>
<input type="file">
</div>
${this.label ? html`<div class="label">${this.label}</div>` : null}
<div class="maincontainer ${this.state === 'dragOver' ? 'dragOver' : ''}">
${this.value.map(
(fileArg) => html`
@ -205,6 +178,7 @@ export class DeesInputFileupload extends DeesElement {
${this.buttonText}
</div>
</div>
</div>
`;
}
@ -221,7 +195,8 @@ export class DeesInputFileupload extends DeesElement {
this.changeSubject.next(this);
}
public firstUpdated() {
public firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
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;
}
}

View File

@ -1,3 +1,80 @@
import { html } from '@design.estate/dees-element';
import { html, css } from '@design.estate/dees-element';
export const demoFunc = () => html`<dees-input-iban .label=${'IBAN'}></dees-input-iban>`;
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;
}
.payment-group {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
`}
</style>
<div class="demo-container">
<dees-panel .title=${'Basic IBAN Input'} .subtitle=${'International Bank Account Number with automatic formatting'}>
<dees-input-iban
.label=${'Bank Account IBAN'}
.description=${'Enter your International Bank Account Number'}
></dees-input-iban>
<dees-input-iban
.label=${'Verified IBAN'}
.description=${'This IBAN has been verified'}
.value=${'DE89370400440532013000'}
></dees-input-iban>
</dees-panel>
<dees-panel .title=${'Payment Information'} .subtitle=${'IBAN input with horizontal layout for payment forms'}>
<div class="payment-group">
<dees-input-text
.label=${'Account Holder'}
.layoutMode=${'horizontal'}
.value=${'John Doe'}
></dees-input-text>
<dees-input-iban
.label=${'IBAN'}
.layoutMode=${'horizontal'}
.value=${'GB82WEST12345698765432'}
></dees-input-iban>
</div>
</dees-panel>
<dees-panel .title=${'Validation & States'} .subtitle=${'Required fields and disabled states'}>
<dees-input-iban
.label=${'Payment Account'}
.description=${'Required for processing payments'}
.required=${true}
></dees-input-iban>
<dees-input-iban
.label=${'Locked IBAN'}
.description=${'This IBAN cannot be changed'}
.value=${'FR1420041010050500013M02606'}
.disabled=${true}
></dees-input-iban>
</dees-panel>
<dees-panel .title=${'Bank Transfer Form'} .subtitle=${'Complete form example with IBAN validation'}>
<dees-form>
<dees-input-text .label=${'Recipient Name'} .required=${true}></dees-input-text>
<dees-input-iban .label=${'Recipient IBAN'} .required=${true}></dees-input-iban>
<dees-input-text .label=${'Transfer Reference'} .description=${'Optional reference for the transfer'}></dees-input-text>
<dees-input-text .label=${'Amount'} .inputType=${'number'} .required=${true}></dees-input-text>
</dees-form>
</dees-panel>
</div>
</dees-demowrapper>
`;

View File

@ -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<DeesInputIban> {
// 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<DeesInputIban>();
public static styles = [
...DeesInputBase.baseStyles,
cssManager.defaultStyles,
css`
/* IBAN input specific styles can go here */
`,
];
public render(): TemplateResult {
return html`
<style>
input[type='text'] {
line-height: 20px;
padding: 5px;
width: 250px;
}
</style>
<div class="input-wrapper">
<dees-label .label=${this.label || 'IBAN'} .description=${this.description}></dees-label>
<dees-input-text
.label=${'IBAN'}
.value=${this.value}
.disabled=${this.disabled}
.required=${this.required}
.placeholder=${'DE89 3704 0044 0532 0130 00'}
@input=${(eventArg: InputEvent) => {
this.validateIban(eventArg);
}}
></dees-input-text>
</div>
`;
}
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;
public firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
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<void> {
@ -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) || '';
}
}

View File

@ -1,14 +1,128 @@
import { html } from '@design.estate/dees-element';
import { html, css } from '@design.estate/dees-element';
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;
}
.settings-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media (max-width: 768px) {
.settings-grid {
grid-template-columns: 1fr;
}
}
`}
</style>
<div class="demo-container">
<dees-panel .title=${'Multi-Option Toggle'} .subtitle=${'Select from multiple options with a sliding indicator'}>
<dees-input-multitoggle
.options=${['option 1', 'option 2', 'a longer option with multiple words']}
.selectedOption=${'option 2'}
.label=${'Display Mode'}
.description=${'Choose how content is displayed'}
.options=${['List View', 'Grid View', 'Compact']}
.selectedOption=${'Grid View'}
></dees-input-multitoggle>
<dees-input-multitoggle
.label=${'T-Shirt Size'}
.description=${'Select your preferred size'}
.options=${['XS', 'S', 'M', 'L', 'XL', 'XXL']}
.selectedOption=${'M'}
></dees-input-multitoggle>
</dees-panel>
<dees-panel .title=${'Boolean Toggle'} .subtitle=${'Simple on/off switches with custom labels'}>
<dees-input-multitoggle
.label=${'Notifications'}
.description=${'Enable or disable push notifications'}
.type=${'boolean'}
.booleanTrueName=${'enabled'}
.booleanFalseName=${'disabled'}
.selectedOption=${'true'}
></dees-input-multitoggle>
<dees-input-multitoggle
.label=${'Theme Mode'}
.description=${'Switch between light and dark theme'}
.type=${'boolean'}
.booleanTrueName=${'Dark'}
.booleanFalseName=${'Light'}
.selectedOption=${'Dark'}
></dees-input-multitoggle>
</dees-panel>
<dees-panel .title=${'Settings Panel'} .subtitle=${'Configuration options in a horizontal layout'}>
<div class="settings-grid">
<dees-input-multitoggle
.label=${'Auto-Save'}
.layoutMode=${'horizontal'}
.type=${'boolean'}
.booleanTrueName=${'Enabled'}
.booleanFalseName=${'Disabled'}
.selectedOption=${'Enabled'}
></dees-input-multitoggle>
<dees-input-multitoggle
.label=${'Language'}
.layoutMode=${'horizontal'}
.options=${['English', 'German', 'French', 'Spanish']}
.selectedOption=${'English'}
></dees-input-multitoggle>
<dees-input-multitoggle
.label=${'Quality'}
.layoutMode=${'horizontal'}
.options=${['Low', 'Medium', 'High', 'Ultra']}
.selectedOption=${'High'}
></dees-input-multitoggle>
<dees-input-multitoggle
.label=${'Privacy'}
.layoutMode=${'horizontal'}
.type=${'boolean'}
.booleanTrueName=${'Private'}
.booleanFalseName=${'Public'}
.selectedOption=${'Private'}
></dees-input-multitoggle>
</div>
</dees-panel>
<dees-panel .title=${'States & Form Integration'} .subtitle=${'Disabled states and form usage'}>
<dees-input-multitoggle
.label=${'Account Type'}
.description=${'This setting is locked'}
.options=${['Free', 'Pro', 'Enterprise']}
.selectedOption=${'Enterprise'}
.disabled=${true}
></dees-input-multitoggle>
<dees-form>
<dees-input-text .label=${'Project Name'} .required=${true}></dees-input-text>
<dees-input-multitoggle
.label=${'Visibility'}
.type=${'boolean'}
.booleanTrueName=${'Public'}
.booleanFalseName=${'Private'}
.selectedOption=${'Private'}
></dees-input-multitoggle>
<dees-input-multitoggle
.label=${'License'}
.options=${['MIT', 'Apache 2.0', 'GPL v3', 'Proprietary']}
.selectedOption=${'MIT'}
></dees-input-multitoggle>
</dees-form>
</dees-panel>
</div>
</dees-demowrapper>
`;

View File

@ -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<DeesInputMultitoggle> {
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,14 +113,20 @@ 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`
<div class="input-wrapper">
<dees-label .label=${this.label} .description=${this.description}></dees-label>
<div class="mainbox">
<div class="selections">
@ -127,14 +139,29 @@ export class DeesInputMultitoggle extends DeesElement {
)}
</div>
</div>
</div>
`;
}
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<string | number | symbol, unknown>) {
super.firstUpdated(_changedProperties);
// Update boolean options if they changed
if (this.type === 'boolean') {
this.options = [this.booleanTrueName || 'true', this.booleanFalseName || 'false'];
}
// 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();
}
}
}

View File

@ -1,3 +1,80 @@
import { html } from '@design.estate/dees-element';
import { html, css } from '@design.estate/dees-element';
export const demoFunc = () => html`<dees-input-phone .label=${'Phone Number'}></dees-input-phone>`;
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;
}
.horizontal-group {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
`}
</style>
<div class="demo-container">
<dees-panel .title=${'Basic Phone Input'} .subtitle=${'Automatic formatting for phone numbers'}>
<dees-input-phone
.label=${'Phone Number'}
.description=${'Enter your phone number with country code'}
.value=${'5551234567'}
></dees-input-phone>
<dees-input-phone
.label=${'Contact Phone'}
.description=${'Required for account verification'}
.required=${true}
.placeholder=${'+1 (555) 000-0000'}
></dees-input-phone>
</dees-panel>
<dees-panel .title=${'Horizontal Layout'} .subtitle=${'Phone inputs arranged horizontally'}>
<div class="horizontal-group">
<dees-input-phone
.label=${'Mobile'}
.layoutMode=${'horizontal'}
.value=${'4155551234'}
></dees-input-phone>
<dees-input-phone
.label=${'Office'}
.layoutMode=${'horizontal'}
.placeholder=${'+1 (800) 555-0000'}
></dees-input-phone>
</div>
</dees-panel>
<dees-panel .title=${'International Numbers'} .subtitle=${'Supports formatting for numbers with country codes'}>
<dees-input-phone
.label=${'International Contact'}
.description=${'Automatically formats international numbers'}
.value=${'441234567890'}
></dees-input-phone>
<dees-input-phone
.label=${'Emergency Contact'}
.value=${'911'}
.disabled=${true}
></dees-input-phone>
</dees-panel>
<dees-panel .title=${'Form Integration'} .subtitle=${'Phone input as part of a contact form'}>
<dees-form>
<dees-input-text .label=${'Full Name'} .required=${true}></dees-input-text>
<dees-input-phone .label=${'Phone Number'} .required=${true}></dees-input-phone>
<dees-input-text .label=${'Email'} .inputType=${'email'}></dees-input-text>
</dees-form>
</dees-panel>
</div>
</dees-demowrapper>
`;

View File

@ -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<DeesInputPhone> {
// STATIC
public static demo = demoFunc;
// INSTANCE
public render() {
return html`<div>Phone Input</div>`;
@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`
<div class="input-wrapper">
<dees-label .label=${this.label} .description=${this.description}></dees-label>
<dees-input-text
.value=${this.formattedPhone}
.disabled=${this.disabled}
.required=${this.required}
.placeholder=${this.placeholder}
@input=${(event: InputEvent) => this.handlePhoneInput(event)}
></dees-input-text>
</div>
`;
}
public firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
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);
}
}

View File

@ -0,0 +1,127 @@
import { html, css, cssManager } from '@design.estate/dees-element';
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;
}
.shopping-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 16px;
}
.product-card {
padding: 16px;
background: ${cssManager.bdTheme('#fff', '#2a2a2a')};
border-radius: 4px;
box-shadow: 0 2px 4px ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(0,0,0,0.3)')};
}
.product-name {
font-weight: 600;
margin-bottom: 8px;
}
.product-price {
color: #1976d2;
margin-bottom: 16px;
}
`}
</style>
<div class="demo-container">
<dees-panel .title=${'Basic Quantity Selector'} .subtitle=${'Simple quantity input with increment/decrement buttons'}>
<dees-input-quantityselector
.label=${'Quantity'}
.description=${'Select the desired quantity'}
.value=${1}
></dees-input-quantityselector>
<dees-input-quantityselector
.label=${'Items in Cart'}
.description=${'Adjust the quantity of items'}
.value=${3}
></dees-input-quantityselector>
</dees-panel>
<dees-panel .title=${'Shopping Cart'} .subtitle=${'Product cards with quantity selectors'}>
<div class="shopping-grid">
<div class="product-card">
<div class="product-name">Premium Headphones</div>
<div class="product-price">$199.99</div>
<dees-input-quantityselector
.label=${'Quantity'}
.layoutMode=${'horizontal'}
.value=${1}
></dees-input-quantityselector>
</div>
<div class="product-card">
<div class="product-name">Wireless Mouse</div>
<div class="product-price">$49.99</div>
<dees-input-quantityselector
.label=${'Quantity'}
.layoutMode=${'horizontal'}
.value=${2}
></dees-input-quantityselector>
</div>
<div class="product-card">
<div class="product-name">USB-C Cable</div>
<div class="product-price">$19.99</div>
<dees-input-quantityselector
.label=${'Quantity'}
.layoutMode=${'horizontal'}
.value=${1}
></dees-input-quantityselector>
</div>
</div>
</dees-panel>
<dees-panel .title=${'Required & Disabled States'} .subtitle=${'Different states for validation and restrictions'}>
<dees-input-quantityselector
.label=${'Number of Licenses'}
.description=${'Select how many licenses you need'}
.required=${true}
.value=${1}
></dees-input-quantityselector>
<dees-input-quantityselector
.label=${'Fixed Quantity'}
.description=${'This quantity cannot be changed'}
.disabled=${true}
.value=${5}
></dees-input-quantityselector>
</dees-panel>
<dees-panel .title=${'Order Form'} .subtitle=${'Complete order form with quantity selection'}>
<dees-form>
<dees-input-text .label=${'Customer Name'} .required=${true}></dees-input-text>
<dees-input-dropdown
.label=${'Product'}
.options=${['Basic Plan', 'Pro Plan', 'Enterprise Plan']}
.required=${true}
></dees-input-dropdown>
<dees-input-quantityselector
.label=${'Quantity'}
.description=${'Number of licenses'}
.value=${1}
></dees-input-quantityselector>
<dees-input-text
.label=${'Special Instructions'}
.inputType=${'textarea'}
></dees-input-text>
</dees-form>
</dees-panel>
</div>
</dees-demowrapper>
`;

View File

@ -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`<dees-input-quantityselector></dees-input-quantityselector>`;
export class DeesInputQuantitySelector extends DeesInputBase<DeesInputQuantitySelector> {
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}
<style>
public static styles = [
...DeesInputBase.baseStyles,
cssManager.defaultStyles,
css`
:host {
display: block;
width: 110px;
width: auto;
user-select: none;
}
.maincontainer {
.quantity-container {
transition: all 0.1s;
font-size: 14px;
display: grid;
grid-template-columns: 33% 34% 33%;
text-align: center;
background:none;
background: ${cssManager.bdTheme('#fafafa', '#222222')};
line-height: 40px;
padding: 0px;
min-width: 100px;
color: ${this.goBright ? '#666' : '#CCC'};
border: ${this.goBright ? '1px solid #333' : '1px solid #CCC'};
min-width: 110px;
color: ${cssManager.bdTheme('#666', '#CCC')};
border: 1px solid ${cssManager.bdTheme('#CCC', '#444')};
border-radius: 4px;
}
.mainContainer:hover {
color: ${this.goBright ? '#333' : '#fff'};
border: ${this.goBright ? '1px solid #333' : '1px solid #fff'};
.quantity-container.disabled {
opacity: 0.5;
pointer-events: none;
}
.quantity-container:hover {
color: ${cssManager.bdTheme('#333', '#fff')};
border-color: ${cssManager.bdTheme('#999', '#666')};
}
.minus {
@ -91,28 +76,41 @@ export class DeesInputQuantitySelector extends DeesElement {
text-align: center;
}
`,
];
</style>
<div class="maincontainer">
public render(): TemplateResult {
return html`
<div class="input-wrapper">
<dees-label .label=${this.label}></dees-label>
<div class="quantity-container ${this.disabled ? 'disabled' : ''}">
<div class="selector minus" @click="${() => {this.decrease();}}">-</div>
<div class="quantity">${this.value}</div>
<div class="selector plus" @click="${() => {this.increase();}}">+</div>
</div>
</div>
`;
}
public increase() {
if (!this.disabled) {
this.value++;
this.changeSubject.next(this);
}
}
public decrease() {
if (this.value > 0) {
if (!this.disabled && this.value > 0) {
this.value--;
} else {
// nothing to do here
}
this.changeSubject.next(this);
}
}
public getValue(): number {
return this.value;
}
public setValue(value: number): void {
this.value = value;
}
}

View File

@ -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`
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
// Implement radio group behavior
const radioGroups = new Map<string, DeesInputRadio[]>();
// 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;
}
});
});
});
}}>
<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" data-group="plan">
<div class="radio-group-title">Select your subscription plan:</div>
<dees-input-radio
.label=${'Basic Plan - $9/month'}
.value=${true}
.key=${'plan-basic'}
></dees-input-radio>
<dees-input-radio
.label=${'Pro Plan - $29/month'}
.key=${'plan-pro'}
></dees-input-radio>
<dees-input-radio
.label=${'Enterprise Plan - $99/month'}
.key=${'plan-enterprise'}
></dees-input-radio>
</div>
<div class="radio-group" data-group="priority">
<div class="radio-group-title">Task Priority:</div>
<dees-input-radio
.label=${'High Priority'}
.key=${'priority-high'}
></dees-input-radio>
<dees-input-radio
.label=${'Medium Priority'}
.value=${true}
.key=${'priority-medium'}
></dees-input-radio>
<dees-input-radio
.label=${'Low Priority'}
.key=${'priority-low'}
></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" data-group="agreement" 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'}
></dees-input-radio>
<dees-input-radio
.label=${'No'}
.layoutMode=${'horizontal'}
.key=${'agree-no'}
></dees-input-radio>
<dees-input-radio
.label=${'Maybe'}
.layoutMode=${'horizontal'}
.key=${'agree-maybe'}
></dees-input-radio>
</div>
<div class="radio-group" data-group="experience" style="flex-direction: row;">
<div style="margin-right: 16px;">Experience Level:</div>
<dees-input-radio
.label=${'Beginner'}
.layoutMode=${'horizontal'}
.key=${'exp-beginner'}
></dees-input-radio>
<dees-input-radio
.label=${'Intermediate'}
.layoutMode=${'horizontal'}
.value=${true}
.key=${'exp-intermediate'}
></dees-input-radio>
<dees-input-radio
.label=${'Expert'}
.layoutMode=${'horizontal'}
.key=${'exp-expert'}
></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" data-group="satisfaction">
<div class="radio-group-title">How satisfied are you?</div>
<dees-input-radio .label=${'Very Satisfied'} .key=${'sat-very'}></dees-input-radio>
<dees-input-radio .label=${'Satisfied'} .value=${true} .key=${'sat-normal'}></dees-input-radio>
<dees-input-radio .label=${'Neutral'} .key=${'sat-neutral'}></dees-input-radio>
<dees-input-radio .label=${'Dissatisfied'} .key=${'sat-dis'}></dees-input-radio>
<dees-input-radio .label=${'Very Dissatisfied'} .key=${'sat-verydis'}></dees-input-radio>
</div>
<div class="radio-group" data-group="recommend">
<div class="radio-group-title">Would you recommend us?</div>
<dees-input-radio .label=${'Definitely'} .key=${'rec-def'}></dees-input-radio>
<dees-input-radio .label=${'Probably'} .value=${true} .key=${'rec-prob'}></dees-input-radio>
<dees-input-radio .label=${'Not Sure'} .key=${'rec-unsure'}></dees-input-radio>
<dees-input-radio .label=${'Probably Not'} .key=${'rec-probnot'}></dees-input-radio>
<dees-input-radio .label=${'Definitely Not'} .key=${'rec-defnot'}></dees-input-radio>
</div>
</div>
</div>
<div class="demo-section">
<h3>States</h3>
<p>Different radio button states</p>
<div class="radio-group" data-group="states">
<dees-input-radio
.label=${'Normal Radio'}
.key=${'state-normal'}
></dees-input-radio>
<dees-input-radio
.label=${'Selected Radio'}
.value=${true}
.key=${'state-selected'}
></dees-input-radio>
<dees-input-radio
.label=${'Disabled Unchecked'}
.disabled=${true}
.key=${'state-disabled1'}
></dees-input-radio>
<dees-input-radio
.label=${'Disabled Checked'}
.disabled=${true}
.value=${true}
.key=${'state-disabled2'}
></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" data-group="theme">
<div class="radio-group-title">Theme Preference:</div>
<dees-input-radio .label=${'Light Theme'} .key=${'theme-light'}></dees-input-radio>
<dees-input-radio .label=${'Dark Theme'} .value=${true} .key=${'theme-dark'}></dees-input-radio>
<dees-input-radio .label=${'System Default'} .key=${'theme-system'}></dees-input-radio>
</div>
<div class="radio-group" data-group="notifications">
<div class="radio-group-title">Notification Frequency:</div>
<dees-input-radio .label=${'All Notifications'} .key=${'notif-all'}></dees-input-radio>
<dees-input-radio .label=${'Important Only'} .value=${true} .key=${'notif-important'}></dees-input-radio>
<dees-input-radio .label=${'None'} .key=${'notif-none'}></dees-input-radio>
</div>
</div>
</div>
</dees-demowrapper>
`;

View File

@ -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<DeesInputRadio> {
public static demo = () => html`<dees-input-radio></dees-input-radio>`;
public static demo = demoFunc;
// INSTANCE

View File

@ -0,0 +1,199 @@
import { html, css } from '@design.estate/dees-element';
import '@design.estate/dees-wcctools/demotools';
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;
}
.grid-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
@media (max-width: 768px) {
.grid-layout {
grid-template-columns: 1fr;
}
}
`}
</style>
<div class="demo-container">
<div class="demo-section">
<h3>Basic Text Inputs</h3>
<p>Standard text inputs with labels and descriptions</p>
<dees-input-text
.label=${'Username'}
.value=${'johndoe'}
.key=${'username'}
></dees-input-text>
<dees-input-text
.label=${'Email Address'}
.value=${'john@example.com'}
.description=${'We will never share your email with anyone'}
.key=${'email'}
></dees-input-text>
<dees-input-text
.label=${'Password'}
.isPasswordBool=${true}
.value=${'secret123'}
.key=${'password'}
></dees-input-text>
</div>
<div class="demo-section">
<h3>Horizontal Layout</h3>
<p>Multiple inputs arranged horizontally for compact forms</p>
<div class="horizontal-group">
<dees-input-text
.label=${'First Name'}
.value=${'John'}
.layoutMode=${'horizontal'}
.key=${'firstName'}
></dees-input-text>
<dees-input-text
.label=${'Last Name'}
.value=${'Doe'}
.layoutMode=${'horizontal'}
.key=${'lastName'}
></dees-input-text>
<dees-input-text
.label=${'Age'}
.value=${'28'}
.layoutMode=${'horizontal'}
.key=${'age'}
></dees-input-text>
</div>
</div>
<div class="demo-section">
<h3>Label Positions</h3>
<p>Different label positioning options for various layouts</p>
<dees-input-text
.label=${'Label on Top (Default)'}
.value=${'Standard layout'}
.labelPosition=${'top'}
></dees-input-text>
<dees-input-text
.label=${'Label on Left'}
.value=${'Inline label'}
.labelPosition=${'left'}
></dees-input-text>
<div class="grid-layout">
<dees-input-text
.label=${'City'}
.value=${'New York'}
.labelPosition=${'left'}
></dees-input-text>
<dees-input-text
.label=${'ZIP Code'}
.value=${'10001'}
.labelPosition=${'left'}
></dees-input-text>
</div>
</div>
<div class="demo-section">
<h3>Validation & States</h3>
<p>Different validation states and input configurations</p>
<dees-input-text
.label=${'Required Field'}
.required=${true}
.key=${'requiredField'}
></dees-input-text>
<dees-input-text
.label=${'Disabled Field'}
.value=${'Cannot edit this'}
.disabled=${true}
></dees-input-text>
<dees-input-text
.label=${'Field with Error'}
.value=${'invalid@'}
.validationText=${'Please enter a valid email address'}
.validationState=${'invalid'}
></dees-input-text>
</div>
<div class="demo-section">
<h3>Advanced Features</h3>
<p>Password visibility toggle and other advanced features</p>
<dees-input-text
.label=${'Password with Toggle'}
.isPasswordBool=${true}
.value=${'mySecurePassword123'}
.description=${'Click the eye icon to show/hide password'}
></dees-input-text>
<dees-input-text
.label=${'API Key'}
.isPasswordBool=${true}
.value=${'sk-1234567890abcdef'}
.description=${'Keep this key secure and never share it'}
></dees-input-text>
</div>
</div>
</dees-demowrapper>
`;

View File

@ -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`
<dees-input-text .label=${'this is a label'} .value=${'test'}></dees-input-text>
<dees-input-text .isPasswordBool=${true}></dees-input-text>
`;
public static demo = demoFunc;
// INSTANCE
@property({

View File

@ -1,15 +1,118 @@
import { html } from '@design.estate/dees-element';
import { html, css } from '@design.estate/dees-element';
export const demoFunc = () => html`
<dees-demowrapper>
<style>
.demoContainer {
max-width: 600px;
margin: auto;
padding: 40px;
background: #000;
${css`
.demo-container {
display: flex;
flex-direction: column;
gap: 24px;
padding: 24px;
max-width: 1200px;
margin: 0 auto;
}
.horizontal-group {
display: flex;
gap: 24px;
align-items: flex-start;
}
.info-box {
margin-top: 16px;
padding: 12px;
background: #e3f2fd;
border-radius: 4px;
font-size: 14px;
color: #1976d2;
}
@media (prefers-color-scheme: dark) {
.info-box {
background: #1e3a5f;
color: #90caf9;
}
}
`}
</style>
<div class="demoContainer">
<dees-input-typelist></dees-input-typelist>
<div class="demo-container">
<dees-panel .title=${'Basic Type List'} .subtitle=${'Add and remove items from a list'}>
<dees-input-typelist
.label=${'Tags'}
.description=${'Add tags by typing and pressing Enter'}
.value=${['javascript', 'typescript', 'web-components']}
></dees-input-typelist>
<dees-input-typelist
.label=${'Team Members'}
.description=${'Add email addresses of team members'}
.value=${['alice@example.com', 'bob@example.com']}
></dees-input-typelist>
</dees-panel>
<dees-panel .title=${'Skills & Keywords'} .subtitle=${'Manage lists of skills and keywords'}>
<dees-input-typelist
.label=${'Your Skills'}
.description=${'List your professional skills'}
.value=${['HTML', 'CSS', 'JavaScript', 'Node.js', 'React']}
></dees-input-typelist>
<div class="horizontal-group">
<dees-input-typelist
.label=${'Categories'}
.layoutMode=${'horizontal'}
.value=${['Technology', 'Design', 'Business']}
></dees-input-typelist>
<dees-input-typelist
.label=${'Keywords'}
.layoutMode=${'horizontal'}
.value=${['innovation', 'startup', 'growth']}
></dees-input-typelist>
</div>
</dees-panel>
<dees-panel .title=${'Required & Disabled States'} .subtitle=${'Different input states for validation'}>
<dees-input-typelist
.label=${'Project Dependencies'}
.description=${'List all required npm packages'}
.required=${true}
.value=${['@design.estate/dees-element', '@design.estate/dees-domtools']}
></dees-input-typelist>
<dees-input-typelist
.label=${'System Tags'}
.description=${'These tags are managed by the system'}
.disabled=${true}
.value=${['system', 'protected', 'readonly']}
></dees-input-typelist>
</dees-panel>
<dees-panel .title=${'Article Publishing Form'} .subtitle=${'Complete form with tag management'}>
<dees-form>
<dees-input-text .label=${'Article Title'} .required=${true}></dees-input-text>
<dees-input-text
.label=${'Summary'}
.inputType=${'textarea'}
.description=${'Brief description of the article'}
></dees-input-text>
<dees-input-typelist
.label=${'Tags'}
.description=${'Add relevant tags for better discoverability'}
.value=${['tutorial', 'web-development']}
></dees-input-typelist>
<dees-input-typelist
.label=${'Co-Authors'}
.description=${'Add email addresses of co-authors'}
></dees-input-typelist>
</dees-form>
<div class="info-box">
<strong>Tip:</strong> Type a value and press Enter to add it to the list. Click on any item to remove it.
</div>
</dees-panel>
</div>
</dees-demowrapper>
`;

View File

@ -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<DeesInputTypelist> {
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`
<div class="label">${this.label}</div>
<div class="input-wrapper">
<dees-label .label=${this.label} .description=${this.description}></dees-label>
<div class="mainbox">
<div class="tags" @click=${() => {
this.shadowRoot.querySelector('input').focus();
}}>
<div class="notags">No tags yet</div>
${this.value.length === 0
? html`<div class="notags">No tags yet</div>`
: this.value.map(
(tag) => html`
<span class="tag">
${tag}
<span class="remove" @click=${(e: Event) => {
e.stopPropagation();
this.removeTag(tag);
}}>×</span>
</span>
`
)}
</div>
<input
type="text"
placeholder="Type, press Enter to add it..."
.value=${this.inputValue}
@input=${(e: InputEvent) => {
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}
/>
</div>
<input type="text" placeholder="Type, press Enter to add it..." />
</div>
`;
}
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;
}
}

View File

@ -0,0 +1,81 @@
import { html, css, cssManager } from '@design.estate/dees-element';
export const demoFunc = () => html`
<style>
${css`
.demo-background {
padding: 24px;
background: ${cssManager.bdTheme('#f0f0f0', '#0a0a0a')};
min-height: 100vh;
}
.demo-container {
max-width: 1200px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 24px;
}
.grid-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media (max-width: 768px) {
.grid-layout {
grid-template-columns: 1fr;
}
}
`}
</style>
<div class="demo-background">
<div class="demo-container">
<dees-panel .title=${'Panel Component'}>
<p>The panel component automatically follows the theme and provides consistent styling for grouped content.</p>
<p>It's perfect for creating sections in your application with proper spacing and borders.</p>
</dees-panel>
<dees-panel .title=${'Panel with Subtitle'} .subtitle=${'Additional context information'}>
<p>Panels can have both a title and subtitle to provide more context.</p>
<p>The subtitle appears in a smaller, muted text below the title.</p>
</dees-panel>
<div class="grid-layout">
<dees-panel .title=${'Feature 1'}>
<p>Grid layouts work great with panels for creating dashboards and feature sections.</p>
<dees-button>Action</dees-button>
</dees-panel>
<dees-panel .title=${'Feature 2'}>
<p>Each panel maintains consistent spacing and styling.</p>
<dees-button>Another Action</dees-button>
</dees-panel>
</div>
<dees-panel .title=${'Complex Content'}>
<h4>Nested Elements</h4>
<p>Panels can contain any type of content:</p>
<ul>
<li>Text and paragraphs</li>
<li>Lists and tables</li>
<li>Form inputs</li>
<li>Other components</li>
</ul>
<dees-input-text .label=${'Example Input'} .description=${'Input inside a panel'}></dees-input-text>
<div style="margin-top: 16px;">
<dees-button>Submit</dees-button>
</div>
</dees-panel>
<dees-panel>
<p>Panels work great even without a title for simple content grouping.</p>
<p>They provide visual separation and consistent padding.</p>
</dees-panel>
</div>
</div>
`;

View File

@ -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`<h3 class="title">${this.title}</h3>` : ''}
${this.subtitle ? html`<p class="subtitle">${this.subtitle}</p>` : ''}
<div class="content">
<slot></slot>
</div>
`;
}
}

View File

@ -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 {
<dees-form>
<dees-input-text key="appName" label="Application Name" value="My App"></dees-input-text>
<dees-input-text key="apiEndpoint" label="API Endpoint" value="https://api.example.com"></dees-input-text>
<dees-input-dropdown
key="environment"
label="Environment"
.options=${[
{ option: 'Development', key: 'dev' },
{ option: 'Staging', key: 'staging' },
{ option: 'Production', key: 'prod' }
]}
.selectedOption=${{ option: 'Production', key: 'prod' }}
></dees-input-dropdown>
<dees-input-checkbox key="enableNotifications" label="Enable Notifications" value="true"></dees-input-checkbox>
<dees-form-submit>Save Settings</dees-form-submit>
<dees-input-checkbox key="enableAnalytics" label="Enable Analytics" value="false"></dees-input-checkbox>
<dees-form-submit>Save General Settings</dees-form-submit>
</dees-form>
</div>
<div class="settings-section">
<h2>Display Preferences</h2>
<div class="horizontal-form-section">
<p style="margin-top: 0; margin-bottom: 16px;">Quick display settings using horizontal layout:</p>
<dees-form horizontal-layout>
<dees-input-dropdown
key="theme"
label="Theme"
.enableSearch=${false}
.options=${[
{ option: 'Light', key: 'light' },
{ option: 'Dark', key: 'dark' },
{ option: 'Auto', key: 'auto' }
]}
.selectedOption=${{ option: 'Dark', key: 'dark' }}
></dees-input-dropdown>
<dees-input-dropdown
key="language"
label="Language"
.enableSearch=${false}
.options=${[
{ option: 'English', key: 'en' },
{ option: 'German', key: 'de' },
{ option: 'Spanish', key: 'es' },
{ option: 'French', key: 'fr' }
]}
.selectedOption=${{ option: 'English', key: 'en' }}
></dees-input-dropdown>
<dees-input-checkbox key="compactMode" label="Compact Mode"></dees-input-checkbox>
</dees-form>
</div>
</div>
<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-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>
</dees-form>
</div>
`;

View File

@ -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';