diff --git a/README.md b/README.md
index 2337b7c..8b00cf1 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ The package expects React 19 and Tailwind CSS 4. Install an exact release so a
consumer upgrades deliberately:
```bash
-pnpm add --save-exact @nextide/ui@2.1.0
+pnpm add --save-exact @nextide/ui@2.2.0
pnpm add --save-dev --save-exact tailwindcss@4.3.1 @tailwindcss/vite@4.3.1
```
diff --git a/apps/playground/src/App.tsx b/apps/playground/src/App.tsx
index cf5a162..1ee0841 100644
--- a/apps/playground/src/App.tsx
+++ b/apps/playground/src/App.tsx
@@ -14,12 +14,14 @@ import {
BriefcaseBusiness,
CalendarClock,
Check,
+ Circle,
Database,
FileJson,
FileText,
Filter,
Gauge,
Layers3,
+ LoaderCircle,
PanelRightClose,
PanelRightOpen,
PanelLeft,
@@ -31,6 +33,7 @@ import {
Sparkles,
ServerCog,
Video,
+ X,
} from "lucide-react"
import { AppShell } from "@nextide/ui/blocks/app-shell"
@@ -2976,8 +2979,34 @@ function BlockPreview({ motionScale }: { motionScale: number }) {
expanded: campaignsExpanded,
action: { label: "Create campaign", icon: },
children: [
- { id: "summer-launch", label: "Summer launch" },
- { id: "partner-rollout", label: "Partner rollout" },
+ {
+ id: "summer-launch",
+ label: "Summer launch",
+ status: "Completed",
+ tone: "success" as const,
+ statusIcon: ,
+ },
+ {
+ id: "partner-rollout",
+ label: "Partner rollout",
+ status: "Processing",
+ tone: "processing" as const,
+ statusIcon: ,
+ },
+ {
+ id: "creative-review",
+ label: "Creative review",
+ status: "Degraded",
+ tone: "neutral" as const,
+ statusIcon: ,
+ },
+ {
+ id: "failed-sync",
+ label: "Failed sync",
+ status: "Failed",
+ tone: "danger" as const,
+ statusIcon: ,
+ },
],
}
: item
diff --git a/packages/ui/README.md b/packages/ui/README.md
index 8866db7..72fa738 100644
--- a/packages/ui/README.md
+++ b/packages/ui/README.md
@@ -8,7 +8,7 @@ shadcn/ui, Base UI, Tailwind CSS 4, and React 19.
Pin an exact release so upgrades remain deliberate:
```bash
-pnpm add --save-exact @nextide/ui@2.1.0
+pnpm add --save-exact @nextide/ui@2.2.0
pnpm add --save-dev --save-exact tailwindcss@4.3.1 @tailwindcss/vite@4.3.1
```
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 3f325a7..de64f7a 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@nextide/ui",
- "version": "2.1.0",
+ "version": "2.2.0",
"description": "Shared Nextide React components and product interface patterns.",
"type": "module",
"license": "UNLICENSED",
diff --git a/packages/ui/src/blocks/navigation-panel.tsx b/packages/ui/src/blocks/navigation-panel.tsx
index b53746a..eb956f5 100644
--- a/packages/ui/src/blocks/navigation-panel.tsx
+++ b/packages/ui/src/blocks/navigation-panel.tsx
@@ -27,7 +27,10 @@ import {
AutocompletePositioner,
} from "@nextide/ui/components/autocomplete"
import { Kbd } from "@nextide/ui/components/kbd"
-import { StatusBadge } from "@nextide/ui/components/status-badge"
+import {
+ StatusBadge,
+ type StatusBadgeIndicator,
+} from "@nextide/ui/components/status-badge"
import { Surface } from "@nextide/ui/components/surface"
import { useContainedScroll } from "@nextide/ui/hooks/use-contained-scroll"
import { cn } from "@nextide/ui/lib/utils"
@@ -45,6 +48,8 @@ type NavigationPanelItem = {
meta?: string
status?: string
tone?: NavigationPanelStatusTone
+ statusIcon?: React.ReactNode
+ statusIndicator?: StatusBadgeIndicator
icon?: React.ReactNode
children?: NavigationPanelItem[]
expanded?: boolean
@@ -870,14 +875,7 @@ function NavigationPanelNav({
{item.meta}
) : null}
- {item.status ? (
-
- {item.status}
-
- ) : null}
+
) : null}
@@ -938,7 +936,7 @@ function NavigationPanelNav({
}}
data-slot="navigation-panel-child"
className={cn(
- "group grid min-h-11 w-full grid-cols-[2rem_minmax(0,1fr)] items-center rounded-lg border border-transparent pr-2 text-left text-sm transition-colors max-lg:w-auto max-lg:min-w-max",
+ "group relative grid min-h-11 w-full grid-cols-[2rem_minmax(0,1fr)] items-center rounded-lg border border-transparent pr-8 text-left text-sm transition-colors max-lg:w-auto max-lg:min-w-max",
childActive
? "bg-nextide-tide/[0.07] text-foreground"
: "text-muted-foreground hover:bg-nextide-panel-strong/70 hover:text-foreground"
@@ -966,14 +964,13 @@ function NavigationPanelNav({
{child.label}
- {child.meta || child.status ? (
+ {child.meta ? (
- {[child.meta, child.status]
- .filter(Boolean)
- .join(" ยท ")}
+ {child.meta}
) : null}
+
)
})}
@@ -999,6 +996,32 @@ function NavigationPanelNav({
)
}
+function NavigationPanelStatus({
+ item,
+ iconOnly = false,
+}: {
+ item: NavigationPanelItem
+ iconOnly?: boolean
+}) {
+ if (!item.status) return null
+
+ return (
+
+ {iconOnly ? {item.status} : item.status}
+
+ )
+}
+
function NavigationPanelFooter({
collapsed,
drawerCollapsed,
diff --git a/packages/ui/src/components/status-badge.tsx b/packages/ui/src/components/status-badge.tsx
index a2b02af..6563c81 100644
--- a/packages/ui/src/components/status-badge.tsx
+++ b/packages/ui/src/components/status-badge.tsx
@@ -38,10 +38,12 @@ function StatusBadge({
tone,
size,
children,
+ icon,
indicator = "dot",
...props
}: React.ComponentProps<"span"> &
VariantProps & {
+ icon?: React.ReactNode
indicator?: StatusBadgeIndicator
}) {
const pulse = indicator === "pulse"
@@ -51,11 +53,22 @@ function StatusBadge({
data-slot="status-badge"
data-tone={tone}
data-size={size ?? "default"}
- data-indicator={indicator}
+ data-indicator={icon ? "icon" : indicator}
className={cn(statusBadgeVariants({ tone, size }), className)}
{...props}
>
- {indicator !== "none" ? (
+ {icon ? (
+
+ {icon}
+
+ ) : indicator !== "none" ? (
processingIcon.evaluate((icon) => getComputedStyle(icon).animationName))
+ .not.toBe("none")
+ await page.emulateMedia({ reducedMotion: "reduce" })
+ await expect
+ .poll(() => processingIcon.evaluate((icon) => getComputedStyle(icon).animationName))
+ .toBe("none")
+ await page.emulateMedia({ reducedMotion: "no-preference" })
await expect(
navigation.getByRole("button", { name: "Collapse Campaigns" })
).toBeVisible()