Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DM-48644: Create a PrimaryNavigation component #179

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d89dfed
Create a PrimaryNavigation component
jonathansick Jan 31, 2025
1d8e8b1
Create story of menu in opened state
jonathansick Feb 4, 2025
7856e72
Change triggers to only open on click
jonathansick Feb 4, 2025
4d50e7a
Add DOM to lib options
jonathansick Feb 5, 2025
ab4f6a7
Position the navigation menu under trigger
jonathansick Feb 5, 2025
e108999
Fix typing of PrimaryNavigation
jonathansick Feb 5, 2025
4a22124
Set displayName for PrimaryNavigation
jonathansick Feb 5, 2025
3717d0b
Fix name of rule for no-html-link-for-pages
jonathansick Feb 5, 2025
d5c7f6e
Move authUrls to Squared
jonathansick Feb 6, 2025
b6fcf7a
Wip: Adopt PrimaryNav in Header
jonathansick Feb 6, 2025
047386f
Increase z-index of viewport container
jonathansick Feb 6, 2025
eeaf8d9
Handle viewport alignment near right side
jonathansick Feb 6, 2025
30dad48
Fix typo in flex-shrink property
jonathansick Feb 7, 2025
1c6c5dd
fixup: remove story import from PrimaryNav
jonathansick Feb 7, 2025
baa33e1
Cleanup HeaderNav with PrimaryNavigation
jonathansick Feb 7, 2025
f0e4f6e
fixup UserMenu
jonathansick Feb 7, 2025
67f8b1b
Fix HeaderNav Link to user Trigger
jonathansick Feb 7, 2025
60e5386
Wrap user menu items in ContentItem
jonathansick Feb 7, 2025
58261d6
Add configuration schema for enabling a Apps Menu
jonathansick Feb 7, 2025
2933326
Split this commit
jonathansick Feb 7, 2025
2d69890
Add appLinks configurations
jonathansick Feb 7, 2025
6313b0c
Add an Apps menu
jonathansick Feb 7, 2025
9f4d20e
Handle internal and external links differently
jonathansick Feb 10, 2025
c4d83c8
Ensure Trigger uses a pointer cursor
jonathansick Feb 10, 2025
694f628
Ensure internal links use pointer cursor
jonathansick Feb 10, 2025
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
30 changes: 30 additions & 0 deletions apps/squareone/squareone.config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,36 @@
"title": "Sentry debug",
"description": "Setting this option to true will print useful information to the console while you're setting up Sentry.",
"default": false
},
"enableAppsMenu": {
"type": "boolean",
"title": "Enable the Apps menu",
"description": "Setting this option to true will enable the Apps menu in the header.",
"default": false
},
"appLinks": {
"type": "array",
"title": "Application links",
"description": "Links to be displayed in the Apps menu",
"items": {
"type": "object",
"required": ["label", "href", "internal"],
"properties": {
"label": {
"type": "string",
"description": "Display label for the link"
},
"href": {
"type": "string",
"description": "URL or path for the link"
},
"internal": {
"type": "boolean",
"description": "Whether this is an internal route or external URL"
}
}
},
"default": []
}
}
}
8 changes: 8 additions & 0 deletions apps/squareone/squareone.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ docsBaseUrl: 'https://rsp.lsst.io'
semaphoreUrl: 'https://data-dev.lsst.cloud/semaphore'
timesSquareUrl: 'http://localhost:3000/times-square/api'
coManageRegistryUrl: 'https://id.lsst.cloud'
enableAppsMenu: true
appLinks:
- label: 'Times Square'
href: '/times-square/'
internal: true
- label: 'Argo CD'
href: '/argo-cd/'
internal: false
apiAspectPageMdx: |
# Rubin Science Platform APIs

Expand Down
58 changes: 58 additions & 0 deletions apps/squareone/src/components/Header/AppsMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useRouter } from 'next/router';
import getConfig from 'next/config';

import { ChevronDown } from 'react-feather';
import { PrimaryNavigation } from '@lsst-sqre/squared';

export default function AppsMenu({}) {
const { publicRuntimeConfig } = getConfig();
const appLinks = publicRuntimeConfig.appLinks || [];

return (
<>
<PrimaryNavigation.Trigger>
Apps <ChevronDown />
</PrimaryNavigation.Trigger>
<PrimaryNavigation.Content>
{appLinks.map((link, index) => (
<PrimaryNavigation.ContentItem key={`${link.href}-${index}`}>
<Link href={link.href} internal={link.internal}>
{link.label}
</Link>
</PrimaryNavigation.ContentItem>
))}
</PrimaryNavigation.Content>
</>
);
}

