UNPKG

@amaui/ui-react

Version:
470 lines 18.4 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", "size", "version", "value", "valueDefault", "onChange", "options", "name", "multiple", "prefix", "sufix", "start", "end", "autoWidth", "getLabel", "fullWidth", "chip", "clear", "readOnly", "noSelectText", "disabled", "renderValues", "renderChip", "IconClear", "IconDropdown", "WrapperProps", "ChipProps", "ChiProps", "ListProps", "MenuProps", "IconButtonProps", "IconProps", "className", "style", "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 { getObjectValue, is, isEnvironment, unique } from '@amaui/utils'; import { classNames, style as styleMethod, useAmauiTheme } from '@amaui/style-react'; import IconMaterialClose from '@amaui/icons-material-rounded-react/IconMaterialCloseW100'; import IconMaterialArrowDropDown from '@amaui/icons-material-rounded-react/IconMaterialArrowDropDownW100'; import ListItemElement from '../ListItem'; import TypeElement from '../Type'; import MenuElement from '../Menu'; import ChipElement from '../Chip'; import ChipsElement from '../Chips'; import TextFieldElement from '../TextField'; import IconButtonElement from '../IconButton'; import LineElement from '../Line'; import { staticClassName } from '../utils'; const useStyle = styleMethod(theme => { const overflow = { width: '100%', overflow: 'hidden', whiteSpace: 'pre', textOverflow: 'ellipsis' }; return { root: { flex: 'unset', minWidth: '184px', '& .amaui-TextField-input': { position: 'absolute', left: '0', bottom: '0', pointerEvents: 'none', opacity: '0' }, '& .amaui-TextField-input-wrapper': { cursor: 'pointer' } }, wrapper: { position: 'relative' }, inputWrapper: { cursor: 'pointer', '&.amaui-TextField-input-wrapper': { cursor: 'pointer' } }, inputWrapper_chip_size_small: { minHeight: '48px' }, inputWrapper_chip_size_regular: { minHeight: '56px' }, inputWrapper_chip_size_large: { minHeight: '64px' }, input: _objectSpread(_objectSpread({ display: 'flex', alignItems: 'center', width: '100%', minHeight: 20, margin: '0', border: '0', color: theme.palette.text.default.primary, background: 'transparent', '-webkit-tap-highlight-color': 'transparent', textAlign: 'start', borderRadius: `${theme.shape.radius.unit / 2}px ${theme.shape.radius.unit / 2}px 0 0`, cursor: 'pointer', pointerEvents: 'none' }, theme.typography.values.b2), overflow), chip: { '&.amaui-TextField-input-wrapper': { height: 'unset' } }, chipGroup: { pointerEvents: 'auto' }, chipGroup_padding: { paddingTop: theme.methods.space.value(0.5, 'px') }, arrow: { transition: theme.methods.transitions.make('transform') }, arrow_open: { transform: 'rotate(-180deg)' }, open: { '&.amaui-TextField-root': { cursor: 'default' } }, fullWidth: { width: '100%' }, readOnly: { '&.amaui-TextField-root': { cursor: 'default' } }, disabled: { '&.amaui-TextField-root': { cursor: 'default' } } }; }, { name: 'amaui-Select' }); const getValue = value => value?.value !== undefined ? value.value : value; const Select = /*#__PURE__*/React.forwardRef((props_, ref) => { const theme = useAmauiTheme(); const props = React.useMemo(() => _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.amauiSelect?.props?.default), props_), [props_]); const Line = React.useMemo(() => theme?.elements?.Line || LineElement, [theme]); const ListItem = React.useMemo(() => theme?.elements?.ListItem || ListItemElement, [theme]); const Type = React.useMemo(() => theme?.elements?.Type || TypeElement, [theme]); const Menu = React.useMemo(() => theme?.elements?.Menu || MenuElement, [theme]); const Chip = React.useMemo(() => theme?.elements?.Chip || ChipElement, [theme]); const Chips = React.useMemo(() => theme?.elements?.Chips || ChipsElement, [theme]); const TextField = React.useMemo(() => theme?.elements?.TextField || TextFieldElement, [theme]); const IconButton = React.useMemo(() => theme?.elements?.IconButton || IconButtonElement, [theme]); const { tonal = true, color = 'primary', size = 'regular', version = 'filled', value: value_, valueDefault, onChange: onChange_, options, name, multiple, prefix, sufix, start, end, autoWidth = true, getLabel: getLabel_, fullWidth, chip, clear, readOnly, noSelectText, disabled, renderValues: renderValues_, renderChip, IconClear = IconMaterialClose, IconDropdown = IconMaterialArrowDropDown, WrapperProps, ChipProps, ChiProps, ListProps = { style: { maxHeight: 250, overflowY: 'auto', overflowX: 'hidden' } }, MenuProps = { portal: true }, IconButtonProps, IconProps, className, style, children: children_ } = props, other = _objectWithoutProperties(props, _excluded); const children = React.Children.toArray(children_); const [value, setValue] = React.useState(() => { const values = valueDefault !== undefined ? valueDefault : value_; return multiple ? (is('array', values) ? values : [values]).filter(Boolean) : values; }); const [open, setOpen] = React.useState(false); const [mouseDown, setMouseDown] = React.useState(false); const [focus, setFocus] = React.useState(false); const { classes } = useStyle(); const refs = { root: React.useRef(undefined), wrapper: React.useRef(undefined), menu: React.useRef(undefined), input: React.useRef(undefined), ids: { list: React.useId() } }; const styles = { root: {}, menu: {} }; if (MenuProps?.portal && autoWidth) { styles.menu.width = refs.wrapper.current?.clientWidth; } React.useEffect(() => { const method = event => { if (event.key === 'Escape') onClose(); }; const rootDocument = isEnvironment('browser') ? refs.root.current?.ownerDocument || window.document : undefined; rootDocument.addEventListener('keydown', method); return () => { // Clean up rootDocument.removeEventListener('keydown', method); }; }, []); React.useEffect(() => { if (value_ !== undefined && value_ !== value) setValue(value_); }, [value_]); const onMouseDown = React.useCallback(event => { if (!disabled && !readOnly) setMouseDown(true); }, []); const onMouseUp = React.useCallback(event => { if (!disabled && !readOnly) setMouseDown(false); }, []); const onFocus = React.useCallback(event => { if (!disabled && !readOnly) setFocus(true); }, []); const onBlur = React.useCallback(event => { if (!disabled && !readOnly) setFocus(false); }, []); const onClick = React.useCallback(event => { if (!disabled && !readOnly) setOpen(open_ => { if (open_) setFocus(false); return !open_; }); }, []); const onClickArrowDown = React.useCallback(event => { if (!disabled && !readOnly) setOpen(open_ => !open_); }, []); const onEnterKeyDown = React.useCallback(event => { if (event.key === 'Enter' && !disabled && !readOnly) setOpen(open_ => { if (open_) refs.input.current.focus(); return !open_; }); }, []); const onClose = React.useCallback(function () { let refocus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; if (!disabled && !readOnly) { setOpen(open_ => { if (open_ && refocus) refs.input.current.focus(); return false; }); } }, []); const onChange = valueNew => { // Inner controlled value if (!props.hasOwnProperty('value')) setValue(valueNew); if (is('function', onChange_)) onChange_(valueNew); }; const onSelect = newValue => { let values = multiple ? is('array', value) ? value : [value] : value; values = multiple ? unique([...values, newValue]) : newValue; onChange(values); }; const onUnselect = itemValue => { if (multiple) { let values = is('array', value) ? value : [value]; values = values.filter(item => item !== itemValue); onChange(values); } }; const onClear = React.useCallback(function () { let refocus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; if (!disabled && !readOnly) { onChange(multiple ? [] : null); if (refocus) refs.input.current.focus(); } }, [multiple, readOnly, disabled]); const items = React.useMemo(() => { return (options || []).map(item => _objectSpread(_objectSpread({}, item), {}, { name: String(item?.name !== undefined ? item?.name : item?.value !== undefined ? item.value : item), value: item?.value !== undefined ? item?.value : item })); }, [options]); const getLabel = (item, propsOther) => { if (is('function', getLabel_)) return getLabel_(item, propsOther); const properties = ['name', 'label', 'primary', 'secondary', 'tertiary', 'value', 'children']; const objects = [item, item?.props].filter(Boolean); for (const itemObject of objects) { if (is('simple', itemObject)) return itemObject; const valueItem = getObjectValue(itemObject, ...properties); if (valueItem !== undefined) return valueItem; } return noSelectText || 'Select an option'; }; const renderValue = function () { let itemValue = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : value; const item = !!items?.length ? items.find(item_ => getValue(item_) === getValue(itemValue)) : children.find(item_ => getValue(item_.props?.value) === getValue(itemValue)); return item ? getLabel(item, props) : getLabel(itemValue, props) || ''; }; const renderValues = renderValues_ || function () { let value__ = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : value; if (multiple) { if (chip) { return /*#__PURE__*/React.createElement(Chips, _extends({ wrap: "wrap", size: size, className: classNames([classes.chipGroup, version !== 'outlined' && classes.chipGroup_padding]) }, ChiProps), value__.map(item => { const other_ = { onClick: event => { event.preventDefault(); event.stopPropagation(); }, onRemove: event => { event.preventDefault(); event.stopPropagation(); onUnselect(item); }, input: true }; if (is('function', renderChip)) return renderChip(item, renderValue(item), other_); return /*#__PURE__*/React.createElement(Chip, _extends({ key: item, size: "small" }, other_, ChipProps), renderValue(item)); })); } const valuesAll = value.map(item => renderValue(item)); if (valuesAll.every(item => is('simple', item))) return valuesAll.join(', '); return value?.length ? valuesAll : noSelectText || name; } return renderValue(value); }; const endIcons = [end, clear && !!(multiple ? value.length : ![undefined, null].includes(value)) && /*#__PURE__*/React.createElement(IconButton, _extends({ onClick: onClear, size: "small", "aria-label": "Input clear" }, IconButtonProps), /*#__PURE__*/React.createElement(IconClear, IconProps)), ...(!readOnly ? [/*#__PURE__*/React.createElement(IconButton, _extends({ key: 1, size: "regular", onClick: onClickArrowDown, "aria-expanded": open, "aria-controls": refs.ids.list }, IconButtonProps), /*#__PURE__*/React.createElement(IconDropdown, _extends({ size: "large" }, IconProps, { className: classNames([IconProps?.className, classes.arrow, open && classes.arrow_open]) })))] : [])]; const sizeListItem = MenuProps?.size || size; return /*#__PURE__*/React.createElement(Line, _extends({ gap: 0, direction: "column", fullWidth: fullWidth }, WrapperProps, { ref: item => { if (WrapperProps?.ref) { if (is('function', WrapperProps.ref)) WrapperProps.ref(item);else WrapperProps.ref.current = item; } refs.wrapper.current = item; }, className: classNames([staticClassName('Select', theme) && ['amaui-Select-wrapper', fullWidth && 'amaui-full-width'], WrapperProps?.className, classes.wrapper]) }), /*#__PURE__*/React.createElement(TextField, _extends({ ref: refs.input, rootRef: item => { if (ref) { if (is('function', ref)) ref(item);else ref.current = item; } refs.root.current = item; }, onBlur: onBlur, onFocus: onFocus, enabled: open || focus || mouseDown || !!(is('array', value) ? value.length : ![undefined, null].includes(value)), focus: open || focus || mouseDown, className: classNames([staticClassName('Select', theme) && ['amaui-Select-root', `amaui-Select-version-${version}`, `amaui-Select-size-${size}`, open && `amaui-Select-open`, mouseDown && `amaui-Select-mouse-down`, focus && `amaui-Select-focus`], className, classes.root, open && classes.open, disabled && classes.disabled]), tonal: tonal, color: color, size: size, version: version, name: name, prefix: prefix, sufix: sufix, start: start, end: endIcons, readOnly: readOnly, endVerticalAlign: "center", role: "combobox", "aria-multiselectable": multiple, "aria-controls": refs.ids.list, "aria-expanded": open, "aria-haspopup": "listbox", "aria-labelledby": name, "aria-disabled": disabled, fullWidth: fullWidth, disabled: disabled, InputWrapperProps: { className: classNames([staticClassName('Select', theme) && ['amaui-Select-input-wrapper'], classes.inputWrapper, chip && [classes.chip, classes[`inputWrapper_chip_size_${size}`]], open && classes.open, readOnly && classes.readOnly]), onMouseDown, onMouseUp, onClick, onKeyDown: onEnterKeyDown }, inputProps: { disabled: true, readOnly: true }, style: _objectSpread(_objectSpread({}, style), styles.root) }, other), /*#__PURE__*/React.createElement(Line, { gap: 0, direction: "row", className: classNames([staticClassName('Select', theme) && ['amaui-Select-input'], classes.input, chip && classes.chip, open && classes.open, readOnly && classes.readOnly]), justify: other.align }, renderValues(value))), (!!items.length || children) && /*#__PURE__*/React.createElement(Menu, _extends({ ref: refs.menu, open: open, portal: false, onClose: () => onClose(false), anchorElement: refs.root.current, transformOrigin: "center top", transformOriginSwitch: "center bottom", maxWidth: "unset", size: size, menuItems: !!items.length ? items.map((item, index) => /*#__PURE__*/React.createElement(ListItem, _extends({ key: index, role: 'option', selected: item.selected !== undefined ? item.selected : multiple ? value.includes(item?.value) : value === item?.value, preselected: item.preselected !== undefined ? item.preselected : !(multiple ? value.includes(item?.value) : value === item?.value), onMouseUp: onMouseUp, onMouseDown: onMouseDown, primary: /*#__PURE__*/React.createElement(Type, { version: sizeListItem === 'large' ? 'b1' : sizeListItem === 'regular' ? 'b2' : 'b3' }, item.name), value: item.value, size: MenuProps?.size || size, button: true }, item.props, { onClick: event => { if (multiple && value.includes(item?.value)) onUnselect(item?.value);else onSelect(item?.value); if (is('function', item.props?.onClick)) item.props?.onClick(event); if (!multiple) setOpen(false); } }))) : children.map((item, index) => /*#__PURE__*/React.cloneElement(item, { key: index, role: 'option', selected: item.props.selected !== undefined ? item.props.selected : multiple ? value.includes(item.props?.value) : value === item.props?.value, preselected: item.props.preselected !== undefined ? item.props.preselected : !(multiple ? value.includes(item.props?.value) : value === item.props?.value), onMouseUp, onMouseDown, onClick: event => { if (multiple && value.includes(item.props?.value)) onUnselect(item.props?.value);else onSelect(item.props?.value); if (is('function', item.props?.onClick)) item.props?.onClick(event); if (!multiple) setOpen(false); } })), AppendProps: { alignment: 'start' }, ModalProps: { // focus: !MenuProps.portal }, ListProps: _objectSpread({ menu: true, size: ListProps?.size || MenuProps?.size || size, role: 'listbox', id: refs.ids.list, 'aria-label': name }, ListProps) }, MenuProps, { style: _objectSpread(_objectSpread({}, styles.menu), MenuProps?.style), className: classNames([MenuProps?.className]) }))); }); Select.displayName = 'amaui-Select'; export default Select;