reroute-js 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/_/basic/package.json +4 -4
- package/_/basic/src/client/index.html +1 -1
- package/_/basic/src/index.ts +1 -3
- package/_/blog/package.json +4 -4
- package/_/blog/src/client/App.tsx +1 -0
- package/_/blog/src/client/index.html +1 -1
- package/_/blog/src/client/routes/blog/content/1-hello-world.tsx +1 -1
- package/_/blog/src/client/routes/blog/content/3-markdown-with-syntax-highlighting.mdx +213 -0
- package/_/blog/src/client/routes/blog/content/4-content-in-markdown.md +143 -0
- package/_/blog/src/client/routes/blog/content/5-mdx-with-components.mdx +267 -0
- package/_/blog/src/client/routes/blog/index.tsx +59 -0
- package/_/blog/src/client/routes/client.tsx +564 -0
- package/_/blog/src/client/routes/docs.tsx +670 -0
- package/_/blog/src/client/routes/index.tsx +37 -0
- package/_/blog/src/client/routes/markdown-demo.md +169 -0
- package/_/blog/src/client/routes/markdown.tsx +160 -0
- package/_/blog/src/index.ts +5 -2
- package/_/store/package.json +4 -4
- package/_/store/src/client/index.html +1 -1
- package/_/store/src/index.ts +1 -1
- package/cli/bin.d.ts +1 -1
- package/cli/bin.js +806 -14
- package/cli/bin.js.map +11 -7
- package/cli/index.d.ts +1 -1
- package/cli/index.js +52 -3
- package/cli/index.js.map +4 -3
- package/cli/src/cli.d.ts +1 -1
- package/cli/src/commands/boot.d.ts +1 -1
- package/cli/src/commands/build.d.ts +19 -0
- package/cli/src/commands/build.d.ts.map +1 -0
- package/cli/src/commands/dev.d.ts +18 -0
- package/cli/src/commands/dev.d.ts.map +1 -0
- package/cli/src/commands/gen.d.ts +1 -1
- package/cli/src/commands/init.d.ts +1 -1
- package/cli/src/commands/start.d.ts +19 -0
- package/cli/src/commands/start.d.ts.map +1 -0
- package/cli/src/libs/index.d.ts +2 -1
- package/cli/src/libs/index.d.ts.map +1 -1
- package/cli/src/libs/log.d.ts +46 -0
- package/cli/src/libs/log.d.ts.map +1 -0
- package/cli/src/libs/markdown-processor.d.ts +1 -1
- package/cli/src/libs/markdown.d.ts +1 -1
- package/cli/src/libs/tailwind.d.ts +1 -1
- package/cli/src/libs/version.d.ts +1 -1
- package/core/index.d.ts +1 -1
- package/core/index.js +5 -5
- package/core/index.js.map +3 -3
- package/core/src/bundler/hash.d.ts +1 -1
- package/core/src/bundler/index.d.ts +1 -1
- package/core/src/bundler/transpile.d.ts +1 -1
- package/core/src/content/discovery.d.ts +1 -1
- package/core/src/content/index.d.ts +1 -1
- package/core/src/content/metadata.d.ts +1 -1
- package/core/src/content/registry.d.ts +1 -1
- package/core/src/index.d.ts +1 -1
- package/core/src/ssr/data.d.ts +1 -1
- package/core/src/ssr/index.d.ts +1 -1
- package/core/src/ssr/modules.d.ts +1 -1
- package/core/src/ssr/render.d.ts +1 -1
- package/core/src/ssr/seed.d.ts +1 -1
- package/core/src/template/html.d.ts +1 -1
- package/core/src/template/index.d.ts +1 -1
- package/core/src/types.d.ts +1 -1
- package/core/src/utils/cache.d.ts +1 -1
- package/core/src/utils/compression.d.ts +1 -1
- package/core/src/utils/index.d.ts +1 -1
- package/core/src/utils/mime.d.ts +1 -1
- package/core/src/utils/path.d.ts +1 -1
- package/elysia/index.d.ts +1 -1
- package/elysia/index.js +5 -5
- package/elysia/index.js.map +3 -3
- package/elysia/src/index.d.ts +1 -1
- package/elysia/src/libs/http.d.ts +1 -1
- package/elysia/src/libs/image.d.ts +1 -1
- package/elysia/src/plugin.d.ts +1 -1
- package/elysia/src/routes/artifacts.d.ts +1 -1
- package/elysia/src/routes/content.d.ts +1 -1
- package/elysia/src/routes/dev.d.ts +1 -1
- package/elysia/src/routes/image.d.ts +1 -1
- package/elysia/src/routes/ssr.d.ts +1 -1
- package/elysia/src/routes/static.d.ts +1 -1
- package/elysia/src/types.d.ts +1 -1
- package/package.json +6 -1
- package/react/index.d.ts +1 -1
- package/react/index.js +50 -24
- package/react/index.js.map +3 -3
- package/react/src/components/ClientOnly.d.ts +1 -1
- package/react/src/components/ContentRoute.d.ts +1 -1
- package/react/src/components/Image.d.ts +1 -1
- package/react/src/components/Link.d.ts +1 -1
- package/react/src/components/Markdown.d.ts +1 -1
- package/react/src/components/Outlet.d.ts +1 -1
- package/react/src/components/index.d.ts +1 -1
- package/react/src/hooks/index.d.ts +1 -1
- package/react/src/hooks/useContent.d.ts +1 -1
- package/react/src/hooks/useData.d.ts +1 -1
- package/react/src/hooks/useNavigate.d.ts +1 -1
- package/react/src/hooks/useParams.d.ts +1 -1
- package/react/src/hooks/useRouter.d.ts +1 -1
- package/react/src/hooks/useSearchParams.d.ts +1 -1
- package/react/src/index.d.ts +1 -1
- package/react/src/providers/ContentProvider.d.ts +1 -1
- package/react/src/providers/RerouteProvider.d.ts +1 -1
- package/react/src/providers/RouterProvider.d.ts +1 -1
- package/react/src/providers/RouterProvider.d.ts.map +1 -1
- package/react/src/providers/index.d.ts +1 -1
- package/react/src/types/any.d.ts +1 -1
- package/react/src/types/index.d.ts +1 -1
- package/react/src/types/router.d.ts +1 -1
- package/react/src/utils/content.d.ts +1 -1
- package/react/src/utils/head.d.ts +1 -1
- package/react/src/utils/index.d.ts +1 -1
- package/_/blog/src/client/components/Counter.tsx +0 -14
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { ClientOnly, Link } from 'reroute-js/react';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'ClientOnly Component Demo',
|
|
6
|
+
description: 'Examples of using ClientOnly to prevent hydration mismatches',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function ClientDemoPage() {
|
|
10
|
+
return (
|
|
11
|
+
<div style={{ minHeight: '100vh', background: '#f9f9f9' }}>
|
|
12
|
+
{/* Header */}
|
|
13
|
+
<header
|
|
14
|
+
style={{
|
|
15
|
+
background: '#fff',
|
|
16
|
+
borderBottom: '1px solid #e0e0e0',
|
|
17
|
+
padding: '1rem 2rem',
|
|
18
|
+
position: 'sticky',
|
|
19
|
+
top: 0,
|
|
20
|
+
zIndex: 100,
|
|
21
|
+
boxShadow: '0 2px 4px rgba(0,0,0,0.05)',
|
|
22
|
+
}}
|
|
23
|
+
>
|
|
24
|
+
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
|
|
25
|
+
<Link
|
|
26
|
+
to='/'
|
|
27
|
+
style={{
|
|
28
|
+
fontSize: '1.5rem',
|
|
29
|
+
fontWeight: 'bold',
|
|
30
|
+
color: '#333',
|
|
31
|
+
textDecoration: 'none',
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
← Back to Home
|
|
35
|
+
</Link>
|
|
36
|
+
</div>
|
|
37
|
+
</header>
|
|
38
|
+
|
|
39
|
+
{/* Main Content */}
|
|
40
|
+
<main style={{ maxWidth: '1200px', margin: '0 auto', padding: '2rem' }}>
|
|
41
|
+
<h1 style={{ fontSize: '2.5rem', marginBottom: '1rem' }}>
|
|
42
|
+
🎯 ClientOnly Component Demo
|
|
43
|
+
</h1>
|
|
44
|
+
<p style={{ fontSize: '1.1rem', color: '#666', marginBottom: '3rem' }}>
|
|
45
|
+
This page demonstrates various use cases for the{' '}
|
|
46
|
+
<code>ClientOnly</code> component, which prevents hydration mismatches
|
|
47
|
+
by only rendering content on the client-side.
|
|
48
|
+
</p>
|
|
49
|
+
|
|
50
|
+
{/* Example 1: Current Time */}
|
|
51
|
+
<Section
|
|
52
|
+
title='Example 1: Dynamic Time Display'
|
|
53
|
+
description='Without ClientOnly, the server-rendered time would differ from the client, causing a hydration mismatch.'
|
|
54
|
+
>
|
|
55
|
+
<div style={{ display: 'flex', gap: '2rem', flexWrap: 'wrap' }}>
|
|
56
|
+
<ExampleCard title='❌ Wrong (Hydration Error)'>
|
|
57
|
+
<div
|
|
58
|
+
style={{
|
|
59
|
+
padding: '1rem',
|
|
60
|
+
background: '#fee',
|
|
61
|
+
border: '2px solid #fcc',
|
|
62
|
+
borderRadius: '4px',
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<p style={{ margin: 0 }}>
|
|
66
|
+
<strong>Time:</strong>
|
|
67
|
+
{/*comment the following line to stop the mismatch error */}
|
|
68
|
+
{new Date().toLocaleTimeString()}
|
|
69
|
+
</p>
|
|
70
|
+
<p
|
|
71
|
+
style={{
|
|
72
|
+
margin: '0.5rem 0 0 0',
|
|
73
|
+
fontSize: '0.875rem',
|
|
74
|
+
color: '#c33',
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
⚠️ This will cause hydration mismatch! throttle your network
|
|
78
|
+
connection to see.
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
</ExampleCard>
|
|
82
|
+
|
|
83
|
+
<ExampleCard title='✅ Correct (With ClientOnly)'>
|
|
84
|
+
<ClientOnly
|
|
85
|
+
fallback={
|
|
86
|
+
<div
|
|
87
|
+
style={{
|
|
88
|
+
padding: '1rem',
|
|
89
|
+
background: '#f0f0f0',
|
|
90
|
+
borderRadius: '4px',
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
Loading time...
|
|
94
|
+
</div>
|
|
95
|
+
}
|
|
96
|
+
>
|
|
97
|
+
<div
|
|
98
|
+
style={{
|
|
99
|
+
padding: '1rem',
|
|
100
|
+
background: '#efe',
|
|
101
|
+
border: '2px solid #cfc',
|
|
102
|
+
borderRadius: '4px',
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
<p style={{ margin: 0 }}>
|
|
106
|
+
<strong>Time:</strong> {new Date().toLocaleTimeString()}
|
|
107
|
+
</p>
|
|
108
|
+
<p
|
|
109
|
+
style={{
|
|
110
|
+
margin: '0.5rem 0 0 0',
|
|
111
|
+
fontSize: '0.875rem',
|
|
112
|
+
color: '#393',
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
✓ No hydration issues!
|
|
116
|
+
</p>
|
|
117
|
+
</div>
|
|
118
|
+
</ClientOnly>
|
|
119
|
+
</ExampleCard>
|
|
120
|
+
</div>
|
|
121
|
+
</Section>
|
|
122
|
+
|
|
123
|
+
{/* Example 2: Browser API */}
|
|
124
|
+
<Section
|
|
125
|
+
title='Example 2: Browser API Access'
|
|
126
|
+
description='Accessing window, navigator, or other browser APIs safely.'
|
|
127
|
+
>
|
|
128
|
+
<ClientOnly fallback={<LoadingCard message='Detecting browser...' />}>
|
|
129
|
+
<BrowserInfoCard />
|
|
130
|
+
</ClientOnly>
|
|
131
|
+
</Section>
|
|
132
|
+
|
|
133
|
+
{/* Example 3: LocalStorage */}
|
|
134
|
+
<Section
|
|
135
|
+
title='Example 3: LocalStorage Integration'
|
|
136
|
+
description='Reading from localStorage without causing SSR issues.'
|
|
137
|
+
>
|
|
138
|
+
<ClientOnly
|
|
139
|
+
fallback={<LoadingCard message='Loading preferences...' />}
|
|
140
|
+
>
|
|
141
|
+
<LocalStorageDemo />
|
|
142
|
+
</ClientOnly>
|
|
143
|
+
</Section>
|
|
144
|
+
|
|
145
|
+
{/* Example 4: Random Values */}
|
|
146
|
+
<Section
|
|
147
|
+
title='Example 4: Random Values'
|
|
148
|
+
description='Generating random values that would differ between server and client.'
|
|
149
|
+
>
|
|
150
|
+
<ClientOnly>
|
|
151
|
+
<RandomContentCard />
|
|
152
|
+
</ClientOnly>
|
|
153
|
+
</Section>
|
|
154
|
+
|
|
155
|
+
{/* Example 5: Live Counter */}
|
|
156
|
+
<Section
|
|
157
|
+
title='Example 5: Live Counter'
|
|
158
|
+
description='A component that updates in real-time and uses Date.now().'
|
|
159
|
+
>
|
|
160
|
+
<ClientOnly
|
|
161
|
+
fallback={
|
|
162
|
+
<div
|
|
163
|
+
style={{
|
|
164
|
+
padding: '2rem',
|
|
165
|
+
background: '#fff',
|
|
166
|
+
border: '1px solid #e0e0e0',
|
|
167
|
+
borderRadius: '8px',
|
|
168
|
+
textAlign: 'center',
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
<p style={{ fontSize: '1.25rem', color: '#666' }}>
|
|
172
|
+
⏳ Initializing counter...
|
|
173
|
+
</p>
|
|
174
|
+
</div>
|
|
175
|
+
}
|
|
176
|
+
>
|
|
177
|
+
<LiveCounter />
|
|
178
|
+
</ClientOnly>
|
|
179
|
+
</Section>
|
|
180
|
+
|
|
181
|
+
{/* Code Examples */}
|
|
182
|
+
<Section
|
|
183
|
+
title='📝 Code Examples'
|
|
184
|
+
description='How to use ClientOnly in your components.'
|
|
185
|
+
>
|
|
186
|
+
<div
|
|
187
|
+
style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}
|
|
188
|
+
>
|
|
189
|
+
<CodeExample
|
|
190
|
+
title='Basic Usage'
|
|
191
|
+
code={`import { ClientOnly } from 'reroute-js/react';
|
|
192
|
+
|
|
193
|
+
function MyComponent() {
|
|
194
|
+
return (
|
|
195
|
+
<ClientOnly>
|
|
196
|
+
<div>{new Date().toLocaleTimeString()}</div>
|
|
197
|
+
</ClientOnly>
|
|
198
|
+
);
|
|
199
|
+
}`}
|
|
200
|
+
/>
|
|
201
|
+
|
|
202
|
+
<CodeExample
|
|
203
|
+
title='With Fallback'
|
|
204
|
+
code={`<ClientOnly fallback={<div>Loading...</div>}>
|
|
205
|
+
<ComponentUsingBrowserAPI />
|
|
206
|
+
</ClientOnly>`}
|
|
207
|
+
/>
|
|
208
|
+
|
|
209
|
+
<CodeExample
|
|
210
|
+
title='LocalStorage Example'
|
|
211
|
+
code={`<ClientOnly fallback={<p>Loading preferences...</p>}>
|
|
212
|
+
<UserPreferences
|
|
213
|
+
theme={localStorage.getItem('theme')}
|
|
214
|
+
/>
|
|
215
|
+
</ClientOnly>`}
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
</Section>
|
|
219
|
+
|
|
220
|
+
{/* Best Practices */}
|
|
221
|
+
<div
|
|
222
|
+
style={{
|
|
223
|
+
marginTop: '3rem',
|
|
224
|
+
padding: '2rem',
|
|
225
|
+
background: '#fff3cd',
|
|
226
|
+
border: '2px solid #ffc107',
|
|
227
|
+
borderRadius: '8px',
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
<h3 style={{ marginTop: 0 }}>💡 Best Practices</h3>
|
|
231
|
+
<ul style={{ lineHeight: '1.8' }}>
|
|
232
|
+
<li>
|
|
233
|
+
Always provide a <code>fallback</code> for better user experience
|
|
234
|
+
</li>
|
|
235
|
+
<li>Keep the fallback similar in size to avoid layout shifts</li>
|
|
236
|
+
<li>
|
|
237
|
+
Use <code>ClientOnly</code> only when necessary to maximize SSR
|
|
238
|
+
benefits
|
|
239
|
+
</li>
|
|
240
|
+
<li>
|
|
241
|
+
Avoid wrapping your entire app - be specific about what needs
|
|
242
|
+
client-only rendering
|
|
243
|
+
</li>
|
|
244
|
+
</ul>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
{/* Navigation */}
|
|
248
|
+
<div
|
|
249
|
+
style={{
|
|
250
|
+
marginTop: '3rem',
|
|
251
|
+
padding: '2rem',
|
|
252
|
+
background: '#fff',
|
|
253
|
+
border: '1px solid #e0e0e0',
|
|
254
|
+
borderRadius: '8px',
|
|
255
|
+
textAlign: 'center',
|
|
256
|
+
}}
|
|
257
|
+
>
|
|
258
|
+
<div
|
|
259
|
+
style={{
|
|
260
|
+
display: 'flex',
|
|
261
|
+
gap: '1rem',
|
|
262
|
+
justifyContent: 'center',
|
|
263
|
+
flexWrap: 'wrap',
|
|
264
|
+
}}
|
|
265
|
+
>
|
|
266
|
+
<Link
|
|
267
|
+
to='/'
|
|
268
|
+
style={{
|
|
269
|
+
padding: '0.75rem 1.5rem',
|
|
270
|
+
background: '#666',
|
|
271
|
+
color: '#fff',
|
|
272
|
+
textDecoration: 'none',
|
|
273
|
+
borderRadius: '4px',
|
|
274
|
+
}}
|
|
275
|
+
>
|
|
276
|
+
← Back to Home
|
|
277
|
+
</Link>
|
|
278
|
+
<Link
|
|
279
|
+
to='/docs'
|
|
280
|
+
style={{
|
|
281
|
+
padding: '0.75rem 1.5rem',
|
|
282
|
+
background: '#0066cc',
|
|
283
|
+
color: '#fff',
|
|
284
|
+
textDecoration: 'none',
|
|
285
|
+
borderRadius: '4px',
|
|
286
|
+
}}
|
|
287
|
+
>
|
|
288
|
+
Fragment Demo →
|
|
289
|
+
</Link>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
</main>
|
|
293
|
+
</div>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Helper Components
|
|
298
|
+
|
|
299
|
+
function Section({
|
|
300
|
+
title,
|
|
301
|
+
description,
|
|
302
|
+
children,
|
|
303
|
+
}: {
|
|
304
|
+
title: string;
|
|
305
|
+
description: string;
|
|
306
|
+
children: React.ReactNode;
|
|
307
|
+
}) {
|
|
308
|
+
return (
|
|
309
|
+
<section
|
|
310
|
+
style={{
|
|
311
|
+
background: '#fff',
|
|
312
|
+
border: '1px solid #e0e0e0',
|
|
313
|
+
borderRadius: '8px',
|
|
314
|
+
padding: '2rem',
|
|
315
|
+
marginBottom: '2rem',
|
|
316
|
+
}}
|
|
317
|
+
>
|
|
318
|
+
<h2 style={{ marginTop: 0, marginBottom: '0.5rem' }}>{title}</h2>
|
|
319
|
+
<p style={{ color: '#666', marginBottom: '1.5rem' }}>{description}</p>
|
|
320
|
+
{children}
|
|
321
|
+
</section>
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function ExampleCard({
|
|
326
|
+
title,
|
|
327
|
+
children,
|
|
328
|
+
}: {
|
|
329
|
+
title: string;
|
|
330
|
+
children: React.ReactNode;
|
|
331
|
+
}) {
|
|
332
|
+
return (
|
|
333
|
+
<div style={{ flex: '1 1 300px' }}>
|
|
334
|
+
<h4 style={{ marginTop: 0, marginBottom: '1rem' }}>{title}</h4>
|
|
335
|
+
{children}
|
|
336
|
+
</div>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function LoadingCard({ message }: { message: string }) {
|
|
341
|
+
return (
|
|
342
|
+
<div
|
|
343
|
+
style={{
|
|
344
|
+
padding: '2rem',
|
|
345
|
+
background: '#f8f9fa',
|
|
346
|
+
border: '1px solid #dee2e6',
|
|
347
|
+
borderRadius: '8px',
|
|
348
|
+
textAlign: 'center',
|
|
349
|
+
}}
|
|
350
|
+
>
|
|
351
|
+
<p style={{ margin: 0, color: '#6c757d' }}>⏳ {message}</p>
|
|
352
|
+
</div>
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function BrowserInfoCard() {
|
|
357
|
+
const [info, setInfo] = useState({
|
|
358
|
+
userAgent: '',
|
|
359
|
+
language: '',
|
|
360
|
+
platform: '',
|
|
361
|
+
screenSize: '',
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
useEffect(() => {
|
|
365
|
+
setInfo({
|
|
366
|
+
userAgent: navigator.userAgent,
|
|
367
|
+
language: navigator.language,
|
|
368
|
+
platform: navigator.platform,
|
|
369
|
+
screenSize: `${window.innerWidth} x ${window.innerHeight}`,
|
|
370
|
+
});
|
|
371
|
+
}, []);
|
|
372
|
+
|
|
373
|
+
return (
|
|
374
|
+
<div
|
|
375
|
+
style={{
|
|
376
|
+
padding: '1.5rem',
|
|
377
|
+
background: '#fff',
|
|
378
|
+
border: '1px solid #e0e0e0',
|
|
379
|
+
borderRadius: '8px',
|
|
380
|
+
}}
|
|
381
|
+
>
|
|
382
|
+
<h4 style={{ marginTop: 0, marginBottom: '1rem' }}>🌐 Browser Info</h4>
|
|
383
|
+
<dl style={{ margin: 0 }}>
|
|
384
|
+
<dt style={{ fontWeight: 'bold', marginTop: '0.5rem' }}>Platform:</dt>
|
|
385
|
+
<dd style={{ margin: '0 0 0.5rem 1rem', color: '#666' }}>
|
|
386
|
+
{info.platform}
|
|
387
|
+
</dd>
|
|
388
|
+
|
|
389
|
+
<dt style={{ fontWeight: 'bold', marginTop: '0.5rem' }}>Language:</dt>
|
|
390
|
+
<dd style={{ margin: '0 0 0.5rem 1rem', color: '#666' }}>
|
|
391
|
+
{info.language}
|
|
392
|
+
</dd>
|
|
393
|
+
|
|
394
|
+
<dt style={{ fontWeight: 'bold', marginTop: '0.5rem' }}>
|
|
395
|
+
Screen Size:
|
|
396
|
+
</dt>
|
|
397
|
+
<dd style={{ margin: '0 0 0.5rem 1rem', color: '#666' }}>
|
|
398
|
+
{info.screenSize}
|
|
399
|
+
</dd>
|
|
400
|
+
|
|
401
|
+
<dt style={{ fontWeight: 'bold', marginTop: '0.5rem' }}>User Agent:</dt>
|
|
402
|
+
<dd
|
|
403
|
+
style={{
|
|
404
|
+
margin: '0 0 0.5rem 1rem',
|
|
405
|
+
color: '#666',
|
|
406
|
+
fontSize: '0.875rem',
|
|
407
|
+
wordBreak: 'break-word',
|
|
408
|
+
}}
|
|
409
|
+
>
|
|
410
|
+
{info.userAgent}
|
|
411
|
+
</dd>
|
|
412
|
+
</dl>
|
|
413
|
+
</div>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function LocalStorageDemo() {
|
|
418
|
+
const [visits, setVisits] = useState(0);
|
|
419
|
+
|
|
420
|
+
useEffect(() => {
|
|
421
|
+
const count = Number.parseInt(localStorage.getItem('visits') || '0', 10);
|
|
422
|
+
const newCount = count + 1;
|
|
423
|
+
localStorage.setItem('visits', String(newCount));
|
|
424
|
+
setVisits(newCount);
|
|
425
|
+
}, []);
|
|
426
|
+
|
|
427
|
+
const handleReset = () => {
|
|
428
|
+
localStorage.setItem('visits', '0');
|
|
429
|
+
setVisits(0);
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
return (
|
|
433
|
+
<div
|
|
434
|
+
style={{
|
|
435
|
+
padding: '1.5rem',
|
|
436
|
+
background: '#fff',
|
|
437
|
+
border: '1px solid #e0e0e0',
|
|
438
|
+
borderRadius: '8px',
|
|
439
|
+
}}
|
|
440
|
+
>
|
|
441
|
+
<h4 style={{ marginTop: 0, marginBottom: '1rem' }}>💾 Visit Counter</h4>
|
|
442
|
+
<p style={{ fontSize: '1.5rem', margin: '1rem 0' }}>
|
|
443
|
+
You've visited this page <strong>{visits}</strong> time
|
|
444
|
+
{visits !== 1 ? 's' : ''}
|
|
445
|
+
</p>
|
|
446
|
+
<button
|
|
447
|
+
type='button'
|
|
448
|
+
onClick={handleReset}
|
|
449
|
+
style={{
|
|
450
|
+
padding: '0.5rem 1rem',
|
|
451
|
+
background: '#dc3545',
|
|
452
|
+
color: '#fff',
|
|
453
|
+
border: 'none',
|
|
454
|
+
borderRadius: '4px',
|
|
455
|
+
cursor: 'pointer',
|
|
456
|
+
}}
|
|
457
|
+
>
|
|
458
|
+
Reset Counter
|
|
459
|
+
</button>
|
|
460
|
+
<p style={{ fontSize: '0.875rem', color: '#666', marginTop: '1rem' }}>
|
|
461
|
+
This data is stored in localStorage and persists across page reloads.
|
|
462
|
+
</p>
|
|
463
|
+
</div>
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function RandomContentCard() {
|
|
468
|
+
const randomColor = `hsl(${Math.random() * 360}, 70%, 80%)`;
|
|
469
|
+
const randomNumber = Math.floor(Math.random() * 100);
|
|
470
|
+
const randomEmoji = ['🎲', '🎰', '🎯', '🎪', '🎨', '🎭', '🎪'][
|
|
471
|
+
Math.floor(Math.random() * 7)
|
|
472
|
+
];
|
|
473
|
+
|
|
474
|
+
return (
|
|
475
|
+
<div
|
|
476
|
+
style={{
|
|
477
|
+
padding: '1.5rem',
|
|
478
|
+
background: randomColor,
|
|
479
|
+
border: '2px solid #333',
|
|
480
|
+
borderRadius: '8px',
|
|
481
|
+
}}
|
|
482
|
+
>
|
|
483
|
+
<h4 style={{ marginTop: 0, marginBottom: '1rem' }}>
|
|
484
|
+
{randomEmoji} Random Content
|
|
485
|
+
</h4>
|
|
486
|
+
<p style={{ fontSize: '1.25rem', margin: '1rem 0' }}>
|
|
487
|
+
Your lucky number: <strong>{randomNumber}</strong>
|
|
488
|
+
</p>
|
|
489
|
+
<p style={{ fontSize: '0.875rem', color: '#333', marginBottom: 0 }}>
|
|
490
|
+
This content is randomly generated on every client render. Without
|
|
491
|
+
ClientOnly, the server and client would generate different values,
|
|
492
|
+
causing a hydration error.
|
|
493
|
+
</p>
|
|
494
|
+
</div>
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function LiveCounter() {
|
|
499
|
+
const [count, setCount] = useState(0);
|
|
500
|
+
const [startTime] = useState(() => Date.now());
|
|
501
|
+
|
|
502
|
+
useEffect(() => {
|
|
503
|
+
const interval = setInterval(() => {
|
|
504
|
+
setCount((c) => c + 1);
|
|
505
|
+
}, 1000);
|
|
506
|
+
|
|
507
|
+
return () => clearInterval(interval);
|
|
508
|
+
}, []);
|
|
509
|
+
|
|
510
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
511
|
+
|
|
512
|
+
return (
|
|
513
|
+
<div
|
|
514
|
+
style={{
|
|
515
|
+
padding: '2rem',
|
|
516
|
+
background: '#fff',
|
|
517
|
+
border: '1px solid #e0e0e0',
|
|
518
|
+
borderRadius: '8px',
|
|
519
|
+
textAlign: 'center',
|
|
520
|
+
}}
|
|
521
|
+
>
|
|
522
|
+
<h4 style={{ marginTop: 0, marginBottom: '1rem' }}>⏱️ Live Counter</h4>
|
|
523
|
+
<div
|
|
524
|
+
style={{
|
|
525
|
+
fontSize: '3rem',
|
|
526
|
+
fontWeight: 'bold',
|
|
527
|
+
color: '#0066cc',
|
|
528
|
+
margin: '1rem 0',
|
|
529
|
+
}}
|
|
530
|
+
>
|
|
531
|
+
{count}
|
|
532
|
+
</div>
|
|
533
|
+
<p style={{ color: '#666' }}>
|
|
534
|
+
Running for {elapsed} second{elapsed !== 1 ? 's' : ''}
|
|
535
|
+
</p>
|
|
536
|
+
<p style={{ fontSize: '0.875rem', color: '#999', marginTop: '1rem' }}>
|
|
537
|
+
This counter updates every second using setInterval and uses Date.now()
|
|
538
|
+
for timing.
|
|
539
|
+
</p>
|
|
540
|
+
</div>
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function CodeExample({ title, code }: { title: string; code: string }) {
|
|
545
|
+
return (
|
|
546
|
+
<div>
|
|
547
|
+
<h4 style={{ marginBottom: '0.5rem' }}>{title}</h4>
|
|
548
|
+
<pre
|
|
549
|
+
style={{
|
|
550
|
+
background: '#f5f5f5',
|
|
551
|
+
padding: '1rem',
|
|
552
|
+
borderRadius: '4px',
|
|
553
|
+
overflow: 'auto',
|
|
554
|
+
fontSize: '0.875rem',
|
|
555
|
+
margin: 0,
|
|
556
|
+
}}
|
|
557
|
+
>
|
|
558
|
+
<code>{code}</code>
|
|
559
|
+
</pre>
|
|
560
|
+
</div>
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export { meta };
|