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/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
import "./.next/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
30 changes: 22 additions & 8 deletions apps/web/src/components/app-shell/code-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,28 @@ export function CodePanel() {
<span className="text-xs text-slate-500">React / Tailwind / JSON</span>
</div>

<pre className="h-[calc(100%-44px)] overflow-auto p-4 text-xs leading-6 text-slate-100">
<code>{`export const rainbowCode = {
product: "global-design-to-code-editor",
studios: ["brand", "theme", "component", "canvas", "code"],
canvas: "v1-ready",
next: "brand-studio"
};`}</code>
</pre>
<div className="grid h-[calc(100%-44px)] place-items-center p-4">
<div className="max-w-md text-center">
<div className="mx-auto grid size-12 place-items-center rounded-2xl border border-white/10 bg-white/5 text-lg">
{"</>"}
</div>

<h3 className="mt-4 text-sm font-bold text-white">
Code output is generated inside Canvas Studio
</h3>

<p className="mt-2 text-xs leading-5 text-slate-400">
Use the Canvas Studio generated-code panel for live TSX export, JSON
export, import, copy, and future RBC CLI install flows.
</p>

<pre className="mt-4 rounded-2xl border border-white/10 bg-black/30 p-4 text-left text-xs leading-6 text-slate-300">
<code>{`rbc export canvas
rbc add component
rbc install theme`}</code>
</pre>
</div>
</div>
</section>
);
}
160 changes: 84 additions & 76 deletions apps/web/src/features/canvas-studio/components/canvas-layers-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,100 +1,108 @@
"use client";

import { RbcBadge } from "@/components/ui/rbc-badge";
import { RbcButton } from "@/components/ui/rbc-button";
import { useCanvasStore } from "@/features/canvas-studio/store/canvas-store";
import { getCanvasNodeLabel } from "@/features/canvas-studio/utils/canvas-node-label";

function LayersEmptyState() {
const addRectangle = useCanvasStore((state) => state.addRectangle);
const addText = useCanvasStore((state) => state.addText);
const applyTemplate = useCanvasStore((state) => state.applyTemplate);

return (
<div className="rounded-3xl border border-dashed border-slate-300 bg-slate-50 p-4 text-center dark:border-slate-800 dark:bg-slate-900/60">
<div className="mx-auto grid size-10 place-items-center rounded-2xl bg-white text-lg shadow-sm dark:bg-slate-950">
</div>

<h3 className="mt-3 text-sm font-black text-slate-950 dark:text-white">
No layers yet
</h3>

<p className="mt-2 text-xs leading-5 text-slate-500">
Add a node or apply a template to start building your visual hierarchy.
</p>

<div className="mt-4 flex flex-wrap justify-center gap-2">
<RbcButton variant="primary" onClick={() => applyTemplate("hero")}>
Template
</RbcButton>
<RbcButton variant="secondary" onClick={addRectangle}>
Rectangle
</RbcButton>
<RbcButton variant="ghost" onClick={addText}>
Text
</RbcButton>
</div>
</div>
);
}

