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
5 changes: 5 additions & 0 deletions apps/web/src/app/(studio)/studio/brand/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { BrandStudioWorkspace } from "@/features/brand-studio/components/brand-studio-workspace";

export default function BrandStudioPage() {
return <BrandStudioWorkspace />;
}
5 changes: 5 additions & 0 deletions apps/web/src/app/(studio)/studio/canvas/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { CanvasStudioPanel } from "@/features/canvas-studio/components/canvas-studio-panel";

export default function CanvasStudioPage() {
return <CanvasStudioPanel />;
}
5 changes: 5 additions & 0 deletions apps/web/src/app/(studio)/studio/code/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { CodeStudioWorkspace } from "@/features/code-studio/components/code-studio-workspace";

export default function CodeStudioPage() {
return <CodeStudioWorkspace />;
}
5 changes: 5 additions & 0 deletions apps/web/src/app/(studio)/studio/components/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ComponentStudioWorkspace } from "@/features/component-studio/components/component-studio-workspace";

export default function ComponentStudioPage() {
return <ComponentStudioWorkspace />;
}
10 changes: 2 additions & 8 deletions apps/web/src/app/(studio)/studio/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { Workspace } from "@/components/app-shell/workspace";
import { StudioHome } from "@/features/dashboard/components/studio-home";

export default function StudioPage() {
return (
<div className="space-y-8">
<StudioHome />
<Workspace />
</div>
);
}
return <StudioHome />;
}
5 changes: 5 additions & 0 deletions apps/web/src/app/(studio)/studio/theme/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ThemeStudioWorkspace } from "@/features/theme-studio/components/theme-studio-workspace";

export default function ThemeStudioPage() {
return <ThemeStudioWorkspace />;
}
5 changes: 3 additions & 2 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Metadata } from "next";
import type { ReactNode } from "react";
import { ThemeProvider } from "@/features/theme-engine/runtime/theme-provider";
import "./globals.css";

export const metadata: Metadata = {
Expand All @@ -23,8 +24,8 @@ export default function RootLayout({ children }: RootLayoutProps) {
Skip to main content
</a>

{children}
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}
}
42 changes: 31 additions & 11 deletions apps/web/src/components/app-shell/command-palette.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"use client";

import { useEffect, useMemo, useState } from "react";
import { useRouter } from "next/navigation";
import { useCanvasStore } from "@/features/canvas-studio/store/canvas-store";
import { studioNavItems } from "@/lib/navigation/studio-nav";
import { useAppShellStore } from "@/stores/app-shell-store";