const Link = ({ href, internal, ...props }) => {
const router = useRouter();
const isActive = href === router.pathname;

// We're implementing our own Link component with an onClick handler because
// if we compose Next.js's Link component we find that Radix Navigation Menu's
// Link component is adding an onClick handler to the Next link that
// causes an error of the sort:
// "onClick" was passed to <Link> with href of /docs but "legacyBehavior"
// was set.
// However, this current implementation is not ideal because it doesn't
// pass through the NavigationMenu's keyboard navigation.
if (internal) {
return (
<PrimaryNavigation.Link active={isActive}>
<span style={{ cursor: 'pointer' }} onClick={() => router.push(href)}>
{props.children}
</span>
</PrimaryNavigation.Link>
);
}

// External links are handled by the PrimaryNavigation.Link component, which
// becomes an <a> tag without any Next onClick handlers.
return (
<PrimaryNavigation.Link active={isActive} href={href}>
{props.children}
</PrimaryNavigation.Link>
);
};
94 changes: 61 additions & 33 deletions apps/squareone/src/components/Header/HeaderNav.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,57 @@
import PropTypes from 'prop-types';
import styled from 'styled-components';
import Link from 'next/link';
import getConfig from 'next/config';
import NextLink from 'next/link';
import { useRouter } from 'next/router';
import { PrimaryNavigation } from '@lsst-sqre/squared';

import useCurrentUrl from '../../hooks/useCurrentUrl';
import Login from './Login';

const StyledNav = styled.nav`
margin: 0 0 30px 0;
padding: 0;
display: flex;
justify-self: end;
width: 100%;
font-size: 1.2rem;
`;

const NavItem = styled.div`
margin: 0 1em;

color: var(--rsd-component-header-nav-text-color);
a {
color: var(--rsd-component-header-nav-text-color);
}

a:hover {
color: var(--rsd-component-header-nav-text-hover-color);
}
`;

const LoginNavItem = styled(NavItem)`
margin: 0 1em 0 auto;
`;
import AppsMenu from './AppsMenu';

