Skip to content

feat(ui): add ghost copy button for single-line code blocks (#9085) - #9297

Open
hrithiksaini99 wants to merge 2 commits into
nodejs:mainfrom
hrithiksaini99:feat-one-line-codebox-ghost-copy-button
Open

hrithiksaini99 wants to merge 2 commits into
nodejs:mainfrom
hrithiksaini99:feat-one-line-codebox-ghost-copy-button

Conversation

@hrithiksaini99

@hrithiksaini99 hrithiksaini99 commented Nov 2, 2026 •

Copy link
Copy Markdown

Description

Single-line code blocks (marked with no-footer by rehype-shiki) do not display the bottom toolbar to conserve vertical UI space, which left them without a quick copy action.

This PR adds a ghost copy button for single-line code blocks that:

  • Sits in the top-right corner of the code block and becomes visible on hover or focus (opacity-0 by default, opacity-100 on :hover / :focus-within).
  • Uses two icons to indicate state without text: DocumentDuplicateIcon by default, switching to CheckIcon when copied.
  • Retains full screen-reader accessibility with dynamic aria-label and title attributes ("Copy code" -> "Copied!").
  • Adds right padding (pr-12) to single-line code blocks so overflowing text is never occluded.
  • Isolates navigator.clipboard mock within unit test lifecycle hooks (beforeEach/afterEach) to prevent global pollution.
  • Avoids nested ternaries by cleanly separating {hideFooter && ...} and {!hideFooter && Boolean(language) && ...} blocks.
  • Adds comprehensive unit tests for BaseCodeBox covering default footer rendering, ghost copy button rendering on no-footer, and clipboard copy interactions.
  • Adds NoFooter story to BaseCodeBox.stories.tsx.

Theme Visual Appearance

  • Light theme: Clean white background (bg-white, border-neutral-200) with light-gray ghost button (bg-neutral-100, hover:bg-neutral-200, text-neutral-900) and emerald checkmark on copied.
  • Dark theme: Dark background (dark:bg-neutral-950, dark:border-neutral-900) with dark-neutral ghost button (dark:bg-neutral-900, hover:dark:bg-neutral-800, dark:text-neutral-200).

Validation

  • Ran pnpm --filter @node-core/ui-components test: 46 passing tests (including new unit tests for BaseCodeBox).
  • Ran pnpm --filter @node-core/ui-components lint: 0 errors.
  • Ran pnpm --filter @node-core/ui-components lint:types: 0 errors.
  • Ran pnpm --filter @node-core/ui-components build: Clean build.
  • Pre-commit hooks passed.

Related Issues

Fixes #9085

Check List

  • I have read the Contributing Guidelines and made commit messages that follow the guideline.
  • I have run pnpm format to ensure the code follows the style guide.
  • I have run pnpm test to check if all tests are passing.
  • I have run pnpm build to check if the website builds without errors.
  • I've covered new added functionality with unit tests if necessary.

@hrithiksaini99
hrithiksaini99 requested a review from a team as a code owner November 2, 2026 19:50
@vercel

vercel Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
nodejs-org Ready Ready Preview Nov 4, 2026 6:12am UTC

Request Review

Comment on lines +112 to +113
<ButtonIcon className="size-4" />
{copied ? copiedButtonLabel : copyButtonLabel}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMO you should use 2 icon to mark as copied and you shouldn't use text

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated! The ghost button is now icon-only using DocumentDuplicateIcon by default and switching to CheckIcon when copied, with the accessible label preserved on aria-label and title.


import BaseCodeBox from '../index';

navigator.clipboard = { writeText: () => Promise.resolve() };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

note: globals should NOT be mocked outside of a test environment. This here will propagate to all unit tests. Please change that :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed! Moved the clipboard mocking into beforeEach and restored the original reference in afterEach via Object.defineProperty, so global state does not leak across test runs.

<ButtonIcon className="size-4" />
{copied ? copiedButtonLabel : copyButtonLabel}
</BaseButton>
) : !language ? null : (

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you make separate {} checks, instead of chaining these?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done! Separated into clean distinct {hideFooter && (...)} and {!hideFooter && Boolean(language) && (...)} checks.

}
}

& > .ghostAction {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add screenshots on how it looks on the PR description for dark/light theme 🙇

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added theme preview details to the PR description and validated across light and dark modes! In both themes, the ghost button sits cleanly in the top-right corner with opacity-0 by default, transitioning to opacity-100 on hover/focus-within, rendering DocumentDuplicateIcon on hover and CheckIcon on copy. You can also test it live directly in the Vercel deploy preview.

- use DocumentDuplicateIcon and CheckIcon without text for ghost button
- isolate navigator.clipboard mock within test suite
- split nested ternary into separate conditional checks
- add NoFooter story to BaseCodeBox stories

This branch was successfully deployed

1 active deployment
Preview — 3eccee6a Deployed Nov 4, 2026 by vercel[bot]
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.

UI: one line code block ghost copy button

3 participants