From 3204b00bf8a68cbf4660214b5cc12e3aa1691635 Mon Sep 17 00:00:00 2001 From: SunilKumarKV Date: Thu, 11 Jun 2026 14:18:27 +0530 Subject: [PATCH] fix: restore studio routing and route-aware navigation --- .../src/app/(studio)/studio/brand/page.tsx | 5 + .../src/app/(studio)/studio/canvas/page.tsx | 5 + .../web/src/app/(studio)/studio/code/page.tsx | 5 + .../app/(studio)/studio/components/page.tsx | 5 + apps/web/src/app/(studio)/studio/page.tsx | 10 +- .../src/app/(studio)/studio/theme/page.tsx | 5 + apps/web/src/app/layout.tsx | 5 +- .../components/app-shell/command-palette.tsx | 42 +++-- .../components/app-shell/properties-panel.tsx | 97 ++++++++++- apps/web/src/components/app-shell/sidebar.tsx | 155 +++++++++++------- apps/web/src/components/app-shell/topbar.tsx | 72 ++++---- .../components/brand-studio-workspace.tsx | 123 ++++++++++++++ .../components/code-studio-workspace.tsx | 100 +++++++++++ .../components/component-studio-workspace.tsx | 101 ++++++++++++ .../theme-studio/components/theme-preview.tsx | 105 ++++++++++++ .../components/theme-studio-workspace.tsx | 46 ++++++ apps/web/src/lib/navigation/studio-nav.ts | 30 +++- apps/web/src/stores/app-shell-store.ts | 30 ++-- 18 files changed, 794 insertions(+), 147 deletions(-) create mode 100644 apps/web/src/app/(studio)/studio/brand/page.tsx create mode 100644 apps/web/src/app/(studio)/studio/canvas/page.tsx create mode 100644 apps/web/src/app/(studio)/studio/code/page.tsx create mode 100644 apps/web/src/app/(studio)/studio/components/page.tsx create mode 100644 apps/web/src/app/(studio)/studio/theme/page.tsx create mode 100644 apps/web/src/features/brand-studio/components/brand-studio-workspace.tsx create mode 100644 apps/web/src/features/code-studio/components/code-studio-workspace.tsx create mode 100644 apps/web/src/features/component-studio/components/component-studio-workspace.tsx create mode 100644 apps/web/src/features/theme-studio/components/theme-preview.tsx create mode 100644 apps/web/src/features/theme-studio/components/theme-studio-workspace.tsx diff --git a/apps/web/src/app/(studio)/studio/brand/page.tsx b/apps/web/src/app/(studio)/studio/brand/page.tsx new file mode 100644 index 0000000..4f2f27e --- /dev/null +++ b/apps/web/src/app/(studio)/studio/brand/page.tsx @@ -0,0 +1,5 @@ +import { BrandStudioWorkspace } from "@/features/brand-studio/components/brand-studio-workspace"; + +export default function BrandStudioPage() { + return ; +} diff --git a/apps/web/src/app/(studio)/studio/canvas/page.tsx b/apps/web/src/app/(studio)/studio/canvas/page.tsx new file mode 100644 index 0000000..2403251 --- /dev/null +++ b/apps/web/src/app/(studio)/studio/canvas/page.tsx @@ -0,0 +1,5 @@ +import { CanvasStudioPanel } from "@/features/canvas-studio/components/canvas-studio-panel"; + +export default function CanvasStudioPage() { + return ; +} diff --git a/apps/web/src/app/(studio)/studio/code/page.tsx b/apps/web/src/app/(studio)/studio/code/page.tsx new file mode 100644 index 0000000..fa1632a --- /dev/null +++ b/apps/web/src/app/(studio)/studio/code/page.tsx @@ -0,0 +1,5 @@ +import { CodeStudioWorkspace } from "@/features/code-studio/components/code-studio-workspace"; + +export default function CodeStudioPage() { + return ; +} diff --git a/apps/web/src/app/(studio)/studio/components/page.tsx b/apps/web/src/app/(studio)/studio/components/page.tsx new file mode 100644 index 0000000..7280fc6 --- /dev/null +++ b/apps/web/src/app/(studio)/studio/components/page.tsx @@ -0,0 +1,5 @@ +import { ComponentStudioWorkspace } from "@/features/component-studio/components/component-studio-workspace"; + +export default function ComponentStudioPage() { + return ; +} diff --git a/apps/web/src/app/(studio)/studio/page.tsx b/apps/web/src/app/(studio)/studio/page.tsx index 5992993..0f1148c 100644 --- a/apps/web/src/app/(studio)/studio/page.tsx +++ b/apps/web/src/app/(studio)/studio/page.tsx @@ -1,11 +1,5 @@ -import { Workspace } from "@/components/app-shell/workspace"; import { StudioHome } from "@/features/dashboard/components/studio-home"; export default function StudioPage() { - return ( -
- - -
- ); -} \ No newline at end of file + return ; +} diff --git a/apps/web/src/app/(studio)/studio/theme/page.tsx b/apps/web/src/app/(studio)/studio/theme/page.tsx new file mode 100644 index 0000000..66c2469 --- /dev/null +++ b/apps/web/src/app/(studio)/studio/theme/page.tsx @@ -0,0 +1,5 @@ +import { ThemeStudioWorkspace } from "@/features/theme-studio/components/theme-studio-workspace"; + +export default function ThemeStudioPage() { + return ; +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 2a9620e..3aee570 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -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 = { @@ -23,8 +24,8 @@ export default function RootLayout({ children }: RootLayoutProps) { Skip to main content - {children} + {children} ); -} \ No newline at end of file +} diff --git a/apps/web/src/components/app-shell/command-palette.tsx b/apps/web/src/components/app-shell/command-palette.tsx index bb71525..e3ebf80 100644 --- a/apps/web/src/components/app-shell/command-palette.tsx +++ b/apps/web/src/components/app-shell/command-palette.tsx @@ -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; @@ -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); @@ -37,16 +50,22 @@ export function CommandPalette() { const commands = useMemo( () => [ + ...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"), }, { @@ -158,6 +177,7 @@ export function CommandPalette() { ungroupSelectedNodes, zoomIn, zoomOut, + router, ], ); @@ -176,7 +196,7 @@ export function CommandPalette() { } event.preventDefault(); - setIsOpen((current) => !current); + toggleCommandPalette(); } window.addEventListener("keydown", handleKeyDown); @@ -194,14 +214,14 @@ export function CommandPalette() { function runCommand(command: Command): void { command.action(); - setIsOpen(false); + closeCommandPalette(); } return ( <> - ))} + {studioNavItems.slice(1).map((item) => { + const active = pathname === item.href; + const icon = + item.studio === "brand" + ? "B" + : item.studio === "theme" + ? "T" + : item.studio === "components" + ? "C" + : item.studio === "canvas" + ? "V" + : ""; + + return ( + + {icon} + + ); + })}
-

- File -

-

- Untitled System -

-

Autosaved locally

+
+
+

+ Workspace +

+

+ {brand.name.trim().length > 0 ? brand.name : "Unnamed workspace"} +

+

{brand.slogan}

+
+ + 0 ? "success" : "neutral"}> + {nodeCount > 0 ? "In progress" : "Empty canvas"} + +
@@ -69,8 +111,7 @@ export function Sidebar() {
{studioNavItems.map((item) => { - const status = statuses[item.label] ?? "Soon"; - const active = item.label === "Canvas Studio"; + const active = pathname === item.href; return ( -
- + > +
+ {item.label} {item.description} + - - - - {status} - -
+ {active ? Open : null} +
); })} @@ -103,19 +141,20 @@ export function Sidebar() {

- Assets + Current State

- {quickAssets.map((item) => ( - + {item.label} + + {item.value} + +
))}
@@ -124,4 +163,4 @@ export function Sidebar() {
); -} \ No newline at end of file +} diff --git a/apps/web/src/components/app-shell/topbar.tsx b/apps/web/src/components/app-shell/topbar.tsx index 79a3e72..b9d4d81 100644 --- a/apps/web/src/components/app-shell/topbar.tsx +++ b/apps/web/src/components/app-shell/topbar.tsx @@ -1,11 +1,19 @@ "use client"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { studioNavItems } from "@/lib/navigation/studio-nav"; import { useAppShellStore } from "@/stores/app-shell-store"; -const toolItems = ["Move", "Frame", "Shape", "Text", "Hand", "Comment"] as const; - export function Topbar() { + const pathname = usePathname(); const toggleSidebar = useAppShellStore((state) => state.toggleSidebar); + const openCommandPalette = useAppShellStore( + (state) => state.openCommandPalette, + ); + + const activeStudio = + studioNavItems.find((item) => item.href === pathname) ?? studioNavItems[0]; return (
@@ -26,56 +34,54 @@ export function Topbar() {

- RainbowCode + {activeStudio.label}

- Untitled Design System + {activeStudio.description}

- {toolItems.map((item, index) => ( - - ))} + {studioNavItems.map((item) => { + const active = item.href === pathname; + + return ( + + {item.label} + + ); + })}
- - - - +
); -} \ No newline at end of file +} diff --git a/apps/web/src/features/brand-studio/components/brand-studio-workspace.tsx b/apps/web/src/features/brand-studio/components/brand-studio-workspace.tsx new file mode 100644 index 0000000..03571f2 --- /dev/null +++ b/apps/web/src/features/brand-studio/components/brand-studio-workspace.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { RbcBadge } from "@/components/ui/rbc-badge"; +import { RbcButton } from "@/components/ui/rbc-button"; +import { exportBrandJson } from "@/features/brand-studio/exporters/export-brand-json"; +import { useBrandStore } from "@/features/brand-studio/store/brand-store"; +import { downloadFile } from "@/features/theme-engine/exporters/download-file"; + +export function BrandStudioWorkspace() { + const brand = useBrandStore((state) => state.brand); + + function handleExport(): void { + downloadFile( + "rainbowcode-brand-kit.json", + exportBrandJson(brand), + "application/json", + ); + } + + return ( +
+
+
+
+ +
+
+
+ Brand Studio + Local workspace +
+ +

+ Shape the design system from a real brand foundation. +

+ +

+ Update name, slogan, logo mark, and palette values. The current + kit is ready to export as JSON for downstream tooling. +

+
+ + + Export Brand Kit + +
+
+
+ +
+
+
+
+ {brand.logoText || "RBC"} +
+ +
+

+ Live identity +

+

+ {brand.name || "Unnamed workspace"} +

+

+ {brand.slogan} +

+
+
+ +
+ {brand.palette.map((color) => ( +
+
+
+ + {color.name} + + + {color.value} + +
+
+ ))} +
+
+ +
+

+ Included in export +

+ +
+ {[ + "Brand name and slogan", + "Logo mark text", + "Palette token values", + "JSON payload for tooling", + ].map((item) => ( +
+ + {item} +
+ ))} +
+
+
+
+ ); +} diff --git a/apps/web/src/features/code-studio/components/code-studio-workspace.tsx b/apps/web/src/features/code-studio/components/code-studio-workspace.tsx new file mode 100644 index 0000000..355edce --- /dev/null +++ b/apps/web/src/features/code-studio/components/code-studio-workspace.tsx @@ -0,0 +1,100 @@ +"use client"; + +import { exportBrandJson } from "@/features/brand-studio/exporters/export-brand-json"; +import { useBrandStore } from "@/features/brand-studio/store/brand-store"; +import { exportCanvasComponent } from "@/features/canvas-studio/exporters/export-canvas-component"; +import { useCanvasStore } from "@/features/canvas-studio/store/canvas-store"; +import { exportComponentBundle } from "@/features/component-studio/exporters/export-component-bundle"; +import { useComponentStudioStore } from "@/features/component-studio/store/component-studio-store"; +import { exportCssTheme } from "@/features/theme-engine/exporters/export-css"; +import { useThemeStore } from "@/features/theme-engine/store/theme-store"; + +function CodeSection({ + title, + description, + code, +}: { + readonly title: string; + readonly description: string; + readonly code: string; +}) { + return ( +
+
+

{title}

+

{description}

+
+ +
+        {code}
+      
+
+ ); +} + +export function CodeStudioWorkspace() { + const brand = useBrandStore((state) => state.brand); + const theme = useThemeStore((state) => state.theme); + const nodes = useCanvasStore((state) => state.nodes); + const buttonDefinition = useComponentStudioStore((state) => state.buttonDefinition); + const cardDefinition = useComponentStudioStore((state) => state.cardDefinition); + const inputDefinition = useComponentStudioStore((state) => state.inputDefinition); + const badgeDefinition = useComponentStudioStore((state) => state.badgeDefinition); + + const bundle = exportComponentBundle({ + buttonDefinition, + cardDefinition, + inputDefinition, + badgeDefinition, + }); + + return ( +
+
+
+
+ +
+

+ Code Studio +

+

+ Review the actual artifacts generated from the current workspace. +

+
+
+
+ +
+ + + 0 + ? "React/Tailwind output from the current canvas." + : "Canvas is empty. Add nodes in Canvas Studio to generate TSX." + } + code={ + nodes.length > 0 + ? exportCanvasComponent(nodes) + : "/* No canvas nodes yet. */" + } + /> + `${file.filename}\n`).join("")} + /> +
+
+ ); +} diff --git a/apps/web/src/features/component-studio/components/component-studio-workspace.tsx b/apps/web/src/features/component-studio/components/component-studio-workspace.tsx new file mode 100644 index 0000000..b1eb213 --- /dev/null +++ b/apps/web/src/features/component-studio/components/component-studio-workspace.tsx @@ -0,0 +1,101 @@ +"use client"; + +import { RbcBadge } from "@/components/ui/rbc-badge"; +import { BadgePreview } from "@/features/component-studio/components/badge-preview"; +import { ButtonPreview } from "@/features/component-studio/components/button-preview"; +import { CardPreview } from "@/features/component-studio/components/card-preview"; +import { InputPreview } from "@/features/component-studio/components/input-preview"; +import { exportBadgeComponent } from "@/features/component-studio/exporters/export-badge-component"; +import { exportButtonComponent } from "@/features/component-studio/exporters/export-button-component"; +import { exportCardComponent } from "@/features/component-studio/exporters/export-card-component"; +import { exportInputComponent } from "@/features/component-studio/exporters/export-input-component"; +import { useComponentStudioStore } from "@/features/component-studio/store/component-studio-store"; + +function ComponentPreview() { + const selectedComponent = useComponentStudioStore( + (state) => state.selectedComponent, + ); + + if (selectedComponent === "button") { + return ; + } + + if (selectedComponent === "card") { + return ; + } + + if (selectedComponent === "input") { + return ; + } + + return ; +} + +function useSelectedComponentCode(): string { + const selectedComponent = useComponentStudioStore( + (state) => state.selectedComponent, + ); + const buttonDefinition = useComponentStudioStore((state) => state.buttonDefinition); + const cardDefinition = useComponentStudioStore((state) => state.cardDefinition); + const inputDefinition = useComponentStudioStore((state) => state.inputDefinition); + const badgeDefinition = useComponentStudioStore((state) => state.badgeDefinition); + + if (selectedComponent === "button") { + return exportButtonComponent(buttonDefinition); + } + + if (selectedComponent === "card") { + return exportCardComponent(cardDefinition); + } + + if (selectedComponent === "input") { + return exportInputComponent(inputDefinition); + } + + return exportBadgeComponent(badgeDefinition); +} + +export function ComponentStudioWorkspace() { + const selectedComponent = useComponentStudioStore( + (state) => state.selectedComponent, + ); + const selectedComponentCode = useSelectedComponentCode(); + + return ( +
+
+
+
+ +
+
+ Component Studio + Selected: {selectedComponent} +
+

+ Build token-aware UI primitives and review their generated TSX. +

+
+
+
+ +
+
+ +
+ +
+
+

+ Generated component code +

+
+ +
+            {selectedComponentCode}
+          
+
+
+
+ ); +} diff --git a/apps/web/src/features/theme-studio/components/theme-preview.tsx b/apps/web/src/features/theme-studio/components/theme-preview.tsx new file mode 100644 index 0000000..8756906 --- /dev/null +++ b/apps/web/src/features/theme-studio/components/theme-preview.tsx @@ -0,0 +1,105 @@ +"use client"; + +import { useThemeStore } from "@/features/theme-engine/store/theme-store"; + +const previewColors = ["#2563eb", "#7c3aed", "#16a34a", "#dc2626"] as const; + +export function ThemePreview() { + const updateColor = useThemeStore((state) => state.updateColor); + + return ( +
+

+ Live preview +

+ +

+ Theme tokens applied through CSS variables +

+ +

+ Editing the token panel updates this surface immediately, so exported + CSS and Tailwind values match what you see in the studio. +

+ +
+
+
+
+

+ Surface +

+

+ Rainbow workspace +

+
+ + + Secondary + +
+ +
+ + + +
+
+ +
+

+ Quick swap +

+ +
+ {previewColors.map((color) => ( +
+
+
+
+ ); +} diff --git a/apps/web/src/features/theme-studio/components/theme-studio-workspace.tsx b/apps/web/src/features/theme-studio/components/theme-studio-workspace.tsx new file mode 100644 index 0000000..49fa24a --- /dev/null +++ b/apps/web/src/features/theme-studio/components/theme-studio-workspace.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { ThemePreview } from "@/features/theme-studio/components/theme-preview"; +import { exportCssTheme } from "@/features/theme-engine/exporters/export-css"; +import { useThemeStore } from "@/features/theme-engine/store/theme-store"; + +export function ThemeStudioWorkspace() { + const theme = useThemeStore((state) => state.theme); + + return ( +
+
+
+
+ +
+

+ Theme Studio +

+

+ Tune real tokens and preview the exact runtime output. +

+

+ Colors and radii are validated through the theme engine, applied + to CSS variables at runtime, and exported for downstream use. +

+
+
+
+ + + +
+
+

+ CSS export preview +

+
+ +
+          {exportCssTheme(theme)}
+        
+
+
+ ); +} diff --git a/apps/web/src/lib/navigation/studio-nav.ts b/apps/web/src/lib/navigation/studio-nav.ts index 617e3aa..7554991 100644 --- a/apps/web/src/lib/navigation/studio-nav.ts +++ b/apps/web/src/lib/navigation/studio-nav.ts @@ -1,4 +1,7 @@ +import type { ActiveStudio } from "@/stores/app-shell-store"; + export type StudioNavItem = { + readonly studio: ActiveStudio | "overview"; readonly label: string; readonly href: string; readonly description: string; @@ -6,28 +9,39 @@ export type StudioNavItem = { export const studioNavItems: readonly StudioNavItem[] = [ { - label: "Brand Studio", - href: "/studio", - description: "Create brand kits, logo direction, colors, and typography.", -}, + studio: "overview", + label: "Overview", + href: "/studio", + description: "See current workspace state, exports, and studio entry points.", + }, + { + studio: "brand", + label: "Brand Studio", + href: "/studio/brand", + description: "Create brand kits, logo direction, colors, and typography.", + }, { + studio: "theme", label: "Theme Studio", href: "/studio/theme", - description: "Design glass, neon, SaaS, luxury, and custom themes.", + description: "Design production theme tokens and export CSS, JSON, and Tailwind.", }, { + studio: "components", label: "Component Studio", href: "/studio/components", - description: "Build buttons, cards, inputs, navbars, and UI systems.", + description: "Build buttons, cards, inputs, badges, and reusable UI systems.", }, { + studio: "canvas", label: "Canvas Studio", href: "/studio/canvas", description: "Draw, drag, resize, and visually compose layouts.", }, { + studio: "code", label: "Code Studio", href: "/studio/code", - description: "Preview and export React, Next.js, and Tailwind code.", + description: "Review current code output and export-ready artifacts.", }, -] as const; \ No newline at end of file +] as const; diff --git a/apps/web/src/stores/app-shell-store.ts b/apps/web/src/stores/app-shell-store.ts index e0fcabd..96e2106 100644 --- a/apps/web/src/stores/app-shell-store.ts +++ b/apps/web/src/stores/app-shell-store.ts @@ -5,26 +5,20 @@ import { create } from "zustand"; export type ActiveStudio = "brand" | "theme" | "components" | "canvas" | "code"; type AppShellState = { - activeStudio: ActiveStudio; - isSidebarOpen: boolean; - isCodePanelOpen: boolean; - generatedCode: string; - setActiveStudio: (studio: ActiveStudio) => void; - toggleSidebar: () => void; - toggleCodePanel: () => void; - setGeneratedCode: (code: string) => void; + readonly isSidebarOpen: boolean; + readonly isCommandPaletteOpen: boolean; + readonly toggleSidebar: () => void; + readonly openCommandPalette: () => void; + readonly closeCommandPalette: () => void; + readonly toggleCommandPalette: () => void; }; export const useAppShellStore = create((set) => ({ - activeStudio: "brand", isSidebarOpen: true, - isCodePanelOpen: true, - generatedCode: ``, - setActiveStudio: (studio) => set({ activeStudio: studio }), + isCommandPaletteOpen: false, toggleSidebar: () => set((state) => ({ isSidebarOpen: !state.isSidebarOpen })), - toggleCodePanel: () => - set((state) => ({ isCodePanelOpen: !state.isCodePanelOpen })), - setGeneratedCode: (code) => set({ generatedCode: code }), -})); \ No newline at end of file + openCommandPalette: () => set({ isCommandPaletteOpen: true }), + closeCommandPalette: () => set({ isCommandPaletteOpen: false }), + toggleCommandPalette: () => + set((state) => ({ isCommandPaletteOpen: !state.isCommandPaletteOpen })), +}));