Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -2139,7 +2139,7 @@ function SpinnerPreview() {
}

// eslint-disable-next-line max-lines-per-function -- Switch statement mapping all components
export function ComponentPreview({ componentName }: ComponentPreviewProps) {
function getComponentPreview(componentName: string): React.ReactNode {
switch (componentName) {
case "accordion":
return <AccordionPreview />;
Expand Down Expand Up @@ -2483,3 +2483,7 @@ export function ComponentPreview({ componentName }: ComponentPreviewProps) {
return <div className="text-muted-foreground">Preview not available</div>;
}
}

export function ComponentPreview({ componentName }: ComponentPreviewProps) {
return getComponentPreview(componentName);
}
4 changes: 2 additions & 2 deletions apps/registry/components/header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type Registry = {
};

export function Header() {
const router = useRouter();
const { push } = useRouter();
const registry = registryData as Registry;

const navItems = [
Expand Down Expand Up @@ -42,7 +42,7 @@ export function Header() {
groupHeading="Components"
items={searchItems}
onSelect={(item) => {
router.push(`/components/${item.id}`);
push(`/components/${item.id}`);
}}
searchPlaceholder="Search components..."
/>
Expand Down
2 changes: 1 addition & 1 deletion apps/registry/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -4492,5 +4492,5 @@
}
],
"version": "0.2.1",
"generatedAt": "2026-05-10T19:14:31.709Z"
"generatedAt": "2026-05-15T12:39:44.318Z"
}
33 changes: 22 additions & 11 deletions apps/registry/registry/default/canvas-shell/canvas-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ function getSafeAreaStyle(
} satisfies CanvasShellSafeAreaStyle;
}

function getCanvasShellContentStyle(): CSSProperties {
return {
paddingBottom: "var(--canvas-shell-safe-bottom)",
paddingLeft: "var(--canvas-shell-safe-left)",
paddingRight: "var(--canvas-shell-safe-right)",
paddingTop: "var(--canvas-shell-safe-top)",
} satisfies CSSProperties;
}

const hasChromeContent = Boolean;

type CanvasShellChromeAfterProps = Pick<
Expand Down Expand Up @@ -235,10 +244,15 @@ function renderLegacyCanvasShell(
);
}