type Command = {
readonly id: string;
Expand All @@ -12,7 +15,17 @@ type Command = {
};

export function CommandPalette() {
const [isOpen, setIsOpen] = useState(false);
const router = useRouter();
const isOpen = useAppShellStore((state) => state.isCommandPaletteOpen);
const openCommandPalette = useAppShellStore(
(state) => state.openCommandPalette,
);
const closeCommandPalette = useAppShellStore(
(state) => state.closeCommandPalette,
);
const toggleCommandPalette = useAppShellStore(
(state) => state.toggleCommandPalette,
);
const [query, setQuery] = useState("");

const addRectangle = useCanvasStore((state) => state.addRectangle);
Expand All @@ -37,16 +50,22 @@ export function CommandPalette() {

const commands = useMemo<readonly Command[]>(
() => [
...studioNavItems.map((item) => ({
id: `open-${item.studio}`,
title: `Open ${item.label}`,
description: item.description,
action: () => router.push(item.href),
})),
{
id: "template-hero",
title: "Apply Hero Template",
description: "Replace canvas with a landing hero layout.",
title: "Apply Feature Intro Layout",
description: "Replace canvas with a reusable intro section layout.",
action: () => applyTemplate("hero"),
},
{
id: "template-pricing",
title: "Apply Pricing Card Template",
description: "Replace canvas with a SaaS pricing card layout.",
title: "Apply Detail Card Layout",
description: "Replace canvas with a reusable detail card layout.",
action: () => applyTemplate("pricing-card"),
},
{
Expand Down Expand Up @@ -158,6 +177,7 @@ export function CommandPalette() {
ungroupSelectedNodes,
zoomIn,
zoomOut,
router,
],
);

Expand All @@ -176,7 +196,7 @@ export function CommandPalette() {
}

event.preventDefault();
setIsOpen((current) => !current);
toggleCommandPalette();
}

window.addEventListener("keydown", handleKeyDown);
Expand All @@ -194,14 +214,14 @@ export function CommandPalette() {

function runCommand(command: Command): void {
command.action();
setIsOpen(false);
closeCommandPalette();
}

return (
<>
<button
type="button"
onClick={() => setIsOpen(true)}
onClick={openCommandPalette}
className="fixed bottom-5 left-1/2 z-40 hidden -translate-x-1/2 items-center gap-3 rounded-2xl border border-slate-200 bg-white/92 px-4 py-2 text-xs font-bold text-slate-700 shadow-2xl backdrop-blur-2xl transition hover:-translate-y-0.5 hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-slate-800 dark:bg-slate-950/92 dark:text-slate-200 lg:flex"
>
<span className="text-slate-500">Command Palette</span>
Expand All @@ -218,7 +238,7 @@ export function CommandPalette() {
className="fixed inset-0 z-[100] grid place-items-start bg-slate-950/48 px-4 py-20 backdrop-blur-sm"
onMouseDown={(event) => {
if (event.currentTarget === event.target) {
setIsOpen(false);
closeCommandPalette();
}
}}
>
Expand All @@ -234,7 +254,7 @@ export function CommandPalette() {
onChange={(event) => setQuery(event.currentTarget.value)}
onKeyDown={(event) => {
if (event.key === "Escape") {
setIsOpen(false);
closeCommandPalette();
}

if (event.key === "Enter" && filteredCommands[0] !== undefined) {
Expand Down Expand Up @@ -287,4 +307,4 @@ export function CommandPalette() {
) : null}
</>
);
}
}
97 changes: 88 additions & 9 deletions apps/web/src/components/app-shell/properties-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,83 @@
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import { RbcBadge } from "@/components/ui/rbc-badge";
import { ComponentStudioPanel } from "@/features/component-studio/components/component-studio-panel";
import { ThemeStudioPanel } from "@/features/theme-studio/components/theme-studio-panel";
import { BrandStudioPanel } from "@/features/brand-studio/components/brand-studio-panel";
import { ThemeExportPanel } from "@/features/theme-studio/components/theme-export-panel";
import { useCanvasStore } from "@/features/canvas-studio/store/canvas-store";
import { studioNavItems } from "@/lib/navigation/studio-nav";

export function PropertiesPanel() {
const pathname = usePathname();
const nodeCount = useCanvasStore((state) => state.nodes.length);

if (pathname === "/studio/brand") {
return <BrandStudioPanel />;
}

if (pathname === "/studio/theme") {
return <ThemeStudioPanel />;
}

if (pathname === "/studio/components") {
return <ComponentStudioPanel />;
}

if (pathname === "/studio/code") {
return (
<div className="space-y-3">
<section className="rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-950">
<p className="text-[10px] font-black uppercase tracking-[0.22em] text-indigo-600 dark:text-indigo-300">
Code Studio
</p>
<h2 className="mt-2 text-sm font-black text-slate-950 dark:text-white">
Export surfaces
</h2>
<p className="mt-2 text-xs leading-5 text-slate-500">
Review current artifacts in the main workspace and export theme
files from here.
</p>
</section>
<ThemeExportPanel />
</div>
);
}

if (pathname === "/studio/canvas") {
return (
<div className="space-y-3">
<section className="rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-950">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-[10px] font-black uppercase tracking-[0.22em] text-indigo-600 dark:text-indigo-300">
Canvas Studio
</p>
<h2 className="mt-2 text-sm font-black text-slate-950 dark:text-white">
Keyboard and export
</h2>
</div>

<RbcBadge variant={nodeCount > 0 ? "success" : "neutral"}>
{nodeCount} nodes
</RbcBadge>
</div>

<div className="mt-4 space-y-2 text-xs font-semibold text-slate-600 dark:text-slate-300">
<div className="rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 dark:border-slate-800 dark:bg-slate-900/70">
Use `Cmd/Ctrl + K` for commands and templates.
</div>
<div className="rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 dark:border-slate-800 dark:bg-slate-900/70">
Use canvas export actions for TSX and JSON output.
</div>
</div>
</section>
</div>
);
}

return (
<div className="space-y-3">
<section className="rounded-2xl border border-slate-200 bg-white p-3 shadow-sm dark:border-slate-800 dark:bg-slate-950">
Expand All @@ -14,19 +87,25 @@ export function PropertiesPanel() {
Inspector
</p>
<h2 className="mt-1 text-sm font-black text-slate-950 dark:text-white">
Design Controls
Studio Overview
</h2>
</div>

<span className="rounded-full bg-indigo-50 px-2 py-1 text-[10px] font-black text-indigo-700 ring-1 ring-indigo-200 dark:bg-indigo-950 dark:text-indigo-300 dark:ring-indigo-900">
Live
</span>
<RbcBadge variant="info">Open</RbcBadge>
</div>
</section>

<BrandStudioPanel />
<ThemeStudioPanel />
<ComponentStudioPanel />
<div className="mt-4 space-y-2">
{studioNavItems.slice(1).map((item) => (
<Link
key={item.href}
href={item.href}
className="block rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-slate-800 dark:bg-slate-900/70 dark:text-slate-200"
>
{item.label}
</Link>
))}
</div>
</section>
</div>
);
}
}
Loading
Loading