create-rue 0.0.14 → 0.0.15

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.
@@ -80,7 +80,10 @@ const themeLabels: Record<string, string> = {
80
80
  sunset: '日落',
81
81
  }
82
82
 
83
- const getStoredTheme = () => localStorage.getItem(themeStorageKey) || 'light'
83
+ const getStoredTheme = () => {
84
+ const storedTheme = localStorage.getItem(themeStorageKey)
85
+ return storedTheme && themes.includes(storedTheme) ? storedTheme : 'light'
86
+ }
84
87
 
85
88
  const getStoredHeaderHidden = () => localStorage.getItem(reportHeaderStorageKey) === '1'
86
89
 
@@ -91,20 +94,48 @@ const applyTheme = (theme: string) => {
91
94
  const ThemePicker: FC = () => {
92
95
  const theme = ref(getStoredTheme())
93
96
 
97
+ const applyThemeState = () => {
98
+ const currentTheme = theme.value
99
+ const themePicker = document.querySelector<HTMLSelectElement>('[data-base-theme-picker="true"]')
100
+
101
+ applyTheme(currentTheme)
102
+
103
+ if (themePicker && themePicker.value !== currentTheme) {
104
+ themePicker.value = currentTheme
105
+ }
106
+ }
107
+
108
+ const syncStoredTheme = () => {
109
+ theme.value = getStoredTheme()
110
+ requestAnimationFrame(applyThemeState)
111
+ }
112
+
94
113
  useEffect(() => {
95
- applyTheme(theme.value)
96
- })
114
+ localStorage.setItem(themeStorageKey, theme.value)
115
+ requestAnimationFrame(applyThemeState)
116
+
117
+ window.addEventListener('storage', syncStoredTheme)
118
+ window.addEventListener('focus', syncStoredTheme)
119
+ document.addEventListener('visibilitychange', syncStoredTheme)
120
+
121
+ return () => {
122
+ window.removeEventListener('storage', syncStoredTheme)
123
+ window.removeEventListener('focus', syncStoredTheme)
124
+ document.removeEventListener('visibilitychange', syncStoredTheme)
125
+ }
126
+ }, [])
97
127
 
98
128
  watch(
99
129
  () => theme.value,
100
130
  () => {
101
- applyTheme(theme.value)
102
131
  localStorage.setItem(themeStorageKey, theme.value)
132
+ requestAnimationFrame(applyThemeState)
103
133
  },
104
134
  )
105
135
 
