Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
84373f4
feat: add unified fullscreen tool playground shell
naymurdev Feb 24, 2026
f59885f
fix: make svg line draw a full-page playground view
naymurdev Feb 24, 2026
e5a749c
fix: restore preset scrolling and stable canvas sizing in svg playground
naymurdev Feb 24, 2026
a754227
fix: keep svg drawing panel closed until explicitly opened
naymurdev Feb 24, 2026
f5887e1
feat: add canva-style left nav rail to svg line draw playground
naymurdev Feb 24, 2026
dbeacc7
feat: make svg rail labels clear and add editor switcher links
naymurdev Feb 24, 2026
14cc59b
feat: add rail icons and editor dropdown for svg playground
naymurdev Feb 24, 2026
5dfbb59
feat: add collapsible narrow rail and shadcn page select
naymurdev Feb 24, 2026
6cde849
feat: apply canva-like playground layout to shadows editor
naymurdev Feb 24, 2026
a9f6787
fix: keep shadows preview pinned and add background switcher
naymurdev Feb 24, 2026
9ef7c34
fix: enlarge shadows presets panel and stabilize collapsed preview la…
naymurdev Feb 24, 2026
5f9cda4
fix: make shadows preview fill remaining width when sidebar is collapsed
naymurdev Feb 24, 2026
d72e44b
feat: add text shadow presets and tailwind v4 output to shadows
naymurdev Feb 25, 2026
53e4151
feat: add compact background color picker control in shadows preview
naymurdev Feb 25, 2026
cb41462
fix: simplify text-shadow presets UI and expand preset library
naymurdev Feb 25, 2026
7a03522
feat: tighten shadows rail width and add theme toggle button
naymurdev Feb 25, 2026
a3516b2
fix: wire text preset layer props and shared text-shadow controls
naymurdev Feb 25, 2026
3b0676d
fix: remove text-mode surface background controls in shadows preview
naymurdev Feb 25, 2026
d92c50a
feat: migrate clip-paths to fullscreen playground editor layout
naymurdev Feb 25, 2026
b98fe91
feat: add right-side export drawer for clip-path component and svg code
naymurdev Feb 25, 2026
39884a7
feat: add svg/shadows-style sidebar rail to clip-path editor
naymurdev Feb 25, 2026
7298435
fix: prevent clip-path sidebar layout collapse in fullscreen grid
naymurdev Feb 25, 2026
3b152b6
feat: add top-right export button in clip-path playground
naymurdev Feb 25, 2026
35fff54
chore: update shapes and animate-svg
naymurdev Mar 31, 2026
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
20 changes: 14 additions & 6 deletions app/(tools)/background-snippets/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import BackgroundSnippetsGenerator from "@/components/view/background-snippets";
import { siteConfig } from "@/lib/utils";
import type { Metadata } from "next";
import React from "react";
import { ToolPlaygroundShell } from "@/components/common/tool-playground-shell";
export const metadata: Metadata = {
title: "Background Snippets Generator",
description:
Expand Down Expand Up @@ -56,11 +57,18 @@ export const metadata: Metadata = {
},
};
function page() {
return (
<>
<BackgroundSnippetsGenerator />
</>
);
return (
<ToolPlaygroundShell
title="Background Snippets Generator"
description="Generate modern CSS snippets for patterned backgrounds."
examples={["Noise", "Grid", "Dots", "Stripes"]}
docs="Use Playground for live editing, Examples to load quick presets, and Export to copy production-ready output."
exportLabel="CSS"
exportCode={`/* export output from active tool */`}
>
<BackgroundSnippetsGenerator />
</ToolPlaygroundShell>
);
}

