@carbon/react
Version:
React components for the Carbon Design System
283 lines (272 loc) • 11 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 2023
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
;
Object.defineProperty(exports, '__esModule', { value: true });
var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
var PropTypes = require('prop-types');
var React = require('react');
var cx = require('classnames');
var iconsReact = require('@carbon/icons-react');
var deprecate = require('../../prop-types/deprecate.js');
var usePrefix = require('../../internal/usePrefix.js');
require('../FluidForm/FluidForm.js');
var FormContext = require('../FluidForm/FormContext.js');
var useId = require('../../internal/useId.js');
var events = require('../../tools/events.js');
require('../Text/index.js');
var index = require('../AILabel/index.js');
var utils = require('../../internal/utils.js');
var Text = require('../Text/Text.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var cx__default = /*#__PURE__*/_interopDefaultLegacy(cx);
const Select = /*#__PURE__*/React__default["default"].forwardRef(function Select({
className,
decorator,
id,
inline = false,
labelText = 'Select',
disabled = false,
children,
// reserved for use with Pagination component
noLabel = false,
// eslint-disable-next-line no-unused-vars
hideLabel = false,
invalid = false,
invalidText = '',
helperText = '',
light = false,
readOnly,
size,
warn = false,
warnText,
onChange,
slug,
...other
}, ref) {
const prefix = usePrefix.usePrefix();
const {
isFluid
} = React.useContext(FormContext.FormContext);
const [isFocused, setIsFocused] = React.useState(false);
const selectInstanceId = useId.useId();
// Convert children to an array of valid elements once using type narrowing
const validChildren = React__default["default"].Children.toArray(children).filter(child => /*#__PURE__*/React__default["default"].isValidElement(child));
// Find the default option based on the specified defaultValue
const defaultOption = validChildren.find(child => child.props?.value === other?.defaultValue);
// Use the default option's text if available; otherwise, fallback to the first option's text
const initialTitle = defaultOption?.props?.text || validChildren[0]?.props?.text || '';
const [title, setTitle] = React.useState(initialTitle);
const selectClasses = cx__default["default"]({
[`${prefix}--select`]: true,
[`${prefix}--select--inline`]: inline,
[`${prefix}--select--light`]: light,
[`${prefix}--select--invalid`]: invalid,
[`${prefix}--select--disabled`]: disabled,
[`${prefix}--select--readonly`]: readOnly,
[`${prefix}--select--warning`]: warn,
[`${prefix}--select--fluid--invalid`]: isFluid && invalid,
[`${prefix}--select--fluid--focus`]: isFluid && isFocused,
[`${prefix}--select--slug`]: slug,
[`${prefix}--select--decorator`]: decorator
});
const labelClasses = cx__default["default"](`${prefix}--label`, {
[`${prefix}--visually-hidden`]: hideLabel,
[`${prefix}--label--disabled`]: disabled
});
const inputClasses = cx__default["default"]({
[`${prefix}--select-input`]: true,
[`${prefix}--select-input--${size}`]: size
});
const errorId = `${id}-error-msg`;
const errorText = (() => {
if (invalid) {
return invalidText;
}
if (warn) {
return warnText;
}
})();
const error = invalid || warn ? /*#__PURE__*/React__default["default"].createElement(Text.Text, {
as: "div",
className: `${prefix}--form-requirement`,
id: errorId
}, errorText) : null;
const helperTextClasses = cx__default["default"](`${prefix}--form__helper-text`, {
[`${prefix}--form__helper-text--disabled`]: disabled
});
const helperId = !helperText ? undefined : `select-helper-text-${selectInstanceId}`;
const helper = helperText ? /*#__PURE__*/React__default["default"].createElement(Text.Text, {
as: "div",
id: helperId,
className: helperTextClasses
}, helperText) : null;
const ariaProps = {};
if (invalid) {
ariaProps['aria-describedby'] = errorId;
} else if (!inline && !isFluid) {
ariaProps['aria-describedby'] = helper ? helperId : undefined;
}
const handleFocus = evt => {
setIsFocused(evt.type === 'focus' ? true : false);
};
const handleChange = evt => {
const selectedOption = evt?.target?.options[evt.target.selectedIndex];
setTitle(selectedOption?.text);
};
const readOnlyEventHandlers = {
onMouseDown: evt => {
// NOTE: does not prevent click
if (readOnly) {
evt.preventDefault();
// focus on the element as per readonly input behavior
evt.target.focus();
}
},
onKeyDown: evt => {
const selectAccessKeys = ['ArrowDown', 'ArrowUp', ' '];
// This prevents the select from opening for the above keys
if (readOnly && selectAccessKeys.includes(evt.key)) {
evt.preventDefault();
}
}
};
// AILabel always size `mini`
const candidate = slug ?? decorator;
const candidateIsAILabel = utils.isComponentElement(candidate, index.AILabel);
const normalizedDecorator = candidateIsAILabel ? /*#__PURE__*/React.cloneElement(candidate, {
size: 'mini'
}) : null;
const input = (() => {
return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("select", _rollupPluginBabelHelpers["extends"]({}, other, ariaProps, {
id: id,
className: inputClasses,
disabled: disabled || undefined,
"aria-invalid": invalid || undefined,
"aria-readonly": readOnly || undefined,
title: title,
onChange: events.composeEventHandlers([onChange, handleChange])
}, readOnlyEventHandlers, {
ref: ref
}), children), /*#__PURE__*/React__default["default"].createElement(iconsReact.ChevronDown, {
className: `${prefix}--select__arrow`
}), invalid && /*#__PURE__*/React__default["default"].createElement(iconsReact.WarningFilled, {
className: `${prefix}--select__invalid-icon`
}), !invalid && warn && /*#__PURE__*/React__default["default"].createElement(iconsReact.WarningAltFilled, {
className: `${prefix}--select__invalid-icon ${prefix}--select__invalid-icon--warning`
}));
})();
return /*#__PURE__*/React__default["default"].createElement("div", {
className: cx__default["default"](`${prefix}--form-item`, className)
}, /*#__PURE__*/React__default["default"].createElement("div", {
className: selectClasses
}, !noLabel && /*#__PURE__*/React__default["default"].createElement(Text.Text, {
as: "label",
htmlFor: id,
className: labelClasses
}, labelText), inline && /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--select-input--inline__wrapper`
}, /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--select-input__wrapper`,
"data-invalid": invalid || null
}, input), error), !inline && /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--select-input__wrapper`,
"data-invalid": invalid || null,
onFocus: handleFocus,
onBlur: handleFocus
}, input, slug ? normalizedDecorator : decorator ? /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--select__inner-wrapper--decorator`
}, normalizedDecorator) : '', isFluid && /*#__PURE__*/React__default["default"].createElement("hr", {
className: `${prefix}--select__divider`
}), isFluid && error ? error : null), !inline && !isFluid && error ? error : helper));
});
Select.displayName = 'Select';
Select.propTypes = {
/**
* Provide the contents of your Select
*/
children: PropTypes__default["default"].node,
/**
* Specify an optional className to be applied to the node containing the label and the select box
*/
className: PropTypes__default["default"].string,
/**
* **Experimental**: Provide a decorator component to be rendered inside the `Select` component
*/
decorator: PropTypes__default["default"].node,
/**
* Optionally provide the default value of the `<select>`
*/
defaultValue: PropTypes__default["default"].any,
/**
* Specify whether the control is disabled
*/
disabled: PropTypes__default["default"].bool,
/**
* Provide text that is used alongside the control label for additional help
*/
helperText: PropTypes__default["default"].node,
/**
* Specify whether the label should be hidden, or not
*/
hideLabel: PropTypes__default["default"].bool,
/**
* Specify a custom `id` for the `<select>`
*/
id: PropTypes__default["default"].string.isRequired,
/**
* Specify whether you want the inline version of this control
*/
inline: PropTypes__default["default"].bool,
/**
* Specify if the currently value is invalid.
*/
invalid: PropTypes__default["default"].bool,
/**
* Message which is displayed if the value is invalid.
*/
invalidText: PropTypes__default["default"].node,
/**
* Provide label text to be read by screen readers when interacting with the
* control
*/
labelText: PropTypes__default["default"].node,
/**
* `true` to use the light version. For use on $ui-01 backgrounds only.
* Don't use this to make tile background color same as container background color.
*/
light: deprecate["default"](PropTypes__default["default"].bool, 'The `light` prop for `Select` is no longer needed and has ' + 'been deprecated in v11 in favor of the new `Layer` component. It will be moved in the next major release.'),
/**
* Reserved for use with Pagination component. Will not render a label for the
* select since Pagination renders one for us.
*/
noLabel: PropTypes__default["default"].bool,
/**
* Provide an optional `onChange` hook that is called each time the value of
* the underlying `<input>` changes
*/
onChange: PropTypes__default["default"].func,
/**
* Whether the select should be read-only
*/
readOnly: PropTypes__default["default"].bool,
/**
* Specify the size of the Select Input.
*/
size: PropTypes__default["default"].oneOf(['sm', 'md', 'lg']),
slug: deprecate["default"](PropTypes__default["default"].node, 'The `slug` prop has been deprecated and will be removed in the next major version. Use the decorator prop instead.'),
/**
* Specify whether the control is currently in warning state
*/
warn: PropTypes__default["default"].bool,
/**
* Provide the text that is displayed when the control is in warning state
*/
warnText: PropTypes__default["default"].node
};
exports["default"] = Select;