UNPKG

@carbon/react

Version:

React components for the Carbon Design System

271 lines (263 loc) 7.94 kB
/** * 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. */ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js'; import cx from 'classnames'; import PropTypes from 'prop-types'; import React, { useContext, useRef, useState } from 'react'; import { Popover, PopoverContent } from '../Popover/index.js'; import { Escape } from '../../internal/keyboard/keys.js'; import { match } from '../../internal/keyboard/match.js'; import { useWindowEvent } from '../../internal/useEvent.js'; import { useId } from '../../internal/useId.js'; import { usePrefix } from '../../internal/usePrefix.js'; /** * Used to render the label for a Toggletip */ function ToggletipLabel({ as: BaseComponent = 'span', children, className: customClassName, ...rest }) { const prefix = usePrefix(); const className = cx(`${prefix}--toggletip-label`, customClassName); const BaseComponentAsAny = BaseComponent; return /*#__PURE__*/React.createElement(BaseComponentAsAny, _extends({ className: className }, rest), children); } ToggletipLabel.propTypes = { /** * Provide a custom element or component to render the top-level node for the * component. */ as: PropTypes.elementType, /** * Custom children to be rendered as the content of the label */ children: PropTypes.node, /** * Provide a custom class name to be added to the outermost node in the * component */ className: PropTypes.string }; // Used to coordinate accessibility props between button and content along with // the actions to open and close the toggletip const ToggletipContext = /*#__PURE__*/React.createContext(undefined); 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 = cx(`${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 : undefined, onClick: actions.toggle }, contentProps: { id }, onClick: { onClick: actions.toggle } }; const onKeyDown = event => { if (open && match(event, Escape)) { event.stopPropagation(); actions.close(); // If the menu is closed while focus is still inside the menu, it should return to the trigger button (#12922) const button = ref.current?.children[0]; if (button instanceof HTMLButtonElement) { button.focus(); } } }; const handleBlur = event => { // Do not close if the menu itself is clicked, should only close on focus out if (open && event.relatedTarget === null) { return; } if (!event.currentTarget.contains(event.relatedTarget)) { // The menu should be closed when focus leaves the `Toggletip` (#12922) actions.close(); } }; // If the `Toggletip` is the last focusable item in the tab order, it should also close when the browser window loses focus (#12922) useWindowEvent('blur', () => { if (open) { actions.close(); } }); useWindowEvent('click', ({ target }) => { if (open && target instanceof Node && !ref.current?.contains(target)) { actions.close(); } }); return /*#__PURE__*/React.createElement(ToggletipContext.Provider, { value: value }, /*#__PURE__*/React.createElement(Popover, _extends({ align: align, as: as, caret: true, className: className, dropShadow: false, highContrast: true, open: open, onKeyDown: onKeyDown, onBlur: handleBlur, ref: ref, autoAlign: autoAlign }, rest), children)); } // Get all the properties from Popover except for "open". // The Typescript types for PropTypes are really messed up so we need lots of // casting. It will be great when we can finally get rid of PropTypes altogether. const { open, ...popoverNonOpenPropTypes } = Popover.propTypes ?? {}; Toggletip.propTypes = { // Has all of Popover's PropTypes except for "open". ...popoverNonOpenPropTypes, /** * Specify if the toggletip should be open by default */ defaultOpen: PropTypes.bool }; /** * `ToggletipButton` controls the visibility of the Toggletip through mouse * clicks and keyboard interactions. */ const ToggletipButton = /*#__PURE__*/React.forwardRef(function ToggletipButton({ children, className: customClassName, label = 'Show information', as: BaseComponent, ...rest }, ref) { const toggletip = useToggletip(); const prefix = usePrefix(); const className = cx(`${prefix}--toggletip-button`, customClassName); const ComponentToggle = BaseComponent ?? 'button'; if (ComponentToggle !== 'button') { return /*#__PURE__*/React.createElement(ComponentToggle, _extends({}, toggletip?.onClick, { className: className }, rest), children); } return /*#__PURE__*/React.createElement("button", _extends({}, toggletip?.buttonProps, { "aria-label": label, type: "button", className: className, ref: ref }, rest), children); }); ToggletipButton.propTypes = { /** * Custom children to be rendered as the content of the label */ children: PropTypes.node, /** * Provide a custom class name to be added to the outermost node in the * component */ className: PropTypes.string, /** * Provide an accessible label for this button */ 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 = /*#__PURE__*/React.forwardRef(function ToggletipContent({ children, className: customClassName }, ref) { const toggletip = useToggletip(); const prefix = usePrefix(); return /*#__PURE__*/React.createElement(PopoverContent, _extends({ className: customClassName }, toggletip?.contentProps, { ref: ref }), /*#__PURE__*/React.createElement("div", { className: `${prefix}--toggletip-content` }, children)); }); ToggletipContent.propTypes = { /** * Custom children to be rendered as the content of the label */ children: PropTypes.node, /** * Provide a custom class name to be added to the outermost node in the * component */ 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 }) { const prefix = usePrefix(); const className = cx(`${prefix}--toggletip-actions`, customClassName); return /*#__PURE__*/React.createElement("div", { className: className }, children); } ToggletipActions.propTypes = { /** * Custom children to be rendered as the content of the label */ children: PropTypes.node, /** * Provide a custom class name to be added to the outermost node in the * component */ className: PropTypes.string }; export { Toggletip, ToggletipActions, ToggletipButton, ToggletipContent, ToggletipLabel };