export default page;
export default page;
14 changes: 11 additions & 3 deletions app/(tools)/clip-paths/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ClipPathGenerator from "@/components/view/clip-path";
import { siteConfig } from "@/lib/utils";
import type { Metadata } from "next";
import React from "react";
import { ToolPlaygroundShell } from "@/components/common/tool-playground-shell";
export const metadata: Metadata = {
title: "SVG Clip-Path Generator",
description:
Expand Down Expand Up @@ -52,10 +53,17 @@ export const metadata: Metadata = {

function page() {
return (
<>
<ToolPlaygroundShell
title="Clip-path Generator"
description="Create polygon, circle, ellipse, and inset clip-path shapes."
examples={["Hexagon", "Blob", "Ticket", "Diamond"]}
docs="Use Playground for live editing, Examples to load quick presets, and Export to copy production-ready output."
exportLabel="CSS"
exportCode={`/* export output from active tool */`}
>
<ClipPathGenerator />
</>
</ToolPlaygroundShell>
);
}

export default page;
export default page;
18 changes: 12 additions & 6 deletions app/(tools)/color-lab/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ColorConverter from "@/components/view/colors";
import { siteConfig } from "@/lib/utils";
import type { Metadata } from "next";
import React, { Suspense } from "react";
import { ToolPlaygroundShell } from "@/components/common/tool-playground-shell";
export const metadata: Metadata = {
title:
"Color Lab – Generate Color Palettes, Convert Codes & Build Shadcn Themes",
Expand Down Expand Up @@ -85,12 +86,17 @@ const PageLoading = () => {
};
function page() {
return (
<>
<Suspense fallback={<PageLoading />}>
<ColorConverter />
</Suspense>
</>
<ToolPlaygroundShell
title="Color Lab"
description="Convert HEX/RGB/HSL and generate scales, contrast, and tokens."
examples={["Brand blue", "Sunset orange", "Mint", "Slate"]}
docs="Use Playground for live editing, Examples to load quick presets, and Export to copy production-ready output."
exportLabel="CSS Variables"
exportCode={`/* export output from active tool */`}
>
<ColorConverter />
</ToolPlaygroundShell>
);
}

export default page;
export default page;
20 changes: 16 additions & 4 deletions app/(tools)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
"use client";

import ToolsHeader from "@/components/common/tools-header";
import { usePathname } from "next/navigation";
import type React from "react";

function Toolslayout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const isLineDraw = pathname === "/svg-line-draw";

return (
<>
<ToolsHeader />
<main className="bg-white pt-28 text-black dark:bg-black dark:text-white">
<div className="h-screen overflow-hidden bg-white text-black dark:bg-black dark:text-white">
{!isLineDraw && <ToolsHeader />}
<main
className={
isLineDraw
? "h-full overflow-hidden"
: "h-full overflow-hidden px-3 pb-3 pt-20 xl:px-6"
}
>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{children}
</main>
</>
</div>
);
}

Expand Down
14 changes: 11 additions & 3 deletions app/(tools)/mesh-gradients/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ShaderGradientGenerator } from "@/components/view/mesh-gradient";
import { siteConfig } from "@/lib/utils";
import type { Metadata } from "next";
import React from "react";
import { ToolPlaygroundShell } from "@/components/common/tool-playground-shell";
export const metadata: Metadata = {
title: "Mesh-Gradient Generator",
description:
Expand Down Expand Up @@ -52,10 +53,17 @@ export const metadata: Metadata = {

function page() {
return (
<>
<ToolPlaygroundShell
title="Mesh Gradient Generator"
description="Build fluid mesh gradients with draggable color points."
examples={["Aurora", "Sunset", "Neon", "Pastel"]}
docs="Use Playground for live editing, Examples to load quick presets, and Export to copy production-ready output."
exportLabel="CSS"
exportCode={`/* export output from active tool */`}
>
<ShaderGradientGenerator />
</>
</ToolPlaygroundShell>
Comment on lines +56 to +65
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.tsx" -o -name "*.ts" | grep -i "mesh-gradient" | head -20

Repository: ui-layouts/ui-tools

Length of output: 292


🏁 Script executed:

rg -n 'h-\[.*vh\]' --type=tsx --type=ts -g '*/mesh-gradient/*'

Repository: ui-layouts/ui-tools

Length of output: 90


🏁 Script executed:

rg -n 'col-span|ScrollArea|overflow' --type=tsx --type=ts -g '*/mesh-gradient/*'

Repository: ui-layouts/ui-tools

Length of output: 90


🏁 Script executed:

rg 'h-\[.*vh\]' ./components/view/mesh-gradient/

Repository: ui-layouts/ui-tools

Length of output: 471


🏁 Script executed:

rg 'col-span|ScrollArea|overflow' ./components/view/mesh-gradient/

Repository: ui-layouts/ui-tools

Length of output: 1737


🏁 Script executed:

wc -l ./components/view/mesh-gradient/index.tsx

Repository: ui-layouts/ui-tools

Length of output: 108


🏁 Script executed:

cat -n ./components/view/mesh-gradient/index.tsx | head -100

Repository: ui-layouts/ui-tools

Length of output: 3243


🏁 Script executed:

cat -n ./components/view/mesh-gradient/index.tsx | sed -n '100,200p'

Repository: ui-layouts/ui-tools

Length of output: 2299


🏁 Script executed:

cat -n ./app/\(tools\)/mesh-gradients/page.tsx

Repository: ui-layouts/ui-tools

Length of output: 2671


🏁 Script executed:

cat -n ./components/view/mesh-gradient/index.tsx | sed -n '200,300p'

Repository: ui-layouts/ui-tools

Length of output: 3226


🏁 Script executed:

cat -n ./components/view/mesh-gradient/index.tsx | sed -n '250,350p'

Repository: ui-layouts/ui-tools

Length of output: 4166


🏁 Script executed:

wc -l ./components/common/tool-playground-shell.tsx

Repository: ui-layouts/ui-tools

Length of output: 112


🏁 Script executed:

cat -n ./components/common/tool-playground-shell.tsx | head -150

Repository: ui-layouts/ui-tools

Length of output: 6158


🏁 Script executed:

cat -n ./components/view/mesh-gradient/index.tsx | sed -n '343,400p'

Repository: ui-layouts/ui-tools

Length of output: 2739


🏁 Script executed:

cat -n ./components/view/mesh-gradient/index.tsx | sed -n '348,380p'

Repository: ui-layouts/ui-tools

Length of output: 1655


🏁 Script executed:

cat -n ./components/view/mesh-gradient/index.tsx | sed -n '400,460p'

Repository: ui-layouts/ui-tools

Length of output: 2680


🏁 Script executed:

cat -n ./components/view/mesh-gradient/control-panel.tsx | head -30

Repository: ui-layouts/ui-tools

Length of output: 1042


🏁 Script executed:

cat -n ./components/view/mesh-gradient/example-gradients.tsx | head -20

Repository: ui-layouts/ui-tools

Length of output: 631


🏁 Script executed:

cat -n ./components/common/tool-playground-shell.tsx | sed -n '128,135p'

Repository: ui-layouts/ui-tools

Length of output: 390


🏁 Script executed:

# Check if there's a render of this page to verify actual layout behavior
git log --oneline -20 -- ./app/\(tools\)/mesh-gradients/page.tsx 2>/dev/null | head -5

Repository: ui-layouts/ui-tools

Length of output: 121


🏁 Script executed:

# Search for other tools using ToolPlaygroundShell to compare patterns
rg 'ToolPlaygroundShell' --type=tsx -l

Repository: ui-layouts/ui-tools

Length of output: 90


🏁 Script executed:

# Check other tool pages to see if they have similar issues
fd 'page.tsx' --path '*/tools/*' --type f | head -10

Repository: ui-layouts/ui-tools

Length of output: 278


🏁 Script executed:

# Get context on one other tool page for comparison
cat -n ./app/\(tools\)/*/page.tsx 2>/dev/null | head -80

Repository: ui-layouts/ui-tools

Length of output: 3528


🏁 Script executed:

# Check how BackgroundSnippetsGenerator is structured for comparison
wc -l ./components/view/background-snippets/index.tsx

Repository: ui-layouts/ui-tools

Length of output: 114


🏁 Script executed:

# Peek at BackgroundSnippetsGenerator to see if it has similar viewport-height issues
rg 'h-\[.*vh\]' ./components/view/background-snippets/ | head -20

Repository: ui-layouts/ui-tools

Length of output: 461


🏁 Script executed:

# Check other tool generators for comparison
fd 'index.tsx' --path '*/view/*' | head -15

Repository: ui-layouts/ui-tools

Length of output: 278


🏁 Script executed:

# Check structure of BackgroundSnippetsGenerator
cat -n ./components/view/background-snippets/index.tsx | head -100

Repository: ui-layouts/ui-tools

Length of output: 4513


🏁 Script executed:

# Check what other view components exist
fd 'index.tsx' ./components/view

Repository: ui-layouts/ui-tools

Length of output: 335


🏁 Script executed:

# Check if BackgroundSnippetsGenerator also uses grid layout like ShaderGradientGenerator
cat -n ./components/view/background-snippets/index.tsx | sed -n '150,250p'

Repository: ui-layouts/ui-tools

Length of output: 3677


🏁 Script executed:

# Look for the actual grid structure in BackgroundSnippetsGenerator
rg -n 'lg:grid|lg:col-span|grid-cols' ./components/view/background-snippets/index.tsx

Repository: ui-layouts/ui-tools

Length of output: 680


Layout breakage: ShaderGradientGenerator viewport-height sizing conflicts with shell's overflow-constrained container.

The generator renders a full grid layout with fixed h-[95vh] dimensions (control panel at line 351, preview area at line 371 in components/view/mesh-gradient/index.tsx). When wrapped by ToolPlaygroundShell, these elements are placed inside a h-full overflow-hidden container (line 132 of tool-playground-shell.tsx), causing the viewport-height content to be clipped. Additionally, the generator's own control panel and example-gradient selector will appear alongside the shell's preset sidebar, creating duplicate control areas.

The generator needs either a refactored layout that respects the shell's constraints, or should skip the shell wrapper until the layout is redesigned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(tools)/mesh-gradients/page.tsx around lines 56 - 65, The
ShaderGradientGenerator's fixed viewport sizing (h-[95vh]) and its internal
control/sidebar conflict with ToolPlaygroundShell's h-full overflow-hidden
container and duplicate the shell's presets; update the integration by adding a
prop to ToolPlaygroundShell (e.g., disableInnerControls or embedded=true) and
modify ShaderGradientGenerator to accept an isEmbedded prop so it switches to
container-relative sizing (replace h-[95vh] with h-full or flex-grow styles) and
hides or collapses its own preset/sidebar and top-level control panel when
isEmbedded is true; alternatively remove the ToolPlaygroundShell wrapper here
until the component is refactored, but the preferred fix is to pass a prop from
the page and conditionally adjust ShaderGradientGenerator's layout and control
rendering.

);
}

export default page;
export default page;
12 changes: 10 additions & 2 deletions app/(tools)/shadows/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ShadowGenerator from "@/components/view/shadow";
import { ToolPlaygroundShell } from "@/components/common/tool-playground-shell";
import { siteConfig } from "@/lib/utils";
import type { Metadata } from "next";
import React from "react";
Expand Down Expand Up @@ -53,9 +54,16 @@ export const metadata: Metadata = {

function page() {
return (
<>
<ToolPlaygroundShell
title="Shadows Lab"
description="Craft layered box-shadows with instant CSS output."
examples={["Soft card", "Neumorph", "Floating panel", "Inset depth"]}
docs="Use Playground for live editing, Examples to load quick presets, and Export to copy production-ready output."
exportLabel="CSS"
exportCode={`/* export output from active tool */`}
>
<ShadowGenerator />
</>
</ToolPlaygroundShell>
);
}

Expand Down
13 changes: 7 additions & 6 deletions app/(tools)/svg-line-draw/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import SVGLineDrawGenerator from "@/components/view/svg-line-draw";
import { siteConfig } from "@/lib/utils";
import type { Metadata } from "next";
import React, { Suspense } from "react";

export const metadata: Metadata = {
title: "SVG Line Draw – Sketch & Animate Hand-Drawn Lines for the Web",
description:
Expand Down Expand Up @@ -38,7 +39,7 @@ export const metadata: Metadata = {
siteName: siteConfig.name,
images: [
{
url: siteConfig.lineDrawOgImage, // Replace with relevant OG image
url: siteConfig.lineDrawOgImage,
width: 1200,
height: 630,
alt: `SVG Line Draw by ${siteConfig.name}`,
Expand All @@ -54,6 +55,7 @@ export const metadata: Metadata = {
creator: "@naymur_dev",
},
};

const PageLoading = () => {
return (
<>
Expand All @@ -69,13 +71,12 @@ const PageLoading = () => {
</>
);
};

function page() {
return (
<>
<Suspense fallback={<PageLoading />}>
<SVGLineDrawGenerator />
</Suspense>
</>
<Suspense fallback={<PageLoading />}>
<SVGLineDrawGenerator />
</Suspense>
);
}

Expand Down
164 changes: 164 additions & 0 deletions components/common/tool-playground-shell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
"use client";

import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";
import { Copy, Download, EllipsisVertical, RotateCcw } from "lucide-react";
import { useState } from "react";
import toast from "react-hot-toast";

interface ToolPlaygroundShellProps {
title: string;
description: string;
examples: string[];
docs: string;
exportLabel: string;
exportCode: string;
children: React.ReactNode;
}

export function ToolPlaygroundShell({
title,
description,
examples,
docs,
exportLabel,
exportCode,
children,
}: ToolPlaygroundShellProps) {
const [topTab, setTopTab] = useState("playground");
const [drawerOpen, setDrawerOpen] = useState(false);
const [filter, setFilter] = useState("");

const copyCode = async () => {
await navigator.clipboard.writeText(exportCode);
toast.success("Copied");
};
Comment on lines +40 to +43
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unhandled clipboard API rejection.

navigator.clipboard.writeText rejects in insecure contexts (plain HTTP, some iframes) and when the user denies permission. The unhandled rejection will surface as a console error with no user feedback.

🛡️ Proposed fix
 const copyCode = async () => {
-	await navigator.clipboard.writeText(exportCode);
-	toast.success("Copied");
+	try {
+		await navigator.clipboard.writeText(exportCode);
+		toast.success("Copied");
+	} catch {
+		toast.error("Failed to copy");
+	}
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const copyCode = async () => {
await navigator.clipboard.writeText(exportCode);
toast.success("Copied");
};
const copyCode = async () => {
try {
await navigator.clipboard.writeText(exportCode);
toast.success("Copied");
} catch {
toast.error("Failed to copy");
}
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/common/tool-playground-shell.tsx` around lines 40 - 43, The
copyCode function calls navigator.clipboard.writeText(exportCode) without
handling rejections; wrap the async call in try/catch inside copyCode, await
navigator.clipboard.writeText(exportCode) in the try, call
toast.success("Copied") on success, and in the catch call toast.error with a
friendly message and optionally fall back to a legacy copy approach (e.g.,
creating a temporary textarea and document.execCommand('copy')) to handle
insecure contexts or permission denials; reference the copyCode function,
navigator.clipboard.writeText, exportCode, and toast.success/toast.error when
applying the change.


return (
<div className="flex h-full min-h-0 flex-col overflow-hidden rounded-2xl border bg-card-bg">
<header className="flex h-16 shrink-0 items-center justify-between gap-3 border-b px-4">
<div className="min-w-0">
<h1 className="truncate font-semibold text-lg">{title}</h1>
<p className="truncate text-muted-foreground text-xs">{description}</p>
</div>

<Tabs value={topTab} onValueChange={setTopTab} className="hidden md:block">
<TabsList>
<TabsTrigger value="playground">Playground</TabsTrigger>
<TabsTrigger value="examples">Examples</TabsTrigger>
<TabsTrigger value="docs">Docs</TabsTrigger>
</TabsList>
</Tabs>
Comment on lines +53 to +59
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Mobile users cannot switch between Playground / Examples / Docs tabs.

The <Tabs> component is hidden md:block, so it's invisible on small screens. The mobile dropdown menu (lines 73-86) only exposes Export/Copy/Reset — tab switching is missing entirely, leaving mobile users locked on the default "playground" tab.

🛡️ Proposed fix: add tab options to the mobile dropdown
 <DropdownMenuContent align="end">
+	<DropdownMenuItem onClick={() => setTopTab("playground")}>Playground</DropdownMenuItem>
+	<DropdownMenuItem onClick={() => setTopTab("examples")}>Examples</DropdownMenuItem>
+	<DropdownMenuItem onClick={() => setTopTab("docs")}>Docs</DropdownMenuItem>
+	<DropdownMenuSeparator />
 	<DropdownMenuItem onClick={() => setDrawerOpen(true)}>
 		Export code
 	</DropdownMenuItem>

Also applies to: 73-86

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/common/tool-playground-shell.tsx` around lines 53 - 59, The mobile
UI hides the Tabs component (Tabs/TabsList/TabsTrigger) so mobile users can't
switch tabs; update the mobile dropdown logic (the component that currently
offers Export/Copy/Reset) to include the three tab options "Playground",
"Examples", and "Docs", render the active tab based on the topTab state, and
call setTopTab with the appropriate value when a mobile option is selected so
mobile users can switch tabs the same way desktop does.


<div className="hidden items-center gap-2 lg:flex">
<Button size="sm" onClick={() => setDrawerOpen(true)}>
Export code
</Button>
<Button variant="outline" size="sm" onClick={copyCode}>
<Copy className="mr-1 h-4 w-4" /> Copy
</Button>
<Button variant="outline" size="sm" onClick={() => location.reload()}>
<RotateCcw className="mr-1 h-4 w-4" /> Reset
</Button>
</div>

<DropdownMenu>
<DropdownMenuTrigger asChild className="lg:hidden">
<Button variant="outline" size="icon">
<EllipsisVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setDrawerOpen(true)}>
Export code
</DropdownMenuItem>
<DropdownMenuItem onClick={copyCode}>Copy</DropdownMenuItem>
<DropdownMenuItem onClick={() => location.reload()}>Reset</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</header>

<div className="grid min-h-0 flex-1 grid-cols-1 lg:grid-cols-[380px_minmax(0,1fr)]">
<aside className="min-h-0 border-r">
<div className="sticky top-0 z-10 border-b bg-card-bg p-3">
<Input
placeholder="Search presets"
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
</div>
<div className="h-[calc(100%-60px)] overflow-y-auto p-3">
{topTab !== "docs" && (
<section className="mb-4 space-y-2">
<h2 className="font-medium text-sm">Presets / Examples</h2>
{examples
.filter((item) =>
item.toLowerCase().includes(filter.toLowerCase()),
)
.map((item) => (
<button
type="button"
key={item}
className="mb-2 block w-full rounded-md border px-3 py-2 text-left text-sm hover:bg-accent"
>
{item}
</button>
Comment on lines +107 to +113
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preset buttons are non-functional — no click handler.

Every preset item renders as a <button> but has no onClick. Users will click these expecting to load a preset and nothing will happen. If this is intentional scaffolding, consider either disabling the buttons or adding a visual indicator (e.g., "coming soon" badge) so users don't perceive the UI as broken.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/common/tool-playground-shell.tsx` around lines 107 - 113, The
preset buttons rendered in ToolPlaygroundShell (the <button> created for each
{item}) have no onClick handler so they do nothing; add an onClick prop to the
button that calls a handler (e.g., handleSelectPreset or onSelectPreset prop)
which loads/applies the preset (or, if still unimplemented, disable the button
via disabled and add a "coming soon" badge or aria-label) and ensure the handler
receives the unique preset identifier (item) so the preset state/update logic in
ToolPlaygroundShell can apply it.

))}
</section>
)}
<section>
<h2 className="mb-2 font-medium text-sm">
{topTab === "docs" ? "Docs" : "Controls"}
</h2>
<div className="rounded-md border bg-background p-3 text-muted-foreground text-sm">
{topTab === "docs"
? docs
: "All existing controls are preserved below in the tool panel. Use this sidebar as the single scroll region."}
</div>
</section>
</div>
</aside>
Comment on lines +89 to +128
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Sidebar layout may be unusable on mobile viewports.

On < lg screens the grid is grid-cols-1, so the sidebar and content area stack vertically inside a flex container with min-h-0 and overflow-hidden. The sidebar has no max-height constraint in this mode, potentially pushing the main content entirely off-screen (or both regions collapsing to zero). Consider hiding the sidebar on small screens or making it collapsible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/common/tool-playground-shell.tsx` around lines 89 - 128, The
sidebar can push main content off-screen on small viewports because the grid
becomes single-column; update the layout so the sidebar (the <aside> element in
tool-playground-shell.tsx) is hidden or collapsible below lg: either add a
responsive visibility class (e.g., hide on <lg and show on lg) to the aside or
implement a simple toggle state (e.g., sidebarOpen with a button) so the aside
is not rendered/occupying space on small screens; ensure the main content region
(the sibling grid column) expands to full width when the sidebar is hidden and
keep the existing search/controls accessible via the toggle or a separate
mobile-only control.


<section className="relative min-h-0 overflow-hidden bg-background/30">
<div className="h-full overflow-hidden p-2">
<div className="h-full overflow-hidden rounded-xl border bg-background">{children}</div>
Comment on lines +130 to +132
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow tool content to scroll inside preview pane

The new shell wraps {children} in nested overflow-hidden containers, so any tool UI taller than the viewport is clipped with no remaining scroll path. This breaks existing tool pages that render full-page editors (for example, components/view/svg-line-draw/index.tsx uses h-screen/h-[100vh] sections) and rely on vertical movement to reach controls, making parts of the editor unreachable after this change.

Useful? React with 👍 / 👎.

</div>
</section>
</div>

<div
className={cn(
"pointer-events-none fixed top-0 right-0 z-[90] h-screen w-full max-w-xl translate-x-full border-l bg-background/95 opacity-0 backdrop-blur transition-all",
drawerOpen && "pointer-events-auto translate-x-0 opacity-100",
)}
>
<div className="flex h-16 items-center justify-between border-b px-4">
<h3 className="font-semibold">Export</h3>
<Button variant="outline" size="sm" onClick={() => setDrawerOpen(false)}>
Close
</Button>
</div>
<div className="h-[calc(100vh-64px)] overflow-y-auto p-4">
<p className="mb-2 text-muted-foreground text-xs">{exportLabel}</p>
<pre className="overflow-x-auto rounded-md border bg-card p-3 text-xs">{exportCode}</pre>
<div className="mt-3 flex gap-2">
<Button size="sm" onClick={copyCode}>
<Copy className="mr-1 h-4 w-4" /> Copy
</Button>
<Button size="sm" variant="outline">
<Download className="mr-1 h-4 w-4" /> Download
</Button>
Comment on lines +156 to +158
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Download button has no onClick handler.

The Download button renders but does nothing. Same concern as the preset buttons — users will click it expecting a file download.

I can help implement a Blob-based download if you'd like — want me to open an issue or generate the handler?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/common/tool-playground-shell.tsx` around lines 156 - 158, Add an
onClick to the Download Button that calls a new handler (e.g., handleDownload)
and implement that handler inside the same component
(components/common/tool-playground-shell.tsx). The handler should read the
current playground content (the component state or prop that holds the
output/editor value), create a Blob from that string, generate an object URL via
URL.createObjectURL, create and click a temporary anchor with download
attribute, and then revoke the object URL to free resources; wire this handler
to the <Button ...> containing the <Download /> icon so the button triggers a
file download.

</div>
</div>
</div>
Comment on lines +137 to +161
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Export drawer lacks backdrop, keyboard dismiss, and focus trap.

The slide-in drawer has no overlay behind it (clicking outside does nothing), no Escape key listener to close, and no focus trap. This degrades both usability (easy to get "stuck") and accessibility (keyboard/screen-reader users can tab behind the drawer).

🛡️ Minimal fix: add overlay + Escape handler
+		{/* Overlay */}
+		{drawerOpen && (
+			<div
+				className="fixed inset-0 z-[89] bg-black/40"
+				onClick={() => setDrawerOpen(false)}
+				onKeyDown={(e) => e.key === "Escape" && setDrawerOpen(false)}
+				role="presentation"
+			/>
+		)}
 		<div
 			className={cn(
 				"pointer-events-none fixed top-0 right-0 z-[90] h-screen w-full max-w-xl translate-x-full border-l bg-background/95 opacity-0 backdrop-blur transition-all",
 				drawerOpen && "pointer-events-auto translate-x-0 opacity-100",
 			)}
 		>

For a proper focus trap, consider using a library like @radix-ui/react-dialog or react-focus-lock.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/common/tool-playground-shell.tsx` around lines 137 - 161, The
export drawer currently toggles via drawerOpen and setDrawerOpen but lacks an
overlay, Escape-key handler, and focus trapping; update the component
(tool-playground-shell.tsx) to render a backdrop element when drawerOpen is true
that calls setDrawerOpen(false) on click, add a useEffect that listens for
'keydown' and closes the drawer when event.key === 'Escape' using
setDrawerOpen(false), and implement a focus trap around the drawer (preferably
by wrapping the drawer content in a FocusLock from react-focus-lock or a similar
utility) so keyboard focus cannot tab out while open; ensure copyCode,
exportCode and exportLabel behavior remains unchanged.

</div>
);
}
Loading