diff --git a/packages/ui/src/components/Alert/Alert.module.css b/packages/ui/src/components/Alert/Alert.module.css index 2d6d7a0825..cda8a45ddb 100644 --- a/packages/ui/src/components/Alert/Alert.module.css +++ b/packages/ui/src/components/Alert/Alert.module.css @@ -18,35 +18,28 @@ @layer components { .bui-Alert { - --loading-duration: 200ms; --alert-bg: var(--bui-bg-surface-1); --alert-fg: var(--bui-fg-primary); --alert-border: var(--bui-border); - position: relative; display: flex; align-items: flex-start; - gap: var(--bui-space-3); - padding: var(--bui-space-3) var(--bui-space-4); + gap: var(--bui-space-2); + padding: var(--bui-space-2) var(--bui-space-3); border-radius: var(--bui-radius-3); border: 1px solid var(--alert-border); font-family: var(--bui-font-regular); font-size: var(--bui-font-size-3); line-height: 1.5; - transition: opacity var(--loading-duration) ease-out; /* Apply variables */ background-color: var(--alert-bg); color: var(--alert-fg); - - &[data-loading='true'] { - opacity: 0.7; - } } .bui-Alert[data-status='info'] { - --alert-bg: var(--bui-bg-surface-1); - --alert-fg: var(--bui-fg-primary); + --alert-bg: var(--bui-bg-info); + --alert-fg: var(--bui-fg-info); --alert-border: var(--bui-border-info); } @@ -74,15 +67,10 @@ align-items: center; justify-content: center; margin-top: 0.125rem; - transition: opacity var(--loading-duration) ease-out; svg { - width: 1.25rem; - height: 1.25rem; - } - - .bui-Alert[data-loading='true'] & { - opacity: 0; + width: 1rem; + height: 1rem; } } @@ -92,21 +80,22 @@ word-wrap: break-word; } - .bui-AlertSpinner { - position: absolute; - top: var(--bui-space-3); - left: var(--bui-space-4); - display: flex; - opacity: 0; - transition: opacity var(--loading-duration) ease-in; + .bui-AlertTitle { + font-weight: var(--bui-font-weight-bold); + font-size: var(--bui-font-size-3); + } - .bui-Alert[data-loading='true'] & { - opacity: 1; - } + .bui-AlertDescription { + font-size: var(--bui-font-size-2); + opacity: 0.9; + } + + .bui-AlertSpinner { + display: flex; & svg { - width: 1.25rem; - height: 1.25rem; + width: 1rem; + height: 1rem; animation: bui-spin 1s linear infinite; } } @@ -120,18 +109,6 @@ } @media (prefers-reduced-motion: reduce) { - .bui-Alert { - transition-duration: 50ms; - } - - .bui-AlertIcon { - transition-duration: 50ms; - } - - .bui-AlertSpinner { - transition-duration: 50ms; - } - .bui-AlertSpinner svg { animation: none; } diff --git a/packages/ui/src/components/Alert/Alert.stories.tsx b/packages/ui/src/components/Alert/Alert.stories.tsx index fa27321d62..b8b4d3ac1d 100644 --- a/packages/ui/src/components/Alert/Alert.stories.tsx +++ b/packages/ui/src/components/Alert/Alert.stories.tsx @@ -20,7 +20,6 @@ import { Box } from '../Box'; import { Text } from '../Text'; import { Button } from '../Button'; import { RiCloudLine } from '@remixicon/react'; -import { useState } from 'react'; const meta = preview.meta({ title: 'Backstage UI/Alert', @@ -41,14 +40,14 @@ const meta = preview.meta({ export const Default = meta.story({ args: { - children: 'This is an alert message', + title: 'This is an alert message', icon: true, }, }); export const StatusVariants = meta.story({ args: { - children: 'This is an alert message', + title: 'This is an alert message', }, parameters: { argTypes: { @@ -59,18 +58,57 @@ export const StatusVariants = meta.story({ }, render: () => ( - - This is an informational alert with helpful information. - - - Your changes have been saved successfully. - - - This action may have unintended consequences. - - - An error occurred while processing your request. - + + + + + + ), +}); + +export const WithDescription = meta.story({ + render: () => ( + + + + + ), }); @@ -78,18 +116,26 @@ export const StatusVariants = meta.story({ export const WithoutIcons = meta.story({ render: () => ( - - This is an informational alert without an icon. - - - Your changes have been saved successfully. - - - This action may have unintended consequences. - - - An error occurred while processing your request. - + + + + ), }); @@ -97,12 +143,16 @@ export const WithoutIcons = meta.story({ export const CustomIcon = meta.story({ render: () => ( - }> - This alert uses a custom cloud icon instead of the default info icon. - - }> - Custom icons work with any status variant. - + } + title="This alert uses a custom cloud icon instead of the default info icon." + /> + } + title="Custom icons work with any status variant." + /> ), }); @@ -113,19 +163,17 @@ export const WithActions = meta.story({ - - + } - > - This alert has a dismiss action on the right. - + /> } - > - Your changes have been saved. Would you like to continue? - + /> - - + } - > - An error occurred while processing your request. Please try again. - + /> ), }); -export const Loading = meta.story({ - render: () => { - const [isLoading, setIsLoading] = useState(false); - - const handleLoad = () => { - setIsLoading(true); - setTimeout(() => { - setIsLoading(false); - }, 3000); - }; - - return ( - - - Load - - } - > - Click the button to see the loading state - - - ); - }, -}); - export const LoadingVariants = meta.story({ render: () => ( Info - - Processing your request... - + Success - - Saving changes... - + Warning - - Checking for issues... - + Danger - - Attempting recovery... - + + + ), +}); + +export const LoadingWithDescription = meta.story({ + render: () => ( + + + ), }); @@ -215,26 +256,21 @@ export const LoadingVariants = meta.story({ export const LongContent = meta.story({ render: () => ( - - This is a longer alert message that demonstrates how the component - handles multiple lines of text. The content will wrap naturally and - maintain proper spacing with the icon and any actions. This is useful - for providing detailed information to users when necessary. - + Dismiss } - > - This alert combines long content with actions. The actions remain - aligned to the right even when the content wraps to multiple lines. This - ensures a consistent and predictable layout regardless of content - length. - + /> ), }); @@ -245,60 +281,44 @@ export const OnDifferentSurfaces = meta.story({ Default Surface - - Alert on default surface - - - Alert on default surface - + + On Surface 0 - - Alert on surface 0 - - - Alert on surface 0 - + + On Surface 1 - - Alert on surface 1 - - - Alert on surface 1 - + + On Surface 2 - - Alert on surface 2 - - - Alert on surface 2 - + + On Surface 3 - - Alert on surface 3 - - - Alert on surface 3 - + + @@ -307,7 +327,7 @@ export const OnDifferentSurfaces = meta.story({ export const Responsive = meta.story({ args: { - children: 'This alert changes status responsively', + title: 'This alert changes status responsively', icon: true, status: { initial: 'info', @@ -320,15 +340,20 @@ export const Responsive = meta.story({ export const WithUtilityProps = meta.story({ render: () => ( - - - Alert with custom margin and padding using utility props - - - - Alert with zero margin bottom - - - + + + + ), }); diff --git a/packages/ui/src/components/Alert/Alert.tsx b/packages/ui/src/components/Alert/Alert.tsx index 180e23313f..6c53ef1697 100644 --- a/packages/ui/src/components/Alert/Alert.tsx +++ b/packages/ui/src/components/Alert/Alert.tsx @@ -36,9 +36,20 @@ import { AlertDefinition } from './definition'; * icon selection based on status when the icon prop is set to true. * * @example - * Basic usage: + * Basic usage with title only: * ```tsx - * This is an informational message + * + * ``` + * + * @example + * With title and description: + * ```tsx + * * ``` * * @example @@ -47,16 +58,16 @@ import { AlertDefinition } from './definition'; * * - * + * * * } - * > - * Operation completed successfully - * + * /> * ``` * * @public @@ -73,8 +84,9 @@ export const Alert = forwardRef( icon, loading, customActions, + title, + description, style, - surfaceChildren: children, } = ownProps; // Determine which icon to render @@ -118,19 +130,26 @@ export const Alert = forwardRef( {...dataAttributes} {...restProps} > - {statusIcon &&
{statusIcon}
} - - {loading && ( - - + {loading ? ( +
+ + +
+ ) : ( + statusIcon &&
{statusIcon}
)} -
{children}
+
+ {title &&
{title}
} + {description && ( +
{description}
+ )} +
{customActions && (
{customActions}
diff --git a/packages/ui/src/components/Alert/definition.ts b/packages/ui/src/components/Alert/definition.ts index 8919151cf3..30c574dc14 100644 --- a/packages/ui/src/components/Alert/definition.ts +++ b/packages/ui/src/components/Alert/definition.ts @@ -27,6 +27,8 @@ export const AlertDefinition = defineComponent()({ classNames: { root: 'bui-Alert', content: 'bui-AlertContent', + title: 'bui-AlertTitle', + description: 'bui-AlertDescription', icon: 'bui-AlertIcon', spinner: 'bui-AlertSpinner', actions: 'bui-AlertActions', @@ -37,25 +39,11 @@ export const AlertDefinition = defineComponent()({ loading: { dataAttribute: true }, icon: {}, customActions: {}, + title: {}, + description: {}, surface: {}, - children: {}, className: {}, style: {}, }, - utilityProps: [ - 'm', - 'mb', - 'ml', - 'mr', - 'mt', - 'mx', - 'my', - 'p', - 'pb', - 'pl', - 'pr', - 'pt', - 'px', - 'py', - ], + utilityProps: ['m', 'mb', 'ml', 'mr', 'mt', 'mx', 'my'], }); diff --git a/packages/ui/src/components/Alert/types.ts b/packages/ui/src/components/Alert/types.ts index ba386dfb57..70d4d7fd90 100644 --- a/packages/ui/src/components/Alert/types.ts +++ b/packages/ui/src/components/Alert/types.ts @@ -27,7 +27,8 @@ export type AlertOwnProps = ContainerSurfaceProps & { icon?: boolean | ReactElement; loading?: boolean; customActions?: ReactNode; - children?: ReactNode; + title?: ReactNode; + description?: ReactNode; className?: string; style?: CSSProperties; }; diff --git a/packages/ui/src/css/tokens.css b/packages/ui/src/css/tokens.css index b96eb0c909..4daf36099e 100644 --- a/packages/ui/src/css/tokens.css +++ b/packages/ui/src/css/tokens.css @@ -114,6 +114,7 @@ --bui-bg-neutral-on-surface-3-disabled: oklch(0% 0 0 / 6%); /* Status background colors */ + --bui-bg-info: #e0f2fe; --bui-bg-danger: #feebe7; --bui-bg-warning: #fff2b2; --bui-bg-success: #e6f6eb; @@ -128,6 +129,7 @@ --bui-fg-solid-disabled: #98a8bc; --bui-fg-tint: #1f5493; --bui-fg-tint-disabled: var(--bui-gray-5); + --bui-fg-info: #0369a1; --bui-fg-danger: #e22b2b; --bui-fg-warning: #e36d05; --bui-fg-success: #1db954; @@ -200,6 +202,7 @@ --bui-bg-neutral-on-surface-3-disabled: oklch(100% 0 0 / 8%); /* Status background colors */ + --bui-bg-info: #082f49; --bui-bg-danger: #3b1219; --bui-bg-warning: #302008; --bui-bg-success: #132d21; @@ -214,6 +217,7 @@ --bui-fg-solid-disabled: #6191cc; --bui-fg-tint: #9cc9ff; --bui-fg-tint-disabled: var(--bui-gray-5); + --bui-fg-info: #7dd3fc; --bui-fg-danger: #e22b2b; --bui-fg-warning: #e36d05; --bui-fg-success: #1db954;