mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-08-28 03:30:05 +00:00
Improvements to menus and suspense
This commit is contained in:
@@ -37,6 +37,7 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
arrow,
|
arrow,
|
||||||
dark,
|
dark,
|
||||||
show,
|
show,
|
||||||
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -46,7 +47,8 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
dark && ["bg-dark", "text-white"],
|
dark && ["bg-dark", "text-white"],
|
||||||
show && "show",
|
show && "show",
|
||||||
className,
|
className,
|
||||||
)}>
|
)}
|
||||||
|
{...rest}>
|
||||||
{header && <span className="dropdown-header">{header}</span>}
|
{header && <span className="dropdown-header">{header}</span>}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { ReactNode, useState } from "react";
|
import React, { ReactNode, useState, useRef, useEffect } from "react";
|
||||||
|
|
||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import { Bell } from "tabler-icons-react";
|
import { Bell } from "tabler-icons-react";
|
||||||
@@ -75,6 +75,30 @@ export const NavigationHeader: React.FC<NavigationHeaderProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [notificationsShown, setNotificationsShown] = useState(false);
|
const [notificationsShown, setNotificationsShown] = useState(false);
|
||||||
const [profileShown, setProfileShown] = useState(false);
|
const [profileShown, setProfileShown] = useState(false);
|
||||||
|
const profileRef = useRef(null);
|
||||||
|
const notificationsRef = useRef(null);
|
||||||
|
|
||||||
|
const handleClickOutside = (event: any) => {
|
||||||
|
if (
|
||||||
|
profileRef.current &&
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
!profileRef.current.contains(event.target)
|
||||||
|
) {
|
||||||
|
setProfileShown(false);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
notificationsRef.current &&
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
!notificationsRef.current.contains(event.target)
|
||||||
|
) {
|
||||||
|
setNotificationsShown(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
@@ -100,7 +124,9 @@ export const NavigationHeader: React.FC<NavigationHeaderProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{notifications ? (
|
{notifications ? (
|
||||||
<div className="nav-item dropdown d-none d-md-flex me-3">
|
<div
|
||||||
|
className="nav-item dropdown d-none d-md-flex me-3"
|
||||||
|
ref={notificationsRef}>
|
||||||
<button
|
<button
|
||||||
style={{
|
style={{
|
||||||
border: 0,
|
border: 0,
|
||||||
@@ -125,6 +151,7 @@ export const NavigationHeader: React.FC<NavigationHeaderProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div
|
<div
|
||||||
|
ref={profileRef}
|
||||||
className={cn("nav-item", {
|
className={cn("nav-item", {
|
||||||
dropdown: !!profileItems,
|
dropdown: !!profileItems,
|
||||||
})}>
|
})}>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { ReactNode, useState } from "react";
|
import React, { ReactNode, useState, useRef, useEffect } from "react";
|
||||||
|
|
||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
|
|
||||||
@@ -45,6 +45,22 @@ export const NavigationMenu: React.FC<NavigationMenuProps> = ({
|
|||||||
searchContent,
|
searchContent,
|
||||||
}) => {
|
}) => {
|
||||||
const [dropdownShown, setDropdownShown] = useState(0);
|
const [dropdownShown, setDropdownShown] = useState(0);
|
||||||
|
const navRef = useRef(null);
|
||||||
|
|
||||||
|
const handleClickOutside = (event: any) => {
|
||||||
|
if (
|
||||||
|
navRef.current &&
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
!navRef.current.contains(event.target)
|
||||||
|
) {
|
||||||
|
setDropdownShown(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const itemClicked = (
|
const itemClicked = (
|
||||||
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
|
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
|
||||||
@@ -82,7 +98,7 @@ export const NavigationMenu: React.FC<NavigationMenuProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return wrapMenu(
|
return wrapMenu(
|
||||||
<ul className="navbar-nav">
|
<ul className="navbar-nav" ref={navRef}>
|
||||||
{items.map((item: any, idx: number) => {
|
{items.map((item: any, idx: number) => {
|
||||||
const onClickItem = (
|
const onClickItem = (
|
||||||
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
|
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import React, { lazy, Suspense } from "react";
|
import React, { lazy, Suspense } from "react";
|
||||||
|
|
||||||
import { Loading, SiteWrapper, SinglePage } from "components";
|
import { SiteWrapper, SuspenseLoader } from "components";
|
||||||
import { useAuthState, useHealthState, UserProvider } from "context";
|
import { useAuthState, useHealthState, UserProvider } from "context";
|
||||||
import { BrowserRouter, Switch, Route } from "react-router-dom";
|
import { BrowserRouter, Switch, Route } from "react-router-dom";
|
||||||
|
|
||||||
@@ -17,11 +17,7 @@ const Users = lazy(() => import("pages/Users"));
|
|||||||
function Router() {
|
function Router() {
|
||||||
const { health } = useHealthState();
|
const { health } = useHealthState();
|
||||||
const { authenticated } = useAuthState();
|
const { authenticated } = useAuthState();
|
||||||
const Spinner = (
|
const Spinner = <SuspenseLoader />;
|
||||||
<SinglePage>
|
|
||||||
<Loading />
|
|
||||||
</SinglePage>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (health.loading) {
|
if (health.loading) {
|
||||||
return Spinner;
|
return Spinner;
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
|
|
||||||
import { Footer } from "components";
|
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
const Root = styled.div`
|
const Root = styled.div`
|
||||||
@@ -24,7 +23,6 @@ function SinglePage({ children }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
<Wrapper>{children}</Wrapper>
|
<Wrapper>{children}</Wrapper>
|
||||||
<Footer />
|
|
||||||
</Root>
|
</Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
31
frontend/src/components/SuspenseLoader.tsx
Normal file
31
frontend/src/components/SuspenseLoader.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { Loading } from "components";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const Root = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function SuspenseLoader() {
|
||||||
|
return (
|
||||||
|
<Root>
|
||||||
|
<Wrapper>
|
||||||
|
<Loading />
|
||||||
|
</Wrapper>
|
||||||
|
</Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SuspenseLoader };
|
@@ -9,7 +9,9 @@ export * from "./Footer";
|
|||||||
export * from "./Loader";
|
export * from "./Loader";
|
||||||
export * from "./Loading";
|
export * from "./Loading";
|
||||||
export * from "./Navigation";
|
export * from "./Navigation";
|
||||||
|
export * from "./NavMenu";
|
||||||
export * from "./Router";
|
export * from "./Router";
|
||||||
export * from "./SinglePage";
|
export * from "./SinglePage";
|
||||||
export * from "./SiteWrapper";
|
export * from "./SiteWrapper";
|
||||||
|
export * from "./SuspenseLoader";
|
||||||
export * from "./Unhealthy";
|
export * from "./Unhealthy";
|
||||||
|
Reference in New Issue
Block a user