Merge pull request #32520 from backstage/bui-alert
BUI - Add new Alert component
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added new `Alert` component with support for status variants (info, success, warning, danger), icons, loading states, and custom actions.
|
||||
|
||||
Updated status color tokens for improved contrast and consistency across light and dark themes:
|
||||
|
||||
- Added new `--bui-bg-info` and `--bui-fg-info` tokens for info status
|
||||
- Updated `--bui-bg-danger`, `--bui-fg-danger` tokens
|
||||
- Updated `--bui-bg-warning`, `--bui-fg-warning` tokens
|
||||
- Updated `--bui-bg-success`, `--bui-fg-success` tokens
|
||||
|
||||
**Affected components**: Alert
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,168 @@
|
||||
'use client';
|
||||
|
||||
import { Alert } from '../../../../../packages/ui/src/components/Alert/Alert';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
|
||||
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
|
||||
import { RiCloseLine, RiCloudLine } from '@remixicon/react';
|
||||
|
||||
export const Default = () => {
|
||||
return <Alert status="info" icon={true} title="This is an alert message" />;
|
||||
};
|
||||
|
||||
export const StatusVariants = () => {
|
||||
return (
|
||||
<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
title="This is an informational alert with helpful information."
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Your changes have been saved successfully."
|
||||
/>
|
||||
<Alert
|
||||
status="warning"
|
||||
icon={true}
|
||||
title="This action may have unintended consequences."
|
||||
/>
|
||||
<Alert
|
||||
status="danger"
|
||||
icon={true}
|
||||
title="An error occurred while processing your request."
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithDescription = () => {
|
||||
return (
|
||||
<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
title="New Feature Available"
|
||||
description="We've added support for custom table columns. Check the documentation to learn more."
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Deployment Successful"
|
||||
description="Your application has been deployed to production. All health checks passed."
|
||||
/>
|
||||
<Alert
|
||||
status="warning"
|
||||
icon={true}
|
||||
title="Pending Review"
|
||||
description="Please review the following items before proceeding with the deployment."
|
||||
/>
|
||||
<Alert
|
||||
status="danger"
|
||||
icon={true}
|
||||
title="Authentication Failed"
|
||||
description="Unable to verify your credentials. Please check your username and password and try again."
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithActions = () => {
|
||||
return (
|
||||
<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
title="This alert has a dismiss action on the right."
|
||||
customActions={
|
||||
<Button size="small" variant="tertiary">
|
||||
Dismiss
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Your changes have been saved. Would you like to continue?"
|
||||
customActions={
|
||||
<ButtonIcon
|
||||
size="small"
|
||||
variant="tertiary"
|
||||
icon={<RiCloseLine />}
|
||||
aria-label="Close"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithActionsAndDescriptions = () => {
|
||||
return (
|
||||
<Alert
|
||||
status="warning"
|
||||
icon={true}
|
||||
title="Update Available"
|
||||
description="A new version of the application is ready to install. This will require a brief restart."
|
||||
customActions={
|
||||
<>
|
||||
<Button size="small" variant="tertiary">
|
||||
Later
|
||||
</Button>
|
||||
<Button size="small" variant="primary">
|
||||
Update Now
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const LoadingStates = () => {
|
||||
return (
|
||||
<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
loading
|
||||
title="Processing your request..."
|
||||
/>
|
||||
<Alert status="success" icon={true} loading title="Saving changes..." />
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
loading
|
||||
title="Processing your request"
|
||||
description="This may take a few moments. Please do not close this window."
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithoutIcons = () => {
|
||||
return (
|
||||
<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={false}
|
||||
title="This is an informational alert without an icon."
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={false}
|
||||
title="Your changes have been saved successfully."
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomIcon = () => {
|
||||
return (
|
||||
<Alert
|
||||
status="info"
|
||||
icon={<RiCloudLine />}
|
||||
title="This alert uses a custom cloud icon instead of the default info icon."
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { alertPropDefs } from './props-definition';
|
||||
import {
|
||||
alertUsageSnippet,
|
||||
defaultSnippet,
|
||||
statusVariantsSnippet,
|
||||
withDescriptionSnippet,
|
||||
withActionsSnippet,
|
||||
loadingStatesSnippet,
|
||||
withoutIconsSnippet,
|
||||
customIconSnippet,
|
||||
} from './snippets';
|
||||
import {
|
||||
Default,
|
||||
StatusVariants,
|
||||
WithDescription,
|
||||
WithActions,
|
||||
LoadingStates,
|
||||
WithoutIcons,
|
||||
CustomIcon,
|
||||
} from './components';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { AlertDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="Alert"
|
||||
description="A component for displaying alert messages with different status levels and optional actions."
|
||||
/>
|
||||
|
||||
<Snippet align="center" py={4} preview={<Default />} code={defaultSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={alertUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={alertPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Status Variants
|
||||
|
||||
The Alert component supports four status variants, each with its own color theme.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<StatusVariants />}
|
||||
code={statusVariantsSnippet}
|
||||
/>
|
||||
|
||||
### With Description
|
||||
|
||||
Add a description to provide additional context or details.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithDescription />}
|
||||
code={withDescriptionSnippet}
|
||||
/>
|
||||
|
||||
### With Actions
|
||||
|
||||
Include custom actions like buttons for interactive alerts.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithActions />}
|
||||
code={withActionsSnippet}
|
||||
/>
|
||||
|
||||
### Loading States
|
||||
|
||||
The loading spinner replaces the icon to indicate an ongoing process.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<LoadingStates />}
|
||||
code={loadingStatesSnippet}
|
||||
/>
|
||||
|
||||
### Without Icons
|
||||
|
||||
Disable icons for a simpler appearance.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithoutIcons />}
|
||||
code={withoutIconsSnippet}
|
||||
/>
|
||||
|
||||
### Custom Icon
|
||||
|
||||
Provide a custom icon element instead of the default status icon.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<CustomIcon />}
|
||||
code={customIconSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={AlertDefinition} />
|
||||
|
||||
<ChangelogComponent component="alert" />
|
||||
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const alertPropDefs: Record<string, PropDef> = {
|
||||
status: {
|
||||
type: 'enum',
|
||||
values: ['info', 'success', 'warning', 'danger'],
|
||||
responsive: true,
|
||||
default: 'info',
|
||||
},
|
||||
icon: {
|
||||
type: 'enum',
|
||||
values: ['boolean', 'React.ReactElement'],
|
||||
responsive: false,
|
||||
},
|
||||
loading: {
|
||||
type: 'enum',
|
||||
values: ['boolean'],
|
||||
responsive: false,
|
||||
},
|
||||
title: {
|
||||
type: 'enum',
|
||||
values: ['React.ReactNode'],
|
||||
responsive: false,
|
||||
},
|
||||
description: {
|
||||
type: 'enum',
|
||||
values: ['React.ReactNode'],
|
||||
responsive: false,
|
||||
},
|
||||
customActions: {
|
||||
type: 'enum',
|
||||
values: ['React.ReactNode'],
|
||||
responsive: false,
|
||||
},
|
||||
m: {
|
||||
type: 'enum',
|
||||
values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
||||
responsive: true,
|
||||
},
|
||||
mx: {
|
||||
type: 'enum',
|
||||
values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
||||
responsive: true,
|
||||
},
|
||||
my: {
|
||||
type: 'enum',
|
||||
values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
||||
responsive: true,
|
||||
},
|
||||
mt: {
|
||||
type: 'enum',
|
||||
values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
||||
responsive: true,
|
||||
},
|
||||
mb: {
|
||||
type: 'enum',
|
||||
values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
||||
responsive: true,
|
||||
},
|
||||
ml: {
|
||||
type: 'enum',
|
||||
values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
||||
responsive: true,
|
||||
},
|
||||
mr: {
|
||||
type: 'enum',
|
||||
values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
||||
responsive: true,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,131 @@
|
||||
export const alertUsageSnippet = `import { Alert } from '@backstage/ui';
|
||||
|
||||
<Alert status="info" title="This is an informational message" />`;
|
||||
|
||||
export const defaultSnippet = `<Alert status="info" icon={true} title="This is an alert message" />`;
|
||||
|
||||
export const statusVariantsSnippet = `<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
title="This is an informational alert with helpful information."
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Your changes have been saved successfully."
|
||||
/>
|
||||
<Alert
|
||||
status="warning"
|
||||
icon={true}
|
||||
title="This action may have unintended consequences."
|
||||
/>
|
||||
<Alert
|
||||
status="danger"
|
||||
icon={true}
|
||||
title="An error occurred while processing your request."
|
||||
/>
|
||||
</Flex>`;
|
||||
|
||||
export const withDescriptionSnippet = `<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
title="New Feature Available"
|
||||
description="We've added support for custom table columns. Check the documentation to learn more."
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Deployment Successful"
|
||||
description="Your application has been deployed to production. All health checks passed."
|
||||
/>
|
||||
<Alert
|
||||
status="warning"
|
||||
icon={true}
|
||||
title="Pending Review"
|
||||
description="Please review the following items before proceeding with the deployment."
|
||||
/>
|
||||
<Alert
|
||||
status="danger"
|
||||
icon={true}
|
||||
title="Authentication Failed"
|
||||
description="Unable to verify your credentials. Please check your username and password and try again."
|
||||
/>
|
||||
</Flex>`;
|
||||
|
||||
export const withActionsSnippet = `<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
title="This alert has a dismiss action on the right."
|
||||
customActions={
|
||||
<Button size="small" variant="tertiary">
|
||||
Dismiss
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Your changes have been saved. Would you like to continue?"
|
||||
customActions={
|
||||
<ButtonIcon
|
||||
size="small"
|
||||
variant="tertiary"
|
||||
icon={<RiCloseLine />}
|
||||
aria-label="Close"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Flex>`;
|
||||
|
||||
export const withActionsAndDescriptionsSnippet = `<Alert
|
||||
status="warning"
|
||||
icon={true}
|
||||
title="Update Available"
|
||||
description="A new version of the application is ready to install. This will require a brief restart."
|
||||
customActions={
|
||||
<>
|
||||
<Button size="small" variant="tertiary">
|
||||
Later
|
||||
</Button>
|
||||
<Button size="small" variant="primary">
|
||||
Update Now
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>`;
|
||||
|
||||
export const loadingStatesSnippet = `<Flex direction="column" gap="4">
|
||||
<Alert status="info" icon={true} loading title="Processing your request..." />
|
||||
<Alert status="success" icon={true} loading title="Saving changes..." />
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
loading
|
||||
title="Processing your request"
|
||||
description="This may take a few moments. Please do not close this window."
|
||||
/>
|
||||
</Flex>`;
|
||||
|
||||
export const withoutIconsSnippet = `<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={false}
|
||||
title="This is an informational alert without an icon."
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={false}
|
||||
title="Your changes have been saved successfully."
|
||||
/>
|
||||
</Flex>`;
|
||||
|
||||
export const customIconSnippet = `import { RiCloudLine } from '@remixicon/react';
|
||||
|
||||
<Alert
|
||||
status="info"
|
||||
icon={<RiCloudLine />}
|
||||
title="This alert uses a custom cloud icon instead of the default info icon."
|
||||
/>`;
|
||||
@@ -28,6 +28,10 @@ export const components: Page[] = [
|
||||
title: 'Accordion',
|
||||
slug: 'accordion',
|
||||
},
|
||||
{
|
||||
title: 'Alert',
|
||||
slug: 'alert',
|
||||
},
|
||||
{
|
||||
title: 'Avatar',
|
||||
slug: 'avatar',
|
||||
|
||||
+26
-25
@@ -1,38 +1,39 @@
|
||||
export type Component =
|
||||
| 'accordion'
|
||||
| 'alert'
|
||||
| 'avatar'
|
||||
| 'box'
|
||||
| 'button'
|
||||
| 'button-link'
|
||||
| 'heading'
|
||||
| 'text'
|
||||
| 'button-icon'
|
||||
| 'icon'
|
||||
| 'tabs'
|
||||
| 'menu'
|
||||
| 'textfield'
|
||||
| 'datatable'
|
||||
| 'select'
|
||||
| 'collapsible'
|
||||
| 'accordion'
|
||||
| 'checkbox'
|
||||
| 'container'
|
||||
| 'link'
|
||||
| 'tooltip'
|
||||
| 'scrollarea'
|
||||
| 'flex'
|
||||
| 'switch'
|
||||
| 'grid'
|
||||
| 'searchfield'
|
||||
| 'radio-group'
|
||||
| 'button-link'
|
||||
| 'card'
|
||||
| 'skeleton'
|
||||
| 'checkbox'
|
||||
| 'collapsible'
|
||||
| 'container'
|
||||
| 'datatable'
|
||||
| 'dialog'
|
||||
| 'flex'
|
||||
| 'grid'
|
||||
| 'header'
|
||||
| 'header-page'
|
||||
| 'heading'
|
||||
| 'icon'
|
||||
| 'link'
|
||||
| 'menu'
|
||||
| 'password-field'
|
||||
| 'radio-group'
|
||||
| 'scrollarea'
|
||||
| 'searchfield'
|
||||
| 'select'
|
||||
| 'skeleton'
|
||||
| 'switch'
|
||||
| 'table'
|
||||
| 'visually-hidden'
|
||||
| 'dialog'
|
||||
| 'tag-group';
|
||||
| 'tabs'
|
||||
| 'tag-group'
|
||||
| 'text'
|
||||
| 'textfield'
|
||||
| 'tooltip'
|
||||
| 'visually-hidden';
|
||||
|
||||
export type Version = `${number}.${number}.${number}`;
|
||||
|
||||
|
||||
+17
-11
@@ -75,9 +75,10 @@
|
||||
--bui-bg-neutral-on-surface-3-hover: oklch(0% 0 0 / .12);
|
||||
--bui-bg-neutral-on-surface-3-pressed: oklch(0% 0 0 / .16);
|
||||
--bui-bg-neutral-on-surface-3-disabled: oklch(0% 0 0 / .06);
|
||||
--bui-bg-danger: #feebe7;
|
||||
--bui-bg-warning: #fff2b2;
|
||||
--bui-bg-success: #e6f6eb;
|
||||
--bui-bg-danger: #ffe2e2;
|
||||
--bui-bg-warning: #ffedd5;
|
||||
--bui-bg-success: #dcfce7;
|
||||
--bui-bg-info: #dbeafe;
|
||||
--bui-fg-primary: var(--bui-black);
|
||||
--bui-fg-secondary: var(--bui-gray-7);
|
||||
--bui-fg-link: #1f5493;
|
||||
@@ -87,13 +88,15 @@
|
||||
--bui-fg-solid-disabled: #98a8bc;
|
||||
--bui-fg-tint: #1f5493;
|
||||
--bui-fg-tint-disabled: var(--bui-gray-5);
|
||||
--bui-fg-danger: #e22b2b;
|
||||
--bui-fg-warning: #e36d05;
|
||||
--bui-fg-success: #1db954;
|
||||
--bui-fg-danger: #991919;
|
||||
--bui-fg-warning: #92310a;
|
||||
--bui-fg-success: #116932;
|
||||
--bui-fg-info: #173da6;
|
||||
--bui-border: #0000001a;
|
||||
--bui-border-hover: #0003;
|
||||
--bui-border-pressed: #0006;
|
||||
--bui-border-disabled: #0000001a;
|
||||
--bui-border-info: #7ea9d6;
|
||||
--bui-border-danger: #f87a7a;
|
||||
--bui-border-warning: #e36d05;
|
||||
--bui-border-success: #53db83;
|
||||
@@ -137,9 +140,10 @@
|
||||
--bui-bg-neutral-on-surface-3-hover: oklch(100% 0 0 / .12);
|
||||
--bui-bg-neutral-on-surface-3-pressed: oklch(100% 0 0 / .2);
|
||||
--bui-bg-neutral-on-surface-3-disabled: oklch(100% 0 0 / .08);
|
||||
--bui-bg-danger: #3b1219;
|
||||
--bui-bg-danger: #300c0c;
|
||||
--bui-bg-warning: #302008;
|
||||
--bui-bg-success: #132d21;
|
||||
--bui-bg-success: #042713;
|
||||
--bui-bg-info: #132049;
|
||||
--bui-fg-primary: var(--bui-white);
|
||||
--bui-fg-secondary: var(--bui-gray-7);
|
||||
--bui-fg-link: #9cc9ff;
|
||||
@@ -149,13 +153,15 @@
|
||||
--bui-fg-solid-disabled: #6191cc;
|
||||
--bui-fg-tint: #9cc9ff;
|
||||
--bui-fg-tint-disabled: var(--bui-gray-5);
|
||||
--bui-fg-danger: #e22b2b;
|
||||
--bui-fg-warning: #e36d05;
|
||||
--bui-fg-success: #1db954;
|
||||
--bui-fg-danger: #fca5a5;
|
||||
--bui-fg-warning: #fdba74;
|
||||
--bui-fg-success: #86efac;
|
||||
--bui-fg-info: #a3cfff;
|
||||
--bui-border: #ffffff1f;
|
||||
--bui-border-hover: #fff6;
|
||||
--bui-border-pressed: #ffffff80;
|
||||
--bui-border-disabled: #fff3;
|
||||
--bui-border-info: #7ea9d6;
|
||||
--bui-border-danger: #f87a7a;
|
||||
--bui-border-warning: #e36d05;
|
||||
--bui-border-success: #53db83;
|
||||
|
||||
+92
-30
@@ -124,6 +124,61 @@ export interface AccordionTriggerProps extends HeadingProps {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const Alert: ForwardRefExoticComponent<
|
||||
AlertProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
|
||||
// @public
|
||||
export const AlertDefinition: {
|
||||
readonly styles: {
|
||||
readonly [key: string]: string;
|
||||
};
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-Alert';
|
||||
readonly contentWrapper: 'bui-AlertContentWrapper';
|
||||
readonly content: 'bui-AlertContent';
|
||||
readonly title: 'bui-AlertTitle';
|
||||
readonly description: 'bui-AlertDescription';
|
||||
readonly icon: 'bui-AlertIcon';
|
||||
readonly spinner: 'bui-AlertSpinner';
|
||||
readonly actions: 'bui-AlertActions';
|
||||
};
|
||||
readonly surface: 'container';
|
||||
readonly propDefs: {
|
||||
readonly status: {
|
||||
readonly dataAttribute: true;
|
||||
readonly default: 'info';
|
||||
};
|
||||
readonly loading: {
|
||||
readonly dataAttribute: true;
|
||||
};
|
||||
readonly icon: {};
|
||||
readonly customActions: {};
|
||||
readonly title: {};
|
||||
readonly description: {};
|
||||
readonly surface: {};
|
||||
readonly className: {};
|
||||
readonly style: {};
|
||||
};
|
||||
readonly utilityProps: readonly ['m', 'mb', 'ml', 'mr', 'mt', 'mx', 'my'];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type AlertOwnProps = ContainerSurfaceProps & {
|
||||
status?: Responsive<'info' | 'success' | 'warning' | 'danger'>;
|
||||
icon?: boolean | ReactElement;
|
||||
loading?: boolean;
|
||||
customActions?: ReactNode;
|
||||
title?: ReactNode;
|
||||
description?: ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface AlertProps extends MarginProps, AlertOwnProps {}
|
||||
|
||||
// @public (undocumented)
|
||||
export type AlignItems = 'stretch' | 'start' | 'center' | 'end';
|
||||
|
||||
@@ -1074,6 +1129,24 @@ export interface LinkProps extends LinkProps_2 {
|
||||
weight?: TextWeights | Partial<Record<Breakpoint, TextWeights>>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface MarginProps {
|
||||
// (undocumented)
|
||||
m?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
mb?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
ml?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
mr?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
mt?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
mx?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
my?: Responsive<Space>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Menu: (props: MenuProps<object>) => JSX_2.Element;
|
||||
|
||||
@@ -1261,6 +1334,24 @@ type Option_2 = {
|
||||
};
|
||||
export { Option_2 as Option };
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PaddingProps {
|
||||
// (undocumented)
|
||||
p?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
pb?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
pl?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
pr?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
pt?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
px?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
py?: Responsive<Space>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PagePagination extends TablePaginationProps {
|
||||
// (undocumented)
|
||||
@@ -1516,36 +1607,7 @@ export type Space =
|
||||
| string;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SpaceProps {
|
||||
// (undocumented)
|
||||
m?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
mb?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
ml?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
mr?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
mt?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
mx?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
my?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
p?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
pb?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
pl?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
pr?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
pt?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
px?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
py?: Responsive<Space>;
|
||||
}
|
||||
export interface SpaceProps extends MarginProps, PaddingProps {}
|
||||
|
||||
// @public (undocumented)
|
||||
export const SubmenuTrigger: (props: SubmenuTriggerProps) => JSX_2.Element;
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@layer tokens, base, components, utilities;
|
||||
|
||||
@layer components {
|
||||
.bui-Alert {
|
||||
--alert-bg: var(--bui-bg-surface-1);
|
||||
--alert-fg: var(--bui-fg-primary);
|
||||
|
||||
display: flex;
|
||||
gap: var(--bui-space-6);
|
||||
padding: var(--bui-space-3) var(--bui-space-3);
|
||||
border-radius: var(--bui-radius-3);
|
||||
font-family: var(--bui-font-regular);
|
||||
font-size: var(--bui-font-size-3);
|
||||
line-height: 1.5;
|
||||
|
||||
/* Apply variables */
|
||||
background-color: var(--alert-bg);
|
||||
color: var(--alert-fg);
|
||||
}
|
||||
|
||||
.bui-Alert[data-status='info'] {
|
||||
--alert-bg: var(--bui-bg-info);
|
||||
--alert-fg: var(--bui-fg-info);
|
||||
}
|
||||
|
||||
.bui-Alert[data-status='success'] {
|
||||
--alert-bg: var(--bui-bg-success);
|
||||
--alert-fg: var(--bui-fg-success);
|
||||
}
|
||||
|
||||
.bui-Alert[data-status='warning'] {
|
||||
--alert-bg: var(--bui-bg-warning);
|
||||
--alert-fg: var(--bui-fg-warning);
|
||||
}
|
||||
|
||||
.bui-Alert[data-status='danger'] {
|
||||
--alert-bg: var(--bui-bg-danger);
|
||||
--alert-fg: var(--bui-fg-danger);
|
||||
}
|
||||
|
||||
.bui-AlertIcon {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
/* Add slight top margin when there's a description for better alignment */
|
||||
.bui-Alert[data-has-description='true'] & {
|
||||
margin-top: 0.125rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-AlertContentWrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: var(--bui-space-3);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
/* Center align when there's no description */
|
||||
.bui-Alert[data-has-description='false'] & {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-AlertContent {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.bui-AlertTitle {
|
||||
font-weight: var(--bui-font-weight-bold);
|
||||
font-size: var(--bui-font-size-3);
|
||||
}
|
||||
|
||||
.bui-AlertDescription {
|
||||
font-size: var(--bui-font-size-2);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.bui-AlertSpinner {
|
||||
display: flex;
|
||||
|
||||
& svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
animation: bui-spin 1s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-AlertActions {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--bui-space-2);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.bui-AlertSpinner svg {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bui-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import preview from '../../../../../.storybook/preview';
|
||||
import { Alert } from './Alert';
|
||||
import { Flex } from '../Flex';
|
||||
import { Box } from '../Box';
|
||||
import { Text } from '../Text';
|
||||
import { Button } from '../Button';
|
||||
import { RiCloudLine } from '@remixicon/react';
|
||||
|
||||
const meta = preview.meta({
|
||||
title: 'Backstage UI/Alert',
|
||||
component: Alert,
|
||||
argTypes: {
|
||||
status: {
|
||||
control: 'select',
|
||||
options: ['info', 'success', 'warning', 'danger'],
|
||||
},
|
||||
icon: {
|
||||
control: 'boolean',
|
||||
},
|
||||
loading: {
|
||||
control: 'boolean',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const Default = meta.story({
|
||||
args: {
|
||||
title: 'This is an alert message',
|
||||
icon: true,
|
||||
},
|
||||
});
|
||||
|
||||
export const StatusVariants = meta.story({
|
||||
args: {
|
||||
title: 'This is an alert message',
|
||||
},
|
||||
parameters: {
|
||||
argTypes: {
|
||||
status: {
|
||||
control: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
title="This is an informational alert with helpful information."
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Your changes have been saved successfully."
|
||||
/>
|
||||
<Alert
|
||||
status="warning"
|
||||
icon={true}
|
||||
title="This action may have unintended consequences."
|
||||
/>
|
||||
<Alert
|
||||
status="danger"
|
||||
icon={true}
|
||||
title="An error occurred while processing your request."
|
||||
/>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const WithDescription = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
title="New Feature Available"
|
||||
description="We've added support for custom table columns. Check the documentation to learn more."
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Deployment Successful"
|
||||
description="Your application has been deployed to production. All health checks passed."
|
||||
/>
|
||||
<Alert
|
||||
status="warning"
|
||||
icon={true}
|
||||
title="Pending Review"
|
||||
description="Please review the following items before proceeding with the deployment."
|
||||
/>
|
||||
<Alert
|
||||
status="danger"
|
||||
icon={true}
|
||||
title="Authentication Failed"
|
||||
description="Unable to verify your credentials. Please check your username and password and try again."
|
||||
/>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const WithoutIcons = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={false}
|
||||
title="This is an informational alert without an icon."
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={false}
|
||||
title="Your changes have been saved successfully."
|
||||
/>
|
||||
<Alert
|
||||
status="warning"
|
||||
icon={false}
|
||||
title="This action may have unintended consequences."
|
||||
/>
|
||||
<Alert
|
||||
status="danger"
|
||||
icon={false}
|
||||
title="An error occurred while processing your request."
|
||||
/>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const CustomIcon = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={<RiCloudLine aria-hidden="true" />}
|
||||
title="This alert uses a custom cloud icon instead of the default info icon."
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={<RiCloudLine aria-hidden="true" />}
|
||||
title="Custom icons work with any status variant."
|
||||
/>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const WithActions = meta.story({
|
||||
render: args => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
title="This alert has a dismiss action on the right."
|
||||
customActions={
|
||||
<Button size="small" variant="tertiary">
|
||||
Dismiss
|
||||
</Button>
|
||||
}
|
||||
{...args}
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Your changes have been saved. Would you like to continue?"
|
||||
customActions={
|
||||
<>
|
||||
<Button size="small" variant="tertiary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="small" variant="primary">
|
||||
Continue
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
{...args}
|
||||
/>
|
||||
<Alert
|
||||
status="danger"
|
||||
icon={true}
|
||||
title="An error occurred while processing your request. Please try again."
|
||||
customActions={
|
||||
<Button size="small" variant="primary">
|
||||
Retry
|
||||
</Button>
|
||||
}
|
||||
{...args}
|
||||
/>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const WithActionsAndDescriptions = WithActions.extend({
|
||||
args: {
|
||||
description: 'This is a description of the alert.',
|
||||
},
|
||||
});
|
||||
|
||||
export const LoadingVariants = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>Info</Text>
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
loading
|
||||
title="Processing your request..."
|
||||
/>
|
||||
|
||||
<Text>Success</Text>
|
||||
<Alert status="success" icon={true} loading title="Saving changes..." />
|
||||
|
||||
<Text>Warning</Text>
|
||||
<Alert
|
||||
status="warning"
|
||||
icon={true}
|
||||
loading
|
||||
title="Checking for issues..."
|
||||
/>
|
||||
|
||||
<Text>Danger</Text>
|
||||
<Alert
|
||||
status="danger"
|
||||
icon={true}
|
||||
loading
|
||||
title="Attempting recovery..."
|
||||
/>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const LoadingWithDescription = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
loading
|
||||
title="Processing your request"
|
||||
description="This may take a few moments. Please do not close this window."
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
loading
|
||||
title="Deployment in Progress"
|
||||
description="Your application is being deployed to production. You'll receive a notification when complete."
|
||||
/>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const LongContent = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
title="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."
|
||||
/>
|
||||
<Alert
|
||||
status="warning"
|
||||
icon={true}
|
||||
title="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."
|
||||
customActions={
|
||||
<Button size="small" variant="tertiary">
|
||||
Dismiss
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const OnDifferentSurfaces = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>Default Surface</Text>
|
||||
<Flex direction="column" gap="2" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on default surface" />
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Alert on default surface"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 0</Text>
|
||||
<Flex direction="column" gap="2" surface="0" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on surface 0" />
|
||||
<Alert status="success" icon={true} title="Alert on surface 0" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 1</Text>
|
||||
<Flex direction="column" gap="2" surface="1" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on surface 1" />
|
||||
<Alert status="success" icon={true} title="Alert on surface 1" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 2</Text>
|
||||
<Flex direction="column" gap="2" surface="2" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on surface 2" />
|
||||
<Alert status="success" icon={true} title="Alert on surface 2" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 3</Text>
|
||||
<Flex direction="column" gap="2" surface="3" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on surface 3" />
|
||||
<Alert status="success" icon={true} title="Alert on surface 3" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const Responsive = meta.story({
|
||||
args: {
|
||||
title: 'This alert changes status responsively',
|
||||
icon: true,
|
||||
status: {
|
||||
initial: 'info',
|
||||
sm: 'success',
|
||||
md: 'warning',
|
||||
lg: 'danger',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const WithUtilityProps = meta.story({
|
||||
render: () => (
|
||||
<Box surface="1" py="4">
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Alert with custom margin"
|
||||
mb="4"
|
||||
mx="4"
|
||||
/>
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Alert with custom margin"
|
||||
mx="4"
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
});
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { forwardRef, Ref, isValidElement, ReactElement } from 'react';
|
||||
import { ProgressBar } from 'react-aria-components';
|
||||
import {
|
||||
RiLoader4Line,
|
||||
RiInformationLine,
|
||||
RiCheckLine,
|
||||
RiErrorWarningLine,
|
||||
RiAlertLine,
|
||||
} from '@remixicon/react';
|
||||
import type { AlertProps } from './types';
|
||||
import { useDefinition } from '../../hooks/useDefinition';
|
||||
import { AlertDefinition } from './definition';
|
||||
|
||||
/**
|
||||
* A component for displaying alert messages with different status levels.
|
||||
*
|
||||
* @remarks
|
||||
* The Alert component supports multiple status variants (info, success, warning, danger)
|
||||
* and can display icons, loading states, and custom actions. It automatically handles
|
||||
* icon selection based on status when the icon prop is set to true.
|
||||
*
|
||||
* @example
|
||||
* Basic usage with title only:
|
||||
* ```tsx
|
||||
* <Alert status="info" title="This is an informational message" />
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* With title and description:
|
||||
* ```tsx
|
||||
* <Alert
|
||||
* status="warning"
|
||||
* icon={true}
|
||||
* title="Pending Review"
|
||||
* description="Please review the following items before proceeding."
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* With custom actions and loading state:
|
||||
* ```tsx
|
||||
* <Alert
|
||||
* status="success"
|
||||
* icon={true}
|
||||
* title="Operation completed"
|
||||
* description="Your changes have been saved successfully."
|
||||
* loading={isProcessing}
|
||||
* customActions={
|
||||
* <>
|
||||
* <Button size="small" variant="tertiary">Dismiss</Button>
|
||||
* <Button size="small" variant="primary">View</Button>
|
||||
* </>
|
||||
* }
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const Alert = forwardRef(
|
||||
(props: AlertProps, ref: Ref<HTMLDivElement>) => {
|
||||
const { ownProps, restProps, dataAttributes, utilityStyle } = useDefinition(
|
||||
AlertDefinition,
|
||||
props,
|
||||
);
|
||||
const {
|
||||
classes,
|
||||
status,
|
||||
icon,
|
||||
loading,
|
||||
customActions,
|
||||
title,
|
||||
description,
|
||||
style,
|
||||
} = ownProps;
|
||||
|
||||
// Determine which icon to render
|
||||
const getStatusIcon = (): ReactElement | null => {
|
||||
// If icon is explicitly false, don't render any icon
|
||||
if (icon === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If icon is a custom React element, use it
|
||||
if (isValidElement(icon)) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
// If icon is true, auto-select based on status
|
||||
if (icon === true) {
|
||||
switch (status) {
|
||||
case 'success':
|
||||
return <RiCheckLine aria-hidden="true" />;
|
||||
case 'warning':
|
||||
return <RiErrorWarningLine aria-hidden="true" />;
|
||||
case 'danger':
|
||||
return <RiAlertLine aria-hidden="true" />;
|
||||
case 'info':
|
||||
default:
|
||||
return <RiInformationLine aria-hidden="true" />;
|
||||
}
|
||||
}
|
||||
|
||||
// Default: no icon
|
||||
return null;
|
||||
};
|
||||
|
||||
const statusIcon = getStatusIcon();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes.root}
|
||||
ref={ref}
|
||||
style={{ ...style, ...utilityStyle }}
|
||||
{...dataAttributes}
|
||||
{...restProps}
|
||||
data-has-description={description ? 'true' : 'false'}
|
||||
>
|
||||
<div className={classes.contentWrapper}>
|
||||
{loading ? (
|
||||
<div className={classes.icon}>
|
||||
<ProgressBar
|
||||
aria-label="Loading"
|
||||
isIndeterminate
|
||||
className={classes.spinner}
|
||||
>
|
||||
<RiLoader4Line aria-hidden="true" />
|
||||
</ProgressBar>
|
||||
</div>
|
||||
) : (
|
||||
statusIcon && <div className={classes.icon}>{statusIcon}</div>
|
||||
)}
|
||||
|
||||
<div className={classes.content}>
|
||||
{title && <div className={classes.title}>{title}</div>}
|
||||
{description && (
|
||||
<div className={classes.description}>{description}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{customActions && (
|
||||
<div className={classes.actions}>{customActions}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Alert.displayName = 'Alert';
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { defineComponent } from '../../hooks/useDefinition';
|
||||
import type { AlertOwnProps } from './types';
|
||||
import styles from './Alert.module.css';
|
||||
|
||||
/**
|
||||
* Component definition for Alert
|
||||
* @public
|
||||
*/
|
||||
export const AlertDefinition = defineComponent<AlertOwnProps>()({
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-Alert',
|
||||
contentWrapper: 'bui-AlertContentWrapper',
|
||||
content: 'bui-AlertContent',
|
||||
title: 'bui-AlertTitle',
|
||||
description: 'bui-AlertDescription',
|
||||
icon: 'bui-AlertIcon',
|
||||
spinner: 'bui-AlertSpinner',
|
||||
actions: 'bui-AlertActions',
|
||||
},
|
||||
surface: 'container',
|
||||
propDefs: {
|
||||
status: { dataAttribute: true, default: 'info' },
|
||||
loading: { dataAttribute: true },
|
||||
icon: {},
|
||||
customActions: {},
|
||||
title: {},
|
||||
description: {},
|
||||
surface: {},
|
||||
className: {},
|
||||
style: {},
|
||||
},
|
||||
utilityProps: ['m', 'mb', 'ml', 'mr', 'mt', 'mx', 'my'],
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './Alert';
|
||||
export * from './types';
|
||||
export { AlertDefinition } from './definition';
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ReactElement, ReactNode, CSSProperties } from 'react';
|
||||
import type {
|
||||
ContainerSurfaceProps,
|
||||
Responsive,
|
||||
MarginProps,
|
||||
} from '../../types';
|
||||
|
||||
/** @public */
|
||||
export type AlertOwnProps = ContainerSurfaceProps & {
|
||||
status?: Responsive<'info' | 'success' | 'warning' | 'danger'>;
|
||||
icon?: boolean | ReactElement;
|
||||
loading?: boolean;
|
||||
customActions?: ReactNode;
|
||||
title?: ReactNode;
|
||||
description?: ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
/**
|
||||
* Properties for {@link Alert}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface AlertProps extends MarginProps, AlertOwnProps {}
|
||||
@@ -114,9 +114,10 @@
|
||||
--bui-bg-neutral-on-surface-3-disabled: oklch(0% 0 0 / 6%);
|
||||
|
||||
/* Status background colors */
|
||||
--bui-bg-danger: #feebe7;
|
||||
--bui-bg-warning: #fff2b2;
|
||||
--bui-bg-success: #e6f6eb;
|
||||
--bui-bg-danger: #ffe2e2;
|
||||
--bui-bg-warning: #ffedd5;
|
||||
--bui-bg-success: #dcfce7;
|
||||
--bui-bg-info: #dbeafe;
|
||||
|
||||
/* Foreground colors */
|
||||
--bui-fg-primary: var(--bui-black);
|
||||
@@ -128,15 +129,17 @@
|
||||
--bui-fg-solid-disabled: #98a8bc;
|
||||
--bui-fg-tint: #1f5493;
|
||||
--bui-fg-tint-disabled: var(--bui-gray-5);
|
||||
--bui-fg-danger: #e22b2b;
|
||||
--bui-fg-warning: #e36d05;
|
||||
--bui-fg-success: #1db954;
|
||||
--bui-fg-danger: #991919;
|
||||
--bui-fg-warning: #92310a;
|
||||
--bui-fg-success: #116932;
|
||||
--bui-fg-info: #173da6;
|
||||
|
||||
/* Border colors */
|
||||
--bui-border: rgba(0, 0, 0, 0.1);
|
||||
--bui-border-hover: rgba(0, 0, 0, 0.2);
|
||||
--bui-border-pressed: rgba(0, 0, 0, 0.4);
|
||||
--bui-border-disabled: rgba(0, 0, 0, 0.1);
|
||||
--bui-border-info: #7ea9d6;
|
||||
--bui-border-danger: #f87a7a;
|
||||
--bui-border-warning: #e36d05;
|
||||
--bui-border-success: #53db83;
|
||||
@@ -199,9 +202,10 @@
|
||||
--bui-bg-neutral-on-surface-3-disabled: oklch(100% 0 0 / 8%);
|
||||
|
||||
/* Status background colors */
|
||||
--bui-bg-danger: #3b1219;
|
||||
--bui-bg-danger: #300c0c;
|
||||
--bui-bg-warning: #302008;
|
||||
--bui-bg-success: #132d21;
|
||||
--bui-bg-success: #042713;
|
||||
--bui-bg-info: #132049;
|
||||
|
||||
/* Foreground colors */
|
||||
--bui-fg-primary: var(--bui-white);
|
||||
@@ -213,15 +217,17 @@
|
||||
--bui-fg-solid-disabled: #6191cc;
|
||||
--bui-fg-tint: #9cc9ff;
|
||||
--bui-fg-tint-disabled: var(--bui-gray-5);
|
||||
--bui-fg-danger: #e22b2b;
|
||||
--bui-fg-warning: #e36d05;
|
||||
--bui-fg-success: #1db954;
|
||||
--bui-fg-danger: #fca5a5;
|
||||
--bui-fg-warning: #fdba74;
|
||||
--bui-fg-success: #86efac;
|
||||
--bui-fg-info: #a3cfff;
|
||||
|
||||
/* Border colors */
|
||||
--bui-border: rgba(255, 255, 255, 0.12);
|
||||
--bui-border-hover: rgba(255, 255, 255, 0.4);
|
||||
--bui-border-pressed: rgba(255, 255, 255, 0.5);
|
||||
--bui-border-disabled: rgba(255, 255, 255, 0.2);
|
||||
--bui-border-info: #7ea9d6;
|
||||
--bui-border-danger: #f87a7a;
|
||||
--bui-border-warning: #e36d05;
|
||||
--bui-border-success: #53db83;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
export { AccordionDefinition } from './components/Accordion/definition';
|
||||
export { AlertDefinition } from './components/Alert/definition';
|
||||
export { AvatarDefinition } from './components/Avatar/definition';
|
||||
export { BoxDefinition } from './components/Box/definition';
|
||||
export { ButtonDefinition } from './components/Button/definition';
|
||||
|
||||
@@ -27,10 +27,11 @@ export * from './components/Flex';
|
||||
export * from './components/Container';
|
||||
|
||||
// UI components
|
||||
export * from './components/Accordion';
|
||||
export * from './components/Alert';
|
||||
export * from './components/Avatar';
|
||||
export * from './components/Button';
|
||||
export * from './components/Card';
|
||||
export * from './components/Accordion';
|
||||
export * from './components/Dialog';
|
||||
export * from './components/FieldLabel';
|
||||
export * from './components/Header';
|
||||
|
||||
@@ -92,7 +92,7 @@ export type Columns =
|
||||
| 'auto';
|
||||
|
||||
/** @public */
|
||||
export interface SpaceProps {
|
||||
export interface MarginProps {
|
||||
m?: Responsive<Space>;
|
||||
mb?: Responsive<Space>;
|
||||
ml?: Responsive<Space>;
|
||||
@@ -100,6 +100,10 @@ export interface SpaceProps {
|
||||
mt?: Responsive<Space>;
|
||||
mx?: Responsive<Space>;
|
||||
my?: Responsive<Space>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface PaddingProps {
|
||||
p?: Responsive<Space>;
|
||||
pb?: Responsive<Space>;
|
||||
pl?: Responsive<Space>;
|
||||
@@ -109,6 +113,9 @@ export interface SpaceProps {
|
||||
py?: Responsive<Space>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface SpaceProps extends MarginProps, PaddingProps {}
|
||||
|
||||
/** @public */
|
||||
export type TextVariants =
|
||||
| 'title-large'
|
||||
|
||||
Reference in New Issue
Block a user