11'use client'
22
33import React , { useState , useEffect , useRef , useMemo } from 'react'
4- import { ChevronRight , Upload , Play , FileCode2 , Zap , Package , Code2 , Terminal , GitBranch } from 'lucide-react'
5- import CopyButton from '../ui/CopyButton'
4+ import { ChevronLeft , ChevronRight , Upload , Play , FileCode2 , Zap , Package , Code2 , Terminal , GitBranch } from 'lucide-react'
65import { ctaInvertedPrimaryClasses } from '../ui/ctaInvertedPrimaryClasses'
76import { colorizeTerminalText } from '@/lib/terminal-colorize'
87import { DemoShikiEditor , DemoShikiStatic , inferDemoLang } from './DemoShiki'
@@ -648,6 +647,34 @@ export default function Demo() {
648647 }
649648 } , [ activeContextView , contextMain , contextJsons , contextBundle ] )
650649
650+ type ContextViewTab = 'bundle' | 'folder' | 'main'
651+
652+ const contextViewOrder = useMemo ( ( ) : ContextViewTab [ ] => {
653+ const order : ContextViewTab [ ] = [ ]
654+ if ( contextBundle ) order . push ( 'bundle' )
655+ if ( contextJsons ) order . push ( 'folder' )
656+ if ( contextMain ) order . push ( 'main' )
657+ return order
658+ } , [ contextBundle , contextJsons , contextMain ] )
659+
660+ const showContextViewTabs = contextViewOrder . length > 1
661+
662+ const cycleContextView = ( direction : - 1 | 1 ) => {
663+ if ( contextViewOrder . length === 0 ) return
664+ const i = contextViewOrder . indexOf ( activeContextView as ContextViewTab )
665+ const from = i >= 0 ? i : 0
666+ const next =
667+ ( from + direction + contextViewOrder . length ) % contextViewOrder . length
668+ setActiveContextView ( contextViewOrder [ next ] )
669+ }
670+
671+ useEffect ( ( ) => {
672+ if ( contextViewOrder . length === 0 ) return
673+ if ( ! contextViewOrder . includes ( activeContextView as ContextViewTab ) ) {
674+ setActiveContextView ( contextViewOrder [ 0 ] )
675+ }
676+ } , [ contextViewOrder , activeContextView ] )
677+
651678 // Intersection observer hooks for animations
652679 // Reset contentRef and ctaRef animation when switching tabs
653680 const { ref : headerRef , inView : headerInView } = useInView ( 0.1 )
@@ -695,15 +722,33 @@ export default function Demo() {
695722 await new Promise ( resolve => setTimeout ( resolve , 200 ) )
696723
697724 if ( uploadedFiles . length > 1 ) {
698- // Generate multi-file context bundles
725+ // Generate multi-file context bundles + first file’s component bundle (same tab UX as single-file)
699726 const { contextJsons : folderContexts , contextMain : projectContext } = generateMultiFileContextBundle ( uploadedFiles , includeStyle )
700727 setContextJsons ( folderContexts )
701728 setContextMain ( projectContext )
729+ setContextBundle ( generateContextBundle ( uploadedFiles [ 0 ] . content , includeStyle ) )
702730 setActiveContextView ( 'main' )
703731 } else {
704- // Single file - use original generation
732+ // Single file — component bundle plus project/folder views so tabs match multi-file demo
705733 const bundle = generateContextBundle ( userCode , includeStyle )
706734 setContextBundle ( bundle )
735+ const demoRelPath =
736+ uploadedFiles . length === 1
737+ ? uploadedFiles [ 0 ] . path . includes ( '/' )
738+ ? uploadedFiles [ 0 ] . path
739+ : `src/components/${ uploadedFiles [ 0 ] . name } `
740+ : fileName === 'page.tsx'
741+ ? 'src/app/dashboard/page.tsx'
742+ : fileName . endsWith ( '.ts' ) && ! fileName . endsWith ( '.tsx' )
743+ ? `src/services/${ fileName } `
744+ : `src/components/${ fileName } `
745+ const { contextJsons : folderContexts , contextMain : projectContext } =
746+ generateMultiFileContextBundle (
747+ [ { name : fileName , content : userCode , path : demoRelPath } ] ,
748+ includeStyle
749+ )
750+ setContextJsons ( folderContexts )
751+ setContextMain ( projectContext )
707752 setActiveContextView ( 'bundle' )
708753 }
709754
@@ -717,6 +762,8 @@ export default function Demo() {
717762 setShowOutput ( false )
718763 setTerminalOutput ( [ ] )
719764 setContextBundle ( null )
765+ setContextJsons ( null )
766+ setContextMain ( null )
720767
721768 // Update filename based on example
722769 if ( example === 'react' ) {
@@ -760,6 +807,8 @@ export default function Demo() {
760807 setShowOutput ( false )
761808 setTerminalOutput ( [ ] )
762809 setContextBundle ( null )
810+ setContextJsons ( null )
811+ setContextMain ( null )
763812 }
764813
765814 // Workflow handler - runs all steps automatically
@@ -1040,12 +1089,12 @@ export default function Demo() {
10401089 { /* Main content area */ }
10411090 < div
10421091 ref = { contentRef }
1043- className = { `grid lg:grid-cols-2 gap-6 transition-all duration-1000 delay-300 ${
1092+ className = { `grid min-w-0 max-w-full lg:grid-cols-2 gap-6 transition-all duration-1000 delay-300 ${
10441093 contentInView ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'
10451094 } `}
10461095 >
10471096 { /* Code editor panel */ }
1048- < div className = "relative" >
1097+ < div className = "relative min-w-0 " >
10491098 { /* Files ready for context generation indicator */ }
10501099 { filesReadyForContextGeneration && uploadedFiles . length > 0 && (
10511100 < div
@@ -1060,7 +1109,7 @@ export default function Demo() {
10601109 </ div >
10611110 ) }
10621111
1063- < div className = "bg-white dark:bg-gray-900 rounded-2xl shadow-2xl ring-1 ring-gray-200/50 dark:ring-gray-800/50 overflow-hidden" >
1112+ < div className = "min-w-0 bg-white dark:bg-gray-900 rounded-2xl shadow-2xl ring-1 ring-gray-200/50 dark:ring-gray-800/50 overflow-hidden" >
10641113 < div className = "bg-gray-50 dark:bg-gray-800 px-6 py-3 flex items-center justify-between border-b border-gray-200 dark:border-gray-700" >
10651114 < div className = "flex items-center gap-3" >
10661115 < Code2 className = "w-4 h-4 text-green-400 flex-shrink-0" />
@@ -1102,16 +1151,13 @@ export default function Demo() {
11021151 ) ) }
11031152 </ div >
11041153 ) }
1105- < div className = "relative overflow-hidden " >
1154+ < div className = "relative min-h-0 min-w-0 " >
11061155 < DemoShikiEditor
11071156 value = { userCode }
11081157 onChange = { setUserCode }
11091158 lang = { inferDemoLang ( fileName ) }
11101159 placeholder = "Paste your React/TypeScript code here..."
11111160 />
1112- < div className = "absolute top-4 right-4 z-[2]" >
1113- < CopyButton text = { userCode } />
1114- </ div >
11151161 </ div >
11161162 </ div >
11171163
@@ -1170,7 +1216,7 @@ export default function Demo() {
11701216 </ div >
11711217 < div
11721218 ref = { terminalScrollRef }
1173- className = "h-[280px] overflow-y-auto overflow-x-auto p-6 font-mono text-sm bg-gray-900 sidebar-scrollable"
1219+ className = "h-[280px] lg:h-[420px] overflow-y-auto overflow-x-auto p-6 font-mono text-sm bg-gray-900 sidebar-scrollable"
11741220 >
11751221 { terminalOutput . map ( ( line , index ) =>
11761222 line . type === 'empty' ? (
@@ -1208,38 +1254,72 @@ export default function Demo() {
12081254 'context.json' }
12091255 </ span >
12101256 </ div >
1211- < div className = "flex-shrink-0" >
1212- < CopyButton text = { displayContextJson } />
1213- </ div >
12141257 </ div >
12151258
1216- { /* Context view toggle buttons */ }
1217- { contextMain && contextJsons && (
1218- < div className = "bg-gray-100 dark:bg-gray-800/50 px-2 sm:px-4 py-2 flex items-center gap-1.5 sm:gap-2 overflow-x-auto border-b border-gray-200 dark:border-gray-700" >
1259+ { /* Context view: tabs + side chevrons to cycle bundle / folder / project */ }
1260+ { showContextViewTabs && (
1261+ < div className = "bg-gray-100 dark:bg-gray-800/50 px-2 sm:px-3 py-2 flex items-center gap-1 sm:gap-2 border-b border-gray-200 dark:border-gray-700" >
12191262 < button
1220- onClick = { ( ) => setActiveContextView ( 'main' ) }
1221- className = { `px-2 sm:px-3 py-1.5 rounded-lg text-[10px] sm:text-xs font-medium transition-all whitespace-nowrap flex-shrink-0 ${
1222- activeContextView === 'main'
1223- ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
1224- : 'text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700/50'
1225- } `}
1263+ type = "button"
1264+ onClick = { ( ) => cycleContextView ( - 1 ) }
1265+ className = "flex-shrink-0 p-1.5 rounded-lg text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700/50 transition-colors"
1266+ aria-label = "Previous context view"
12261267 >
1227- context_main.json
1268+ < ChevronLeft className = "w-4 h-4 sm:w-5 sm:h-5" />
12281269 </ button >
1270+ < div className = "flex flex-1 items-center gap-1.5 sm:gap-2 overflow-x-auto min-w-0" >
1271+ { contextBundle && (
1272+ < button
1273+ type = "button"
1274+ onClick = { ( ) => setActiveContextView ( 'bundle' ) }
1275+ className = { `px-2 sm:px-3 py-1.5 rounded-lg text-[10px] sm:text-xs font-medium transition-all whitespace-nowrap flex-shrink-0 ${
1276+ activeContextView === 'bundle'
1277+ ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
1278+ : 'text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700/50'
1279+ } `}
1280+ >
1281+ context.json
1282+ </ button >
1283+ ) }
1284+ { contextJsons && (
1285+ < button
1286+ type = "button"
1287+ onClick = { ( ) => setActiveContextView ( 'folder' ) }
1288+ className = { `px-2 sm:px-3 py-1.5 rounded-lg text-[10px] sm:text-xs font-medium transition-all whitespace-nowrap flex-shrink-0 ${
1289+ activeContextView === 'folder'
1290+ ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
1291+ : 'text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700/50'
1292+ } `}
1293+ >
1294+ context.json (per folder)
1295+ </ button >
1296+ ) }
1297+ { contextMain && (
1298+ < button
1299+ type = "button"
1300+ onClick = { ( ) => setActiveContextView ( 'main' ) }
1301+ className = { `px-2 sm:px-3 py-1.5 rounded-lg text-[10px] sm:text-xs font-medium transition-all whitespace-nowrap flex-shrink-0 ${
1302+ activeContextView === 'main'
1303+ ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
1304+ : 'text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700/50'
1305+ } `}
1306+ >
1307+ context_main.json
1308+ </ button >
1309+ ) }
1310+ </ div >
12291311 < button
1230- onClick = { ( ) => setActiveContextView ( 'folder' ) }
1231- className = { `px-2 sm:px-3 py-1.5 rounded-lg text-[10px] sm:text-xs font-medium transition-all whitespace-nowrap flex-shrink-0 ${
1232- activeContextView === 'folder'
1233- ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
1234- : 'text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700/50'
1235- } `}
1312+ type = "button"
1313+ onClick = { ( ) => cycleContextView ( 1 ) }
1314+ className = "flex-shrink-0 p-1.5 rounded-lg text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700/50 transition-colors"
1315+ aria-label = "Next context view"
12361316 >
1237- context.json (per folder)
1317+ < ChevronRight className = "w-4 h-4 sm:w-5 sm:h-5" />
12381318 </ button >
12391319 </ div >
12401320 ) }
12411321
1242- < div className = "h-[280px] overflow-y-auto overflow-x-auto p-3 sm:p-6 bg-gray-50 dark:bg-gray-900 sidebar-scrollable w-full" >
1322+ < div className = "h-[280px] lg:h-[420px] overflow-y-auto overflow-x-auto p-3 sm:p-6 bg-gray-50 dark:bg-gray-900 sidebar-scrollable w-full" >
12431323 < DemoShikiStatic
12441324 code = { displayContextJson }
12451325 lang = "json"
@@ -1325,7 +1405,7 @@ export default function Demo() {
13251405 </ div >
13261406 < div
13271407 ref = { workflowScrollRef }
1328- className = "h-[500px] overflow-y-auto overflow-x-auto p-6 font-mono text-sm bg-gray-900 sidebar-scrollable"
1408+ className = "h-[500px] lg:h-[600px] overflow-y-auto overflow-x-auto p-6 font-mono text-sm bg-gray-900 sidebar-scrollable"
13291409 >
13301410 { workflowOutput . map ( ( line : any , index : number ) => {
13311411 if ( line . type === 'empty' ) {
@@ -1439,7 +1519,6 @@ export default function Demo() {
14391519 < code className = "text-sm sm:text-base lg:text-lg font-mono font-semibold text-gray-900 dark:text-gray-100" aria-label = "Installation command" >
14401520 npm install -g logicstamp-context
14411521 </ code >
1442- < CopyButton text = "npm install -g logicstamp-context" className = "ml-2" />
14431522 </ div >
14441523 </ div >
14451524 < a
@@ -1477,7 +1556,6 @@ export default function Demo() {
14771556 < code className = "text-sm sm:text-base lg:text-lg font-mono font-semibold text-gray-900 dark:text-gray-100" aria-label = "Installation command" >
14781557 npm install -g logicstamp-context
14791558 </ code >
1480- < CopyButton text = "npm install -g logicstamp-context" className = "ml-2" />
14811559 </ div >
14821560 </ div >
14831561 { /* MCP Installation */ }
@@ -1489,7 +1567,6 @@ export default function Demo() {
14891567 < code className = "text-sm sm:text-base lg:text-lg font-mono font-semibold text-gray-900 dark:text-gray-100" aria-label = "Installation command" >
14901568 npm install -g logicstamp-mcp
14911569 </ code >
1492- < CopyButton text = "npm install -g logicstamp-mcp" className = "ml-2" />
14931570 </ div >
14941571 </ div >
14951572 </ div >
0 commit comments