@atlaskit/button
Version:
A button triggers an event or action. They let users know what will happen next.
211 lines (202 loc) • 8.97 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
var _excluded = ["analyticsContext", "appearance", "autoFocus", "buttonCss", "children", "className", "href", "component", "iconAfter", "iconBefore", "interactionName", "isDisabled", "isSelected", "onBlur", "onClick", "onFocus", "onMouseDown", "overlay", "shouldFitContainer", "spacing", "tabIndex", "type", "testId"];
/* eslint-disable @atlaskit/design-system/consistent-css-prop-usage */
/**
* @jsxRuntime classic
* @jsx jsx
*/
import React, { useCallback, useContext, useEffect, useRef } from 'react';
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
import { css, jsx } from '@emotion/react';
import { usePlatformLeafEventHandler } from '@atlaskit/analytics-next';
import noop from '@atlaskit/ds-lib/noop';
import useAutoFocus from '@atlaskit/ds-lib/use-auto-focus';
import FocusRing from '@atlaskit/focus-ring';
// eslint-disable-next-line no-duplicate-imports
import InteractionContext from '@atlaskit/interaction-context';
import { N500 } from '@atlaskit/theme/colors';
import blockEvents from './block-events';
import { getContentStyle, getFadingCss, getIconStyle, overlayCss } from './css';
import { getIfVisuallyHiddenChildren } from './get-if-visually-hidden-children';
// Disabled buttons will still publish events for nested elements in webkit.
// We are disabling pointer events on child elements so that
// the button will always be the target of events
// Note: firefox does not have this behaviour for child elements
var noPointerEventsOnChildrenCss = {
'> *': {
pointerEvents: 'none'
}
};
/**
* These CSS variables consumed by the new icons, to allow them to have appropriate
* padding inside Button while also maintaining spacing for the existing icons.
*
* These styles can be removed once the new icons are fully rolled out, feature flag
* platform-visual-refresh-icons is cleaned up,
* and we bump Button to set padding based on the new icons.
*/
var iconBeforeSpacingFixStyle = css({
'--ds--button--new-icon-padding-end': "var(--ds-space-025, 2px)",
'--ds--button--new-icon-padding-start': "var(--ds-space-050, 4px)",
marginInlineStart: "var(--ds-space-negative-025, -2px)"
});
var iconAfterSpacingFixStyle = css({
'--ds--button--new-icon-padding-end': "var(--ds-space-050, 4px)",
'--ds--button--new-icon-padding-start': "var(--ds-space-025, 2px)",
marginInlineEnd: "var(--ds-space-negative-025, -2px)"
});
var getSpacingFix = function getSpacingFix(children, spacingStyles) {
if (!children || getIfVisuallyHiddenChildren(children)) {
return null;
}
return spacingStyles;
};
var getChildren = function getChildren(children, childrenStyles) {
if (getIfVisuallyHiddenChildren(children)) {
return children;
}
return children ? jsx("span", {
css: childrenStyles
}, children) : null;
};
var ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(props, ref) {
var analyticsContext = props.analyticsContext,
_props$appearance = props.appearance,
appearance = _props$appearance === void 0 ? 'default' : _props$appearance,
_props$autoFocus = props.autoFocus,
autoFocus = _props$autoFocus === void 0 ? false : _props$autoFocus,
buttonCss = props.buttonCss,
children = props.children,
className = props.className,
href = props.href,
_props$component = props.component,
Component = _props$component === void 0 ? href ? 'a' : 'button' : _props$component,
iconAfter = props.iconAfter,
iconBefore = props.iconBefore,
interactionName = props.interactionName,
_props$isDisabled = props.isDisabled,
isDisabled = _props$isDisabled === void 0 ? false : _props$isDisabled,
_props$isSelected = props.isSelected,
isSelected = _props$isSelected === void 0 ? false : _props$isSelected,
onBlur = props.onBlur,
_props$onClick = props.onClick,
providedOnClick = _props$onClick === void 0 ? noop : _props$onClick,
onFocus = props.onFocus,
_props$onMouseDown = props.onMouseDown,
providedOnMouseDown = _props$onMouseDown === void 0 ? noop : _props$onMouseDown,
overlay = props.overlay,
shouldFitContainer = props.shouldFitContainer,
_props$spacing = props.spacing,
spacing = _props$spacing === void 0 ? 'default' : _props$spacing,
_props$tabIndex = props.tabIndex,
tabIndex = _props$tabIndex === void 0 ? 0 : _props$tabIndex,
_props$type = props.type,
type = _props$type === void 0 ? !href ? 'button' : undefined : _props$type,
testId = props.testId,
rest = _objectWithoutProperties(props, _excluded);
var ourRef = useRef();
var setRef = useCallback(function (node) {
ourRef.current = node;
if (ref == null) {
return;
}
if (typeof ref === 'function') {
ref(node);
return;
}
// We can write to ref's `current` property, but Typescript does not like it.
// @ts-ignore
ref.current = node;
}, [ourRef, ref]);
// Cross browser auto focusing is pretty broken, so we are doing it ourselves
useAutoFocus(ourRef, autoFocus);
var interactionContext = useContext(InteractionContext);
var handleClick = useCallback(function (e, analyticsEvent) {
interactionContext && interactionContext.tracePress(interactionName, e.timeStamp);
providedOnClick(e, analyticsEvent);
}, [providedOnClick, interactionContext, interactionName]);
var onClick = usePlatformLeafEventHandler({
fn: handleClick,
action: 'clicked',
componentName: 'button',
packageName: "@atlaskit/button",
packageVersion: "0.0.0-development",
analyticsData: analyticsContext
});
// Button currently calls preventDefault, which is not standard button behaviour
var onMouseDown = useCallback(function (event) {
event.preventDefault();
providedOnMouseDown(event);
}, [providedOnMouseDown]);
// Lose focus when becoming disabled (standard button behaviour)
useEffect(function () {
var el = ourRef.current;
if (isDisabled && el && el === document.activeElement) {
el.blur();
}
}, [isDisabled]);
// we are 'disabling' input with a button when there is an overlay
var hasOverlay = Boolean(overlay);
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
var fadeStyles = css(getFadingCss({
hasOverlay: hasOverlay
}));
var isInteractive = !isDisabled && !hasOverlay;
/**
* HACK: Spinner needs to have different colours in the "new" tokens design compared to the old design.
* For now, while we support both, these styles reach into Spinner when a theme is set, applies the right color.
* Ticket to remove: https://product-fabric.atlassian.net/browse/DSP-2067.
*/
var spinnerHackCss = {};
if (isSelected || isDisabled || appearance === 'warning') {
spinnerHackCss = {
'[data-theme] & circle': {
stroke: "".concat(isSelected || isDisabled ? "var(--ds-icon-subtle, ".concat(N500, ")") : "var(--ds-icon-warning-inverse, ".concat(N500, ")"), " !important")
}
};
}
return jsx(FocusRing, null, jsx(Component, _extends({}, rest, {
ref: setRef
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
,
className: className,
css: [buttonCss, isInteractive ? null : noPointerEventsOnChildrenCss]
// using undefined so that the property doesn't exist when false
,
"data-has-overlay": hasOverlay ? true : undefined,
"data-testid": testId,
disabled: isDisabled,
href: isInteractive ? href : undefined,
onBlur: onBlur,
onClick: onClick,
onFocus: onFocus,
onMouseDown: onMouseDown
// Adding a tab index so element is always focusable, even when not a <button> or <a>
// Disabling focus via keyboard navigation when disabled
// as this is standard button behaviour
,
tabIndex: isDisabled ? -1 : tabIndex,
type: type
}, blockEvents({
isInteractive: isInteractive
})), iconBefore ? jsx("span", {
css: [fadeStyles,
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
getIconStyle({
spacing: spacing
}), getSpacingFix(children, iconBeforeSpacingFixStyle)]
}, iconBefore) : null, getChildren(children, [fadeStyles, getContentStyle({
spacing: spacing
})]), iconAfter ? jsx("span", {
css: [fadeStyles,
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
getIconStyle({
spacing: spacing
}), getSpacingFix(children, iconAfterSpacingFixStyle)]
}, iconAfter) : null, overlay ? jsx("span", {
css: [overlayCss, spinnerHackCss]
}, overlay) : null));
});
// eslint-disable-next-line @repo/internal/react/require-jsdoc
export default ButtonBase;