@amaui/ui-react
Version:
UI for React
353 lines (347 loc) • 15.3 kB
JavaScript
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;