mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
Compare commits
2 Commits
release
...
feature/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc5ccc482f | ||
|
|
fa7b02e214 |
@@ -10,24 +10,51 @@ export interface ComponentProps {
|
||||
const Header: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
// State for managing mobile menu visibility (optional, for hamburger menu)
|
||||
// Optional: State for managing mobile menu visibility if a hamburger menu is implemented by the consumer
|
||||
// const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div
|
||||
className={
|
||||
props.className || "relative flex h-16 justify-between bg-white"
|
||||
props.className ||
|
||||
"relative flex flex-col lg:flex-row lg:h-16 items-center justify-between bg-white p-4 lg:px-6" // Adjusted padding
|
||||
}
|
||||
>
|
||||
<div className="relative z-20 flex px-2 lg:px-0">
|
||||
{/* Left Components Area */}
|
||||
{/* On mobile, this area takes full width and space-between can be used if a hamburger is passed as a leftComponent */}
|
||||
{/* On desktop, it takes auto width */}
|
||||
<div className="relative z-20 flex w-full items-center justify-between lg:w-auto lg:justify-start">
|
||||
{props.leftComponents}
|
||||
{/* Example of where a consumer might place a hamburger button toggle:
|
||||
<div className="lg:hidden">
|
||||
<button onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}>Menu</button>
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
|
||||
{/* Center Components Area */}
|
||||
{/* On mobile, this stacks below left, takes full width, and centers its content */}
|
||||
{/* On desktop, it's absolutely positioned to truly center in the header */}
|
||||
{props.centerComponents && (
|
||||
<div className="relative z-0 flex flex-1 items-center justify-center px-2 sm:absolute sm:inset-0">
|
||||
<div
|
||||
className={`relative z-0 flex flex-1 items-center justify-center w-full mt-4 lg:mt-0 lg:absolute lg:inset-x-0`}
|
||||
>
|
||||
{props.centerComponents}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="hidden lg:relative lg:z-10 lg:ml-4 lg:flex lg:items-center">
|
||||
{/* Right Components Area */}
|
||||
{/* On mobile, this stacks below center, takes full width, and centers its content */}
|
||||
{/* On desktop, it's on the right, with a left margin */}
|
||||
{/* The visibility of these components on mobile (e.g. inside a collapsed menu) would be handled by the consumer
|
||||
by conditionally rendering `rightComponents` or by classes passed via `props.className` if a hamburger menu is implemented.
|
||||
For now, they will be visible and stacked on mobile.
|
||||
*/}
|
||||
<div
|
||||
className={`relative z-10 flex flex-col items-center lg:flex-row lg:items-center w-full lg:w-auto lg:ml-4 mt-4 lg:mt-0`}
|
||||
>
|
||||
{props.rightComponents}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import React, { FunctionComponent, ReactElement, useState } from "react";
|
||||
import Icon from "../Icon/Icon";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
|
||||
export interface ComponentProps {
|
||||
children: ReactElement | Array<ReactElement>;
|
||||
@@ -9,16 +11,88 @@ export interface ComponentProps {
|
||||
const Navbar: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
const className: string =
|
||||
props.className || "flex text-center lg:space-x-8 lg:py-2 bg-white ";
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState<boolean>(false);
|
||||
|
||||
const defaultClassName: string =
|
||||
"text-center lg:space-x-8 lg:py-2 bg-white";
|
||||
const navChildrenClassName: string = props.className || defaultClassName;
|
||||
|
||||
// Clone children to pass isRenderedOnMobile prop
|
||||
const childrenWithMobileProp = (isMobile: boolean): ReactElement[] => {
|
||||
return React.Children.map(props.children, (child) => {
|
||||
if (React.isValidElement(child)) {
|
||||
return React.cloneElement(child as ReactElement<any>, {
|
||||
isRenderedOnMobile: isMobile,
|
||||
});
|
||||
}
|
||||
return child;
|
||||
});
|
||||
};
|
||||
|
||||
const rightElementWithMobileProp = (isMobile: boolean): ReactElement | undefined => {
|
||||
if (props.rightElement && React.isValidElement(props.rightElement)) {
|
||||
return React.cloneElement(props.rightElement as ReactElement<any>, { isRenderedOnMobile: isMobile });
|
||||
}
|
||||
return props.rightElement;
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className={props.rightElement ? `flex justify-between` : ""}>
|
||||
<div data-testid="nav-children" className={className}>
|
||||
{props.children}
|
||||
<nav className={`bg-white ${props.rightElement ? "lg:flex lg:justify-between" : ""}`}>
|
||||
{/* Hamburger Menu Button */}
|
||||
<div className="flex items-center justify-between px-4 py-3 lg:hidden">
|
||||
{/* Placeholder for left-aligned items on mobile if any, e.g. a logo if props.children is empty or also hidden */}
|
||||
<div>
|
||||
{/* If there's a logo in props.children that should be visible on mobile, it needs specific handling.
|
||||
Assuming for now that props.children are primarily nav items.
|
||||
A common pattern is to have a dedicated logo prop or first child treatment.
|
||||
*/}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsMobileMenuOpen(!isMobileMenuOpen);
|
||||
}}
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span className="sr-only">Open main menu</span>
|
||||
{isMobileMenuOpen ? (
|
||||
<Icon icon={IconProp.Close} className="block h-6 w-6" />
|
||||
) : (
|
||||
<Icon icon={IconProp.HamburgerMenu} className="block h-6 w-6" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{props.rightElement && (
|
||||
<div className={className}>{props.rightElement}</div>
|
||||
|
||||
{/* Desktop Menu */}
|
||||
<div className={`hidden lg:flex ${props.rightElement ? "w-auto" : "w-full"}`}>
|
||||
<div
|
||||
data-testid="nav-children-desktop"
|
||||
className={`${navChildrenClassName} hidden lg:flex ${props.rightElement ? "" : "w-full justify-center"}`}
|
||||
>
|
||||
{childrenWithMobileProp(false)}
|
||||
</div>
|
||||
{props.rightElement && (
|
||||
<div className={`${navChildrenClassName} hidden lg:flex`}>
|
||||
{rightElementWithMobileProp(false)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{isMobileMenuOpen && (
|
||||
<div className="lg:hidden" id="mobile-menu">
|
||||
<div className="space-y-1 px-2 pt-2 pb-3 sm:px-3">
|
||||
{childrenWithMobileProp(true)}
|
||||
</div>
|
||||
{props.rightElement && (
|
||||
<div className="border-t border-gray-200 pt-4 pb-3">
|
||||
<div className="space-y-1 px-2">
|
||||
{rightElementWithMobileProp(true)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import Icon, { ThickProp } from "../Icon/Icon";
|
||||
import Link from "../Link/Link";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import React, { FunctionComponent, ReactElement, useState, useEffect } from "react";
|
||||
|
||||
export interface ComponentProps {
|
||||
title: string;
|
||||
@@ -13,7 +13,7 @@ export interface ComponentProps {
|
||||
activeRoute?: undefined | Route;
|
||||
exact?: boolean;
|
||||
children?: undefined | ReactElement | Array<ReactElement>;
|
||||
isRenderedOnMobile?: boolean;
|
||||
isRenderedOnMobile?: boolean; // Passed by NavBar.tsx
|
||||
onMouseOver?: (() => void) | undefined;
|
||||
onClick?: (() => void) | undefined;
|
||||
onMouseLeave?: (() => void) | undefined;
|
||||
@@ -23,6 +23,8 @@ export interface ComponentProps {
|
||||
const NavBarItem: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
const [isMobileSubmenuOpen, setIsMobileSubmenuOpen] = useState<boolean>(false);
|
||||
|
||||
const activeRoute: Route | undefined = props.activeRoute || props.route;
|
||||
const isActive: boolean = Boolean(
|
||||
activeRoute &&
|
||||
|
||||
@@ -9,36 +9,67 @@ export interface ComponentProps {
|
||||
description: string;
|
||||
link: URL;
|
||||
};
|
||||
isMobileView?: boolean; // Added to control styling, passed from NavBarItem
|
||||
}
|
||||
|
||||
const NavBarItem: FunctionComponent<ComponentProps> = (
|
||||
const NavBarMenu: FunctionComponent<ComponentProps> = ( // Renamed component
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
let children: Array<ReactElement>;
|
||||
let childrenElements: Array<ReactElement>;
|
||||
if (!Array.isArray(props.children) && props.children) {
|
||||
children = [props.children];
|
||||
childrenElements = [props.children];
|
||||
} else {
|
||||
children = props.children;
|
||||
childrenElements = props.children as Array<ReactElement>;
|
||||
}
|
||||
|
||||
// Base classes
|
||||
let mainContainerClasses: string = "";
|
||||
let childrenOuterContainerClasses: string = "overflow-hidden rounded-lg";
|
||||
let childrenInnerContainerClasses: string = "relative grid bg-white";
|
||||
let footerContainerClasses: string = "bg-gray-50";
|
||||
let footerLinkClasses: string = "-m-3 flow-root rounded-md p-3 transition duration-150 ease-in-out hover:bg-gray-100";
|
||||
let footerTitleClasses: string = "text-base font-medium text-gray-900";
|
||||
let footerDescriptionClasses: string = "mt-1 block text-sm text-gray-500 text-left";
|
||||
|
||||
|
||||
if (props.isMobileView) {
|
||||
// Mobile specific classes: Displayed inline, full width, simplified padding
|
||||
mainContainerClasses = "w-full mt-1"; // No absolute positioning, simpler margin for nesting
|
||||
childrenOuterContainerClasses = "rounded-md border border-gray-200"; // Simple border for mobile
|
||||
childrenInnerContainerClasses += " gap-3 p-4 grid-cols-1"; // Simplified padding, single column
|
||||
footerContainerClasses += " p-4 border-t border-gray-200"; // Simplified padding
|
||||
footerLinkClasses = "block p-2 hover:bg-gray-100 rounded-md"; // Simpler link styling for mobile
|
||||
footerTitleClasses = "text-sm font-medium text-gray-800";
|
||||
footerDescriptionClasses = "mt-1 block text-xs text-gray-500";
|
||||
|
||||
} else {
|
||||
// Desktop specific classes: Dropdown menu
|
||||
mainContainerClasses = "absolute left-1/3 z-10 mt-10 w-screen max-w-md -translate-x-1/2 transform px-2 sm:px-0 lg:max-w-3xl";
|
||||
childrenOuterContainerClasses += " shadow-lg ring-1 ring-black ring-opacity-5"; // Desktop shadow
|
||||
childrenInnerContainerClasses += " gap-6 px-5 py-6 sm:gap-8 sm:p-8 lg:grid-cols-2";
|
||||
footerContainerClasses += " p-5 sm:p-8";
|
||||
// footerLinkClasses, footerTitleClasses, footerDescriptionClasses retain their defaults for desktop
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="absolute left-1/3 z-10 mt-10 w-screen max-w-md -translate-x-1/2 transform px-2 sm:px-0 lg:max-w-3xl">
|
||||
<div className="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5">
|
||||
<div className="relative grid gap-6 bg-white px-5 py-6 sm:gap-8 sm:p-8 lg:grid-cols-2">
|
||||
{children}
|
||||
<div className={mainContainerClasses}>
|
||||
<div className={childrenOuterContainerClasses}>
|
||||
<div className={childrenInnerContainerClasses}>
|
||||
{childrenElements}
|
||||
</div>
|
||||
{props.footer && (
|
||||
<div className="bg-gray-50 p-5 sm:p-8">
|
||||
<div className={footerContainerClasses}>
|
||||
<Link
|
||||
to={props.footer.link}
|
||||
openInNewTab={true}
|
||||
className="-m-3 flow-root rounded-md p-3 transition duration-150 ease-in-out hover:bg-gray-100"
|
||||
className={footerLinkClasses}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<span className="text-base font-medium text-gray-900">
|
||||
<span className={footerTitleClasses}>
|
||||
{props.footer.title}
|
||||
</span>
|
||||
</span>
|
||||
<span className="mt-1 block text-sm text-gray-500 text-left">
|
||||
<span className={footerDescriptionClasses}>
|
||||
{props.footer.description}
|
||||
</span>
|
||||
</Link>
|
||||
@@ -49,4 +80,4 @@ const NavBarItem: FunctionComponent<ComponentProps> = (
|
||||
);
|
||||
};
|
||||
|
||||
export default NavBarItem;
|
||||
export default NavBarMenu; // Renamed export
|
||||
|
||||
Reference in New Issue
Block a user