Skip to content

Conversation

@mikegolus
Copy link
Collaborator

@mikegolus mikegolus commented Dec 12, 2025

πŸ”— Relevant links

πŸ—’οΈ What

This PR is to replace the local instance (src/components/badge) of the Badge component in devdot with the MDS Badge.

  • Added @types/react back to the dependency list but at v18.2.68 instead of v18.2.6 to better align with the web monorepo version
  • Removed @hashicorp/platform-packer-plugins and the src/views/packer-plugins directory as it was not in use
  • The new Badge component's icon prop takes an icon name from the FlightIconName string literal union type rather than requiring an icon component be passed to it. All instances of imported icon components related to badge icons have been refactored to use FlightIconName
  • All instances of the old devdot Badge have been replaced with the MDS Badge
  • All references to the old BadgeProps from src/components/badge/types.ts have been replaced with MDS BadgeProps
  • The old Badge from src/components/badge has been deleted
  • The new badge handles accessibility differently than the old Badge attempted to.
  • The need for custom icons in the "key command" badges associated with search has been removed by using text including the ⌘ (place of interest) ascii character and the letter "K"
  • The badge color neutral-dark-mode was equivalent to color neutral and type inverted. neutral-dark-mode has been removed from the MDS badge in favor of using neutral inverted
  • A couple cases of the badge background-color being overwritten with var(--mds-color-surface-primary) have been removed since this is now the default background color for these specific badge instances
  • Added some css to provide better UX when hovering over the HashiCorp logo and moving to the popover nav panel.
  • The NumericBadge component from src/components/numeric-badge has been deleted as it was not in use.

πŸ“Έ Screenshots

Screenshots with the search modal open were chosen because they have the most visible instances of the badge component for review.

Dark Mode

Before:
Screenshot 2025-12-16 at 10 08 30β€―AM

After:
Screenshot 2025-12-16 at 10 08 33β€―AM

Light Mode

Before:
Screenshot 2025-12-16 at 10 11 23β€―AM

After:
Screenshot 2025-12-16 at 10 11 25β€―AM

πŸ§ͺ Testing

  • ???

@vercel
Copy link

vercel bot commented Dec 12, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
dev-portal Ready Ready Preview, Comment Jan 7, 2026 11:31pm

@github-actions
Copy link

github-actions bot commented Dec 12, 2025

πŸ“¦ Next.js Bundle Analysis

This analysis was generated by the next.js bundle analysis action πŸ€–

πŸŽ‰ Global Bundle Size Decreased

Page Size (compressed)
global 315.27 KB (🟒 -991 B)
Details

The global bundle is the javascript bundle that loads alongside every page. It is in its own category because its impact is much higher - an increase to its size means that every page on your website loads slower, and a decrease means every page loads faster.

Any third party scripts you have added directly to your app using the <script> tag are not accounted for in this analysis

If you want further insight into what is behind the changes, give @next/bundle-analyzer a try!

One Hundred Pages Changed Size

The following pages changed size from the code in this PR compared to its base branch:

