create-fluxstack 1.20.1 → 1.21.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/resources/live-components.md +103 -57
- package/LLMD/resources/live-rooms.md +187 -88
- package/README.md +27 -25
- package/app/client/.live-stubs/LiveCounter.js +4 -4
- package/app/client/src/App.tsx +11 -12
- package/app/client/src/components/AppLayout.tsx +290 -252
- package/app/client/src/components/BackButton.tsx +16 -13
- package/app/client/src/components/DemoPage.tsx +135 -22
- package/app/client/src/index.css +21 -11
- package/app/client/src/live/AuthDemo.tsx +270 -333
- package/app/client/src/live/CounterDemo.tsx +151 -206
- package/app/client/src/live/FormDemo.tsx +140 -119
- package/app/client/src/live/PingPongDemo.tsx +180 -202
- package/app/client/src/live/RoomChatDemo.tsx +397 -374
- package/app/client/src/pages/HomePage.tsx +170 -104
- package/app/server/live/LiveCounter.ts +71 -68
- package/app/server/live/LiveSharedCounter.ts +18 -12
- package/app/server/live/auto-generated-components.ts +1 -3
- package/app/server/live/rooms/CounterRoom.ts +15 -10
- package/core/client/index.ts +0 -3
- package/core/client/state/createStore.ts +88 -88
- package/core/client/state/index.ts +5 -5
- package/core/server/live/auto-generated-components.ts +1 -3
- package/core/utils/version.ts +1 -1
- package/package.json +1 -1
- package/tsconfig.json +7 -6
- package/app/client/src/components/LiveUploadWidget.tsx +0 -200
- package/app/client/src/live/UploadDemo.tsx +0 -21
- package/app/server/live/LiveUpload.ts +0 -96
- package/core/client/hooks/useLiveUpload.ts +0 -70
|
@@ -1,252 +1,290 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
{ to: '/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{ to: '/
|
|
18
|
-
{ to: '/
|
|
19
|
-
|
|
20
|
-
{ to: '/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
<
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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,13 +1,16 @@
|
|
|
1
|
-
import { useNavigate } from 'react-router'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
}
|