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 [showProjectMenu, setShowProjectMenu] = useState(false);
|
||||||
const [showUserMenu, setShowUserMenu] = useState(false);
|
const [showUserMenu, setShowUserMenu] = useState(false);
|
||||||
const [showMobileMenu, setShowMobileMenu] = useState(false);
|
const [showMobileMenu, setShowMobileMenu] = useState(false);
|
||||||
const projectMenuRef = useRef<HTMLDivElement>(null);
|
const desktopProjectMenuRef = useRef<HTMLDivElement>(null);
|
||||||
const userMenuRef = useRef<HTMLDivElement>(null);
|
const desktopUserMenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
const mobileProjectMenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
const mobileUserMenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Handle click outside for project menu
|
// Handle click outside for project menu
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleClickOutside(event: MouseEvent) {
|
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);
|
setShowProjectMenu(false);
|
||||||
}
|
}
|
||||||
if (userMenuRef.current && !userMenuRef.current.contains(event.target as Node)) {
|
if (isClickOutsideUserMenu) {
|
||||||
setShowUserMenu(false);
|
setShowUserMenu(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,7 +148,10 @@ export function DashboardLayout({children}: DashboardLayoutProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Sidebar content (reusable for both desktop and mobile)
|
// Sidebar content (reusable for both desktop and mobile)
|
||||||
const sidebarContent = (
|
const getSidebarContent = (
|
||||||
|
projectMenuRef: React.RefObject<HTMLDivElement | null>,
|
||||||
|
userMenuRef: React.RefObject<HTMLDivElement | null>,
|
||||||
|
) => (
|
||||||
<>
|
<>
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<div className="h-16 flex items-center gap-2 px-6 border-b border-neutral-200">
|
<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 => (
|
{availableProjects.map(project => (
|
||||||
<button
|
<button
|
||||||
key={project.id}
|
key={project.id}
|
||||||
onClick={() => {
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
setActiveProject(project);
|
setActiveProject(project);
|
||||||
setShowProjectMenu(false);
|
setShowProjectMenu(false);
|
||||||
}}
|
}}
|
||||||
@@ -182,6 +201,7 @@ export function DashboardLayout({children}: DashboardLayoutProps) {
|
|||||||
<div className="border-t border-neutral-200 my-1" />
|
<div className="border-t border-neutral-200 my-1" />
|
||||||
<Link
|
<Link
|
||||||
href="/projects/create"
|
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"
|
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" />
|
<Plus className="h-4 w-4" />
|
||||||
@@ -267,7 +287,9 @@ export function DashboardLayout({children}: DashboardLayoutProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-screen bg-neutral-50">
|
<div className="flex h-screen bg-neutral-50">
|
||||||
{/* Desktop Sidebar - Hidden on mobile */}
|
{/* 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 */}
|
{/* Mobile Sidebar Overlay */}
|
||||||
{showMobileMenu && (
|
{showMobileMenu && (
|
||||||
@@ -282,7 +304,7 @@ export function DashboardLayout({children}: DashboardLayoutProps) {
|
|||||||
showMobileMenu ? 'translate-x-0' : '-translate-x-full'
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {zodResolver} from '@hookform/resolvers/zod';
|
import {zodResolver} from '@hookform/resolvers/zod';
|
||||||
|
import type {Project} from '@plunk/db';
|
||||||
import {ProjectSchemas} from '@plunk/shared';
|
import {ProjectSchemas} from '@plunk/shared';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -21,11 +22,13 @@ import React, {useState} from 'react';
|
|||||||
import {useForm} from 'react-hook-form';
|
import {useForm} from 'react-hook-form';
|
||||||
import type {z} from 'zod';
|
import type {z} from 'zod';
|
||||||
|
|
||||||
|
import {useActiveProject} from '../../lib/contexts/ActiveProjectProvider';
|
||||||
import {useProjects} from '../../lib/hooks/useProject';
|
import {useProjects} from '../../lib/hooks/useProject';
|
||||||
import {network} from '../../lib/network';
|
import {network} from '../../lib/network';
|
||||||
|
|
||||||
export default function CreateProject() {
|
export default function CreateProject() {
|
||||||
const {mutate: projectsMutate} = useProjects();
|
const {mutate: projectsMutate} = useProjects();
|
||||||
|
const {setActiveProject} = useActiveProject();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof ProjectSchemas.create>>({
|
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>) {
|
async function onSubmit(values: z.infer<typeof ProjectSchemas.create>) {
|
||||||
try {
|
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
|
// Refresh the projects list
|
||||||
await projectsMutate();
|
await projectsMutate();
|
||||||
|
|
||||||
|
// Set the newly created project as active
|
||||||
|
setActiveProject(newProject);
|
||||||
|
|
||||||
// Redirect to dashboard
|
// Redirect to dashboard
|
||||||
await router.push('/');
|
await router.push('/');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user