import { html, type TemplateResult } from 'lit';
import {
formatUsageFromDefaults,
type PlaygroundControl,
type PlaygroundDefinition,
type PlaygroundPreviewHandlers,
type PlaygroundProps,
whenBoolean,
whenString,
} from '../types.js';
const ATTRIBUTE_NAMES: Record = {
showButtons: 'show-buttons',
buttonLayout: 'button-layout',
useGrouping: 'use-grouping',
minFractionDigits: 'min-fraction-digits',
maxFractionDigits: 'max-fraction-digits',
allowEmpty: 'allow-empty',
inputId: 'input-id',
ariaLabel: 'aria-label',
ariaLabelledby: 'aria-labelledby',
nuiType: 'nui-type',
inputClass: 'input-class',
incrementIcon: 'increment-icon',
decrementIcon: 'decrement-icon',
};
export const NUI_INPUT_NUMBER_DEFAULTS: PlaygroundProps = {
value: '42',
min: '0',
max: '100',
step: '1',
format: true,
showButtons: true,
buttonLayout: 'stacked',
mode: 'decimal',
locale: '',
currency: 'USD',
useGrouping: true,
minFractionDigits: '',
maxFractionDigits: '',
prefix: '',
suffix: '',
placeholder: '',
disabled: false,
readonly: false,
invalid: false,
required: false,
allowEmpty: true,
size: '',
variant: '',
fluid: false,
unstyled: false,
nuiType: '',
inputClass: '',
name: '',
inputId: '',
ariaLabel: '',
ariaLabelledby: '',
incrementIcon: 'mdi:chevron-up',
decrementIcon: 'mdi:chevron-down',
};
const CONTROLS: PlaygroundControl[] = [
{
key: 'value',
label: 'value',
type: 'text',
section: 'Content',
placeholder: '42',
},
{
key: 'placeholder',
label: 'placeholder',
type: 'text',
section: 'Content',
},
{
key: 'prefix',
label: 'prefix',
type: 'text',
section: 'Content',
},
{
key: 'suffix',
label: 'suffix',
type: 'text',
section: 'Content',
},
{
key: 'name',
label: 'name',
type: 'text',
section: 'Content',
},
{
key: 'inputClass',
label: 'input-class',
type: 'text',
section: 'Content',
},
{
key: 'size',
label: 'size',
type: 'select',
section: 'Appearance',
options: [
{ value: '', label: '(default)' },
{ value: 'small', label: 'small' },
{ value: 'large', label: 'large' },
],
},
{
key: 'variant',
label: 'variant',
type: 'select',
section: 'Appearance',
options: [
{ value: '', label: '(default)' },
{ value: 'outlined', label: 'outlined' },
{ value: 'filled', label: 'filled' },
],
},
{
key: 'buttonLayout',
label: 'button-layout',
type: 'select',
section: 'Appearance',
options: [
{ value: 'stacked', label: 'stacked' },
{ value: 'horizontal', label: 'horizontal' },
{ value: 'vertical', label: 'vertical' },
],
},
{ key: 'nuiType', label: 'nui-type', type: 'text', section: 'Appearance' },
{
key: 'mode',
label: 'mode',
type: 'select',
section: 'Modifiers',
options: [
{ value: 'decimal', label: 'decimal' },
{ value: 'currency', label: 'currency' },
],
},
{ key: 'format', label: 'format', type: 'boolean', section: 'Modifiers' },
{
key: 'showButtons',
label: 'show-buttons',
type: 'boolean',
section: 'Modifiers',
},
{
key: 'useGrouping',
label: 'use-grouping',
type: 'boolean',
section: 'Modifiers',
},
{
key: 'allowEmpty',
label: 'allow-empty',
type: 'boolean',
section: 'Modifiers',
},
{
key: 'unstyled',
label: 'unstyled',
type: 'boolean',
section: 'Modifiers',
},
{ key: 'fluid', label: 'fluid', type: 'boolean', section: 'Layout' },
{
key: 'min',
label: 'min',
type: 'text',
section: 'Layout',
placeholder: '0',
},
{
key: 'max',
label: 'max',
type: 'text',
section: 'Layout',
placeholder: '100',
},
{
key: 'step',
label: 'step',
type: 'text',
section: 'Layout',
placeholder: '1',
},
{
key: 'locale',
label: 'locale',
type: 'text',
section: 'Layout',
placeholder: 'en-US',
},
{
key: 'currency',
label: 'currency',
type: 'text',
section: 'Layout',
placeholder: 'USD',
},
{
key: 'minFractionDigits',
label: 'min-fraction-digits',
type: 'text',
section: 'Layout',
},
{
key: 'maxFractionDigits',
label: 'max-fraction-digits',
type: 'text',
section: 'Layout',
},
{ key: 'disabled', label: 'disabled', type: 'boolean', section: 'State' },
{ key: 'readonly', label: 'readonly', type: 'boolean', section: 'State' },
{ key: 'invalid', label: 'invalid', type: 'boolean', section: 'State' },
{ key: 'required', label: 'required', type: 'boolean', section: 'State' },
{
key: 'inputId',
label: 'input-id',
type: 'text',
section: 'HTML',
},
{
key: 'ariaLabel',
label: 'aria-label',
type: 'text',
section: 'HTML',
},
{
key: 'ariaLabelledby',
label: 'aria-labelledby',
type: 'text',
section: 'HTML',
},
{
key: 'incrementIcon',
label: 'increment-icon',
type: 'text',
section: 'HTML',
},
{
key: 'decrementIcon',
label: 'decrement-icon',
type: 'text',
section: 'HTML',
},
];
function parseOptionalNumber(value: unknown): number | null {
if (value === undefined || value === null || value === '') {
return null;
}
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
function parseOptionalFractionDigits(value: unknown): number | undefined {
if (value === undefined || value === null || value === '') {
return undefined;
}
const parsed = Number(value);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
}
function handleInputNumberChange(
event: Event,
handlers?: PlaygroundPreviewHandlers,
): void {
if (!handlers) {
return;
}
const value = (event as CustomEvent<{ value: number | null }>).detail.value;
handlers.onPropChange('value', value === null ? '' : String(value));
}
function renderPreview(
props: PlaygroundProps,
handlers?: PlaygroundPreviewHandlers,
): TemplateResult {
const value = parseOptionalNumber(props.value);
return html`
0 ? Number(props.step) : 1}
.format=${Boolean(props.format)}
.showButtons=${Boolean(props.showButtons)}
.useGrouping=${Boolean(props.useGrouping)}
.allowEmpty=${Boolean(props.allowEmpty)}
.minFractionDigits=${parseOptionalFractionDigits(props.minFractionDigits)}
.maxFractionDigits=${parseOptionalFractionDigits(props.maxFractionDigits)}
mode=${whenString(props.mode)}
locale=${whenString(props.locale)}
currency=${whenString(props.currency)}
placeholder=${whenString(props.placeholder)}
prefix=${whenString(props.prefix)}
suffix=${whenString(props.suffix)}
name=${whenString(props.name)}
input-id=${whenString(props.inputId)}
aria-label=${whenString(props.ariaLabel)}
aria-labelledby=${whenString(props.ariaLabelledby)}
input-class=${whenString(props.inputClass)}
nui-type=${whenString(props.nuiType)}
button-layout=${whenString(props.buttonLayout)}
increment-icon=${whenString(props.incrementIcon)}
decrement-icon=${whenString(props.decrementIcon)}
?disabled=${whenBoolean(props.disabled)}
?readonly=${whenBoolean(props.readonly)}
?invalid=${whenBoolean(props.invalid)}
?required=${whenBoolean(props.required)}
?fluid=${whenBoolean(props.fluid)}
?unstyled=${whenBoolean(props.unstyled)}
size=${whenString(props.size)}
variant=${whenString(props.variant)}
@input=${(event: Event) => handleInputNumberChange(event, handlers)}
>
`;
}
export const nuiInputNumberPlayground: PlaygroundDefinition = {
tag: 'nui-input-number',
label: 'Input number',
description:
'Numeric input with formatting, min/max, step, and optional increment buttons.',
defaults: NUI_INPUT_NUMBER_DEFAULTS,
controls: CONTROLS,
renderPreview,
formatUsage: (props) =>
formatUsageFromDefaults(
'nui-input-number',
props,
NUI_INPUT_NUMBER_DEFAULTS,
ATTRIBUTE_NAMES,
),
};