Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"src/pages/report/**",
"src/fallback/**",
"src/scripts/**",
"tests/**",
"node_modules/**",
"public/docs/css/fa/**",
"assets/diagrams/**"
Expand Down
1 change: 1 addition & 0 deletions dictionary-octopus.txt
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ netstandard
newcert
newguid
NEWID
newnav
Newtonsoft
nfsadmin
nlog
Expand Down
143 changes: 143 additions & 0 deletions src/components/Avatar.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
---
import type { HTMLAttributes } from 'astro/types';
import type { AvatarShape, AvatarSize } from '../lib/avatar';
type SharedProps = {
size: AvatarSize;
shape: AvatarShape;
src?: string;
srcSet?: string;
alt?: string;
isMuted?: boolean;
title?: string;
};
type Props =
| (HTMLAttributes<'div'> & SharedProps & { href?: never })
| (HTMLAttributes<'a'> & SharedProps & { href: string | URL });
const {
size,
shape,
src,
srcSet,
alt = '',
isMuted = false,
title,
href,
class: className,
...rest
} = Astro.props satisfies Props;
const classes = `avatar avatar--${size} avatar--${shape}${isMuted ? ' avatar--muted' : ''}${className ? ` ${className}` : ''}`;
const Tag = href ? 'a' : 'div';
---

<Tag
class={classes}
title={title}
{...href ? { href } : {}}
{...rest}
data-avatar
data-avatar-size={size}
>
<span class="avatar__fallback" data-avatar-fallback><slot /></span>
<img
class="avatar__image"
src={src}
srcset={srcSet}
alt={alt}
data-avatar-image
hidden
/>
</Tag>

<script>
import '../scripts/avatar';
</script>

<style>
.avatar {
position: relative;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
overflow: hidden;
container-type: inline-size;
background: var(--colorAvatarBackgroundUserDefault);
color: var(--colorAvatarText);
font-weight: 400;
text-decoration: none;
text-transform: uppercase;
user-select: none;
transition: background-color 150ms ease-in-out;
}

.avatar[hidden] {
display: none;
}

.avatar--small {
width: 20px;
height: 20px;
}

.avatar--medium {
width: 36px;
height: 36px;
}

.avatar--large {
width: 48px;
height: 48px;
}

.avatar--circle {
border-radius: var(--borderRadiusCircle);
}

.avatar--rounded {
border-radius: var(--borderRadiusSmall);
}

.avatar--muted {
opacity: 0.4;
}

.avatar__fallback {
display: block;
margin: 0;
color: var(--colorAvatarText);
font-size: 44cqi;
}

.avatar__fallback[hidden] {
display: none;
}

.avatar__image {
width: 100%;
height: 100%;
aspect-ratio: 1 / 1;
background: var(--colorAvatarBackgroundImageDefault);
object-fit: scale-down;
transition: background-color 150ms ease-in-out;
}

a.avatar:hover,
a.avatar:focus-visible {
background: var(--colorAvatarBackgroundUserHover);
color: var(--colorAvatarText);
}

a.avatar:focus-visible {
outline: var(--borderWidth2) solid var(--colorBorderSelected);
outline-offset: var(--borderWidth2);
}

a.avatar:hover .avatar__image {
background: var(--colorAvatarBackgroundImageHover);
}
</style>
36 changes: 36 additions & 0 deletions src/components/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Search from '../themes/octopus/components/Search.astro';
import MobileMenu from './MobileMenu.astro';
import OctopusLogo from './icons/OctopusLogo.astro';
import ThemeSwitcher from './ThemeSwitcher.astro';
import TopNav from './TopNav.astro';

const stats = new accelerator.statistics('components/Header.astro');
stats.start();
Expand All @@ -26,6 +27,41 @@ const _ = Lang(lang);
stats.stop();
---

<script is:inline>
{
const key = 'use-new-nav';
const newNavRequested = new URLSearchParams(location.search).get('newnav');

let useNewNav = false;
try {
if (newNavRequested == null) {
const storedItem = JSON.parse(sessionStorage.getItem(key));
if (storedItem) {
// Remember the new nav setting for an hour
if (Date.now() - storedItem.timestamp < 60 * 60 * 1000) {
useNewNav = true;
} else {
sessionStorage.removeItem(key);
}
}
} else if (newNavRequested !== '0') {
useNewNav = true;
sessionStorage.setItem(key, JSON.stringify({ timestamp: Date.now() }));
} else {
sessionStorage.removeItem(key);
}
} catch {}

if (useNewNav) {
document.documentElement.dataset.nav = 'new';
}
}
</script>

<div class="new-top-nav">
<TopNav />
</div>

<header class="octo-header-bkg site-header header-default">
<div class="octo-header">
<div class="octo-header__wrapper--left">
Expand Down
140 changes: 125 additions & 15 deletions src/components/TopNav.astro
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
---
import Logo from '../assets/octopus-logo-v2.svg';
import Avatar from './Avatar.astro';
import Button from './Button.astro';

