diff --git a/packages/apsara-ui/src/Alert/Alert.stories.tsx b/packages/apsara-ui/src/Alert/Alert.stories.tsx
new file mode 100644
index 00000000..c8b2a3e3
--- /dev/null
+++ b/packages/apsara-ui/src/Alert/Alert.stories.tsx
@@ -0,0 +1,35 @@
+import React from "react";
+import Alert from "./Alert";
+
+export default {
+ title: "General/Alert",
+ component: Alert,
+};
+
+export const alert = () => {
+ return (
+
+ );
+};
diff --git a/packages/apsara-ui/src/Alert/Alert.styles.tsx b/packages/apsara-ui/src/Alert/Alert.styles.tsx
new file mode 100644
index 00000000..997db9d0
--- /dev/null
+++ b/packages/apsara-ui/src/Alert/Alert.styles.tsx
@@ -0,0 +1,103 @@
+import styled from "styled-components";
+
+export const AlertWrapper = styled.div`
+ .apsara-alert {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ color: rgba(0, 0, 0, 0.85);
+ font-size: 14px;
+ font-variant: tabular-nums;
+ line-height: 1.5715;
+ list-style: none;
+ font-feature-settings: "tnum";
+ position: relative;
+ display: flex;
+ align-items: center;
+ padding: 8px 15px;
+ word-wrap: break-word;
+ border-radius: 2px;
+
+ &-content {
+ flex: 1;
+ min-width: 0;
+ }
+ &-icon {
+ margin-right: 8px;
+ }
+ &-description {
+ display: none;
+ font-size: 14px;
+ line-height: 22px;
+ }
+
+ &-success {
+ background-color: #f6ffed;
+ border: 1px solid #b7eb8f;
+
+ .apsara-alert-icon {
+ color: #52c41a;
+ }
+ }
+ &-info {
+ background-color: #e6f7ff;
+ border: 1px solid #91d5ff;
+
+ .apsara-alert-icon {
+ color: #1890ff;
+ }
+ }
+ &-warning {
+ background-color: #fffbe6;
+ border: 1px solid #ffe58f;
+
+ .apsara-alert-icon {
+ color: #faad14;
+ }
+ }
+ &-error {
+ background-color: #fff2f0;
+ border: 1px solid #ffccc7;
+
+ .apsara-alert-icon {
+ color: #ff4d4f;
+ }
+
+ .apsara-alert-description > pre {
+ margin: 0;
+ padding: 0;
+ }
+ }
+
+ &-action {
+ margin-left: 8px;
+ }
+
+ &-with-description {
+ align-items: flex-start;
+ padding: 15px 15px 15px 24px;
+
+ &.apsara-alert-no-icon {
+ padding: 15px 15px;
+ }
+ .apsara-alert-icon {
+ margin-right: 15px;
+ font-size: 24px;
+ }
+
+ .apsara-alert-message {
+ display: block;
+ margin-bottom: 4px;
+ color: rgba(0, 0, 0, 0.85);
+ font-size: 16px;
+ }
+ .apsara-alert-description {
+ display: block;
+ }
+ }
+
+ &-message {
+ color: rgba(0, 0, 0, 0.85);
+ }
+ }
+`;
diff --git a/packages/apsara-ui/src/Alert/Alert.tsx b/packages/apsara-ui/src/Alert/Alert.tsx
new file mode 100644
index 00000000..fdad9909
--- /dev/null
+++ b/packages/apsara-ui/src/Alert/Alert.tsx
@@ -0,0 +1,94 @@
+import * as React from "react";
+import CheckCircleOutlined from "@ant-design/icons/CheckCircleOutlined";
+import ExclamationCircleOutlined from "@ant-design/icons/ExclamationCircleOutlined";
+import InfoCircleOutlined from "@ant-design/icons/InfoCircleOutlined";
+import CloseCircleOutlined from "@ant-design/icons/CloseCircleOutlined";
+import CheckCircleFilled from "@ant-design/icons/CheckCircleFilled";
+import ExclamationCircleFilled from "@ant-design/icons/ExclamationCircleFilled";
+import InfoCircleFilled from "@ant-design/icons/InfoCircleFilled";
+import CloseCircleFilled from "@ant-design/icons/CloseCircleFilled";
+import { replaceElement } from "../FormBuilder/utils/reactNode";
+import classNames from "classnames";
+import { AlertWrapper } from "./Alert.styles";
+
+export interface AlertProps {
+ type?: "success" | "info" | "warning" | "error";
+ message: React.ReactNode;
+ description?: React.ReactNode;
+ showIcon?: boolean;
+ style?: React.CSSProperties;
+ className?: string;
+ icon?: React.ReactNode;
+ action?: React.ReactNode;
+}
+
+const iconMapFilled = {
+ success: CheckCircleFilled,
+ info: InfoCircleFilled,
+ error: CloseCircleFilled,
+ warning: ExclamationCircleFilled,
+};
+
+const iconMapOutlined = {
+ success: CheckCircleOutlined,
+ info: InfoCircleOutlined,
+ error: CloseCircleOutlined,
+ warning: ExclamationCircleOutlined,
+};
+
+const Alert = ({ description, message, className = "", style, showIcon = false, action, ...props }: AlertProps) => {
+ const [closed, _setClosed] = React.useState(false);
+
+ const prefixCls = "apsara-alert";
+
+ const getType = () => {
+ const { type } = props;
+ if (type !== undefined) {
+ return type;
+ }
+ return "info";
+ };
+
+ const type = getType();
+
+ const renderIconNode = () => {
+ const { icon } = props;
+ const iconType = (description ? iconMapOutlined : iconMapFilled)[type] || null;
+ if (icon) {
+ return replaceElement(icon, {icon}, () => ({
+ className: classNames(`${prefixCls}-icon`, {
+ [(icon as any).props.className]: (icon as any).props.className,
+ }),
+ }));
+ }
+ return React.createElement(iconType, { className: `${prefixCls}-icon` });
+ };
+
+ const isShowIcon = showIcon === undefined ? true : showIcon;
+
+ const alertClasses = classNames(
+ prefixCls,
+ `${prefixCls}-${type}`,
+ {
+ [`${prefixCls}-with-description`]: !!description,
+ [`${prefixCls}-no-icon`]: !isShowIcon,
+ },
+ className,
+ );
+
+ return (
+
+
+ {isShowIcon ? renderIconNode() : null}
+
+
{message}
+
{description}
+
+
+ {action ?
{action}
: null}
+
+
+ );
+};
+
+export default Alert;
diff --git a/packages/apsara-ui/src/Alert/index.tsx b/packages/apsara-ui/src/Alert/index.tsx
new file mode 100644
index 00000000..f1a2cf4d
--- /dev/null
+++ b/packages/apsara-ui/src/Alert/index.tsx
@@ -0,0 +1,3 @@
+import Alert from "./Alert";
+
+export default Alert;
diff --git a/packages/apsara-ui/src/index.ts b/packages/apsara-ui/src/index.ts
index c5d5d6b9..18b32f87 100644
--- a/packages/apsara-ui/src/index.ts
+++ b/packages/apsara-ui/src/index.ts
@@ -50,6 +50,7 @@ import { Col } from "./FormBuilder/grid/";
import { InputProps } from "./Input/Input";
import { TooltipPlacement } from "./Table/TableProps";
import Modal from "./Modal";
+import Alert from "./Alert";
export { DynamicList } from "./DynamicList";
export * from "./Notification";
@@ -113,4 +114,5 @@ export {
InputProps,
TooltipPlacement,
Modal,
+ Alert,
};