-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create a PrimaryNavigation component
This component wraps Radix UI's NavigationMenu and provide's a replacement for the current header navigation in Squareone. With NavigationMenu we'll be able to give individual menu items submenus, and not necessarily have to treat the user menu as a special case. This work styles the basic PrimaryNavigation component and demonstrates it with a storybook story. More work is need to make the submenu appear directly below its trigger.
- Loading branch information
1 parent
122d6bc
commit 77274e7
Showing
5 changed files
with
298 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@lsst-sqre/squared': minor | ||
--- | ||
|
||
Add a new PrimaryNavigation component. This component uses the Radix [NavigationMenu](https://www.radix-ui.com/primitives/docs/components/navigation-menu) primitive and is intended to be a comprehensive solution for the primary navigation in the header of Squareone. The earlier `GafaelfawrUserMenu` component in Squared also uses `NavigationMenu`, but as a single item. With `PrimaryNavigation`, the functionality of `GafaelfawrUserMenu` can be composed into an instance of `PrimaryNavigation`. Like `GafaelfawrMenu`, `PrimaryNavigation` is set up so that menus only appear after clicking on a trigger, rather than on hover. As well, `PrimaryNavigation` ensures the menu is proximate to the trigger (an improvement on the default `NavigationMenu` functionality that centers the menu below the whole navigation element. |
61 changes: 61 additions & 0 deletions
61
packages/squared/src/components/PrimaryNavigation/PrimaryNavigation.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { within, userEvent, screen } from '@storybook/testing-library'; | ||
import { expect } from '@storybook/jest'; | ||
|
||
import { ChevronDown } from 'react-feather'; | ||
|
||
import { PrimaryNavigation } from './PrimaryNavigation'; | ||
|
||
const meta: Meta<typeof PrimaryNavigation> = { | ||
title: 'Components/PrimaryNavigation', | ||
component: PrimaryNavigation, | ||
parameters: { | ||
layout: 'centered', | ||
backgrounds: { | ||
default: 'dark', | ||
values: [{ name: 'dark', value: '#1f2121' }], | ||
}, | ||
}, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof PrimaryNavigation>; | ||
|
||
export const Default: Story = { | ||
args: {}, | ||
render: (args) => ( | ||
<PrimaryNavigation {...args}> | ||
<PrimaryNavigation.Item> | ||
<PrimaryNavigation.TriggerLink href="#"> | ||
Portal | ||
</PrimaryNavigation.TriggerLink> | ||
</PrimaryNavigation.Item> | ||
|
||
<PrimaryNavigation.Item> | ||
<PrimaryNavigation.TriggerLink href="/nb"> | ||
Notebooks | ||
</PrimaryNavigation.TriggerLink> | ||
</PrimaryNavigation.Item> | ||
|
||
<PrimaryNavigation.Item> | ||
<PrimaryNavigation.TriggerLink href="/docs"> | ||
Documentation | ||
</PrimaryNavigation.TriggerLink> | ||
</PrimaryNavigation.Item> | ||
|
||
<PrimaryNavigation.Item> | ||
<PrimaryNavigation.Trigger> | ||
Account <ChevronDown /> | ||
</PrimaryNavigation.Trigger> | ||
<PrimaryNavigation.Content> | ||
<PrimaryNavigation.ContentItem> | ||
<PrimaryNavigation.Link href="#">Settings</PrimaryNavigation.Link> | ||
</PrimaryNavigation.ContentItem> | ||
<PrimaryNavigation.ContentItem> | ||
<PrimaryNavigation.Link href="#">Logout</PrimaryNavigation.Link> | ||
</PrimaryNavigation.ContentItem> | ||
</PrimaryNavigation.Content> | ||
</PrimaryNavigation.Item> | ||
</PrimaryNavigation> | ||
), | ||
}; |
226 changes: 226 additions & 0 deletions
226
packages/squared/src/components/PrimaryNavigation/PrimaryNavigation.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
import React from 'react'; | ||
import styled from 'styled-components'; | ||
|
||
import * as RadixNavigationMenu from '@radix-ui/react-navigation-menu'; | ||
|
||
export interface PrimaryNavigationProps { | ||
children: React.ReactNode; | ||
} | ||
|
||
/** | ||
* Primary navigation component that provides a layout for navigation items and a viewport. | ||
* | ||
* @component | ||
* @param {PrimaryNavigationProps} props - The component props | ||
* @param {ReactNode} props.children - The navigation items to be rendered within the ItemList | ||
* @returns {JSX.Element} A navigation component with items and a viewport container | ||
*/ | ||
export const PrimaryNavigation = ({ children }: PrimaryNavigationProps) => { | ||
return ( | ||
<Root> | ||
<ItemList>{children}</ItemList> | ||
<ViewportContainer> | ||
<ContentViewport | ||
onPointerEnter={(event) => event.preventDefault()} | ||
onPointerLeave={(event) => event.preventDefault()} | ||
/> | ||
</ViewportContainer> | ||
</Root> | ||
); | ||
}; | ||
|
||
/** | ||
* The root component for the primary navigation. | ||
* | ||
* This is a styled version of the `RadixNavigationMenu.Root` component and | ||
* isn't directly used by consumers of the component. | ||
*/ | ||
const Root = styled(RadixNavigationMenu.Root)` | ||
position: relative; | ||
`; | ||
|
||
/** | ||
* The list of items in the primary navigation. | ||
* | ||
* This is a styled version of the `RadixNavigationMenu.List` component and | ||
* isn't directly used by consumers of the component. | ||
*/ | ||
const ItemList = styled(RadixNavigationMenu.List)` | ||
list-style: none; | ||
margin-bottom: 0; | ||
padding: 0; | ||
display: flex; | ||
justify-self: end; | ||
width: 100%; | ||
font-size: 1.2rem; | ||
`; | ||
|
||
/** | ||
* An item in the primary navigation that can either link to a page or display | ||
* a dropdown menu. | ||
*/ | ||
const Item = styled(RadixNavigationMenu.Item)` | ||
margin: 0 1em; | ||
`; | ||
|
||
/** | ||
* A trigger in the primary navigation that links to a page rather than | ||
* displaying a dropdown menu. | ||
* | ||
* Use the `href` prop to specify the URL of the page. | ||
*/ | ||
const TriggerLink = styled(RadixNavigationMenu.Link)` | ||
color: var(--rsd-component-header-nav-text-color); | ||
border: none; | ||
border-radius: 0.5rem; | ||
padding: 2px 4px; | ||
display: inline-block; // For consistency with MenuTrigger button | ||
/* padding: 0; */ | ||
&:hover { | ||
color: var(--rsd-component-header-nav-text-hover-color); | ||
} | ||
&:focus { | ||
outline: 1px solid var(--rsd-color-primary-500); | ||
} | ||
`; | ||
|
||
/** | ||
* The trigger for a `PrimaryNavigation.Item` that is displays a `Content` | ||
* dropdown when activated. | ||
*/ | ||
const Trigger = styled(RadixNavigationMenu.Trigger)` | ||
color: var(--rsd-component-header-nav-text-color); | ||
padding: 2px 4px; | ||
/* padding: 0; */ | ||
// Reset button styles | ||
background-color: transparent; | ||
border: none; | ||
border-radius: 0.5rem; | ||
&:focus { | ||
outline: 1px solid var(--rsd-color-primary-500); | ||
} | ||
&:hover { | ||
color: var(--rsd-component-header-nav-text-hover-color); | ||
} | ||
svg { | ||
display: inline-block; | ||
width: 1rem; | ||
height: 1rem; | ||
vertical-align: middle; | ||
} | ||
&[data-state='open'] { | ||
svg { | ||
transform: rotate(180deg); | ||
} | ||
} | ||
`; | ||
|
||
/** | ||
* The content of a `PrimaryNavigation.Item` that is displayed as a dropdown | ||
* when the item is activated. | ||
*/ | ||
const Content = styled(RadixNavigationMenu.Content)` | ||
/* This unit for the padding is also the basis for the spacing and | ||
* sizing of the menu items. | ||
*/ | ||
--gafaelfawr-user-menu-padding: 0.5rem; | ||
display: flex; | ||
flex-direction: column; | ||
gap: 0.25rem; | ||
list-style: none; | ||
font-size: 1rem; | ||
background-color: var(--rsd-component-header-nav-menulist-background-color); | ||
min-width: 12rem; | ||
border-radius: 0.5rem; | ||
padding: var(--gafaelfawr-user-menu-padding); | ||
padding-right: 0; // to avoid double padding on the right side with MenuLink | ||
box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), | ||
0px 10px 20px -15px rgba(22, 23, 24, 0.2); | ||
animation-duration: 400ms; | ||
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); | ||
will-change: transform, opacity; | ||
`; | ||
|
||
/** | ||
* A content item within a `PrimaryNavigation.Content`. | ||
*/ | ||
const ContentItem = styled(RadixNavigationMenu.Item)` | ||
display: flex; | ||
`; | ||
|
||
/** | ||
* A link to a page within the content of a `PrimaryNavigation.ContentItem`. | ||
* | ||
* Use the `href` prop to specify the URL of the page. | ||
*/ | ||
export const Link = styled(RadixNavigationMenu.Link)` | ||
/* The styling on the menu triggers is overriding this colour. Need to re-address. */ | ||
border-radius: 0.5rem; | ||
padding: calc(var(--gafaelfawr-user-menu-padding) / 2) | ||
var(--gafaelfawr-user-menu-padding); | ||
margin: calc(var(--gafaelfawr-user-menu-padding) / -2); | ||
margin-bottom: calc(var(--gafaelfawr-user-menu-padding) / 2); | ||
width: 100%; | ||
color: var(--rsd-component-header-nav-menulist-text-color); | ||
&:last-of-type { | ||
margin-bottom: 0; | ||
} | ||
outline: 1px solid transparent; | ||
&:focus { | ||
outline: 1px solid | ||
var(--rsd-component-header-nav-menulist-selected-background-color); | ||
} | ||
&:hover { | ||
background-color: var( | ||
--rsd-component-header-nav-menulist-selected-background-color | ||
); | ||
color: white !important; | ||
} | ||
`; | ||
|
||
const ViewportContainer = styled.div` | ||
position: absolute; | ||
display: flex; | ||
justify-content: center; | ||
width: 100%; | ||
top: 100%; | ||
left: 0; | ||
perspective: 2000px; | ||
`; | ||
|
||
const ContentViewport = styled(RadixNavigationMenu.Viewport)` | ||
position: relative; | ||
transform-origin: top center; | ||
margin-top: 10px; | ||
width: 100%; | ||
background-color: white; | ||
border-radius: 6px; | ||
overflow: hidden; | ||
height: var(--radix-navigation-menu-viewport-height); | ||
width: var(--radix-navigation-menu-viewport-width); | ||
`; | ||
|
||
// Associate child components with the parent for easier imports. | ||
PrimaryNavigation.Item = Item; | ||
PrimaryNavigation.Trigger = Trigger; | ||
PrimaryNavigation.TriggerLink = TriggerLink; | ||
PrimaryNavigation.Content = Content; | ||
PrimaryNavigation.ContentItem = ContentItem; | ||
PrimaryNavigation.Link = Link; | ||
|
||
export default PrimaryNavigation; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './PrimaryNavigation'; | ||
export { default } from './PrimaryNavigation'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters