Fix event propagation in sidebar

This commit is contained in:
Dries Augustyns
2025-12-04 21:20:40 +01:00
parent b4b7cc78f1
commit 8c348d3580
2 changed files with 41 additions and 9 deletions
+30 -8
View File
@@ -67,16 +67,30 @@ export function DashboardLayout({children}: DashboardLayoutProps) {
const [showProjectMenu, setShowProjectMenu] = useState(false);
const [showUserMenu, setShowUserMenu] = useState(false);
const [showMobileMenu, setShowMobileMenu] = useState(false);
const projectMenuRef = useRef<HTMLDivElement>(null);
const userMenuRef = useRef<HTMLDivElement>(null);
const desktopProjectMenuRef = useRef<HTMLDivElement>(null);
const desktopUserMenuRef = useRef<HTMLDivElement>(null);
const mobileProjectMenuRef = useRef<HTMLDivElement>(null);
const mobileUserMenuRef = useRef<HTMLDivElement>(null);
// Handle click outside for project menu
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (projectMenuRef.current && !projectMenuRef.current.contains(event.target as Node)) {
const isClickOutsideProjectMenu =
desktopProjectMenuRef.current &&
!desktopProjectMenuRef.current.contains(event.target as Node) &&
mobileProjectMenuRef.current &&
!mobileProjectMenuRef.current.contains(event.target as Node);
const isClickOutsideUserMenu =
desktopUserMenuRef.current &&
!desktopUserMenuRef.current.contains(event.target as Node) &&
mobileUserMenuRef.current &&
!mobileUserMenuRef.current.contains(event.target as Node);
if (isClickOutsideProjectMenu) {
setShowProjectMenu(false);
}
if (userMenuRef.current && !userMenuRef.current.contains(event.target as Node)) {
if (isClickOutsideUserMenu) {
setShowUserMenu(false);
}
}
@@ -134,7 +148,10 @@ export function DashboardLayout({children}: DashboardLayoutProps) {
);
// Sidebar content (reusable for both desktop and mobile)
const sidebarContent = (
const getSidebarContent = (
projectMenuRef: React.RefObject<HTMLDivElement | null>,
userMenuRef: React.RefObject<HTMLDivElement | null>,
) => (
<>
{/* Logo */}
<div className="h-16 flex items-center gap-2 px-6 border-b border-neutral-200">
@@ -164,7 +181,9 @@ export function DashboardLayout({children}: DashboardLayoutProps) {
{availableProjects.map(project => (
<button
key={project.id}
onClick={() => {
onClick={e => {
e.preventDefault();
e.stopPropagation();
setActiveProject(project);
setShowProjectMenu(false);
}}
@@ -182,6 +201,7 @@ export function DashboardLayout({children}: DashboardLayoutProps) {
<div className="border-t border-neutral-200 my-1" />
<Link
href="/projects/create"
onClick={() => setShowProjectMenu(false)}
className="w-full flex items-center gap-2 px-3 py-2 text-sm hover:bg-neutral-50 transition-colors text-neutral-700"
>
<Plus className="h-4 w-4" />
@@ -267,7 +287,9 @@ export function DashboardLayout({children}: DashboardLayoutProps) {
return (
<div className="flex h-screen bg-neutral-50">
{/* Desktop Sidebar - Hidden on mobile */}
<div className="hidden lg:flex w-64 bg-white border-r border-neutral-200 flex-col">{sidebarContent}</div>
<div className="hidden lg:flex w-64 bg-white border-r border-neutral-200 flex-col">
{getSidebarContent(desktopProjectMenuRef, desktopUserMenuRef)}
</div>
{/* Mobile Sidebar Overlay */}
{showMobileMenu && (
@@ -282,7 +304,7 @@ export function DashboardLayout({children}: DashboardLayoutProps) {
showMobileMenu ? 'translate-x-0' : '-translate-x-full'
}`}
>
<div className="flex flex-col h-full">{sidebarContent}</div>
<div className="flex flex-col h-full">{getSidebarContent(mobileProjectMenuRef, mobileUserMenuRef)}</div>
</div>
{/* Main Content */}
+11 -1
View File
@@ -1,4 +1,5 @@
import {zodResolver} from '@hookform/resolvers/zod';
import type {Project} from '@plunk/db';
import {ProjectSchemas} from '@plunk/shared';
import {
Button,
@@ -21,11 +22,13 @@ import React, {useState} from 'react';
import {useForm} from 'react-hook-form';
import type {z} from 'zod';
import {useActiveProject} from '../../lib/contexts/ActiveProjectProvider';
import {useProjects} from '../../lib/hooks/useProject';
import {network} from '../../lib/network';
export default function CreateProject() {
const {mutate: projectsMutate} = useProjects();
const {setActiveProject} = useActiveProject();
const router = useRouter();
const form = useForm<z.infer<typeof ProjectSchemas.create>>({
@@ -39,11 +42,18 @@ export default function CreateProject() {
async function onSubmit(values: z.infer<typeof ProjectSchemas.create>) {
try {
await network.fetch<{success: boolean}, typeof ProjectSchemas.create>('POST', '/users/@me/projects', values);
const newProject = await network.fetch<Project, typeof ProjectSchemas.create>(
'POST',
'/users/@me/projects',
values,
);
// Refresh the projects list
await projectsMutate();
// Set the newly created project as active
setActiveProject(newProject);
// Redirect to dashboard
await router.push('/');
} catch (error) {