Page Size (compressed) First Load
/ 133.39 KB (🟒 -1.98 KB) 448.66 KB
/404 115.44 KB (🟒 -1.28 KB) 430.71 KB
/500 115.45 KB (🟒 -1.28 KB) 430.71 KB
/[productSlug]/docs 186.15 KB (🟒 -1.76 KB) 501.41 KB
/[productSlug]/integrations 144.94 KB (🟒 -2.59 KB) 460.21 KB
/[productSlug]/integrations/[organizationSlug]/[integrationSlug] 167.49 KB (🟒 -3.29 KB) 482.76 KB
/[productSlug]/integrations/[organizationSlug]/[integrationSlug]/[integrationVersion] 167.5 KB (🟒 -3.29 KB) 482.76 KB
/[productSlug]/integrations/[organizationSlug]/[integrationSlug]/[integrationVersion]/components/[componentType]/[componentSlug] 177.04 KB (🟒 -3.46 KB) 492.3 KB
/[productSlug]/sandbox 179.27 KB (🟒 -1.76 KB) 494.54 KB
/[productSlug]/tutorials 150.13 KB (🟒 -1.28 KB) 465.4 KB
/[productSlug]/tutorials/[...tutorialSlug] 229.8 KB (🟒 -1.75 KB) 545.07 KB
/[productSlug]/tutorials/[collectionSlug] 195.73 KB (🟒 -1.75 KB) 510.99 KB
/_error 115.47 KB (🟒 -1.28 KB) 430.74 KB
/boundary 158.29 KB (🟒 -1.84 KB) 473.55 KB
/boundary/api-docs/[[...page]] 164.2 KB (🟒 -1.91 KB) 479.46 KB
/boundary/docs/[...page] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/boundary/install 180.6 KB (🟒 -1.77 KB) 495.87 KB
/certifications 175.4 KB (🟒 -1.92 KB) 490.67 KB
/certifications/[slug] 175.8 KB (🟒 -1.91 KB) 491.06 KB
/certifications/signin 175.36 KB (🟒 -1.66 KB) 490.63 KB
/consul 158.29 KB (🟒 -1.84 KB) 473.55 KB
/consul/api-docs/[[...page]] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/consul/commands/[[...page]] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/consul/docs/[...page] 180.57 KB (🟒 -1.77 KB) 495.83 KB
/consul/install 174.08 KB (🟒 -1.77 KB) 489.35 KB
/consul/install/enterprise 174.12 KB (🟒 -1.77 KB) 489.38 KB
/hcp 158.28 KB (🟒 -1.84 KB) 473.55 KB
/hcp/api-docs/consul/[[...page]] 160.58 KB (🟒 -2.61 KB) 475.85 KB
/hcp/api-docs/hvn/[[...page]] 160.58 KB (🟒 -2.61 KB) 475.84 KB
/hcp/api-docs/identity/[[...page]] 160.58 KB (🟒 -2.61 KB) 475.85 KB
/hcp/api-docs/operations/[[...page]] 160.58 KB (🟒 -2.61 KB) 475.85 KB
/hcp/api-docs/packer/[[...page]] 160.58 KB (🟒 -2.61 KB) 475.84 KB
/hcp/api-docs/rbac/[[...page]] 160.58 KB (🟒 -2.61 KB) 475.84 KB
/hcp/api-docs/vagrant-box-registry/[[...page]] 160.59 KB (🟒 -2.61 KB) 475.86 KB
/hcp/api-docs/vault-secrets/[[...page]] 158.89 KB (🟒 -2.61 KB) 474.16 KB
/hcp/api-docs/vault/[[...page]] 158.89 KB (🟒 -2.61 KB) 474.15 KB
/hcp/api-docs/waypoint/[[...page]] 160.58 KB (🟒 -2.61 KB) 475.85 KB
/hcp/api-docs/webhook/[[...page]] 160.58 KB (🟒 -2.61 KB) 475.85 KB
/hcp/docs/[...page] 180.57 KB (🟒 -1.77 KB) 495.83 KB
/nomad 158.29 KB (🟒 -1.84 KB) 473.55 KB
/nomad/api-docs/[[...page]] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/nomad/commands/[[...page]] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/nomad/docs/[...page] 180.57 KB (🟒 -1.77 KB) 495.83 KB
/nomad/install 173.52 KB (🟒 -1.77 KB) 488.79 KB
/nomad/install/enterprise 173.5 KB (🟒 -1.77 KB) 488.77 KB
/nomad/plugins/[[...page]] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/nomad/tools/[[...page]] 180.57 KB (🟒 -1.76 KB) 495.84 KB
/open-api-docs-preview 165.04 KB (🟒 -2.61 KB) 480.31 KB
/open-api-docs-preview-v2/[[...page]] 163.84 KB (🟒 -2.61 KB) 479.11 KB
/packer 158.28 KB (🟒 -1.84 KB) 473.55 KB
/packer/docs/[...page] 180.57 KB (🟒 -1.77 KB) 495.83 KB
/packer/guides/[[...page]] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/packer/install 173.5 KB (🟒 -1.77 KB) 488.76 KB
/profile/account 137.43 KB (🟒 -2.26 KB) 452.7 KB
/profile/bookmarks 136.82 KB (🟒 -1.15 KB) 452.08 KB
/sentinel 158.29 KB (🟒 -1.84 KB) 473.55 KB
/sentinel/docs/[...page] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/sentinel/install 173.5 KB (🟒 -1.77 KB) 488.76 KB
/terraform 158.29 KB (🟒 -1.84 KB) 473.55 KB
/terraform/cdktf/[[...page]] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/terraform/cli/[[...page]] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/terraform/cloud-docs/[[...page]] 180.58 KB (🟒 -1.77 KB) 495.84 KB
/terraform/cloud-docs/agents/[[...page]] 180.58 KB (🟒 -1.77 KB) 495.85 KB
/terraform/docs/[...page] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/terraform/enterprise/[[...page]] 180.58 KB (🟒 -1.77 KB) 495.84 KB
/terraform/install 173.5 KB (🟒 -1.77 KB) 488.77 KB
/terraform/internals/[[...page]] 180.58 KB (🟒 -1.77 KB) 495.84 KB
/terraform/intro/[[...page]] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/terraform/language/[[...page]] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/terraform/mcp-server/[[...page]] 180.58 KB (🟒 -1.76 KB) 495.84 KB
/terraform/migrate/[[...page]] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/terraform/plugin/[[...page]] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/terraform/plugin/framework/[[...page]] 180.58 KB (🟒 -1.77 KB) 495.85 KB
/terraform/plugin/log/[[...page]] 180.58 KB (🟒 -1.77 KB) 495.84 KB
/terraform/plugin/mux/[[...page]] 180.58 KB (🟒 -1.77 KB) 495.84 KB
/terraform/plugin/sdkv2/[[...page]] 180.58 KB (🟒 -1.76 KB) 495.84 KB
/terraform/plugin/testing/[[...page]] 180.58 KB (🟒 -1.77 KB) 495.85 KB
/terraform/registry/[[...page]] 180.58 KB (🟒 -1.77 KB) 495.84 KB
/tutorials 126.54 KB (🟒 -1.62 KB) 441.81 KB
/tutorials/library 148.65 KB (🟒 -1.12 KB) 463.91 KB
/vagrant 158.29 KB (🟒 -1.84 KB) 473.55 KB
/vagrant/docs/[...page] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/vagrant/install 174.04 KB (🟒 -1.77 KB) 489.31 KB
/vagrant/install/vmware 173.5 KB (🟒 -1.77 KB) 488.77 KB
/vagrant/intro/[[...page]] 180.57 KB (🟒 -1.76 KB) 495.84 KB
/vagrant/vagrant-cloud/[[...page]] 180.58 KB (🟒 -1.77 KB) 495.84 KB
/validated-designs 119.87 KB (🟒 -1.94 KB) 435.13 KB
/validated-designs/[...slug] 176.3 KB (🟒 -1.77 KB) 491.57 KB
/validated-patterns 148.54 KB (🟒 -1.29 KB) 463.8 KB
/validated-patterns/[...tutorialSlug] 189.29 KB (🟒 -1.75 KB) 504.55 KB
/validated-patterns/[collectionSlug] 137.14 KB (🟒 -1.12 KB) 452.41 KB
/vault 158.28 KB (🟒 -1.84 KB) 473.55 KB
/vault/api-docs/[[...page]] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/vault/docs/[...page] 180.57 KB (🟒 -1.76 KB) 495.83 KB
/vault/install 173.49 KB (🟒 -1.77 KB) 488.76 KB
/vault/install/enterprise 173.5 KB (🟒 -1.77 KB) 488.77 KB
/waypoint 158.29 KB (🟒 -1.84 KB) 473.55 KB
/waypoint/docs/[...page] 180.57 KB (🟒 -1.77 KB) 495.84 KB
/well-architected-framework 172.96 KB (🟒 -1.92 KB) 488.23 KB
/well-architected-framework/[...page] 180.58 KB (🟒 -1.77 KB) 495.84 KB
Details

