create-fluxstack 1.13.0 → 1.14.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/LLMD/patterns/anti-patterns.md +100 -0
- package/LLMD/reference/routing.md +39 -39
- package/LLMD/resources/live-auth.md +20 -2
- package/LLMD/resources/live-components.md +94 -10
- package/LLMD/resources/live-logging.md +95 -33
- package/LLMD/resources/live-upload.md +59 -8
- package/app/client/index.html +2 -2
- package/app/client/public/favicon.svg +46 -0
- package/app/client/src/App.tsx +2 -1
- package/app/client/src/assets/fluxstack-static.svg +46 -0
- package/app/client/src/assets/fluxstack.svg +183 -0
- package/app/client/src/components/AppLayout.tsx +138 -9
- package/app/client/src/components/BackButton.tsx +13 -13
- package/app/client/src/components/DemoPage.tsx +4 -4
- package/app/client/src/live/AuthDemo.tsx +23 -21
- package/app/client/src/live/ChatDemo.tsx +2 -2
- package/app/client/src/live/CounterDemo.tsx +12 -12
- package/app/client/src/live/FormDemo.tsx +2 -2
- package/app/client/src/live/LiveDebuggerPanel.tsx +779 -0
- package/app/client/src/live/RoomChatDemo.tsx +24 -16
- package/app/client/src/main.tsx +13 -13
- package/app/client/src/pages/ApiTestPage.tsx +6 -6
- package/app/client/src/pages/HomePage.tsx +80 -52
- package/app/server/live/LiveAdminPanel.ts +1 -0
- package/app/server/live/LiveChat.ts +78 -77
- package/app/server/live/LiveCounter.ts +1 -1
- package/app/server/live/LiveForm.ts +1 -0
- package/app/server/live/LiveLocalCounter.ts +38 -37
- package/app/server/live/LiveProtectedChat.ts +1 -0
- package/app/server/live/LiveRoomChat.ts +1 -0
- package/app/server/live/LiveUpload.ts +1 -0
- package/app/server/live/register-components.ts +19 -19
- package/config/system/runtime.config.ts +4 -0
- package/core/build/optimizer.ts +235 -235
- package/core/client/components/Live.tsx +17 -11
- package/core/client/components/LiveDebugger.tsx +1324 -0
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
- package/core/client/hooks/useLiveComponent.ts +11 -1
- package/core/client/hooks/useLiveDebugger.ts +392 -0
- package/core/client/index.ts +14 -0
- package/core/plugins/built-in/index.ts +134 -134
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +4 -0
- package/core/plugins/built-in/vite/index.ts +75 -21
- package/core/server/index.ts +15 -15
- package/core/server/live/ComponentRegistry.ts +55 -26
- package/core/server/live/FileUploadManager.ts +188 -24
- package/core/server/live/LiveDebugger.ts +462 -0
- package/core/server/live/LiveLogger.ts +38 -5
- package/core/server/live/LiveRoomManager.ts +17 -1
- package/core/server/live/StateSignature.ts +87 -27
- package/core/server/live/WebSocketConnectionManager.ts +11 -10
- package/core/server/live/auto-generated-components.ts +1 -1
- package/core/server/live/websocket-plugin.ts +233 -8
- package/core/server/plugins/static-files-plugin.ts +179 -69
- package/core/types/build.ts +219 -219
- package/core/types/plugin.ts +107 -107
- package/core/types/types.ts +145 -9
- package/core/utils/logger/startup-banner.ts +82 -82
- package/core/utils/version.ts +6 -6
- package/package.json +1 -1
- package/app/client/src/assets/react.svg +0 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
1
2
|
import { Link, Outlet, useLocation } from 'react-router'
|
|
2
|
-
import { FaBook, FaGithub } from 'react-icons/fa'
|
|
3
|
+
import { FaBook, FaGithub, FaBars, FaTimes } from 'react-icons/fa'
|
|
4
|
+
import FluxStackLogo from '@client/src/assets/fluxstack.svg'
|
|
5
|
+
import faviconSvg from '@client/src/assets/fluxstack-static.svg?raw'
|
|
3
6
|
|
|
4
7
|
const navItems = [
|
|
5
8
|
{ to: '/', label: 'Home' },
|
|
@@ -12,17 +15,60 @@ const navItems = [
|
|
|
12
15
|
{ to: '/api-test', label: 'API Test' }
|
|
13
16
|
]
|
|
14
17
|
|
|
18
|
+
const routeFlameHue: Record<string, string> = {
|
|
19
|
+
'/': '0deg', // roxo original
|
|
20
|
+
'/counter': '180deg', // ciano
|
|
21
|
+
'/form': '300deg', // rosa
|
|
22
|
+
'/upload': '60deg', // amarelo
|
|
23
|
+
'/chat': '120deg', // verde
|
|
24
|
+
'/room-chat': '240deg', // azul
|
|
25
|
+
'/auth': '330deg', // vermelho
|
|
26
|
+
'/api-test': '90deg', // lima
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
export function AppLayout() {
|
|
16
30
|
const location = useLocation()
|
|
31
|
+
const [menuOpen, setMenuOpen] = useState(false)
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const current = navItems.find(item => item.to === location.pathname)
|
|
35
|
+
document.title = current ? `${current.label} - FluxStack` : 'FluxStack'
|
|
36
|
+
|
|
37
|
+
// Dynamic favicon with hue-rotate
|
|
38
|
+
const hue = routeFlameHue[location.pathname] || '0deg'
|
|
39
|
+
const colored = faviconSvg.replace(
|
|
40
|
+
'<svg ',
|
|
41
|
+
`<svg style="filter: hue-rotate(${hue})" `
|
|
42
|
+
)
|
|
43
|
+
const blob = new Blob([colored], { type: 'image/svg+xml' })
|
|
44
|
+
const url = URL.createObjectURL(blob)
|
|
45
|
+
let link = document.querySelector<HTMLLinkElement>('link[rel="icon"]')
|
|
46
|
+
if (!link) {
|
|
47
|
+
link = document.createElement('link')
|
|
48
|
+
link.rel = 'icon'
|
|
49
|
+
document.head.appendChild(link)
|
|
50
|
+
}
|
|
51
|
+
link.type = 'image/svg+xml'
|
|
52
|
+
link.href = url
|
|
53
|
+
return () => URL.revokeObjectURL(url)
|
|
54
|
+
}, [location.pathname])
|
|
17
55
|
|
|
18
56
|
return (
|
|
19
57
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900">
|
|
20
|
-
<header className="sticky top-0 z-
|
|
21
|
-
<div className="container mx-auto px-6 py-4 flex
|
|
22
|
-
<
|
|
58
|
+
<header className="sticky top-0 z-50 backdrop-blur-md bg-slate-900/60 border-b border-white/10">
|
|
59
|
+
<div className="container mx-auto px-4 sm:px-6 py-3 sm:py-4 flex items-center justify-between gap-4">
|
|
60
|
+
<Link to="/" className="flex items-center gap-2 text-white font-semibold tracking-wide">
|
|
61
|
+
<img
|
|
62
|
+
src={FluxStackLogo}
|
|
63
|
+
alt="FluxStack"
|
|
64
|
+
className="w-9 h-9 transition-[filter] duration-500 drop-shadow-[0_0_8px_rgba(168,85,247,0.5)]"
|
|
65
|
+
style={{ filter: `hue-rotate(${routeFlameHue[location.pathname] || '0deg'})` }}
|
|
66
|
+
/>
|
|
23
67
|
FluxStack
|
|
24
|
-
</
|
|
25
|
-
|
|
68
|
+
</Link>
|
|
69
|
+
|
|
70
|
+
{/* Desktop nav */}
|
|
71
|
+
<nav className="hidden md:flex items-center gap-2">
|
|
26
72
|
{navItems.map((item) => {
|
|
27
73
|
const active = location.pathname === item.to
|
|
28
74
|
return (
|
|
@@ -40,27 +86,110 @@ export function AppLayout() {
|
|
|
40
86
|
)
|
|
41
87
|
})}
|
|
42
88
|
</nav>
|
|
89
|
+
|
|
43
90
|
<div className="flex items-center gap-2">
|
|
91
|
+
<a
|
|
92
|
+
href="https://live-docs.marcosbrendon.com/"
|
|
93
|
+
target="_blank"
|
|
94
|
+
rel="noopener noreferrer"
|
|
95
|
+
className="hidden sm:inline-flex items-center gap-2 px-3 py-1.5 bg-purple-500/20 border border-purple-500/30 text-purple-200 rounded-lg text-sm hover:bg-purple-500/30 transition-all"
|
|
96
|
+
>
|
|
97
|
+
<FaBook />
|
|
98
|
+
Live Docs
|
|
99
|
+
</a>
|
|
44
100
|
<a
|
|
45
101
|
href="/swagger"
|
|
46
102
|
target="_blank"
|
|
47
103
|
rel="noopener noreferrer"
|
|
48
|
-
className="inline-flex items-center gap-2 px-3 py-1.5 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
|
|
104
|
+
className="hidden sm:inline-flex items-center gap-2 px-3 py-1.5 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
|
|
49
105
|
>
|
|
50
106
|
<FaBook />
|
|
51
|
-
Docs
|
|
107
|
+
API Docs
|
|
52
108
|
</a>
|
|
53
109
|
<a
|
|
54
110
|
href="https://github.com/MarcosBrendonDePaula/FluxStack"
|
|
55
111
|
target="_blank"
|
|
56
112
|
rel="noopener noreferrer"
|
|
57
|
-
className="inline-flex items-center gap-2 px-3 py-1.5 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
|
|
113
|
+
className="hidden sm:inline-flex items-center gap-2 px-3 py-1.5 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
|
|
58
114
|
>
|
|
59
115
|
<FaGithub />
|
|
60
116
|
GitHub
|
|
61
117
|
</a>
|
|
118
|
+
|
|
119
|
+
{/* Mobile menu toggle */}
|
|
120
|
+
<button
|
|
121
|
+
onClick={() => setMenuOpen(!menuOpen)}
|
|
122
|
+
className="md:hidden p-2 text-gray-300 hover:text-white transition-colors"
|
|
123
|
+
aria-label="Toggle menu"
|
|
124
|
+
>
|
|
125
|
+
{menuOpen ? <FaTimes size={20} /> : <FaBars size={20} />}
|
|
126
|
+
</button>
|
|
62
127
|
</div>
|
|
63
128
|
</div>
|
|
129
|
+
|
|
130
|
+
{/* Mobile nav */}
|
|
131
|
+
{menuOpen && (
|
|
132
|
+
<div className="md:hidden border-t border-white/10 bg-slate-900/90 backdrop-blur-md">
|
|
133
|
+
<nav className="container mx-auto px-4 py-3 flex gap-4 relative">
|
|
134
|
+
<div className="flex flex-col gap-1 flex-1">
|
|
135
|
+
{navItems.map((item) => {
|
|
136
|
+
const active = location.pathname === item.to
|
|
137
|
+
return (
|
|
138
|
+
<Link
|
|
139
|
+
key={item.to}
|
|
140
|
+
to={item.to}
|
|
141
|
+
onClick={() => setMenuOpen(false)}
|
|
142
|
+
className={`px-3 py-2 rounded-lg text-sm transition-all ${
|
|
143
|
+
active
|
|
144
|
+
? 'bg-white/15 text-white'
|
|
145
|
+
: 'text-gray-300 hover:bg-white/10'
|
|
146
|
+
}`}
|
|
147
|
+
>
|
|
148
|
+
{item.label}
|
|
149
|
+
</Link>
|
|
150
|
+
)
|
|
151
|
+
})}
|
|
152
|
+
<div className="flex flex-wrap gap-2 mt-2 pt-2 border-t border-white/10">
|
|
153
|
+
<a
|
|
154
|
+
href="https://live-docs.marcosbrendon.com/"
|
|
155
|
+
target="_blank"
|
|
156
|
+
rel="noopener noreferrer"
|
|
157
|
+
className="inline-flex items-center gap-2 px-3 py-2 bg-purple-500/20 border border-purple-500/30 text-purple-200 rounded-lg text-sm hover:bg-purple-500/30 transition-all"
|
|
158
|
+
>
|
|
159
|
+
<FaBook />
|
|
160
|
+
Live Docs
|
|
161
|
+
</a>
|
|
162
|
+
<a
|
|
163
|
+
href="/swagger"
|
|
164
|
+
target="_blank"
|
|
165
|
+
rel="noopener noreferrer"
|
|
166
|
+
className="inline-flex items-center gap-2 px-3 py-2 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
|
|
167
|
+
>
|
|
168
|
+
<FaBook />
|
|
169
|
+
API Docs
|
|
170
|
+
</a>
|
|
171
|
+
<a
|
|
172
|
+
href="https://github.com/MarcosBrendonDePaula/FluxStack"
|
|
173
|
+
target="_blank"
|
|
174
|
+
rel="noopener noreferrer"
|
|
175
|
+
className="inline-flex items-center gap-2 px-3 py-2 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
|
|
176
|
+
>
|
|
177
|
+
<FaGithub />
|
|
178
|
+
GitHub
|
|
179
|
+
</a>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
{/* Logo floating right */}
|
|
184
|
+
<img
|
|
185
|
+
src={FluxStackLogo}
|
|
186
|
+
alt=""
|
|
187
|
+
className="absolute right-4 top-1/2 -translate-y-1/2 w-40 h-40 opacity-15 pointer-events-none transition-[filter] duration-500"
|
|
188
|
+
style={{ filter: `hue-rotate(${routeFlameHue[location.pathname] || '0deg'})` }}
|
|
189
|
+
/>
|
|
190
|
+
</nav>
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
64
193
|
</header>
|
|
65
194
|
|
|
66
195
|
<Outlet />
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { useNavigate } from 'react-router'
|
|
2
|
-
|
|
3
|
-
export function BackButton() {
|
|
4
|
-
const navigate = useNavigate()
|
|
5
|
-
return (
|
|
6
|
-
<button
|
|
7
|
-
onClick={() => navigate(-1)}
|
|
8
|
-
className="px-4 py-2 bg-white/10 backdrop-blur-sm border border-white/20 text-white rounded-lg font-medium hover:bg-white/20 transition-all"
|
|
9
|
-
>
|
|
10
|
-
← Voltar
|
|
11
|
-
</button>
|
|
12
|
-
)
|
|
13
|
-
}
|
|
1
|
+
import { useNavigate } from 'react-router'
|
|
2
|
+
|
|
3
|
+
export function BackButton() {
|
|
4
|
+
const navigate = useNavigate()
|
|
5
|
+
return (
|
|
6
|
+
<button
|
|
7
|
+
onClick={() => navigate(-1)}
|
|
8
|
+
className="px-4 py-2 bg-white/10 backdrop-blur-sm border border-white/20 text-white rounded-lg font-medium hover:bg-white/20 transition-all"
|
|
9
|
+
>
|
|
10
|
+
← Voltar
|
|
11
|
+
</button>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
@@ -3,15 +3,15 @@ import { BackButton } from './BackButton'
|
|
|
3
3
|
|
|
4
4
|
export function DemoPage({ children, note }: { children: ReactNode; note?: ReactNode }) {
|
|
5
5
|
return (
|
|
6
|
-
<div className="min-h-
|
|
7
|
-
<div className="mb-8">
|
|
6
|
+
<div className="min-h-[calc(100vh-56px)] sm:min-h-[calc(100vh-64px)] flex flex-col items-center justify-center px-3 sm:px-4 py-6 sm:py-8">
|
|
7
|
+
<div className="mb-4 sm:mb-8">
|
|
8
8
|
<BackButton />
|
|
9
9
|
</div>
|
|
10
|
-
<div className="w-full max-w-6xl">
|
|
10
|
+
<div className="w-full max-w-6xl mx-auto flex flex-col items-center">
|
|
11
11
|
{children}
|
|
12
12
|
</div>
|
|
13
13
|
{note && (
|
|
14
|
-
<p className="mt-6 text-gray-400 text-sm max-w-md text-center">
|
|
14
|
+
<p className="mt-4 sm:mt-6 text-gray-400 text-xs sm:text-sm max-w-md text-center px-2">
|
|
15
15
|
{note}
|
|
16
16
|
</p>
|
|
17
17
|
)}
|
|
@@ -27,7 +27,7 @@ function PublicSection() {
|
|
|
27
27
|
})
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
|
-
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
|
|
30
|
+
<div className="bg-white/5 border border-white/10 rounded-xl p-4 sm:p-6">
|
|
31
31
|
<h3 className="text-lg font-semibold text-white mb-1">Contador Público</h3>
|
|
32
32
|
<p className="text-gray-400 text-xs mb-4">Sem autenticação necessária</p>
|
|
33
33
|
|
|
@@ -113,8 +113,8 @@ function AdminSection() {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
return (
|
|
116
|
-
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
|
|
117
|
-
<div className="flex items-center justify-between mb-4">
|
|
116
|
+
<div className="bg-white/5 border border-white/10 rounded-xl p-4 sm:p-6">
|
|
117
|
+
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 mb-4">
|
|
118
118
|
<div>
|
|
119
119
|
<h3 className="text-lg font-semibold text-white">Painel Admin</h3>
|
|
120
120
|
<p className="text-gray-400 text-xs">
|
|
@@ -227,7 +227,7 @@ function AuthControls() {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
return (
|
|
230
|
-
<div className="bg-white/5 border border-white/10 rounded-xl p-6 mb-6">
|
|
230
|
+
<div className="bg-white/5 border border-white/10 rounded-xl p-4 sm:p-6 mb-6">
|
|
231
231
|
<h3 className="text-lg font-semibold text-white mb-2">Autenticação</h3>
|
|
232
232
|
|
|
233
233
|
<div className="flex items-center gap-3 mb-4">
|
|
@@ -237,7 +237,7 @@ function AuthControls() {
|
|
|
237
237
|
</span>
|
|
238
238
|
</div>
|
|
239
239
|
|
|
240
|
-
<div className="flex gap-2">
|
|
240
|
+
<div className="flex flex-col sm:flex-row gap-2">
|
|
241
241
|
<input
|
|
242
242
|
value={token}
|
|
243
243
|
onChange={e => setToken(e.target.value)}
|
|
@@ -245,26 +245,28 @@ function AuthControls() {
|
|
|
245
245
|
placeholder="Token (JWT, API key, etc.)"
|
|
246
246
|
className="flex-1 px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white text-sm placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500/50"
|
|
247
247
|
/>
|
|
248
|
-
<
|
|
249
|
-
onClick={handleLogin}
|
|
250
|
-
disabled={isLoggingIn}
|
|
251
|
-
className="px-4 py-2 rounded-lg bg-emerald-500/20 border border-emerald-500/30 text-emerald-200 text-sm hover:bg-emerald-500/30 disabled:opacity-50"
|
|
252
|
-
>
|
|
253
|
-
{isLoggingIn ? 'Autenticando...' : 'Login'}
|
|
254
|
-
</button>
|
|
255
|
-
{authenticated && (
|
|
248
|
+
<div className="flex gap-2">
|
|
256
249
|
<button
|
|
257
|
-
onClick={
|
|
258
|
-
|
|
250
|
+
onClick={handleLogin}
|
|
251
|
+
disabled={isLoggingIn}
|
|
252
|
+
className="flex-1 sm:flex-initial px-4 py-2 rounded-lg bg-emerald-500/20 border border-emerald-500/30 text-emerald-200 text-sm hover:bg-emerald-500/30 disabled:opacity-50"
|
|
259
253
|
>
|
|
260
|
-
|
|
254
|
+
{isLoggingIn ? 'Autenticando...' : 'Login'}
|
|
261
255
|
</button>
|
|
262
|
-
|
|
256
|
+
{authenticated && (
|
|
257
|
+
<button
|
|
258
|
+
onClick={handleLogout}
|
|
259
|
+
className="flex-1 sm:flex-initial px-4 py-2 rounded-lg bg-red-500/20 border border-red-500/30 text-red-200 text-sm hover:bg-red-500/30"
|
|
260
|
+
>
|
|
261
|
+
Logout
|
|
262
|
+
</button>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
263
265
|
</div>
|
|
264
266
|
|
|
265
267
|
<div className="mt-4 p-3 bg-emerald-500/10 border border-emerald-500/20 rounded-lg">
|
|
266
268
|
<p className="text-emerald-300 text-xs font-semibold mb-2">Tokens de teste (dev only):</p>
|
|
267
|
-
<div className="grid grid-cols-3 gap-2 text-xs">
|
|
269
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2 text-xs">
|
|
268
270
|
<button
|
|
269
271
|
onClick={() => { setToken('admin-token'); }}
|
|
270
272
|
className="px-2 py-1 rounded bg-purple-500/20 text-purple-300 hover:bg-purple-500/30"
|
|
@@ -304,8 +306,8 @@ function AuthControls() {
|
|
|
304
306
|
export function AuthDemo() {
|
|
305
307
|
return (
|
|
306
308
|
<div className="space-y-6 w-full max-w-2xl mx-auto">
|
|
307
|
-
<div className="text-center mb-8">
|
|
308
|
-
<h2 className="text-3xl font-bold text-white mb-2">Live Components Auth</h2>
|
|
309
|
+
<div className="text-center mb-6 sm:mb-8">
|
|
310
|
+
<h2 className="text-2xl sm:text-3xl font-bold text-white mb-2">Live Components Auth</h2>
|
|
309
311
|
<p className="text-gray-400">
|
|
310
312
|
Sistema de autenticação declarativo para componentes real-time
|
|
311
313
|
</p>
|
|
@@ -318,7 +320,7 @@ export function AuthDemo() {
|
|
|
318
320
|
<AdminSection />
|
|
319
321
|
</div>
|
|
320
322
|
|
|
321
|
-
<div className="bg-white/5 border border-white/10 rounded-xl p-6 text-xs text-gray-500 space-y-2">
|
|
323
|
+
<div className="bg-white/5 border border-white/10 rounded-xl p-4 sm:p-6 text-xs text-gray-500 space-y-2 overflow-x-auto">
|
|
322
324
|
<h4 className="text-sm font-semibold text-gray-300 mb-3">Como funciona</h4>
|
|
323
325
|
<p><strong className="text-purple-300">Server:</strong> <code>static auth = { required: true, roles: ['admin'] }</code></p>
|
|
324
326
|
<p><strong className="text-purple-300">Server:</strong> <code>static actionAuth = { deleteUser: { permissions: ['users.delete'] } }</code></p>
|
|
@@ -44,7 +44,7 @@ export function ChatDemo() {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
return (
|
|
47
|
-
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 w-full mx-auto">
|
|
47
|
+
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-4 sm:p-6 md:p-8 w-full max-w-2xl mx-auto">
|
|
48
48
|
<h2 className="text-2xl font-bold text-white mb-2 text-center">Chat Compartilhado</h2>
|
|
49
49
|
<p className="text-gray-400 text-sm text-center mb-4">
|
|
50
50
|
Sala global em tempo real. Abra em várias abas para testar.
|
|
@@ -70,7 +70,7 @@ export function ChatDemo() {
|
|
|
70
70
|
<div
|
|
71
71
|
ref={containerRef}
|
|
72
72
|
onScroll={handleScroll}
|
|
73
|
-
className="bg-black/40 border border-white/10 rounded-xl p-5 h-[28rem] overflow-auto space-y-3"
|
|
73
|
+
className="bg-black/40 border border-white/10 rounded-xl p-3 sm:p-5 h-72 sm:h-96 md:h-[28rem] overflow-auto space-y-3"
|
|
74
74
|
>
|
|
75
75
|
{chat.$state.messages.length === 0 && (
|
|
76
76
|
<div className="text-gray-500 text-sm text-center">Nenhuma mensagem ainda</div>
|
|
@@ -47,16 +47,16 @@ export function CounterDemo() {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
return (
|
|
50
|
-
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 max-w-md w-full">
|
|
51
|
-
<h2 className="text-2xl font-bold text-white mb-2 text-center">
|
|
50
|
+
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-5 sm:p-8 max-w-md w-full flex flex-col">
|
|
51
|
+
<h2 className="text-xl sm:text-2xl font-bold text-white mb-2 text-center">
|
|
52
52
|
{title}
|
|
53
53
|
</h2>
|
|
54
54
|
|
|
55
|
-
<p className="text-gray-400 text-sm text-center mb-6">
|
|
55
|
+
<p className="text-gray-400 text-xs sm:text-sm text-center mb-4 sm:mb-6">
|
|
56
56
|
{description}
|
|
57
57
|
</p>
|
|
58
58
|
|
|
59
|
-
<div className="flex justify-center gap-4 mb-6">
|
|
59
|
+
<div className="flex flex-wrap justify-center gap-2 sm:gap-4 mb-4 sm:mb-6">
|
|
60
60
|
<div className={`flex items-center gap-2 px-3 py-1 rounded-full text-xs ${
|
|
61
61
|
counter.$connected
|
|
62
62
|
? 'bg-emerald-500/20 text-emerald-300'
|
|
@@ -74,8 +74,8 @@ export function CounterDemo() {
|
|
|
74
74
|
</div>
|
|
75
75
|
</div>
|
|
76
76
|
|
|
77
|
-
<div className="text-center mb-8">
|
|
78
|
-
<div className="text-8xl font-bold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
|
|
77
|
+
<div className="text-center mb-6 sm:mb-8 flex-1 flex flex-col justify-center">
|
|
78
|
+
<div className="text-6xl sm:text-8xl font-bold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
|
|
79
79
|
{counter.$state.count}
|
|
80
80
|
</div>
|
|
81
81
|
|
|
@@ -139,16 +139,16 @@ export function CounterDemo() {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
return (
|
|
142
|
-
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 max-w-md w-full">
|
|
143
|
-
<h2 className="text-2xl font-bold text-white mb-2 text-center">
|
|
142
|
+
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-5 sm:p-8 max-w-md w-full flex flex-col">
|
|
143
|
+
<h2 className="text-xl sm:text-2xl font-bold text-white mb-2 text-center">
|
|
144
144
|
Contador Local (sem Room)
|
|
145
145
|
</h2>
|
|
146
|
-
<p className="text-gray-400 text-sm text-center mb-6">
|
|
146
|
+
<p className="text-gray-400 text-xs sm:text-sm text-center mb-4 sm:mb-6">
|
|
147
147
|
Estado local do componente, sem eventos de sala.
|
|
148
148
|
</p>
|
|
149
149
|
|
|
150
|
-
<div className="text-center mb-8">
|
|
151
|
-
<div className="text-8xl font-bold bg-gradient-to-r from-amber-400 via-orange-400 to-rose-400 bg-clip-text text-transparent">
|
|
150
|
+
<div className="text-center mb-6 sm:mb-8 flex-1 flex flex-col justify-center">
|
|
151
|
+
<div className="text-6xl sm:text-8xl font-bold bg-gradient-to-r from-amber-400 via-orange-400 to-rose-400 bg-clip-text text-transparent">
|
|
152
152
|
{localCounter.$state.count}
|
|
153
153
|
</div>
|
|
154
154
|
</div>
|
|
@@ -189,7 +189,7 @@ export function CounterDemo() {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
return (
|
|
192
|
-
<div className="flex flex-col lg:flex-row gap-6 items-
|
|
192
|
+
<div className="flex flex-col lg:flex-row gap-4 sm:gap-6 items-stretch justify-center">
|
|
193
193
|
{renderLocalCounter()}
|
|
194
194
|
{renderCounter(
|
|
195
195
|
'Contador Isolado',
|
|
@@ -9,7 +9,7 @@ export function FormDemo() {
|
|
|
9
9
|
// Sucesso
|
|
10
10
|
if (form.submitted) {
|
|
11
11
|
return (
|
|
12
|
-
<div className="p-6 bg-green-500/20 border border-green-500/30 rounded-xl text-center">
|
|
12
|
+
<div className="p-4 sm:p-6 bg-green-500/20 border border-green-500/30 rounded-xl text-center w-full max-w-xl mx-auto">
|
|
13
13
|
<div className="text-4xl mb-3">✅</div>
|
|
14
14
|
<h2 className="text-xl font-bold text-white mb-2">Enviado!</h2>
|
|
15
15
|
<p className="text-gray-300">Obrigado, <span className="text-green-400">{form.name}</span>!</p>
|
|
@@ -27,7 +27,7 @@ export function FormDemo() {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
|
-
<div className="p-6 bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl">
|
|
30
|
+
<div className="p-4 sm:p-6 bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl w-full max-w-xl mx-auto">
|
|
31
31
|
<div className="flex items-center justify-between mb-6">
|
|
32
32
|
<h2 className="text-xl font-bold text-white">Live Form</h2>
|
|
33
33
|
<span className={`px-3 py-1 rounded-full text-xs ${
|