UNPKG

@atlaskit/button

Version:

A button triggers an event or action. They let users know what will happen next.

205 lines (196 loc) 7.6 kB
import _extends from "@babel/runtime/helpers/extends"; /* 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 const 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. */ const 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)" }); const 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)" }); const getSpacingFix = (children, spacingStyles) => { if (!children || getIfVisuallyHiddenChildren(children)) { return null; } return spacingStyles; }; const getChildren = (children, childrenStyles) => { if (getIfVisuallyHiddenChildren(children)) { return children; } return children ? jsx("span", { css: childrenStyles }, children) : null; }; const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(props, ref) { const { // I don't think analytics should be in button, but for now it is analyticsContext, appearance = 'default', autoFocus = false, buttonCss, children, className, href, // use the provided component prop, // else default to anchor if there is a href, and button if there is no href component: Component = href ? 'a' : 'button', iconAfter, iconBefore, interactionName, isDisabled = false, isSelected = false, onBlur, onClick: providedOnClick = noop, onFocus, onMouseDown: providedOnMouseDown = noop, overlay, // Pulling out so it doesn't spread on rendered component shouldFitContainer, spacing = 'default', tabIndex = 0, type = !href ? 'button' : undefined, testId, ...rest } = props; const ourRef = useRef(); const setRef = useCallback(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); const interactionContext = useContext(InteractionContext); const handleClick = useCallback((e, analyticsEvent) => { interactionContext && interactionContext.tracePress(interactionName, e.timeStamp); providedOnClick(e, analyticsEvent); }, [providedOnClick, interactionContext, interactionName]); const 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 const onMouseDown = useCallback(event => { event.preventDefault(); providedOnMouseDown(event); }, [providedOnMouseDown]); // Lose focus when becoming disabled (standard button behaviour) useEffect(() => { const el = ourRef.current; if (isDisabled && el && el === document.activeElement) { el.blur(); } }, [isDisabled]); // we are 'disabling' input with a button when there is an overlay const 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 const fadeStyles = css(getFadingCss({ hasOverlay })); const 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: `${isSelected || isDisabled ? `var(--ds-icon-subtle, ${N500})` : `var(--ds-icon-warning-inverse, ${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 })), iconBefore ? jsx("span", { css: [fadeStyles, // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766 getIconStyle({ spacing }), getSpacingFix(children, iconBeforeSpacingFixStyle)] }, iconBefore) : null, getChildren(children, [fadeStyles, getContentStyle({ 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 }), 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;