Merge pull request #4928 from 7heMech/develop

UI/UX improvements
This commit is contained in:
jc21
2025-11-18 18:37:22 +10:00
committed by GitHub
9 changed files with 70 additions and 35 deletions

View File

@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Nginx Proxy Manager</title> <title>Nginx Proxy Manager</title>
<meta name="description" content="In The Office Planner" /> <meta name="description" content="In The Office Planner" />
<link rel="preload" href="/images/logo-no-text.svg" as="image" type="image/svg+xml" fetchPriority="high">
<link <link
rel="apple-touch-icon" rel="apple-touch-icon"
sizes="180x180" sizes="180x180"

View File

@@ -13,6 +13,15 @@
--tblr-backdrop-opacity: 0.8 !important; --tblr-backdrop-opacity: 0.8 !important;
} }
[data-bs-theme="dark"] .modal-content {
--tblr-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
[data-bs-theme="dark"] .modal-backdrop {
--tblr-backdrop-bg: #000 !important;
--tblr-backdrop-opacity: 0.65 !important;
}
.domain-name { .domain-name {
font-family: monospace; font-family: monospace;
} }
@@ -95,3 +104,15 @@ label.row {
border-radius: var(--tblr-border-radius) 0 0 var(--tblr-border-radius); border-radius: var(--tblr-border-radius) 0 0 var(--tblr-border-radius);
} }
} }
/* Fix for dropdown menus being clipped by table-responsive containers. */
.table-responsive .dropdown {
position: static;
}
/* Fix for Tabler scrollbar compensation */
@media (min-width: 992px) {
:host, :root {
margin-left: 0;
}
}

View File

