(message).type.displayName === 'Text'
@@ -267,21 +298,24 @@ export default function BannerCallout({
{(primaryAction || secondaryAction) && (
{secondaryAction && responsiveMinWidth !== 'xs' && (
-
+
+ )}
+ {primaryAction && (
+
)}
- {primaryAction && }
{secondaryAction && responsiveMinWidth === 'xs' && (
)}
)}
{dismissButton && (
-
+
void;
+ };
+};
+
+export default function DismissButton({ dismissButton, size = 'lg' }: Props) {
+ const { accessibilityDismissButtonLabel } = useDefaultLabelContext('BannerCallout');
+
+ return (
+
+
+
+ );
+}
diff --git a/packages/gestalt/src/BannerCallout/Footer.tsx b/packages/gestalt/src/BannerCallout/Footer.tsx
new file mode 100644
index 0000000000..c1470dd868
--- /dev/null
+++ b/packages/gestalt/src/BannerCallout/Footer.tsx
@@ -0,0 +1,169 @@
+import { ComponentProps, useCallback, useEffect, useRef, useState } from 'react';
+import Box from '../Box';
+import Button from '../Button';
+import ButtonLink from '../ButtonLink';
+import Flex from '../Flex';
+
+type ActionDataType =
+ | {
+ accessibilityLabel: string;
+ disabled?: boolean;
+ href: string;
+ label: string;
+ onClick?: ComponentProps['onClick'];
+ rel?: 'none' | 'nofollow';
+ role: 'link';
+ target?: null | 'self' | 'blank';
+ }
+ | {
+ accessibilityLabel: string;
+ disabled?: boolean;
+ label: string;
+ onClick?: ComponentProps['onClick'];
+ role?: 'button';
+ };
+
+function Action({
+ data,
+ level,
+ type,
+ size = 'lg',
+}: {
+ data: ActionDataType;
+ level: string;
+ size?: 'md' | 'lg';
+ type: 'default' | 'error' | 'info' | 'recommendation' | 'success' | 'warning';
+}) {
+ const primaryColor: ComponentProps['color'] = 'red';
+
+ let secondaryColor: 'white' | 'transparent' | 'gray' = 'white';
+
+ if (type === 'default') {
+ secondaryColor = 'gray';
+ }
+
+ const color: ComponentProps['color'] =
+ level === 'primary' ? primaryColor : secondaryColor;
+
+ const { accessibilityLabel, disabled, label } = data;
+
+ return data.role === 'link' ? (
+
+ ) : (
+
+ );
+}
+
+type Props = {
+ primaryAction?:
+ | {
+ role: 'link';
+ accessibilityLabel: string;
+ disabled?: boolean;
+ href: string;
+ label: string;
+ onClick?: ComponentProps['onClick'];
+ rel?: 'none' | 'nofollow';
+ target?: null | 'self' | 'blank';
+ }
+ | {
+ role?: 'button';
+ accessibilityLabel: string;
+ disabled?: boolean;
+ label: string;
+ onClick?: ComponentProps['onClick'];
+ };
+ secondaryAction?:
+ | {
+ role: 'link';
+ accessibilityLabel: string;
+ disabled?: boolean;
+ href: string;
+ label: string;
+ onClick?: ComponentProps['onClick'];
+ rel?: 'none' | 'nofollow';
+ target?: null | 'self' | 'blank';
+ }
+ | {
+ role?: 'button';
+ accessibilityLabel: string;
+ disabled?: boolean;
+ label: string;
+ onClick?: ComponentProps['onClick'];
+ };
+ type: 'default' | 'error' | 'info' | 'recommendation' | 'success' | 'warning';
+ checkWrapped?: boolean;
+ marginTop: 0 | 4 | 6;
+ buttonSize: 'md' | 'lg';
+};
+
+export default function Footer({
+ secondaryAction,
+ primaryAction,
+ type,
+ checkWrapped = false,
+ marginTop,
+ buttonSize,
+}: Props) {
+ const [isWrapped, setIsWrapped] = useState(false);
+ const wrappedRef = useRef(null);
+
+ const checkWrappedButton = useCallback(() => {
+ if (wrappedRef.current && !isWrapped && wrappedRef.current.offsetTop > 0) {
+ setIsWrapped(true);
+ } else if (wrappedRef.current && isWrapped && !(wrappedRef.current.offsetTop > 0)) {
+ setIsWrapped(false);
+ }
+ }, [isWrapped]);
+
+ useEffect(() => {
+ if (checkWrapped) {
+ checkWrappedButton();
+
+ if (typeof window !== 'undefined') window.addEventListener('resize', checkWrappedButton);
+ }
+
+ return () => {
+ if (checkWrapped && typeof window !== 'undefined')
+ window?.removeEventListener('resize', checkWrappedButton);
+ };
+ }, [checkWrappedButton, checkWrapped]);
+
+ return (
+
+
+ {secondaryAction && (
+
+
+
+ )}
+ {primaryAction && (
+
+
+
+
+
+ )}
+
+
+ );
+}
diff --git a/packages/gestalt/src/BannerCallout/HeaderSection.tsx b/packages/gestalt/src/BannerCallout/HeaderSection.tsx
new file mode 100644
index 0000000000..cb4c5bf27c
--- /dev/null
+++ b/packages/gestalt/src/BannerCallout/HeaderSection.tsx
@@ -0,0 +1,81 @@
+import { Children, ReactElement } from 'react';
+import Box from '../Box';
+import { useDefaultLabelContext } from '../contexts/DefaultLabelProvider';
+import Flex from '../Flex';
+import Heading from '../Heading';
+import Icon from '../Icon';
+import MESSAGING_TYPE_ATTRIBUTES from '../MESSAGING_TYPE_ATTRIBUTES';
+import Text from '../Text';
+
+type Props = {
+ gap: 3 | 6;
+ iconSize: 32 | 24;
+ message: string | ReactElement;
+ type: 'default' | 'error' | 'info' | 'recommendation' | 'success' | 'warning';
+ title?: string;
+ marginBottom?: 4;
+ iconAccessibilityLabel?: string;
+};
+
+export default function HeaderSection({
+ iconSize,
+ gap,
+ title,
+ message,
+ type,
+ iconAccessibilityLabel,
+ marginBottom,
+}: Props) {
+ const {
+ iconAccessibilityLabelError,
+ iconAccessibilityLabelInfo,
+ iconAccessibilityLabelRecommendation,
+ iconAccessibilityLabelSuccess,
+ iconAccessibilityLabelWarning,
+ } = useDefaultLabelContext('BannerCallout');
+
+ const getDefaultIconAccessibilityLabel = () => {
+ switch (type) {
+ case 'success':
+ return iconAccessibilityLabelSuccess;
+ case 'info':
+ return iconAccessibilityLabelInfo;
+ case 'recommendation':
+ return iconAccessibilityLabelRecommendation;
+ case 'warning':
+ return iconAccessibilityLabelWarning;
+ case 'error':
+ return iconAccessibilityLabelError;
+ default:
+ return '';
+ }
+ };
+
+ return (
+
+
+
+
+
+ {(title || message) && (
+
+ {title && {title}}
+ {message && typeof message === 'string' && {message}}
+ {message &&
+ typeof message !== 'string' &&
+ // @ts-expect-error - TS2339
+ Children.only(message).type.displayName === 'Text'
+ ? message
+ : null}
+
+ )}
+
+ {' '}
+
+ );
+}
diff --git a/packages/gestalt/src/BannerCallout/VRBannerCallout.tsx b/packages/gestalt/src/BannerCallout/VRBannerCallout.tsx
new file mode 100644
index 0000000000..c7d2101dec
--- /dev/null
+++ b/packages/gestalt/src/BannerCallout/VRBannerCallout.tsx
@@ -0,0 +1,208 @@
+import { ComponentProps, ReactElement } from 'react';
+import {
+ SEMA_SPACE_800,
+ SEMA_SPACE_1200,
+} from 'gestalt-design-tokens/dist/js/vr-theme/constants.es';
+import DismissButton from './DismissButton';
+import Footer from './Footer';
+import HeaderSection from './HeaderSection';
+import Box from '../Box';
+import Button from '../Button';
+import ButtonLink from '../ButtonLink';
+import Flex from '../Flex';
+import MESSAGING_TYPE_ATTRIBUTES from '../MESSAGING_TYPE_ATTRIBUTES';
+
+type Props = {
+ dismissButton?: {
+ accessibilityLabel?: string;
+ onDismiss: () => void;
+ };
+ iconAccessibilityLabel?: string;
+ message: string | ReactElement;
+ primaryAction?:
+ | {
+ role: 'link';
+ accessibilityLabel: string;
+ disabled?: boolean;
+ href: string;
+ label: string;
+ onClick?: ComponentProps['onClick'];
+ rel?: 'none' | 'nofollow';
+ target?: null | 'self' | 'blank';
+ }
+ | {
+ role?: 'button';
+ accessibilityLabel: string;
+ disabled?: boolean;
+ label: string;
+ onClick?: ComponentProps['onClick'];
+ };
+ secondaryAction?:
+ | {
+ role: 'link';
+ accessibilityLabel: string;
+ disabled?: boolean;
+ href: string;
+ label: string;
+ onClick?: ComponentProps['onClick'];
+ rel?: 'none' | 'nofollow';
+ target?: null | 'self' | 'blank';
+ }
+ | {
+ role?: 'button';
+ accessibilityLabel: string;
+ disabled?: boolean;
+ label: string;
+ onClick?: ComponentProps['onClick'];
+ };
+ type: 'default' | 'error' | 'info' | 'recommendation' | 'success' | 'warning';
+ title?: string;
+};
+
+export default function BannerCallout({
+ dismissButton,
+ iconAccessibilityLabel,
+ message,
+ primaryAction,
+ secondaryAction,
+ type,
+ title,
+}: Props) {
+ const isRtl = typeof document === 'undefined' ? false : document?.dir === 'rtl';
+
+ const largePadding = isRtl
+ ? { paddingRight: SEMA_SPACE_800, paddingLeft: SEMA_SPACE_1200 }
+ : { paddingRight: SEMA_SPACE_1200, paddingLeft: SEMA_SPACE_800 };
+
+ const backgroundColor = MESSAGING_TYPE_ATTRIBUTES[type]?.backgroundColor;
+
+ return (
+
+ {/*
+ SM BREAKPOINT
+ */}
+
+
+
+
+
+
+ {(primaryAction || secondaryAction) && (
+
+ )}
+
+ {dismissButton && }
+
+
+ {/*
+ MD BREAKPOINT
+ */}
+
+
+
+
+
+
+ {(primaryAction || secondaryAction) && (
+
+
+
+ )}
+
+ {dismissButton && }
+
+
+ {/*
+ LG BREAKPOINT
+ */}
+
+
+
+
+
+
+ {(primaryAction || secondaryAction) && (
+
+
+
+ )}
+
+ {dismissButton && }
+
+
+
+ );
+}
diff --git a/packages/gestalt/src/BannerSlim.tsx b/packages/gestalt/src/BannerSlim.tsx
index 43cf02903d..0b61872ed5 100644
--- a/packages/gestalt/src/BannerSlim.tsx
+++ b/packages/gestalt/src/BannerSlim.tsx
@@ -144,8 +144,21 @@ export default function BannerSlim({
}: Props) {
const isBare = type.endsWith('Bare');
const isDefault = type === 'neutral';
- // @ts-expect-error - TS7053 - Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Readonly<{ neutral: { backgroundColor: string; }; success: { icon: string; iconColor: string; color: string; backgroundColor: string; }; info: { icon: string; iconColor: string; color: string; backgroundColor: string; }; warning: { ...; }; error: { ...; }; recommendation: { ...; }; }>'.
- const { backgroundColor, iconColor, icon } = MESSAGING_TYPE_ATTRIBUTES[type.replace('Bare', '')];
+
+ const status = Object.freeze({
+ 'neutral': 'neutral',
+ 'success': 'success',
+ 'successBare': 'success',
+ 'info': 'info',
+ 'infoBare': 'info',
+ 'recommendation': 'recommendation',
+ 'recommendationBare': 'recommendation',
+ 'warning': 'warning',
+ 'warningBare': 'warning',
+ 'error': 'error',
+ 'errorBare': 'error',
+ });
+
const { accessibilityDismissButtonLabel } = useDefaultLabelContext('BannerSlim');
const {
iconAccessibilityLabelError,
@@ -183,7 +196,7 @@ export default function BannerSlim({
return (
diff --git a/packages/gestalt/src/ButtonLink.tsx b/packages/gestalt/src/ButtonLink.tsx
index 6086740f50..d2660045b4 100644
--- a/packages/gestalt/src/ButtonLink.tsx
+++ b/packages/gestalt/src/ButtonLink.tsx
@@ -1,6 +1,7 @@
-import { forwardRef, useImperativeHandle, useRef } from 'react';
+import { ComponentProps, forwardRef, useImperativeHandle, useRef } from 'react';
import getAriaLabel from './accessibility/getAriaLabel';
import NewTabAccessibilityLabel from './accessibility/NewTabAccessibilityLabel';
+import Button from './Button';
import { useColorScheme } from './contexts/ColorSchemeProvider';
import { useDefaultLabelContext } from './contexts/DefaultLabelProvider';
import Flex from './Flex';
@@ -35,14 +36,7 @@ type ButtonProps = {
/**
* The background color of ButtonLink.
*/
- color?:
- | 'gray'
- | 'red'
- | 'blue'
- | 'transparent'
- | 'semiTransparentWhite'
- | 'transparentWhiteText'
- | 'white';
+ color?: ComponentProps['color'];
/**
* Available for testing purposes, if needed. Consider [better queries](https://testing-library.com/docs/queries/about/#priority) before using this prop.
*/
diff --git a/packages/gestalt/src/MESSAGING_TYPE_ATTRIBUTES.ts b/packages/gestalt/src/MESSAGING_TYPE_ATTRIBUTES.ts
index ea4c3febf1..c4698f4817 100644
--- a/packages/gestalt/src/MESSAGING_TYPE_ATTRIBUTES.ts
+++ b/packages/gestalt/src/MESSAGING_TYPE_ATTRIBUTES.ts
@@ -1,7 +1,24 @@
-export default Object.freeze({
+import { ComponentProps } from 'react';
+import Box from './Box';
+import Icon from './Icon';
+
+const MESSAGING_ATTRIBUTES: {
+ [status: string]: {
+ icon?: ComponentProps['icon'];
+ iconColor?: ComponentProps['color'];
+ color?: string;
+ backgroundColor?: ComponentProps['color'];
+ };
+} = Object.freeze({
neutral: {
backgroundColor: 'secondary',
},
+ default: {
+ icon: 'pinterest',
+ iconColor: 'default',
+ color: 'white',
+ backgroundColor: 'default',
+ },
success: {
icon: 'check-circle',
iconColor: 'success',
@@ -33,3 +50,5 @@ export default Object.freeze({
backgroundColor: 'recommendationWeak',
},
});
+
+export default MESSAGING_ATTRIBUTES;
diff --git a/packages/gestalt/src/__snapshots__/BannerCallout.test.tsx.snap b/packages/gestalt/src/__snapshots__/BannerCallout.test.tsx.snap
index d0aedd77ad..2bef8ee3ce 100644
--- a/packages/gestalt/src/__snapshots__/BannerCallout.test.tsx.snap
+++ b/packages/gestalt/src/__snapshots__/BannerCallout.test.tsx.snap
@@ -297,7 +297,7 @@ exports[` bannercallout with rich text message 1`] = `
message + title + primaryAction + dismissButton 1`] =