@carbon/react
Version:
React components for the Carbon Design System
250 lines (246 loc) • 7.82 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.
*/
import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
import PropTypes from 'prop-types';
import React from 'react';
import { IconButtonKinds, IconButton } from '../IconButton/index.js';
import ButtonBase from './ButtonBase.js';
const ButtonKinds = ['primary', 'secondary', 'danger', 'ghost', 'danger--primary', 'danger--ghost', 'danger--tertiary', 'tertiary'];
const ButtonSizes = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
const ButtonTooltipAlignments = ['start', 'center', 'end'];
const ButtonTooltipPositions = ['top', 'right', 'bottom', 'left'];
function isIconOnlyButton(hasIconOnly, _kind) {
if (hasIconOnly === true) {
return true;
}
return false;
}
const Button = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
as,
autoAlign = false,
children,
hasIconOnly = false,
tooltipHighContrast = true,
tooltipDropShadow = false,
iconDescription,
kind = 'primary',
onBlur,
onClick,
onFocus,
onMouseEnter,
onMouseLeave,
renderIcon: ButtonImageElement,
size,
tooltipAlignment = 'center',
tooltipPosition = 'top',
...rest
} = props;
if (ButtonImageElement && !children && !iconDescription) {
console.error('Button: renderIcon property specified without also providing an iconDescription property. ' + 'This may impact accessibility for screen reader users.');
}
const iconOnlyImage = !ButtonImageElement ? null : /*#__PURE__*/React.createElement(ButtonImageElement, null);
if (!isIconOnlyButton(hasIconOnly)) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {
tooltipAlignment,
...propsWithoutTooltipAlignment
} = props;
return /*#__PURE__*/React.createElement(ButtonBase, _extends({
ref: ref
}, propsWithoutTooltipAlignment));
} else {
let align = undefined;
if (tooltipPosition === 'top' || tooltipPosition === 'bottom') {
if (tooltipAlignment === 'center') {
align = tooltipPosition;
}
if (tooltipAlignment === 'end') {
align = `${tooltipPosition}-end`;
}
if (tooltipAlignment === 'start') {
align = `${tooltipPosition}-start`;
}
}
if (tooltipPosition === 'right' || tooltipPosition === 'left') {
align = tooltipPosition;
}
return (
/*#__PURE__*/
// @ts-expect-error - `IconButton` does not support all `size`s that
// `Button` supports.
//
// TODO: What should be done here?
// 1. Should the `IconButton` not be rendered if the `size` is not
// supported?
// 2. Should an error be thrown?
// 3. Something else?
React.createElement(IconButton, _extends({}, rest, {
ref: ref,
as: as,
align: align,
label: iconDescription,
kind: kind,
size: size,
highContrast: tooltipHighContrast,
dropShadow: tooltipDropShadow,
onMouseEnter: onMouseEnter,
onMouseLeave: onMouseLeave,
onFocus: onFocus,
onBlur: onBlur,
autoAlign: autoAlign,
onClick: onClick,
renderIcon: iconOnlyImage ? null : ButtonImageElement // avoid doubling the icon.
}), iconOnlyImage ?? children)
);
}
});
Button.displayName = 'Button';
Button.propTypes = {
/**
* Specify how the button itself should be rendered.
* Make sure to apply all props to the root node and render children appropriately
*/
as: PropTypes.oneOfType([PropTypes.func, PropTypes.string, PropTypes.elementType]),
/**
* **Experimental**: Will attempt to automatically align the tooltip. Requires
* React v17+
* @see https://github.com/carbon-design-system/carbon/issues/18714
*/
autoAlign: PropTypes.bool,
/**
* Specify the content of your Button
*/
children: PropTypes.node,
/**
* Specify an optional className to be added to your Button
*/
className: PropTypes.string,
/**
* Specify the message read by screen readers for the danger button variant
*/
dangerDescription: PropTypes.string,
/**
* Specify whether the Button should be disabled, or not
*/
disabled: PropTypes.bool,
/**
* Specify if the button is an icon-only button
*/
hasIconOnly: PropTypes.bool,
/**
* Optionally specify an href for your Button to become an `<a>` element
*/
href: PropTypes.string,
/**
* If specifying the `renderIcon` prop, provide a description for that icon that can
* be read by screen readers
*/
iconDescription: props => {
if (props.renderIcon && !props.children && !props.iconDescription) {
return new Error('renderIcon property specified without also providing an iconDescription property.');
}
return null;
},
/**
* Specify whether the Button is expressive, or not
*/
isExpressive: PropTypes.bool,
/**
* Specify whether the Button is currently selected. Only applies to the Ghost variant.
*/
isSelected: PropTypes.bool,
/**
* Specify the kind of Button you want to create
*/
kind: (props, propName, componentName) => {
const {
hasIconOnly
} = props;
const validKinds = hasIconOnly ? IconButtonKinds : ButtonKinds;
if (props[propName] === undefined) {
return null;
}
if (!validKinds.includes(props[propName])) {
return new Error(`Invalid prop \`${propName}\` supplied to \`${componentName}\`. Expected one of ${validKinds.join(', ')}.`);
}
return null;
},
/**
* Provide an optional function to be called when the button element
* loses focus
*/
onBlur: PropTypes.func,
/**
* Provide an optional function to be called when the button element
* is clicked
*/
onClick: PropTypes.func,
/**
* Provide an optional function to be called when the button element
* receives focus
*/
onFocus: PropTypes.func,
/**
* Provide an optional function to be called when the mouse
* enters the button element
*/
onMouseEnter: PropTypes.func,
/**
* Provide an optional function to be called when the mouse
* leaves the button element
*/
onMouseLeave: PropTypes.func,
/**
* Optionally specify a `rel` when using an `<a>` element.
*/
rel: PropTypes.string,
/**
* A component used to render an icon.
*/
renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* Optional prop to specify the role of the Button
*/
role: PropTypes.string,
/**
* Specify the size of the button, from the following list of sizes:
*/
size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl', '2xl']),
/**
* Optional prop to specify the tabIndex of the Button
*/
tabIndex: PropTypes.number,
/**
* Optionally specify a `target` when using an `<a>` element.
*/
target: PropTypes.string,
/**
* Specify the alignment of the tooltip to the icon-only button.
* Can be one of: start, center, or end.
*/
tooltipAlignment: PropTypes.oneOf(['start', 'center', 'end']),
/**
* Enable drop shadow for tooltips for icon-only buttons.
*/
tooltipDropShadow: PropTypes.bool,
/**
* Enable high-contrast theme for tooltips for icon-only buttons.
* Defaults to true.
*/
tooltipHighContrast: PropTypes.bool,
/**
* Specify the direction of the tooltip for icon-only buttons.
* Can be either top, right, bottom, or left.
*/
tooltipPosition: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
/**
* Optional prop to specify the type of the Button
*/
type: PropTypes.oneOf(['button', 'reset', 'submit'])
};
export { ButtonKinds, ButtonSizes, ButtonTooltipAlignments, ButtonTooltipPositions, Button as default };