Fix event propagation in sidebar
This commit is contained in:
@@ -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 */}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user