@primer/react
Version:
An implementation of GitHub's Primer Design System using React
233 lines (227 loc) • 12.6 kB
JavaScript
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;
;