export function CanvasLayersPanel() {
const nodes = useCanvasStore((state) => state.nodes);
const selectedNodeIds = useCanvasStore((state) => state.selectedNodeIds);
const selectNode = useCanvasStore((state) => state.selectNode);
const deleteSelectedNode = useCanvasStore((state) => state.deleteSelectedNode);
const bringSelectedToFront = useCanvasStore(
(state) => state.bringSelectedToFront,
);
const sendSelectedToBack = useCanvasStore((state) => state.sendSelectedToBack);

const hasSelection = selectedNodeIds.length > 0;

return (
<aside
<section
aria-label="Canvas layers"
className="rounded-2xl border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-950"
className="overflow-hidden rounded-[28px] border border-white/70 bg-white/78 shadow-[0_18px_70px_rgba(15,23,42,0.08)] backdrop-blur-2xl dark:border-white/10 dark:bg-slate-950/76"
>
<div className="flex items-start justify-between gap-3">
<div className="flex items-center justify-between gap-3 border-b border-slate-200/70 px-4 py-4 dark:border-slate-800">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">
<p className="text-xs font-semibold uppercase tracking-[0.22em] text-indigo-600 dark:text-indigo-300">
Layers
</p>
<h3 className="mt-1 text-sm font-semibold text-slate-950 dark:text-white">
Canvas Nodes
</h3>
<h2 className="mt-1 text-sm font-black text-slate-950 dark:text-white">
Canvas hierarchy
</h2>
</div>

<button
type="button"
onClick={deleteSelectedNode}
disabled={!hasSelection}
className="rounded-lg px-2 py-1 text-xs font-medium text-slate-600 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 dark:text-slate-300 dark:hover:bg-slate-900"
>
Delete
</button>
<RbcBadge variant={nodes.length > 0 ? "success" : "neutral"}>
{nodes.length}
</RbcBadge>
</div>

<div className="mt-3 grid grid-cols-2 gap-2">
<button
type="button"
onClick={bringSelectedToFront}
disabled={!hasSelection}
className="rounded-lg border border-slate-200 px-2 py-1.5 text-xs font-medium text-slate-600 hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 dark:border-slate-800 dark:text-slate-300 dark:hover:bg-slate-900"
>
To Front
</button>

<button
type="button"
onClick={sendSelectedToBack}
disabled={!hasSelection}
className="rounded-lg border border-slate-200 px-2 py-1.5 text-xs font-medium text-slate-600 hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 dark:border-slate-800 dark:text-slate-300 dark:hover:bg-slate-900"
>
To Back
</button>
</div>
<div className="p-4">
{nodes.length === 0 ? (
<LayersEmptyState />
) : (
<div className="space-y-2">
{[...nodes].reverse().map((node, reversedIndex) => {
const index = nodes.length - reversedIndex - 1;
const isSelected = selectedNodeIds.includes(node.id);

{nodes.length === 0 ? (
<div className="mt-4 rounded-xl border border-dashed border-slate-200 p-4 text-sm text-slate-500 dark:border-slate-800">
No layers yet. Add a rectangle or text node.
</div>
) : (
<div className="mt-4 space-y-2">
{[...nodes].reverse().map((node, reverseIndex) => {
const originalIndex = nodes.length - reverseIndex - 1;
const isSelected = selectedNodeIds.includes(node.id);
return (
<button
key={node.id}
type="button"
onClick={() => selectNode(node.id)}
className={`flex w-full items-center justify-between gap-3 rounded-2xl border px-3 py-3 text-left transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 ${
isSelected
? "border-indigo-300 bg-indigo-50 shadow-sm dark:border-indigo-800 dark:bg-indigo-950/50"
: "border-slate-200 bg-slate-50 hover:bg-white dark:border-slate-800 dark:bg-slate-900 dark:hover:bg-slate-900/70"
}`}
>
<span className="min-w-0">
<span className="block truncate text-sm font-bold text-slate-950 dark:text-white">
{getCanvasNodeLabel(node, index)}
</span>
<span className="mt-0.5 block text-xs text-slate-500">
{node.type} · {Math.round(node.width)}×
{Math.round(node.height)}
</span>
</span>

return (
<button
key={node.id}
type="button"
onClick={(event) =>
selectNode(node.id, event.metaKey || event.ctrlKey)
}
className={`w-full rounded-xl border p-3 text-left transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 ${
isSelected
? "border-slate-900 bg-slate-100 dark:border-white dark:bg-slate-900"
: "border-slate-200 hover:bg-slate-50 dark:border-slate-800 dark:hover:bg-slate-900"
}`}
>
<span className="block text-sm font-semibold text-slate-900 dark:text-white">
{getCanvasNodeLabel(node, originalIndex)}
</span>
<span className="mt-1 block text-xs text-slate-500">
{node.type} · {Math.round(node.width)}×
{Math.round(node.height)}
</span>
</button>
);
})}
</div>
)}
</aside>
<RbcBadge variant={isSelected ? "info" : "neutral"}>
{isSelected ? "Selected" : `#${index + 1}`}
</RbcBadge>
</button>
);
})}
</div>
)}
</div>
</section>
);
}
Loading
Loading