/*
* Navigation (within the Header).
*/
export default function HeaderNav() {
const currentUrl = useCurrentUrl();
const { publicRuntimeConfig } = getConfig();

return (
<StyledNav>
<NavItem>
<a href="/portal/app">Portal</a>
<PrimaryNavigation.TriggerLink href="/portal/app">
Portal
</PrimaryNavigation.TriggerLink>
</NavItem>

<NavItem>
<a href="/nb/hub">Notebooks</a>
<PrimaryNavigation.TriggerLink href="/nb/hub">
Notebooks
</PrimaryNavigation.TriggerLink>
</NavItem>

<NavItem>
<Link href="/api-aspect">APIs</Link>
<InternalTriggerLink href="/api-aspect">APIs</InternalTriggerLink>
</NavItem>

{publicRuntimeConfig.enableAppsMenu && (
<NavItem>
<AppsMenu />
</NavItem>
)}

<NavItem>
<Link href="/docs">Documentation</Link>
<InternalTriggerLink href="/docs">Documentation</InternalTriggerLink>
</NavItem>

<NavItem>
<Link href="/support">Support</Link>
<InternalTriggerLink href="/support">Support</InternalTriggerLink>
</NavItem>

<NavItem>
<a href="https://community.lsst.org">Community</a>
<PrimaryNavigation.TriggerLink href="https://community.lsst.org">
Community
</PrimaryNavigation.TriggerLink>
</NavItem>

<LoginNavItem>
Expand All @@ -70,4 +61,41 @@ export default function HeaderNav() {
);
}

const StyledNav = styled(PrimaryNavigation)`
margin: 0 0 30px 0;
padding: 0;
/* display: flex; */
justify-self: end;
width: 100%;
font-size: 1.2rem;
`;

const NavItem = styled(PrimaryNavigation.Item)`
margin: 0 1em;

color: var(--rsd-component-header-nav-text-color);
a {
color: var(--rsd-component-header-nav-text-color);
}

a:hover {
color: var(--rsd-component-header-nav-text-hover-color);
}
`;

const LoginNavItem = styled(NavItem)`
margin: 0 1em 0 auto;
`;

const InternalTriggerLink = ({ href, ...props }) => {
const router = useRouter();
const isActive = href === router.pathname;

return (
<PrimaryNavigation.Trigger asChild active={isActive}>
<NextLink href={href} className="NavigationMenuLink" {...props} />
</PrimaryNavigation.Trigger>
);
};

HeaderNav.propTypes = {};
13 changes: 9 additions & 4 deletions apps/squareone/src/components/Header/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@

import PropTypes from 'prop-types';

import { getLoginUrl } from '../../lib/utils/url';
import useUserInfo from '../../hooks/useUserInfo';
import UserMenu from './UserMenu';
import { PrimaryNavigation, getLoginUrl } from '@lsst-sqre/squared';

export default function Login({ pageUrl }) {
const { isLoggedIn } = useUserInfo();

if (!isLoggedIn) {
return <a href={getLoginUrl(pageUrl)}>Log in</a>;
if (isLoggedIn === true) {
return <UserMenu pageUrl={pageUrl} />;
}
return <UserMenu pageUrl={pageUrl} />;

return (
<PrimaryNavigation.TriggerLink href={getLoginUrl(pageUrl)}>
Log in
</PrimaryNavigation.TriggerLink>
);
}

Login.propTypes = {
Expand Down
47 changes: 35 additions & 12 deletions apps/squareone/src/components/Header/UserMenu.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,47 @@
/* Menu for a user profile and settings, built on @react/menu-button. */
/* Menu for a user profile and settings. */

import PropTypes from 'prop-types';
import getConfig from 'next/config';
import { GafaelfawrUserMenu } from '@lsst-sqre/squared';
import { ChevronDown } from 'react-feather';
import { PrimaryNavigation } from '@lsst-sqre/squared';
import { useGafaelfawrUser } from '@lsst-sqre/squared';
import { getLogoutUrl } from '@lsst-sqre/squared';

export default function UserMenu({ pageUrl }) {
const { publicRuntimeConfig } = getConfig();
const { coManageRegistryUrl } = publicRuntimeConfig;
const { user } = useGafaelfawrUser();
const logoutUrl = getLogoutUrl(pageUrl);

if (!user) {
return <></>;
}

return (
<GafaelfawrUserMenu currentUrl={pageUrl}>
{coManageRegistryUrl && (
<GafaelfawrUserMenu.Link href={coManageRegistryUrl}>
Account settings
</GafaelfawrUserMenu.Link>
)}
<GafaelfawrUserMenu.Link href="/auth/tokens">
Security tokens
</GafaelfawrUserMenu.Link>
</GafaelfawrUserMenu>
<>
<PrimaryNavigation.Trigger>
{user.username} <ChevronDown />
</PrimaryNavigation.Trigger>
<PrimaryNavigation.Content>
{coManageRegistryUrl && (
<PrimaryNavigation.ContentItem>
<PrimaryNavigation.Link href={coManageRegistryUrl}>
Account settings
</PrimaryNavigation.Link>
</PrimaryNavigation.ContentItem>
)}
<PrimaryNavigation.ContentItem>
<PrimaryNavigation.Link href="/auth/tokens">
Security tokens
</PrimaryNavigation.Link>
</PrimaryNavigation.ContentItem>
<PrimaryNavigation.ContentItem>
<PrimaryNavigation.Link href={logoutUrl}>
Log out
</PrimaryNavigation.Link>
</PrimaryNavigation.ContentItem>
</PrimaryNavigation.Content>
</>
);
}

Expand Down
2 changes: 1 addition & 1 deletion apps/squareone/src/components/HomepageHero/HomepageHero.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const ServiceCard = styled.div`
flex: 1 0 auto;
}
.sticky-footer-container {
flex-shink: 0;
flex-shrink: 0;
margin-top: 2rem;
}
`;
Expand Down
2 changes: 1 addition & 1 deletion apps/squareone/src/components/Page/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const StyledLayout = styled.div`
flex: 1 0 auto;
}
.sticky-footer-container {
flex-shink: 0;
flex-shrink: 0;
}
`;

Expand Down
2 changes: 1 addition & 1 deletion packages/squared/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module.exports = {
root: true,
extends: ['@lsst-sqre/eslint-config', 'plugin:storybook/recommended'],
rules: {
'no-html-link-for-pages': 'off',
'@next/next/no-html-link-for-pages': 'off',
'react/no-unescaped-entities': 'off',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import styled from 'styled-components';

import useGafaelfawrUser from '../../hooks/useGafaelfawrUser';
import { getLoginUrl, getLogoutUrl } from './authUrls';
import { getLoginUrl, getLogoutUrl } from '../../lib/authUrls';
import Menu, { MenuLink } from './Menu';

export interface GafaelfawrUserMenuProps {
Expand Down
Loading
Loading