diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx
index 550bfa7b8..45430c2bf 100644
--- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx
+++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx
@@ -1,9 +1,12 @@
"use client";
import { observer } from "mobx-react";
-import { useParams } from "next/navigation";
+import { useParams, useRouter } from "next/navigation";
import useSWR from "swr";
// ui
+import { Banner } from "@plane/propel/banner";
+import { Button } from "@plane/propel/button";
+import { ArchiveIcon } from "@plane/propel/icons";
import { Loader } from "@plane/ui";
// components
import { PageHead } from "@/components/core/page-title";
@@ -16,6 +19,7 @@ import { useProject } from "@/hooks/store/use-project";
const ArchivedIssueDetailsPage = observer(() => {
// router
const { workspaceSlug, projectId, archivedIssueId } = useParams();
+ const router = useRouter();
// states
// hooks
const {
@@ -62,18 +66,35 @@ const ArchivedIssueDetailsPage = observer(() => {
) : (
-
-
- {workspaceSlug && projectId && archivedIssueId && (
-
- )}
+ <>
+
}
+ action={
+
+ }
+ className="border-b border-custom-border-200"
+ />
+
+
+ {workspaceSlug && projectId && archivedIssueId && (
+
+ )}
+
-
+ >
)}
>
);
diff --git a/packages/propel/package.json b/packages/propel/package.json
index df630fcfc..46abc585a 100644
--- a/packages/propel/package.json
+++ b/packages/propel/package.json
@@ -28,6 +28,10 @@
"import": "./dist/avatar/index.mjs",
"require": "./dist/avatar/index.js"
},
+ "./banner": {
+ "import": "./dist/banner/index.mjs",
+ "require": "./dist/banner/index.js"
+ },
"./button": {
"import": "./dist/button/index.mjs",
"require": "./dist/button/index.js"
diff --git a/packages/propel/src/banner/banner.stories.tsx b/packages/propel/src/banner/banner.stories.tsx
new file mode 100644
index 000000000..70187155a
--- /dev/null
+++ b/packages/propel/src/banner/banner.stories.tsx
@@ -0,0 +1,181 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+import { Banner } from "./banner";
+
+const meta = {
+ title: "Components/Banner",
+ component: Banner,
+ parameters: {
+ layout: "fullscreen",
+ },
+ tags: ["autodocs"],
+ argTypes: {
+ variant: {
+ control: "select",
+ options: ["success", "error", "warning", "info"],
+ description: "Visual variant of the banner",
+ },
+ title: {
+ control: "text",
+ description: "Banner message text",
+ },
+ icon: {
+ control: false,
+ description: "Icon element to display before the title",
+ },
+ action: {
+ control: false,
+ description: "Action element(s) to display on the right side",
+ },
+ },
+} satisfies Meta
;
+
+export default meta;
+type Story = StoryObj;
+
+// Sample icons for different variants
+const SuccessIcon = () => (
+
+);
+
+const ErrorIcon = () => (
+
+);
+
+const WarningIcon = () => (
+
+);
+
+const InfoIcon = () => (
+
+);
+
+const CloseButton = ({ onClick }: { onClick?: () => void }) => (
+
+);
+
+// ============================================================================
+// Interactive Stories
+// ============================================================================
+
+export const Interactive: Story = {
+ args: {
+ variant: "info",
+ title: "This is an interactive banner. Use the controls to customize it.",
+ icon: ,
+ dismissible: true,
+ },
+};
+
+// ============================================================================
+// Main Variants
+// ============================================================================
+
+export const Success: Story = {
+ args: {
+ variant: "success",
+ title: "Operation completed successfully",
+ icon: ,
+ action: ,
+ },
+};
+
+export const Error: Story = {
+ args: {
+ variant: "error",
+ title: "An error occurred while processing your request",
+ icon: ,
+ action: ,
+ },
+};
+
+export const Warning: Story = {
+ args: {
+ variant: "warning",
+ title: "Your session will expire in 5 minutes",
+ icon: ,
+ action: ,
+ },
+};
+
+export const Info: Story = {
+ args: {
+ variant: "info",
+ title: "New features are available. Check out what's new!",
+ icon: ,
+ action: ,
+ },
+};
diff --git a/packages/propel/src/banner/banner.tsx b/packages/propel/src/banner/banner.tsx
new file mode 100644
index 000000000..f3c22ee22
--- /dev/null
+++ b/packages/propel/src/banner/banner.tsx
@@ -0,0 +1,131 @@
+import React from "react";
+import { cn } from "../utils";
+import {
+ TBannerVariant,
+ getBannerStyling,
+ getBannerTitleStyling,
+ getBannerActionStyling,
+ getBannerDismissStyling,
+ getBannerDismissIconStyling,
+} from "./helper";
+
+export interface BannerProps extends Omit, "title"> {
+ /** Visual variant of the banner */
+ variant?: TBannerVariant;
+ /** Icon to display before the title */
+ icon?: React.ReactNode;
+ /** Banner title/message */
+ title?: React.ReactNode;
+ /** Action elements to display on the right side */
+ action?: React.ReactNode;
+ /** Whether the banner can be dismissed */
+ dismissible?: boolean;
+ /** Callback when banner is dismissed */
+ onDismiss?: () => void;
+ /** Whether to show the banner */
+ visible?: boolean;
+ /** Animation duration for show/hide */
+ animationDuration?: number;
+}
+
+export const Banner = React.forwardRef(
+ (
+ {
+ icon,
+ title,
+ action,
+ variant = "info",
+ dismissible = false,
+ onDismiss,
+ visible = true,
+ animationDuration = 200,
+ className,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ // Handle dismissal
+ const handleDismiss = () => {
+ if (onDismiss) {
+ onDismiss();
+ }
+ };
+
+ // Don't render if not visible
+ if (!visible) {
+ return null;
+ }
+
+ // Get styling using helper functions
+ const containerStyling = getBannerStyling(variant);
+ const iconStyling = "flex items-center justify-center flex-shrink-0 size-5";
+ const titleStyling = getBannerTitleStyling();
+ const actionStyling = getBannerActionStyling();
+ const dismissStyling = getBannerDismissStyling();
+ const dismissIconStyling = getBannerDismissIconStyling();
+
+ // Render custom icon component if provided
+ const renderIcon = () => {
+ if (icon) {
+ return {icon}
;
+ }
+ return null;
+ };
+
+ // Render dismiss button if dismissible
+ const renderDismissButton = () => {
+ if (!dismissible) return null;
+
+ return (
+
+ );
+ };
+
+ return (
+
+ {/* Left side: Icon and Title */}
+
+ {renderIcon()}
+ {title &&
{title}
}
+ {children}
+
+
+ {/* Right side: Actions */}
+ {(action || dismissible) && (
+
+ {action}
+ {renderDismissButton()}
+
+ )}
+
+ );
+ }
+);
+
+Banner.displayName = "Banner";
+
+// Export variant types for external use
+export type BannerVariant = TBannerVariant;
diff --git a/packages/propel/src/banner/helper.tsx b/packages/propel/src/banner/helper.tsx
new file mode 100644
index 000000000..cf128a209
--- /dev/null
+++ b/packages/propel/src/banner/helper.tsx
@@ -0,0 +1,46 @@
+export type TBannerVariant = "success" | "error" | "warning" | "info";
+
+export interface IBannerStyling {
+ [key: string]: string;
+}
+
+export const bannerSizeStyling = {
+ container: "py-3 px-6 h-12",
+ icon: "w-5 h-5",
+ title: "text-sm",
+ action: "gap-2",
+};
+
+// TODO: update this with new color once its implemented
+// Banner variant styling
+export const bannerStyling: IBannerStyling = {
+ success: "bg-green-500/10",
+ error: "bg-red-500/10",
+ warning: "bg-yellow-500/10",
+ info: "bg-blue-500/10",
+};
+
+// Base banner styles
+export const bannerBaseStyles = "flex items-center justify-between w-full transition-all duration-200";
+
+// Get banner container styling
+export const getBannerStyling = (variant: TBannerVariant): string => {
+ const variantStyles = bannerStyling[variant];
+ const sizeStyles = bannerSizeStyling.container;
+
+ return `${bannerBaseStyles} ${variantStyles} ${sizeStyles}`;
+};
+
+// Get title styling
+export const getBannerTitleStyling = (): string =>
+ `font-medium text-custom-text-200 flex-1 min-w-0 ${bannerSizeStyling.title}`;
+
+// Get action container styling
+export const getBannerActionStyling = (): string => `flex items-center flex-shrink-0 ${bannerSizeStyling.action}`;
+
+// Get dismiss button styling
+export const getBannerDismissStyling = (): string =>
+ "rounded p-1 hover:bg-custom-background-90 transition-colors flex-shrink-0";
+
+// Get dismiss icon styling
+export const getBannerDismissIconStyling = (): string => "text-custom-text-200";
diff --git a/packages/propel/src/banner/index.ts b/packages/propel/src/banner/index.ts
new file mode 100644
index 000000000..12d0a82b4
--- /dev/null
+++ b/packages/propel/src/banner/index.ts
@@ -0,0 +1,3 @@
+export { Banner } from "./banner";
+export type { BannerProps, BannerVariant } from "./banner";
+export type { TBannerVariant } from "./helper";
diff --git a/packages/propel/tsdown.config.ts b/packages/propel/tsdown.config.ts
index 677fea529..d698a2b90 100644
--- a/packages/propel/tsdown.config.ts
+++ b/packages/propel/tsdown.config.ts
@@ -5,6 +5,7 @@ export default defineConfig({
"src/accordion/index.ts",
"src/animated-counter/index.ts",
"src/avatar/index.ts",
+ "src/banner/index.ts",
"src/button/index.ts",
"src/calendar/index.ts",
"src/card/index.ts",