-
Notifications
You must be signed in to change notification settings - Fork 10
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
Refactoring getNavigationTree
to include folders
#105
Changes from 24 commits
34eea76
357b0ab
822b002
50b9643
2008875
2b3123a
cad984c
c366b49
f289666
1cb9e86
678197a
b529bf8
23f82c8
448f6ee
af5e17a
4761784
f216ead
5d0001c
ddbe5bb
37f21b2
5a3a1f3
1756b6d
d70bf28
3163c55
c0217d8
d52d6f2
7cb1683
59fce61
0a0ef7a
7176b92
cbbca16
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
--- | ||
title: 'Checkbox' | ||
description: 'A checkbox' | ||
path: 'Components/Forms/Checkbox' | ||
--- | ||
|
||
import { Checkbox } from '.' | ||
|
||
## Simple | ||
|
||
<Checkbox /> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function Checkbox(props) { | ||
return <input type="checkbox" {...props} /> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { NavigationNode, ComponentNode } from 'swingset/types' | ||
import { LinkItem } from './link-item' | ||
import { cx } from 'class-variance-authority' | ||
|
||
function Category({ | ||
title, | ||
items, | ||
}: { | ||
title: string | ||
items: NavigationNode[] | ||
}) { | ||
return ( | ||
<li className="ss-list-none"> | ||
<section className="ss-mb-4"> | ||
<CategoryHeading>{title.toUpperCase()}</CategoryHeading> | ||
<ComponentList items={items} /> | ||
</section> | ||
</li> | ||
) | ||
} | ||
//Swap this out for already existing heading && Enquire about semantics, https://helios.hashicorp.design/components/application-state uses <div> | ||
function CategoryHeading({ children }: { children: string }) { | ||
return ( | ||
<div className="ss-uppercase ss-text-xs ss-font-semibold ss-leading-6 ss-text-foreground-faint ss-border-b ss-border-faint ss-pb-2"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently using a |
||
{children.toUpperCase()} | ||
</div> | ||
) | ||
} | ||
|
||
function ComponentList({ | ||
prestonbourne marked this conversation as resolved.
Show resolved
Hide resolved
|
||
isNested, | ||
items, | ||
}: { | ||
isNested?: true | ||
items: NavigationNode[] | ||
}) { | ||
return ( | ||
<ul | ||
className={cx( | ||
'ss-mt-2 ss-space-y-1', | ||
isNested && 'ss-ml-2 ss-border-l ss-border-faint ss-mt-0' | ||
)} | ||
> | ||
{items.map((item) => { | ||
const isFolder = item.__type === 'folder' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be a good candidate for using a switch statement on |
||
|
||
if (isFolder) { | ||
return ( | ||
<Folder key={item.title} title={item.title} items={item.children} /> | ||
) | ||
} | ||
|
||
const hasChildren = (item.children?.length as number) > 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to determine whether children can be undefined or an empty |
||
|
||
if (hasChildren) { | ||
return ( | ||
<li key={item.title}> | ||
<LinkItem to={item.slug} title={item.title} /> | ||
<ComponentList | ||
isNested | ||
items={item.children as unknown[] as ComponentNode[]} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This Should be a fairly quick fix, see #107 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Want to add a |
||
/> | ||
</li> | ||
) | ||
} | ||
|
||
return ( | ||
<li key={item.title}> | ||
<LinkItem to={item.slug} title={item.title} /> | ||
</li> | ||
) | ||
})} | ||
</ul> | ||
) | ||
} | ||
|
||
function Folder({ | ||
title, | ||
items, | ||
}: { | ||
title: ComponentNode['title'] | ||
items: ComponentNode[] | ||
}) { | ||
return ( | ||
<details> | ||
<summary | ||
className={cx( | ||
'ss-text-foreground-primary hover:ss-text-foreground-action hover:ss-bg-surface-action', | ||
'ss-rounded-md ss-p-2 ss-text-sm ss-leading-6 ss-cursor-pointer' | ||
)} | ||
> | ||
{title} | ||
</summary> | ||
<ComponentList isNested items={items} /> | ||
</details> | ||
) | ||
} | ||
|
||
export default Category |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,22 @@ | ||||||||
import { NavigationTree } from 'swingset/types' | ||||||||
import Category from './category' | ||||||||
|
||||||||
type SideNavBarProps = { | ||||||||
categories: NavigationTree | ||||||||
} | ||||||||
|
||||||||
function SideNavigation(props: SideNavBarProps) { | ||||||||
const { categories } = props | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit, we can destructure this:
Suggested change
|
||||||||
|
||||||||
|
||||||||
|
||||||||
const categoriesJSX = Object.values(categories).map( | ||||||||
(category) => ( | ||||||||
<Category title={category.title} key={category.title} items={category.children} /> | ||||||||
) | ||||||||
) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think |
||||||||
|
||||||||
return <nav>{categoriesJSX}</nav> | ||||||||
} | ||||||||
|
||||||||
export { SideNavigation } |
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,18 @@ | ||||||||||||||||
import { Link } from "../link" | ||||||||||||||||
import { cx } from "class-variance-authority" | ||||||||||||||||
|
||||||||||||||||
function LinkItem({ title, to }: Record<'title' | 'to', string>) { | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of
Suggested change
|
||||||||||||||||
return ( | ||||||||||||||||
<Link | ||||||||||||||||
className={cx( | ||||||||||||||||
'ss-text-foreground-primary hover:ss-text-foreground-action hover:ss-bg-surface-action', | ||||||||||||||||
'ss-group ss-flex ss-gap-x-3 ss-rounded-md ss-p-2 ss-text-sm ss-leading-6 ss-no-underline' | ||||||||||||||||
)} | ||||||||||||||||
href={`swingset/${to}`} | ||||||||||||||||
> | ||||||||||||||||
{title} | ||||||||||||||||
</Link> | ||||||||||||||||
) | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
export {LinkItem} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,39 +4,10 @@ import '../style.css' | |
import React from 'react' | ||
import Link from 'next/link' | ||
import { meta, categories } from 'swingset/meta' | ||
import { cx } from 'class-variance-authority' | ||
import { SideNavigation } from './components/side-nav' | ||
|
||
import Page from './page' | ||
|
||
function NavList({ items, level }: any) { | ||
const isNested = level > 0 | ||
|
||
return ( | ||
<ul | ||
role="list" | ||
className={cx( | ||
'ss-mt-2 ss-space-y-1', | ||
isNested && 'ss-ml-2 ss-border-l ss-border-faint ss-mt-0' | ||
)} | ||
> | ||
{items.map(({ title, slug, children }: any) => ( | ||
<li key={slug}> | ||
<Link | ||
href={`/swingset/${slug}`} | ||
className={cx( | ||
'ss-text-foreground-primary hover:ss-text-foreground-action hover:ss-bg-surface-action', | ||
'ss-group ss-flex ss-gap-x-3 ss-rounded-md ss-p-2 ss-text-sm ss-leading-6' | ||
)} | ||
> | ||
{title} | ||
</Link> | ||
{children && <NavList level={level + 1} items={children} />} | ||
</li> | ||
))} | ||
</ul> | ||
) | ||
} | ||
|
||
export default function SwingsetLayout({ | ||
children, | ||
}: { | ||
|
@@ -45,7 +16,7 @@ export default function SwingsetLayout({ | |
return ( | ||
<html lang="en" className="ss-h-full"> | ||
<body className="ss-h-full flex flex-col"> | ||
<div className="ss-hidden lg:ss-fixed lg:ss-inset-y-0 lg:ss-z-50 lg:ss-flex lg:ss-w-72 lg:ss-flex-col"> | ||
<aside className="ss-hidden lg:ss-fixed lg:ss-inset-y-0 lg:ss-z-50 lg:ss-flex lg:ss-w-72 lg:ss-flex-col"> | ||
<div className="ss-flex ss-grow ss-flex-col ss-gap-y-5 ss-overflow-y-auto ss-border-r ss-border-faint ss-bg-surface-faint ss-px-6 ss-py-10"> | ||
<div className="ss-flex ss-shrink-0 ss-items-center"> | ||
<Link | ||
|
@@ -55,25 +26,9 @@ export default function SwingsetLayout({ | |
Swingset | ||
</Link> | ||
</div> | ||
<nav className="ss-flex ss-flex-1 ss-flex-col"> | ||
<ul | ||
role="list" | ||
className="ss-flex ss-flex-1 ss-flex-col ss-gap-y-7" | ||
> | ||
{Object.entries(categories).map(([title, items]) => ( | ||
<> | ||
<li> | ||
<h3 className="ss-uppercase ss-text-xs ss-font-semibold ss-leading-6 ss-text-foreground-faint ss-border-b ss-border-faint ss-pb-2"> | ||
{title} | ||
</h3> | ||
<NavList items={items} level={0} /> | ||
</li> | ||
</> | ||
))} | ||
</ul> | ||
</nav> | ||
<SideNavigation categories={categories} /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extracted all Nav logic into it's own folder in |
||
</div> | ||
</div> | ||
</aside> | ||
<main className="ss-py-10 lg:ss-pl-72 ss-flex ss-flex-col ss-flex-grow ss-h-full"> | ||
<div className="ss-px-4 sm:ss-px-6 lg:ss-px-8 ss-flex ss-flex-col ss-flex-grow"> | ||
{children} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,12 @@ export default function SwingsetLayout({ | |
<li> | ||
<h3 className="ss-my-2 ss-text-gray-600">{title}</h3> | ||
</li> | ||
{/** | ||
* TODO: Rewrite this for the new | ||
* https://github.com/hashicorp/swingset/pull/105 | ||
* @type {NavigationTree} | ||
* Leaving ts ignore to ensure build step succeeds | ||
* @ts-ignore */} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue #106 |
||
{(items as string[]).map((slug: string) => ( | ||
<li> | ||
<Link href={`/swingset/${slug}`}>{slug}</Link> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,87 @@ | ||
import { ComponentEntity, DocsEntity } from './types.js' | ||
import { | ||
ComponentEntity, | ||
DocsEntity, | ||
ComponentNode, | ||
CategoryNode, | ||
} from './types.js' | ||
|
||
export function getNavigationTree(entities: (ComponentEntity | DocsEntity)[]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some unit tests for this function could be helpful! It'd make it easier for us to iterate on the logic going forward too. We can do this as a follow-up. |
||
let result: Record< | ||
string, | ||
Pick<ComponentEntity, 'title' | 'slug' | 'componentPath' | 'children'>[] | ||
> = {} | ||
|
||
const componentEntities = entities.filter( | ||
(entity) => entity.__type === 'component' | ||
) as ComponentEntity[] | ||
|
||
const componentEntitiesWithChildren = componentEntities.map((entity) => { | ||
if (entity.isNested) return entity | ||
const componentEntitiesWithChildren = componentEntities.map( | ||
(componentEntity) => { | ||
if (componentEntity.isNested) return componentEntity | ||
|
||
entity.children = componentEntities.filter( | ||
(childEntity) => | ||
childEntity.isNested && | ||
childEntity.componentPath === entity.componentPath | ||
) | ||
componentEntity.children = componentEntities.filter( | ||
(childEntity) => | ||
childEntity.isNested && | ||
childEntity.componentPath === componentEntity.componentPath | ||
) | ||
|
||
return entity | ||
}) | ||
return componentEntity | ||
} | ||
) | ||
|
||
//TODO: Account for duplicate folder names [category]/[folder] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just noting that this is applicable for folders, not necessarily categories |
||
const categories = new Map<CategoryNode['title'], CategoryNode>() | ||
|
||
// bucket components into categories, nested documents are categorized under their component's path | ||
for (const entity of componentEntitiesWithChildren) { | ||
if (entity.isNested) continue | ||
|
||
//TODO: Handle Default Category | ||
const category = entity.navigationData?.category! | ||
const entityData: ComponentNode = { | ||
__type: 'component', | ||
title: entity.title, | ||
slug: entity.slug, | ||
componentPath: entity.componentPath, | ||
children: entity.children, | ||
} | ||
|
||
const categoryTitle = entity.navigationData?.category || 'default' | ||
|
||
let storedCategory = | ||
categories.has(categoryTitle) && categories.get(categoryTitle)! | ||
|
||
result[category] ||= [] | ||
//if category doesnt exist, create it | ||
if (storedCategory === false) { | ||
categories.set(categoryTitle, { | ||
type: 'category', | ||
title: categoryTitle, | ||
children: [], | ||
}) | ||
storedCategory = categories.get(categoryTitle)! | ||
} | ||
|
||
const folderTitle = entity.navigationData?.folder | ||
const hasFolder = !!folderTitle | ||
const folder = storedCategory.children.find( | ||
(node) => node.title === folderTitle | ||
) | ||
|
||
|
||
result[category].push({ | ||
title: entity.title, | ||
slug: entity.slug, | ||
componentPath: entity.componentPath, | ||
children: entity.children, | ||
}) | ||
//if node belongs in a folder, and folder doesnt exist, create folder with node | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reduced nesting, should read like:
|
||
if (hasFolder && !!folder === false) { | ||
storedCategory.children.push({ | ||
__type: 'folder', | ||
title: folderTitle, | ||
parentCategory: categoryTitle, | ||
children: [entityData], | ||
}) | ||
continue | ||
} | ||
|
||
//if node belongs in a folder, and folder already exists, add node to folder | ||
if (hasFolder && !!folder) { | ||
folder.children ||= [] | ||
folder.children.push(entity) | ||
continue | ||
} | ||
|
||
result[category].sort((a, b) => (a.slug > b.slug ? 1 : -1)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sorting will be handled separately |
||
//if node doesnt belong in folder, add node | ||
storedCategory.children.push(entityData) | ||
} | ||
|
||
return result | ||
const tree = Object.fromEntries(categories) | ||
return tree | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We want to return This might work (builds an array from the Array.from(categories.values()) |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,6 @@ import { compile, CompileOptions } from '@mdx-js/mdx' | |
import { VFile } from 'vfile' | ||
import { matter } from 'vfile-matter' | ||
import { type LoaderContext } from 'webpack' | ||
|
||
import { resolveComponents } from './resolvers/component.js' | ||
import { stringifyEntity } from './resolvers/stringify-entity.js' | ||
import { getNavigationTree } from './get-nav-tree.js' | ||
|
@@ -67,9 +66,7 @@ export async function loader( | |
const result = ` | ||
export const meta = { | ||
${entities | ||
.map( | ||
(entity) => `'${entity.slug}': ${stringifyEntity(entity)}` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prettier formatting fix |
||
) | ||
.map((entity) => `'${entity.slug}': ${stringifyEntity(entity)}`) | ||
.join(',\n')} | ||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added new components under the [Form] folder to better demonstrate sorting and collapsing.