Skip to content

feat(ui): add CodeBlock component with syntax highlighting and code tabs#116

Merged
kylengn merged 7 commits intomainfrom
feat/add-code-block
Nov 11, 2025
Merged

feat(ui): add CodeBlock component with syntax highlighting and code tabs#116
kylengn merged 7 commits intomainfrom
feat/add-code-block

Conversation

@kylengn
Copy link
Contributor

@kylengn kylengn commented Nov 10, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Added new code display component with syntax highlighting capabilities
    • Added multi-file code viewer with tab switching
    • Added copy-to-clipboard button for code snippets
    • Added configurable line numbers for code blocks
    • Added diff formatting support for code comparisons
  • Style Updates

    • Added new shadow styling variable to theme system

@kylengn kylengn self-assigned this Nov 10, 2025
Copilot AI review requested due to automatic review settings November 10, 2025 16:41
@vercel
Copy link

vercel bot commented Nov 10, 2025

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

Project Deployment Preview Comments Updated (UTC)
react-ui Ready Ready Preview Comment Nov 11, 2025 6:05am

@coderabbitai
Copy link

coderabbitai bot commented Nov 10, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR introduces a complete CodeBlock UI system featuring tabbed syntax-highlighted code display with theme support and copy functionality. It includes new React components, hooks for theme resolution and Shiki integration, CSS styling, and supporting dependencies.

Changes

Cohort / File(s) Summary
Theme & Styling
libs/react/ui/index.css, libs/react/ui/src/components/icon/icon.tsx
Adds CSS custom property --shadow-separator-inset for light and dark themes and inline theme mapping. Adds copy icon (RiFileCopyLine) to icon registry.
Dependencies
libs/react/ui/package.json
Adds @radix-ui/react-use-controllable-state and shiki packages to runtime dependencies.
CodeBlock Components
libs/react/ui/src/components/code-block/code-block.tsx, code-tabs.tsx, code-content.tsx, code-copy-button.tsx
Implements CodeBlock wrapper with context, CodeTabs for tabbed code switching, CodeContent for rendering syntax-highlighted or plain code, and CodeCopyButton for clipboard integration.
Code Block Stories
libs/react/ui/src/components/code-block/code-block.stories.tsx
Defines six Storybook stories demonstrating CodeBlock usage: Default, WithDiff, MultipleFiles, WithoutLineNumbers, Snippet, and SyntaxHighlighting.
Theme & Highlighting Hooks
libs/react/ui/src/hooks/useResolvedTheme.ts, useShikiHighlight.ts, useShikiStyleInjection.ts
Introduces useResolvedTheme for theme resolution, useShikiHighlight and useShikiHighlightMultiple for dynamic code highlighting via Shiki, and useShikiStyleInjection for style injection.
Public API Exports
libs/react/ui/src/components/code-block/index.ts, libs/react/ui/src/components/index.ts, libs/react/ui/src/hooks/index.ts
Barrel exports for code-block components and new hooks to consolidate public API.

Sequence Diagram

sequenceDiagram
    participant User
    participant CodeTabs
    participant CodeBlock Context
    participant CodeContent
    participant useShikiHighlight as useShikiHighlight Hook
    participant Shiki
    participant CodeCopyButton
    
    User->>CodeTabs: Select tab
    CodeTabs->>CodeBlock Context: Update active value
    CodeBlock Context->>CodeContent: Render with active language
    CodeContent->>useShikiHighlight: Request highlighted code
    
    alt syntaxHighlighting enabled
        useShikiHighlight->>Shiki: Dynamic import & highlight
        Shiki-->>useShikiHighlight: Return HTML
        useShikiHighlight-->>CodeContent: highlightedCode + loading state
        CodeContent->>CodeContent: Render highlighted (dangerouslySetInnerHTML)
    else fallback
        CodeContent->>CodeContent: Render plain code block
    end
    
    User->>CodeCopyButton: Click copy
    CodeCopyButton->>CodeBlock Context: Get active code
    CodeCopyButton->>CodeCopyButton: Copy to clipboard
    CodeCopyButton->>User: Show check icon
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Dynamic imports and state management in useShikiHighlight/useShikiHighlightMultiple: Requires careful review of cancellation logic, loading state handling, and Shiki integration patterns
  • Context setup and value propagation in CodeBlock: Verify proper context creation, value management, and descendant component integration
  • CodeTabs controllable state via useControllableState: Ensure correct implementation of controlled/uncontrolled behavior and tab switching logic
  • CodeContent rendering paths: Review both syntax-highlighted (dangerouslySetInnerHTML) and plain code rendering, line numbering logic, and fallback handling
  • useShikiStyleInjection side effect: Validate style injection timing, deduplication, and CSS correctness
  • Integration between components and hooks: Ensure all theme resolution, highlighting, and context flows work cohesively across the system

