import { memo, useState, useEffect, useCallback, forwardRef } from "react"; import type { ReactNode } from "react"; import { tss } from "../tss"; import { breakpointsValues } from "../theme"; import { Text } from "onyxia-ui/Text"; import { GlHeaderLinks } from "./GlHeaderLinks"; import { DarkModeSwitch } from "onyxia-ui/DarkModeSwitch"; import { GlGithubStarCount } from "../shared/GlGithubStarCount"; import UnfoldIcon from "@mui/icons-material/FormatLineSpacing"; import { useConstCallback } from "powerhooks/useConstCallback"; import { useClickAway } from "powerhooks/useClickAway"; import { getScrollableParent } from "powerhooks/getScrollableParent"; import { useDomRect } from "powerhooks/useDomRect"; import { useEvt } from "evt/hooks/useEvt"; import { Evt } from "evt"; import { useMergeRefs } from "powerhooks/useMergeRefs"; import { useStateRef } from "powerhooks/useStateRef"; type Behavior = "hide" | "wrap" | "normal"; type CustomItem = { item: NonNullable; behaviorOnSmallDevice?: Behavior; }; export type GlHeaderProps = { links: { label: ReactNode; href: string; onClick?: () => void; isActive?: boolean; }[]; title?: ReactNode; titleDark?: ReactNode; titleSmallScreen?: ReactNode; titleSmallScreenDark?: ReactNode; customBreakpoint?: number; className?: string; classes?: Partial["classes"]>; enableDarkModeSwitch?: boolean; githubRepoUrl?: string; githubButtonSize?: "normal" | "large"; showGithubStarCount?: boolean; customItemStart?: CustomItem; customItemEnd?: CustomItem; }; export const GlHeader = memo( forwardRef((props, ref_forwarded) => { const { links, className, customItemEnd, customItemStart, enableDarkModeSwitch, githubButtonSize, githubRepoUrl, showGithubStarCount, title, titleDark, titleSmallScreen, titleSmallScreenDark, customBreakpoint, } = props; const [isMenuUnfolded, setIsMenuUnfolded] = useState(false); const [isSmallDevice, setIsSmallDevice] = useState( undefined, ); const [breakpoint, setBreakpoint] = useState( undefined, ); const toggleMenuUnfolded = useConstCallback(() => { setIsMenuUnfolded(!isMenuUnfolded); }); const ref = useStateRef(null); const { ref: ref_useClickAway } = useClickAway({ "onClickAway": () => { setIsMenuUnfolded(false); }, }); const { domRect: { height: headerHeight, width: headerWidth }, } = useDomRect({ ref }); const setRootElement = useMergeRefs([ ref, ref_forwarded, ref_useClickAway, ]); const { ref: titleRef, domRect: { width: titleWidth }, } = useDomRect(); const { ref: buttonsAndLinksRef, domRect: { width: buttonsAndLinksWidth }, } = useDomRect(); useEffect(() => { if (isSmallDevice) { return; } setIsMenuUnfolded(false); }, [isSmallDevice]); useEffect(() => { if ( isSmallDevice || titleWidth === 0 || buttonsAndLinksWidth === 0 || headerWidth === 0 || customBreakpoint !== undefined ) { return; } const contentWidth = titleWidth + buttonsAndLinksWidth + theme.spacing(7) + 2 * theme.paddingRightLeft; if (headerWidth < contentWidth && breakpoint !== undefined) { return; } setBreakpoint(contentWidth); }, [titleWidth, buttonsAndLinksWidth, headerWidth, isSmallDevice]); useEvt( ctx => { if (!ref.current) { return; } const scrollableParent = getScrollableParent({ "element": ref.current, "doReturnElementIfScrollable": true, }); Evt.from(ctx, scrollableParent, "scroll").attach(() => { const { scrollTop } = scrollableParent; if (headerHeight < scrollTop) { setIsMenuUnfolded(false); } }); }, [ref.current, headerHeight], ); const getCustomItemBehavior = useCallback( (customItem: CustomItem | undefined): Behavior => { if (customItem === undefined) { return "normal"; } return customItem.behaviorOnSmallDevice ?? "normal"; }, [ customItemStart?.behaviorOnSmallDevice, customItemEnd?.behaviorOnSmallDevice, ], ); const { theme, classes, cx } = useStyles({ isSmallDevice, "customItemStartSmallBehavior": getCustomItemBehavior(customItemStart), "customItemEndSmallBehavior": getCustomItemBehavior(customItemEnd), "classesOverrides": props.classes, }); useEffect(() => { if (customBreakpoint !== undefined) { setIsSmallDevice(theme.windowInnerWidth < customBreakpoint); return; } if (breakpoint === undefined) { return; } setIsSmallDevice( theme.windowInnerWidth < breakpoint || theme.windowInnerWidth < breakpointsValues.sm, ); }, [theme.windowInnerWidth, breakpoint]); return (
{(() => { return (
{(() => { const transformElementIfString = ( element: ReactNode, ) => { if (typeof element === "string") { return ( {element} ); } return element; }; if ( theme.windowInnerWidth >= breakpointsValues.md ) { if (!theme.isDarkModeEnabled) { return transformElementIfString( title, ); } return transformElementIfString( titleDark ?? title, ); } if (!theme.isDarkModeEnabled) { return transformElementIfString( titleSmallScreen ?? title, ); } return transformElementIfString( titleSmallScreenDark ?? titleSmallScreen ?? titleDark ?? title, ); })()}
); })()}
{customItemStart !== undefined && (
{customItemStart.item}
)}
({ ...link, "classes": { "link": classes.link, "underline": classes.underline, }, "className": classes.linkRoot, }))} type="largeScreen" />
{githubRepoUrl !== undefined && ( )} {enableDarkModeSwitch !== undefined && enableDarkModeSwitch && ( )} {customItemEnd !== undefined && (
{customItemEnd.item}
)}
{(customItemStart !== undefined || customItemEnd !== undefined) && (
{customItemStart !== undefined && (
{customItemStart.item}
)} {customItemEnd !== undefined && (
{customItemEnd.item}
)}
)} ({ ...link, "classes": { "link": classes.linkSmallScreen, "root": classes.linkRootSmallScreen, "underline": classes.underlineSmallScreen, }, }))} className={classes.smallDeviceLinks} type="smallScreen" isUnfolded={isMenuUnfolded} />
); }), ); const useStyles = tss .withName({ GlHeader }) .withParams<{ isSmallDevice: boolean | undefined; customItemStartSmallBehavior: Behavior; customItemEndSmallBehavior: Behavior; }>() .create( ({ theme, isSmallDevice, customItemEndSmallBehavior, customItemStartSmallBehavior, }) => { function getSmallDeviceCustomItemDisplay(behavior: Behavior) { if (isSmallDevice && behavior === "wrap") { return undefined; } return "none"; } function getCustomItemDisplay(behavior: Behavior) { if (!isSmallDevice) { return undefined; } return behavior === "normal" ? undefined : "none"; } return { "root": { "padding": theme.spacing({ "rightLeft": `${theme.paddingRightLeft}px`, "topBottom": `${theme.spacing(3)}px`, }), "position": "relative", "opacity": isSmallDevice === undefined ? 0 : 1, "maxWidth": "100%", "overflowX": !isSmallDevice ? "hidden" : undefined, "overflowY": !isSmallDevice ? "hidden" : undefined, "backdropFilter": "blur(10px)", }, "largeScreenContentWrapper": { "display": "flex", "justifyContent": "space-between", "alignItems": "center", }, "titleWrapper": { "marginRight": isSmallDevice ? undefined : theme.spacing(8), }, "titleText": { "whiteSpace": "nowrap", }, "linkAndButtonWrapper": { "display": "grid", "gridAutoFlow": "column", "alignItems": "center", "gap": theme.spacing(3), }, "link": { "marginTop": theme.spacing(1), }, "smallDeviceLinks": { "position": "absolute", "top": "100%", "left": 0, "width": "100%", }, "unfoldIconWrapper": { "display": isSmallDevice ? "flex" : "none", "alignItems": "center", }, "links": { "order": 2, "display": isSmallDevice ? "none" : "flex", "pointerEvents": isSmallDevice ? "none" : undefined, }, "linksWrapperLargeScreen": { "order": isSmallDevice ? -1 : undefined, }, "smallDeviceCustomItemsWrapper": { "display": !isSmallDevice ? "none" : "grid", "gridAutoFlow": "row", "alignItems": "end", ...(() => { const value = theme.spacing(3); return { "gap": value, ...theme.spacing.topBottom("margin", `${value}px`), }; })(), }, "commonSmallDeviceCustomItemWrapper": { "display": "flex", "justifyContent": "flex-end", }, "smallDeviceCustomItemStartWrapper": { "display": getSmallDeviceCustomItemDisplay( customItemStartSmallBehavior, ), }, "smallDeviceCustomItemEndWrapper": { "display": getSmallDeviceCustomItemDisplay( customItemEndSmallBehavior, ), }, "customItemStartWrapper": { "display": getCustomItemDisplay( customItemStartSmallBehavior, ), }, "customItemEndWrapper": { "display": getCustomItemDisplay(customItemEndSmallBehavior), }, "commonCustomItemWrapper": {}, "unfoldIcon": {}, "githubStar": {}, "darkModeSwitch": {}, "linkRoot": {}, "underline": {}, "linkRootSmallScreen": {}, "underlineSmallScreen": {}, "linkSmallScreen": {}, "linksContentWrapper": {}, "linksContentWrapperSmallScreen": {}, "linksOverline": {}, }; }, );