function renderFloatingContent(
children: ReactNode,
contentStyle: CSSProperties,
) {
type CanvasShellFloatingContentProps = {
children?: ReactNode;
contentStyle: CSSProperties;
};

function CanvasShellFloatingContent({
children,
contentStyle,
}: CanvasShellFloatingContentProps) {
return (
<div
className="relative z-0 h-full w-full min-h-0 min-w-0"
Expand Down Expand Up @@ -291,12 +305,7 @@ function renderFloatingCanvasShell(
...getSafeAreaStyle(safeAreaInsets),
...style,
} satisfies CSSProperties;
const contentStyle = {
paddingBottom: "var(--canvas-shell-safe-bottom)",
paddingLeft: "var(--canvas-shell-safe-left)",
paddingRight: "var(--canvas-shell-safe-right)",
paddingTop: "var(--canvas-shell-safe-top)",
} satisfies CSSProperties;
const contentStyle = getCanvasShellContentStyle();

return (
<section
Expand All @@ -314,7 +323,9 @@ function renderFloatingCanvasShell(
leftBar={hasLeftBar ? resolvedLeftBar : undefined}
topBar={hasTopBar ? topBar : undefined}
/>
{renderFloatingContent(children, contentStyle)}
<CanvasShellFloatingContent contentStyle={contentStyle}>
{children}
</CanvasShellFloatingContent>
<CanvasShellChromeAfter
bottomBar={hasBottomBar ? resolvedBottomBar : undefined}
inset={inset}
Expand Down
30 changes: 15 additions & 15 deletions apps/registry/registry/default/carousel/carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,6 @@ function useCarouselLogic({
const [canScrollPrevious, setCanScrollPrevious] = useState(false);
const [canScrollNext, setCanScrollNext] = useState(false);

const onSelect = useCallback((api: CarouselApi) => {
if (!api) {
return;
}

setCanScrollPrevious(api.canScrollPrev());
setCanScrollNext(api.canScrollNext());
}, []);

const scrollPrevious = useCallback(() => {
api?.scrollPrev();
}, [api]);
Expand Down Expand Up @@ -117,19 +108,28 @@ function useCarouselLogic({
return;
}

api.on("reInit", onSelect);
api.on("select", onSelect);
const updateScrollButtons = (currentApi: CarouselApi) => {
if (!currentApi) {
return;
}

setCanScrollPrevious(currentApi.canScrollPrev());
setCanScrollNext(currentApi.canScrollNext());
};

api.on("reInit", updateScrollButtons);
api.on("select", updateScrollButtons);

const rafId = requestAnimationFrame(() => {
onSelect(api);
updateScrollButtons(api);
});

return () => {
api?.off("select", onSelect);
api?.off("reInit", onSelect);
api?.off("select", updateScrollButtons);
api?.off("reInit", updateScrollButtons);
cancelAnimationFrame(rafId);
};
}, [api, onSelect]);
}, [api]);

return {
api,
Expand Down
74 changes: 28 additions & 46 deletions apps/registry/registry/default/code-block/code-block.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use client";

import { type ReactNode, useEffect, useRef, useState } from "react";
import { Children, isValidElement, useState } from "react";

import { Check, Copy } from "lucide-react";
import { useTheme } from "next-themes";
import type { ReactNode, WheelEvent } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import {
oneDark,
Expand All @@ -14,33 +15,25 @@ import { cn } from "@vllnt/ui";
import { Button } from "@vllnt/ui";

type CodeBlockProps = {
children: ReactNode;
children?: ReactNode;
className?: string;
code?: string;
language?: string;
showLanguage?: boolean;
};

function extractTextFromChildren(children: ReactNode): string {
if (typeof children === "string") {
return children;
}
if (typeof children === "number") {
return String(children);
}
if (Array.isArray(children)) {
return children.map(extractTextFromChildren).join("");
}
if (
children &&
typeof children === "object" &&
"props" in children &&
children.props &&
typeof children.props === "object" &&
"children" in children.props
) {
return extractTextFromChildren(children.props.children as ReactNode);
}
return String(children ?? "");
export function extractTextFromChildren(children: ReactNode): string {
return Children.toArray(children)
.map((child) => {
if (typeof child === "string" || typeof child === "number") {
return String(child);
}
if (isValidElement<{ children?: ReactNode }>(child)) {
return extractTextFromChildren(child.props.children);
}
return "";
})
.join("");
}

function findScrollableParent(
Expand All @@ -51,9 +44,19 @@ function findScrollableParent(
return findScrollableParent(element.parentElement);
}

function redirectVerticalWheel(event: WheelEvent<HTMLDivElement>): void {
if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) return;
const scrollable = findScrollableParent(event.currentTarget);
if (scrollable) {
scrollable.scrollTop += event.deltaY;
event.preventDefault();
}
}

export function CodeBlock({
children,
className,
code: codeProperty,
language = "typescript",
showLanguage = false,
}: CodeBlockProps) {
Expand All @@ -63,28 +66,7 @@ export function CodeBlock({
const resolvedTheme = theme === "system" ? systemTheme : theme;
const isDark = resolvedTheme !== "light";
const codeStyle = isDark ? oneDark : oneLight;
const code = extractTextFromChildren(children);

const scrollRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const element = scrollRef.current;
if (!element) return;

const onWheel = (event: WheelEvent) => {
if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) return;
const scrollable = findScrollableParent(element);
if (scrollable) {
scrollable.scrollTop += event.deltaY;
event.preventDefault();
}
};

element.addEventListener("wheel", onWheel, { passive: false });
return () => {
element.removeEventListener("wheel", onWheel);
};
}, []);
const code = codeProperty ?? extractTextFromChildren(children);

const handleCopy = async () => {
await navigator.clipboard.writeText(code);
Expand All @@ -103,7 +85,7 @@ export function CodeBlock({
>
<div
className="relative overflow-x-auto overflow-y-hidden touch-pan-y"
ref={scrollRef}
onWheel={redirectVerticalWheel}
>
<SyntaxHighlighter
codeTagProps={{
Expand Down
22 changes: 19 additions & 3 deletions apps/registry/registry/default/code-playground/code-playground.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useState } from "react";
import { Children, isValidElement, useState } from "react";

import { Check, Code, Copy, FileCode } from "lucide-react";
import type { ReactNode } from "react";
Expand All @@ -27,7 +27,8 @@ function CodeLine({ highlightLines, line, lineNumber }: CodeLineProps) {
}

export type CodePlaygroundProps = {
children: ReactNode;
children?: ReactNode;
code?: string;
description?: string;
filename?: string;
highlightLines?: number[];
Expand All @@ -38,8 +39,23 @@ export type CodePlaygroundProps = {

const EMPTY_HIGHLIGHT_LINES: number[] = [];

function extractTextFromChildren(children: ReactNode): string {
return Children.toArray(children)
.map((child) => {
if (typeof child === "string" || typeof child === "number") {
return String(child);
}
if (isValidElement<{ children?: ReactNode }>(child)) {
return extractTextFromChildren(child.props.children);
}
return "";
})
.join("");
}

export function CodePlayground({
children,
code: codeProperty,
description,
filename,
highlightLines = EMPTY_HIGHLIGHT_LINES,
Expand All @@ -48,7 +64,7 @@ export function CodePlayground({
title,
}: CodePlaygroundProps) {
const [copied, setCopied] = useState(false);
const code = typeof children === "string" ? children : "";
const code = codeProperty ?? extractTextFromChildren(children);
const lines = code.split("\n");

const handleCopy = async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { memo, useCallback, useEffect, useRef } from "react";

import type { ReactNode } from "react";

import { useDocumentEventListener } from "@vllnt/ui";
import { cn } from "@vllnt/ui";
import { Button } from "@vllnt/ui";

Expand Down Expand Up @@ -152,13 +153,7 @@ function CompletionDialogImpl({
[isOpen, onClose, onConfirm, onCancel, confirmShortcut, cancelShortcut],
);

useEffect(() => {
if (!isOpen) return;
document.addEventListener("keydown", handleKeyDown, true);
return () => {
document.removeEventListener("keydown", handleKeyDown, true);
};
}, [isOpen, handleKeyDown]);
useDocumentEventListener("keydown", handleKeyDown, true);

if (!isOpen) return null;

Expand Down
Loading
Loading