aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaheshtheDev <[email protected]>2026-01-19 22:37:03 +0000
committerMaheshtheDev <[email protected]>2026-01-19 22:37:03 +0000
commit43cb9feecbaf4d83517696154073cc75b7c9c5c4 (patch)
treef51b57d20d915e0c249180b6972598899d15623f
parentdocs changes (#678) (diff)
downloadsupermemory-43cb9feecbaf4d83517696154073cc75b7c9c5c4.tar.xz
supermemory-43cb9feecbaf4d83517696154073cc75b7c9c5c4.zip
chore: ux improvements and space selector (#684)01-19-chore_ux_improvements_and_space_selector
-rw-r--r--apps/web/app/new/page.tsx30
-rw-r--r--apps/web/components/new/add-document/index.tsx115
-rw-r--r--apps/web/components/new/chat/index.tsx24
-rw-r--r--apps/web/components/new/header.tsx68
-rw-r--r--apps/web/components/new/mcp-modal/index.tsx17
-rw-r--r--apps/web/components/new/memories-grid.tsx8
-rw-r--r--apps/web/components/new/space-selector.tsx169
7 files changed, 238 insertions, 193 deletions
diff --git a/apps/web/app/new/page.tsx b/apps/web/app/new/page.tsx
index 3d85a427..aad09a28 100644
--- a/apps/web/app/new/page.tsx
+++ b/apps/web/app/new/page.tsx
@@ -19,7 +19,7 @@ export default function NewPage() {
return (
<HotkeysProvider>
- <div className="h-screen overflow-hidden bg-black">
+ <div className="bg-black">
<AnimatedGradientBackground
topPosition="15%"
animateFromBottom={false}
@@ -28,21 +28,23 @@ export default function NewPage() {
onAddMemory={() => setIsAddDocumentOpen(true)}
onOpenMCP={() => setIsMCPModalOpen(true)}
/>
- <main className="relative">
- <div key={`main-container-${isChatOpen}`} className="relative z-10">
- <div className="flex flex-row h-[calc(100vh-90px)] relative">
- <div className="flex-1 flex flex-col justify-start p-6 pr-0">
- <MemoriesGrid isChatOpen={isChatOpen} />
- </div>
- <AnimatePresence mode="popLayout">
- <ChatSidebar
- isChatOpen={isChatOpen}
- setIsChatOpen={setIsChatOpen}
- />
- </AnimatePresence>
- </div>
+ <main
+ key={`main-container-${isChatOpen}`}
+ className="z-10 flex flex-row relative"
+ >
+ <div className="flex-1 p-6 pr-0">
+ <MemoriesGrid isChatOpen={isChatOpen} />
+ </div>
+ <div className="sticky top-0 h-screen">
+ <AnimatePresence mode="popLayout">
+ <ChatSidebar
+ isChatOpen={isChatOpen}
+ setIsChatOpen={setIsChatOpen}
+ />
+ </AnimatePresence>
</div>
</main>
+
<AddDocumentModal
isOpen={isAddDocumentOpen}
onClose={() => setIsAddDocumentOpen(false)}
diff --git a/apps/web/components/new/add-document/index.tsx b/apps/web/components/new/add-document/index.tsx
index a99505bd..282150e4 100644
--- a/apps/web/components/new/add-document/index.tsx
+++ b/apps/web/components/new/add-document/index.tsx
@@ -1,38 +1,21 @@
"use client"
-import { useState, useEffect, useMemo, useCallback } from "react"
+import { useState, useEffect, useCallback } from "react"
import { Dialog, DialogContent, DialogTitle } from "@repo/ui/components/dialog"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/lib/fonts"
-import {
- FileTextIcon,
- GlobeIcon,
- ZapIcon,
- ChevronsUpDownIcon,
- FolderIcon,
- Loader2,
-} from "lucide-react"
+import { FileTextIcon, GlobeIcon, ZapIcon, Loader2 } from "lucide-react"
import { Button } from "@ui/components/button"
import { ConnectContent } from "./connections"
import { NoteContent } from "./note"
import { LinkContent, type LinkData } from "./link"
import { FileContent, type FileData } from "./file"
import { useProject } from "@/stores"
-import { $fetch } from "@lib/api"
-import { DEFAULT_PROJECT_ID } from "@repo/lib/constants"
-import type { Project } from "@repo/lib/types"
-import { useQuery } from "@tanstack/react-query"
-import { motion } from "motion/react"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@repo/ui/components/dropdown-menu"
import { toast } from "sonner"
import { useDocumentMutations } from "../../../hooks/use-document-mutations"
import { useCustomer } from "autumn-js/react"
import { useMemoriesUsage } from "@/hooks/use-memories-usage"
+import { SpaceSelector } from "../space-selector"
type TabType = "note" | "link" | "file" | "connect"
@@ -115,7 +98,6 @@ export function AddDocument({
const [localSelectedProject, setLocalSelectedProject] = useState<string>(
globalSelectedProject,
)
- const [isProjectSelectorOpen, setIsProjectSelectorOpen] = useState(false)
// Form data state for button click handling
const [noteContent, setNoteContent] = useState("")
@@ -147,33 +129,6 @@ export function AddDocument({
setLocalSelectedProject(globalSelectedProject)
}, [globalSelectedProject])
- const { data: projects = [], isLoading: isLoadingProjects } = useQuery({
- queryKey: ["projects"],
- queryFn: async () => {
- const response = await $fetch("@get/projects")
-
- if (response.error) {
- throw new Error(response.error?.message || "Failed to load projects")
- }
-
- return response.data?.projects || []
- },
- staleTime: 30 * 1000,
- })
-
- const projectName = useMemo(() => {
- if (localSelectedProject === DEFAULT_PROJECT_ID) return "Default Project"
- const found = projects.find(
- (p: Project) => p.containerTag === localSelectedProject,
- )
- return found?.name ?? localSelectedProject
- }, [projects, localSelectedProject])
-
- const handleProjectSelect = (containerTag: string) => {
- setLocalSelectedProject(containerTag)
- setIsProjectSelectorOpen(false)
- }
-
useEffect(() => {
if (defaultTab) {
setActiveTab(defaultTab)
@@ -334,65 +289,11 @@ export function AddDocument({
<ConnectContent selectedProject={localSelectedProject} />
)}
<div className="flex justify-between">
- <DropdownMenu
- open={isProjectSelectorOpen}
- onOpenChange={setIsProjectSelectorOpen}
- >
- <DropdownMenuTrigger asChild>
- <Button
- variant="insideOut"
- className="gap-2"
- disabled={isSubmitting}
- >
- <FolderIcon className="size-4" />
- <span className="max-w-[120px] truncate">
- {isLoadingProjects ? "..." : projectName}
- </span>
- <motion.div
- animate={{ rotate: isProjectSelectorOpen ? 180 : 0 }}
- transition={{ duration: 0.2 }}
- >
- <ChevronsUpDownIcon className="size-4" color="#737373" />
- </motion.div>
- </Button>
- </DropdownMenuTrigger>
- <DropdownMenuContent
- align="start"
- className="w-56 bg-[#1B1F24] border border-[#2A2E35] rounded-[12px] p-1.5 max-h-64 overflow-y-auto"
- >
- <DropdownMenuItem
- onClick={() => handleProjectSelect(DEFAULT_PROJECT_ID)}
- className={cn(
- "flex items-center gap-2 px-3 py-2 rounded-[8px] cursor-pointer",
- localSelectedProject === DEFAULT_PROJECT_ID
- ? "bg-[#4BA0FA]/20 text-white"
- : "text-[#737373] hover:bg-[#14161A] hover:text-white",
- )}
- >
- <FolderIcon className="h-4 w-4" />
- <span className="text-sm font-medium">Default Project</span>
- </DropdownMenuItem>
- {projects
- .filter((p: Project) => p.containerTag !== DEFAULT_PROJECT_ID)
- .map((project: Project) => (
- <DropdownMenuItem
- key={project.id}
- onClick={() => handleProjectSelect(project.containerTag)}
- className={cn(
- "flex items-center gap-2 px-3 py-2 rounded-[8px] cursor-pointer",
- localSelectedProject === project.containerTag
- ? "bg-[#4BA0FA]/20 text-white"
- : "text-[#737373] hover:bg-[#14161A] hover:text-white",
- )}
- >
- <FolderIcon className="h-4 w-4" />
- <span className="text-sm font-medium truncate">
- {project.name}
- </span>
- </DropdownMenuItem>
- ))}
- </DropdownMenuContent>
- </DropdownMenu>
+ <SpaceSelector
+ value={localSelectedProject}
+ onValueChange={setLocalSelectedProject}
+ variant="insideOut"
+ />
<div className="flex items-center gap-2">
<Button
variant="ghost"
diff --git a/apps/web/components/new/chat/index.tsx b/apps/web/components/new/chat/index.tsx
index 08f4e2ef..634c0bb1 100644
--- a/apps/web/components/new/chat/index.tsx
+++ b/apps/web/components/new/chat/index.tsx
@@ -94,11 +94,28 @@ export function ChatSidebar({
>({})
const [isInputExpanded, setIsInputExpanded] = useState(false)
const [isScrolledToBottom, setIsScrolledToBottom] = useState(true)
+ const [heightOffset, setHeightOffset] = useState(95)
const pendingFollowUpGenerations = useRef<Set<string>>(new Set())
const messagesContainerRef = useRef<HTMLDivElement>(null)
const { selectedProject } = useProject()
const { setCurrentChatId } = usePersistentChat()
+ // Adjust chat height based on scroll position
+ useEffect(() => {
+ const handleWindowScroll = () => {
+ const scrollThreshold = 80
+ const scrollY = window.scrollY
+ const progress = Math.min(scrollY / scrollThreshold, 1)
+ const newOffset = 95 - progress * (95 - 15)
+ setHeightOffset(newOffset)
+ }
+
+ window.addEventListener("scroll", handleWindowScroll, { passive: true })
+ handleWindowScroll()
+
+ return () => window.removeEventListener("scroll", handleWindowScroll)
+ }, [])
+
const { messages, sendMessage, status, setMessages, stop } = useChat({
transport: new DefaultChatTransport({
api: `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat/v2`,
@@ -364,7 +381,7 @@ export function ChatSidebar({
>
<motion.button
onClick={toggleChat}
- className="flex items-center gap-2 rounded-full px-3 py-1.5 text-xs font-medium border border-[#17181A] text-white cursor-pointer"
+ className="flex items-center gap-3 rounded-full px-3 py-1.5 text-sm font-medium border border-[#17181A] text-white cursor-pointer whitespace-nowrap"
style={{
background: "linear-gradient(180deg, #0A0E14 0%, #05070A 100%)",
}}
@@ -377,9 +394,12 @@ export function ChatSidebar({
<motion.div
key="open"
className={cn(
- "w-[450px] h-[calc(100vh-95px)] bg-[#05070A] backdrop-blur-md flex flex-col rounded-2xl m-4 mt-2 border border-[#17181AB2] relative pt-4",
+ "w-[450px] bg-[#05070A] backdrop-blur-md flex flex-col rounded-2xl m-4 mt-2 border border-[#17181AB2] relative pt-4",
dmSansClassName(),
)}
+ style={{
+ height: `calc(100vh - ${heightOffset}px)`,
+ }}
initial={{ x: "100px", opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: "100px", opacity: 0 }}
diff --git a/apps/web/components/new/header.tsx b/apps/web/components/new/header.tsx
index 3bb2d115..77362dec 100644
--- a/apps/web/components/new/header.tsx
+++ b/apps/web/components/new/header.tsx
@@ -5,11 +5,9 @@ import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
import { useAuth } from "@lib/auth-context"
import { useEffect, useState } from "react"
import {
- ChevronsLeftRight,
LayoutGridIcon,
Plus,
SearchIcon,
- FolderIcon,
LogOut,
Settings,
Home,
@@ -20,22 +18,18 @@ import { Button } from "@ui/components/button"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/lib/fonts"
import { Tabs, TabsList, TabsTrigger } from "@ui/components/tabs"
-import { useProjectName } from "@/hooks/use-project-name"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@ui/components/dropdown-menu"
-import { useQuery } from "@tanstack/react-query"
-import { $fetch } from "@repo/lib/api"
import { authClient } from "@lib/auth"
-import { DEFAULT_PROJECT_ID } from "@repo/lib/constants"
import { useProjectMutations } from "@/hooks/use-project-mutations"
import { useProject } from "@/stores"
import { useRouter } from "next/navigation"
import Link from "next/link"
-import type { Project } from "@repo/lib/types"
+import { SpaceSelector } from "./space-selector"
interface HeaderProps {
onAddMemory?: () => void
@@ -45,23 +39,9 @@ interface HeaderProps {
export function Header({ onAddMemory, onOpenMCP }: HeaderProps) {
const { user } = useAuth()
const [name, setName] = useState<string>("")
- const projectName = useProjectName()
const { selectedProject } = useProject()
const { switchProject } = useProjectMutations()
const router = useRouter()
- const { data: projects = [] } = useQuery({
- queryKey: ["projects"],
- queryFn: async () => {
- const response = await $fetch("@get/projects")
-
- if (response.error) {
- throw new Error(response.error?.message || "Failed to load projects")
- }
-
- return response.data?.projects || []
- },
- staleTime: 30 * 1000,
- })
useEffect(() => {
const storedName =
@@ -130,47 +110,11 @@ export function Header({ onAddMemory, onOpenMCP }: HeaderProps) {
</DropdownMenuContent>
</DropdownMenu>
<div className="self-stretch w-px bg-[#FFFFFF33]" />
- <div className="flex items-center gap-2">
- <p>📁 {projectName}</p>
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <button
- type="button"
- className="cursor-pointer hover:opacity-70 transition-opacity"
- aria-label="Change project"
- >
- <ChevronsLeftRight className="size-4 rotate-90" />
- </button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="start" className="w-56">
- <DropdownMenuItem
- onClick={() => switchProject(DEFAULT_PROJECT_ID)}
- className={cn(
- "cursor-pointer",
- selectedProject === DEFAULT_PROJECT_ID && "bg-accent",
- )}
- >
- <FolderIcon className="h-3.5 w-3.5 mr-2" />
- <span className="text-sm">Default Project</span>
- </DropdownMenuItem>
- {projects
- .filter((p: Project) => p.containerTag !== DEFAULT_PROJECT_ID)
- .map((project: Project) => (
- <DropdownMenuItem
- key={project.id}
- onClick={() => switchProject(project.containerTag)}
- className={cn(
- "cursor-pointer",
- selectedProject === project.containerTag && "bg-accent",
- )}
- >
- <FolderIcon className="h-3.5 w-3.5 mr-2 opacity-70" />
- <span className="text-sm truncate">{project.name}</span>
- </DropdownMenuItem>
- ))}
- </DropdownMenuContent>
- </DropdownMenu>
- </div>
+ <SpaceSelector
+ value={selectedProject}
+ onValueChange={switchProject}
+ showChevron
+ />
</div>
<Tabs defaultValue="grid">
<TabsList className="rounded-full border border-[#161F2C] h-11! z-10!">
diff --git a/apps/web/components/new/mcp-modal/index.tsx b/apps/web/components/new/mcp-modal/index.tsx
index 6816c21e..4a5cc0b1 100644
--- a/apps/web/components/new/mcp-modal/index.tsx
+++ b/apps/web/components/new/mcp-modal/index.tsx
@@ -2,9 +2,12 @@ import { dmSans125ClassName, dmSansClassName } from "@/lib/fonts"
import { Dialog, DialogContent, DialogFooter } from "@repo/ui/components/dialog"
import { cn } from "@lib/utils"
import * as DialogPrimitive from "@radix-ui/react-dialog"
-import { ChevronsUpDownIcon, XIcon } from "lucide-react"
+import { XIcon } from "lucide-react"
import { Button } from "@ui/components/button"
import { MCPSteps } from "./mcp-detail-view"
+import { SpaceSelector } from "../space-selector"
+import { useProject } from "@/stores"
+import { useProjectMutations } from "@/hooks/use-project-mutations"
export function MCPModal({
isOpen,
@@ -13,6 +16,8 @@ export function MCPModal({
isOpen: boolean
onClose: () => void
}) {
+ const { selectedProject } = useProject()
+ const { switchProject } = useProjectMutations()
return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent
@@ -50,9 +55,11 @@ export function MCPModal({
</div>
<DialogFooter className="justify-between!">
<div className="flex items-center gap-2">
- <Button variant="insideOut">
- My Space <ChevronsUpDownIcon className="size-4" color="#737373" />
- </Button>
+ <SpaceSelector
+ value={selectedProject}
+ onValueChange={switchProject}
+ variant="insideOut"
+ />
<Button
variant="ghost"
className="text-[#737373] cursor-pointer rounded-full"
@@ -60,7 +67,7 @@ export function MCPModal({
Migrate from MCP v1
</Button>
</div>
- <Button variant="insideOut" className="px-6 py-[10px]">
+ <Button variant="insideOut" className="px-6 py-[10px]" onClick={onClose}>
Done
</Button>
</DialogFooter>
diff --git a/apps/web/components/new/memories-grid.tsx b/apps/web/components/new/memories-grid.tsx
index e6095645..dd4a4e9b 100644
--- a/apps/web/components/new/memories-grid.tsx
+++ b/apps/web/components/new/memories-grid.tsx
@@ -54,7 +54,7 @@ export function MemoriesGrid({ isChatOpen }: { isChatOpen: boolean }) {
const response = await $fetch("@post/documents/documents", {
body: {
page: pageParam as number,
- limit: (pageParam as number) === 1 ? (IS_DEV ? 500 : 500) : PAGE_SIZE,
+ limit: PAGE_SIZE,
sort: "createdAt",
order: "desc",
containerTags: selectedProject ? [selectedProject] : undefined,
@@ -91,6 +91,8 @@ export function MemoriesGrid({ isChatOpen }: { isChatOpen: boolean }) {
)
}, [data])
+ const isLoadingMore = isFetchingNextPage
+
const loadMoreDocuments = useCallback(async (): Promise<void> => {
if (hasNextPage && !isFetchingNextPage) {
await fetchNextPage()
@@ -148,7 +150,7 @@ export function MemoriesGrid({ isChatOpen }: { isChatOpen: boolean }) {
}
return (
- <div className="h-full">
+ <div className="relative">
<Button
className={cn(
dmSansClassName(),
@@ -189,7 +191,7 @@ export function MemoriesGrid({ isChatOpen }: { isChatOpen: boolean }) {
onRender={maybeLoadMore}
/>
- {isFetchingNextPage && (
+ {isLoadingMore && (
<div className="py-8 flex items-center justify-center">
<SuperLoader />
</div>
diff --git a/apps/web/components/new/space-selector.tsx b/apps/web/components/new/space-selector.tsx
new file mode 100644
index 00000000..68c615a5
--- /dev/null
+++ b/apps/web/components/new/space-selector.tsx
@@ -0,0 +1,169 @@
+"use client"
+
+import { useState, useMemo } from "react"
+import { cn } from "@lib/utils"
+import { dmSansClassName } from "@/lib/fonts"
+import { $fetch } from "@repo/lib/api"
+import { DEFAULT_PROJECT_ID } from "@repo/lib/constants"
+import { useQuery } from "@tanstack/react-query"
+import { ChevronsLeftRight, Plus } from "lucide-react"
+import type { Project } from "@repo/lib/types"
+import { CreateProjectDialog } from "@/components/create-project-dialog"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@ui/components/dropdown-menu"
+
+export interface SpaceSelectorProps {
+ value: string
+ onValueChange: (containerTag: string) => void
+ variant?: "default" | "insideOut"
+ showChevron?: boolean
+ triggerClassName?: string
+ contentClassName?: string
+ showNewSpace?: boolean
+}
+
+const triggerVariants = {
+ default: "px-3 py-2 rounded-md hover:bg-white/5",
+ insideOut: "px-3 py-2 rounded-full bg-[#0D121A] shadow-inside-out",
+}
+
+export function SpaceSelector({
+ value,
+ onValueChange,
+ variant = "default",
+ showChevron = false,
+ triggerClassName,
+ contentClassName,
+ showNewSpace = true,
+}: SpaceSelectorProps) {
+ const [isOpen, setIsOpen] = useState(false)
+ const [showCreateDialog, setShowCreateDialog] = useState(false)
+
+ const { data: projects = [], isLoading } = useQuery({
+ queryKey: ["projects"],
+ queryFn: async () => {
+ const response = await $fetch("@get/projects")
+
+ if (response.error) {
+ throw new Error(response.error?.message || "Failed to load projects")
+ }
+
+ return response.data?.projects || []
+ },
+ staleTime: 30 * 1000,
+ })
+
+ const selectedProjectName = useMemo(() => {
+ if (value === DEFAULT_PROJECT_ID) return "My Space"
+ const found = projects.find((p: Project) => p.containerTag === value)
+ return found?.name ?? value
+ }, [projects, value])
+
+ const handleSelect = (containerTag: string) => {
+ onValueChange(containerTag)
+ setIsOpen(false)
+ }
+
+ const handleNewSpace = () => {
+ setIsOpen(false)
+ setShowCreateDialog(true)
+ }
+
+ return (
+ <>
+ <DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
+ <DropdownMenuTrigger asChild>
+ <button
+ type="button"
+ className={cn(
+ "flex items-center gap-2 cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50",
+ triggerVariants[variant],
+ dmSansClassName(),
+ triggerClassName,
+ )}
+ >
+ <span className="text-sm font-bold tracking-[-0.98px]">📁</span>
+ <span className="text-sm font-medium text-white">
+ {isLoading ? "..." : selectedProjectName}
+ </span>
+ {showChevron && (
+ <ChevronsLeftRight className="size-4 rotate-90 text-white/70" />
+ )}
+ </button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent
+ align="start"
+ className={cn(
+ "min-w-[200px] p-3 rounded-[11px] border border-[#2E3033] shadow-[0px_1.5px_20px_0px_rgba(0,0,0,0.65)]",
+ dmSansClassName(),
+ contentClassName,
+ )}
+ style={{
+ background: "linear-gradient(180deg, #0A0E14 0%, #05070A 100%)",
+ }}
+ >
+ <div className="flex flex-col gap-3">
+ <div className="flex flex-col">
+ {/* Default Project */}
+ <DropdownMenuItem
+ onClick={() => handleSelect(DEFAULT_PROJECT_ID)}
+ className={cn(
+ "flex items-center gap-2 px-3 py-2 rounded-md cursor-pointer text-white text-sm font-medium",
+ value === DEFAULT_PROJECT_ID
+ ? "bg-[#161E2B] border border-[rgba(115,115,115,0.1)]"
+ : "opacity-50 hover:opacity-100 hover:bg-[#161E2B]/50",
+ )}
+ >
+ <span className="font-bold tracking-[-0.98px]">📁</span>
+ <span>My Space</span>
+ </DropdownMenuItem>
+
+ {/* User Projects */}
+ {projects
+ .filter((p: Project) => p.containerTag !== DEFAULT_PROJECT_ID)
+ .map((project: Project) => (
+ <DropdownMenuItem
+ key={project.id}
+ onClick={() => handleSelect(project.containerTag)}
+ className={cn(
+ "flex items-center gap-2 px-3 py-2 rounded-md cursor-pointer text-white text-sm font-medium",
+ value === project.containerTag
+ ? "bg-[#161E2B] border border-[rgba(115,115,115,0.1)]"
+ : "opacity-50 hover:opacity-100 hover:bg-[#161E2B]/50",
+ )}
+ >
+ <span className="font-bold tracking-[-0.98px]">📁</span>
+ <span className="truncate">{project.name}</span>
+ </DropdownMenuItem>
+ ))}
+ </div>
+
+ {showNewSpace && (
+ <button
+ type="button"
+ onClick={handleNewSpace}
+ className="flex items-center justify-center gap-2 px-3 py-2 rounded-md cursor-pointer text-white text-sm font-medium border border-[#161F2C] hover:bg-[#0D121A]/80 transition-colors"
+ style={{
+ background:
+ "linear-gradient(180deg, #0D121A 0%, #000000 100%)",
+ }}
+ >
+ <Plus className="size-4" />
+ <span>New Space</span>
+ </button>
+ )}
+ </div>
+ </DropdownMenuContent>
+ </DropdownMenu>
+
+ <CreateProjectDialog
+ open={showCreateDialog}
+ onOpenChange={setShowCreateDialog}
+ />
+ </>
+ )
+}