feat(ui): add CodeBlock component with syntax highlighting and code tabs#116
feat(ui): add CodeBlock component with syntax highlighting and code tabs#116
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughThis 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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. Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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.tslibs/react/ui/src/components/code-block/index.tslibs/react/ui/src/hooks/useResolvedTheme.tslibs/react/ui/src/hooks/useShikiStyleInjection.tslibs/react/ui/src/components/icon/icon.tsxlibs/react/ui/package.jsonlibs/react/ui/src/components/code-block/code-block.stories.tsxlibs/react/ui/src/hooks/useShikiHighlight.tslibs/react/ui/src/components/index.tslibs/react/ui/index.csslibs/react/ui/src/components/code-block/code-copy-button.tsxlibs/react/ui/src/components/code-block/code-tabs.tsxlibs/react/ui/src/components/code-block/code-block.tsxlibs/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
copyicon 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.2andshiki@3.15.0are 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
dangerouslySetInnerHTMLis 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
syntaxHighlightingis 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
codesobject in dependencies (line 138) will trigger re-highlighting whenever the object identity changes. This is expected behavior but consumers should memoize thecodesobject 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-errorcomment on line 260 is appropriate sincesyntaxHighlightingis 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
CodeTabsPropstype 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
CodeTabsContentcomponent 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-insetvariable (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
lineNumbersprop
124-170: LGTM! Robust controlled/uncontrolled state handling.The main
CodeTabscomponent:
- Properly memoizes the first key (line 141) to avoid recalculation
- Uses
useControllableStatefor flexible controlled/uncontrolled usage- Delegates rendering to the internal
CodeTabsContentcomponent- 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
BundledLanguagetype as a string alias (line 11) provides flexibility for language specification.
38-63: LGTM! Proper controlled state handling.The main
CodeBlockcomponent correctly usesuseControllableStatefrom 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
CodeCopyButtoncomponent.
240-275: LGTM! Well-integrated syntax highlighting.The
CodeBlockContentcomponent properly:
- Resolves the current theme
- Injects Shiki styles when needed
- Handles the highlighting lifecycle with loading states
- Falls back to
CodeBlockFallbackwhen highlighting is disabled or loading (line 262-263)- Delegates to the shared
CodeContentcomponent for the final renderThe integration with the highlighting hooks is clean and follows React best practices.
There was a problem hiding this comment.
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
CodeBlockandCodeTabscomponents 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.
…ter performance and readability
…age, and description
Summary by CodeRabbit
Release Notes
New Features
Style Updates