create-rue 0.8.0 → 0.9.7

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.
@@ -2,7 +2,7 @@ import { computed, type FC, ref, useEffect, watch } from '@rue-js/rue'
2
2
  import { RouterLink, useRoute } from '@rue-js/router'
3
3
 
4
4
  const themeStorageKey = 'rue.base.theme'
5
- const reportHeaderStorageKey = 'rue.base.report.hideHeader'
5
+ const headerToggleRoutes = ['/', '/report', '/todo']
6
6
 
7
7
  const navItems = [
8
8
  { to: '/', label: '首页' },
@@ -80,9 +80,21 @@ 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
+ }
87
+
88
+ const getHeaderStorageKey = (path: string) => {
89
+ if (path === '/') {
90
+ return 'rue.base.header.hide.home'
91
+ }
92
+
93
+ return `rue.base.header.hide.${path.replace(/^\//, '')}`
94
+ }
84
95
 
85
- const getStoredHeaderHidden = () => localStorage.getItem(reportHeaderStorageKey) === '1'
96
+ const getStoredHeaderHidden = (path: string) =>
97
+ localStorage.getItem(getHeaderStorageKey(path)) === '1'
86
98
 
87
99
  const applyTheme = (theme: string) => {
88
100
  document.documentElement.setAttribute('data-theme', theme)
@@ -91,20 +103,48 @@ const applyTheme = (theme: string) => {
91
103
  const ThemePicker: FC = () => {
92
104
  const theme = ref(getStoredTheme())
93
105
 
106
+ const applyThemeState = () => {
107
+ const currentTheme = theme.value
108
+ const themePicker = document.querySelector<HTMLSelectElement>('[data-base-theme-picker="true"]')
109
+
110
+ applyTheme(currentTheme)
111
+
112
+ if (themePicker && themePicker.value !== currentTheme) {
113
+ themePicker.value = currentTheme
114
+ }
115
+ }
116
+
117
+ const syncStoredTheme = () => {
118
+ theme.value = getStoredTheme()
119
+ requestAnimationFrame(applyThemeState)
120
+ }
121
+
94
122
  useEffect(() => {
95
- applyTheme(theme.value)
96
- })
123
+ localStorage.setItem(themeStorageKey, theme.value)
124
+ requestAnimationFrame(applyThemeState)
125
+
126
+ window.addEventListener('storage', syncStoredTheme)
127
+ window.addEventListener('focus', syncStoredTheme)
128
+ document.addEventListener('visibilitychange', syncStoredTheme)
129
+
130
+ return () => {
131
+ window.removeEventListener('storage', syncStoredTheme)
132
+ window.removeEventListener('focus', syncStoredTheme)
133
+ document.removeEventListener('visibilitychange', syncStoredTheme)
134
+ }
135
+ }, [])
97
136
 
98
137
  watch(
99
138
  () => theme.value,
100
139
  () => {
101
- applyTheme(theme.value)
102
140
  localStorage.setItem(themeStorageKey, theme.value)
141
+ requestAnimationFrame(applyThemeState)
103
142
  },
104
143
  )
105
144
 
106
145
  return (
107
146
  <select
147
+ data-base-theme-picker="true"
108
148
  aria-label="切换主题"
109
149
  className="select select-bordered select-sm w-40"
110
150
  onChange={(event: Event) => {
@@ -112,7 +152,7 @@ const ThemePicker: FC = () => {
112
152
  }}
113
153
  >
114
154
  {themes.map((name) => (
115
- <option key={name} value={name} selected={theme.value === name}>
155
+ <option key={name} value={name}>
116
156
  {themeLabels[name] || name}
117
157
  </option>
118
158
  ))}
@@ -123,11 +163,34 @@ const ThemePicker: FC = () => {
123
163
  const Header: FC = () => {
124
164
  const route = useRoute()
125
165
  const currentPath = computed(() => route.get()?.path || '/')
166
+ const navClass = (path: string) =>
167
+ `btn btn-sm ${currentPath.get() === path ? 'btn-primary' : 'btn-ghost'}`.trim()
168
+
169
+ const applyHeaderNavState = () => {
170
+ const path = currentPath.get()
171
+ const navLinks = document.querySelectorAll<HTMLElement>('[data-base-nav-path]')
172
+
173
+ navLinks.forEach((link) => {
174
+ const targetPath = link.dataset.baseNavPath || ''
175
+ link.className = `btn btn-sm ${path === targetPath ? 'btn-primary' : 'btn-ghost'}`.trim()
176
+ })
177
+ }
178
+
179
+ watch(
180
+ () => currentPath.get(),
181
+ () => {
182
+ requestAnimationFrame(applyHeaderNavState)
183
+ },
184
+ )
185
+
186
+ useEffect(() => {
187
+ requestAnimationFrame(applyHeaderNavState)
188
+ }, [])
126
189
 
127
190
  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">
191
+ <header className="sticky top-0 z-40 border-b border-base-300/70 bg-base-100/90 backdrop-blur">
192
+ <div className="content-width flex flex-wrap items-center gap-3 py-3">
193
+ <div className="min-w-0 flex-1 md:flex-none">
131
194
  <RouterLink to="/" className="flex min-w-0 items-center gap-3">
132
195
  <div className="flex h-10 w-10 items-center justify-center rounded-2xl bg-primary text-primary-content shadow-lg shadow-primary/20">
133
196
  R
@@ -137,18 +200,19 @@ const Header: FC = () => {
137
200
  </div>
138
201
  </RouterLink>
139
202
  </div>
140
- <nav className="navbar-center hidden flex-1 justify-center gap-2 md:flex">
203
+ <nav className="order-3 flex w-full flex-wrap gap-2 md:order-none md:w-auto md:flex-1 md:justify-center">
141
204
  {navItems.map((item) => (
142
205
  <RouterLink
143
206
  key={item.to}
144
207
  to={item.to}
145
- className={`btn btn-sm ${currentPath.get() === item.to ? 'btn-primary' : 'btn-ghost'}`}
208
+ data-base-nav-path={item.to}
209
+ className={navClass(item.to)}
146
210
  >
147
- {item.label}
211
+ <span>{item.label}</span>
148
212
  </RouterLink>
149
213
  ))}
150
214
  </nav>
151
- <div className="navbar-end flex items-center gap-2">
215
+ <div className="flex items-center gap-2 md:ml-auto">
152
216
  <ThemePicker />
153
217
  </div>
154
218
  </div>
@@ -174,26 +238,72 @@ const Footer: FC = () => (
174
238
  const HeaderToggleButton: FC<{ hidden: boolean; onToggle: () => void }> = (props) => (
175
239
  <button
176
240
  type="button"
177
- 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 ? '显示顶部导航' : '隐藏顶部导航'}
241
+ className="btn btn-circle btn-sm fixed left-3 top-3 z-50 border-base-300 bg-base-100/95 text-base-content shadow-lg backdrop-blur"
242
+ aria-label={props.hidden ? '显示头部和底部' : '隐藏头部和底部'}
243
+ title={props.hidden ? '显示头部和底部' : '隐藏头部和底部'}
180
244
  onClick={props.onToggle}
181
245
  >
182
- {props.hidden ? '显' : '隐'}
246
+ {props.hidden ? (
247
+ <svg
248
+ viewBox="0 0 24 24"
249
+ width="16"
250
+ height="16"
251
+ aria-hidden="true"
252
+ fill="none"
253
+ stroke="currentColor"
254
+ stroke-width="2"
255
+ stroke-linecap="round"
256
+ stroke-linejoin="round"
257
+ >
258
+ <path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z" />
259
+ <circle cx="12" cy="12" r="3" />
260
+ </svg>
261
+ ) : (
262
+ <svg
263
+ viewBox="0 0 24 24"
264
+ width="16"
265
+ height="16"
266
+ aria-hidden="true"
267
+ fill="none"
268
+ stroke="currentColor"
269
+ stroke-width="2"
270
+ stroke-linecap="round"
271
+ stroke-linejoin="round"
272
+ >
273
+ <path d="M3 3l18 18" />
274
+ <path d="M10.6 10.7a3 3 0 0 0 4.2 4.2" />
275
+ <path d="M9.9 5.1A10.9 10.9 0 0 1 12 5c6.5 0 10 7 10 7a21.7 21.7 0 0 1-4 5.2" />
276
+ <path d="M6.6 6.7C4.2 8.3 2.7 10.9 2 12c0 0 3.5 7 10 7 1.8 0 3.4-.5 4.8-1.3" />
277
+ </svg>
278
+ )}
183
279
  </button>
184
280
  )
185
281
 
186
282
  const SiteLayout: FC = (props) => {
187
283
  const route = useRoute()
188
- const reportHeaderHidden = ref(getStoredHeaderHidden())
189
284
  const currentPath = computed(() => route.get()?.path || '/')
190
- const isReportRoute = computed(() => currentPath.get() === '/report')
191
- const hideHeader = computed(() => isReportRoute.get() && reportHeaderHidden.value)
285
+ const allowHeaderToggle = computed(() => headerToggleRoutes.includes(currentPath.get()))
286
+ const reportHeaderHidden = ref(getStoredHeaderHidden(currentPath.get()))
287
+ const hideHeader = computed(() => allowHeaderToggle.get() && reportHeaderHidden.value)
288
+
289
+ watch(
290
+ () => currentPath.get(),
291
+ (path) => {
292
+ reportHeaderHidden.value = allowHeaderToggle.get() ? getStoredHeaderHidden(path) : false
293
+ },
294
+ )
192
295
 
193
296
  watch(
194
297
  () => reportHeaderHidden.value,
195
298
  () => {
196
- localStorage.setItem(reportHeaderStorageKey, reportHeaderHidden.value ? '1' : '0')
299
+ if (!allowHeaderToggle.get()) {
300
+ return
301
+ }
302
+
303
+ localStorage.setItem(
304
+ getHeaderStorageKey(currentPath.get()),
305
+ reportHeaderHidden.value ? '1' : '0',
306
+ )
197
307
  },
198
308
  )
199
309
 
@@ -201,7 +311,7 @@ const SiteLayout: FC = (props) => {
201
311
  <div className="app-shell">
202
312
  <div className="app-surface">
203
313
  {!hideHeader.get() && <Header />}
204
- {isReportRoute.get() && (
314
+ {allowHeaderToggle.get() && (
205
315
  <HeaderToggleButton
206
316
  hidden={hideHeader.get()}
207
317
  onToggle={() => {
@@ -209,12 +319,8 @@ const SiteLayout: FC = (props) => {
209
319
  }}
210
320
  />
211
321
  )}
212
- <main
213
- className={`content-width pb-10 ${hideHeader.get() ? 'pt-6 sm:pt-8' : 'pt-6 sm:pt-8'}`}
214
- >
215
- {props.children}
216
- </main>
217
- <Footer />
322
+ <main className="content-width pb-10 pt-6 sm:pt-8">{props.children}</main>
323
+ {!hideHeader.get() && <Footer />}
218
324
  </div>
219
325
  </div>
220
326
  )
@@ -6,33 +6,32 @@
6
6
  "scripts": {
7
7
  "dev": "vite",
8
8
  "build": "vite build",
9
- "typecheck": "tsc --noEmit",
9
+ "preview": "vite preview",
10
+ "typecheck": "tsgo --noEmit",
10
11
  "oxlint": "oxlint -c ./.oxlintrc.json .",
11
12
  "oxlint-fix": "oxlint -c ./.oxlintrc.json . --fix",
12
13
  "format": "oxfmt .",
13
14
  "format-check": "oxfmt --check ."
14
15
  },
15
16
  "dependencies": {
16
- "@rue-js/design": "^0.8.0",
17
- "@rue-js/jsx-runtime": "^0.8.0",
18
- "@rue-js/router": "^0.8.0",
19
- "@rue-js/rue": "^0.8.0",
20
- "@rue-js/runtime": "^0.8.0",
21
- "@rue-js/runtime-vapor": "^0.8.0"
17
+ "@rue-js/router": "^0.9.7",
18
+ "@rue-js/rue": "^0.9.7",
19
+ "daisyui": "^5.7.28",
20
+ "oxfmt": "^0.66.0",
21
+ "oxlint": "^1.81.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@rue-js/jsx-dev-runtime": "^0.8.0",
25
- "@rue-js/vite-plugin-rue": "^0.8.0",
26
- "@swc/core": "^1.15.3",
27
- "@tailwindcss/typography": "^0.5.19",
28
- "@tailwindcss/vite": "^4.1.17",
29
- "@types/node": "^24.10.1",
30
- "daisyui": "^5.5.23",
31
- "oxfmt": "^0.42.0",
32
- "oxlint": "^1.57.0",
33
- "typescript": "^5.9.3",
34
- "vite": "^8.0.3",
35
- "vite-plugin-wasm": "^3.5.0"
24
+ "@rue-js/jsx-dev-runtime": "^0.8.21",
25
+ "@rue-js/vite-plugin-rue": "^0.9.7",
26
+ "@swc/core": "1.15.47",
27
+ "@tailwindcss/typography": "^0.5.20",
28
+ "@tailwindcss/vite": "^4.3.3",
29
+ "@types/node": "^26.4.1",
30
+ "@typescript/native-preview": "beta",
31
+ "tailwindcss": "^4.3.3",
32
+ "typescript": "^7.0.2",
33
+ "vite": "^8.2.2",
34
+ "vite-plugin-wasm": "^3.6.0"
36
35
  },
37
36
  "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
38
37
  }
@@ -2,7 +2,7 @@
2
2
  "compilerOptions": {
3
3
  "target": "ES2020",
4
4
  "module": "ESNext",
5
- "lib": ["DOM", "DOM.Iterable", "ES2021"],
5
+ "lib": ["DOM", "DOM.Iterable", "ES2020"],
6
6
  "strict": true,
7
7
  "skipLibCheck": true,
8
8
  "moduleResolution": "bundler",
@@ -11,12 +11,11 @@
11
11
  "resolveJsonModule": true,
12
12
  "isolatedModules": true,
13
13
  "noEmit": false,
14
- "types": ["vitest/globals"],
14
+ "types": ["vite/client", "@rue-js/rue/jsx", "node"],
15
15
  "jsx": "react-jsx",
16
16
  "jsxImportSource": "@rue-js",
17
17
  "declaration": true,
18
- "outDir": "./dist",
19
- "baseUrl": "."
18
+ "outDir": "./dist"
20
19
  },
21
20
  "include": ["app/**/*.ts", "app/**/*.tsx", "app/**/*.d.ts", "vite.config.ts"]
22
21
  }
@@ -2,27 +2,20 @@ import { defineConfig } from 'vite'
2
2
  import VitePluginRue from '@rue-js/vite-plugin-rue'
3
3
  import wasm from 'vite-plugin-wasm'
4
4
  import tailwindcss from '@tailwindcss/vite'
5
- import { createRequire } from 'node:module'
6
5
 
7
- const require = createRequire(import.meta.url)
8
- const runtimeDomEntry = require.resolve('@rue-js/runtime/src/dom.ts')
9
-
10
- export default defineConfig({
6
+ export default defineConfig(({ command }) => ({
11
7
  resolve: {
12
- alias: [
13
- {
14
- find: '@rue-js/runtime/dom',
15
- replacement: runtimeDomEntry,
16
- },
17
- ],
18
8
  conditions: ['import', 'module', 'browser', 'default'],
19
9
  },
20
10
  plugins: [
21
11
  wasm(),
22
12
  tailwindcss() as any,
23
13
  VitePluginRue({
24
- include: ['/app/'],
25
- debug: true,
14
+ debug: command === 'serve',
26
15
  }),
27
16
  ],
28
- })
17
+ server: {
18
+ port: 5173,
19
+ strictPort: false,
20
+ },
21
+ }))
@@ -1 +0,0 @@
1
- declare const __SSR__: boolean