fix: make workspace project name visible on mobile and desktop cards
- Move the project name below the workspace title row so it reads as a distinct line with a project icon. - Move the status badge into the title row next to the workspace name, preventing it from crowding the project label. - Add .workspace-title-row flex styles and update .workspace-project-name to display inline-flex with a brand-colored project icon. Quality gates: npm run typecheck, npm run lint clean, npm test -- --run 87 passed.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
dir: apps/web/src/components/features
|
||||
|
||||
## role
|
||||
Contains reusable React components organized by feature domains for the web application UI.
|
||||
Contains reusable React components that implement specific product features and business logic for the web application.
|
||||
## parent
|
||||
index: apps/web/src/components/.pi-map.index.md
|
||||
map: apps/web/src/components/.pi-map.md
|
||||
|
||||
@@ -4,10 +4,10 @@ dir: apps/web/src/components/features
|
||||
index: apps/web/src/components/features/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Contains reusable React components organized by feature domains for the web application UI.
|
||||
Contains reusable React components that implement specific product features and business logic for the web application.
|
||||
## files
|
||||
## arch
|
||||
Feature-based component colocation with domain-driven folder structure, separating UI concerns by business capability rather than technical layer.
|
||||
Feature-based component organization following domain-driven design principles, likely with co-located components, hooks, and utilities per feature area.
|
||||
## tags
|
||||
-
|
||||
## symbols
|
||||
|
||||
@@ -6,114 +6,114 @@ import { SpacesBottomSheet } from "./spaces-bottom-sheet";
|
||||
import type { IconName } from "../../../utils/icons";
|
||||
|
||||
interface MobileNavProps {
|
||||
sessionCount?: number;
|
||||
sessionCount?: number;
|
||||
}
|
||||
|
||||
interface NavItem {
|
||||
to: string;
|
||||
label: string;
|
||||
icon: IconName;
|
||||
isGroup?: boolean;
|
||||
group?: "tools" | "spaces";
|
||||
to: string;
|
||||
label: string;
|
||||
icon: IconName;
|
||||
isGroup?: boolean;
|
||||
group?: "tools" | "spaces";
|
||||
}
|
||||
|
||||
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: "/tools",
|
||||
label: "Tools",
|
||||
icon: "settings",
|
||||
isGroup: true,
|
||||
group: "tools",
|
||||
},
|
||||
{ to: "/settings", label: "Settings", icon: "settings" },
|
||||
{ to: "/", label: "Home", icon: "dashboard" },
|
||||
{
|
||||
to: "/spaces",
|
||||
label: "Spaces",
|
||||
icon: "projects",
|
||||
isGroup: true,
|
||||
group: "spaces",
|
||||
},
|
||||
{ to: "/sessions", label: "Sessions", icon: "terminal" },
|
||||
{
|
||||
to: "/tools",
|
||||
label: "Tools",
|
||||
icon: "settings",
|
||||
isGroup: true,
|
||||
group: "tools",
|
||||
},
|
||||
{ to: "/settings", label: "Settings", icon: "settings" },
|
||||
];
|
||||
|
||||
export const MobileNav: React.FC<MobileNavProps> = ({ sessionCount }) => {
|
||||
const location = useLocation();
|
||||
const [toolsSheetOpen, setToolsSheetOpen] = useState(false);
|
||||
const [spacesSheetOpen, setSpacesSheetOpen] = useState(false);
|
||||
const location = useLocation();
|
||||
const [toolsSheetOpen, setToolsSheetOpen] = useState(false);
|
||||
const [spacesSheetOpen, setSpacesSheetOpen] = useState(false);
|
||||
|
||||
const isToolsActive =
|
||||
location.pathname === "/tool-workshop" ||
|
||||
location.pathname === "/config-profiles";
|
||||
const isToolsActive =
|
||||
location.pathname === "/tool-workshop" ||
|
||||
location.pathname === "/config-profiles";
|
||||
|
||||
const isSpacesActive =
|
||||
location.pathname.startsWith("/projects") ||
|
||||
location.pathname.startsWith("/workspaces");
|
||||
const isSpacesActive =
|
||||
location.pathname.startsWith("/projects") ||
|
||||
location.pathname.startsWith("/workspaces");
|
||||
|
||||
const handleNavClick = (item: NavItem) => {
|
||||
if (!item.isGroup) return;
|
||||
if (item.group === "spaces") {
|
||||
setSpacesSheetOpen(true);
|
||||
} else {
|
||||
setToolsSheetOpen(true);
|
||||
}
|
||||
};
|
||||
const handleNavClick = (item: NavItem) => {
|
||||
if (!item.isGroup) return;
|
||||
if (item.group === "spaces") {
|
||||
setSpacesSheetOpen(true);
|
||||
} else {
|
||||
setToolsSheetOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<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;
|
||||
return (
|
||||
<button
|
||||
key={item.to}
|
||||
className={`mobile-nav-item ${isActive ? "active" : ""}`}
|
||||
onClick={() => handleNavClick(item)}
|
||||
type="button"
|
||||
>
|
||||
<div className="mobile-nav-icon-wrapper">
|
||||
<Icon name={item.icon} size="md" />
|
||||
</div>
|
||||
<span className="mobile-nav-label">{item.label}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<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;
|
||||
return (
|
||||
<button
|
||||
key={item.to}
|
||||
className={`mobile-nav-item ${isActive ? "active" : ""}`}
|
||||
onClick={() => handleNavClick(item)}
|
||||
type="button"
|
||||
>
|
||||
<div className="mobile-nav-icon-wrapper">
|
||||
<Icon name={item.icon} size="md" />
|
||||
</div>
|
||||
<span className="mobile-nav-label">{item.label}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
`mobile-nav-item ${isActive ? "active" : ""}`
|
||||
}
|
||||
end={item.to === "/"}
|
||||
>
|
||||
<div className="mobile-nav-icon-wrapper">
|
||||
<Icon name={item.icon} size="md" />
|
||||
{item.to === "/sessions" && sessionCount ? (
|
||||
<span className="mobile-nav-badge">{sessionCount}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<span className="mobile-nav-label">{item.label}</span>
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
return (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
`mobile-nav-item ${isActive ? "active" : ""}`
|
||||
}
|
||||
end={item.to === "/"}
|
||||
>
|
||||
<div className="mobile-nav-icon-wrapper">
|
||||
<Icon name={item.icon} size="md" />
|
||||
{item.to === "/sessions" && sessionCount ? (
|
||||
<span className="mobile-nav-badge">{sessionCount}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<span className="mobile-nav-label">{item.label}</span>
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<ToolsBottomSheet
|
||||
isOpen={toolsSheetOpen}
|
||||
onClose={() => setToolsSheetOpen(false)}
|
||||
/>
|
||||
<SpacesBottomSheet
|
||||
isOpen={spacesSheetOpen}
|
||||
onClose={() => setSpacesSheetOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
<ToolsBottomSheet
|
||||
isOpen={toolsSheetOpen}
|
||||
onClose={() => setToolsSheetOpen(false)}
|
||||
/>
|
||||
<SpacesBottomSheet
|
||||
isOpen={spacesSheetOpen}
|
||||
onClose={() => setSpacesSheetOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/web/src/components/features/workspace
|
||||
|
||||
## role
|
||||
Provides React UI components for workspace management, navigation, and detail views in a web-based development environment.
|
||||
Provides the complete UI component suite for workspace management, including creation, detail viewing, file editing, Git operations, tool management, and settings.
|
||||
## parent
|
||||
index: apps/web/src/components/features/.pi-map.index.md
|
||||
map: apps/web/src/components/features/.pi-map.md
|
||||
|
||||
@@ -4,9 +4,9 @@ dir: apps/web/src/components/features/workspace
|
||||
index: apps/web/src/components/features/workspace/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Provides React UI components for workspace management, navigation, and detail views in a web-based development environment.
|
||||
Provides the complete UI component suite for workspace management, including creation, detail viewing, file editing, Git operations, tool management, and settings.
|
||||
## files
|
||||
- workspace-card.tsx | React card component that displays workspace information with status, metadata, and action buttons | exp: WorkspaceCardProps, func:WorkspaceCard({ workspace, loading = false, onStartTool, onSync, onDelete, }: WorkspaceCardProps), call:onStartTool, call:onSync, call:onDelete | dep: react-router-dom, ../../icon, ./workspace-instance-chips, ../../../types/workspace
|
||||
- workspace-card.tsx | React component that renders a card displaying workspace information with status, metadata, and action buttons. | exp: WorkspaceCardProps, func:WorkspaceCard({ workspace, loading = false, onStartTool, onSync, onDelete, }: WorkspaceCardProps), call:onStartTool, call:onSync, call:onDelete | dep: react-router-dom, ../../icon, ./workspace-instance-chips, ../../../types/workspace
|
||||
- workspace-create-form.tsx | React form component for creating workspaces with cascading project/repository/branch selectors and support for both contextual and standalone modes | exp: WorkspaceCreateFormProps, func:WorkspaceCreateForm({ onSubmit, onCancel, defaultProjectId, defaultRepoId, }: WorkspaceCreateFormProps), call:Boolean, call:useState, call:useGitRepo, call:useEffect, call:git.branches.includes, call:setSelectedBranch, call:setIsNewBranch, call:useCallback, call:listProjects, call:setProjects, call:setSelectedProject, call:setError, call:setFetchingProjects, call:loadProjects, call:setRepos, call:setSelectedRepo, call:listRepositories, call:loadRepos, call:setNewBranchName, call:e.preventDefault, call:name.trim, call:newBranchName.trim, call:setSubmitting, call:createWorkspaceTopLevel, call:onSubmit, call:projects.map, call:repos.map, call:setName, call:handleBranchChange, call:git.branches.map | dep: react, ../../icon, ../../../api/projects, ../../../api/git-repositories, ../../../api/workspaces, ../../../hooks/use-git-repo, ../../../types, icon, api/projects, api/git-repositories, api/workspaces, hooks/use-git-repo, types
|
||||
- workspace-detail-header.tsx | Renders a header component for a workspace detail page showing breadcrumb navigation and branch information. | exp: WorkspaceDetailHeaderProps, func:WorkspaceDetailHeader({ workspace, }: WorkspaceDetailHeaderProps) | dep: ../../icon, react
|
||||
- workspace-file-panel.tsx | Renders a file browser and editor panel with Git integration for a workspace detail page. | exp: func:WorkspaceFilePanel({ workspaceId }: WorkspaceFilePanelProps), call:useWorkspaceFiles, call:useWorkspaceGit, call:useState, call:setSelectedPath, call:setIsEditing, call:setEditContent, call:navigateTo, call:loadFile, call:currentPath.split("/").slice(0, -1).join, call:saveFile, call:setCommitMessage, call:commit, call:entries.map, call:handleSelect | dep: react, ../../icon, ../../../hooks/use-workspace-files, ../../../hooks/use-workspace-git, ../../../api/workspace-files
|
||||
@@ -17,7 +17,7 @@ Provides React UI components for workspace management, navigation, and detail vi
|
||||
- workspace-tab-bar.tsx | Renders desktop and mobile tab bar components for workspace navigation with files, git, tools, and settings tabs. | exp: WorkspaceTab, func:WorkspaceTabBar({ active, onChange }: WorkspaceTabBarProps), call:TABS.map, call:onChange, func:WorkspaceMobileTabBar({ active, onChange, }: WorkspaceTabBarProps), call:TABS.map, call:onChange | dep: ../../icon, react
|
||||
- workspace-tools-panel.tsx | Renders a tools management panel for a workspace that displays running tool instances and allows starting new tools via a modal | exp: func:WorkspaceToolsPanel({ workspace }: WorkspaceToolsPanelProps), call:useWorkspaceInstances, call:useState, call:setShowModal, call:instances.map, call:e.stopPropagation, call:refresh | dep: react, ../../icon, ../tool/tool-starter, ../../../hooks/use-workspace-instances, ../../../types/workspace, icon, tool-starter, use-workspace-instances, workspace types
|
||||
## arch
|
||||
Feature-based component organization with page-specific composite components (header/detail/settings) and reusable atomic pieces (cards, chips, forms), following a panel/tab architecture for workspace detail layout.
|
||||
Feature-based component organization with compound page pattern (header/tab-bar/panels), form abstraction for creation, and modal-driven tool management with responsive mobile/desktop tab navigation.
|
||||
## tags
|
||||
workspace, call:set, call:use, panel, git, branch, react, call:on
|
||||
## symbols
|
||||
|
||||
@@ -35,15 +35,18 @@ export function WorkspaceCard({
|
||||
className="workspace-title-link"
|
||||
>
|
||||
<div className="workspace-title-stack">
|
||||
<div className="workspace-title-row">
|
||||
<h4 className="workspace-name">{workspace.name}</h4>
|
||||
<span className={`status-badge ${statusClass}`}>
|
||||
{workspace.status}
|
||||
</span>
|
||||
</div>
|
||||
<span className="workspace-project-name">
|
||||
<Icon name="projects" size="sm" />
|
||||
{workspace.project_name}
|
||||
</span>
|
||||
<h4 className="workspace-name">{workspace.name}</h4>
|
||||
</div>
|
||||
</Link>
|
||||
<span className={`status-badge ${statusClass}`}>
|
||||
{workspace.status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="workspace-card-body">
|
||||
|
||||
Reference in New Issue
Block a user