add DurationInput component

This commit is contained in:
Elijah Duffy
2025-12-11 15:20:42 -08:00
parent a7fa9fd6d8
commit f843c91284
5 changed files with 408 additions and 55 deletions

View File

@@ -23,17 +23,31 @@
import { generateIdentifier, prefixZero, targetMust } from './util';
import { FocusManager } from './focus';
import { Time } from '@internationalized/date';
import { incrementValue, decrementValue } from './numeric-utils';
interface Props {
/**
* Name of the input for form integration, form will receive ISO 8601
* formatted string.
*/
name?: string;
/** Label for the input */
label?: string;
/** Bindable Time value */
value?: Time | null;
/** Bindable formatted time string, always matches current value and cannot be set */
formattedValue?: string;
/** Whether the input is required */
required?: boolean;
/** Message to show when the input is invalid */
invalidMessage?: string;
/** Controls visibility for a confirmation text below the input */
showConfirm?: boolean;
/** Whether to use compact styling */
compact?: boolean;
/** Class is applied to the root container (div element) */
class?: ClassValue | null | undefined;
/** Triggered whenever the time value is changed by the user */
onchange?: (details: { time: Time | null; formattedTime: string }) => void;
}
@@ -95,55 +109,6 @@
}
};
/**
* incrementValue increments the value of the input by 1
* @param input The input element to increment
* @param max The maximum value of the input
* @param start The starting value of the input
* @returns true if the value was incremented, false if it looped back to 0
*/
const incrementValue = (input: HTMLInputElement, max: number, start: number): boolean => {
if (input.value.length === 0) {
input.value = start.toString();
return true;
}
const value = parseInt(input.value);
if (value === max) {
input.value = start.toString();
return false;
} else if (value > max) {
input.value = (value - max).toString();
return false;
} else {
input.value = (value + 1).toString();
return true;
}
};
/**
* decrementValue decrements the value of the input by 1
* @param input The input element to decrement
* @param max The maximum value of the input
* @param start The starting value of the input
* @returns true if the value was decremented, false if it looped back to max
*/
const decrementValue = (input: HTMLInputElement, max: number, start: number): boolean => {
if (input.value.length === 0) {
input.value = max.toString();
return true;
}
const value = parseInt(input.value);
if (value <= start) {
input.value = max.toString();
return false;
} else {
input.value = (value - 1).toString();
return true;
}
};
/**
* updateValue updates `value` with the current time in 24-hour format.
* If any component is invalid or blank, it sets `value` to an empty string.
@@ -216,10 +181,10 @@
}
if (e.key === 'ArrowUp') {
incrementValue(target, 12, 1);
incrementValue(target, { max: 12, start: 1 });
if (target.value === '12') toggleAMPM();
} else if (e.key === 'ArrowDown') {
decrementValue(target, 12, 1);
decrementValue(target, { max: 12, start: 1 });
if (target.value === '11') toggleAMPM();
} else {
return;
@@ -248,9 +213,9 @@
const target = targetMust<HTMLInputElement>(e);
if (e.key === 'ArrowUp') {
incrementValue(target, 59, 0);
incrementValue(target, { max: 59, start: 0 });
} else if (e.key === 'ArrowDown') {
decrementValue(target, 59, 0);
decrementValue(target, { max: 59, start: 0 });
} else {
return;
}