type Props = {
active?: string;
links?: { label: string; href: string }[];
};

// TODO: Make these real links
const {
active = 'Docs',
links = [
{ label: 'Docs', href: '#' },
{ label: 'Learn', href: '#' },
{ label: 'API', href: '#' },
{ label: 'CLI', href: '#' },
{ label: 'Docs', href: '/docs' },
{ label: 'Learn', href: '#' }, // TODO: Make this a real link

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure just yet what this is supposed to point to, I think the others are good though

{ label: 'API', href: '/docs/octopus-rest-api' },
{ label: 'CLI', href: '/docs/octopus-rest-api/cli' },

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've assumed CLI means the CLI that talks to an Octopus Server instance over HTTP, not the CLI of the Octopus Server binary itself, which is only applicable to self-hosted

],
} = Astro.props;
} = Astro.props satisfies Props;

const pathname = Astro.url.pathname.replace(/\/+$/, '') || '/';
const currentLabel = links
.filter(({ href }) => pathname === href || pathname.startsWith(`${href}/`))
.sort((a, b) => b.href.length - a.href.length)[0]?.label;
---

<header class="top-nav">
Expand All @@ -34,10 +37,10 @@ const {
<a
class:list={[
'top-nav__link',
label === active && 'top-nav__link--active',
label === currentLabel && 'top-nav__link--active',
]}
href={href}
aria-current={label === active ? 'page' : undefined}
aria-current={label === currentLabel ? 'page' : undefined}
>
{label}
</a>
Expand All @@ -46,11 +49,118 @@ const {
</nav>
</div>
<div class="top-nav__trailing">
<!-- All of these are just non-functional and for visual/presentation only at this stage -->
<Button icon="fa-regular fa-moon" aria-label="Switch to dark theme" />
<!-- TODO: Make these real links -->
<!-- TODO: Fix the theme switcher icons -->
<Button
icon="fa-regular fa-moon"
aria-label="Dark mode"
aria-pressed="false"
data-theme-toggle-button
/>
<!-- TODO: Make this a real link -->
<Button label="Changelog" href="#" />

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what this is supposed to point to just yet, I couldn't actually find release notes for non-self-hosted releases available anywhere

<Button label="Sign in" href="#" />
<Button label="Start for free" href="#" importance="loud" />
<Button
label="Sign in"
href="https://octopus.com/signin"
data-signed-out-only
/>
<Button
label="Start for free"
href="https://octopus.com/free-signup"
importance="loud"
data-signed-out-only
/>
<Avatar
size="medium"
shape="circle"
alt=""
data-signed-in-only
data-user-avatar
hidden
>
<span data-user-initials></span>
</Avatar>
</div>
</header>

<script>
import '../scripts/signed-in-user';
import '../scripts/theme-switcher';
</script>

<style>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made these scoped styles just like I recently did with Button. I intended to do this all along, but I couldn't do it when I first added this because our caching strategy was broken and CSS with a content-hash that no longer exists could be pointed to by an old cached HTML page. That's all fixed now.

.top-nav {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space24);
padding: var(--space16) var(--space24)
calc(var(--space16) - var(--borderWidth1));
border-block-end: var(--borderWidth1) solid var(--colorBorderPrimary);
background: var(--colorBackgroundPrimaryDefault);
}

.top-nav__leading,
.top-nav__trailing {
flex-shrink: 0; /* Disallow shrinking so button labels don't wrap across 2 lines */
}

.top-nav__leading {
display: flex;
align-items: center;
gap: var(--space64);
}

.top-nav__logo {
display: flex;
}

.top-nav__logo-mark {
display: block;
height: 2rem;
width: auto;
color: var(--colorTextPrimary);
}

.top-nav__links {
display: flex;
align-items: center;
gap: var(--space2);
}

.top-nav__trailing {
display: flex;
align-items: center;
gap: var(--space12);
}

a.top-nav__link {
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--space8) var(--space12);
border-radius: var(--borderRadiusSmall);
background: var(--colorNavBackgroundRest);
color: var(--colorTextPrimary);
font: var(--textBodyRegularMedium);
text-align: center;
text-decoration: none;
}

a.top-nav__link:hover:not(.top-nav__link--active),
a.top-nav__link:focus:not(.top-nav__link--active) {
background: var(--colorNavBackgroundHover);
}

a.top-nav__link:focus-visible {
outline: var(--borderWidth2) solid var(--colorBorderSelected);
outline-offset: var(--borderWidth1);
}

a.top-nav__link--active {
background: var(--colorNavBackgroundActive);
color: var(--colorTextSelected);
font: var(--textBodyBoldMedium);
}
</style>
Loading