• HTML

    spin-button.html html

    <spin-button value="0" zero-label="Add to Cart" increment-label="Increment">
    	<button type="button" class="decrement" aria-label="Decrement" hidden>−</button>
    	<p class="value" hidden>0</p>
    	<button type="button" class="increment primary">Add to Cart</button>
    </spin-button>
    CSS

    spin-button.css css

    spin-button {
    	display: inline-flex;
        max-width: 7rem;
    
    	.value {
    		box-sizing: border-box;
    		margin: 0;
    		border-top: 1px solid var(--color-border);
    		border-bottom: 1px solid var(--color-border);
    		height: var(--input-height);
    		min-width: 2rem;
    		max-width: 3rem;
    		text-align: center;
    		font-size: var(--font-size-s);
    		padding: 0 var(--space-s);
    		line-height: 2;
    		flex-grow: 1;
    	}
    
    	> button {
    		flex-grow: 0;
    		box-sizing: border-box;
    		height: var(--input-height);
    		min-width: var(--input-height);
    		border: 1px solid var(--color-border);
    		background-color: var(--color-secondary);
    		color: var(--color-text);
    		padding: 0 var(--space-s);
    		font-size: var(--font-size-s);
    		line-height: var(--line-height-s);
    		white-space: nowrap;
    		cursor: pointer;
    		transition: all var(--transition-shorter) var(--easing-inout);
    
    		&:disabled {
    			opacity: var(--opacity-translucent);
    		}
    
    		&:not(:disabled) {
    			cursor: pointer;
    			opacity: var(--opacity-solid);
    
    			&:hover {
    				background-color: var(--color-secondary-hover);
    			}
    
    			&:active {
    				background-color: var(--color-secondary-active);
    			}
    		}
    
    		&:first-child {
    			border-radius: var(--space-xs) 0 0 var(--space-xs);
    		}
    
    		&:last-child {
    			border-radius: 0 var(--space-xs) var(--space-xs) 0;
    		}
    	}
    
    	[hidden] + button {
    		flex-grow: 1;
    		border-radius: var(--space-xs);
    		color: var(--color-text-inverted);
    		background-color: var(--color-primary);
    		border-color: var(--color-primary-active);
    
    		&:hover {
    			background-color: var(--color-primary-hover);
    		}
    
    		&:active {
    			background-color: var(--color-primary-active);
    		}
    	}
    }
    TS

    spin-button.ts ts

    import { UIElement, asInteger, setProperty, setText, toggleAttribute } from '../../../'
    
    export class SpinButton extends UIElement<{
    	value: number,
    	zero: boolean,
    }> {
    	static localName ='spin-button'
    	static observedAttributes = ['value']
    
    	init = {
            value: asInteger(),
    		zero: () => this.get('value') === 0
        }
    
    	connectedCallback() {
            super.connectedCallback()
    
    		const zeroLabel = this.getAttribute('zero-label') || 'Add to Cart'
    		const incrementLabel = this.getAttribute('increment-label') || 'Increment'
    		const max = asInteger(9)(this.getAttribute('max'))
    
    		// Event handlers
    		const changeValue = (direction: number = 1) => () => {
    			this.set('value', v => v + direction)
    		}
    
    		// Effects
    		this.first('.value')
    			.sync(setText('value'), setProperty('hidden', 'zero'))
    		this.first('.decrement')
    			.on('click', changeValue(-1))
    			.sync(setProperty('hidden', 'zero'))
    		this.first('.increment')
    			.on('click', changeValue())
    			.sync(
    				setText(() => this.get('zero') ? zeroLabel : '+'),
    				setProperty('ariaLabel', () => this.get('zero') ? zeroLabel : incrementLabel),
    				toggleAttribute('disabled', () => this.get('value') >= max)
    			)
        }
    }
    SpinButton.define()