Only the gzipped size is provided here based on an expert tip.

First Load is the size of the global bundle plus the bundle for the individual page. If a user were to show up to your website and land on a given page, the first load size represents the amount of javascript that user would need to download. If next/link is used, subsequent page loads would only need to download that page's bundle (the number in the "Size" column), since the global bundle has already been downloaded.

Any third party scripts you have added directly to your app using the <script> tag are not accounted for in this analysis

Next to the size is how much the size has increased or decreased compared with the base branch of this PR. If this percentage has increased by 20% or more, there will be a red status indicator applied, indicating that special attention should be given to this.

Comment on lines +18 to +23
export function isInjectedPermalink(child: ReactNode): boolean {
if (!isValidElement(child)) return false

// props is unknown-ish, so narrow safely
const className = (child.props as { className?: unknown }).className
return className === "__permalink-h"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The former typing of child as ReactChild | ReactFragment | ReactPortal | PromiseLikeOfReactNode was causing issues with the bump to @types/react v18.2.68.

This change provides a cleaner, React 18 friendly version with these benefits:

  • Works across React 18 and many @types/react patch versions
  • Doesn’t rely on deprecated-ish unions like ReactChild
  • Avoids the whole PromiseLike thing entirely which is only an experimental or React 19 feature.
  • Uses React.isValidElement instead of props in child hacks

Comment on lines 19 to 23
<Text.DisplayExpressive
tag={`h${level}`}
tag={tag}
size="400"
weight={weight}
ref={ref}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Better, and more strict, polymorphic typing was introduced for MDS Text components in the latest release. This requires the heading tag to be cast as only valid HTML tags.

Comment on lines +10 to +17
&::after {
content: "";
position: absolute;
right: -24px;
top: 0;
bottom: 0;
width: 24px;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Just a little extra fix thrown in here to make it easier to move the mouse cursor from the top-left HashiCorp logo to the "popover" nav panel.

Comment on lines +100 to +102
ref={(element: HTMLButtonElement | null) => {
buttonElements.current[index] = element
}}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Callback refs in React must return void, but the ref callback here was returning the assigned element because the assignment expression evaluates to element.

Some older @types/react versions were looser about callback ref return types or type inference didn’t catch the expression-return. Our newer version is stricter (and closer to React’s intended contract).

>
<span className={s.label}>
{icon}
{icon ? <FlightIcon name={icon} size={16} /> : null}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Since icon here is now the icon name from the FlightIconName string literal union type, we now need to render the icon svg here with the MDS FlightIcon component by passing icon to the name prop.

Comment on lines +64 to +69
<FlightIcon
title="Interactive"
name="terminal-screen"
size={12}
color="var(--mds-color-foreground-primary"
className={s.interactiveIcon}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Rather than adding an override/modification to the Badge component to just display an icon we just render the FlightIcon component.

{text, ...rest}: Omit<BadgeProps, 'type' | 'className' | 'size'>
) {
return <Badge {...props} type="base" className={s.badge} size="small" />
return <Badge text={text} className={s.badge} size="small" {...rest} />
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Instead of relying on a type="base" variant of badge, since it was only used in this one place, we are just applying a very small style override. This could be done differently in the future but this is fine for now.

Comment on lines +61 to +64
const Adapter: QueryParamAdapterComponent = (props) => (
<NextAdapter {...props} />
)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

use-query-params types the adapter prop as a component that returns a ReactElement. The NextAdapter value is typed as a memoized component (MemoExoticComponent<...>), and in React 18’s types that shape is being treated as returning a ReactNode. Since ReactNode can be string, typescript was yelling that "this might be a string, and I require a ReactElement.”

By using a wrapper component pattern typescript is forced to see a normal function component, and it satisfies QueryParamProvider’s adapter type.

For more info see https://www.npmjs.com/package/next-query-params/v/4.1.0

.badgeContainer {
margin-left: 0.3333em;
position: relative;
top: -0.1em;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not needed with how the MDS badge alignment works

Copy link
Contributor

@LeahMarieBush LeahMarieBush left a comment

Choose a reason for hiding this comment

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

Looks good! One small comment about centering of the badges in the search results but other than that it looks great! I appreciate the product dropdown opening fix too πŸ™Œ


import { ReactElement } from 'react'
import Badge from 'components/badge'
import { Badge, type FlightIconName } from '@hashicorp/mds-react/components'
Copy link
Contributor

Choose a reason for hiding this comment

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

The badges in the preview look a bit off center compared to prod:
Screenshot 2025-12-12 at 3 22 38β€―PM

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I updated the css in the mds-react package to not include vertical-align: middle on the badge element. This wasn't necessary for .com and was causing this alignment issue in devdot.

LeahMarieBush
LeahMarieBush previously approved these changes Dec 16, 2025
Copy link
Contributor

@LeahMarieBush LeahMarieBush left a comment

Choose a reason for hiding this comment

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

LGTM!

@github-actions
Copy link

github-actions bot commented Jan 6, 2026

This PR is stale because it has been open 20 days with no activity. It will be closed in 5 days unless you remove the stale label or comment.

@github-actions github-actions bot added the stale label Jan 6, 2026
@github-actions github-actions bot removed the stale label Jan 8, 2026
@mikegolus mikegolus merged commit b99b8f8 into main Jan 8, 2026
12 checks passed
@mikegolus mikegolus deleted the mikegolus/implement-mds-badge branch January 8, 2026 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants