fix: put Sessions back in the middle of nav on desktop and mobile

- Reorder desktop sidebar so Sessions sits between spaces/tools groups:
  Home, Projects, Workspaces, Sessions, Tool Workshop, Config Profiles, Settings.
- Reorder mobile bottom nav so Sessions is the center item:
  Home, Spaces, Sessions, Tools, Settings.
- Workspace cards already display the owning project name; no extra change needed.

Quality gates: npm run typecheck, npm run lint clean,
npm test -- --run 87 passed.
This commit is contained in:
Developer
2026-06-13 19:44:47 +00:00
parent 4ced6141ae
commit 1d09193652
19 changed files with 321 additions and 293 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src/components
## role
Provides foundational, reusable React UI components and utilities for rendering layouts, interactive elements, data states, icons, code editing, routing guards, and toast notifications across the web application.
Provides reusable, accessible React UI components and utilities for the web application including layout shell, data states, icons, code display, authentication guards, and toast notifications.
## parent
index: apps/web/src/.pi-map.index.md
map: apps/web/src/.pi-map.md
+3 -3
View File
@@ -4,9 +4,9 @@ dir: apps/web/src/components
index: apps/web/src/components/.pi-map.index.md
## role
Provides foundational, reusable React UI components and utilities for rendering layouts, interactive elements, data states, icons, code editing, routing guards, and toast notifications across the web application.
Provides reusable, accessible React UI components and utilities for the web application including layout shell, data states, icons, code display, authentication guards, and toast notifications.
## files
- app-shell.tsx | Renders the main application shell layout with navigation, session management, and responsive mobile/desktop views for a React Router-based app. | exp: AppShell | dep: react-router-dom, ../api/sessions, ../hooks/use-theme, ../state/auth, ../state/sessions, ../hooks/use-mobile-viewport, ../state/events, ../state/toast, ../state/notifications, ./features/notification/event-toast-bridge, ./features/notification/notification-center, ./icon, ./features/mobile/mobile-nav, ./features/tool/start-tool-fab, ../utils/icons
- app-shell.tsx | Renders the main application shell layout with navigation, session management, and responsive mobile/desktop views for a React Router-based application. | exp: AppShell | dep: react-router-dom, ../api/sessions, ../hooks/use-theme, ../state/auth, ../state/sessions, ../hooks/use-mobile-viewport, ../state/events, ../state/toast, ../state/notifications, ./features/notification/event-toast-bridge, ./features/notification/notification-center, ./icon, ./features/mobile/mobile-nav, ./features/tool/start-tool-fab, ../utils/icons
- code-editor.tsx | A React component that renders a syntax-highlighted code editor with line numbers using react-simple-code-editor. | exp: CodeEditor | dep: react, react-simple-code-editor, ../utils/language
- data-states.tsx | Provides reusable React components for displaying loading, error, and empty data states in a UI. | exp: LoadingState, ErrorState, EmptyState | dep: ./icon, icon
- icon.tsx | Provides a centralized, type-safe React icon component that maps semantic names to Phosphor icons with configurable size, weight, color, and accessibility attributes. | exp: IconName, IconProps, Icon | dep: react, @phosphor-icons/react
@@ -17,7 +17,7 @@ Provides foundational, reusable React UI components and utilities for rendering
- toast-rules.test.ts | Unit tests for mapping instance events to toast notification categories and severities | dep: vitest, ./toast-rules, ../types/events
- toast-rules.ts | Maps instance events to toast notifications with deduplication logic to prevent spam | exp: func:mapEventToCategory(event: InstanceEventPayload) → string, call:event.event.startsWith, func:mapEventToSeverity(event: InstanceEventPayload) → "info" | "warning" | "error" | "success", func:handleEventToast(event: InstanceEventPayload) → void, call:shouldShowToast, call:toast.info, call:toast.success, call:toast.warning, call:toast.error, func:clearToastDedup() → void, call:lastToastTime.clear | dep: ../state/toast, ../types/events, toast state module, InstanceEventPayload type
## arch
Component-based architecture with functional React patterns, custom hooks for state management, centralized icon mapping with type safety, HOC-like route guards, and utility modules for cross-cutting concerns (toast rules, data states) with co-located test files.
Component-based architecture with functional React components, custom hooks for state management, centralized icon mapping with type safety, test-driven development for business logic (toast rules, protected routes), and separation of concerns between presentation (UI components) and behavior (utility modules).
## tags
toast, icon, react, state, code, loading, event, editor
## symbols
+146 -146
View File
@@ -16,166 +16,166 @@ import { StartToolFAB } from "./features/tool/start-tool-fab";
import type { IconName } from "../utils/icons";
const NAV_ITEMS: {
to: string;
label: string;
icon: IconName;
badge?: "sessions";
to: string;
label: string;
icon: IconName;
badge?: "sessions";
}[] = [
{ to: "/", label: "Home", icon: "dashboard" },
{ to: "/sessions", label: "Sessions", icon: "terminal", badge: "sessions" },
{ to: "/workspaces", label: "Workspaces", icon: "folder" },
{ to: "/projects", label: "Projects", icon: "projects" },
{ to: "/tool-workshop", label: "Tool Workshop", icon: "settings" },
{ to: "/config-profiles", label: "Config Profiles", icon: "folder" },
{ to: "/settings", label: "Settings", icon: "settings" },
{ to: "/", label: "Home", icon: "dashboard" },
{ to: "/projects", label: "Projects", icon: "projects" },
{ to: "/workspaces", label: "Workspaces", icon: "folder" },
{ to: "/sessions", label: "Sessions", icon: "terminal", badge: "sessions" },
{ to: "/tool-workshop", label: "Tool Workshop", icon: "settings" },
{ to: "/config-profiles", label: "Config Profiles", icon: "folder" },
{ to: "/settings", label: "Settings", icon: "settings" },
];
const SessionItem = ({ session }: { session: Session }) => {
const isRunning = session.status === "running";
const isRunning = session.status === "running";
// Determine the link target:
// - Web tools open their tunnel URL
// - Terminal tools open the terminal page
// - Everything else falls back to the project page
const hasTerminal = session.tool_type_interfaces.includes("terminal");
const hasWeb = session.tool_type_interfaces.includes("web");
const href =
session.url && hasWeb
? session.url
: hasTerminal
? `/instances/${session.id}/terminal`
: `/projects/${session.project_id}`;
// Determine the link target:
// - Web tools open their tunnel URL
// - Terminal tools open the terminal page
// - Everything else falls back to the project page
const hasTerminal = session.tool_type_interfaces.includes("terminal");
const hasWeb = session.tool_type_interfaces.includes("web");
const href =
session.url && hasWeb
? session.url
: hasTerminal
? `/instances/${session.id}/terminal`
: `/projects/${session.project_id}`;
const tooltipParts = [session.display_name, session.project_name];
if (session.workspace_name) tooltipParts.push(session.workspace_name);
else if (session.repository_name) tooltipParts.push(session.repository_name);
tooltipParts.push(`(${session.status})`);
const tooltipParts = [session.display_name, session.project_name];
if (session.workspace_name) tooltipParts.push(session.workspace_name);
else if (session.repository_name) tooltipParts.push(session.repository_name);
tooltipParts.push(`(${session.status})`);
return (
<a
href={href}
target={`session-${session.id}`}
rel="noreferrer"
className="nav-item session-item"
title={tooltipParts.join(" ")}
>
<span className={`session-status ${isRunning ? "running" : ""}`} />
<Icon name={session.tool_icon as IconName} size="sm" />
<span className="session-name">
{session.display_name}
<span className="session-tool">{session.tool_type_name}</span>
</span>
</a>
);
return (
<a
href={href}
target={`session-${session.id}`}
rel="noreferrer"
className="nav-item session-item"
title={tooltipParts.join(" ")}
>
<span className={`session-status ${isRunning ? "running" : ""}`} />
<Icon name={session.tool_icon as IconName} size="sm" />
<span className="session-name">
{session.display_name}
<span className="session-tool">{session.tool_type_name}</span>
</span>
</a>
);
};
export const AppShell = () => {
useTheme();
const { user, logout } = useAuth();
const { sessions } = useSessions();
const location = useLocation();
const isMobile = useMobileViewport();
const isMobileTerminal =
isMobile &&
location.pathname.includes("/instances/") &&
location.pathname.includes("/terminal");
useTheme();
const { user, logout } = useAuth();
const { sessions } = useSessions();
const location = useLocation();
const isMobile = useMobileViewport();
const isMobileTerminal =
isMobile &&
location.pathname.includes("/instances/") &&
location.pathname.includes("/terminal");
if (isMobileTerminal) {
return (
<EventProvider>
<ToastProvider>
<NotificationProvider>
<EventToastBridge />
<div className="shell mobile-terminal-shell">
<Outlet />
</div>
</NotificationProvider>
</ToastProvider>
</EventProvider>
);
}
if (isMobileTerminal) {
return (
<EventProvider>
<ToastProvider>
<NotificationProvider>
<EventToastBridge />
<div className="shell mobile-terminal-shell">
<Outlet />
</div>
</NotificationProvider>
</ToastProvider>
</EventProvider>
);
}
return (
<EventProvider>
<ToastProvider>
<NotificationProvider>
<EventToastBridge />
<div className="shell">
<header className="shell-header">
<Link className="brand" to="/">
Headquarter
</Link>
<div className="header-actions">
<NotificationCenter isMobileTerminal={isMobileTerminal} />
<Link className="user-chip" to="/profile">
{user?.name ?? "User"}
</Link>
<button
className="ghost-button"
onClick={() => {
void logout();
}}
type="button"
>
<Icon name="logout" size="sm" />
Logout
</button>
</div>
</header>
return (
<EventProvider>
<ToastProvider>
<NotificationProvider>
<EventToastBridge />
<div className="shell">
<header className="shell-header">
<Link className="brand" to="/">
Headquarter
</Link>
<div className="header-actions">
<NotificationCenter isMobileTerminal={isMobileTerminal} />
<Link className="user-chip" to="/profile">
{user?.name ?? "User"}
</Link>
<button
className="ghost-button"
onClick={() => {
void logout();
}}
type="button"
>
<Icon name="logout" size="sm" />
Logout
</button>
</div>
</header>
<div className="shell-body">
{!isMobile && (
<aside className="shell-nav" aria-label="Primary navigation">
{NAV_ITEMS.map((item) => {
const activeCount = sessions.filter(
(s) => s.status === "running",
).length;
return (
<NavLink
key={item.to}
to={item.to}
className={({ isActive }) =>
isActive ? "nav-item nav-item-active" : "nav-item"
}
end={item.to === "/"}
>
<Icon name={item.icon} size="sm" />
{item.label}
{item.badge === "sessions" && activeCount > 0 && (
<span className="nav-badge">{activeCount}</span>
)}
</NavLink>
);
})}
<div className="shell-body">
{!isMobile && (
<aside className="shell-nav" aria-label="Primary navigation">
{NAV_ITEMS.map((item) => {
const activeCount = sessions.filter(
(s) => s.status === "running",
).length;
return (
<NavLink
key={item.to}
to={item.to}
className={({ isActive }) =>
isActive ? "nav-item nav-item-active" : "nav-item"
}
end={item.to === "/"}
>
<Icon name={item.icon} size="sm" />
{item.label}
{item.badge === "sessions" && activeCount > 0 && (
<span className="nav-badge">{activeCount}</span>
)}
</NavLink>
);
})}
{sessions.length > 0 && (
<>
<div className="nav-divider" />
<div className="nav-section-title">Live sessions</div>
{sessions.map((session) => (
<SessionItem key={session.id} session={session} />
))}
</>
)}
</aside>
)}
{sessions.length > 0 && (
<>
<div className="nav-divider" />
<div className="nav-section-title">Live sessions</div>
{sessions.map((session) => (
<SessionItem key={session.id} session={session} />
))}
</>
)}
</aside>
)}
<main className={`shell-content ${isMobile ? "mobile" : ""}`}>
<Outlet />
</main>
</div>
<main className={`shell-content ${isMobile ? "mobile" : ""}`}>
<Outlet />
</main>
</div>
{isMobile && (
<MobileNav
sessionCount={
sessions.filter((s) => s.status === "running").length
}
/>
)}
<StartToolFAB />
</div>
</NotificationProvider>
</ToastProvider>
</EventProvider>
);
{isMobile && (
<MobileNav
sessionCount={
sessions.filter((s) => s.status === "running").length
}
/>
)}
<StartToolFAB />
</div>
</NotificationProvider>
</ToastProvider>
</EventProvider>
);
};
@@ -2,7 +2,7 @@
dir: apps/web/src/components/features
## role
Contains reusable UI components organized by feature domain for the web application.
Contains reusable React components organized by feature domains for the web application UI.
## parent
index: apps/web/src/components/.pi-map.index.md
map: apps/web/src/components/.pi-map.md
+2 -2
View File
@@ -4,10 +4,10 @@ dir: apps/web/src/components/features
index: apps/web/src/components/features/.pi-map.index.md
## role
Contains reusable UI components organized by feature domain for the web application.
Contains reusable React components organized by feature domains for the web application UI.
## files
## arch
Feature-based colocation pattern with domain-specific subdirectories grouping related components, likely using composition patterns with shared UI primitives.
Feature-based component colocation with domain-driven folder structure, separating UI concerns by business capability rather than technical layer.
## tags
-
## symbols
@@ -2,7 +2,7 @@
dir: apps/web/src/components/features/mobile
## role
Provides a complete set of mobile-optimized UI components for core application patterns including navigation, lists, detail views, editing, modals, and specialized terminal interfaces.
Provides mobile-optimized UI components for core application features including navigation, data views, forms, modals, and specialized terminal interfaces.
## parent
index: apps/web/src/components/features/.pi-map.index.md
map: apps/web/src/components/features/.pi-map.md
@@ -4,22 +4,22 @@ dir: apps/web/src/components/features/mobile
index: apps/web/src/components/features/mobile/.pi-map.index.md
## role
Provides a complete set of mobile-optimized UI components for core application patterns including navigation, lists, detail views, editing, modals, and specialized terminal interfaces.
Provides mobile-optimized UI components for core application features including navigation, data views, forms, modals, and specialized terminal interfaces.
## files
- mobile-action-sheet.tsx | Renders a mobile-optimized action sheet modal with title, configurable action buttons, and cancel option. | exp: MobileActionSheetItem, func:MobileActionSheet({ isOpen, onClose, title, actions, }: MobileActionSheetProps), call:useRef, call:useEffect, call:onClose, call:document.addEventListener, call:document.removeEventListener, call:e.stopPropagation, call:actions.map, call:action.onClick | dep: react, ../../icon, icon
- mobile-detail-view.tsx | A React component that renders a mobile-optimized detail view with a header (back button, title, edit/delete actions) and a field list supporting multiple value types (text, code, JSON, boolean). | exp: MobileDetailView | dep: ../../icon, react
- mobile-edit-view.tsx | A React component that renders a mobile-optimized form for editing data with configurable field types and save/cancel actions. | exp: MobileEditView | dep: react
- mobile-fab.tsx | Renders a floating action button component for mobile with an add icon and configurable click handler and label. | exp: MobileFAB | dep: ../../icon, react, icon
- mobile-list-view.tsx | Renders a mobile-optimized list view with optional search, custom item rendering, and empty state handling | exp: MobileListView | dep: react, ../../icon, ../../../utils/icons, icon
- mobile-nav.tsx | Renders a mobile navigation bar with route links and expandable bottom sheets for grouped navigation items. | exp: MobileNav | dep: react, react-router-dom, ../../icon, ../tool/tools-bottom-sheet, ./spaces-bottom-sheet, ../../../utils/icons
- mobile-list-view.tsx | Renders a mobile-optimized list view with optional search, empty state, and customizable item rendering | exp: MobileListView | dep: react, ../../icon, ../../../utils/icons, icon
- mobile-nav.tsx | Renders a mobile navigation bar with grouped items that open bottom sheets and standard items that use React Router links, including active state indicators and session count badges. | exp: MobileNav | dep: react, react-router-dom, ../../icon, ../tool/tools-bottom-sheet, ./spaces-bottom-sheet, ../../../utils/icons
- mobile-page-header.tsx | Renders a mobile-only page header with optional back navigation and custom actions. | exp: func:MobilePageHeader({ title, showBack = true, actions }: MobilePageHeaderProps), call:useNavigate, call:useMobileViewport, call:navigate | dep: react-router-dom, ../../../hooks/use-mobile-viewport, ../../icon, use-mobile-viewport hook, Icon component
- mobile-terminal-header.tsx | Renders a mobile-responsive header for a terminal interface with navigation, title, connection status, font size controls, and close actions. | exp: MobileTerminalHeader | dep: react, ../../icon, icon
- mobile-terminal-wrapper.tsx | Wraps a terminal component with mobile-specific UI including auto-hiding header, virtual keyboard handling, and special keys interface. | exp: MobileTerminalWrapper | dep: react, ../terminal/terminal, ./mobile-terminal-header, ../terminal/special-keys-strip, ../terminal/special-keys-panel, ../../../hooks/use-mobile-viewport, ../../../hooks/use-virtual-keyboard, ../../../hooks/use-auto-hide, ../../../hooks/use-special-keys
- spaces-bottom-sheet.tsx | Renders a mobile bottom sheet navigation menu for switching between "Projects" and "Workspaces" spaces with active state highlighting | exp: SpacesBottomSheet | dep: react-router-dom, ../../icon
- spaces-bottom-sheet.tsx | Renders a mobile bottom sheet navigation menu for switching between "Projects" and "Workspaces" spaces with active state highlighting. | exp: SpacesBottomSheet | dep: react-router-dom, ../../icon, icon
## arch
Composable mobile-first component library using bottom sheets, action sheets, and floating action buttons as primary mobile interaction patterns, with feature-specific wrappers that handle mobile concerns like virtual keyboards and auto-hiding headers.
Compositional React component library with feature-specific mobile adaptations, using bottom sheets/action sheets for mobile-native UX patterns, and wrapper components that inject mobile-specific behaviors (auto-hiding headers, virtual keyboard handling) into existing features.
## tags
mobile, terminal, sheet, view, react, icon, header, renders
mobile, terminal, sheet, react, icon, view, header, renders
## symbols
- MobileActionSheet
- MobilePageHeader
@@ -3,81 +3,86 @@ import { Icon } from "../../icon";
import type { IconName } from "../../../utils/icons";
interface MobileListItem {
id: string;
title: string;
subtitle?: string;
icon?: string;
status?: string;
id: string;
title: string;
subtitle?: string;
icon?: string;
status?: string;
}
interface MobileListViewProps {
items: MobileListItem[];
onItemClick: (id: string) => void;
onItemDelete?: (id: string) => void;
onItemDuplicate?: (id: string) => void;
emptyMessage?: string;
searchPlaceholder?: string;
onSearch?: (query: string) => void;
renderItem?: (item: MobileListItem) => ReactNode;
items: MobileListItem[];
onItemClick: (id: string) => void;
onItemDelete?: (id: string) => void;
onItemDuplicate?: (id: string) => void;
emptyMessage?: string;
searchPlaceholder?: string;
onSearch?: (query: string) => void;
renderItem?: (item: MobileListItem) => ReactNode;
}
export const MobileListView: React.FC<MobileListViewProps> = ({
items,
onItemClick,
emptyMessage = "No items found",
searchPlaceholder = "Search...",
onSearch,
renderItem,
items,
onItemClick,
emptyMessage = "No items found",
searchPlaceholder = "Search...",
onSearch,
renderItem,
}) => {
return (
<div className="mobile-list-view">
{onSearch && (
<div className="mobile-list-search">
<input
type="search"
placeholder={searchPlaceholder}
onChange={(e) => onSearch(e.target.value)}
className="mobile-list-search-input"
/>
</div>
)}
return (
<div className="mobile-list-view">
{onSearch && (
<div className="mobile-list-search">
<input
type="search"
placeholder={searchPlaceholder}
onChange={(e) => onSearch(e.target.value)}
className="mobile-list-search-input"
/>
</div>
)}
{items.length === 0 ? (
<div className="mobile-list-empty">
<Icon name="folder" size="lg" />
<p>{emptyMessage}</p>
</div>
) : (
<div className="mobile-list-items">
{items.map((item) => (
<button
key={item.id}
className="mobile-list-item"
onClick={() => onItemClick(item.id)}
type="button"
>
{item.icon && (
<div className="mobile-list-item-icon">
<Icon name={item.icon as IconName} size="md" />
</div>
)}
{renderItem ? (
renderItem(item)
) : (
<div className="mobile-list-item-content">
<div className="mobile-list-item-title">{item.title}</div>
{item.subtitle && (
<div className="mobile-list-item-subtitle">{item.subtitle}</div>
)}
</div>
)}
<div className="mobile-list-item-actions" style={{ transform: "rotate(180deg)" }}>
<Icon name="arrow-left" size="sm" />
</div>
</button>
))}
</div>
)}
</div>
);
{items.length === 0 ? (
<div className="mobile-list-empty">
<Icon name="folder" size="lg" />
<p>{emptyMessage}</p>
</div>
) : (
<div className="mobile-list-items">
{items.map((item) => (
<button
key={item.id}
className="mobile-list-item"
onClick={() => onItemClick(item.id)}
type="button"
>
{item.icon && (
<div className="mobile-list-item-icon">
<Icon name={item.icon as IconName} size="md" />
</div>
)}
{renderItem ? (
renderItem(item)
) : (
<div className="mobile-list-item-content">
<div className="mobile-list-item-title">{item.title}</div>
{item.subtitle && (
<div className="mobile-list-item-subtitle">
{item.subtitle}
</div>
)}
</div>
)}
<div
className="mobile-list-item-actions"
style={{ transform: "rotate(180deg)" }}
>
<Icon name="arrow-left" size="sm" />
</div>
</button>
))}
</div>
)}
</div>
);
};
@@ -19,9 +19,21 @@ interface NavItem {
const MOBILE_NAV_ITEMS: NavItem[] = [
{ to: "/", label: "Home", icon: "dashboard" },
{
to: "/spaces",
label: "Spaces",
icon: "projects",
isGroup: true,
group: "spaces",
},
{ to: "/sessions", label: "Sessions", icon: "terminal" },
{ to: "/spaces", label: "Spaces", icon: "projects", isGroup: true, group: "spaces" },
{ to: "/tools", label: "Tools", icon: "settings", isGroup: true, group: "tools" },
{
to: "/tools",
label: "Tools",
icon: "settings",
isGroup: true,
group: "tools",
},
{ to: "/settings", label: "Settings", icon: "settings" },
];
@@ -49,10 +61,15 @@ export const MobileNav: React.FC<MobileNavProps> = ({ sessionCount }) => {
return (
<>
<nav className="mobile-nav" role="navigation" aria-label="Mobile navigation">
<nav
className="mobile-nav"
role="navigation"
aria-label="Mobile navigation"
>
{MOBILE_NAV_ITEMS.map((item) => {
if (item.isGroup) {
const isActive = item.group === "spaces" ? isSpacesActive : isToolsActive;
const isActive =
item.group === "spaces" ? isSpacesActive : isToolsActive;
return (
<button
key={item.to}
@@ -2,61 +2,65 @@ import { useLocation, useNavigate } from "react-router-dom";
import { Icon } from "../../icon";
interface SpacesBottomSheetProps {
isOpen: boolean;
onClose: () => void;
isOpen: boolean;
onClose: () => void;
}
const SPACES_ITEMS = [
{ to: "/projects", label: "Projects" },
{ to: "/workspaces", label: "Workspaces" },
{ to: "/projects", label: "Projects" },
{ to: "/workspaces", label: "Workspaces" },
];
export const SpacesBottomSheet: React.FC<SpacesBottomSheetProps> = ({
isOpen,
onClose,
isOpen,
onClose,
}) => {
const location = useLocation();
const navigate = useNavigate();
const location = useLocation();
const navigate = useNavigate();
if (!isOpen) return null;
if (!isOpen) return null;
const handleSelect = (to: string) => {
onClose();
navigate(to);
};
const handleSelect = (to: string) => {
onClose();
navigate(to);
};
return (
<div
className="mobile-bottom-sheet-overlay"
onClick={onClose}
role="presentation"
>
<div
className="mobile-bottom-sheet"
onClick={(e) => e.stopPropagation()}
role="dialog"
aria-label="Spaces menu"
>
<div className="mobile-bottom-sheet-header">
<div className="mobile-bottom-sheet-handle" />
<h3 className="mobile-bottom-sheet-title">Spaces</h3>
</div>
<div className="mobile-bottom-sheet-content">
{SPACES_ITEMS.map((item) => (
<button
key={item.to}
className={`mobile-bottom-sheet-item ${
location.pathname === item.to ? "active" : ""
}`}
onClick={() => handleSelect(item.to)}
type="button"
>
<span className="mobile-bottom-sheet-item-label">{item.label}</span>
{location.pathname === item.to && <Icon name="success" size="sm" />}
</button>
))}
</div>
</div>
</div>
);
return (
<div
className="mobile-bottom-sheet-overlay"
onClick={onClose}
role="presentation"
>
<div
className="mobile-bottom-sheet"
onClick={(e) => e.stopPropagation()}
role="dialog"
aria-label="Spaces menu"
>
<div className="mobile-bottom-sheet-header">
<div className="mobile-bottom-sheet-handle" />
<h3 className="mobile-bottom-sheet-title">Spaces</h3>
</div>
<div className="mobile-bottom-sheet-content">
{SPACES_ITEMS.map((item) => (
<button
key={item.to}
className={`mobile-bottom-sheet-item ${
location.pathname === item.to ? "active" : ""
}`}
onClick={() => handleSelect(item.to)}
type="button"
>
<span className="mobile-bottom-sheet-item-label">
{item.label}
</span>
{location.pathname === item.to && (
<Icon name="success" size="sm" />
)}
</button>
))}
</div>
</div>
</div>
);
};