diff --git a/apps/apollo-vertex/content/docs/patterns/shell.mdx b/apps/apollo-vertex/content/docs/patterns/shell.mdx
index 51af506f4..ba6e070f7 100644
--- a/apps/apollo-vertex/content/docs/patterns/shell.mdx
+++ b/apps/apollo-vertex/content/docs/patterns/shell.mdx
@@ -11,14 +11,6 @@ import { PreviewFullScreen } from '../../../app/_components/preview-full-screen'
-## Minimal Header Variant
-
-Use the `variant="minimal"` prop to render a horizontal header layout instead of the default sidebar.
-
-
-
-
-
## Features
- **Optional OAuth2 Authentication**: Plug-and-play authorization code flow with PKCE
@@ -52,12 +44,49 @@ function App() {
}
```
+### Nested navigation
+
+A group sets `path` for the prefix and `to` for the link. This preview opens on Active. That child has the current background. Projects stays in the active color for `/preview/shell/projects` and the routes under it, and does not take that background.
+
+
+
+
+
+```tsx
+import { FolderOpen } from 'lucide-react';
+
+const projects = {
+ path: '/preview/shell/projects',
+ to: '/preview/shell/projects/active',
+ label: 'projects',
+ icon: FolderOpen,
+ subItems: [
+ { path: '/preview/shell/projects/active', label: 'active' },
+ { path: '/preview/shell/projects/archive', label: 'archive' },
+ ],
+};
+```
+
+Labels are translation keys. A key in the app's default catalog is used first, including keys that are not in Apollo Vertex. Vertex keys such as `dashboard` still resolve when the app catalog does not define them.
+
+### App translations
+
+Pass `configureI18n` to `ApolloShell` or `LocaleProvider` when the app initializes i18next itself. The shell then runs that function instead of replacing the app catalog with the Vertex one.
+
+### Minimal header
+
+Use `variant="minimal"` for a horizontal header instead of the sidebar. The header highlights only the most specific matching path, so a landing page is not selected together with a section under it. A nested page still highlights that section. `to` is only the link target.
+
+
+
+
+
## Adding Authentication
To enable built-in OAuth2 authentication with PKCE, wrap the shell with `ShellAuthProvider`.
Both `ShellAuthProvider` and `ApolloShell` must be inside a `QueryClientProvider` from `@tanstack/react-query`.
-When a `ShellAuthProvider` is present, the shell automatically shows a login screen until the user is authenticated.
+When a `ShellAuthProvider` is present, the shell automatically shows a login screen until the user is authenticated. While the token is still loading, it shows the loading skeleton instead of that login screen.
```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
@@ -166,7 +195,7 @@ Names that don't resolve are denied. Requires `@uipath/vs-core` 4.4.0 or later.
```
-## Resolving group ids by role
+### Resolving group ids by role
`GroupMembershipGuard` and `useIsGroupMember` need group ids, but applications
typically reason in terms of roles whose backing group names are known ahead of
diff --git a/apps/apollo-vertex/templates/shell/ShellRoot.tsx b/apps/apollo-vertex/templates/shell/ShellRoot.tsx
index 0eedb8f70..1c78561c7 100644
--- a/apps/apollo-vertex/templates/shell/ShellRoot.tsx
+++ b/apps/apollo-vertex/templates/shell/ShellRoot.tsx
@@ -6,7 +6,16 @@ import { ShellThemeProvider } from "./ShellThemeProvider";
const navItems: ShellNavItem[] = [
{ path: "/preview/shell/dashboard", label: "dashboard", icon: Home },
- { path: "/preview/shell/projects", label: "projects", icon: FolderOpen },
+ {
+ path: "/preview/shell/projects",
+ to: "/preview/shell/projects/active",
+ label: "projects",
+ icon: FolderOpen,
+ subItems: [
+ { path: "/preview/shell/projects/active", label: "active" },
+ { path: "/preview/shell/projects/archive", label: "archive" },
+ ],
+ },
{ path: "/preview/shell/analytics", label: "analytics", icon: BarChart3 },
{ path: "/preview/shell/team", label: "team", icon: Users },
{ path: "/preview/shell/settings", label: "settings", icon: Settings },
diff --git a/apps/apollo-vertex/templates/shell/ShellRoutes.tsx b/apps/apollo-vertex/templates/shell/ShellRoutes.tsx
index 03fbbc8bb..601a36cfe 100644
--- a/apps/apollo-vertex/templates/shell/ShellRoutes.tsx
+++ b/apps/apollo-vertex/templates/shell/ShellRoutes.tsx
@@ -39,6 +39,18 @@ export const shellProjectsRoute = createRoute({
component: () => ,
});
+export const shellProjectsActiveRoute = createRoute({
+ getParentRoute: () => shellRoute,
+ path: "/projects/active",
+ component: () => ,
+});
+
+export const shellProjectsArchiveRoute = createRoute({
+ getParentRoute: () => shellRoute,
+ path: "/projects/archive",
+ component: () => ,
+});
+
export const shellAnalyticsRoute = createRoute({
getParentRoute: () => shellRoute,
path: "/analytics",
diff --git a/apps/apollo-vertex/templates/shell/ShellTemplate.tsx b/apps/apollo-vertex/templates/shell/ShellTemplate.tsx
index 8dbc2022d..d3a841a67 100644
--- a/apps/apollo-vertex/templates/shell/ShellTemplate.tsx
+++ b/apps/apollo-vertex/templates/shell/ShellTemplate.tsx
@@ -22,6 +22,8 @@ import {
shellDashboardRoute,
shellIndexRoute,
shellInvoiceDetailRoute,
+ shellProjectsActiveRoute,
+ shellProjectsArchiveRoute,
shellProjectsRoute,
shellRoute,
shellSettingsRoute,
@@ -31,6 +33,7 @@ import { rootRoute } from "./ShellRouteTree";
export interface ShellTemplateProps {
variant?: "minimal";
+ initialPath?: string;
}
const SHELL_PREVIEW_PATH_KEY = "shell-preview-path";
@@ -54,6 +57,8 @@ const routeTree = rootRoute.addChildren([
shellDashboardRoute,
shellInvoiceDetailRoute,
shellProjectsRoute,
+ shellProjectsActiveRoute,
+ shellProjectsArchiveRoute,
shellAnalyticsRoute,
shellTeamRoute,
shellSettingsRoute,
@@ -68,7 +73,12 @@ const routeTree = rootRoute.addChildren([
catchAllRoute,
]);
-function getInitialEntry(storageKey: ShellPreviewPathKey, variant?: "minimal") {
+function getInitialEntry(
+ storageKey: ShellPreviewPathKey,
+ variant?: "minimal",
+ initialPath?: string,
+) {
+ if (initialPath) return initialPath;
const stored = localStorage.getItem(storageKey);
if (stored) return stored;
return variant === "minimal" ? "/preview/shell-minimal" : "/preview/shell";
@@ -77,26 +87,30 @@ function getInitialEntry(storageKey: ShellPreviewPathKey, variant?: "minimal") {
function createShellRouter(
storageKey: ShellPreviewPathKey,
variant?: "minimal",
+ initialPath?: string,
) {
const history = createMemoryHistory({
- initialEntries: [getInitialEntry(storageKey, variant)],
+ initialEntries: [getInitialEntry(storageKey, variant, initialPath)],
});
return createRouter({ routeTree, history });
}
-export function ShellTemplate({ variant }: ShellTemplateProps) {
+export function ShellTemplate({ variant, initialPath }: ShellTemplateProps) {
const storageKey =
variant === "minimal"
? SHELL_MINIMAL_PREVIEW_PATH_KEY
: SHELL_PREVIEW_PATH_KEY;
- const [router] = useState(() => createShellRouter(storageKey, variant));
+ const [router] = useState(() =>
+ createShellRouter(storageKey, variant, initialPath),
+ );
useEffect(() => {
+ if (initialPath) return;
const unsubscribe = router.subscribe("onResolved", ({ toLocation }) => {
localStorage.setItem(storageKey, toLocation.pathname);
});
return unsubscribe;
- }, [router, storageKey]);
+ }, [router, storageKey, initialPath]);
return (
diff --git a/packages/apollo-vertex/locales/en.json b/packages/apollo-vertex/locales/en.json
index 13e05ef3f..86086d6c6 100644
--- a/packages/apollo-vertex/locales/en.json
+++ b/packages/apollo-vertex/locales/en.json
@@ -3,6 +3,7 @@
"access_denied": "Access Denied",
"access_denied_description": "You don't have access to this application. Ask your administrator to add you to the users group.",
"actions": "Actions",
+ "active": "Active",
"actual_input": "Actual input",
"actual_output": "Actual output",
"add_attachment": "Add attachment",
@@ -24,6 +25,7 @@
"analytics": "Analytics",
"apollo_vertex": "Apollo Vertex",
"apollo_vertex_description": "- UiPath Apollo Vertex",
+ "archive": "Archive",
"are_you_sure": "Are you sure?",
"asc": "Asc",
"ask_ai": "Ask AI",
@@ -186,6 +188,8 @@
"more_pages": "More pages",
"multi_line_too_few_metrics": "Multi-line needs two distinct metrics, but the request resolved to fewer than two.",
"my_template": "My Template",
+ "nav_collapse": "Collapse {{label}}",
+ "nav_expand": "Expand {{label}}",
"need_help": "Need help? Contact your administrator",
"new_conversation": "New conversation",
"new_conversation_confirm_description": "This will clear all messages and cannot be undone.",
diff --git a/packages/apollo-vertex/registry.json b/packages/apollo-vertex/registry.json
index 64fca8247..239949608 100644
--- a/packages/apollo-vertex/registry.json
+++ b/packages/apollo-vertex/registry.json
@@ -2406,6 +2406,14 @@
"path": "registry/shell/shell-sidebar.tsx",
"type": "registry:ui"
},
+ {
+ "path": "registry/shell/shell-nav-active.ts",
+ "type": "registry:ui"
+ },
+ {
+ "path": "registry/shell/shell-nav-label.ts",
+ "type": "registry:ui"
+ },
{
"path": "registry/shell/shell-company.tsx",
"type": "registry:ui"
diff --git a/packages/apollo-vertex/registry/shell/shell-company.tsx b/packages/apollo-vertex/registry/shell/shell-company.tsx
index 28eb02120..ea0cd790c 100644
--- a/packages/apollo-vertex/registry/shell/shell-company.tsx
+++ b/packages/apollo-vertex/registry/shell/shell-company.tsx
@@ -2,7 +2,6 @@ import { Link } from "@tanstack/react-router";
import { AnimatePresence, motion } from "framer-motion";
import { PanelLeft } from "lucide-react";
import { useState } from "react";
-import { useVertexTranslation } from "@/lib/vertex-translation";
import { Button } from "@/components/ui/button";
import { useSidebar } from "@/components/ui/sidebar";
import {
@@ -12,6 +11,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
+import { useVertexTranslation } from "@/lib/vertex-translation";
import type { CompanyLogo } from "./shell";
import {
fastFadeTransition,
@@ -187,6 +187,7 @@ export const Company = ({