Forms
Float Input code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/float-input.blade.php
@props([
'name' => null,
'value' => null,
'min' => null,
'max' => null,
'step' => 0.1,
'decimals' => null,
'placeholder' => null,
'size' => 'base',
'icon' => null,
'invalid' => false,
'disabled' => false,
'controls' => true,
])
@php
/*
| Decimal (floating-point) number input. Allows a single decimal point,
| strips other non-numeric characters as you type, clamps to min/max and
| optionally rounds to a fixed number of `decimals` on blur. Stepper buttons
| and up/down arrow keys nudge by `step`.
|
| Usage:
| <x-ui.float-input name="price" :value="9.99" :min="0" :step="0.01" :decimals="2" icon="currency-dollar" />
| <x-ui.float-input name="weight" :step="0.5" :controls="false" />
*/
$sizes = [
'sm' => 'text-sm py-1.5',
'base' => 'text-sm py-2.5',
'lg' => 'text-base py-3',
];
$pad = 'px-3.5';
if ($icon) { $pad = 'ps-10 pe-3.5'; }
if ($controls) { $pad = $icon ? 'ps-10 pe-10' : 'ps-3.5 pe-10'; }
$ring = $invalid
? 'border-red-400 dark:border-red-500 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 dark:border-gray-700 focus:border-brand-500 focus:ring-brand-500';
$classes = implode(' ', [
'block w-full rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 shadow-sm transition focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed',
$sizes[$size] ?? $sizes['base'],
$pad,
$ring,
]);
@endphp
<div
x-data="{
value: @js($value !== null ? (string) (float) $value : ''),
min: @js($min !== null ? (float) $min : null),
max: @js($max !== null ? (float) $max : null),
step: @js((float) $step),
decimals: @js($decimals !== null ? (int) $decimals : null),
disabled: @js((bool) $disabled),
sanitize() {
let v = this.value.toString().replace(/[^0-9.\-]/g, '');
v = v.charAt(0) + v.slice(1).replace(/-/g, '');
let parts = v.split('.');
if (parts.length > 2) { v = parts.shift() + '.' + parts.join(''); }
this.value = v;
},
format(n) {
return this.decimals !== null ? n.toFixed(this.decimals) : n.toString();
},
clamp() {
if (this.value === '' || this.value === '-' || this.value === '.') { return; }
let n = parseFloat(this.value);
if (isNaN(n)) { this.value = ''; return; }
if (this.min !== null && n < this.min) { n = this.min; }
if (this.max !== null && n > this.max) { n = this.max; }
this.value = this.format(n);
},
nudge(dir) {
if (this.disabled) { return; }
let n = parseFloat(this.value);
if (isNaN(n)) { n = this.min !== null ? this.min : 0; }
else { n += dir * this.step; }
if (this.min !== null && n < this.min) { n = this.min; }
if (this.max !== null && n > this.max) { n = this.max; }
let dp = this.decimals !== null ? this.decimals : (this.step.toString().split('.')[1] || '').length;
this.value = parseFloat(n.toFixed(dp)).toString();
},
}"
class="relative"
>
@if ($icon)
<span class="pointer-events-none absolute inset-y-0 start-0 flex items-center ps-3 text-gray-400 dark:text-gray-500">
<x-ui.icon :name="$icon" size="base" />
</span>
@endif
<input
type="text"
inputmode="decimal"
@if ($name) name="{{ $name }}" @endif
@if ($placeholder) placeholder="{{ $placeholder }}" @endif
@disabled($disabled)
@if ($invalid) aria-invalid="true" @endif
x-model="value"
@input="sanitize()"
@blur="clamp()"
@keydown.arrow-up.prevent="nudge(1)"
@keydown.arrow-down.prevent="nudge(-1)"
{{ $attributes->merge(['class' => $classes]) }}
>
@if ($controls)
<div class="absolute inset-y-0 end-0 flex flex-col border-s border-gray-200 dark:border-gray-700">
<button type="button" tabindex="-1" @click="nudge(1)" @disabled($disabled) aria-label="Increment" class="flex flex-1 items-center justify-center px-2.5 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 disabled:opacity-50 disabled:cursor-not-allowed transition">
<x-ui.icon name="caret-up" weight="bold" size="xs" />
</button>
<button type="button" tabindex="-1" @click="nudge(-1)" @disabled($disabled) aria-label="Decrement" class="flex flex-1 items-center justify-center px-2.5 border-t border-gray-200 dark:border-gray-700 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 disabled:opacity-50 disabled:cursor-not-allowed transition">
<x-ui.icon name="caret-down" weight="bold" size="xs" />
</button>
</div>
@endif
</div>