create-fluxstack 1.21.1 → 1.22.1

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.
Files changed (55) hide show
  1. package/app/client/src/App.tsx +9 -11
  2. package/app/client/src/components/AppLayout.tsx +291 -290
  3. package/app/client/src/components/BackButton.tsx +17 -16
  4. package/app/client/src/components/ColorWheel.tsx +1 -0
  5. package/app/client/src/components/DemoPage.tsx +136 -135
  6. package/app/client/src/components/ErrorBoundary.tsx +2 -1
  7. package/app/client/src/components/LiveErrorBoundary.tsx +2 -1
  8. package/app/client/src/components/ThemePicker.tsx +1 -0
  9. package/app/client/src/framework/ClientOnly.tsx +14 -0
  10. package/app/client/src/framework/LivePage.tsx +55 -0
  11. package/app/client/src/framework/RscHomePage.tsx +125 -0
  12. package/app/client/src/framework/RscLink.tsx +31 -0
  13. package/app/client/src/framework/RscNav.tsx +42 -0
  14. package/app/client/src/framework/RscRoot.tsx +54 -0
  15. package/app/client/src/framework/csrf.ts +22 -0
  16. package/app/client/src/framework/entry.browser.tsx +51 -0
  17. package/app/client/src/framework/entry.rsc.tsx +41 -0
  18. package/app/client/src/framework/entry.ssr.tsx +12 -0
  19. package/app/client/src/framework/navigation.ts +29 -0
  20. package/app/client/src/framework/params.tsx +22 -0
  21. package/app/client/src/framework/routes.ts +143 -0
  22. package/app/client/src/framework/vite-rsc.d.ts +21 -0
  23. package/app/client/src/hooks/useThemeClock.ts +16 -1
  24. package/app/client/src/live/AuthDemo.tsx +271 -270
  25. package/app/client/src/live/CounterDemo.tsx +152 -151
  26. package/app/client/src/live/FormDemo.tsx +141 -140
  27. package/app/client/src/live/PingPongDemo.tsx +181 -180
  28. package/app/client/src/live/RoomChatDemo.tsx +398 -397
  29. package/app/client/src/live/SharedCounterDemo.tsx +2 -1
  30. package/app/client/src/pages/ApiTestPage.tsx +1 -0
  31. package/app/client/src/pages/auth.tsx +13 -0
  32. package/app/client/src/pages/blog/[slug].tsx +23 -0
  33. package/app/client/src/pages/counter.tsx +15 -0
  34. package/app/client/src/pages/form.tsx +13 -0
  35. package/app/client/src/pages/index.tsx +9 -0
  36. package/app/client/src/pages/ping-pong.tsx +13 -0
  37. package/app/client/src/pages/room-chat.tsx +13 -0
  38. package/app/client/src/pages/shared-counter.tsx +13 -0
  39. package/app/client/src/pages/sobre.tsx +19 -0
  40. package/app/server/index.ts +5 -1
  41. package/app/server/live/rooms/index.ts +14 -0
  42. package/config/system/plugins.config.ts +12 -2
  43. package/core/cli/commands/dev.ts +9 -1
  44. package/core/plugins/built-in/rsc/index.ts +156 -0
  45. package/core/plugins/built-in/ssr/bun-asset-loader.ts +46 -0
  46. package/core/plugins/built-in/ssr/index.ts +143 -0
  47. package/core/plugins/built-in/ssr/registry.ts +38 -0
  48. package/core/plugins/built-in/vite/index.ts +7 -0
  49. package/core/server/live/websocket-plugin.ts +29 -10
  50. package/core/utils/version.ts +6 -6
  51. package/create-fluxstack.ts +64 -5
  52. package/package.json +112 -108
  53. package/playwright.config.ts +6 -2
  54. package/vite.config.ts +22 -0
  55. package/app/client/.live-stubs/LiveUpload.js +0 -15
