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
45 changes: 45 additions & 0 deletions apps/web/app/activity-demo/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use client';

import React, { useState, useEffect } from 'react';

export default function Sidebar() {
const [isExpanded, setIsExpanded] = useState(false);
const [time, setTime] = useState(0);

useEffect(() => {
// This effect demonstrates that effects are cleaned up while the
// component is inside a hidden <Activity> boundary (React 19 behavior).
const id = setInterval(() => setTime((t) => t + 1), 1000);
return () => clearInterval(id);
}, []);

return (
<aside
style={{
width: isExpanded ? 300 : 120,
transition: 'width 0.25s',
border: '1px solid #ddd',
padding: 12,
borderRadius: 8,
background: '#fff',
}}
>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<button onClick={() => setIsExpanded((p) => !p)}>
{isExpanded ? 'Collapse' : 'Expand'}
</button>
<div style={{ marginLeft: 'auto', fontSize: 12, color: '#666' }}>time: {time}s</div>
</div>

{isExpanded && (
<nav style={{ marginTop: 12 }}>
<ul style={{ paddingLeft: 16 }}>
<li>Menu item A</li>
<li>Menu item B</li>
<li>Menu item C</li>
</ul>
</nav>
)}
</aside>
);
}
44 changes: 44 additions & 0 deletions apps/web/app/activity-demo/TabsExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';

import React, { useState } from 'react';

export default function TabsExample() {
const [tab, setTab] = useState<'home' | 'contact'>('home');

return (
<div style={{ border: '1px solid #eee', padding: 12, borderRadius: 8 }}>
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
<button onClick={() => setTab('home')}>Home</button>
<button onClick={() => setTab('contact')}>Contact</button>
</div>

<div style={{ display: 'grid', gap: 12 }}>
{/* We intentionally do not use conditional mounting here to preserve DOM/state */}
<div style={{ display: tab === 'home' ? 'block' : 'none' }}>
<h3>Home</h3>
<p>This is the home tab content. Switch to Contact and back to see state preserved.</p>
</div>

<div style={{ display: tab === 'contact' ? 'block' : 'none' }}>
<Contact />
</div>
</div>
</div>
);
}

function Contact() {
const [message, setMessage] = useState('');
return (
<div>
<h3>Contact Us</h3>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
rows={6}
style={{ width: '100%' }}
/>
<p>Message length: {message.length}</p>
</div>
);
}
50 changes: 50 additions & 0 deletions apps/web/app/activity-demo/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use client';

import React, { useState } from 'react';
import Sidebar from './Sidebar';
import TabsExample from './TabsExample';

// Note: React 19 introduced <Activity>. In environments without it, we
// fallback to simple CSS-based hiding while preserving DOM/state.
// If your React version supports Activity, replace the fallback with:
// import { Activity } from 'react';

export default function ActivityDemoPage() {
const [showSidebar, setShowSidebar] = useState(true);

return (
<main style={{ display: 'grid', gap: 16, padding: 24 }}>
<h1>Activity API Demo</h1>
<p>
이 페이지는 <code>&lt;Activity&gt;</code> 경계의 동작을 데모합니다. React 버전이 Activity를
지원하면 경계를 사용하세요. 여기서는 호환을 위해 간단한 숨김(fallback) 방식을 사용합니다.
</p>

<section style={{ display: 'flex', gap: 16 }}>
<div style={{ flex: '0 0 340px' }}>
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<button onClick={() => setShowSidebar((p) => !p)}>Toggle Sidebar</button>
</div>

{/* Fallback: hide with CSS but keep DOM mounted */}
<div style={{ display: showSidebar ? 'block' : 'none' }}>
<Sidebar />
</div>
</div>

<div style={{ flex: 1 }}>
<TabsExample />
</div>
</section>

<section>
<h2>Notes / Caveats</h2>
<ul>
<li>숨김 상태에서는 Effects가 정리(clean-up)될 수 있습니다.</li>
<li>비디오나 오디오처럼 외부 리소스를 제어해야 하는 경우 수동 정리가 필요합니다.</li>
<li>Activity는 실험적입니다. 사용 전 React 버전을 확인하세요.</li>
</ul>
</section>
</main>
);
}
1 change: 1 addition & 0 deletions apps/web/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default function Home() {
<Link href="/loadable">Loadable Component Page</Link>
<Link href="/index-db-test">IndexDB Page</Link>
<Link href="/events-demo">React Event Wrapper Page</Link>
<Link href="/activity-demo">React Activity Test Page</Link>
<div>
<InstallButton />
</div>
Expand Down