Skip to content

Commit

Permalink
feat: stub OpenAPI docs mobile menu (#2090)
Browse files Browse the repository at this point in the history
* feat: add mobile-menu-levels component

* feat: add open-api-docs-mobile-menu-levels

* feat: use stubbed OpenAPI docs menu levels

* refactor: lift out fixed-productData import

* docs: update comment

* chore: enable flags for review

* docs: add comment on level-components

* chore: take a baby step towards decoupling

* chore: revert flags for dev & review
  • Loading branch information
zchsh authored Jul 20, 2023
1 parent 043f3a7 commit 426e697
Show file tree
Hide file tree
Showing 11 changed files with 292 additions and 10 deletions.
55 changes: 55 additions & 0 deletions src/components/mobile-menu-levels/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useState } from 'react'
// Icons
import { IconChevronLeft16 } from '@hashicorp/flight-icons/svg-react/chevron-left-16'
import { IconChevronRight16 } from '@hashicorp/flight-icons/svg-react/chevron-right-16'
// Components
import Button from 'components/button'
import MobileMenuContainer from 'components/mobile-menu-container'
import { MobileAuthenticationControls } from 'components/mobile-menu-container'
// Types
import type { MobileMenuLevelData } from './types'
// Styles
import s from './mobile-menu-levels.module.css'

/**
* This component allows the consumer to render a multi-pane mobile
* menu without being required to use `SidebarSidecarLayout`
* or the associated `sidebar-nav-data` React context.
*
* Intent is to solidify this into an alternative pattern
* for composing mobile menu contents.
*/
function MobileMenuLevels({ levels }: { levels: MobileMenuLevelData[] }) {
const [currentLevel, setCurrentLevel] = useState(levels.length - 1)

return (
<MobileMenuContainer className={s.mobileMenuContainer}>
<MobileAuthenticationControls />
<div className={s.levelButtons}>
{currentLevel > 0 ? (
<Button
className={s.levelUpButton}
color="tertiary"
icon={<IconChevronLeft16 />}
iconPosition="leading"
onClick={() => setCurrentLevel(currentLevel - 1)}
text={levels[currentLevel - 1].levelButtonText}
/>
) : null}
{currentLevel < levels.length - 1 ? (
<Button
className={s.levelDownButton}
color="tertiary"
icon={<IconChevronRight16 />}
iconPosition="trailing"
onClick={() => setCurrentLevel(currentLevel + 1)}
text={levels[currentLevel + 1].levelButtonText}
/>
) : null}
</div>
{levels[currentLevel].content}
</MobileMenuContainer>
)
}

export default MobileMenuLevels
125 changes: 125 additions & 0 deletions src/components/mobile-menu-levels/level-components/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Components
import {
SidebarHorizontalRule,
SidebarNavMenuItem,
} from 'components/sidebar/components'
// Utils
import { generateTopLevelSubNavItems } from 'lib/generate-top-level-sub-nav-items'
import { generateProductLandingSidebarMenuItems } from 'components/sidebar/helpers/generate-product-landing-nav-items'
import { generateResourcesNavItems } from 'components/sidebar/helpers'
// Types
import type { MenuItem } from 'components/sidebar'
import type { ProductData, ProductSlug } from 'types/products'
import type { MobileMenuLevelData } from '../types'
// Styles
import s from './level-components.module.css'

/**
* TODO: these "level components" are a good candidate for refactoring.
* We should refactor `mobile-menu-levels` `level-components` to decouple
* business logic & presentation.
*
* This approach here to build `MobileMenuLevelData` doesn't fit as nicely
* into our "view-model" approach to component composition.
*
* This was created as an initial mechanism to decouple the mobile menu from
* the sidebar nav data context, and the intertwined `generate` functions were
* retained as a way to ensure consistency with existing mobile menus.
*
* We'll likely want to refactor this further to split out the intertwined:
* - logic for generating the menu item data (eg `generateResourcesNavItems`)
* - presentation of that data using `<SidebarNavMenuItem />`
*
* To do this consistently across the app, it feels like this might be a
* reasonably large scope of refactor. With this in mind, this work
* was left out of the scope of the API docs template revision.
*
* Task:
* https://app.asana.com/0/1202097197789424/1205093670087688/f
*/

