@mskcc/carbon-react
Version:
Carbon react components for the MSKCC DSM
262 lines (257 loc) • 8.13 kB
JavaScript
/**
* MSKCC 2021, 2024
*/
import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
import { Search as Search$1 } from '@carbon/icons-react';
import cx from 'classnames';
import PropTypes from 'prop-types';
import React__default, { useContext, useRef, useState } from 'react';
import { focus } from '../../internal/focus/index.js';
import { useId } from '../../internal/useId.js';
import { usePrefix } from '../../internal/usePrefix.js';
import { composeEventHandlers } from '../../tools/events.js';
import { useMergedRefs } from '../../internal/useMergedRefs.js';
import deprecate from '../../prop-types/deprecate.js';
import '../FluidForm/FluidForm.js';
import { FormContext } from '../FluidForm/FormContext.js';
import Button from '../Button/Button.js';
import '../Button/Button.Skeleton.js';
import { match } from '../../internal/keyboard/match.js';
import { Escape } from '../../internal/keyboard/keys.js';
const Search = /*#__PURE__*/React__default.forwardRef(function Search(_ref, forwardRef) {
let {
autoComplete = 'off',
className,
closeButtonLabelText = 'Clear search input',
defaultValue,
disabled,
isExpanded = true,
id,
labelText,
// @ts-expect-error: deprecated prop
light,
onChange = () => {},
onClear = () => {},
onKeyDown,
onExpand,
placeholder = 'Search',
renderIcon,
role = 'searchbox',
size = 'md',
type = 'text',
value,
disableTooltip,
...rest
} = _ref;
const hasPropValue = value || defaultValue ? true : false;
const prefix = usePrefix();
const {
isFluid
} = useContext(FormContext);
const inputRef = useRef(null);
const ref = useMergedRefs([forwardRef, inputRef]);
const inputId = useId('search-input');
const uniqueId = id || inputId;
const searchId = `${uniqueId}-search`;
const [hasContent, setHasContent] = useState(hasPropValue || false);
const [prevValue, setPrevValue] = useState(value);
const searchClasses = cx({
[`${prefix}--search`]: true,
[`${prefix}--search--sm`]: size === 'sm',
[`${prefix}--search--md`]: size === 'md',
[`${prefix}--search--lg`]: size === 'lg',
[`${prefix}--search--light`]: light,
[`${prefix}--search--disabled`]: disabled,
[`${prefix}--search--fluid`]: isFluid
}, className);
const clearClasses = cx('msk-search--close', {
'msk-search--close-hidden': !hasContent || !isExpanded
// [`${prefix}--search-close`]: true,
// [`${prefix}--search-close--hidden`]: !hasContent || !isExpanded,
});
const clearBtnClasses = cx('msk-search--close-btn', {
// 'msk-search--close-hidden': !hasContent || !isExpanded,
// [`${prefix}--search-close`]: true,
// [`${prefix}--search-close--hidden`]: !hasContent || !isExpanded,
});
if (value !== prevValue) {
setHasContent(!!value);
setPrevValue(value);
}
function clearInput() {
if (!value && inputRef.current) {
inputRef.current.value = '';
}
const inputTarget = Object.assign({}, inputRef.current, {
value: ''
});
const clearedEvt = {
target: inputTarget,
type: 'change'
};
onChange(clearedEvt);
onClear();
setHasContent(false);
focus(inputRef);
}
function handleChange(event) {
setHasContent(event.target.value !== '');
}
function handleKeyDown(event) {
if (match(event, Escape)) {
event.stopPropagation();
clearInput();
}
}
return /*#__PURE__*/React__default.createElement("div", {
role: "search",
"aria-label": placeholder,
className: searchClasses
}, /*#__PURE__*/React__default.createElement("div", {
"aria-labelledby": onExpand ? uniqueId : undefined,
role: onExpand ? 'button' : undefined,
className: `${prefix}--search-magnifier`,
onClick: onExpand
}, /*#__PURE__*/React__default.createElement(CustomSearchIcon, {
icon: renderIcon
})), /*#__PURE__*/React__default.createElement("label", {
id: searchId,
htmlFor: uniqueId,
className: `${prefix}--label`
}, labelText), /*#__PURE__*/React__default.createElement("input", _extends({}, rest, {
autoComplete: autoComplete,
className: `${prefix}--search-input`,
defaultValue: defaultValue,
disabled: disabled,
role: role,
ref: ref,
id: uniqueId,
onChange: composeEventHandlers([onChange, handleChange]),
onKeyDown: composeEventHandlers([onKeyDown, handleKeyDown]),
placeholder: placeholder,
type: type,
value: value
})), /*#__PURE__*/React__default.createElement(Button, {
kind: "ghost",
icon: "clear",
hasIconOnly: true,
size: size,
"aria-label": closeButtonLabelText,
disabled: disabled,
onClick: clearInput,
title: closeButtonLabelText,
iconDescription: closeButtonLabelText,
wrapperClasses: clearClasses,
className: clearBtnClasses,
disableTooltip: disableTooltip
}, "clear"));
});
Search.displayName = 'Search';
Search.propTypes = {
/**
* Specify an optional value for the `autocomplete` property on the underlying
* `<input>`, defaults to "off"
*/
autoComplete: PropTypes.string,
/**
* Specify an optional className to be applied to the container node
*/
className: PropTypes.string,
/**
* Specify a label to be read by screen readers on the "close" button
*/
closeButtonLabelText: PropTypes.string,
/**
* Optionally provide the default value of the `<input>`
*/
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* Specify whether the `<input>` should be disabled
*/
disabled: PropTypes.bool,
/**
* Specify a custom `id` for the input
*/
id: PropTypes.string,
/**
* Specify whether or not ExpandableSearch should render expanded or not
*/
isExpanded: PropTypes.bool,
/**
* Provide the label text for the Search icon
*/
labelText: PropTypes.node.isRequired,
/**
* Specify light version or default version of this control
*/
light: deprecate(PropTypes.bool, 'The `light` prop for `Search` 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.'),
/**
* Optional callback called when the search value changes.
*/
onChange: PropTypes.func,
/**
* Optional callback called when the search value is cleared.
*/
onClear: PropTypes.func,
/**
* Optional callback called when the magnifier icon is clicked in ExpandableSearch.
*/
onExpand: PropTypes.func,
/**
* Provide a handler that is invoked on the key down event for the input
*/
onKeyDown: PropTypes.func,
/**
* Provide an optional placeholder text for the Search.
* Note: if the label and placeholder differ,
* VoiceOver on Mac will read both
*/
placeholder: PropTypes.string,
/**
* Rendered icon for the Search.
* Can be a React component class
*/
// @ts-expect-error: PropTypes are not expressive enough to cover this case
renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* Specify the role for the underlying `<input>`, defaults to `searchbox`
*/
role: PropTypes.string,
/**
* Specify the size of the Search
*/
size: PropTypes.oneOf(['sm', 'md', 'lg']),
/**
* Optional prop to specify the type of the `<input>`
*/
type: PropTypes.string,
/**
* Specify the value of the `<input>`
*/
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
};
function CustomSearchIcon(_ref2) {
let {
icon: Icon
} = _ref2;
const prefix = usePrefix();
if (Icon) {
return /*#__PURE__*/React__default.createElement(Icon, {
className: `${prefix}--search-magnifier-icon`
});
}
return /*#__PURE__*/React__default.createElement(Search$1, {
className: `${prefix}--search-magnifier-icon`
});
}
CustomSearchIcon.propTypes = {
/**
* Specify whether the tooltip should be disabled
*/
disableTooltip: PropTypes.bool,
/**
* Rendered icon for the Search. Can be a React component class
*/
icon: PropTypes.oneOfType([PropTypes.func, PropTypes.object])
};
export { Search as default };