diff --git a/apps/apollo-vertex/content/docs/components/sonner.mdx b/apps/apollo-vertex/content/docs/components/sonner.mdx
index 88aaf3620f..db2ab19ff4 100644
--- a/apps/apollo-vertex/content/docs/components/sonner.mdx
+++ b/apps/apollo-vertex/content/docs/components/sonner.mdx
@@ -19,14 +19,14 @@ Add the `` component once to your root route. Status colors, icons, c
```tsx
import { createRootRoute, Outlet } from "@tanstack/react-router"
-import { Toaster } from "@uipath/apollo-vertex"
+import { ThemeProvider, Toaster } from "@uipath/apollo-vertex"
export const Route = createRootRoute({
component: () => (
- <>
+
- >
+
),
})
```
@@ -66,7 +66,7 @@ toast.error("Upload failed", {
| `position` | `ToasterProps["position"]` | `"top-center"` | Position of the toast stack on screen |
| `duration` | `number` | `5000` | Auto-dismiss delay in milliseconds |
| `closeButton` | `boolean` | `true` | Show a close button on each toast |
-| `theme` | `"light" \| "dark" \| "system"` | `"system"` | Color theme. Defaults to the current app theme via `next-themes` |
+| `theme` | `"light" \| "dark" \| "system"` | shell `resolvedTheme` | Ignored. The toaster always uses the shell resolved theme from `ThemeProvider`. |
| `toastOptions` | `ToasterProps["toastOptions"]` | — | Global options applied to all toasts (classNames, style, etc.) |
## Toast function options
diff --git a/apps/apollo-vertex/content/docs/guidelines/notifications/notification-examples.tsx b/apps/apollo-vertex/content/docs/guidelines/notifications/notification-examples.tsx
index 936d8df888..05021079e9 100644
--- a/apps/apollo-vertex/content/docs/guidelines/notifications/notification-examples.tsx
+++ b/apps/apollo-vertex/content/docs/guidelines/notifications/notification-examples.tsx
@@ -13,62 +13,65 @@ import { Alert, AlertDescription, AlertTitle } from "@/registry/alert/alert";
import { Badge } from "@/registry/badge/badge";
import { Button } from "@/registry/button/button";
import { Toaster } from "@/registry/sonner/sonner";
+import { ShellThemeProvider } from "../../../../templates/shell/ShellThemeProvider";
export function SonnerExamples() {
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ Click each button to trigger a toast at the top-center of the screen.
+ Toasts auto-dismiss after 5 seconds.
+
-
- Click each button to trigger a toast at the top-center of the screen.
- Toasts auto-dismiss after 5 seconds.
-
-
+
);
}
diff --git a/packages/apollo-vertex/package.json b/packages/apollo-vertex/package.json
index 52def91279..62fa2115d6 100644
--- a/packages/apollo-vertex/package.json
+++ b/packages/apollo-vertex/package.json
@@ -85,7 +85,6 @@
"input-otp": "^1.4.2",
"lucide-react": "^0.577.0",
"luxon": "^3.7.1",
- "next-themes": "^0.4.6",
"react-day-picker": "^9.13.0",
"react-joyride": "^3.0.2",
"react-resizable-panels": "^4.7.3",
diff --git a/packages/apollo-vertex/registry.json b/packages/apollo-vertex/registry.json
index f211814906..9b06396052 100644
--- a/packages/apollo-vertex/registry.json
+++ b/packages/apollo-vertex/registry.json
@@ -2704,7 +2704,8 @@
"type": "registry:ui",
"title": "Sonner",
"description": "An opinionated toast component for React.",
- "dependencies": ["sonner", "next-themes"],
+ "dependencies": ["sonner"],
+ "registryDependencies": ["@uipath/shell"],
"cssVars": {
"theme": {
"color-info": "var(--info)",
diff --git a/packages/apollo-vertex/registry/shell/shell-theme-provider.tsx b/packages/apollo-vertex/registry/shell/shell-theme-provider.tsx
index d312ab0f1f..ecbca6168d 100644
--- a/packages/apollo-vertex/registry/shell/shell-theme-provider.tsx
+++ b/packages/apollo-vertex/registry/shell/shell-theme-provider.tsx
@@ -72,6 +72,22 @@ function isValidTheme(value: string | null): value is Theme {
return value === "light" || value === "dark" || value === "system";
}
+function useSystemDark() {
+ return useSyncExternalStore(
+ (cb) => {
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
+ mq.addEventListener("change", cb);
+ return () => mq.removeEventListener("change", cb);
+ },
+ () => window.matchMedia("(prefers-color-scheme: dark)").matches,
+ () => false,
+ );
+}
+
+function resolveSystemTheme(systemDark: boolean): "light" | "dark" {
+ return systemDark ? "dark" : "light";
+}
+
function applyThemeClass(resolved: "light" | "dark") {
const root = window.document.documentElement;
root.classList.remove("light", "dark");
@@ -117,18 +133,10 @@ export function ThemeProvider({
return isValidTheme(stored) ? stored : defaultTheme;
});
- const systemDark = useSyncExternalStore(
- (cb) => {
- const mq = window.matchMedia("(prefers-color-scheme: dark)");
- mq.addEventListener("change", cb);
- return () => mq.removeEventListener("change", cb);
- },
- () => window.matchMedia("(prefers-color-scheme: dark)").matches,
- () => false,
- );
+ const systemDark = useSystemDark();
const resolvedTheme =
- theme === "system" ? (systemDark ? "dark" : "light") : theme;
+ theme === "system" ? resolveSystemTheme(systemDark) : theme;
const setTheme = (newTheme: Theme) => {
localStorage.setItem(storageKey, newTheme);
diff --git a/packages/apollo-vertex/registry/sonner/sonner.test.tsx b/packages/apollo-vertex/registry/sonner/sonner.test.tsx
new file mode 100644
index 0000000000..edc0eeeb58
--- /dev/null
+++ b/packages/apollo-vertex/registry/sonner/sonner.test.tsx
@@ -0,0 +1,75 @@
+import { cleanup, fireEvent, render } from "@testing-library/react";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { THEME_STORAGE_KEY } from "@/registry/shell/shell-constants";
+import { ThemeProvider, useTheme } from "@/registry/shell/shell-theme-provider";
+
+import { Toaster } from "./sonner";
+
+vi.mock("sonner", () => ({
+ Toaster: ({ theme }: { theme?: string }) => (
+
+ ),
+}));
+
+const darkLabel = "use dark";
+
+function installMatchMedia(matches: boolean) {
+ Object.defineProperty(window, "matchMedia", {
+ writable: true,
+ configurable: true,
+ value: () => ({
+ matches,
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ }),
+ });
+}
+
+function ThemeToggle() {
+ const { setTheme } = useTheme();
+ return (
+
+ );
+}
+
+describe("Toaster", () => {
+ beforeEach(() => {
+ installMatchMedia(false);
+ localStorage.clear();
+ });
+
+ afterEach(() => {
+ cleanup();
+ localStorage.clear();
+ });
+
+ it("passes the shell resolved theme to Sonner", () => {
+ const { getByRole, getByTestId } = render(
+
+
+
+ ,
+ );
+
+ expect(getByTestId("sonner").dataset.theme).toBe("light");
+
+ fireEvent.click(getByRole("button", { name: darkLabel }));
+
+ expect(getByTestId("sonner").dataset.theme).toBe("dark");
+ });
+
+ it("resolves a system shell theme to light or dark", () => {
+ installMatchMedia(true);
+ localStorage.setItem(THEME_STORAGE_KEY, "system");
+
+ const { getByTestId } = render(
+
+
+ ,
+ );
+
+ expect(getByTestId("sonner").dataset.theme).toBe("dark");
+ });
+});
diff --git a/packages/apollo-vertex/registry/sonner/sonner.tsx b/packages/apollo-vertex/registry/sonner/sonner.tsx
index 0f4e7af995..2a73a7c22f 100644
--- a/packages/apollo-vertex/registry/sonner/sonner.tsx
+++ b/packages/apollo-vertex/registry/sonner/sonner.tsx
@@ -7,9 +7,10 @@ import {
OctagonXIcon,
TriangleAlertIcon,
} from "lucide-react";
-import { useTheme } from "next-themes";
import { Toaster as Sonner, type ToasterProps } from "sonner";
+import { useTheme } from "@/registry/shell/shell-theme-provider";
+
// Shared icon alignment: top-align icons with the first line of toast text.
const iconAlign = "[&>[data-icon]]:!items-start [&>[data-icon]>svg]:!mt-0.5";
@@ -35,16 +36,12 @@ export const apolloToastClassNames: NonNullable<
"!left-auto !right-2 !top-2 !transform-none !border-none !bg-transparent !rounded-sm",
};
-const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme();
+const Toaster = ({ theme: _theme, ...props }: ToasterProps) => {
+ const { resolvedTheme } = useTheme();
return (