Suggested reviewers

  • EnzalRad
  • dvxam
  • noe-charmet

Poem

🐰 ✨ A fluffy hop through syntax so bright,
Code blocks now shine in dark and light!
Tabs and themes in harmony dance,
Shiki-powered, with copy's glance—
Chef's kiss to the highlighting trance! 🌟

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the primary changes: adding a CodeBlock component with syntax highlighting and code tabs capabilities.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (1)
libs/react/ui/src/hooks/useShikiStyleInjection.ts (1)

22-22: Consider making the font family configurable.

The hardcoded "Commit Mono" font may not be appropriate for all use cases or available in all environments. Consider accepting a font family parameter or using a CSS variable that can be customized.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1294ccb and b184ed2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • libs/react/ui/index.css (3 hunks)
  • libs/react/ui/package.json (1 hunks)
  • libs/react/ui/src/components/code-block/code-block.stories.tsx (1 hunks)
  • libs/react/ui/src/components/code-block/code-block.tsx (1 hunks)
  • libs/react/ui/src/components/code-block/code-content.tsx (1 hunks)
  • libs/react/ui/src/components/code-block/code-copy-button.tsx (1 hunks)
  • libs/react/ui/src/components/code-block/code-tabs.tsx (1 hunks)
  • libs/react/ui/src/components/code-block/index.ts (1 hunks)
  • libs/react/ui/src/components/icon/icon.tsx (2 hunks)
  • libs/react/ui/src/components/index.ts (1 hunks)
  • libs/react/ui/src/hooks/index.ts (1 hunks)
  • libs/react/ui/src/hooks/useResolvedTheme.ts (1 hunks)
  • libs/react/ui/src/hooks/useShikiHighlight.ts (1 hunks)
  • libs/react/ui/src/hooks/useShikiStyleInjection.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

⚙️ CodeRabbit configuration file

We handle errors at the edge of our applications in most cases. Do not recommend to add error handling around every single function. We prefer them to bubble up and be handled at upper layers.

Files:

  • libs/react/ui/src/hooks/index.ts
  • libs/react/ui/src/components/code-block/index.ts
  • libs/react/ui/src/hooks/useResolvedTheme.ts
  • libs/react/ui/src/hooks/useShikiStyleInjection.ts
  • libs/react/ui/src/components/icon/icon.tsx
  • libs/react/ui/package.json
  • libs/react/ui/src/components/code-block/code-block.stories.tsx
  • libs/react/ui/src/hooks/useShikiHighlight.ts
  • libs/react/ui/src/components/index.ts
  • libs/react/ui/index.css
  • libs/react/ui/src/components/code-block/code-copy-button.tsx
  • libs/react/ui/src/components/code-block/code-tabs.tsx
  • libs/react/ui/src/components/code-block/code-block.tsx
  • libs/react/ui/src/components/code-block/code-content.tsx
🧬 Code graph analysis (4)
libs/react/ui/src/components/code-block/code-block.stories.tsx (2)
libs/react/ui/src/components/code-block/code-block.tsx (8)
  • CodeBlock (38-63)
  • CodeBlockHeader (67-77)
  • CodeBlockFiles (83-91)
  • CodeBlockFilename (97-115)
  • CodeBlockCopyButton (123-146)
  • CodeBlockBody (180-184)
  • CodeBlockItem (191-228)
  • CodeBlockContent (240-275)
libs/react/ui/src/components/code-block/code-tabs.tsx (1)
  • CodeTabs (124-170)
libs/react/ui/src/components/code-block/code-copy-button.tsx (1)
libs/react/ui/src/components/icon/icon.tsx (1)
  • Icon (65-68)
libs/react/ui/src/components/code-block/code-tabs.tsx (5)
libs/react/ui/src/hooks/useResolvedTheme.ts (1)
  • useResolvedTheme (4-13)
libs/react/ui/src/hooks/useShikiStyleInjection.ts (1)
  • useShikiStyleInjection (3-27)
libs/react/ui/src/hooks/useShikiHighlight.ts (1)
  • useShikiHighlightMultiple (79-141)
libs/react/ui/src/components/code-block/code-copy-button.tsx (1)
  • CodeCopyButton (15-58)
libs/react/ui/src/components/code-block/code-content.tsx (1)
  • CodeContent (12-67)
libs/react/ui/src/components/code-block/code-block.tsx (5)
libs/react/ui/src/components/code-block/code-copy-button.tsx (1)
  • CodeCopyButton (15-58)
