UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

233 lines (227 loc) • 12.6 kB
'use strict'; var React = require('react'); var styles = require('./styles.js'); var useRefObjectAsForwardedRef = require('../hooks/useRefObjectAsForwardedRef.js'); var defaultSxProp = require('../utils/defaultSxProp.js'); var ConditionalWrapper = require('../internal/components/ConditionalWrapper.js'); var clsx = require('clsx'); var ButtonBase_module = require('./ButtonBase.module.css.js'); var reactIs = require('react-is'); var jsxRuntime = require('react/jsx-runtime'); var useId = require('../hooks/useId.js'); var Box = require('../Box/Box.js'); var Spinner = require('../Spinner/Spinner.js'); var VisuallyHidden = require('../VisuallyHidden/VisuallyHidden.js'); var AriaStatus = require('../live-region/AriaStatus.js'); var CounterLabel = require('../CounterLabel/CounterLabel.js'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var React__default = /*#__PURE__*/_interopDefault(React); const renderModuleVisual = (Visual, loading, visualName, counterLabel) => /*#__PURE__*/jsxRuntime.jsx("span", { "data-component": visualName, className: clsx.clsx(!counterLabel && ButtonBase_module.Visual, loading ? ButtonBase_module.LoadingSpinner : ButtonBase_module.VisualWrap), children: loading ? /*#__PURE__*/jsxRuntime.jsx(Spinner, { size: "small" }) : reactIs.isElement(Visual) ? Visual : /*#__PURE__*/jsxRuntime.jsx(Visual, {}) }); renderModuleVisual.displayName = "renderModuleVisual"; const ButtonBase = /*#__PURE__*/React.forwardRef(({ children, as: Component = 'button', sx: sxProp = defaultSxProp.defaultSxProp, ...props }, forwardedRef) => { const { leadingVisual: LeadingVisual, trailingVisual: TrailingVisual, trailingAction: TrailingAction, ['aria-describedby']: ariaDescribedBy, ['aria-labelledby']: ariaLabelledBy, count, icon: Icon, id, variant = 'default', size = 'medium', alignContent = 'center', block = false, loading, loadingAnnouncement = 'Loading', inactive, onClick, labelWrap, className, ...rest } = props; const innerRef = React__default.default.useRef(null); useRefObjectAsForwardedRef.useRefObjectAsForwardedRef(forwardedRef, innerRef); const uuid = useId.useId(id); const loadingAnnouncementID = `${uuid}-loading-announcement`; if (process.env.NODE_ENV !== "production") { /** * The Linter yells because it thinks this conditionally calls an effect, * but since this is a compile-time flag and not a runtime conditional * this is safe, and ensures the entire effect is kept out of prod builds * shaving precious bytes from the output, and avoiding mounting a noop effect */ // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/rules-of-hooks React__default.default.useEffect(() => { if (innerRef.current && !(innerRef.current instanceof HTMLButtonElement) && !(innerRef.current instanceof HTMLAnchorElement) && !(innerRef.current.tagName === 'SUMMARY')) { // eslint-disable-next-line no-console console.warn('This component should be an instanceof a semantic button or anchor'); } }, [innerRef]); } if (sxProp !== defaultSxProp.defaultSxProp) { return /*#__PURE__*/jsxRuntime.jsxs(ConditionalWrapper.ConditionalWrapper // If anything is passed to `loading`, we need the wrapper: // If we just checked for `loading` as a boolean, the wrapper wouldn't be rendered // when `loading` is `false`. // Then, the component re-renders in a way that the button will lose focus when switching between loading states. , { if: typeof loading !== 'undefined', className: block ? ButtonBase_module.ConditionalWrapper : undefined, "data-loading-wrapper": true, children: [/*#__PURE__*/jsxRuntime.jsx(Box, { as: Component, sx: sxProp, "aria-disabled": loading ? true : undefined, ...rest, ref: innerRef, className: clsx.clsx(ButtonBase_module.ButtonBase, className), "data-block": block ? 'block' : null, "data-inactive": inactive ? true : undefined, "data-loading": Boolean(loading), "data-no-visuals": !LeadingVisual && !TrailingVisual && !TrailingAction ? true : undefined, "data-size": size, "data-variant": variant, "data-label-wrap": labelWrap, "data-has-count": count !== undefined ? true : undefined, "aria-describedby": [loadingAnnouncementID, ariaDescribedBy].filter(descriptionID => Boolean(descriptionID)).join(' ') // aria-labelledby is needed because the accessible name becomes unset when the button is in a loading state. // We only set it when the button is in a loading state because it will supercede the aria-label when the screen // reader announces the button name. , "aria-labelledby": loading ? [`${uuid}-label`, ariaLabelledBy].filter(labelID => Boolean(labelID)).join(' ') : ariaLabelledBy, id: id, onClick: loading ? undefined : onClick, children: Icon ? loading ? /*#__PURE__*/jsxRuntime.jsx(Spinner, { size: "small" }) : reactIs.isElement(Icon) ? Icon : /*#__PURE__*/jsxRuntime.jsx(Icon, {}) : /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [/*#__PURE__*/jsxRuntime.jsxs(Box, { as: "span", "data-component": "buttonContent", sx: styles.getAlignContentSize(alignContent), className: ButtonBase_module.ButtonContent, children: [ /* If there are no leading/trailing visuals/actions to replace with a loading spinner, render a loading spiner in place of the button content. */ loading && !LeadingVisual && !TrailingVisual && !TrailingAction && count === undefined && renderModuleVisual(Spinner, loading, 'loadingSpinner', false), /* Render a leading visual unless the button is in a loading state. Then replace the leading visual with a loading spinner. */ LeadingVisual && renderModuleVisual(LeadingVisual, Boolean(loading), 'leadingVisual', false), children && /*#__PURE__*/jsxRuntime.jsx("span", { "data-component": "text", className: ButtonBase_module.Label, id: loading ? `${uuid}-label` : undefined, children: children }), /* If there is a count, render a counter label unless there is a trailing visual. Then render the counter label as a trailing visual. Replace the counter label or the trailing visual with a loading spinner if: - the button is in a loading state - there is no leading visual to replace with a loading spinner */ count !== undefined && !TrailingVisual ? renderModuleVisual(() => /*#__PURE__*/jsxRuntime.jsx(CounterLabel, { className: ButtonBase_module.CounterLabel, "data-component": "ButtonCounter", children: count }), Boolean(loading) && !LeadingVisual, 'trailingVisual', true) : TrailingVisual ? renderModuleVisual(TrailingVisual, Boolean(loading) && !LeadingVisual, 'trailingVisual', false) : null] }), /* If there is a trailing action, render it unless the button is in a loading state and there is no leading or trailing visual to replace with a loading spinner. */ TrailingAction && renderModuleVisual(TrailingAction, Boolean(loading) && !LeadingVisual && !TrailingVisual, 'trailingAction', false)] }) }), loading && /*#__PURE__*/jsxRuntime.jsx(VisuallyHidden.VisuallyHidden, { children: /*#__PURE__*/jsxRuntime.jsx(AriaStatus.AriaStatus, { id: loadingAnnouncementID, children: loadingAnnouncement }) })] }); } return /*#__PURE__*/jsxRuntime.jsxs(ConditionalWrapper.ConditionalWrapper // If anything is passed to `loading`, we need the wrapper: // If we just checked for `loading` as a boolean, the wrapper wouldn't be rendered // when `loading` is `false`. // Then, the component re-renders in a way that the button will lose focus when switching between loading states. , { if: typeof loading !== 'undefined', className: block ? ButtonBase_module.ConditionalWrapper : undefined, "data-loading-wrapper": true, children: [/*#__PURE__*/jsxRuntime.jsx(Component, { "aria-disabled": loading ? true : undefined, ...rest, // @ts-ignore temporary disable as we migrate to css modules, until we remove PolymorphicForwardRefComponent ref: innerRef, className: clsx.clsx(ButtonBase_module.ButtonBase, className), "data-block": block ? 'block' : null, "data-inactive": inactive ? true : undefined, "data-loading": Boolean(loading), "data-no-visuals": !LeadingVisual && !TrailingVisual && !TrailingAction ? true : undefined, "data-size": size, "data-variant": variant, "data-label-wrap": labelWrap, "data-has-count": count !== undefined ? true : undefined, "aria-describedby": [loadingAnnouncementID, ariaDescribedBy].filter(descriptionID => Boolean(descriptionID)).join(' ') // aria-labelledby is needed because the accessible name becomes unset when the button is in a loading state. // We only set it when the button is in a loading state because it will supercede the aria-label when the screen // reader announces the button name. , "aria-labelledby": loading ? [`${uuid}-label`, ariaLabelledBy].filter(labelID => Boolean(labelID)).join(' ') : ariaLabelledBy, id: id // @ts-ignore temporary disable as we migrate to css modules, until we remove PolymorphicForwardRefComponent , onClick: loading ? undefined : onClick, children: Icon ? loading ? /*#__PURE__*/jsxRuntime.jsx(Spinner, { size: "small" }) : reactIs.isElement(Icon) ? Icon : /*#__PURE__*/jsxRuntime.jsx(Icon, {}) : /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [/*#__PURE__*/jsxRuntime.jsxs("span", { "data-component": "buttonContent", "data-align": alignContent, className: ButtonBase_module.ButtonContent, children: [ /* If there are no leading/trailing visuals/actions to replace with a loading spinner, render a loading spiner in place of the button content. */ loading && !LeadingVisual && !TrailingVisual && !TrailingAction && count === undefined && renderModuleVisual(Spinner, loading, 'loadingSpinner', false), /* Render a leading visual unless the button is in a loading state. Then replace the leading visual with a loading spinner. */ LeadingVisual && renderModuleVisual(LeadingVisual, Boolean(loading), 'leadingVisual', false), children && /*#__PURE__*/jsxRuntime.jsx("span", { "data-component": "text", className: ButtonBase_module.Label, id: loading ? `${uuid}-label` : undefined, children: children }), /* If there is a count, render a counter label unless there is a trailing visual. Then render the counter label as a trailing visual. Replace the counter label or the trailing visual with a loading spinner if: - the button is in a loading state - there is no leading visual to replace with a loading spinner */ count !== undefined && !TrailingVisual ? renderModuleVisual(() => /*#__PURE__*/jsxRuntime.jsx(CounterLabel, { className: ButtonBase_module.CounterLabel, "data-component": "ButtonCounter", children: count }), Boolean(loading) && !LeadingVisual, 'trailingVisual', true) : TrailingVisual ? renderModuleVisual(TrailingVisual, Boolean(loading) && !LeadingVisual, 'trailingVisual', false) : null] }), /* If there is a trailing action, render it unless the button is in a loading state and there is no leading or trailing visual to replace with a loading spinner. */ TrailingAction && renderModuleVisual(TrailingAction, Boolean(loading) && !LeadingVisual && !TrailingVisual, 'trailingAction', false)] }) }), loading && /*#__PURE__*/jsxRuntime.jsx(VisuallyHidden.VisuallyHidden, { children: /*#__PURE__*/jsxRuntime.jsx(AriaStatus.AriaStatus, { id: loadingAnnouncementID, children: loadingAnnouncement }) })] }); }); exports.ButtonBase = ButtonBase;