11import React from 'react' ;
2- import { Input } from '@object-ui/components' ;
2+ import { Input , Slider } from '@object-ui/components' ;
33import { FieldWidgetProps } from './types' ;
44
55/**
66 * PercentField - Percentage input with configurable decimal precision
77 * Stores values as decimals (0-1) and displays as percentages (0-100%)
8+ * Includes a slider for interactive control.
89 */
910export function PercentField ( { value, onChange, field, readonly, errorMessage, className, ...props } : FieldWidgetProps < number > ) {
1011 const percentField = ( field || ( props as any ) . schema ) as any ;
@@ -21,6 +22,7 @@ export function PercentField({ value, onChange, field, readonly, errorMessage, c
2122
2223 // Convert between stored value (0-1) and display value (0-100)
2324 const displayValue = value != null ? ( value * 100 ) : '' ;
25+ const sliderValue = value != null ? value * 100 : 0 ;
2426
2527 const handleChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
2628 if ( e . target . value === '' ) {
@@ -32,22 +34,49 @@ export function PercentField({ value, onChange, field, readonly, errorMessage, c
3234 onChange ( val as any ) ;
3335 } ;
3436
37+ const handleSliderChange = ( values : number [ ] ) => {
38+ if ( readonly || props . disabled ) return ;
39+ if ( ! Array . isArray ( values ) || values . length === 0 ) {
40+ onChange ( null as any ) ;
41+ return ;
42+ }
43+ const raw = values [ 0 ] ;
44+ const nextValue = typeof raw === 'number' ? raw / 100 : null ;
45+ onChange ( nextValue as any ) ;
46+ } ;
47+
48+ // Derive slider step from precision so slider granularity matches the input
49+ const sliderStep = Math . pow ( 10 , - precision ) ;
50+
3551 return (
36- < div className = "relative" >
37- < Input
38- { ...props }
39- type = "number"
40- value = { displayValue }
41- onChange = { handleChange }
42- placeholder = { percentField ?. placeholder || '0' }
52+ < div className = "space-y-2" >
53+ < div className = "relative" >
54+ < Input
55+ { ...props }
56+ type = "number"
57+ value = { displayValue }
58+ onChange = { handleChange }
59+ placeholder = { percentField ?. placeholder || '0' }
60+ disabled = { readonly || props . disabled }
61+ className = { `pr-8 ${ className || '' } ` }
62+ step = { Math . pow ( 10 , - precision ) . toFixed ( precision ) }
63+ aria-invalid = { ! ! errorMessage }
64+ />
65+ < span className = "absolute right-3 top-1/2 -translate-y-1/2 text-sm text-gray-500" >
66+ %
67+ </ span >
68+ </ div >
69+ < Slider
70+ value = { [ sliderValue ] }
71+ onValueChange = { handleSliderChange }
72+ min = { 0 }
73+ max = { 100 }
74+ step = { sliderStep }
4375 disabled = { readonly || props . disabled }
44- className = { `pr-8 ${ className || '' } ` }
45- step = { Math . pow ( 10 , - precision ) . toFixed ( precision ) }
46- aria-invalid = { ! ! errorMessage }
76+ className = "w-full"
77+ aria-label = "Percentage"
78+ data-testid = "percent-slider"
4779 />
48- < span className = "absolute right-3 top-1/2 -translate-y-1/2 text-sm text-gray-500" >
49- %
50- </ span >
5180 </ div >
5281 ) ;
5382}
0 commit comments