import React, { useMemo, VFC } from "react";
import styled, { css } from "styled-components";
import type { CommonProps, WithThemeSchemeProp } from "../types";
import { AppRoute } from "../routes.defs";
import { Const } from "../const";
import { NamedColors } from "../utils/style";
import { buildRouteLink } from "../utils/shared";
import { PageWrapper } from "./PageWrapper";
import { BurgerMenuIcon } from "./icons/BurgerMenuIcon";
import { MoonIcon } from "./icons/MoonIcon";
import { SunIcon } from "./icons/SunIcon";
import { PlusIcon } from "./icons/PlusIcon";
interface PageHeaderProps extends CommonProps {
forceShowLogo?: boolean;
setDrawerPrimaryOpen?: (predicate: (prev: boolean) => boolean) => void;
setDrawerSettingsOpen?: (predicate: (prev: boolean) => boolean) => void;
}
export const PageHeader: VFC<PageHeaderProps & WithThemeSchemeProp> = ({
commonProps,
themeScheme,
forceShowLogo = true,
setDrawerPrimaryOpen = undefined,
setDrawerSettingsOpen = undefined,
}) => {
const invertThemeScheme = themeScheme === "light" ? "dark" : "light";
const toggleDrawerPrimary = () => {
if (setDrawerPrimaryOpen) {
setDrawerPrimaryOpen((prev) => !prev);
}
if (setDrawerSettingsOpen) {
setDrawerSettingsOpen((prev) => !prev);
}
};
const pageHeaderActions = useMemo(() => {
if (commonProps.authenticated) {
return (
<>
<a
aria-label={"View your profile and repositories"}
title={`View @${commonProps.currentUserUsername || "ghost"} profile and settings`}
href={buildRouteLink(
AppRoute.USER_DETAILS,
{
username: commonProps.currentUserUsername || "ghost",
},
{ encodeURIComponent: false },
)}
>
<PageHeaderAvatar
aria-label={commonProps.currentUserUsername || "ghost"}
themeScheme={themeScheme}
src={commonProps.currentUserAvatarUri || ""}
/>
</a>
</>
);
}
return (
<>
<a
aria-label={"Register a new account"}
href={buildRouteLink(AppRoute.AUTH_REGISTER, null)}
>
Register
</a>
<a
aria-label={"Login to your account"}
href={buildRouteLink(AppRoute.AUTH_LOGIN, null)}
>
Login
</a>
</>
);
}, [commonProps.authenticated]);
return (
<StyledPageHeader themeScheme={themeScheme}>
<PageWrapper style={{ gap: 12 }}>
<StyledBurgerMenu
themeScheme={themeScheme}
onClick={toggleDrawerPrimary}
>
<BurgerMenuIcon
color={NamedColors.TEXT_DEFAULT[themeScheme]}
size={24}
/>
</StyledBurgerMenu>
<StyledLogoArea themeScheme={themeScheme} forceShowLogo={forceShowLogo}>
<a href={"/"}>
<h1>{Const.APP_NAME}</h1>
</a>
</StyledLogoArea>
<StyledPageHeaderNav themeScheme={themeScheme}>
<a
aria-label={"Explore Repositories"}
href={buildRouteLink(AppRoute.REPOSITORY_EXPLORE, null)}
className={
commonProps.path ===
buildRouteLink(AppRoute.REPOSITORY_EXPLORE, null)
? "active"
: undefined
}
>
Explore
</a>
<a
aria-label={"Contribute to GitFOSS development"}
href={buildRouteLink(AppRoute.REPOSITORY_DETAILS, {
orgSlug: "ethicdevs",
repoSlug: "gitfoss",
})}
className={
commonProps.path!.startsWith(
buildRouteLink(AppRoute.REPOSITORY_DETAILS, {
orgSlug: "ethicdevs",
repoSlug: "gitfoss",
}),
)
? "active"
: undefined
}
>
Contribute
</a>
</StyledPageHeaderNav>
<div style={{ flex: 1 }} />
<StyledActionsArea>
{commonProps.authenticated && (
<a
aria-label={"Create a new Repository"}
title={"New Repository"}
href={buildRouteLink(AppRoute.REPOSITORY_CREATE, null)}
>
<PlusIcon
color={NamedColors.TEXT_DEFAULT[themeScheme]}
size={24}
/>
</a>
)}
<a
data-smooth-scroll={"disabled"}
aria-label={`Switch to ${invertThemeScheme} theme`}
title={`Switch to ${invertThemeScheme} theme`}
href={buildRouteLink(AppRoute.THEME_SET_SCHEME_ACTION, {
themeScheme: invertThemeScheme,
})}
>
{themeScheme === "light" ? (
<MoonIcon
color={NamedColors.TEXT_DEFAULT[themeScheme]}
size={24}
/>
) : (
<SunIcon
color={NamedColors.TEXT_DEFAULT[themeScheme]}
color2={NamedColors.BRAND_LINE[themeScheme]}
size={24}
/>
)}
</a>
{pageHeaderActions}
</StyledActionsArea>
</PageWrapper>
</StyledPageHeader>
);
};
const StyledBurgerMenu = styled.button<WithThemeSchemeProp>`
${({ themeScheme }) => css`
display: flex;
justify-content: center;
align-items: center;
/* above mobile size */
@media only screen and (min-width: 768px) {
& {
display: none;
}
}
width: 44px;
min-width: 44px;
max-width: 44px;
height: 44px;
min-height: 44px;
max-height: 44px;
padding-top: -2px;
font-size: 20px;
font-weight: thin;
color: ${NamedColors.TEXT_MUTED[themeScheme]};
background: ${NamedColors.HEADER_NAV_PILL[themeScheme]};
border-image: none;
border: none;
border-radius: 22px;
cursor: pointer;
&.active,
&:hover {
color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
background: ${NamedColors.CARD[themeScheme]};
}
&:active {
font-size: 22px;
}
`}
`;
const StyledPageHeader = styled.header<WithThemeSchemeProp>`
display: flex;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: center;
height: 100%;
width: 100%;
/* above mobile size */
@media only screen and (min-width: 768px) {
& > ${PageWrapper} {
padding: 0 8px;
}
}
& > ${PageWrapper} {
height: 100%;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: center;
padding: 0;
gap: 16px;
}
& a {
transition: color 140ms ease-in-out 0s;
text-decoration: none;
${({ themeScheme }) => css`
color: ${NamedColors.TEXT_MUTED[themeScheme]};
`};
&:hover {
${({ themeScheme }) => css`
color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
`};
text-decoration: underline;
}
}
`;
const StyledLogoArea = styled.div<
WithThemeSchemeProp & { forceShowLogo: boolean }
>`
${({ forceShowLogo }) =>
forceShowLogo !== true &&
css`
@media only screen and (min-width: 768px) {
display: none;
}
`};
@media only screen and (max-width: 768px) {
& > a > h1 {
font-size: 22px;
}
}
& > a {
display: flex;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: center;
${({ themeScheme }) => css`
color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
`};
h1 {
margin: 0;
}
}
`;
const StyledPageHeaderNav = styled.nav<WithThemeSchemeProp>`
display: flex;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: center;
/* flex: 1; */
height: 40px;
/* width: 100%; */
gap: 2px;
margin: 0;
${({ themeScheme }) => css`
border-radius: 20px;
background-color: ${NamedColors.HEADER_NAV_PILL[themeScheme]};
`};
@media only screen and (max-width: 768px) {
display: none;
}
& > a {
display: flex;
justify-content: center;
align-items: center;
white-space: nowrap;
height: 40px;
min-width: 120px;
padding: 0 16px;
border-radius: 20px;
font-weight: normal;
text-decoration: none;
${({ themeScheme }) => css`
color: ${NamedColors.TEXT_MUTED[themeScheme]};
/* background-color: ${NamedColors.CARD[themeScheme]}; */
`};
&.active,
&:hover {
${({ themeScheme }) => css`
color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
background-color: ${NamedColors.CARD[themeScheme]};
font-weight: bold;
font-family: monospace;
text-decoration: none;
`};
}
}
`;
const StyledActionsArea = styled.div`
display: flex;
flex-flow: row nowrap;
justify-content: flex-end;
align-items: center;
& > a {
margin-left: 12px;
white-space: nowrap;
}
`;
const PageHeaderAvatar = styled.img<WithThemeSchemeProp>`
width: 40px;
height: 40px;
border-image: none;
border-radius: 40px;
${({ themeScheme }) => css`
border: 1px solid ${NamedColors.BORDER_CARD[themeScheme]};
background-color: ${NamedColors.CARD_OVERLAY[themeScheme]};
`};
`;
.ts
TypeScript
(application/typescript)
import React, { useMemo, VFC } from "react";
import styled, { css } from "styled-components";
import type { CommonProps, WithThemeSchemeProp } from "../types";
import { AppRoute } from "../routes.defs";
import { Const } from "../const";
import { NamedColors } from "../utils/style";
import { buildRouteLink } from "../utils/shared";
import { PageWrapper } from "./PageWrapper";
import { BurgerMenuIcon } from "./icons/BurgerMenuIcon";
import { MoonIcon } from "./icons/MoonIcon";
import { SunIcon } from "./icons/SunIcon";
import { PlusIcon } from "./icons/PlusIcon";
interface PageHeaderProps extends CommonProps {
forceShowLogo?: boolean;
setDrawerPrimaryOpen?: (predicate: (prev: boolean) => boolean) => void;
setDrawerSettingsOpen?: (predicate: (prev: boolean) => boolean) => void;
}
export const PageHeader: VFC<PageHeaderProps & WithThemeSchemeProp> = ({
commonProps,
themeScheme,
forceShowLogo = true,
setDrawerPrimaryOpen = undefined,
setDrawerSettingsOpen = undefined,
}) => {
const invertThemeScheme = themeScheme === "light" ? "dark" : "light";
const toggleDrawerPrimary = () => {
if (setDrawerPrimaryOpen) {
setDrawerPrimaryOpen((prev) => !prev);
}
if (setDrawerSettingsOpen) {
setDrawerSettingsOpen((prev) => !prev);
}
};
const pageHeaderActions = useMemo(() => {
if (commonProps.authenticated) {
return (
<>
<a
aria-label={"View your profile and repositories"}
title={`View @${commonProps.currentUserUsername || "ghost"} profile and settings`}
href={buildRouteLink(
AppRoute.USER_DETAILS,
{
username: commonProps.currentUserUsername || "ghost",
},
{ encodeURIComponent: false },
)}
>
<PageHeaderAvatar
aria-label={commonProps.currentUserUsername || "ghost"}
themeScheme={themeScheme}
src={commonProps.currentUserAvatarUri || ""}
/>
</a>
</>
);
}
return (
<>
<a
aria-label={"Register a new account"}
href={buildRouteLink(AppRoute.AUTH_REGISTER, null)}
>
Register
</a>
<a
aria-label={"Login to your account"}
href={buildRouteLink(AppRoute.AUTH_LOGIN, null)}
>
Login
</a>
</>
);
}, [commonProps.authenticated]);
return (
<StyledPageHeader themeScheme={themeScheme}>
<PageWrapper style={{ gap: 12 }}>
<StyledBurgerMenu
themeScheme={themeScheme}
onClick={toggleDrawerPrimary}
>
<BurgerMenuIcon
color={NamedColors.TEXT_DEFAULT[themeScheme]}
size={24}
/>
</StyledBurgerMenu>
<StyledLogoArea themeScheme={themeScheme} forceShowLogo={forceShowLogo}>
<a href={"/"}>
<h1>{Const.APP_NAME}</h1>
</a>
</StyledLogoArea>
<StyledPageHeaderNav themeScheme={themeScheme}>
<a
aria-label={"Explore Repositories"}
href={buildRouteLink(AppRoute.REPOSITORY_EXPLORE, null)}
className={
commonProps.path ===
buildRouteLink(AppRoute.REPOSITORY_EXPLORE, null)
? "active"
: undefined
}
>
Explore
</a>
<a
aria-label={"Contribute to GitFOSS development"}
href={buildRouteLink(AppRoute.REPOSITORY_DETAILS, {
orgSlug: "ethicdevs",
repoSlug: "gitfoss",
})}
className={
commonProps.path!.startsWith(
buildRouteLink(AppRoute.REPOSITORY_DETAILS, {
orgSlug: "ethicdevs",
repoSlug: "gitfoss",
}),
)
? "active"
: undefined
}
>
Contribute
</a>
</StyledPageHeaderNav>
<div style={{ flex: 1 }} />
<StyledActionsArea>
{commonProps.authenticated && (
<a
aria-label={"Create a new Repository"}
title={"New Repository"}
href={buildRouteLink(AppRoute.REPOSITORY_CREATE, null)}
>
<PlusIcon
color={NamedColors.TEXT_DEFAULT[themeScheme]}
size={24}
/>
</a>
)}
<a
data-smooth-scroll={"disabled"}
aria-label={`Switch to ${invertThemeScheme} theme`}
title={`Switch to ${invertThemeScheme} theme`}
href={buildRouteLink(AppRoute.THEME_SET_SCHEME_ACTION, {
themeScheme: invertThemeScheme,
})}
>
{themeScheme === "light" ? (
<MoonIcon
color={NamedColors.TEXT_DEFAULT[themeScheme]}
size={24}
/>
) : (
<SunIcon
color={NamedColors.TEXT_DEFAULT[themeScheme]}
color2={NamedColors.BRAND_LINE[themeScheme]}
size={24}
/>
)}
</a>
{pageHeaderActions}
</StyledActionsArea>
</PageWrapper>
</StyledPageHeader>
);
};
const StyledBurgerMenu = styled.button<WithThemeSchemeProp>`
${({ themeScheme }) => css`
display: flex;
justify-content: center;
align-items: center;
/* above mobile size */
@media only screen and (min-width: 768px) {
& {
display: none;
}
}
width: 44px;
min-width: 44px;
max-width: 44px;
height: 44px;
min-height: 44px;
max-height: 44px;
padding-top: -2px;
font-size: 20px;
font-weight: thin;
color: ${NamedColors.TEXT_MUTED[themeScheme]};
background: ${NamedColors.HEADER_NAV_PILL[themeScheme]};
border-image: none;
border: none;
border-radius: 22px;
cursor: pointer;
&.active,
&:hover {
color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
background: ${NamedColors.CARD[themeScheme]};
}
&:active {
font-size: 22px;
}
`}
`;
const StyledPageHeader = styled.header<WithThemeSchemeProp>`
display: flex;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: center;
height: 100%;
width: 100%;
/* above mobile size */
@media only screen and (min-width: 768px) {
& > ${PageWrapper} {
padding: 0 8px;
}
}
& > ${PageWrapper} {
height: 100%;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: center;
padding: 0;
gap: 16px;
}
& a {
transition: color 140ms ease-in-out 0s;
text-decoration: none;
${({ themeScheme }) => css`
color: ${NamedColors.TEXT_MUTED[themeScheme]};
`};
&:hover {
${({ themeScheme }) => css`
color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
`};
text-decoration: underline;
}
}
`;
const StyledLogoArea = styled.div<
WithThemeSchemeProp & { forceShowLogo: boolean }
>`
${({ forceShowLogo }) =>
forceShowLogo !== true &&
css`
@media only screen and (min-width: 768px) {
display: none;
}
`};
@media only screen and (max-width: 768px) {
& > a > h1 {
font-size: 22px;
}
}
& > a {
display: flex;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: center;
${({ themeScheme }) => css`
color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
`};
h1 {
margin: 0;
}
}
`;
const StyledPageHeaderNav = styled.nav<WithThemeSchemeProp>`
display: flex;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: center;
/* flex: 1; */
height: 40px;
/* width: 100%; */
gap: 2px;
margin: 0;
${({ themeScheme }) => css`
border-radius: 20px;
background-color: ${NamedColors.HEADER_NAV_PILL[themeScheme]};
`};
@media only screen and (max-width: 768px) {
display: none;
}
& > a {
display: flex;
justify-content: center;
align-items: center;
white-space: nowrap;
height: 40px;
min-width: 120px;
padding: 0 16px;
border-radius: 20px;
font-weight: normal;
text-decoration: none;
${({ themeScheme }) => css`
color: ${NamedColors.TEXT_MUTED[themeScheme]};
/* background-color: ${NamedColors.CARD[themeScheme]}; */
`};
&.active,
&:hover {
${({ themeScheme }) => css`
color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
background-color: ${NamedColors.CARD[themeScheme]};
font-weight: bold;
font-family: monospace;
text-decoration: none;
`};
}
}
`;
const StyledActionsArea = styled.div`
display: flex;
flex-flow: row nowrap;
justify-content: flex-end;
align-items: center;
& > a {
margin-left: 12px;
white-space: nowrap;
}
`;
const PageHeaderAvatar = styled.img<WithThemeSchemeProp>`
width: 40px;
height: 40px;
border-image: none;
border-radius: 40px;
${({ themeScheme }) => css`
border: 1px solid ${NamedColors.BORDER_CARD[themeScheme]};
background-color: ${NamedColors.CARD_OVERLAY[themeScheme]};
`};
`;