libs/react/ui/src/hooks/useResolvedTheme.ts (1)
  • useResolvedTheme (4-13)
libs/react/ui/src/hooks/useShikiStyleInjection.ts (1)
  • useShikiStyleInjection (3-27)
libs/react/ui/src/hooks/useShikiHighlight.ts (1)
  • useShikiHighlight (16-66)
libs/react/ui/src/components/code-block/code-content.tsx (1)
  • CodeContent (12-67)
🪛 ast-grep (0.39.7)
libs/react/ui/src/components/code-block/code-content.tsx

[warning] 33-33: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🔇 Additional comments (26)
libs/react/ui/src/components/icon/icon.tsx (1)

5-5: LGTM! Clean addition to the icon registry.

The new copy icon mapping follows the existing pattern and maintains alphabetical ordering in both the import and mapping sections.

Also applies to: 56-56

libs/react/ui/src/components/code-block/code-copy-button.tsx (1)

37-43: Good error handling implementation.

The try-catch block properly wraps the copy operation and ensures errors are converted to Error instances before passing to the onError callback.

libs/react/ui/src/components/index.ts (1)

5-5: LGTM! Proper barrel export.

The new code-block export is correctly positioned in alphabetical order and follows the existing pattern.

libs/react/ui/src/components/code-block/index.ts (1)

1-2: LGTM! Clean barrel exports.

The barrel pattern consolidates the code-block public API surface appropriately.

libs/react/ui/src/hooks/index.ts (1)

2-4: LGTM! Proper hook exports.

The new hooks are correctly exported and maintain alphabetical ordering.

libs/react/ui/package.json (1)

34-34: Dependencies are current and secure.

Verification confirms that both @radix-ui/react-use-controllable-state@1.2.2 and shiki@3.15.0 are the latest stable versions. No security vulnerabilities were found for either package. The caret constraints used are appropriate for managing minor and patch updates.

libs/react/ui/index.css (3)

357-358: LGTM! Clean addition of separator inset shadow for light theme.

The new CSS variable follows the established naming convention and is properly placed within the light theme root section.


553-554: LGTM! Appropriate dark theme variant.

The dark theme values are properly adjusted with different opacity levels to ensure proper visibility on dark backgrounds.


896-896: LGTM! Proper theme system integration.

The variable is correctly aliased in the inline theme mapping, making it available for use throughout the codebase.

libs/react/ui/src/components/code-block/code-content.tsx (2)

1-10: LGTM! Clean type definitions and imports.

The component props are well-structured with proper TypeScript typing and spread of HTML attributes.


23-38: LGTM! Proper handling of Shiki-generated HTML.

The use of dangerouslySetInnerHTML is appropriate here since Shiki is a trusted syntax highlighting library that generates safe HTML. The biome-ignore comment correctly documents this decision. The static analysis warning is a false positive in this context.

libs/react/ui/src/hooks/useShikiHighlight.ts (2)

16-66: LGTM! Well-implemented hook with proper cleanup.

The hook correctly:

  • Guards execution when syntaxHighlighting is false
  • Uses dynamic import for code splitting
  • Implements cancellation to prevent state updates after unmount
  • Has complete effect dependencies

The silent error handling (lines 51-55) is appropriate since falling back to non-highlighted code is acceptable behavior.


79-141: LGTM! Efficient batch highlighting with proper cancellation.

The hook properly handles multiple code blocks with:

  • In-loop cancellation checks (lines 106-108) to avoid unnecessary work
  • Batched state update after all highlighting completes
  • Proper cleanup on unmount

Note that the codes object in dependencies (line 138) will trigger re-highlighting whenever the object identity changes. This is expected behavior but consumers should memoize the codes object to avoid unnecessary re-highlighting.

libs/react/ui/src/components/code-block/code-block.stories.tsx (4)

1-23: LGTM! Well-structured Storybook setup.

The meta configuration and type definitions are properly set up for the CodeBlock component stories.


38-96: LGTM! Clear demonstration of basic usage and diff functionality.

The Default and WithDiff stories effectively demonstrate the component's core capabilities with appropriate example code.


150-164: LGTM! Good demonstration of multi-file tab functionality.

The MultipleFiles story effectively showcases the CodeTabs component with realistic TypeScript examples.


248-278: LGTM! Proper handling of custom story arg.

The @ts-expect-error comment on line 260 is appropriate since syntaxHighlighting is being passed through context.args rather than as a direct CodeBlock prop. This is a valid Storybook pattern for controlling component behavior in stories.

libs/react/ui/src/components/code-block/code-tabs.tsx (5)

1-23: LGTM! Clean type definitions and imports.

