feat(structure): adjust
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||
import './dees-shopping-productcard.js';
|
||||
|
||||
export const demoFunc = () => html`
|
||||
<dees-demowrapper>
|
||||
<style>
|
||||
${css`
|
||||
.demo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
padding: 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.shopping-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.cart-summary {
|
||||
margin-top: 24px;
|
||||
padding: 20px;
|
||||
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 21.8%)')};
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.cart-summary-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||
}
|
||||
|
||||
.cart-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('hsl(215.3 25% 26.7%)', 'hsl(217.9 10.6% 74.9%)')};
|
||||
}
|
||||
|
||||
.cart-total {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 16px;
|
||||
margin-top: 16px;
|
||||
border-top: 2px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||
}
|
||||
`}
|
||||
</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=${'Modern e-commerce product cards with interactive quantity selectors'} .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
const updateCartSummary = () => {
|
||||
const card1 = elementArg.querySelector('#headphones-qty') as any;
|
||||
const card2 = elementArg.querySelector('#mouse-qty') as any;
|
||||
const card3 = elementArg.querySelector('#keyboard-qty') as any;
|
||||
|
||||
const qty1 = card1?.quantity || 0;
|
||||
const qty2 = card2?.quantity || 0;
|
||||
const qty3 = card3?.quantity || 0;
|
||||
|
||||
const price1 = 349.99 * qty1;
|
||||
const price2 = 99.99 * qty2;
|
||||
const price3 = 79.99 * qty3;
|
||||
const total = price1 + price2 + price3;
|
||||
|
||||
const summary = elementArg.querySelector('#cart-summary-content');
|
||||
if (summary) {
|
||||
summary.innerHTML = `
|
||||
${qty1 > 0 ? `<div class="cart-item">
|
||||
<span>Sony WH-1000XM5 (${qty1})</span>
|
||||
<span>$${price1.toFixed(2)}</span>
|
||||
</div>` : ''}
|
||||
${qty2 > 0 ? `<div class="cart-item">
|
||||
<span>Logitech MX Master 3S (${qty2})</span>
|
||||
<span>$${price2.toFixed(2)}</span>
|
||||
</div>` : ''}
|
||||
${qty3 > 0 ? `<div class="cart-item">
|
||||
<span>Keychron K2 (${qty3})</span>
|
||||
<span>$${price3.toFixed(2)}</span>
|
||||
</div>` : ''}
|
||||
${total === 0 ? '<div class="cart-item" style="text-align: center; color: #999;">Your cart is empty</div>' : ''}
|
||||
<div class="cart-total">
|
||||
<span>Total</span>
|
||||
<span>$${total.toFixed(2)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
||||
// Initial update
|
||||
setTimeout(updateCartSummary, 100);
|
||||
|
||||
// Set up listeners
|
||||
elementArg.querySelectorAll('dees-shopping-productcard').forEach(card => {
|
||||
card.addEventListener('quantityChange', updateCartSummary);
|
||||
});
|
||||
}}>
|
||||
<div class="shopping-grid">
|
||||
<dees-shopping-productcard
|
||||
id="headphones-qty"
|
||||
.productData=${{
|
||||
name: 'Sony WH-1000XM5 Wireless Headphones',
|
||||
category: 'Audio',
|
||||
description: 'Industry-leading noise canceling with Auto NC Optimizer',
|
||||
price: 349.99,
|
||||
originalPrice: 399.99,
|
||||
iconName: 'lucide:headphones'
|
||||
}}
|
||||
.quantity=${1}
|
||||
></dees-shopping-productcard>
|
||||
|
||||
<dees-shopping-productcard
|
||||
id="mouse-qty"
|
||||
.productData=${{
|
||||
name: 'Logitech MX Master 3S',
|
||||
category: 'Accessories',
|
||||
description: 'Performance wireless mouse with ultra-fast scrolling',
|
||||
price: 99.99,
|
||||
iconName: 'lucide:mouse-pointer'
|
||||
}}
|
||||
.quantity=${2}
|
||||
></dees-shopping-productcard>
|
||||
|
||||
<dees-shopping-productcard
|
||||
id="keyboard-qty"
|
||||
.productData=${{
|
||||
name: 'Keychron K2 Wireless Mechanical Keyboard',
|
||||
category: 'Keyboards',
|
||||
description: 'Compact 75% layout with hot-swappable switches',
|
||||
price: 79.99,
|
||||
originalPrice: 94.99,
|
||||
iconName: 'lucide:keyboard'
|
||||
}}
|
||||
.quantity=${1}
|
||||
></dees-shopping-productcard>
|
||||
</div>
|
||||
|
||||
<div class="cart-summary">
|
||||
<h3 class="cart-summary-title">Order Summary</h3>
|
||||
<div id="cart-summary-content">
|
||||
<!-- Content will be dynamically updated -->
|
||||
</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>
|
||||
`;
|
||||
@@ -0,0 +1,186 @@
|
||||
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 {
|
||||
'dees-input-quantityselector': DeesInputQuantitySelector;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-input-quantityselector')
|
||||
export class DeesInputQuantitySelector extends DeesInputBase<DeesInputQuantitySelector> {
|
||||
public static demo = demoFunc;
|
||||
|
||||
// INSTANCE
|
||||
|
||||
@property({
|
||||
type: Number
|
||||
})
|
||||
accessor value: number = 1;
|
||||
|
||||
|
||||
|
||||
public static styles = [
|
||||
...DeesInputBase.baseStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
width: auto;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.quantity-container {
|
||||
transition: all 0.15s ease;
|
||||
font-size: 14px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
min-width: 120px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quantity-container.disabled {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||
border-color: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.quantity-container:hover:not(.disabled) {
|
||||
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||
}
|
||||
|
||||
.quantity-container:focus-within {
|
||||
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
|
||||
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.1)', 'hsl(217.2 91.2% 59.8% / 0.1)')};
|
||||
}
|
||||
|
||||
.selector {
|
||||
flex: 0 0 40px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.selector:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||
}
|
||||
|
||||
.selector:active {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 11%)')};
|
||||
}
|
||||
|
||||
.selector.minus {
|
||||
border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
}
|
||||
|
||||
.selector.plus {
|
||||
border-left: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
}
|
||||
|
||||
.quantity {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.006em;
|
||||
}
|
||||
|
||||
/* Keyboard navigation focus styles */
|
||||
.selector:focus {
|
||||
outline: none;
|
||||
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Min value state */
|
||||
.quantity-container[data-min="true"] .selector.minus {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.quantity-container[data-min="true"] .selector.minus:hover {
|
||||
background: transparent;
|
||||
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
${this.label ? html`<dees-label .label=${this.label} .description=${this.description} .required=${this.required}></dees-label>` : ''}
|
||||
<div
|
||||
class="quantity-container ${this.disabled ? 'disabled' : ''}"
|
||||
data-min="${this.value <= 0}"
|
||||
>
|
||||
<div
|
||||
class="selector minus"
|
||||
@click="${() => {this.decrease();}}"
|
||||
tabindex="${this.disabled ? '-1' : '0'}"
|
||||
@keydown="${(e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
this.decrease();
|
||||
}
|
||||
}}"
|
||||
role="button"
|
||||
aria-label="Decrease quantity"
|
||||
>−</div>
|
||||
<div class="quantity" aria-live="polite" aria-atomic="true">${this.value}</div>
|
||||
<div
|
||||
class="selector plus"
|
||||
@click="${() => {this.increase();}}"
|
||||
tabindex="${this.disabled ? '-1' : '0'}"
|
||||
@keydown="${(e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
this.increase();
|
||||
}
|
||||
}}"
|
||||
role="button"
|
||||
aria-label="Increase quantity"
|
||||
>+</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
public increase() {
|
||||
if (!this.disabled) {
|
||||
this.value++;
|
||||
this.changeSubject.next(this);
|
||||
}
|
||||
}
|
||||
|
||||
public decrease() {
|
||||
if (!this.disabled && this.value > 0) {
|
||||
this.value--;
|
||||
this.changeSubject.next(this);
|
||||
}
|
||||
}
|
||||
|
||||
public getValue(): number {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public setValue(value: number): void {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user