Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"utils": "@ucdjs-internal/shared-ui/lib/utils",
"hooks": "@ucdjs-internal/shared-ui/hooks",
"lib": "@ucdjs-internal/shared-ui/lib",
"ui": "@ucdjs-internal/shared-ui/components"
"ui": "@ucdjs-internal/shared-ui/ui"
},
"menuColor": "default",
"menuAccent": "subtle",
Expand Down
10 changes: 5 additions & 5 deletions packages/shared-ui/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
},
"iconLibrary": "lucide",
"aliases": {
"components": "@ucdjs-internal/shared-ui/components",
"utils": "@ucdjs-internal/shared-ui/lib/utils",
"hooks": "@ucdjs-internal/shared-ui/hooks",
"lib": "@ucdjs-internal/shared-ui/lib",
"ui": "@ucdjs-internal/shared-ui/components"
"components": "#components",
"utils": "#lib/utils",
"hooks": "#hooks",
"lib": "#lib",
"ui": "#ui"
}
}
6 changes: 6 additions & 0 deletions packages/shared-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
},
"exports": {
".": "./dist/index.mjs",
"./components/shiki-code": "./dist/components/shiki-code.mjs",
"./hooks/use-mobile": "./dist/hooks/use-mobile.mjs",
"./lib/utils": "./dist/lib/utils.mjs",
"./ui/alert-dialog": "./dist/ui/alert-dialog.mjs",
Expand All @@ -37,6 +38,9 @@
"./ui/card": "./dist/ui/card.mjs",
"./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",
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

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".

Copilot uses AI. Check for mistakes.
"./ui/dialog": "./dist/ui/dialog.mjs",
"./ui/dropdown-menu": "./dist/ui/dropdown-menu.mjs",
"./ui/field": "./dist/ui/field.mjs",
"./ui/input": "./dist/ui/input.mjs",
Expand Down Expand Up @@ -72,10 +76,12 @@
"@fontsource-variable/inter": "catalog:web",
"class-variance-authority": "catalog:web",
"clsx": "catalog:web",
"cmdk": "catalog:web",
"lucide-react": "catalog:web",
"react": "catalog:web",
"react-dom": "catalog:web",
"shadcn": "catalog:web",
"shiki": "catalog:web",
"tailwind-merge": "catalog:web",
"tailwindcss": "catalog:web",
"tw-animate-css": "catalog:web",
Expand Down
2 changes: 2 additions & 0 deletions packages/shared-ui/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ShikiCode } from "./shiki-code";
export type { ShikiCodeProps } from "./shiki-code";
63 changes: 63 additions & 0 deletions packages/shared-ui/src/components/shiki-code.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { BundledLanguage, BundledTheme } from "shiki";
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
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 }} />;
Comment on lines +48 to +62
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
});
Comment on lines +1 to +63
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
187 changes: 187 additions & 0 deletions packages/shared-ui/src/ui/command.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import { cn } from "#lib/utils";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "#ui/dialog";
import {
InputGroup,
InputGroupAddon,
} from "#ui/input-group";
import { Command as CommandPrimitive } from "cmdk";
import { CheckIcon, SearchIcon } from "lucide-react";
import * as React from "react";

function Command({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive>) {
return (
<CommandPrimitive
data-slot="command"
className={cn(
"bg-popover text-popover-foreground rounded-xl! p-1 flex size-full flex-col overflow-hidden",
className,
)}
{...props}
/>
);
}

function CommandDialog({
title = "Command Palette",
description = "Search for a command to run...",
children,
className,
showCloseButton = false,
...props
}: Omit<React.ComponentProps<typeof Dialog>, "children"> & {
title?: string;
description?: string;
className?: string;
showCloseButton?: boolean;
children: React.ReactNode;
}) {
return (
<Dialog {...props}>
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogContent
className={cn(
"rounded-xl! top-1/3 translate-y-0 overflow-hidden p-0",
className,
)}
showCloseButton={showCloseButton}
>
{children}
</DialogContent>
</Dialog>
);
}

function CommandInput({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
<div data-slot="command-input-wrapper" className="p-1 pb-0">
<InputGroup className="bg-input/30 border-input/30 h-8! rounded-lg! shadow-none! *:data-[slot=input-group-addon]:pl-2!">
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
"w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
/>
<InputGroupAddon>
<SearchIcon className="size-4 shrink-0 opacity-50" />
</InputGroupAddon>
</InputGroup>
</div>
);
}

function CommandList({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.List>) {
return (
<CommandPrimitive.List
data-slot="command-list"
className={cn(
"no-scrollbar max-h-72 scroll-py-1 outline-none overflow-x-hidden overflow-y-auto",
className,
)}
{...props}
/>
);
}

function CommandEmpty({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
return (
<CommandPrimitive.Empty
data-slot="command-empty"
className={cn("py-6 text-center text-sm", className)}
{...props}
/>
);
}

function CommandGroup({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
return (
<CommandPrimitive.Group
data-slot="command-group"
className={cn("text-foreground **:[[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium", className)}
{...props}
/>
);
}

function CommandSeparator({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
return (
<CommandPrimitive.Separator
data-slot="command-separator"
className={cn("bg-border -mx-1 h-px", className)}
{...props}
/>
);
}

function CommandItem({
className,
children,
...props
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
return (
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"data-selected:bg-muted data-selected:text-foreground data-selected:*:[svg]:text-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! [&_svg:not([class*='size-'])]:size-4 group/command-item data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className,
)}
{...props}
>
{children}
<CheckIcon className="ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
</CommandPrimitive.Item>
);
}

function CommandShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="command-shortcut"
className={cn("text-muted-foreground group-data-selected/command-item:text-foreground ml-auto text-xs tracking-widest", className)}
{...props}
/>
);
}

export {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
};
Loading
Loading