diff --git a/src/blocks/alert/Alert.tsx b/src/blocks/alert/Alert.tsx new file mode 100644 index 0000000000..bc51cfef1e --- /dev/null +++ b/src/blocks/alert/Alert.tsx @@ -0,0 +1,141 @@ +import type { FC } from 'react'; +import styled, { FlattenSimpleInterpolation } from 'styled-components'; + +import type { TransformedHTMLAttributes } from '../Blocks.types'; +import type { AlertVariant } from './Alert.types'; +import { alertVariants } from './Alert.utils'; +import { Cross } from 'blocks/icons'; +import { HoverableSVG } from 'blocks/hoverableSVG'; +import { getTextVariantStyles } from 'blocks/Blocks.utils'; + +export type AlertProps = { + /* Additional prop from styled components to apply custom css to Alert */ + css?: FlattenSimpleInterpolation; + /* Sets the variant of the alert */ + variant: AlertVariant; + /* Close function to be called on close button click */ + onClose?: () => void; + /* Retry function to be called on action button click */ + onAction?: () => void; + /* Text to be displayed on the action button */ + actionText?: string; + /* Boolean to set the visibility of the icon */ + showIcon?: boolean; + /* Header text for the alert */ + heading?: string; + /* Description text for the alert */ + description?: string; +} & TransformedHTMLAttributes; + +const StyledAlert = styled.div` + /* Common Alert CSS */ + + display: flex; + font-family: var(--font-family); + border-radius: var(--radius-sm); + justify-content: center; + white-space: nowrap; + padding: var(--spacing-xs); + justify-content: space-between; + ${({ variant }) => ` + border: var(--border-sm) solid var(--${alertVariants[variant].borderColor}); + background-color: var(--${alertVariants[variant].bgColor}); + `} + + /* Common icon css added through CSS class */ + .icon { + display: flex; + justify-content: center; + margin-right: var(--spacing-xxxs); + color: var(--${({ variant }) => alertVariants[variant].iconColor}); + } + + /* Custom CSS applied via styled component css prop */ + ${(props) => props.css || ''} +`; + +const StyledLink = styled.div<{ variant: AlertVariant }>` + /* Link CSS */ + text-decoration: none; + cursor: pointer; + color: var(--${({ variant }) => alertVariants[variant].ctaColor}); +`; + +const TextContainer = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + gap: var(--spacing-xxxs); + flex: 1 0 0; +`; + +const RightContainer = styled.div` + display: flex; + gap: var(--spacing-xs, 12px); + align-items: center; + height: 24px; +`; + +const Heading = styled.p` + ${() => getTextVariantStyles('h5-semibold', 'components-alert-text-default')} +`; + +const Description = styled.p` + ${() => getTextVariantStyles('bs-regular', 'components-alert-text-body')} +`; + +const Alert: FC = ({ + description, + heading, + onClose, + onAction, + actionText = 'Try Again', + showIcon = true, + variant = 'info', + ...props +}) => { + const { icon: Icon } = alertVariants[variant]; + + return ( + + {showIcon && ( + + + + )} + + {heading && {heading}} + {description && {description}} + + + {onAction && ( + + {actionText} + + )} + {onClose && ( + + } + onClick={onClose} + /> + )} + + + ); +}; + +Alert.displayName = 'Alert'; + +export { Alert }; diff --git a/src/blocks/alert/Alert.types.ts b/src/blocks/alert/Alert.types.ts new file mode 100644 index 0000000000..2e0a42f4bf --- /dev/null +++ b/src/blocks/alert/Alert.types.ts @@ -0,0 +1 @@ +export type AlertVariant = 'success' | 'warning' | 'error' | 'info'; diff --git a/src/blocks/alert/Alert.utils.ts b/src/blocks/alert/Alert.utils.ts new file mode 100644 index 0000000000..be53753be6 --- /dev/null +++ b/src/blocks/alert/Alert.utils.ts @@ -0,0 +1,38 @@ +import { AlertVariant } from './Alert.types'; +import { IconProps, InfoFilled, TickCircleFilled, WarningCircleFilled } from '../icons'; +import { ThemeColors } from 'blocks/theme/Theme.types'; +import { FC } from 'react'; + +export const alertVariants: Record< + AlertVariant, + { icon: FC; iconColor: ThemeColors; borderColor: ThemeColors; bgColor: ThemeColors; ctaColor: ThemeColors } +> = { + success: { + icon: TickCircleFilled, + iconColor: 'components-alert-icon-success', + borderColor: 'components-alert-stroke-success', + bgColor: 'components-alert-background-success', + ctaColor: 'components-alert-text-cta-success', + }, + warning: { + icon: WarningCircleFilled, + iconColor: 'components-alert-icon-warning', + borderColor: 'components-alert-stroke-warning', + bgColor: 'components-alert-background-warning', + ctaColor: 'components-alert-text-cta-warning', + }, + info: { + icon: InfoFilled, + iconColor: 'components-alert-icon-info', + borderColor: 'components-alert-stroke-info', + bgColor: 'components-alert-background-info', + ctaColor: 'components-alert-text-cta-info', + }, + error: { + icon: WarningCircleFilled, + iconColor: 'components-alert-icon-error', + borderColor: 'components-alert-stroke-error', + bgColor: 'components-alert-background-error', + ctaColor: 'components-alert-text-cta-error', + }, +}; diff --git a/src/blocks/alert/index.ts b/src/blocks/alert/index.ts new file mode 100644 index 0000000000..cfb6dd88bb --- /dev/null +++ b/src/blocks/alert/index.ts @@ -0,0 +1,3 @@ +export * from './Alert'; +export * from './Alert.utils'; +export * from './Alert.types'; diff --git a/src/blocks/index.ts b/src/blocks/index.ts index 31b014d08c..b1f45024a0 100644 --- a/src/blocks/index.ts +++ b/src/blocks/index.ts @@ -1,3 +1,4 @@ +export { Alert, type AlertProps } from './alert'; export { Box, type BoxProps } from './box'; export { Button, type ButtonProps } from './button'; export { Dropdown, type DropdownProps } from './dropdown'; diff --git a/src/blocks/theme/colors/colors.semantics.ts b/src/blocks/theme/colors/colors.semantics.ts index 09d3f2c7f8..9c047713a9 100644 --- a/src/blocks/theme/colors/colors.semantics.ts +++ b/src/blocks/theme/colors/colors.semantics.ts @@ -1,3 +1,4 @@ +import { alertSemantics } from '../semantics/semantics.alert'; import { secondaryButtonSemantics, dangerButtonSemantics, @@ -23,6 +24,7 @@ import { tooltipSemantics } from '../semantics/semantics.tooltip'; // TODO: find a better way to do this in future type SemanticKeys = { + alert: 'components-alert'; buttonPrimary: 'components-button-primary'; buttonSecondary: 'components-button-secondary'; buttonTertiary: 'components-button-tertiary'; @@ -46,6 +48,7 @@ type SemanticKeys = { }; export const semanticKeys: SemanticKeys = { + alert: 'components-alert', buttonPrimary: 'components-button-primary', buttonSecondary: 'components-button-secondary', buttonTertiary: 'components-button-tertiary', @@ -69,6 +72,7 @@ export const semanticKeys: SemanticKeys = { }; export const colorSemantics = { + [semanticKeys.alert]: alertSemantics, [semanticKeys.buttonPrimary]: primaryButtonSemantics, [semanticKeys.buttonSecondary]: secondaryButtonSemantics, [semanticKeys.buttonTertiary]: tertiaryButtonSemantics, diff --git a/src/blocks/theme/semantics/semantics.alert.ts b/src/blocks/theme/semantics/semantics.alert.ts new file mode 100644 index 0000000000..7b9ab7bb79 --- /dev/null +++ b/src/blocks/theme/semantics/semantics.alert.ts @@ -0,0 +1,24 @@ +import { colorBrands } from '../colors/colors.brands'; +import { colorPrimitives } from '../colors/colors.primitives'; +import { textSemantics } from './semantics.text'; + +export const alertSemantics = { + 'text-default': { light: textSemantics['primary'].light, dark: textSemantics['primary'].dark }, + 'text-body': { light: textSemantics['tertiary'].light, dark: textSemantics['tertiary'].dark }, + 'icon-success': { light: colorBrands['success-500'], dark: colorBrands['success-300'] }, + 'icon-warning': { light: colorBrands['warning-700'], dark: colorBrands['warning-100'] }, + 'icon-error': { light: colorBrands['danger-600'], dark: colorBrands['danger-500'] }, + 'icon-info': { light: colorPrimitives['blue-700'], dark: colorPrimitives['blue-100'] }, + 'text-cta-success': { light: colorBrands['success-500'], dark: colorBrands['success-300'] }, + 'text-cta-warning': { light: colorBrands['warning-700'], dark: colorBrands['warning-100'] }, + 'text-cta-error': { light: colorBrands['danger-600'], dark: colorBrands['danger-500'] }, + 'text-cta-info': { light: colorPrimitives['blue-700'], dark: colorPrimitives['blue-100'] }, + 'background-success': { light: colorBrands['success-100'], dark: colorBrands['success-900'] }, + 'background-warning': { light: colorBrands['warning-100'], dark: colorBrands['warning-900'] }, + 'background-error': { light: colorBrands['danger-100'], dark: colorBrands['danger-900'] }, + 'background-info': { light: colorPrimitives['blue-100'], dark: colorPrimitives['blue-900'] }, + 'stroke-success': { light: colorBrands['success-300'], dark: colorBrands['success-700'] }, + 'stroke-warning': { light: colorBrands['warning-300'], dark: colorBrands['warning-700'] }, + 'stroke-error': { light: colorBrands['danger-300'], dark: colorBrands['danger-700'] }, + 'stroke-info': { light: colorPrimitives['blue-300'], dark: colorPrimitives['blue-700'] }, +};