UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

197 lines (187 loc) • 10.1 kB
import clsx from 'clsx'; import React, { useEffect } from 'react'; import styled from 'styled-components'; import { XIcon, StopIcon, InfoIcon, CheckCircleIcon, AlertIcon } from '@primer/octicons-react'; import { IconButton } from '../Button/IconButton.js'; import '../Button/ButtonBase.js'; import '../utils/defaultSxProp.js'; import { ButtonComponent } from '../Button/Button.js'; import { get } from '../constants.js'; import { VisuallyHidden } from '../internal/components/VisuallyHidden.js'; import { useMergedRefs } from '../internal/hooks/useMergedRefs.js'; function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } const iconForVariant = { critical: /*#__PURE__*/React.createElement(StopIcon, null), info: /*#__PURE__*/React.createElement(InfoIcon, null), success: /*#__PURE__*/React.createElement(CheckCircleIcon, null), upsell: /*#__PURE__*/React.createElement(InfoIcon, null), warning: /*#__PURE__*/React.createElement(AlertIcon, null) }; const labels = { critical: 'Critical', info: 'Information', success: 'Success', upsell: 'Recommendation', warning: 'Warning' }; const Banner = /*#__PURE__*/React.forwardRef(function Banner({ 'aria-label': label, children, description, hideTitle, icon, onDismiss, primaryAction, secondaryAction, title, variant = 'info', ...rest }, forwardRef) { const dismissible = variant !== 'critical' && onDismiss; const hasActions = primaryAction || secondaryAction; const bannerRef = React.useRef(null); const ref = useMergedRefs(forwardRef, bannerRef); if (process.env.NODE_ENV !== "production") { // This hook is called consistently depending on the environment // eslint-disable-next-line react-hooks/rules-of-hooks useEffect(() => { if (title) { return; } const { current: banner } = bannerRef; if (!banner) { return; } const hasTitle = banner.querySelector('[data-banner-title]'); if (!hasTitle) { throw new Error('Expected a title to be provided to the <Banner> component with the `title` prop or through `<Banner.Title>` but no title was found'); } }, [title]); } return /*#__PURE__*/React.createElement(StyledBanner, _extends({}, rest, { "aria-label": label !== null && label !== void 0 ? label : labels[variant], as: "section", "data-dismissible": onDismiss ? '' : undefined, "data-title-hidden": hideTitle ? '' : undefined, "data-variant": variant, tabIndex: -1, ref: ref }), /*#__PURE__*/React.createElement("style", null, BannerContainerQuery), /*#__PURE__*/React.createElement("div", { className: "BannerIcon" }, icon && variant === 'info' ? icon : iconForVariant[variant]), /*#__PURE__*/React.createElement("div", { className: "BannerContainer" }, /*#__PURE__*/React.createElement("div", { className: "BannerContent" }, title ? hideTitle ? /*#__PURE__*/React.createElement(VisuallyHidden, null, /*#__PURE__*/React.createElement(BannerTitle, null, title)) : /*#__PURE__*/React.createElement(BannerTitle, null, title) : null, description ? /*#__PURE__*/React.createElement(BannerDescription, null, description) : null, children), hasActions ? /*#__PURE__*/React.createElement(BannerActions, { primaryAction: primaryAction, secondaryAction: secondaryAction }) : null), dismissible ? /*#__PURE__*/React.createElement(IconButton, { "aria-label": "Dismiss banner", onClick: onDismiss, className: "BannerDismiss", icon: XIcon, variant: "invisible" }) : null); }); /** * For styling, it's important that the icons and the text have the same height * for alignment to occur in multi-line scenarios. Currently, we use a * line-height of `20px` so that means that the height of icons should match * that value. */ const StyledBanner = styled.div.withConfig({ displayName: "Banner__StyledBanner", componentId: "sc-1n1s78v-0" })(["display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:start;background-color:var(--banner-bgColor);border:var(--borderWidth-thin,1px) solid var(--banner-borderColor);padding:var(--base-size-8,0.5rem);border-radius:var(--borderRadius-medium,", ");@supports (container-type:inline-size){container:banner / inline-size;}&[data-variant='critical']{--banner-bgColor:", ";--banner-borderColor:", ";--banner-icon-fgColor:", ";}&[data-variant='info']{--banner-bgColor:", ";--banner-borderColor:", ";--banner-icon-fgColor:", ";}&[data-variant='success']{--banner-bgColor:", ";--banner-borderColor:", ";--banner-icon-fgColor:", ";}&[data-variant='upsell']{--banner-bgColor:var(--bgColor-upsell-muted,", ");--banner-borderColor:var(--borderColor-upsell-muted,", ");--banner-icon-fgColor:var(--fgColor-upsell-muted,", ");}&[data-variant='warning']{--banner-bgColor:", ";--banner-borderColor:", ";--banner-icon-fgColor:", ";}.BannerIcon{display:grid;place-items:center;padding:var(--base-size-8,0.5rem);}.BannerIcon svg{color:var(--banner-icon-fgColor);fill:var(--banner-icon-fgColor);height:var(--base-size-20,1.25rem);}&[data-title-hidden=''] .BannerIcon svg{height:var(--base-size-16,1rem);}.BannerContainer{font-size:var(--text-body-size-medium,0.875rem);align-items:start;line-height:var(--text-body-lineHeight-medium,calc(20 / 14));row-gap:var(--base-size-4,0.25rem);column-gap:var(--base-size-4,0.25rem);}& :where(.BannerContainer){display:flex;flex-wrap:wrap;justify-content:space-between;}&[data-dismissible] .BannerContainer{display:grid;grid-template-columns:auto;grid-template-rows:auto;}.BannerContent{display:grid;row-gap:var(--base-size-4,0.25rem);grid-column-start:1;margin-block:var(--base-size-8,0.5rem);}&[data-title-hidden=''] .BannerContent{margin-block:var(--base-size-6,0.375rem);}@media screen and (min-width:544px){.BannerContent{flex:1 1 0%;}}.BannerTitle{margin:0;font-size:inherit;font-weight:var(--base-text-weight-semibold,600);}.BannerActionsContainer{display:flex;column-gap:var(--base-size-8,0.5rem);align-items:center;}.BannerActions :where([data-primary-action='trailing']){display:none;}@media screen and (min-width:544px){.BannerActions :where([data-primary-action='trailing']){display:flex;}.BannerActions :where([data-primary-action='leading']){display:none;}}&[data-dismissible] .BannerActions{margin-block-end:var(--size-small,0.375rem);}&[data-dismissible] .BannerActionsContainer[data-primary-action='trailing']{display:none;}&[data-dismissible] .BannerActionsContainer[data-primary-action='leading']{display:flex;}.BannerDismiss{display:grid;place-items:center;padding:var(--base-size-8,0.5rem);margin-inline-start:var(--base-size-4,0.25rem);}.BannerDismiss svg{color:var(--banner-icon-fgColor);}"], get('radii.2'), get('colors.danger.subtle'), get('colors.danger.muted'), get('colors.danger.fg'), get('colors.accent.subtle'), get('colors.accent.muted'), get('colors.accent.fg'), get('colors.success.subtle'), get('colors.success.muted'), get('colors.success.fg'), get('colors.done.subtle'), get('colors.done.muted'), get('colors.done.fg'), get('colors.attention.subtle'), get('colors.attention.muted'), get('colors.attention.fg')); const BannerContainerQuery = ` @container banner (max-width: 500px) { .BannerContainer { display: grid; grid-template-rows: auto auto; } .BannerActions { margin-block-end: var(--size-small, 0.375rem); } .BannerActions [data-primary-action="trailing"] { display: none; } .BannerActions [data-primary-action="leading"] { display: flex; } } @container banner (min-width: 500px) { .BannerContainer { display: grid; grid-template-columns: auto auto; } .BannerActions [data-primary-action="trailing"] { display: flex; } .BannerActions [data-primary-action="leading"] { display: none; } } `; function BannerTitle(props) { const { as: Heading = 'h2', className, children, ...rest } = props; return /*#__PURE__*/React.createElement(Heading, _extends({}, rest, { className: clsx('BannerTitle', className), "data-banner-title": "" }), children); } BannerTitle.displayName = "BannerTitle"; function BannerDescription({ children, className, ...rest }) { return /*#__PURE__*/React.createElement("div", _extends({}, rest, { className: clsx('BannerDescription', className) }), children); } BannerDescription.displayName = "BannerDescription"; function BannerActions({ primaryAction, secondaryAction }) { return /*#__PURE__*/React.createElement("div", { className: "BannerActions" }, /*#__PURE__*/React.createElement("div", { className: "BannerActionsContainer", "data-primary-action": "trailing" }, secondaryAction !== null && secondaryAction !== void 0 ? secondaryAction : null, primaryAction !== null && primaryAction !== void 0 ? primaryAction : null), /*#__PURE__*/React.createElement("div", { className: "BannerActionsContainer", "data-primary-action": "leading" }, primaryAction !== null && primaryAction !== void 0 ? primaryAction : null, secondaryAction !== null && secondaryAction !== void 0 ? secondaryAction : null)); } BannerActions.displayName = "BannerActions"; function BannerPrimaryAction({ children, className, ...rest }) { return /*#__PURE__*/React.createElement(ButtonComponent, _extends({ className: clsx('BannerPrimaryAction', className), variant: "default" }, rest), children); } BannerPrimaryAction.displayName = "BannerPrimaryAction"; function BannerSecondaryAction({ children, className, ...rest }) { return /*#__PURE__*/React.createElement(ButtonComponent, _extends({ className: clsx('BannerPrimaryAction', className), variant: "invisible" }, rest), children); } BannerSecondaryAction.displayName = "BannerSecondaryAction"; export { Banner, BannerActions, BannerDescription, BannerPrimaryAction, BannerSecondaryAction, BannerTitle };