@@ -5,7 +5,11 @@ import { useTheme } from "src/hooks";
import { changeLocale, getFlagCodeForLocale, localeOptions, T } from "src/locale"; import { changeLocale, getFlagCodeForLocale, localeOptions, T } from "src/locale";
import styles from "./LocalePicker.module.css"; import styles from "./LocalePicker.module.css";
function LocalePicker() { interface Props {
menuAlign?: "start" | "end";
}
function LocalePicker({ menuAlign = "start" }: Props) {
const { locale, setLocale } = useLocaleState(); const { locale, setLocale } = useLocaleState();
const { getTheme } = useTheme(); const { getTheme } = useTheme();
@@ -23,7 +27,10 @@ function LocalePicker() {
<button type="button" className={cns} data-bs-toggle="dropdown"> <button type="button" className={cns} data-bs-toggle="dropdown">
<Flag countryCode={getFlagCodeForLocale(locale)} /> <Flag countryCode={getFlagCodeForLocale(locale)} />
</button> </button>
<div className="dropdown-menu"> <div className={cn("dropdown-menu", {
"dropdown-menu-end": menuAlign === "end",
})}
>
{localeOptions.map((item) => { {localeOptions.map((item) => {
return ( return (
<a <a

View File

@@ -2,5 +2,5 @@ interface Props {
children: React.ReactNode; children: React.ReactNode;
} }
export function SiteContainer({ children }: Props) { export function SiteContainer({ children }: Props) {
return <div className="container-xl py-3">{children}</div>; return <div className="container-xl py-3 min-w-0 overflow-x-auto">{children}</div>;
} }

View File

@@ -25,7 +25,7 @@ export function SiteHeader() {
> >
<span className="navbar-toggler-icon" /> <span className="navbar-toggler-icon" />
</button> </button>
<div className="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3"> <div className="navbar-brand navbar-brand-autodark pe-0 pe-md-3">
<NavLink to="/"> <NavLink to="/">
<div className={styles.logo}> <div className={styles.logo}>
<img <img
@@ -48,11 +48,11 @@ export function SiteHeader() {
<ThemeSwitcher /> <ThemeSwitcher />
</div> </div>
</div> </div>
<div className="nav-item d-none d-md-flex me-3"> <div className="nav-item d-md-flex">
<div className="nav-item dropdown"> <div className="nav-item dropdown">
<a <a
href="/" href="/"
className="nav-link d-flex lh-1 p-0 px-2" className="nav-link d-flex lh-1"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-label="Open user menu" aria-label="Open user menu"
> >
@@ -70,6 +70,22 @@ export function SiteHeader() {
</div> </div>
</a> </a>
<div className="dropdown-menu dropdown-menu-end dropdown-menu-arrow"> <div className="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
<div className="d-md-none">
{/* biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: This div is not interactive. */}
<div className="p-2 pb-1 pe-1 d-flex align-items-center" onClick={e => e.stopPropagation()}>
<div className="ps-2 pe-1 me-auto">
<div>{currentUser?.nickname}</div>
<div className="mt-1 small text-secondary text-nowrap">
<T id={isAdmin ? "role.admin" : "role.standard-user"} />
</div>
</div>
<div className="d-flex align-items-center">
<ThemeSwitcher className="me-n2" />
<LocalePicker menuAlign="end" />
</div>
</div>
<div className="dropdown-divider" />
</div>
<a <a
href="?" href="?"
className="dropdown-item" className="dropdown-item"

View File

@@ -176,17 +176,13 @@ const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
}; };
export function SiteMenu() { export function SiteMenu() {
// This is hacky AF. But that's the price of using a non-react UI kit. const closeMenu = () => setTimeout(() => {
const closeMenus = () => { const navbarToggler = document.querySelector<HTMLElement>(".navbar-toggler");
const navMenus = document.querySelectorAll(".nav-item.dropdown"); const navbarMenu = document.querySelector("#navbar-menu");
navMenus.forEach((menu) => { if (navbarToggler && navbarMenu?.classList.contains("show")) {
menu.classList.remove("show"); navbarToggler.click();
const dropdown = menu.querySelector(".dropdown-menu"); }
if (dropdown) { }, 300);
dropdown.classList.remove("show");
}
});
};
return ( return (
<header className="navbar-expand-md"> <header className="navbar-expand-md">
@@ -198,7 +194,7 @@ export function SiteMenu() {
<ul className="navbar-nav"> <ul className="navbar-nav">
{menuItems.length > 0 && {menuItems.length > 0 &&
menuItems.map((item) => { menuItems.map((item) => {
return getMenuItem(item, closeMenus); return getMenuItem(item, closeMenu);
})} })}
</ul> </ul>
</div> </div>

View File

@@ -12,10 +12,12 @@ interface TableLayoutProps<TFields> {
function TableLayout<TFields>(props: TableLayoutProps<TFields>) { function TableLayout<TFields>(props: TableLayoutProps<TFields>) {
const hasRows = props.tableInstance.getRowModel().rows.length > 0; const hasRows = props.tableInstance.getRowModel().rows.length > 0;
return ( return (
<table className="table table-vcenter table-selectable mb-0"> <div className="table-responsive">
{hasRows ? <TableHeader tableInstance={props.tableInstance} /> : null} <table className="table table-vcenter table-selectable mb-0">
<TableBody {...props} /> {hasRows ? <TableHeader tableInstance={props.tableInstance} /> : null}
</table> <TableBody {...props} />
</table>
</div>
); );
} }

View File

@@ -1,10 +1,3 @@
.logo { .logo {
width: 200px; width: 200px;
} }
.helperBtns {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
}

View File

@@ -1,4 +1,3 @@
import cn from "classnames";
import { Field, Form, Formik } from "formik"; import { Field, Form, Formik } from "formik";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import Alert from "react-bootstrap/Alert"; import Alert from "react-bootstrap/Alert";
@@ -43,17 +42,17 @@ export default function Login() {
return ( return (
<Page className="page page-center"> <Page className="page page-center">
<div className={cn("d-none", "d-md-flex", styles.helperBtns)}>
<LocalePicker />
<ThemeSwitcher />
</div>
<div className="container container-tight py-4"> <div className="container container-tight py-4">
<div className="text-center mb-4"> <div className="d-flex justify-content-between align-items-center mb-4 ps-4 pe-3">
<img <img
className={styles.logo} className={styles.logo}
src="/images/logo-text-horizontal-grey.png" src="/images/logo-text-horizontal-grey.png"
alt="Nginx Proxy Manager" alt="Nginx Proxy Manager"
/> />
<div className="d-flex align-items-center gap-1">
<LocalePicker />
<ThemeSwitcher />
</div>
</div> </div>
<div className="card card-md"> <div className="card card-md">
<div className="card-body"> <div className="card-body">