number-input.tsx 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { AffixedInput } from '@/vdb/components/data-input/affixed-input.js';
  2. import { Input } from '@/vdb/components/ui/input.js';
  3. import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
  4. import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
  5. import { ReactNode } from 'react';
  6. export type NumberInputProps = DashboardFormComponentProps & {
  7. min?: number;
  8. max?: number;
  9. step?: number;
  10. prefix?: ReactNode;
  11. suffix?: ReactNode;
  12. };
  13. /**
  14. * @description
  15. * A component for displaying a numeric value.
  16. *
  17. * @docsCategory form-components
  18. * @docsPage NumberInput
  19. */
  20. export function NumberInput({
  21. fieldDef,
  22. onChange,
  23. prefix: overridePrefix,
  24. suffix: overrideSuffix,
  25. ...fieldProps
  26. }: Readonly<NumberInputProps>) {
  27. const readOnly = fieldProps.disabled || isReadonlyField(fieldDef);
  28. const isFloat = fieldDef ? fieldDef.type === 'float' : false;
  29. const min = fieldProps.min ?? fieldDef?.ui?.min;
  30. const max = fieldProps.max ?? fieldDef?.ui?.max;
  31. const step = fieldProps.step ?? (fieldDef?.ui?.step || (isFloat ? 0.01 : 1));
  32. const prefix = overridePrefix ?? fieldDef?.ui?.prefix;
  33. const suffix = overrideSuffix ?? fieldDef?.ui?.suffix;
  34. const shouldUseAffixedInput = prefix || suffix;
  35. const value = fieldProps.value ?? '';
  36. const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  37. if (readOnly) return;
  38. let numValue = e.target.valueAsNumber;
  39. if (Number.isNaN(numValue) && e.target.value) {
  40. const normalized = e.target.value.replace(',', '.');
  41. numValue = Number(normalized);
  42. }
  43. if (Number.isNaN(numValue)) {
  44. onChange(null);
  45. } else {
  46. onChange(numValue);
  47. }
  48. };
  49. if (shouldUseAffixedInput) {
  50. return (
  51. <AffixedInput
  52. {...fieldProps}
  53. value={value}
  54. type="number"
  55. onChange={handleChange}
  56. min={min}
  57. max={max}
  58. step={step}
  59. prefix={prefix}
  60. suffix={suffix}
  61. className="bg-background"
  62. disabled={readOnly}
  63. />
  64. );
  65. }
  66. return (
  67. <Input
  68. type="number"
  69. onChange={handleChange}
  70. {...fieldProps}
  71. value={value}
  72. min={min}
  73. max={max}
  74. step={step}
  75. disabled={readOnly}
  76. />
  77. );
  78. }