feat(shared-ui): add ShikiCode component for syntax highlighting#482
feat(shared-ui): add ShikiCode component for syntax highlighting#482
Conversation
Introduces the `ShikiCode` component, which utilizes the `shiki` library for syntax highlighting. The component allows customization of language and theme, enhancing code presentation in the UI. Additionally, updates the `package.json` to include `shiki` as a dependency and modifies the `tsdown.config.ts` to export the new component.
Introduce `Command` and `Dialog` components to enhance UI functionality. Update aliases in `components.json` for better organization and add new entries in `pnpm-lock.yaml` for `cmdk` package.
|
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughThis PR updates import alias configurations for the shared UI package and introduces four new UI component modules (ShikiCode, Command, ContextMenu, Dialog) with corresponding package exports and external dependencies (shiki, cmdk). Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Suggested labels
Poem
✨ Finishing touches
🧪 Generate unit tests (beta)
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 |
🌏 Preview Deployments
Built from commit: 🤖 This comment will be updated automatically when you push new commits to this PR. |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
This PR adds syntax highlighting capabilities to the shared-ui package by introducing 4 new components: Command, Dialog, ContextMenu, and ShikiCode. The PR adds two new dependencies (shiki 3.22.0 for syntax highlighting and cmdk 1.1.1 for command palette functionality), updates TypeScript configuration to support the new components directory, and modifies build configurations accordingly.
Changes:
- Adds ShikiCode component for syntax highlighting using the Shiki library
- Adds Command, Dialog, and ContextMenu UI components built on base-ui/react and cmdk libraries
- Adds dependencies: shiki (3.22.0) and cmdk (1.1.1)
- Updates TypeScript path mappings and build configuration to support the new components directory
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-workspace.yaml | Adds shiki and cmdk to the web catalog |
| pnpm-lock.yaml | Adds dependency entries for shiki 3.22.0 and cmdk 1.1.1 |
| tooling/tsconfig/base.json | Adds TypeScript path mapping for components directory |
| packages/shared-ui/tsdown.config.ts | Adds components directory to build entry points |
| packages/shared-ui/package.json | Adds new dependencies and exports for command, dialog, and shiki-code (missing context-menu) |
| packages/shared-ui/components.json | Updates aliases to use shorter # import syntax |
| apps/web/components.json | Corrects ui alias from components to ui |
| packages/shared-ui/src/ui/dialog.tsx | New Dialog component implementation using base-ui |
| packages/shared-ui/src/ui/context-menu.tsx | New ContextMenu component implementation using base-ui |
| packages/shared-ui/src/ui/command.tsx | New Command component implementation using cmdk |
| packages/shared-ui/src/components/shiki-code.tsx | New ShikiCode component for syntax highlighting (has critical issues) |
| packages/shared-ui/src/components/index.ts | Exports for components directory |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export const ShikiCode = memo<ShikiCodeProps>(({ | ||
| code, | ||
| language = "typescript", | ||
| theme = "github-dark", | ||
| className, | ||
| }) => { | ||
| const highlighter = use(getHighlighter()); | ||
|
|
||
| const html = highlighter.codeToHtml(code, { | ||
| lang: language, | ||
| theme, | ||
| }); | ||
|
|
||
| // eslint-disable-next-line react-dom/no-dangerously-set-innerhtml | ||
| return <div className={className} dangerouslySetInnerHTML={{ __html: html }} />; |
There was a problem hiding this comment.
Using memo() here may be premature optimization. The component performs syntax highlighting on every render regardless of memoization since the html variable is computed inside the component body. The memo wrapper only prevents re-renders when props haven't changed, but the expensive highlighting operation still occurs. Consider computing the html value based on code, language, and theme changes only, or document why memoization is beneficial here.
| @@ -0,0 +1,63 @@ | |||
| import type { BundledLanguage, BundledTheme } from "shiki"; | |||
There was a problem hiding this comment.
The PR title mentions only "ShikiCode component" but the description states that 4 components are being added (Command, Dialog, ContextMenu, and ShikiCode). The PR title should be updated to reflect the full scope of changes to accurately describe what's being added.
| import type { BundledLanguage, BundledTheme } from "shiki"; | ||
| import { cache, memo, use } from "react"; | ||
| import { createJavaScriptRegexEngine } from "shiki"; | ||
| import { createHighlighterCore } from "shiki/core"; | ||
|
|
||
| export interface ShikiCodeProps { | ||
| /** | ||
| * The code to highlight | ||
| */ | ||
| code: string; | ||
| /** | ||
| * The language to use for syntax highlighting | ||
| * @default "typescript" | ||
| */ | ||
| language?: BundledLanguage; | ||
| /** | ||
| * The theme to use for syntax highlighting | ||
| * @default "github-dark" | ||
| */ | ||
| theme?: BundledTheme; | ||
| /** | ||
| * Additional CSS class names | ||
| */ | ||
| className?: string; | ||
| } | ||
|
|
||
| const getHighlighter = cache(async () => { | ||
| return await createHighlighterCore({ | ||
| themes: [import("shiki/themes/github-dark.mjs")], | ||
| langs: [ | ||
| import("shiki/langs/javascript.mjs"), | ||
| import("shiki/langs/typescript.mjs"), | ||
| import("shiki/langs/json.mjs"), | ||
| ], | ||
| engine: createJavaScriptRegexEngine(), | ||
| }); | ||
| }); | ||
|
|
||
| /** | ||
| * A syntax highlighting component powered by Shiki. | ||
| * Renders code to HTML with syntax highlighting. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * <ShikiCode code="const x = 1;" language="typescript" /> | ||
| * ``` | ||
| */ | ||
| export const ShikiCode = memo<ShikiCodeProps>(({ | ||
| code, | ||
| language = "typescript", | ||
| theme = "github-dark", | ||
| className, | ||
| }) => { | ||
| const highlighter = use(getHighlighter()); | ||
|
|
||
| const html = highlighter.codeToHtml(code, { | ||
| lang: language, | ||
| theme, | ||
| }); | ||
|
|
||
| // eslint-disable-next-line react-dom/no-dangerously-set-innerhtml | ||
| return <div className={className} dangerouslySetInnerHTML={{ __html: html }} />; | ||
| }); |
There was a problem hiding this comment.
The component uses React's cache function and the use hook with an async function, which are server component patterns. However, the component is not explicitly marked with 'use server' directive. If this component is intended to be a server component, add the 'use server' directive at the top of the file. If it's meant to be a client component, this pattern won't work - you'll need to precompute the highlighted HTML on the server or use a different approach for client-side rendering.
| "./ui/collapsible": "./dist/ui/collapsible.mjs", | ||
| "./ui/combobox": "./dist/ui/combobox.mjs", | ||
| "./ui/command": "./dist/ui/command.mjs", | ||
| "./ui/context-menu": "./dist/ui/context-menu.mjs", |
There was a problem hiding this comment.
The ContextMenu component is implemented but missing from the package.json exports. This component cannot be imported by consumers of the package. Add an export entry for "./ui/context-menu" pointing to "./dist/ui/context-menu.mjs".
🔗 Linked issue
📚 Description
This PR adds 4 new components. Command, Dialog, ContextMenu and ShikiCode.
Summary by CodeRabbit