@@ -65,11 +65,8 @@ export function LeftSidebar(props: LeftSidebarProps) {
"h-screen bg-separator border-r border-dark flex flex-col shrink-0",
"transition-all duration-200 overflow-hidden relative z-[100]",
collapsed ? "w-8" : "w-72",
- "max-md:fixed max-md:left-0 max-md:top-0 max-md:w-72 max-md:z-[1000]",
- "max-md:transition-transform max-md:duration-300",
- collapsed
- ? "max-md:-translate-x-full max-md:shadow-none"
- : "max-md:translate-x-0 max-md:shadow-[2px_0_8px_rgba(0,0,0,0.5)]"
+ "mobile-sidebar",
+ collapsed && "mobile-sidebar-collapsed"
)}
>
{!collapsed &&
}
diff --git a/src/components/ProjectSidebar.tsx b/src/components/ProjectSidebar.tsx
index 17ada1e8e..43a8b708b 100644
--- a/src/components/ProjectSidebar.tsx
+++ b/src/components/ProjectSidebar.tsx
@@ -9,7 +9,7 @@ import { HTML5Backend, getEmptyImage } from "react-dnd-html5-backend";
import { useDrag, useDrop, useDragLayer } from "react-dnd";
import { sortProjectsByOrder, reorderProjects, normalizeOrder } from "@/utils/projectOrdering";
import { matchesKeybind, formatKeybind, KEYBINDS } from "@/utils/ui/keybinds";
-import { abbreviatePath } from "@/utils/ui/pathAbbreviation";
+import { abbreviatePath, splitAbbreviatedPath } from "@/utils/ui/pathAbbreviation";
import {
partitionWorkspacesByAge,
formatOldWorkspaceThreshold,
@@ -78,7 +78,7 @@ const DraggableProjectItemBase: React.FC
= ({
drag(drop(node))}
className={cn(
- "py-1 px-3 flex items-center border-l-transparent transition-all duration-150 bg-separator",
+ "py-2 px-3 flex items-center border-l-transparent transition-all duration-150 bg-separator",
isDragging ? "cursor-grabbing opacity-40 [&_*]:!cursor-grabbing" : "cursor-grab",
isOver && "bg-accent/[0.08]",
selected && "bg-hover border-l-accent",
@@ -130,21 +130,19 @@ const ProjectDragLayer: React.FC = () => {
if (!isDragging || !currentOffset || !item?.projectPath) return null;
- const name = item.projectPath.split("/").pop() ?? item.projectPath;
const abbrevPath = abbreviatePath(item.projectPath);
+ const { dirPath, basename } = splitAbbreviatedPath(abbrevPath);
return (
⠿
-
▶
+
▶
-
- {name}
-
-
- {abbrevPath}
+
+ {dirPath}
+ {basename}
@@ -489,18 +487,26 @@ const ProjectSidebarInner: React.FC
= ({
▶
-
-
- {projectName}
-
+
-
- {abbreviatePath(projectPath)}
+
+ {(() => {
+ const abbrevPath = abbreviatePath(projectPath);
+ const { dirPath, basename } = splitAbbreviatedPath(abbrevPath);
+ return (
+ <>
+ {dirPath}
+
+ {basename}
+
+ >
+ );
+ })()}
{projectPath}
diff --git a/src/styles/globals.css b/src/styles/globals.css
index c90651b0b..c598f6ead 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -256,8 +256,8 @@ body,
overflow: hidden;
}
-/* Mobile improvements */
-@media (max-width: 768px) {
+/* Mobile improvements - only apply to touch devices */
+@media (max-width: 768px) and (pointer: coarse) {
html {
-webkit-text-size-adjust: 100%;
touch-action: manipulation;
@@ -273,6 +273,41 @@ body,
min-height: 44px;
min-width: 44px;
}
+
+ /* Show mobile menu button only on touch devices */
+ .mobile-menu-btn {
+ display: flex !important;
+ }
+
+ /* Show mobile overlay only on touch devices */
+ .mobile-overlay {
+ display: block !important;
+ }
+
+ /* Mobile sidebar positioning only on touch devices */
+ .mobile-sidebar {
+ position: fixed !important;
+ left: 0 !important;
+ top: 0 !important;
+ width: 18rem !important;
+ z-index: 1000 !important;
+ transition: transform 0.3s !important;
+ box-shadow: 2px 0 8px rgba(0, 0, 0, 0.5) !important;
+ }
+
+ .mobile-sidebar-collapsed {
+ transform: translateX(-100%) !important;
+ box-shadow: none !important;
+ }
+
+ /* Mobile layout - stack vertically on touch devices */
+ .mobile-layout {
+ flex-direction: column !important;
+ }
+
+ .mobile-main-content {
+ width: 100% !important;
+ }
}
code {
diff --git a/src/utils/ui/pathAbbreviation.test.ts b/src/utils/ui/pathAbbreviation.test.ts
index 321dbbaff..188b31a4f 100644
--- a/src/utils/ui/pathAbbreviation.test.ts
+++ b/src/utils/ui/pathAbbreviation.test.ts
@@ -1,4 +1,4 @@
-import { abbreviatePath } from "./pathAbbreviation";
+import { abbreviatePath, splitAbbreviatedPath } from "./pathAbbreviation";
describe("abbreviatePath", () => {
it("should abbreviate all directory components except the last one", () => {
@@ -32,3 +32,51 @@ describe("abbreviatePath", () => {
);
});
});
+
+describe("splitAbbreviatedPath", () => {
+ it("should split abbreviated path into directory and basename", () => {
+ expect(splitAbbreviatedPath("/U/a/P/c/cmux")).toEqual({
+ dirPath: "/U/a/P/c/",
+ basename: "cmux",
+ });
+ });
+
+ it("should handle paths without leading slash", () => {
+ expect(splitAbbreviatedPath("U/a/P/c/cmux")).toEqual({
+ dirPath: "U/a/P/c/",
+ basename: "cmux",
+ });
+ });
+
+ it("should handle single directory paths", () => {
+ expect(splitAbbreviatedPath("/Users")).toEqual({
+ dirPath: "/",
+ basename: "Users",
+ });
+ expect(splitAbbreviatedPath("Users")).toEqual({
+ dirPath: "",
+ basename: "Users",
+ });
+ });
+
+ it("should handle root path", () => {
+ expect(splitAbbreviatedPath("/")).toEqual({
+ dirPath: "/",
+ basename: "",
+ });
+ });
+
+ it("should handle empty string", () => {
+ expect(splitAbbreviatedPath("")).toEqual({
+ dirPath: "",
+ basename: "",
+ });
+ });
+
+ it("should handle paths with long basenames", () => {
+ expect(splitAbbreviatedPath("/U/a/very-long-project-name")).toEqual({
+ dirPath: "/U/a/",
+ basename: "very-long-project-name",
+ });
+ });
+});
diff --git a/src/utils/ui/pathAbbreviation.ts b/src/utils/ui/pathAbbreviation.ts
index a1ba11b4f..3f474abcf 100644
--- a/src/utils/ui/pathAbbreviation.ts
+++ b/src/utils/ui/pathAbbreviation.ts
@@ -31,3 +31,23 @@ export function abbreviatePath(path: string): string {
return abbreviated.join("/");
}
+
+/**
+ * Split an abbreviated path into directory path and basename
+ * Example: /U/a/P/c/cmux -> { dirPath: "/U/a/P/c/", basename: "cmux" }
+ */
+export function splitAbbreviatedPath(path: string): { dirPath: string; basename: string } {
+ if (!path || typeof path !== "string") {
+ return { dirPath: "", basename: path };
+ }
+
+ const lastSlashIndex = path.lastIndexOf("/");
+ if (lastSlashIndex === -1) {
+ return { dirPath: "", basename: path };
+ }
+
+ return {
+ dirPath: path.slice(0, lastSlashIndex + 1), // Include the trailing slash
+ basename: path.slice(lastSlashIndex + 1),
+ };
+}