/**
* Render a list of resource nav items for a given product slug.
*/
export function ProductResourceNavItems({ slug }: { slug: ProductSlug }) {
/**
* TODO: this is some business logic that's intertwined with presentation.
* As mentioned in the comment at the top of this file, this is an initial
* cut at decoupling the mobile menu from the sidebar nav data context,
* and is likely something we want to further refactor.
*/
const menuItems = generateResourcesNavItems(slug)

return (
<ul className={s.listResetStyles}>
{menuItems.map((item: MenuItem, index: number) => (
// eslint-disable-next-line react/no-array-index-key
<SidebarNavMenuItem item={item} key={index} />
))}
</ul>
)
}

/**
* Return mobile menu level data for the shared `main` menu pane.
*/
export function mobileMenuLevelMain(): MobileMenuLevelData {
/**
* TODO: this is some business logic that's intertwined with presentation.
* As mentioned in the comment at the top of this file, this is an initial
* cut at decoupling the mobile menu from the sidebar nav data context,
* and is likely something we want to further refactor.
*/
const menuItems = generateTopLevelSubNavItems()

return {
levelButtonText: 'Main Menu',
content: (
<>
<h3 className={s.heading}>Main Menu</h3>
<ul className={s.listResetStyles}>
{menuItems.map((item: MenuItem, index: number) => (
// eslint-disable-next-line react/no-array-index-key
<SidebarNavMenuItem item={item} key={index} />
))}
</ul>
</>
),
}
}