@@ -1,290 +1,291 @@
1
- import { useState, useEffect } from 'react'
2
- import type { ReactNode } from 'react'
3
- import { Link, Outlet, useLocation } from 'react-router'
4
- import { FaBars, FaBook, FaChevronDown, FaExternalLinkAlt, FaGithub, FaTimes } from 'react-icons/fa'
5
- import FluxStackLogo from '@client/src/assets/fluxstack.svg'
6
- import faviconSvg from '@client/src/assets/fluxstack-static.svg?raw'
7
- import { useThemeClock } from '../hooks/useThemeClock'
8
- import { ThemePicker } from './ThemePicker'
9
- import type { ColorPalette } from '../lib/theme-clock'
10
- import { themeConfig } from '../config/theme.config'
11
-
12
- const navItems = [
13
- { to: '/', label: 'Home' },
14
- ]
15
-
16
- const demoItems = [
17
- { to: '/counter', label: 'Counter' },
18
- { to: '/form', label: 'Form' },
19
-
20
- { to: '/room-chat', label: 'Room Chat' },
21
- { to: '/auth', label: 'Auth' },
22
- { to: '/ping-pong', label: 'Ping Pong' },
23
- ]
24
-
25
- const allRouteItems = [...navItems, ...demoItems]
26
-
27
- const MAX_FAVICON_CACHE = 20
28
- const faviconUrlCache = new Map<string, string>()
29
-
30
- function HeaderLink({
31
- href,
32
- children,
33
- subtle = false,
34
- }: {
35
- href: string
36
- children: ReactNode
37
- subtle?: boolean
38
- }) {
39
- return (
40
- <a
41
- href={href}
42
- target="_blank"
43
- rel="noopener noreferrer"
44
- className={`hidden h-9 items-center gap-2 rounded-lg border px-3 text-sm transition sm:inline-flex ${
45
- subtle
46
- ? 'border-white/10 bg-white/[0.025] text-gray-400 hover:border-white/20 hover:bg-white/[0.05] hover:text-white'
47
- : 'border-theme-active bg-theme-muted text-theme hover:shadow-theme'
48
- }`}
49
- >
50
- {children}
51
- </a>
52
- )
53
- }
54
-
55
- export function AppLayout() {
56
- const location = useLocation()
57
- const [menuOpen, setMenuOpen] = useState(false)
58
- const [demosOpen, setDemosOpen] = useState(false)
59
- const autoTheme = useThemeClock()
60
- const [overrideTheme, setOverrideTheme] = useState<ColorPalette | null>(null)
61
- const theme = overrideTheme || autoTheme
62
-
63
- useEffect(() => {
64
- setDemosOpen(false)
65
- setMenuOpen(false)
66
- }, [location.pathname])
67
-
68
- useEffect(() => {
69
- const current = allRouteItems.find(item => item.to === location.pathname)
70
- document.title = current ? `${current.label} - FluxStack` : 'FluxStack'
71
-
72
- const hue = `${Math.round(theme.baseHue - 270)}deg`
73
- let url = faviconUrlCache.get(hue)
74
- if (!url) {
75
- if (faviconUrlCache.size >= MAX_FAVICON_CACHE) {
76
- const oldestKey = faviconUrlCache.keys().next().value!
77
- const oldestUrl = faviconUrlCache.get(oldestKey)!
78
- URL.revokeObjectURL(oldestUrl)
79
- faviconUrlCache.delete(oldestKey)
80
- }
81
- const colored = faviconSvg.replace(
82
- '<svg ',
83
- `<svg style="filter: hue-rotate(${hue})" `
84
- )
85
- const blob = new Blob([colored], { type: 'image/svg+xml' })
86
- url = URL.createObjectURL(blob)
87
- faviconUrlCache.set(hue, url)
88
- }
89
- let link = document.querySelector<HTMLLinkElement>('link[rel="icon"]')
90
- if (!link) {
91
- link = document.createElement('link')
92
- link.rel = 'icon'
93
- document.head.appendChild(link)
94
- }
95
- link.type = 'image/svg+xml'
96
- link.href = url
97
- }, [location.pathname, theme.baseHue])
98
-
99
- const activeDemo = demoItems.some(item => item.to === location.pathname)
100
-
101
- return (
102
- <div
103
- className="min-h-screen text-white"
104
- style={{ backgroundColor: `oklch(7% 0.018 ${theme.baseHue})` }}
105
- >
106
- <header className="sticky top-0 z-50 border-b border-white/10 bg-black/45 backdrop-blur-2xl">
107
- <div className="mx-auto flex h-16 max-w-7xl items-center justify-between gap-4 px-4 sm:px-6 lg:px-8">
108
- <Link to="/" className="group flex min-w-0 items-center gap-3">
109
- <span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-white/10 bg-white/[0.04]">
110
- <img
111
- src={FluxStackLogo}
112
- alt="FluxStack"
113
- className="h-6 w-6 transition-[filter] duration-500"
114
- style={{
115
- filter: `hue-rotate(${theme.baseHue - 270}deg) drop-shadow(0 0 8px ${theme.primaryGlow})`,
116
- }}
117
- />
118
- </span>
119
- <span className="truncate text-sm font-semibold tracking-tight text-white">
120
- FluxStack
121
- </span>
122
- </Link>
123
-
124
- <nav className="hidden min-w-0 flex-1 items-center justify-center md:flex">
125
- <div className="relative flex max-w-full items-center gap-1 rounded-lg border border-white/10 bg-white/[0.025] p-1">
126
- {navItems.map((item) => {
127
- const active = location.pathname === item.to
128
- return (
129
- <Link
130
- key={item.to}
131
- to={item.to}
132
- className={`whitespace-nowrap rounded-md px-3 py-1.5 text-sm transition ${
133
- active
134
- ? 'bg-white text-black shadow-sm'
135
- : 'text-gray-400 hover:bg-white/[0.06] hover:text-white'
136
- }`}
137
- >
138
- {item.label}
139
- </Link>
140
- )
141
- })}
142
- <div className="relative">
143
- <button
144
- type="button"
145
- onClick={() => setDemosOpen(open => !open)}
146
- className={`inline-flex items-center gap-2 whitespace-nowrap rounded-md px-3 py-1.5 text-sm transition ${
147
- activeDemo
148
- ? 'bg-white text-black shadow-sm'
149
- : 'text-gray-400 hover:bg-white/[0.06] hover:text-white'
150
- }`}
151
- aria-expanded={demosOpen}
152
- >
153
- Live Demos
154
- <FaChevronDown className={`h-2.5 w-2.5 transition ${demosOpen ? 'rotate-180' : ''}`} />
155
- </button>
156
-
157
- {demosOpen && (
158
- <div className="absolute left-1/2 top-11 z-50 w-72 -translate-x-1/2 rounded-lg border border-white/10 bg-[#07070b]/95 p-2 shadow-2xl shadow-black/40 backdrop-blur-2xl">
159
- <div className="px-3 py-2">
160
- <p className="text-xs font-semibold uppercase tracking-[0.18em] text-gray-500">Live Components</p>
161
- <p className="mt-1 text-xs leading-5 text-gray-400">Demos de estado no servidor, salas e auth.</p>
162
- </div>
163
- <div className="mt-1 grid gap-1">
164
- {demoItems.map((item) => {
165
- const active = location.pathname === item.to
166
- return (
167
- <Link
168
- key={item.to}
169
- to={item.to}
170
- className={`rounded-md px-3 py-2 text-sm transition ${
171
- active
172
- ? 'bg-white text-black'
173
- : 'text-gray-300 hover:bg-white/[0.06] hover:text-white'
174
- }`}
175
- >
176
- {item.label}
177
- </Link>
178
- )
179
- })}
180
- </div>
181
- </div>
182
- )}
183
- </div>
184
- </div>
185
- </nav>
186
-
187
- <div className="flex shrink-0 items-center gap-2">
188
- <HeaderLink href="https://live-docs.marcosbrendon.com/">
189
- <FaBook className="h-3.5 w-3.5" />
190
- Live Docs
191
- <FaExternalLinkAlt className="h-2.5 w-2.5 opacity-60" />
192
- </HeaderLink>
193
- <HeaderLink href="/swagger" subtle>
194
- <FaBook className="h-3.5 w-3.5" />
195
- API
196
- </HeaderLink>
197
- <HeaderLink href="https://github.com/MarcosBrendonDePaula/FluxStack" subtle>
198
- <FaGithub className="h-3.5 w-3.5" />
199
- GitHub
200
- </HeaderLink>
201
-
202
- <button
203
- onClick={() => setMenuOpen(!menuOpen)}
204
- className="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-white/10 bg-white/[0.03] text-gray-300 transition hover:bg-white/[0.06] hover:text-white md:hidden"
205
- aria-label="Toggle menu"
206
- >
207
- {menuOpen ? <FaTimes size={16} /> : <FaBars size={16} />}
208
- </button>
209
- </div>
210
- </div>
211
-
212
- {menuOpen && (
213
- <div className="border-t border-white/10 bg-black/70 backdrop-blur-2xl md:hidden">
214
- <nav className="mx-auto grid max-w-7xl gap-1 px-4 py-3 sm:px-6">
215
- {navItems.map((item) => {
216
- const active = location.pathname === item.to
217
- return (
218
- <Link
219
- key={item.to}
220
- to={item.to}
221
- onClick={() => setMenuOpen(false)}
222
- className={`rounded-lg px-3 py-2 text-sm transition ${
223
- active
224
- ? 'bg-white text-black'
225
- : 'text-gray-400 hover:bg-white/[0.06] hover:text-white'
226
- }`}
227
- >
228
- {item.label}
229
- </Link>
230
- )
231
- })}
232
- <div className="mt-2 border-t border-white/10 pt-3">
233
- <p className="px-3 pb-2 text-xs font-semibold uppercase tracking-[0.18em] text-gray-500">
234
- Live Demos
235
- </p>
236
- <div className="grid gap-1">
237
- {demoItems.map((item) => {
238
- const active = location.pathname === item.to
239
- return (
240
- <Link
241
- key={item.to}
242
- to={item.to}
243
- onClick={() => setMenuOpen(false)}
244
- className={`rounded-lg px-3 py-2 text-sm transition ${
245
- active
246
- ? 'bg-white text-black'
247
- : 'text-gray-400 hover:bg-white/[0.06] hover:text-white'
248
- }`}
249
- >
250
- {item.label}
251
- </Link>
252
- )
253
- })}
254
- </div>
255
- </div>
256
- <div className="mt-2 grid gap-2 border-t border-white/10 pt-3 sm:grid-cols-3">
257
- <a href="https://live-docs.marcosbrendon.com/" target="_blank" rel="noopener noreferrer" className="rounded-lg border border-theme-active bg-theme-muted px-3 py-2 text-sm text-theme">
258
- Live Docs
259
- </a>
260
- <a href="/swagger" target="_blank" rel="noopener noreferrer" className="rounded-lg border border-white/10 bg-white/[0.03] px-3 py-2 text-sm text-gray-300">
261
- API Docs
262
- </a>
263
- <a href="https://github.com/MarcosBrendonDePaula/FluxStack" target="_blank" rel="noopener noreferrer" className="rounded-lg border border-white/10 bg-white/[0.03] px-3 py-2 text-sm text-gray-300">
264
- GitHub
265
- </a>
266
- </div>
267
- </nav>
268
- </div>
269
- )}
270
- </header>
271
-
272
- <main className="min-h-[calc(100vh-128px)]">
273
- <Outlet />
274
- </main>
275
-
276
- <footer className="border-t border-white/10 bg-black/20 py-5">
277
- <div className="mx-auto flex max-w-7xl flex-col items-center justify-between gap-2 px-4 text-center sm:flex-row sm:px-6 lg:px-8">
278
- <p className="text-xs text-gray-500">
279
- Built with <span style={{ color: theme.primary }}>FluxStack</span> - Bun + Elysia + React
280
- </p>
281
- <p className="text-xs text-gray-600">
282
- <span style={{ color: theme.primary }}>{theme.period}</span> palette
283
- </p>
284
- </div>
285
- </footer>
286
-
287
- {themeConfig.showPicker && <ThemePicker palette={theme} onOverride={setOverrideTheme} />}
288
- </div>
289
- )
290
- }
1
+ 'use client'
2
+ import { useState, useEffect } from 'react'
3
+ import type { ReactNode } from 'react'
4
+ import { Link, Outlet, useLocation } from 'react-router'
5
+ import { FaBars, FaBook, FaChevronDown, FaExternalLinkAlt, FaGithub, FaTimes } from 'react-icons/fa'
6
+ import FluxStackLogo from '@client/src/assets/fluxstack.svg'
7
+ import faviconSvg from '@client/src/assets/fluxstack-static.svg?raw'
8
+ import { useThemeClock } from '../hooks/useThemeClock'
9
+ import { ThemePicker } from './ThemePicker'
10
+ import type { ColorPalette } from '../lib/theme-clock'
11
+ import { themeConfig } from '../config/theme.config'
12
+
13
+ const navItems = [
14
+ { to: '/', label: 'Home' },
15
+ ]
16
+
17
+ const demoItems = [
18
+ { to: '/counter', label: 'Counter' },
19
+ { to: '/form', label: 'Form' },
20
+
21
+ { to: '/room-chat', label: 'Room Chat' },
22
+ { to: '/auth', label: 'Auth' },
23
+ { to: '/ping-pong', label: 'Ping Pong' },
24
+ ]
25
+
26
+ const allRouteItems = [...navItems, ...demoItems]
27
+
28
+ const MAX_FAVICON_CACHE = 20
29
+ const faviconUrlCache = new Map<string, string>()
30
+
31
+ function HeaderLink({
32
+ href,
33
+ children,
34
+ subtle = false,
35
+ }: {
36
+ href: string
37
+ children: ReactNode
38
+ subtle?: boolean
39
+ }) {
40
+ return (
41
+ <a
42
+ href={href}
43
+ target="_blank"
44
+ rel="noopener noreferrer"
45
+ className={`hidden h-9 items-center gap-2 rounded-lg border px-3 text-sm transition sm:inline-flex ${
46
+ subtle
47
+ ? 'border-white/10 bg-white/[0.025] text-gray-400 hover:border-white/20 hover:bg-white/[0.05] hover:text-white'
48
+ : 'border-theme-active bg-theme-muted text-theme hover:shadow-theme'
49
+ }`}
50
+ >
51
+ {children}
52
+ </a>
53
+ )
54
+ }
55
+
56
+ export function AppLayout() {
57
+ const location = useLocation()
58
+ const [menuOpen, setMenuOpen] = useState(false)
59
+ const [demosOpen, setDemosOpen] = useState(false)
60
+ const autoTheme = useThemeClock()
61
+ const [overrideTheme, setOverrideTheme] = useState<ColorPalette | null>(null)
62
+ const theme = overrideTheme || autoTheme
63
+
64
+ useEffect(() => {
65
+ setDemosOpen(false)
66
+ setMenuOpen(false)
67
+ }, [location.pathname])
68
+
69
+ useEffect(() => {
70
+ const current = allRouteItems.find(item => item.to === location.pathname)
71
+ document.title = current ? `${current.label} - FluxStack` : 'FluxStack'
72
+
73
+ const hue = `${Math.round(theme.baseHue - 270)}deg`
74
+ let url = faviconUrlCache.get(hue)
75
+ if (!url) {
76
+ if (faviconUrlCache.size >= MAX_FAVICON_CACHE) {
77
+ const oldestKey = faviconUrlCache.keys().next().value!
78
+ const oldestUrl = faviconUrlCache.get(oldestKey)!
79
+ URL.revokeObjectURL(oldestUrl)
80
+ faviconUrlCache.delete(oldestKey)
81
+ }
82
+ const colored = faviconSvg.replace(
83
+ '<svg ',
84
+ `<svg style="filter: hue-rotate(${hue})" `
85
+ )
86
+ const blob = new Blob([colored], { type: 'image/svg+xml' })
87
+ url = URL.createObjectURL(blob)
88
+ faviconUrlCache.set(hue, url)
89
+ }
90
+ let link = document.querySelector<HTMLLinkElement>('link[rel="icon"]')
91
+ if (!link) {
92
+ link = document.createElement('link')
93
+ link.rel = 'icon'
94
+ document.head.appendChild(link)
95
+ }
96
+ link.type = 'image/svg+xml'
97
+ link.href = url
98
+ }, [location.pathname, theme.baseHue])
99
+
100
+ const activeDemo = demoItems.some(item => item.to === location.pathname)
101
+
102
+ return (
103
+ <div
104
+ className="min-h-screen text-white"
105
+ style={{ backgroundColor: `oklch(7% 0.018 ${theme.baseHue})` }}
106
+ >
107
+ <header className="sticky top-0 z-50 border-b border-white/10 bg-black/45 backdrop-blur-2xl">
108
+ <div className="mx-auto flex h-16 max-w-7xl items-center justify-between gap-4 px-4 sm:px-6 lg:px-8">
109
+ <Link to="/" className="group flex min-w-0 items-center gap-3">
110
+ <span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-white/10 bg-white/[0.04]">
111
+ <img
112
+ src={FluxStackLogo}
113
+ alt="FluxStack"
114
+ className="h-6 w-6 transition-[filter] duration-500"
115
+ style={{
116
+ filter: `hue-rotate(${theme.baseHue - 270}deg) drop-shadow(0 0 8px ${theme.primaryGlow})`,
117
+ }}
118
+ />
119
+ </span>
120
+ <span className="truncate text-sm font-semibold tracking-tight text-white">
121
+ FluxStack
122
+ </span>
123
+ </Link>
124
+
125
+ <nav className="hidden min-w-0 flex-1 items-center justify-center md:flex">
126
+ <div className="relative flex max-w-full items-center gap-1 rounded-lg border border-white/10 bg-white/[0.025] p-1">
127
+ {navItems.map((item) => {
128
+ const active = location.pathname === item.to
129
+ return (
130
+ <Link
131
+ key={item.to}
132
+ to={item.to}
133
+ className={`whitespace-nowrap rounded-md px-3 py-1.5 text-sm transition ${
134
+ active
135
+ ? 'bg-white text-black shadow-sm'
136
+ : 'text-gray-400 hover:bg-white/[0.06] hover:text-white'
137
+ }`}
138
+ >
139
+ {item.label}
140
+ </Link>
141
+ )
142
+ })}
143
+ <div className="relative">
144
+ <button
145
+ type="button"
146
+ onClick={() => setDemosOpen(open => !open)}
147
+ className={`inline-flex items-center gap-2 whitespace-nowrap rounded-md px-3 py-1.5 text-sm transition ${
148
+ activeDemo
149
+ ? 'bg-white text-black shadow-sm'
150
+ : 'text-gray-400 hover:bg-white/[0.06] hover:text-white'
151
+ }`}
152
+ aria-expanded={demosOpen}
153
+ >
154
+ Live Demos
155
+ <FaChevronDown className={`h-2.5 w-2.5 transition ${demosOpen ? 'rotate-180' : ''}`} />
156
+ </button>
157
+
158
+ {demosOpen && (
159
+ <div className="absolute left-1/2 top-11 z-50 w-72 -translate-x-1/2 rounded-lg border border-white/10 bg-[#07070b]/95 p-2 shadow-2xl shadow-black/40 backdrop-blur-2xl">
160
+ <div className="px-3 py-2">
161
+ <p className="text-xs font-semibold uppercase tracking-[0.18em] text-gray-500">Live Components</p>
162
+ <p className="mt-1 text-xs leading-5 text-gray-400">Demos de estado no servidor, salas e auth.</p>
163
+ </div>
164
+ <div className="mt-1 grid gap-1">
165
+ {demoItems.map((item) => {
166
+ const active = location.pathname === item.to
167
+ return (
168
+ <Link
169
+ key={item.to}
170
+ to={item.to}
171
+ className={`rounded-md px-3 py-2 text-sm transition ${
172
+ active
173
+ ? 'bg-white text-black'
174
+ : 'text-gray-300 hover:bg-white/[0.06] hover:text-white'
175
+ }`}
176
+ >
177
+ {item.label}
178
+ </Link>
179
+ )
180
+ })}
181
+ </div>
182
+ </div>
183
+ )}
184
+ </div>
185
+ </div>
186
+ </nav>
187
+
188
+ <div className="flex shrink-0 items-center gap-2">
189
+ <HeaderLink href="https://live-docs.marcosbrendon.com/">
190
+ <FaBook className="h-3.5 w-3.5" />
191
+ Live Docs
192
+ <FaExternalLinkAlt className="h-2.5 w-2.5 opacity-60" />
193
+ </HeaderLink>
194
+ <HeaderLink href="/swagger" subtle>
195
+ <FaBook className="h-3.5 w-3.5" />
196
+ API
197
+ </HeaderLink>
198
+ <HeaderLink href="https://github.com/MarcosBrendonDePaula/FluxStack" subtle>
199
+ <FaGithub className="h-3.5 w-3.5" />
200
+ GitHub
201
+ </HeaderLink>
202
+
203
+ <button
204
+ onClick={() => setMenuOpen(!menuOpen)}
205
+ className="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-white/10 bg-white/[0.03] text-gray-300 transition hover:bg-white/[0.06] hover:text-white md:hidden"
206
+ aria-label="Toggle menu"
207
+ >
208
+ {menuOpen ? <FaTimes size={16} /> : <FaBars size={16} />}
209
+ </button>
210
+ </div>
211
+ </div>
212
+
213
+ {menuOpen && (
214
+ <div className="border-t border-white/10 bg-black/70 backdrop-blur-2xl md:hidden">
215
+ <nav className="mx-auto grid max-w-7xl gap-1 px-4 py-3 sm:px-6">
216
+ {navItems.map((item) => {
217
+ const active = location.pathname === item.to
218
+ return (
219
+ <Link
220
+ key={item.to}
221
+ to={item.to}
222
+ onClick={() => setMenuOpen(false)}
223
+ className={`rounded-lg px-3 py-2 text-sm transition ${
224
+ active
225
+ ? 'bg-white text-black'
226
+ : 'text-gray-400 hover:bg-white/[0.06] hover:text-white'
227
+ }`}
228
+ >
229
+ {item.label}
230
+ </Link>
231
+ )
232
+ })}
233
+ <div className="mt-2 border-t border-white/10 pt-3">
234
+ <p className="px-3 pb-2 text-xs font-semibold uppercase tracking-[0.18em] text-gray-500">
235
+ Live Demos
236
+ </p>
237
+ <div className="grid gap-1">
238
+ {demoItems.map((item) => {
239
+ const active = location.pathname === item.to
240
+ return (
241
+ <Link
242
+ key={item.to}
243
+ to={item.to}
244
+ onClick={() => setMenuOpen(false)}
245
+ className={`rounded-lg px-3 py-2 text-sm transition ${
246
+ active
247
+ ? 'bg-white text-black'
248
+ : 'text-gray-400 hover:bg-white/[0.06] hover:text-white'
249
+ }`}
250
+ >
251
+ {item.label}
252
+ </Link>
253
+ )
254
+ })}
255
+ </div>
256
+ </div>
257
+ <div className="mt-2 grid gap-2 border-t border-white/10 pt-3 sm:grid-cols-3">
258
+ <a href="https://live-docs.marcosbrendon.com/" target="_blank" rel="noopener noreferrer" className="rounded-lg border border-theme-active bg-theme-muted px-3 py-2 text-sm text-theme">
259
+ Live Docs
260
+ </a>
261
+ <a href="/swagger" target="_blank" rel="noopener noreferrer" className="rounded-lg border border-white/10 bg-white/[0.03] px-3 py-2 text-sm text-gray-300">
262
+ API Docs
263
+ </a>
264
+ <a href="https://github.com/MarcosBrendonDePaula/FluxStack" target="_blank" rel="noopener noreferrer" className="rounded-lg border border-white/10 bg-white/[0.03] px-3 py-2 text-sm text-gray-300">
265
+ GitHub
266
+ </a>
267
+ </div>
268
+ </nav>
269
+ </div>
270
+ )}
271
+ </header>
272
+
273
+ <main className="min-h-[calc(100vh-128px)]">
274
+ <Outlet />
275
+ </main>
276
+
277
+ <footer className="border-t border-white/10 bg-black/20 py-5">
278
+ <div className="mx-auto flex max-w-7xl flex-col items-center justify-between gap-2 px-4 text-center sm:flex-row sm:px-6 lg:px-8">
279
+ <p className="text-xs text-gray-500">
280
+ Built with <span style={{ color: theme.primary }}>FluxStack</span> - Bun + Elysia + React
281
+ </p>
282
+ <p className="text-xs text-gray-600">
283
+ <span style={{ color: theme.primary }}>{theme.period}</span> palette
284
+ </p>
285
+ </div>
286
+ </footer>
287
+
288
+ {themeConfig.showPicker && <ThemePicker palette={theme} onOverride={setOverrideTheme} />}
289
+ </div>
290
+ )
291
+ }
@@ -1,16 +1,17 @@
1
- import { useNavigate } from 'react-router'
2
- import { FaArrowLeft } from 'react-icons/fa6'
3
-
4
- export function BackButton() {
5
- const navigate = useNavigate()
6
-
7
- return (
8
- <button
9
- onClick={() => navigate(-1)}
10
- className="inline-flex items-center gap-2 rounded-lg border border-white/10 bg-white/[0.03] px-3 py-2 text-sm font-medium text-gray-300 transition hover:border-white/20 hover:bg-white/[0.06] hover:text-white"
11
- >
12
- <FaArrowLeft className="h-3 w-3" />
13
- Back
14
- </button>
15
- )
16
- }
1
+ 'use client'
2
+ import { useNavigate } from 'react-router'
3
+ import { FaArrowLeft } from 'react-icons/fa6'
4
+
5
+ export function BackButton() {
6
+ const navigate = useNavigate()
7
+
8
+ return (
9
+ <button
10
+ onClick={() => navigate(-1)}
11
+ className="inline-flex items-center gap-2 rounded-lg border border-white/10 bg-white/[0.03] px-3 py-2 text-sm font-medium text-gray-300 transition hover:border-white/20 hover:bg-white/[0.06] hover:text-white"
12
+ >
13
+ <FaArrowLeft className="h-3 w-3" />
14
+ Back
15
+ </button>
16
+ )
17
+ }
@@ -1,3 +1,4 @@
1
+ 'use client'
1
2
  /**
2
3
  * ColorWheel — Interactive color wheel like Adobe Color
3
4
  *