@carbon/react
Version:
React components for the Carbon Design System
297 lines (287 loc) • 9.34 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.
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
var cx = require('classnames');
var PropTypes = require('prop-types');
var React = require('react');
var index = require('../Popover/index.js');
var keys = require('../../internal/keyboard/keys.js');
var match = require('../../internal/keyboard/match.js');
var useEvent = require('../../internal/useEvent.js');
var useId = require('../../internal/useId.js');
var usePrefix = require('../../internal/usePrefix.js');
/**
* Used to render the label for a Toggletip
*/
function ToggletipLabel({
as: BaseComponent = 'span',
children,
className: customClassName,
...rest
}) {
const prefix = usePrefix.usePrefix();
const className = cx(`${prefix}--toggletip-label`, customClassName);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
const BaseComponentAsAny = BaseComponent;
return /*#__PURE__*/React.createElement(BaseComponentAsAny, _rollupPluginBabelHelpers.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 React.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 = React.useRef(null);
const [open, setOpen] = React.useState(defaultOpen);
const prefix = usePrefix.usePrefix();
const id = useId.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.match(event, keys.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)
useEvent.useWindowEvent('blur', () => {
if (open) {
actions.close();
}
});
React.useEffect(() => {
if (!ref.current) return;
const targetDocument = ref.current.ownerDocument || document;
const eventType = 'PointerEvent' in window ? 'pointerdown' : 'mousedown';
const handleOutsideClick = event => {
const node = event.target;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- https://github.com/carbon-design-system/carbon/issues/20452
if (open && node && !ref.current.contains(node)) {
setOpen(false);
}
};
const options = {
capture: true
};
targetDocument.addEventListener(eventType, handleOutsideClick, options);
return () => {
targetDocument.removeEventListener(eventType, handleOutsideClick, options);
};
}, [open]);
return /*#__PURE__*/React.createElement(ToggletipContext.Provider, {
value: value
}, /*#__PURE__*/React.createElement(index.Popover, _rollupPluginBabelHelpers.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.
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- https://github.com/carbon-design-system/carbon/issues/20452
const {
open,
...popoverNonOpenPropTypes
} = index.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.usePrefix();
const className = cx(`${prefix}--toggletip-button`, customClassName);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
const ComponentToggle = BaseComponent ?? 'button';
if (ComponentToggle !== 'button') {
return /*#__PURE__*/React.createElement(ComponentToggle, _rollupPluginBabelHelpers.extends({}, toggletip?.onClick, {
className: className
}, rest), children);
}
return /*#__PURE__*/React.createElement("button", _rollupPluginBabelHelpers.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';
const frFn = React.forwardRef;
/**
* `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 = frFn((props, ref) => {
const {
children,
className: customClassName
} = props;
const toggletip = useToggletip();
const prefix = usePrefix.usePrefix();
return /*#__PURE__*/React.createElement(index.PopoverContent, _rollupPluginBabelHelpers.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.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
};
exports.Toggletip = Toggletip;
exports.ToggletipActions = ToggletipActions;
exports.ToggletipButton = ToggletipButton;
exports.ToggletipContent = ToggletipContent;
exports.ToggletipLabel = ToggletipLabel;
exports.default = Toggletip;