You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
275 lines
6.3 KiB
275 lines
6.3 KiB
import { useState } from 'react'; |
|
import { |
|
Box, |
|
inputLabelClasses as muiInputLabelClasses, |
|
OutlinedInputProps as MUIOutlinedInputProps, |
|
outlinedInputClasses as muiOutlinedInputClasses, |
|
Slider as MUISlider, |
|
sliderClasses as muiSliderClasses, |
|
SliderProps as MUISliderProps, |
|
TypographyProps as MUITypographyProps, |
|
FormControl, |
|
} from '@mui/material'; |
|
|
|
import { BORDER_RADIUS, GREY } from '../lib/consts/DEFAULT_THEME'; |
|
|
|
import MessageBox, { MessageBoxProps } from './MessageBox'; |
|
import OutlinedInput from './OutlinedInput'; |
|
import OutlinedInputLabel from './OutlinedInputLabel'; |
|
import { BodyText } from './Text'; |
|
|
|
type SliderOnBlur = Exclude<MUISliderProps['onBlur'], undefined>; |
|
type SliderOnChange = Exclude<MUISliderProps['onChange'], undefined>; |
|
type SliderOnFocus = Exclude<MUISliderProps['onFocus'], undefined>; |
|
type SliderValue = Exclude<MUISliderProps['value'], undefined>; |
|
|
|
type SliderOptionalProps = { |
|
isAllowTextInput?: boolean; |
|
labelId?: string; |
|
labelProps?: MUITypographyProps; |
|
messageBoxProps?: Partial<MessageBoxProps>; |
|
sliderProps?: Omit<MUISliderProps, 'onChange'> & { |
|
onChange?: (value: number | number[]) => void; |
|
}; |
|
}; |
|
|
|
type SliderProps = { |
|
label: string; |
|
value: SliderValue; |
|
} & SliderOptionalProps; |
|
|
|
type TextInputOnChange = Exclude<MUIOutlinedInputProps['onChange'], undefined>; |
|
|
|
const SLIDER_DEFAULT_PROPS: Required<SliderOptionalProps> = { |
|
isAllowTextInput: false, |
|
labelId: '', |
|
labelProps: {}, |
|
messageBoxProps: {}, |
|
sliderProps: {}, |
|
}; |
|
|
|
const createInputLabelDecorator = ({ |
|
isFocused, |
|
label, |
|
}: { |
|
isFocused?: boolean; |
|
label: string; |
|
}) => { |
|
const borderColor = GREY; |
|
const borderStyle = 'solid'; |
|
const content = '""'; |
|
|
|
let rootTop = '0'; |
|
let labelGapMargin = '0 .6em 0 .4em'; |
|
let borderWidth = '1px 0 0 0'; |
|
let opacity = '0.3'; |
|
|
|
if (isFocused) { |
|
rootTop = '-1px'; |
|
labelGapMargin = '0 1em 0 1em'; |
|
borderWidth = '2px 0 0 0'; |
|
opacity = '1'; |
|
} |
|
|
|
return ( |
|
<Box |
|
sx={{ |
|
display: 'flex', |
|
flexDirection: 'row', |
|
position: 'absolute', |
|
top: rootTop, |
|
width: '100%', |
|
|
|
'> :last-child': { |
|
flexGrow: 1, |
|
}, |
|
}} |
|
> |
|
<Box |
|
sx={{ |
|
borderColor, |
|
borderStyle, |
|
borderWidth, |
|
content, |
|
opacity, |
|
width: '.6em', |
|
}} |
|
/> |
|
<BodyText |
|
sx={{ |
|
fontSize: '.75em', |
|
margin: labelGapMargin, |
|
visibility: 'hidden', |
|
}} |
|
text={label} |
|
/> |
|
<Box |
|
sx={{ |
|
borderColor, |
|
borderStyle, |
|
borderWidth, |
|
content, |
|
opacity, |
|
}} |
|
/> |
|
</Box> |
|
); |
|
}; |
|
|
|
const createOutlinedInput = ({ |
|
isFocused, |
|
max, |
|
min, |
|
onBlur, |
|
onChange, |
|
onFocus, |
|
sliderValue, |
|
sx, |
|
}: { |
|
isFocused?: boolean; |
|
max?: number; |
|
min?: number; |
|
onBlur?: SliderOnBlur; |
|
onChange?: TextInputOnChange; |
|
onFocus?: SliderOnFocus; |
|
sliderValue?: SliderValue; |
|
sx?: MUISliderProps['sx']; |
|
}) => ( |
|
<OutlinedInput |
|
{...{ |
|
className: isFocused ? muiOutlinedInputClasses.focused : '', |
|
inputProps: { max, min }, |
|
onBlur, |
|
onChange, |
|
onFocus, |
|
sx, |
|
type: 'number', |
|
value: sliderValue, |
|
}} |
|
/> |
|
); |
|
|
|
const Slider = ({ |
|
messageBoxProps = SLIDER_DEFAULT_PROPS.messageBoxProps, |
|
isAllowTextInput = SLIDER_DEFAULT_PROPS.isAllowTextInput, |
|
label, |
|
labelId = SLIDER_DEFAULT_PROPS.labelId, |
|
labelProps = SLIDER_DEFAULT_PROPS.labelProps, |
|
sliderProps = SLIDER_DEFAULT_PROPS.sliderProps, |
|
value, |
|
}: SliderProps): JSX.Element => { |
|
const { |
|
sx: messageBoxSx, |
|
text: messageBoxText, |
|
...messageBoxRestProps |
|
} = messageBoxProps; |
|
const { sx: labelSx } = labelProps; |
|
const { |
|
max, |
|
min, |
|
onChange: sliderChangeCallback, |
|
sx: sliderSx, |
|
valueLabelDisplay: sliderValueLabelDisplay, |
|
} = sliderProps; |
|
|
|
const [isFocused, setIsFocused] = useState<boolean>(false); |
|
|
|
const handleLocalSliderBlur: SliderOnBlur = () => { |
|
setIsFocused(false); |
|
}; |
|
|
|
const handleLocalSliderFocus: SliderOnFocus = () => { |
|
setIsFocused(true); |
|
}; |
|
|
|
const handleSliderChange: SliderOnChange = (event, newValue) => { |
|
sliderChangeCallback?.call(null, newValue); |
|
}; |
|
|
|
const handleTextInputChange: TextInputOnChange = ({ |
|
target: { value: newValue }, |
|
}) => { |
|
sliderChangeCallback?.call(null, parseFloat(newValue)); |
|
}; |
|
|
|
return ( |
|
<FormControl sx={{ display: 'flex', flexDirection: 'column' }}> |
|
<OutlinedInputLabel |
|
{...{ |
|
className: isFocused ? muiInputLabelClasses.focused : '', |
|
id: labelId, |
|
sx: { |
|
...labelSx, |
|
}, |
|
}} |
|
> |
|
{label} |
|
</OutlinedInputLabel> |
|
{createInputLabelDecorator({ isFocused, label })} |
|
<Box |
|
sx={{ |
|
alignItems: 'center', |
|
display: 'flex', |
|
flexDirection: 'row', |
|
|
|
'> :first-child': { flexGrow: 1 }, |
|
}} |
|
> |
|
<MUISlider |
|
{...{ |
|
'aria-labelledby': labelId, |
|
max, |
|
min, |
|
onBlur: handleLocalSliderBlur, |
|
onChange: handleSliderChange, |
|
onFocus: handleLocalSliderFocus, |
|
sx: { |
|
color: GREY, |
|
marginLeft: '1em', |
|
marginRight: '1em', |
|
|
|
[`& .${muiSliderClasses.thumb}`]: { |
|
borderRadius: BORDER_RADIUS, |
|
transform: 'translate(-50%, -50%) rotate(45deg)', |
|
}, |
|
|
|
...sliderSx, |
|
}, |
|
value, |
|
valueLabelDisplay: sliderValueLabelDisplay, |
|
}} |
|
/> |
|
{createOutlinedInput({ |
|
isFocused, |
|
max, |
|
min, |
|
onBlur: handleLocalSliderBlur, |
|
onChange: handleTextInputChange, |
|
onFocus: handleLocalSliderFocus, |
|
sliderValue: value, |
|
sx: isAllowTextInput |
|
? undefined |
|
: { |
|
visibility: 'collapse', |
|
}, |
|
})} |
|
</Box> |
|
{messageBoxText && ( |
|
<MessageBox |
|
// eslint-disable-next-line react/jsx-props-no-spreading |
|
{...{ |
|
...messageBoxRestProps, |
|
sx: { marginTop: '.4em', ...messageBoxSx }, |
|
text: messageBoxText, |
|
}} |
|
/> |
|
)} |
|
</FormControl> |
|
); |
|
}; |
|
|
|
Slider.defaultProps = SLIDER_DEFAULT_PROPS; |
|
|
|
export type { SliderProps }; |
|
|
|
export default Slider;
|
|
|