UNPKG

@amaui/ui-react

Version:
353 lines (347 loc) 15.3 kB
import _extends from "@babel/runtime/helpers/extends"; import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; const _excluded = ["tonal", "color", "colorInactive", "size", "value", "valueDefault", "valueActive", "valueActiveDefault", "onChange", "onChangeActive", "values", "precision", "onlyValue", "readOnly", "disabled", "icon", "icons", "iconInactive", "iconActive", "IconProps", "IconActiveProps", "IconInactiveProps", "Component", "className", "children"]; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } import React from 'react'; import { clamp, is, isEnvironment, valueFromPercentageWithinRange } from '@amaui/utils'; import { classNames, style as styleMethod, useAmauiTheme } from '@amaui/style-react'; import IconMaterialGrade from '@amaui/icons-material-rounded-react/IconMaterialGradeW100'; import IconMaterialGradeFilled from '@amaui/icons-material-rounded-react/IconMaterialGradeW100Filled'; import { staticClassName } from '../utils'; const useStyle = styleMethod(theme => ({ root: { display: 'inline-flex', alignItems: 'flex-start', cursor: 'pointer', touchAction: 'none' }, iconWrapper: { display: 'inline-flex', position: 'relative', transition: theme.methods.transitions.make('transform', { duration: 'xs' }), '&:hover': { transform: 'scale(1.14)' } }, iconWrapper_readOnly: { '&:hover': { transform: 'none' } }, icon: { display: 'inline-flex' }, active: { display: 'inline-flex', position: 'absolute', inset: '0', overflow: 'hidden', height: '100%', width: '0px' }, inactive: { opacity: theme.palette.light ? '0.24' : '0.4' }, focus_outline: { outline: `1px solid ${theme.palette.text.default.secondary}` }, readOnly: { cursor: 'default' }, disabled: { opacity: theme.palette.visual_contrast.default.opacity.disabled, cursor: 'default' } }), { name: 'amaui-Rating' }); const Rating = /*#__PURE__*/React.forwardRef((props_, ref) => { const theme = useAmauiTheme(); const props = React.useMemo(() => _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.amauiRating?.props?.default), props_), [props_]); const { tonal = true, color = 'primary', colorInactive = 'default', size = 'regular', value: value_, valueDefault, valueActive: valueActive_, valueActiveDefault, onChange, onChangeActive, values = 5, precision = 1, onlyValue, readOnly, disabled, icon, icons, iconInactive = /*#__PURE__*/React.createElement(IconMaterialGrade, null), iconActive = /*#__PURE__*/React.createElement(IconMaterialGradeFilled, null), IconProps, IconActiveProps, IconInactiveProps, Component = 'span', className, children } = props, other = _objectWithoutProperties(props, _excluded); const { classes } = useStyle(); const [init, setInit] = React.useState(false); const [value, setValue] = React.useState(valueDefault !== undefined ? valueDefault : value_); const [valueActive, setValueActive] = React.useState(valueActiveDefault !== undefined ? valueActiveDefault : valueActive_); const [hover, setHover] = React.useState(false); const [focus, setFocus] = React.useState(false); const [mouseDown, setMouseDown] = React.useState(false); const refs = { root: React.useRef(undefined), props: React.useRef(undefined), value: React.useRef(undefined), valueActive: React.useRef(undefined), values: React.useRef([]), mouseDown: React.useRef(undefined), hover: React.useRef(undefined), direction: React.useRef(undefined) }; refs.props.current = props; refs.value.current = value; refs.valueActive.current = valueActive; refs.mouseDown.current = mouseDown; refs.hover.current = hover; refs.direction.current = theme.direction; const min = 0; const max = values; const valueDecimals = (String(precision).includes('e-') ? +String(precision).split('e-')[1] : String(precision).split('.')[1]?.length) || 0; const valuePrecision = valueMouse => { let value__ = valueFromPercentageWithinRange(valueMouse * 100, min, max); if (refs.direction.current === 'rtl') value__ = max + min - value__; if (value__ <= min) return min; if (value__ >= max) return max; // previous value let previous = clamp(+(value__ - value__ % precision).toFixed(valueDecimals), min, max); if (value__ < 0) previous -= precision; // next value const next = clamp(+(previous + precision).toFixed(valueDecimals), min, max); const valueNew = value__ > next ? previous : next; return valueNew; }; React.useEffect(() => { const onMouseUp = () => { if (!disabled && !readOnly) setMouseDown(false); }; const onMouseMove = event => { if (!refs.props.current.disabled && !refs.props.current.readOnly && (refs.mouseDown.current || refs.hover.current)) { const valuePrevious = refs.hover.current ? refs.valueActive.current : refs.value.current; const x = event.clientX; const rect = refs.root.current.getBoundingClientRect(); const { width: width_ } = rect; // Value to the precision point value const value__ = valuePrecision((x - rect.x) / width_); const valueNew = value__; if (valueNew !== valuePrevious) { if (!props.hasOwnProperty('value')) { // Inner controlled value if (refs.hover.current) setValueActive(valueNew);else setValue(valueNew); } if (refs.hover.current) { if (is('function', onChangeActive)) onChangeActive(valueNew); } else { if (is('function', onChange)) onChange(valueNew); } } } }; const onTouchMove = event => { if (!refs.props.current.disabled && !refs.props.current.readOnly && refs.mouseDown.current) { const valuePrevious = refs.hover.current ? refs.valueActive.current : refs.value.current; const x = event.touches[0].clientX; const rect = refs.root.current.getBoundingClientRect(); const { width: width_ } = rect; // Value to the precision point value const value__ = valuePrecision((x - rect.x) / width_); const valueNew = value__; if (valueNew !== valuePrevious) { // Inner controlled value if (!props.hasOwnProperty('value')) setValue(valueNew); if (is('function', onChange)) onChange(valueNew); } } }; const rootDocument = isEnvironment('browser') ? refs.root.current?.ownerDocument || window.document : undefined; rootDocument.addEventListener('mouseup', onMouseUp); rootDocument.addEventListener('mousemove', onMouseMove); rootDocument.addEventListener('touchend', onMouseUp, { passive: true }); rootDocument.addEventListener('touchmove', onTouchMove, { passive: true }); setInit(true); return () => { rootDocument.removeEventListener('mouseup', onMouseUp); rootDocument.removeEventListener('touchend', onMouseUp); rootDocument.removeEventListener('mousemove', onMouseMove); rootDocument.removeEventListener('touchmove', onTouchMove); }; }, []); React.useEffect(() => { if (init && value_ !== value) setValue(value_); }, [value_]); React.useEffect(() => { if (init && valueActive_ !== valueActive) setValueActive(valueActive_); }, [valueActive_]); const onMouseDown = React.useCallback(() => { if (!disabled && !readOnly) setMouseDown(true); }, [disabled, readOnly]); const onClick = React.useCallback(event => { if (!disabled && !readOnly) { // Make precision value // if value is same as previous value clear // otherwise update the value y const x = event.clientX; const rect = refs.root.current.getBoundingClientRect(); const { width: width_ } = rect; const valueNew = valuePrecision((x - rect.x) / width_); if (value === valueNew) onClear();else { if (!props.hasOwnProperty('value')) setValue(valueNew); if (is('function', onChange)) onChange(valueNew); } } }, [disabled, readOnly, value, valueActive]); const move = function () { let forward_ = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; const forward = theme.direction === 'ltr' ? forward_ : !forward_; let value__ = refs.value.current || 0; // previous value const previous = clamp(+(value__ - precision).toFixed(valueDecimals), min, max); // next value const next = clamp(+(value__ + precision).toFixed(valueDecimals), min, max); value__ = forward ? next : previous; const valueNew = value__; if (valueNew !== refs.value.current) { if (!props.hasOwnProperty('value')) setValue(valueNew); if (is('function', onChange)) onChange(valueNew); } }; const onClear = () => { if (!props.hasOwnProperty('value')) setValue(''); if (is('function', onChange)) onChange(''); setHover(false); }; const onKeyDown = React.useCallback(event => { if (!disabled && !readOnly) { if (['Enter', 'Escape', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'ArrowLeft', 'Home', 'End'].includes(event.key)) { // Prevent default event.preventDefault(); switch (event.key) { case 'End': if (!props.hasOwnProperty('value')) setValue(0); if (is('function', onChange)) return onChange(0); return; case 'Home': if (!props.hasOwnProperty('value')) setValue(values); if (is('function', onChange)) return onChange(values); return; case 'ArrowUp': case 'ArrowRight': return move(); case 'ArrowDown': case 'ArrowLeft': return move(false); case 'Enter': if (value === valueActive) { if (!props.hasOwnProperty('value')) setValue(''); if (is('function', onChange)) onChange(''); } return; case 'Escape': if (!props.hasOwnProperty('value')) setValue(''); if (is('function', onChange)) onChange(''); return; default: break; } } } }, [disabled, readOnly, value, valueActive, precision]); const onFocus = React.useCallback(event => { if (!disabled && !readOnly && !mouseDown) setFocus(true); }, [disabled, readOnly, mouseDown]); const onBlur = React.useCallback(() => { if (!disabled && !readOnly) setFocus(false); }, [disabled, readOnly]); const onMouseEnter = React.useCallback(() => { if (!disabled && !readOnly) setHover(true); }, [disabled, readOnly]); const onMouseLeave = React.useCallback(() => { if (!disabled && !readOnly) setHover(false); }, [disabled, readOnly]); const width = index => { const value__ = !hover ? value : valueActive; if (value__ > index - 1 && value__ <= index) { if (value__ === index) return '100%'; return `${value__ % 1 * 100}%`; } if (is('number', value__) && index < +value__.toFixed(1) && !onlyValue) return '100%'; }; const getIcon = function (index) { let inactive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; if (inactive) return icons?.[index]?.iconInactive || icons?.[index]?.icon || icon || iconInactive; return icons?.[index]?.iconActive || icons?.[index]?.icon || icon || iconActive; }; const selected = index => value > index - 1 && value <= index; return /*#__PURE__*/React.createElement(Component, _extends({ ref: item => { if (ref) { if (is('function', ref)) ref(item);else ref.current = ref; } refs.root.current = item; }, tabIndex: !disabled && !readOnly ? 0 : undefined, onBlur: onBlur, onFocus: onFocus, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onMouseDown: onMouseDown, onTouchStart: onMouseDown, onKeyDown: onKeyDown, className: classNames([staticClassName('Rating', theme) && ['amaui-Rating-root', `amaui-Rating-size-${size}`, hover && `amaui-Button-hover`, mouseDown && `amaui-Button-mouse-down`, focus && [`amaui-Button-focus`, [undefined, 0].includes(value) && `amaui-Button-focus-noValue`], readOnly && `amaui-Rating-read-only`, disabled && `amaui-Rating-disabled`], className, classes.root, focus && [classes.focus, [undefined, 0].includes(value) && classes.focus_outline], readOnly && classes.readOnly, disabled && classes.disabled]) }, other), new Array(values).fill(undefined).map((item, index) => { const IconInactive = getIcon(index + 1); const IconActive = getIcon(index + 1, false); return /*#__PURE__*/React.createElement("span", { ref: item_ => refs.values.current.push(item_), key: index, onClick: onClick, className: classNames([staticClassName('Rating', theme) && ['amaui-Rating-icon-wrapper', focus && selected(index + 1) && 'amaui-Rating-focus'], classes.iconWrapper, focus && selected(index + 1) && classes.focus_outline, (readOnly || disabled) && classes.iconWrapper_readOnly]) }, /*#__PURE__*/React.createElement("span", { className: classNames([staticClassName('Rating', theme) && ['amaui-Rating-icon', 'amaui-Rating-icon-inactive'], classes.icon, classes.inactive]) }, /*#__PURE__*/React.cloneElement(IconInactive, _objectSpread(_objectSpread({ color: colorInactive, size: IconInactive.props?.size !== undefined ? IconInactive.props?.size : size }, IconProps), IconInactiveProps))), /*#__PURE__*/React.createElement("span", { className: classNames([staticClassName('Rating', theme) && ['amaui-Rating-icon', 'amaui-Rating-icon-active'], classes.icon, classes.active]), style: { width: width(index + 1) } }, /*#__PURE__*/React.cloneElement(IconActive, _objectSpread(_objectSpread({ tonal: IconActive.props?.tonal !== undefined ? IconActive.props?.tonal : tonal, color: IconActive.props?.color !== undefined ? IconActive.props?.color : color, size: IconActive.props?.size !== undefined ? IconActive.props?.size : size }, IconProps), IconActiveProps)))); })); }); Rating.displayName = 'amaui-Rating'; export default Rating;