/**
* Return mobile menu level data for a common `product` menu pane.
*/
export function mobileMenuLevelProduct(
productData: ProductData
): MobileMenuLevelData {
/**
* TODO: this is some business logic that's intertwined with presentation.
* As mentioned in the comment at the top of this file, this is an initial
* cut at decoupling the mobile menu from the sidebar nav data context,
* and is likely something we want to further refactor.
*/
const menuItems = [
{
title: productData.name,
fullPath: `/${productData.slug}`,
theme: productData.slug,
},
...generateProductLandingSidebarMenuItems(productData),
]

return {
levelButtonText: `${productData.name} Home`,
content: (
<ul className={s.listResetStyles}>
{menuItems.map((item: MenuItem, index: number) => (
// eslint-disable-next-line react/no-array-index-key
<SidebarNavMenuItem item={item} key={index} />
))}
<SidebarHorizontalRule />
<ProductResourceNavItems slug={productData.slug} />
</ul>
),
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.listResetStyles {
list-style: none;
margin: 0;
padding: 0;
width: 100%;
}

.heading {
color: var(--token-color-foreground-primary);
font-size: var(--token-typography-display-100-font-size);
font-weight: var(--token-typography-font-weight-semibold);
line-height: var(--token-typography-display-100-line-height);
padding: 0 8px;
margin: 0 0 12px 0;
}
19 changes: 19 additions & 0 deletions src/components/mobile-menu-levels/mobile-menu-levels.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.mobileMenuContainer {
padding: 24px;
z-index: 4;
}

.levelButtons {
display: flex;
margin-bottom: 12px;
}

.levelUpButton {
margin-right: auto;
padding: 7px 9px 7px 3px;
}

.levelDownButton {
margin-left: auto;
padding: 7px 9px 7px 3px;
}
6 changes: 6 additions & 0 deletions src/components/mobile-menu-levels/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ReactNode } from 'react'

export interface MobileMenuLevelData {
levelButtonText: string
content: ReactNode
}
2 changes: 1 addition & 1 deletion src/pages/hcp/api-docs/vault-secrets/[[...page]].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export const getStaticProps: GetStaticProps<
* 🚩 If the flag is enabled, use the revised template
*/
if (USE_REVISED_TEMPLATE) {
return await getOpenApiDocsStaticProps({ params })
return await getOpenApiDocsStaticProps(PRODUCT_SLUG, { params })
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/views/open-api-docs-view/components/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './open-api-docs-mobile-menu-levels'
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Components
import MobileMenuLevels from 'components/mobile-menu-levels'
import { SidebarHorizontalRule } from 'components/sidebar/components'
import {
ProductResourceNavItems,
mobileMenuLevelMain,
mobileMenuLevelProduct,
} from 'components/mobile-menu-levels/level-components'
// Types
import type { ProductData } from 'types/products'

/**
* Placeholder for OpenApiDocsView mobile menu levels.
*/
export function OpenApiDocsMobileMenuLevels({
productData,
}: {
// Product data, used to generate mobile menu levels.
productData: ProductData
}) {
return (
<MobileMenuLevels
levels={[
mobileMenuLevelMain(),
mobileMenuLevelProduct(productData),
{
levelButtonText: 'Previous',
content: (
<div>
{/* API docs mobile menu contents */}
<div style={{ border: '1px solid magenta' }}>
PLACEHOLDER for OpenApiDocsView mobile menu contents
</div>
{/* Common resources for this product */}
<SidebarHorizontalRule />
<ProductResourceNavItems slug={productData.slug} />
</div>
),
},
]}
/>
)
}
8 changes: 4 additions & 4 deletions src/views/open-api-docs-view/index.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
// Layout
import SidebarLayout from 'layouts/sidebar-layout'
// Local
import MobileMenuLevelsGeneric from 'components/mobile-menu-levels-generic'
import { OpenApiDocsMobileMenuLevels } from './components'
// Types
import type { OpenApiDocsViewProps } from './types'

/**
* Placeholder for a revised OpenAPI docs view.
*/
function OpenApiDocsView(props: OpenApiDocsViewProps) {
function OpenApiDocsView({ productData, ...restProps }: OpenApiDocsViewProps) {
return (
<SidebarLayout
sidebarSlot={
<div style={{ border: '1px solid magenta' }}>
PLACEHOLDER for sidebar contents
</div>
}
mobileMenuSlot={<MobileMenuLevelsGeneric />}
mobileMenuSlot={<OpenApiDocsMobileMenuLevels productData={productData} />}
>
<div style={{ border: '1px solid magenta' }}>
<h1>OpenApiDocsView Placeholder</h1>
<pre style={{ whiteSpace: 'pre-wrap' }}>
<code>{JSON.stringify(props, null, 2)}</code>
<code>{JSON.stringify(restProps, null, 2)}</code>
</pre>
</div>
</SidebarLayout>
Expand Down
26 changes: 21 additions & 5 deletions src/views/open-api-docs-view/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { GetStaticPaths, GetStaticProps } from 'next'
import { OpenApiDocsParams, OpenApiDocsViewProps } from './types'
// Types
import type {
GetStaticPaths,
GetStaticPropsContext,
GetStaticPropsResult,
} from 'next'
import type { OpenApiDocsParams, OpenApiDocsViewProps } from './types'
import type { ProductSlug } from 'types/products'
import { cachedGetProductData } from 'lib/get-product-data'

/**
* Get static paths for the view.
Expand Down Expand Up @@ -28,11 +35,20 @@ export const getStaticPaths: GetStaticPaths<OpenApiDocsParams> = async () => {
*
* For now, we have a placeholder. We'll expand this as we build out the view.
*/
export const getStaticProps: GetStaticProps<
OpenApiDocsViewProps
> = async () => {
export async function getStaticProps(
/**
* Product slug is used to grab productData, which we use in a few places,
* including in the mobile navigation, which has a product-nav pane.
*/
productSlug: ProductSlug,
// Note: params aren't used yet, but will be for versioned API docs.
{ params }: GetStaticPropsContext<OpenApiDocsParams>
): Promise<GetStaticPropsResult<OpenApiDocsViewProps>> {
const productData = cachedGetProductData(productSlug)

return {
props: {
productData,
placeholder: 'placeholder data for the revised API docs template',
IS_REVISED_TEMPLATE: true,
},
Expand Down
2 changes: 2 additions & 0 deletions src/views/open-api-docs-view/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ParsedUrlQuery } from 'querystring'
import { ProductData } from 'types/products'

/**
* Params type for `getStaticPaths` and `getStaticProps`.
Expand All @@ -22,5 +23,6 @@ export interface OpenApiDocsParams extends ParsedUrlQuery {
*/
export interface OpenApiDocsViewProps {
placeholder: string
productData: ProductData
IS_REVISED_TEMPLATE: true
}

1 comment on commit 426e697

@vercel
Copy link

@vercel vercel bot commented on 426e697 Jul 20, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.