Stack trace line 1\nStack trace line 2", + showDetail: false, + }, +}; + +export const WithDetailExpanded: Story = { + args: { + detail: "
Stack trace line 1\nStack trace line 2", + showDetail: true, + }, +}; + +export const HtmlContent: Story = { + args: { + isHtml: true, + children: "Visit this link for details.", + }, +}; diff --git a/frontend/packages/ui/src/lib/notifications/shared/NotificationPill.tsx b/frontend/packages/ui/src/lib/notifications/shared/NotificationPill.tsx new file mode 100644 index 0000000000..540f744045 --- /dev/null +++ b/frontend/packages/ui/src/lib/notifications/shared/NotificationPill.tsx @@ -0,0 +1,135 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) KALEIDOS INC + +import { memo } from "react"; +import clsx from "clsx"; +import { Icon, type IconId } from "../../foundations/assets/Icon"; +import { IconButton } from "../../buttons/IconButton"; +import styles from "./NotificationPill.module.scss"; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export type NotificationPillLevel = + | "default" + | "info" + | "warning" + | "error" + | "success"; + +export type NotificationPillType = "toast" | "context"; + +export type NotificationPillAppearance = "neutral" | "ghost"; + +export interface NotificationPillProps { + /** Severity level – controls colours. */ + level: NotificationPillLevel; + /** Where this pill is displayed. */ + type: NotificationPillType; + /** Visual appearance variant. */ + appearance?: NotificationPillAppearance; + /** + * When `true`, `children` is treated as an HTML string and injected via + * `dangerouslySetInnerHTML`. Use only with trusted content. + */ + isHtml?: boolean; + /** Optional detail section HTML string. */ + detail?: string; + /** Controls whether the detail section is expanded. */ + showDetail?: boolean; + /** Called when the user toggles the detail section. */ + onToggleDetail?: () => void; + /** Main content. Either a React node or (when `isHtml` is true) an HTML string. */ + children?: React.ReactNode | string; +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function iconByLevel(level: NotificationPillLevel): IconId { + switch (level) { + case "info": + case "default": + return "info"; + case "warning": + return "msg-neutral"; + case "error": + return "delete-text"; + case "success": + return "status-tick"; + } +} + +// --------------------------------------------------------------------------- +// Component +// --------------------------------------------------------------------------- + +function NotificationPillInner({ + level, + type, + appearance, + isHtml = false, + detail, + showDetail, + onToggleDetail, + children, +}: NotificationPillProps) { + const rootClass = clsx(styles["notification-pill"], { + [styles["appearance-neutral"]]: appearance === "neutral", + [styles["appearance-ghost"]]: appearance === "ghost", + [styles["with-detail"]]: Boolean(detail), + [styles["type-toast"]]: type === "toast", + [styles["type-context"]]: type === "context", + [styles["level-default"]]: level === "default", + [styles["level-warning"]]: level === "warning", + [styles["level-error"]]: level === "error", + [styles["level-success"]]: level === "success", + [styles["level-info"]]: level === "info", + }); + + const iconId = iconByLevel(level); + + return ( +