mirror of
https://github.com/coollabsio/coolify.git
synced 2026-10-28 09:34:36 +08:00
feat(ui): replace native number spinners with a branded stepper
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3b92248309
commit
2111e95c7a
7 files changed
+122
-2
No files matched your search
@@ -80,6 +80,9 @@ class Input extends Component
|
||||
if ($hasPeek) {
|
||||
$this->defaultClass = $this->defaultClass.' input-with-password-toggle';
|
||||
}
|
||||
if ($this->type === 'number') {
|
||||
$this->defaultClass = $this->defaultClass.' input-with-stepper';
|
||||
}
|
||||
if ($this->copyable) {
|
||||
// Reserve clearance for a single copy button, or for both the peek eye
|
||||
// and the copy button when the field is a maskable password.
|
||||
|
||||
@@ -587,6 +587,35 @@ tr td:first-child {
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
/* Number fields use the brand −/+ stepper (components/forms/input) instead of the native spinner. */
|
||||
.input.input-with-stepper {
|
||||
padding-right: 4.25rem;
|
||||
appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.input.input-with-stepper::-webkit-outer-spin-button,
|
||||
.input.input-with-stepper::-webkit-inner-spin-button {
|
||||
margin: 0;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.number-stepper {
|
||||
@apply absolute inset-y-px right-px flex;
|
||||
}
|
||||
|
||||
.number-stepper button {
|
||||
@apply flex w-8 cursor-pointer items-center justify-center border-l border-neutral-200 text-neutral-500 transition-colors last:rounded-r-[5px] hover:bg-neutral-100 hover:text-black active:bg-neutral-200 disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent dark:border-white/[0.08] dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-white dark:active:bg-white/[0.1];
|
||||
}
|
||||
|
||||
.number-stepper button svg {
|
||||
@apply transition-transform duration-150 ease-out;
|
||||
}
|
||||
|
||||
.number-stepper button:active:not(:disabled) svg {
|
||||
@apply scale-75;
|
||||
}
|
||||
|
||||
/* Room for both the peek eye and the copy button on maskable read-only fields. */
|
||||
.input.input-with-copy-and-peek {
|
||||
padding-right: 4.25rem;
|
||||
@@ -2261,6 +2290,16 @@ html[data-theme="custom"]:not(.dark) :is(.application-settings-section, .applica
|
||||
padding-right: 4.25rem;
|
||||
}
|
||||
|
||||
.application-settings-workspace .input.input-with-stepper,
|
||||
.application-settings-form .input.input-with-stepper {
|
||||
padding-right: 4.25rem;
|
||||
}
|
||||
|
||||
.application-settings-workspace .number-stepper button:last-child,
|
||||
.application-settings-form .number-stepper button:last-child {
|
||||
border-radius: 0 7px 7px 0;
|
||||
}
|
||||
|
||||
.application-settings-workspace .input:focus-visible,
|
||||
.application-settings-workspace .select:focus-visible,
|
||||
.application-settings-form .input:focus-visible,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { initializeCopyButtonComponent } from './copy-button.js';
|
||||
import { initializeNumberStepperComponent } from './number-stepper.js';
|
||||
import { initializeRuntimeLogsComponent } from './runtime-logs.js';
|
||||
import { initializeTerminalComponent } from './terminal.js';
|
||||
import './traffic-globe.js';
|
||||
@@ -48,6 +49,7 @@ document.addEventListener('livewire:navigated', () => {
|
||||
// available before Alpine processes terminal markup after wire:navigate.
|
||||
document.addEventListener('alpine:init', initializeTerminalComponent);
|
||||
document.addEventListener('alpine:init', initializeCopyButtonComponent);
|
||||
document.addEventListener('alpine:init', initializeNumberStepperComponent);
|
||||
document.addEventListener('alpine:init', initializeRuntimeLogsComponent);
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Alpine data provider for the −/+ buttons of <x-forms.input type="number"> (x-data="numberStepper").
|
||||
|
||||
/**
|
||||
* Returns the input value one step up (direction 1) or down (direction -1), clamped to min/max.
|
||||
* Uses the same strings the input element exposes: an empty or "any" step counts as 1.
|
||||
*/
|
||||
export function nextStepValue({ value, step, min, max }, direction) {
|
||||
const stepSize = Number.parseFloat(step) > 0 ? Number.parseFloat(step) : 1;
|
||||
const current = Number.parseFloat(value);
|
||||
let next = (Number.isFinite(current) ? current : 0) + direction * stepSize;
|
||||
|
||||
if (min !== '' && Number.isFinite(Number(min))) {
|
||||
next = Math.max(next, Number(min));
|
||||
}
|
||||
if (max !== '' && Number.isFinite(Number(max))) {
|
||||
next = Math.min(next, Number(max));
|
||||
}
|
||||
|
||||
// Remove floating point noise such as 0.30000000000000004.
|
||||
return String(Number.parseFloat(next.toFixed(10)));
|
||||
}
|
||||
|
||||
export function initializeNumberStepperComponent() {
|
||||
window.Alpine.data('numberStepper', () => ({
|
||||
step(direction) {
|
||||
const input = this.$root.querySelector('input');
|
||||
if (input.disabled || input.readOnly) {
|
||||
return;
|
||||
}
|
||||
input.value = nextStepValue(input, direction);
|
||||
// wire:model and x-model listen to these events.
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { nextStepValue } from './number-stepper.js';
|
||||
|
||||
const field = (value, { step = '', min = '', max = '' } = {}) => ({ value, step, min, max });
|
||||
|
||||
test('steps by 1 when the step is empty or "any"', () => {
|
||||
assert.equal(nextStepValue(field('5'), 1), '6');
|
||||
assert.equal(nextStepValue(field('5', { step: 'any' }), -1), '4');
|
||||
});
|
||||
|
||||
test('uses the step attribute without floating point noise', () => {
|
||||
assert.equal(nextStepValue(field('0.1', { step: '0.2' }), 1), '0.3');
|
||||
});
|
||||
|
||||
test('starts from 0 when the field is empty', () => {
|
||||
assert.equal(nextStepValue(field(''), 1), '1');
|
||||
assert.equal(nextStepValue(field(''), -1), '-1');
|
||||
});
|
||||
|
||||
test('clamps to min and max', () => {
|
||||
assert.equal(nextStepValue(field('0', { min: '0' }), -1), '0');
|
||||
assert.equal(nextStepValue(field('99', { max: '99' }), 1), '99');
|
||||
assert.equal(nextStepValue(field('', { min: '60' }), 1), '60');
|
||||
});
|
||||
@@ -73,7 +73,9 @@
|
||||
|
||||
</div>
|
||||
@else
|
||||
@if ($copyable)
|
||||
@if ($type === 'number')
|
||||
<div class="relative" x-data="numberStepper">
|
||||
@elseif ($copyable)
|
||||
<div class="relative">
|
||||
@endif
|
||||
<input autocomplete="{{ $autocomplete }}" @if ($value) value="{{ $value }}" @endif
|
||||
@@ -86,7 +88,19 @@
|
||||
@if ($htmlId !== 'null') id={{ $htmlId }} @endif name="{{ $name }}"
|
||||
placeholder="{{ $attributes->get('placeholder') }}"
|
||||
@if ($autofocus) x-ref="autofocusInput" autofocus @endif>
|
||||
@if ($copyable)
|
||||
@if ($type === 'number')
|
||||
<div class="number-stepper">
|
||||
<button type="button" tabindex="-1" aria-label="Decrease" x-on:click="step(-1)"
|
||||
@disabled($disabled || $readonly)>
|
||||
<x-reicon name="minus" class="size-3.5" />
|
||||
</button>
|
||||
<button type="button" tabindex="-1" aria-label="Increase" x-on:click="step(1)"
|
||||
@disabled($disabled || $readonly)>
|
||||
<x-reicon name="plus" class="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@elseif ($copyable)
|
||||
<x-copy-button :resolve="$copyResolve" label="Copy to clipboard"
|
||||
class="absolute top-1/2 right-1 z-10 -translate-y-1/2" />
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
'feedback' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M12 1.25C6.06294 1.25 1.25 6.06294 1.25 12C1.25 17.9371 6.06294 22.75 12 22.75C17.9371 22.75 22.75 17.9371 22.75 12C22.75 6.06294 17.9371 1.25 12 1.25ZM2.75 12C2.75 9.71567 3.57804 7.62475 4.95034 6.011L8.1528 9.21346C7.58488 9.99619 7.25 10.959 7.25 12C7.25 13.041 7.58488 14.0038 8.1528 14.7865L4.95033 17.989C3.57804 16.3753 2.75 14.2843 2.75 12ZM9.21346 8.1528L6.011 4.95034C7.62475 3.57804 9.71567 2.75 12 2.75C14.2843 2.75 16.3753 3.57804 17.989 4.95034L14.7865 8.1528C14.0038 7.58488 13.041 7.25 12 7.25C10.959 7.25 9.99619 7.58488 9.21346 8.1528ZM6.01099 19.0497C7.62474 20.422 9.71567 21.25 12 21.25C14.2843 21.25 16.3753 20.422 17.989 19.0497L14.7865 15.8472C14.0038 16.4151 13.041 16.75 12 16.75C10.959 16.75 9.99619 16.4151 9.21346 15.8472L6.01099 19.0497ZM15.8472 14.7865L19.0497 17.989C20.422 16.3753 21.25 14.2843 21.25 12C21.25 9.71567 20.422 7.62475 19.0497 6.011L15.8472 9.21346C16.4151 9.99619 16.75 10.959 16.75 12C16.75 13.041 16.4151 14.0038 15.8472 14.7865ZM8.75 12C8.75 10.2051 10.2051 8.75 12 8.75C13.7949 8.75 15.25 10.2051 15.25 12C15.25 13.7949 13.7949 15.25 12 15.25C10.2051 15.25 8.75 13.7949 8.75 12Z" fill="currentColor"/>',
|
||||
'logout' => '<path d="M12 3.25C12.4142 3.25 12.75 3.58579 12.75 4C12.75 4.41421 12.4142 4.75 12 4.75C7.99594 4.75 4.75 7.99594 4.75 12C4.75 16.0041 7.99594 19.25 12 19.25C12.4142 19.25 12.75 19.5858 12.75 20C12.75 20.4142 12.4142 20.75 12 20.75C7.16751 20.75 3.25 16.8325 3.25 12C3.25 7.16751 7.16751 3.25 12 3.25Z" fill="currentColor"/><path d="M16.4697 9.53033C16.1768 9.23744 16.1768 8.76256 16.4697 8.46967C16.7626 8.17678 17.2374 8.17678 17.5303 8.46967L20.5303 11.4697C20.8232 11.7626 20.8232 12.2374 20.5303 12.5303L17.5303 15.5303C17.2374 15.8232 16.7626 15.8232 16.4697 15.5303C16.1768 15.2374 16.1768 14.7626 16.4697 14.4697L18.1893 12.75H10C9.58579 12.75 9.25 12.4142 9.25 12C9.25 11.5858 9.58579 11.25 10 11.25H18.1893L16.4697 9.53033Z" fill="currentColor"/>',
|
||||
'search' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M16.489 17.5497C14.8753 18.922 12.7843 19.75 10.5 19.75C5.39137 19.75 1.25 15.6086 1.25 10.5C1.25 5.39137 5.39137 1.25 10.5 1.25C15.6086 1.25 19.75 5.39137 19.75 10.5C19.75 12.7843 18.922 14.8753 17.5497 16.489L22.5303 21.4697C22.8232 21.7626 22.8232 22.2374 22.5303 22.5303C22.2374 22.8232 21.7626 22.8232 21.4697 22.5303L16.489 17.5497ZM2.75 10.5C2.75 6.21979 6.21979 2.75 10.5 2.75C14.7802 2.75 18.25 6.21979 18.25 10.5C18.25 12.589 17.4235 14.485 16.0796 15.8788C16.0408 15.905 16.004 15.9353 15.9697 15.9697C15.9353 16.004 15.905 16.0408 15.8788 16.0796C14.485 17.4235 12.589 18.25 10.5 18.25C6.21979 18.25 2.75 14.7802 2.75 10.5Z" fill="currentColor"/>',
|
||||
'minus' => '<path d="M6 12H18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>',
|
||||
'plus' => '<path d="M6 12H18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 18V6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>',
|
||||
'grid' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M4.37109 1.25H7.62891C8.02426 1.24999 8.36535 1.24998 8.64627 1.27293C8.94278 1.29715 9.2377 1.35064 9.52148 1.49524C9.94485 1.71095 10.2891 2.05516 10.5048 2.47852C10.6494 2.76231 10.7028 3.05722 10.7271 3.35373C10.75 3.63466 10.75 3.97572 10.75 4.37108V7.62893C10.75 8.02428 10.75 8.36535 10.7271 8.64627C10.7028 8.94278 10.6494 9.2377 10.5048 9.52148C10.2891 9.94485 9.94485 10.2891 9.52148 10.5048C9.2377 10.6494 8.94278 10.7028 8.64627 10.7271C8.36535 10.75 8.02428 10.75 7.62893 10.75H4.37108C3.97572 10.75 3.63466 10.75 3.35373 10.7271C3.05722 10.7028 2.76231 10.6494 2.47852 10.5048C2.05516 10.2891 1.71095 9.94485 1.49524 9.52148C1.35064 9.2377 1.29715 8.94278 1.27293 8.64627C1.24998 8.36535 1.24999 8.02429 1.25 7.62894V4.37109C1.24999 3.97574 1.24998 3.63465 1.27293 3.35373C1.29715 3.05722 1.35064 2.76231 1.49524 2.47852C1.71095 2.05516 2.05516 1.71095 2.47852 1.49524C2.76231 1.35064 3.05722 1.29715 3.35373 1.27293C3.63465 1.24998 3.97574 1.24999 4.37109 1.25ZM3.47588 2.76795C3.27213 2.78459 3.19659 2.81285 3.15951 2.83175C3.01839 2.90365 2.90365 3.01839 2.83175 3.15951C2.81285 3.19659 2.78459 3.27213 2.76795 3.47588C2.75059 3.68838 2.75 3.96759 2.75 4.4V7.6C2.75 8.03242 2.75059 8.31162 2.76795 8.52413C2.78459 8.72787 2.81285 8.80341 2.83175 8.84049C2.90365 8.98162 3.01839 9.09635 3.15951 9.16826C3.19659 9.18715 3.27213 9.21541 3.47588 9.23206C3.68838 9.24942 3.96759 9.25 4.4 9.25H7.6C8.03242 9.25 8.31162 9.24942 8.52413 9.23206C8.72787 9.21541 8.80341 9.18715 8.84049 9.16826C8.98162 9.09635 9.09635 8.98162 9.16826 8.84049C9.18715 8.80341 9.21541 8.72787 9.23206 8.52413C9.24942 8.31162 9.25 8.03242 9.25 7.6V4.4C9.25 3.96759 9.24942 3.68838 9.23206 3.47588C9.21541 3.27213 9.18715 3.19659 9.16826 3.15951C9.09635 3.01839 8.98162 2.90365 8.84049 2.83175C8.80341 2.81285 8.72787 2.78459 8.52413 2.76795C8.31162 2.75059 8.03242 2.75 7.6 2.75H4.4C3.96759 2.75 3.68838 2.75059 3.47588 2.76795Z" fill="currentColor"/><path fill-rule="evenodd" clip-rule="evenodd" d="M19.6289 1.25H16.3711C15.9757 1.24999 15.6347 1.24998 15.3537 1.27293C15.0572 1.29715 14.7623 1.35064 14.4785 1.49524C14.0552 1.71095 13.711 2.05516 13.4952 2.47852C13.3506 2.76231 13.2972 3.05722 13.2729 3.35373C13.25 3.63465 13.25 3.97571 13.25 4.37105V7.62891C13.25 8.02425 13.25 8.36535 13.2729 8.64627C13.2972 8.94278 13.3506 9.2377 13.4952 9.52148C13.711 9.94485 14.0552 10.2891 14.4785 10.5048C14.7623 10.6494 15.0572 10.7028 15.3537 10.7271C15.6347 10.75 15.9757 10.75 16.3711 10.75H19.6289C20.0243 10.75 20.3653 10.75 20.6463 10.7271C20.9428 10.7028 21.2377 10.6494 21.5215 10.5048C21.9448 10.2891 22.2891 9.94485 22.5048 9.52148C22.6494 9.2377 22.7028 8.94278 22.7271 8.64627C22.75 8.36535 22.75 8.02428 22.75 7.62893V4.37108C22.75 3.97572 22.75 3.63466 22.7271 3.35373C22.7028 3.05722 22.6494 2.76231 22.5048 2.47852C22.2891 2.05516 21.9448 1.71095 21.5215 1.49524C21.2377 1.35064 20.9428 1.29715 20.6463 1.27293C20.3654 1.24998 20.0243 1.24999 19.6289 1.25ZM15.1595 2.83175C15.1966 2.81285 15.2721 2.78459 15.4759 2.76795C15.6884 2.75059 15.9676 2.75 16.4 2.75H19.6C20.0324 2.75 20.3116 2.75059 20.5241 2.76795C20.7279 2.78459 20.8034 2.81285 20.8405 2.83175C20.9816 2.90365 21.0964 3.01839 21.1683 3.15951C21.1872 3.19659 21.2154 3.27213 21.2321 3.47588C21.2494 3.68838 21.25 3.96759 21.25 4.4V7.6C21.25 8.03242 21.2494 8.31162 21.2321 8.52413C21.2154 8.72787 21.1872 8.80341 21.1683 8.84049C21.0964 8.98162 20.9816 9.09635 20.8405 9.16826C20.8034 9.18715 20.7279 9.21541 20.5241 9.23206C20.3116 9.24942 20.0324 9.25 19.6 9.25H16.4C15.9676 9.25 15.6884 9.24942 15.4759 9.23206C15.2721 9.21541 15.1966 9.18715 15.1595 9.16826C15.0184 9.09635 14.9037 8.98162 14.8317 8.84049C14.8129 8.80341 14.7846 8.72787 14.7679 8.52413C14.7506 8.31162 14.75 8.03242 14.75 7.6V4.4C14.75 3.96759 14.7506 3.68838 14.7679 3.47588C14.7846 3.27213 14.8129 3.19659 14.8317 3.15951C14.9037 3.01839 15.0184 2.90365 15.1595 2.83175Z" fill="currentColor"/><path fill-rule="evenodd" clip-rule="evenodd" d="M19.6289 13.25H16.3711C15.9758 13.25 15.6346 13.25 15.3537 13.2729C15.0572 13.2972 14.7623 13.3506 14.4785 13.4952C14.0552 13.711 13.711 14.0552 13.4952 14.4785C13.3506 14.7623 13.2972 15.0572 13.2729 15.3537C13.25 15.6346 13.25 15.9757 13.25 16.371V19.6289C13.25 20.0243 13.25 20.3654 13.2729 20.6463C13.2972 20.9428 13.3506 21.2377 13.4952 21.5215C13.711 21.9448 14.0552 22.2891 14.4785 22.5048C14.7623 22.6494 15.0572 22.7028 15.3537 22.7271C15.6347 22.75 15.9757 22.75 16.3711 22.75H19.6289C20.0243 22.75 20.3653 22.75 20.6463 22.7271C20.9428 22.7028 21.2377 22.6494 21.5215 22.5048C21.9448 22.2891 22.2891 21.9448 22.5048 21.5215C22.6494 21.2377 22.7028 20.9428 22.7271 20.6463C22.75 20.3653 22.75 20.0243 22.75 19.6289V16.3711C22.75 15.9757 22.75 15.6347 22.7271 15.3537C22.7028 15.0572 22.6494 14.7623 22.5048 14.4785C22.2891 14.0552 21.9448 13.711 21.5215 13.4952C21.2377 13.3506 20.9428 13.2972 20.6463 13.2729C20.3654 13.25 20.0243 13.25 19
Line truncated
|
||||
'eye' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M12 8.25C9.92893 8.25 8.25 9.92893 8.25 12C8.25 14.0711 9.92893 15.75 12 15.75C14.0711 15.75 15.75 14.0711 15.75 12C15.75 9.92893 14.0711 8.25 12 8.25ZM9.75 12C9.75 10.7574 10.7574 9.75 12 9.75C13.2426 9.75 14.25 10.7574 14.25 12C14.25 13.2426 13.2426 14.25 12 14.25C10.7574 14.25 9.75 13.2426 9.75 12Z" fill="currentColor"/><path fill-rule="evenodd" clip-rule="evenodd" d="M12 3.25C7.48587 3.25 4.44529 5.9542 2.68057 8.24686L2.64874 8.2882C2.24964 8.80653 1.88206 9.28392 1.63269 9.8484C1.36564 10.4529 1.25 11.1117 1.25 12C1.25 12.8883 1.36564 13.5471 1.63269 14.1516C1.88206 14.7161 2.24964 15.1935 2.64875 15.7118L2.68057 15.7531C4.44529 18.0458 7.48587 20.75 12 20.75C16.5141 20.75 19.5547 18.0458 21.3194 15.7531L21.3512 15.7118C21.7504 15.1935 22.1179 14.7161 22.3673 14.1516C22.6344 13.5471 22.75 12.8883 22.75 12C22.75 11.1117 22.6344 10.4529 22.3673 9.8484C22.1179 9.28391 21.7504 8.80652 21.3512 8.28818L21.3194 8.24686C19.5547 5.9542 16.5141 3.25 12 3.25ZM3.86922 9.1618C5.49864 7.04492 8.15036 4.75 12 4.75C15.8496 4.75 18.5014 7.04492 20.1308 9.1618C20.5694 9.73159 20.8263 10.0721 20.9952 10.4545C21.1532 10.812 21.25 11.2489 21.25 12C21.25 12.7511 21.1532 13.188 20.9952 13.5455C20.8263 13.9279 20.5694 14.2684 20.1308 14.8382C18.5014 16.9551 15.8496 19.25 12 19.25C8.15036 19.25 5.49864 16.9551 3.86922 14.8382C3.43064 14.2684 3.17374 13.9279 3.00476 13.5455C2.84684 13.188 2.75 12.7511 2.75 12C2.75 11.2489 2.84684 10.812 3.00476 10.4545C3.17374 10.0721 3.43063 9.73159 3.86922 9.1618Z" fill="currentColor"/>',
|
||||
|
||||
Reference in new issue
Block a user