-
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
Conversation
packages/swingset-theme-hashicorp/src/components/side-nav/index.tsx
Outdated
Show resolved
Hide resolved
packages/swingset/src/loader.ts
Outdated
import { resolveComponents } from './resolvers/component.js' | ||
import { stringifyEntity } from './resolvers/stringify-entity.js' | ||
import { getNavigationTree } from './get-nav-tree.js' | ||
import { getNavigationTree } from './get-nav-tree.js' |
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.
import { getNavigationTree } from './get-nav-tree.js' | |
import { getNavigationTree } from './get-nav-tree.js' |
packages/swingset/src/types.ts
Outdated
/* | ||
Utility type to extract just necessary properties before going to theme, also renames '__type' key to type for external usage. | ||
Unwanted Behavior?? The children property comes through as a ComponentEntity, which exposes the entities to the theme/clients | ||
*/ | ||
export type ComponentNode = Omit< | ||
Pick< | ||
ComponentEntity, | ||
'__type' | 'title' | 'slug' | 'componentPath' | 'children' | ||
>, | ||
'__type' | ||
> & { type: ComponentEntity['__type'] } |
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.
Can we remove the Omit
here?
/* | |
Utility type to extract just necessary properties before going to theme, also renames '__type' key to type for external usage. | |
Unwanted Behavior?? The children property comes through as a ComponentEntity, which exposes the entities to the theme/clients | |
*/ | |
export type ComponentNode = Omit< | |
Pick< | |
ComponentEntity, | |
'__type' | 'title' | 'slug' | 'componentPath' | 'children' | |
>, | |
'__type' | |
> & { type: ComponentEntity['__type'] } | |
/* | |
Utility type to extract just necessary properties before going to theme, also renames '__type' key to type for external usage. | |
Unwanted Behavior?? The children property comes through as a ComponentEntity, which exposes the entities to the theme/clients | |
*/ | |
export type ComponentNode = Pick< | |
ComponentEntity, | |
"title" | "slug" | "componentPath" | "children" | |
> & { type: ComponentEntity["__type"] }; |
|
||
|
||
|
||
if (!!storedCategory) { |
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.
I would potentially split out the category creation (if not exists) from the folder handling. I think the flow would look like (in plain english):
check if category is stored
if category is not stored, create the category object
then, handle folder logic
This dedupes some of the folder logic between these two branches.
(optional) I also wonder if it'd be easier to build up the category map and folder map separately, and then after we're done iterating over all of the entities we can make sure each folder gets added to its proper category. that way we don't need to check each of a category's children for the correct folder when we're trying to add an element to it.
packages/swingset/src/types.ts
Outdated
'__type' | 'title' | 'slug' | 'componentPath' | 'children' | ||
>, | ||
'__type' | ||
> & { type: ComponentEntity['__type'] } |
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.
- Removes
__
fromtype
so themes know to use this as a discriminator. - Extracting the
children
property without converting the type fromComponentEntity[]
toComponentNode[]
is leading to issues with type inference. Currently usingas unknown
type cast. Will address in separate issue and PR.ComponentEntity
should beComponentNode
incategories
#107
//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 comment
The reason will be displayed to describe this comment to others. Learn more.
Currently using a div
for a heading because it's the pattern Helios uses.
<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 comment
The reason will be displayed to describe this comment to others. Learn more.
This as unknown[] as ComponentNode[]
typecast is a result of typescript expecting ComponentEntities
instead of ComponentNodes
. Which is unwanted behavior (see changes to types.ts
)
Should be a fairly quick fix, see #107
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.
Want to add a // TODO
here linking to the issue so we know to clean this up as part of addressing that issue?
@@ -0,0 +1,3 @@ | |||
export function Checkbox(props) { |
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.
) | ||
} | ||
|
||
const hasChildren = (item.children?.length as number) > 0 |
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.
Need to determine whether children can be undefined or an empty []
. Currently the type is NavigationNode[] | undefined
hence the typecast, but all the values resolve to []
* 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Issue #106
packages/swingset/src/types.ts
Outdated
|
||
export type ComponentNode = Pick< | ||
ComponentEntity, | ||
'__type' | 'title' | 'slug' | 'componentPath' | 'children' |
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.
reverted back to __type
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Reduced nesting, should read like:
- If Category doesn't exist yet create it
- Check if current entity belongs in folder or not
- Add entity to folder if it already exists, if not create folder and add entity
- If entity doesn't have folder, add entity
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
prettier formatting fix
|
||
result[category].sort((a, b) => (a.slug > b.slug ? 1 : -1)) |
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.
sorting will be handled separately
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.
Looking good! I think the component breakdown you've come up with is sensible. There seems to be a mismatch between what we're returning from getNavigationTree
and the type we're expecting (returning an object instead of CategoryNode[]
)
<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 comment
The reason will be displayed to describe this comment to others. Learn more.
Want to add a // TODO
here linking to the issue so we know to clean this up as part of addressing that issue?
export type NavigationNode = ComponentNode | FolderNode | ||
|
||
export type CategoryNode = { | ||
type: 'category' |
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.
Is __type
the name we want to use? (I think yes based on your other comments)
type: 'category' | |
__type: 'category' |
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.
Originally the Omit<...> & {type: '....'}
changed it but I saw in the first review you requested to remove the Omit.
I'm impartial on the naming but think type
felt more consistent with the other properties.
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.
The other two nodes use __type
, so I'd say let's pick one or the other and be consistent between each interface 👍
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.
Will stick with what's already there then, since __type
is used across a few other files already
const tree = Object.fromEntries(categories) | ||
return tree |
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.
We want to return NavigationTree
here, right? That's an array of CategoryNode
, so we don't want an object here.
This might work (builds an array from the Map
's values):
Array.from(categories.values())
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 comment
The reason will be displayed to describe this comment to others. Learn more.
I think Object.values()
will be unnecessary here if we fix the return value of getNavigationTree
. In that case, we can use categories.map()
.
function SideNavigation(props: SideNavBarProps) { | ||
const { categories } = props |
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.
nit, we can destructure this:
function SideNavigation(props: SideNavBarProps) { | |
const { categories } = props | |
function SideNavigation({ categories }: SideNavBarProps) { |
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 comment
The reason will be displayed to describe this comment to others. Learn more.
The use of Record
is clever here, but I think it'd be clearer if we used a more conventional type definition:
function LinkItem({ title, to }: Record<'title' | 'to', string>) { | |
type LinkItemProps = { | |
title: string | |
to: string | |
} | |
function LinkItem({ title, to }: LinkItemProps) { |
)} | ||
> | ||
{items.map((item) => { | ||
const isFolder = item.__type === 'folder' |
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.
This could be a good candidate for using a switch statement on __type
. It's stylistic really, but I think it would make the logic of this block easier to understand.
} | ||
) | ||
|
||
//TODO: Account for duplicate folder names [category]/[folder] |
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.
Just noting that this is applicable for folders, not necessarily categories
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 comment
The 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.
/**TODO: | ||
* We should be pushing @type {ComponentNode} instead of @type {ComponentEntity} | ||
* https://github.com/hashicorp/swingset/issues/107 | ||
*/ |
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.
Issue #107
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.
Made the suggested adjustments. Definitely agree tests are the next step, also want to get Vercel previews working so you + over reviewers can reference it
Asana Task 🔗
What
categories
fromswingset/meta
hashicorp-theme
to match newNavigationTree
data structure.Why
Folder
this way themes can easier integrate multifaceted components or sub components.How
getNavigationTree
Node
types within tree