UNPKG

@carbon/react

Version:

React components for the Carbon Design System

191 lines (189 loc) 7.06 kB
/** * Copyright IBM Corp. 2016, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import { usePrefix } from "../../internal/usePrefix.js"; import { Enter, Escape, Space } from "../../internal/keyboard/keys.js"; import { match } from "../../internal/keyboard/match.js"; import { useId } from "../../internal/useId.js"; import { noopFn } from "../../internal/noopFn.js"; import { deprecate } from "../../prop-types/deprecate.js"; import { useMergedRefs } from "../../internal/useMergedRefs.js"; import { Tooltip } from "../Tooltip/Tooltip.js"; import { composeEventHandlers } from "../../tools/events.js"; import { FormContext } from "../FluidForm/FormContext.js"; import { isSearchValuePresent } from "./utils.js"; import classNames from "classnames"; import React, { useContext, useEffect, useRef, useState } from "react"; import PropTypes from "prop-types"; import { jsx, jsxs } from "react/jsx-runtime"; import { Close, Search } from "@carbon/icons-react"; //#region src/components/Search/Search.tsx /** * Copyright IBM Corp. 2016, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const Search$1 = React.forwardRef(({ autoComplete = "off", className, closeButtonLabelText = "Clear search input", defaultValue, disabled, isExpanded = true, id, labelText, light, onChange = () => {}, onClear = () => {}, onKeyDown, onExpand, placeholder = "Search", renderIcon, role, size = "md", type = "search", value, ...rest }, forwardRef) => { const hasPropValue = isSearchValuePresent(value) || isSearchValuePresent(defaultValue); const prefix = usePrefix(); const { isFluid } = useContext(FormContext); const inputRef = useRef(null); const ref = useMergedRefs([forwardRef, inputRef]); const expandButtonRef = useRef(null); const inputId = useId("search-input"); const uniqueId = id || inputId; const searchId = `${uniqueId}-search`; const [hasContent, setHasContent] = useState(hasPropValue || false); const searchClasses = classNames({ [`${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 = classNames({ [`${prefix}--search-close`]: true, [`${prefix}--search-close--hidden`]: !hasContent || !isExpanded }); useEffect(() => { if (typeof value !== "undefined") setHasContent(isSearchValuePresent(value)); }, [value]); function clearInput() { if (!value && inputRef.current) inputRef.current.value = ""; if (inputRef.current) { const inputTarget = Object.assign({}, inputRef.current, { value: "" }); onChange({ bubbles: false, cancelable: false, currentTarget: inputRef.current, defaultPrevented: false, eventPhase: 0, isDefaultPrevented: () => false, isPropagationStopped: () => false, isTrusted: false, nativeEvent: new Event("change"), persist: noopFn, preventDefault: noopFn, stopPropagation: noopFn, target: inputTarget, timeStamp: 0, type: "change" }); } onClear(); setHasContent(false); inputRef.current?.focus(); } function handleChange(event) { setHasContent(event.target.value !== ""); } function handleKeyDown(event) { if (match(event, Escape)) { event.stopPropagation(); if (inputRef.current?.value) clearInput(); else if (onExpand && isExpanded) expandButtonRef.current?.focus(); } } function handleExpandButtonKeyDown(event) { if (match(event, Enter) || match(event, Space)) { event.stopPropagation(); if (onExpand) onExpand(event); } } const magnifierButton = /* @__PURE__ */ jsx("div", { "aria-labelledby": onExpand ? searchId : void 0, role: onExpand ? "button" : void 0, className: `${prefix}--search-magnifier`, onClick: onExpand, onKeyDown: handleExpandButtonKeyDown, tabIndex: onExpand && !isExpanded ? 0 : -1, ref: expandButtonRef, "aria-expanded": onExpand && isExpanded ? true : onExpand && !isExpanded ? false : void 0, "aria-controls": onExpand ? uniqueId : void 0, children: /* @__PURE__ */ jsx(CustomSearchIcon, { icon: renderIcon }) }); return /* @__PURE__ */ jsxs("div", { role: "search", "aria-label": placeholder, className: searchClasses, children: [ onExpand && !isExpanded ? /* @__PURE__ */ jsx(Tooltip, { className: `${prefix}--search-tooltip ${prefix}--search-magnifier-tooltip ${prefix}--icon-tooltip`, align: "top", label: "Search", children: magnifierButton }) : magnifierButton, /* @__PURE__ */ jsx("label", { id: searchId, htmlFor: uniqueId, className: `${prefix}--label`, children: labelText }), /* @__PURE__ */ jsx("input", { autoComplete, className: `${prefix}--search-input`, defaultValue, disabled, role, ref, id: uniqueId, onChange: composeEventHandlers([onChange, handleChange]), onKeyDown: composeEventHandlers([onKeyDown, handleKeyDown]), placeholder, type, value, tabIndex: onExpand && !isExpanded ? -1 : void 0, ...rest }), /* @__PURE__ */ jsx("button", { "aria-label": closeButtonLabelText, className: clearClasses, disabled, onClick: clearInput, title: closeButtonLabelText, type: "button", children: /* @__PURE__ */ jsx(Close, {}) }) ] }); }); Search$1.displayName = "Search"; Search$1.propTypes = { autoComplete: PropTypes.string, className: PropTypes.string, closeButtonLabelText: PropTypes.string, defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), disabled: PropTypes.bool, id: PropTypes.string, isExpanded: PropTypes.bool, labelText: PropTypes.node.isRequired, 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."), onChange: PropTypes.func, onClear: PropTypes.func, onExpand: PropTypes.func, onKeyDown: PropTypes.func, placeholder: PropTypes.string, renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), role: deprecate(PropTypes.string, "The `role` prop has been deprecated since <input type=\"search\"> already provides correct semantics. It will be removed in the next major release of Carbon."), size: PropTypes.oneOf([ "sm", "md", "lg" ]), type: PropTypes.string, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) }; function CustomSearchIcon({ icon: Icon }) { const prefix = usePrefix(); if (Icon) return /* @__PURE__ */ jsx(Icon, { className: `${prefix}--search-magnifier-icon` }); return /* @__PURE__ */ jsx(Search, { className: `${prefix}--search-magnifier-icon` }); } CustomSearchIcon.propTypes = { icon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]) }; //#endregion export { Search$1 as default };