The CodeTabsProps type properly extends Radix Tabs.Root props while excluding children, and adds all necessary custom props for the code block functionality.


25-58: LGTM! Well-structured internal component.

The CodeTabsContent component properly:

  • Resolves the theme for Shiki
  • Memoizes the codes keys (line 48) to avoid unnecessary recalculations
  • Injects Shiki styles when syntax highlighting is enabled
  • Handles the highlighting lifecycle

64-94: LGTM! Accessible tab list with proper styling.

The tab list implementation:

  • Uses semantic Radix UI components for accessibility
  • Applies appropriate active state styling (lines 81-85)
  • Conditionally renders the copy button with the active code (line 93)
  • Includes custom separator styling with the new --shadow-separator-inset variable (line 71)

95-122: LGTM! Clean tab content rendering.

The content rendering properly:

  • Maps over all code entries
  • Passes highlighted code to CodeContent when available
  • Maintains consistent styling with the parent CodeBlock system
  • Respects the lineNumbers prop

124-170: LGTM! Robust controlled/uncontrolled state handling.

The main CodeTabs component:

  • Properly memoizes the first key (line 141) to avoid recalculation
  • Uses useControllableState for flexible controlled/uncontrolled usage
  • Delegates rendering to the internal CodeTabsContent component
  • Passes through all necessary props including the active value
libs/react/ui/src/components/code-block/code-block.tsx (4)

1-36: LGTM! Clean type definitions and context setup.

The types and context structure provide a solid foundation for the component system. The BundledLanguage type as a string alias (line 11) provides flexibility for language specification.


38-63: LGTM! Proper controlled state handling.

The main CodeBlock component correctly uses useControllableState from Radix UI to support both controlled and uncontrolled usage patterns.


123-146: LGTM! Proper integration with CodeCopyButton.

The component correctly extracts the active code from context by matching the language value (line 132) and delegates to the shared CodeCopyButton component.


240-275: LGTM! Well-integrated syntax highlighting.

The CodeBlockContent component properly:

  • Resolves the current theme
  • Injects Shiki styles when needed
  • Handles the highlighting lifecycle with loading states
  • Falls back to CodeBlockFallback when highlighting is disabled or loading (line 262-263)
  • Delegates to the shared CodeContent component for the final render

The integration with the highlighting hooks is clean and follows React best practices.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds comprehensive code block components with syntax highlighting capabilities to the UI library. The implementation uses Shiki for syntax highlighting and includes support for themes, line numbers, copy-to-clipboard functionality, and tabbed code displays.

Key Changes:

  • Adds new CodeBlock and CodeTabs components with composable sub-components for headers, files, and copy buttons
  • Implements custom hooks (useShikiHighlight, useShikiStyleInjection, useResolvedTheme) to manage syntax highlighting and theme resolution
  • Integrates Shiki v3.15.0 and @radix-ui/react-use-controllable-state for enhanced functionality

Reviewed Changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds dependencies for Shiki syntax highlighting library and supporting packages
libs/react/ui/package.json Updates package dependencies to include shiki and @radix-ui/react-use-controllable-state
libs/react/ui/src/hooks/useShikiHighlight.ts Implements hooks for loading and highlighting single or multiple code blocks using Shiki
libs/react/ui/src/hooks/useShikiStyleInjection.ts Manages dynamic style injection for Shiki-generated HTML
libs/react/ui/src/hooks/useResolvedTheme.ts Resolves the current theme (light/dark) including system preferences
libs/react/ui/src/hooks/index.ts Exports new hooks for public API
libs/react/ui/src/components/code-block/code-block.tsx Main code block component with context-based state management
libs/react/ui/src/components/code-block/code-tabs.tsx Tabbed interface for displaying multiple code snippets
libs/react/ui/src/components/code-block/code-content.tsx Renders code content with optional syntax highlighting and line numbers
libs/react/ui/src/components/code-block/code-copy-button.tsx Copy-to-clipboard button with visual feedback
libs/react/ui/src/components/code-block/index.ts Exports code block components
libs/react/ui/src/components/code-block/code-block.stories.tsx Storybook examples demonstrating component usage
libs/react/ui/src/components/index.ts Adds code-block to component exports
libs/react/ui/src/components/icon/icon.tsx Adds copy icon for clipboard functionality
libs/react/ui/index.css Defines new shadow variables for separator styling
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

noe-charmet
noe-charmet previously approved these changes Nov 11, 2025
@kylengn kylengn merged commit 2cfbf33 into main Nov 11, 2025
5 checks passed
@kylengn kylengn deleted the feat/add-code-block branch November 11, 2025 16:32
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