Product
Sidebar alert card
Sidebar billing notice with a status indicator and corrective action.
$ npx context-ds add sidebar-alert-cardUsage
Use it for
Payment failed and scheduled cancellation, above Help.
Avoid
- More than one at a time
- Using it for news or upsells
Dependencies: lucide-react
Example
examples/sidebar-alert-card.tsx
"use client";import { useState } from "react";import { SidebarAlertCard } from "@/components/product/sidebar-alert-card";import { Button } from "@/components/ui/button";import { ExampleMessage } from "./demo-feedback";export function Product() { const [resumed, setResumed] = useState(false); return <div className="grid w-53 gap-3"> <SidebarAlertCard tone="danger" tag="Last payment failed" message="Your payment method is failing." action="Fix issue" onClick={() => { window.location.href = "/preview/settings-page/Billing"; }} /> {resumed ? <><ExampleMessage>Example plan resumed.</ExampleMessage><Button variant="outline" onClick={() => setResumed(false)}>Reset example</Button></> : <SidebarAlertCard tone="warning" tag="3 days left" message="Your plan ends on Oct 20." action="Resume plan" onClick={() => setResumed(true)} />} </div>;}Source
components/product/sidebar-alert-card.tsx
"use client";import * as React from "react";import { ArrowUpRight, RotateCcw } from "lucide-react";import { cn } from "@/lib/utils";const TONES = { danger: { card: "border-danger/30 bg-danger/4 hover:border-danger/50 hover:bg-danger/7", text: "text-danger", dot: "bg-danger", ping: "bg-danger/60", }, warning: { card: "border-warning/30 bg-warning/5 hover:border-warning/50 hover:bg-warning/8", text: "text-warning", dot: "bg-warning", ping: "bg-warning/60", },} as const;// A billing problem at the foot of the sidebar, above Help: the whole card is the fix. A pulsing dot and a short tag// in the tone's color ("Last payment failed", "3 days left"), one line in body text, then the action with its arrow. Danger for a failed payment (the arrow goes up and right, out to billing); warning for a plan// that is ending (the arrow turns back, resuming it). Hidden while the sidebar shows the playground's APIs.function SidebarAlertCard({ tone = "danger", tag, message, action, pendingAction, pending = false, icon = tone === "danger" ? "external" : "resume", onClick, className,}: { tone?: keyof typeof TONES; tag: React.ReactNode; message: React.ReactNode; action: React.ReactNode; /** The action's words while it runs ("Opening..."). */ pendingAction?: React.ReactNode; pending?: boolean; icon?: "external" | "resume"; onClick?: () => void; className?: string;}) { const t = TONES[tone]; return ( <button type="button" data-slot="sidebar-alert-card" onClick={onClick} disabled={pending} className={cn( "group mb-2 flex w-full flex-col gap-1 rounded-card border px-3 py-2.5 text-left outline-none transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-focus-ring disabled:cursor-not-allowed disabled:opacity-70", t.card, className, )} > <span className="flex items-center gap-2"> <span aria-hidden className="relative flex size-1.5 shrink-0"> <span className={cn("absolute inline-flex size-full animate-ping rounded-pill [animation-duration:2s] motion-reduce:hidden", t.ping)} /> <span className={cn("relative inline-flex size-1.5 rounded-pill", t.dot)} /> </span> <span className={cn("text-caption", t.text)}>{tag}</span> </span> <span className="text-body-sm text-fg">{message}</span> <span className={cn("flex items-center text-caption", icon === "external" ? "gap-0.5" : "gap-1", t.text)}> {pending && pendingAction ? pendingAction : action} {icon === "external" ? ( <ArrowUpRight className="size-3" /> ) : ( <RotateCcw className="size-3 transition-transform duration-150 ease-out group-hover:-rotate-45 motion-reduce:transition-none" /> )} </span> </button> );}export { SidebarAlertCard };Reference: brand-dev-webapp src/components/sidebar-payment-failed-card.tsx and src/components/sidebar-cancelling-card.tsx.