106
136
  return (
107
137
  <select
138
+ data-base-theme-picker="true"
108
139
  aria-label="切换主题"
109
140
  className="select select-bordered select-sm w-40"
110
141
  onChange={(event: Event) => {
@@ -112,7 +143,7 @@ const ThemePicker: FC = () => {
112
143
  }}
113
144
  >
114
145
  {themes.map((name) => (
115
- <option key={name} value={name} selected={theme.value === name}>
146
+ <option key={name} value={name}>
116
147
  {themeLabels[name] || name}
117
148
  </option>
118
149
  ))}
@@ -123,11 +154,34 @@ const ThemePicker: FC = () => {
123
154
  const Header: FC = () => {
124
155
  const route = useRoute()
125
156
  const currentPath = computed(() => route.get()?.path || '/')
157
+ const navClass = (path: string) =>
158
+ `btn btn-sm ${currentPath.get() === path ? 'btn-primary' : 'btn-ghost'}`.trim()
159
+
160
+ const applyHeaderNavState = () => {
161
+ const path = currentPath.get()
162
+ const navLinks = document.querySelectorAll<HTMLElement>('[data-base-nav-path]')
163
+
164
+ navLinks.forEach((link) => {
165
+ const targetPath = link.dataset.baseNavPath || ''
166
+ link.className = `btn btn-sm ${path === targetPath ? 'btn-primary' : 'btn-ghost'}`.trim()
167
+ })
168
+ }
169
+
170
+ watch(
171
+ () => currentPath.get(),
172
+ () => {
173
+ requestAnimationFrame(applyHeaderNavState)
174
+ },
175
+ )
176
+
177
+ useEffect(() => {
178
+ requestAnimationFrame(applyHeaderNavState)
179
+ }, [])
126
180
 
127
181
  return (
128
- <header className="sticky top-0 z-40 border-b border-base-300/70 bg-base-100/85 backdrop-blur">
129
- <div className="content-width navbar min-h-0 gap-4 py-3">
130
- <div className="navbar-start min-w-0">
182
+ <header className="sticky top-0 z-40 border-b border-base-300/70 bg-base-100/90 backdrop-blur">
183
+ <div className="content-width flex flex-wrap items-center gap-3 py-3">
184
+ <div className="min-w-0 flex-1 md:flex-none">
131
185
  <RouterLink to="/" className="flex min-w-0 items-center gap-3">
132
186
  <div className="flex h-10 w-10 items-center justify-center rounded-2xl bg-primary text-primary-content shadow-lg shadow-primary/20">
133
187
  R
@@ -137,18 +191,19 @@ const Header: FC = () => {
137
191
  </div>
138
192
  </RouterLink>
139
193
  </div>
140
- <nav className="navbar-center hidden flex-1 justify-center gap-2 md:flex">
194
+ <nav className="order-3 flex w-full flex-wrap gap-2 md:order-none md:w-auto md:flex-1 md:justify-center">
141
195
  {navItems.map((item) => (
142
196
  <RouterLink
143
197
  key={item.to}
144
198
  to={item.to}
145
- className={`btn btn-sm ${currentPath.get() === item.to ? 'btn-primary' : 'btn-ghost'}`}
199
+ data-base-nav-path={item.to}
200
+ className={navClass(item.to)}
146
201
  >
147
- {item.label}
202
+ <span>{item.label}</span>
148
203
  </RouterLink>
149
204
  ))}
150
205
  </nav>
151
- <div className="navbar-end flex items-center gap-2">
206
+ <div className="flex items-center gap-2 md:ml-auto">
152
207
  <ThemePicker />
153
208
  </div>
154
209
  </div>
@@ -175,8 +230,8 @@ const HeaderToggleButton: FC<{ hidden: boolean; onToggle: () => void }> = (props
175
230
  <button
176
231
  type="button"
177
232
  className="btn btn-circle btn-sm fixed left-3 top-3 z-50 border-base-300 bg-base-100/90 shadow-lg backdrop-blur"
178
- aria-label={props.hidden ? '显示顶部导航' : '隐藏顶部导航'}
179
- title={props.hidden ? '显示顶部导航' : '隐藏顶部导航'}
233
+ aria-label={props.hidden ? '显示头部和底部' : '隐藏头部和底部'}
234
+ title={props.hidden ? '显示头部和底部' : '隐藏头部和底部'}
180
235
  onClick={props.onToggle}
181
236
  >
182
237
  {props.hidden ? '显' : '隐'}
@@ -185,10 +240,10 @@ const HeaderToggleButton: FC<{ hidden: boolean; onToggle: () => void }> = (props
185
240
 
186
241
  const SiteLayout: FC = (props) => {
187
242
  const route = useRoute()
188
- const reportHeaderHidden = ref(getStoredHeaderHidden())
189
243
  const currentPath = computed(() => route.get()?.path || '/')
190
- const isReportRoute = computed(() => currentPath.get() === '/report')
191
- const hideHeader = computed(() => isReportRoute.get() && reportHeaderHidden.value)
244
+ const allowHeaderToggle = computed(() => currentPath.get() === '/report')
245
+ const reportHeaderHidden = ref(getStoredHeaderHidden())
246
+ const hideHeader = computed(() => allowHeaderToggle.get() && reportHeaderHidden.value)
192
247
 
193
248
  watch(
194
249
  () => reportHeaderHidden.value,
@@ -201,7 +256,7 @@ const SiteLayout: FC = (props) => {
201
256
  <div className="app-shell">
202
257
  <div className="app-surface">
203
258
  {!hideHeader.get() && <Header />}
204
- {isReportRoute.get() && (
259
+ {allowHeaderToggle.get() && (
205
260
  <HeaderToggleButton
206
261
  hidden={hideHeader.get()}
207
262
  onToggle={() => {
@@ -209,12 +264,10 @@ const SiteLayout: FC = (props) => {
209
264
  }}
210
265
  />
211
266
  )}
212
- <main
213
- className={`content-width pb-10 ${hideHeader.get() ? 'pt-6 sm:pt-8' : 'pt-6 sm:pt-8'}`}
214
- >
267
+ <main className="content-width pb-10 pt-6 sm:pt-8">
215
268
  {props.children}
216
269
  </main>
217
- <Footer />
270
+ {!hideHeader.get() && <Footer />}
218
271
  </div>
219
272
  </div>
220
273
  )