UNPKG

@carbon/react

Version:

React components for the Carbon Design System

201 lines (199 loc) 6.32 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 { Escape } from "../../internal/keyboard/keys.js"; import { match } from "../../internal/keyboard/match.js"; import { useId } from "../../internal/useId.js"; import { useWindowEvent } from "../../internal/useEvent.js"; import { Popover, PopoverContent } from "../Popover/index.js"; import classNames from "classnames"; import React, { forwardRef, useContext, useEffect, useRef, useState } from "react"; import PropTypes from "prop-types"; import { jsx } from "react/jsx-runtime"; //#region src/components/Toggletip/index.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. */ /** * Used to render the label for a Toggletip */ function ToggletipLabel({ as: BaseComponent = "span", children, className: customClassName, ...rest }) { return /* @__PURE__ */ jsx(BaseComponent, { className: classNames(`${usePrefix()}--toggletip-label`, customClassName), ...rest, children }); } ToggletipLabel.propTypes = { as: PropTypes.elementType, children: PropTypes.node, className: PropTypes.string }; const ToggletipContext = React.createContext(void 0); function useToggletip() { return useContext(ToggletipContext); } /** * Used as a container for the button and content of a toggletip. This component * is responsible for coordinating between interactions with the button and the * visibility of the content */ function Toggletip({ align, as, autoAlign, className: customClassName, children, defaultOpen = false, ...rest }) { const ref = useRef(null); const [open, setOpen] = useState(defaultOpen); const prefix = usePrefix(); const id = useId(); const className = classNames(`${prefix}--toggletip`, customClassName, { [`${prefix}--toggletip--open`]: open, [`${prefix}--autoalign`]: autoAlign }); const actions = { toggle: () => { setOpen(!open); }, close: () => { setOpen(false); } }; const value = { buttonProps: { "aria-expanded": open, "aria-controls": id, "aria-describedby": open ? id : void 0, onClick: actions.toggle }, contentProps: { id }, onClick: { onClick: actions.toggle } }; const onKeyDown = (event) => { if (open && match(event, Escape)) { event.stopPropagation(); actions.close(); const button = ref.current?.children[0]; if (button instanceof HTMLButtonElement) button.focus(); } }; const handleBlur = (event) => { if (open && event.relatedTarget === null) return; if (!event.currentTarget.contains(event.relatedTarget)) actions.close(); }; useWindowEvent("blur", () => { if (open) actions.close(); }); useEffect(() => { if (!open || !ref.current) return; const targetDocument = ref.current.ownerDocument || document; const eventType = "PointerEvent" in window ? "pointerdown" : "mousedown"; const handleOutsideClick = (event) => { const { current } = ref; if (!current) return; const isInsideCurrent = (target) => target instanceof Node && current.contains(target); if (!(isInsideCurrent(event.target) || typeof event.composedPath === "function" && event.composedPath().some(isInsideCurrent))) setOpen(false); }; const options = { capture: true }; targetDocument.addEventListener(eventType, handleOutsideClick, options); return () => { targetDocument.removeEventListener(eventType, handleOutsideClick, options); }; }, [open]); return /* @__PURE__ */ jsx(ToggletipContext.Provider, { value, children: /* @__PURE__ */ jsx(Popover, { align, as, caret: true, className, dropShadow: false, highContrast: true, open, onKeyDown, onBlur: handleBlur, ref, autoAlign, ...rest, children }) }); } const { open, ...popoverNonOpenPropTypes } = Popover.propTypes ?? {}; Toggletip.propTypes = { ...popoverNonOpenPropTypes, defaultOpen: PropTypes.bool }; /** * `ToggletipButton` controls the visibility of the Toggletip through mouse * clicks and keyboard interactions. */ const ToggletipButton = forwardRef(function ToggletipButton({ children, className: customClassName, label = "Show information", as: BaseComponent, ...rest }, ref) { const toggletip = useToggletip(); const className = classNames(`${usePrefix()}--toggletip-button`, customClassName); const ComponentToggle = BaseComponent ?? "button"; if (ComponentToggle !== "button") return /* @__PURE__ */ jsx(ComponentToggle, { ...toggletip?.onClick, className, ...rest, children }); return /* @__PURE__ */ jsx("button", { ...toggletip?.buttonProps, "aria-label": label, type: "button", className, ref, ...rest, children }); }); ToggletipButton.propTypes = { children: PropTypes.node, className: PropTypes.string, label: PropTypes.string }; ToggletipButton.displayName = "ToggletipButton"; /** * `ToggletipContent` is a wrapper around `PopoverContent`. It places the * `children` passed in as a prop inside of `PopoverContent` so that they will * be rendered inside of the popover for this component. */ const ToggletipContent = forwardRef((props, ref) => { const { children, className: customClassName } = props; const toggletip = useToggletip(); const prefix = usePrefix(); return /* @__PURE__ */ jsx(PopoverContent, { className: customClassName, ...toggletip?.contentProps, ref, children: /* @__PURE__ */ jsx("div", { className: `${prefix}--toggletip-content`, children }) }); }); ToggletipContent.propTypes = { children: PropTypes.node, className: PropTypes.string }; ToggletipContent.displayName = "ToggletipContent"; /** * `ToggletipActions` is a container for one or two actions present at the base * of a toggletip. It is used for layout of these items. */ function ToggletipActions({ children, className: customClassName }) { return /* @__PURE__ */ jsx("div", { className: classNames(`${usePrefix()}--toggletip-actions`, customClassName), children }); } ToggletipActions.propTypes = { children: PropTypes.node, className: PropTypes.string }; //#endregion export { ToggletipActions, ToggletipButton, ToggletipContent, ToggletipLabel, Toggletip as default };