@zpress/ui 0.5.1 → 0.6.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.
@@ -11,12 +11,25 @@ interface ThemeOption {
11
11
  readonly label: string
12
12
  readonly swatch: string
13
13
  readonly defaultColorMode: 'dark' | 'light' | 'toggle'
14
+ readonly modes: readonly ('dark' | 'light')[]
14
15
  }
15
16
 
16
17
  const THEME_OPTIONS: readonly ThemeOption[] = [
17
- { name: 'base', label: 'Base', swatch: '#a78bfa', defaultColorMode: 'toggle' },
18
- { name: 'midnight', label: 'Midnight', swatch: '#60a5fa', defaultColorMode: 'dark' },
19
- { name: 'arcade', label: 'Arcade', swatch: '#00ff88', defaultColorMode: 'dark' },
18
+ {
19
+ name: 'base',
20
+ label: 'Base',
21
+ swatch: '#a78bfa',
22
+ defaultColorMode: 'toggle',
23
+ modes: ['dark', 'light'],
24
+ },
25
+ {
26
+ name: 'midnight',
27
+ label: 'Midnight',
28
+ swatch: '#60a5fa',
29
+ defaultColorMode: 'dark',
30
+ modes: ['dark'],
31
+ },
32
+ { name: 'arcade', label: 'Arcade', swatch: '#00ff88', defaultColorMode: 'dark', modes: ['dark'] },
20
33
  ]
21
34
 
22
35
  const VALID_THEME_NAMES = new Set(THEME_OPTIONS.map((t) => t.name))
@@ -52,6 +65,8 @@ function applyTheme(theme: ThemeOption): void {
52
65
  html.dataset.zpTheme = theme.name
53
66
  localStorage.setItem('zpress-theme', theme.name)
54
67
 
68
+ html.dataset.zpModes = theme.modes.join(' ')
69
+
55
70
  if (theme.defaultColorMode === 'dark') {
56
71
  // 'rp-dark' is Rspress's dark mode class; 'dark' is added for Tailwind compatibility
57
72
  html.classList.add('rp-dark', 'dark')
@@ -6,6 +6,16 @@ declare const __ZPRESS_COLOR_MODE__: string
6
6
  declare const __ZPRESS_THEME_COLORS__: string
7
7
  declare const __ZPRESS_THEME_DARK_COLORS__: string
8
8
 
9
+ /**
10
+ * Supported color modes per built-in theme — used to set `data-zp-modes`
11
+ * so the appearance toggle is hidden for single-mode themes.
12
+ */
13
+ const THEME_MODES: Readonly<Record<string, string>> = {
14
+ base: 'dark light',
15
+ midnight: 'dark',
16
+ arcade: 'dark',
17
+ }
18
+
9
19
  const COLOR_VAR_MAP: Record<string, readonly string[]> = {
10
20
  brand: ['--zp-c-brand-1', '--rp-c-brand'],
11
21
  brandLight: ['--rp-c-brand-light'],
@@ -167,7 +177,15 @@ export function ThemeProvider(): React.ReactElement | null {
167
177
  // 1. Set theme attribute
168
178
  html.dataset.zpTheme = themeName
169
179
 
170
- // 2. Force color mode if not toggle
180
+ // 2. Set supported modes hides the appearance toggle for single-mode themes
181
+ const modes = THEME_MODES[themeName]
182
+ if (modes) {
183
+ html.dataset.zpModes = modes
184
+ } else {
185
+ html.dataset.zpModes = 'dark light'
186
+ }
187
+
188
+ // 3. Force color mode if not toggle
171
189
  if (colorMode === 'dark') {
172
190
  html.classList.add('rp-dark', 'dark')
173
191
  html.dataset.dark = 'true'
@@ -186,12 +204,12 @@ export function ThemeProvider(): React.ReactElement | null {
186
204
  }
187
205
  }
188
206
 
189
- // 3. Apply base color overrides
207
+ // 4. Apply base color overrides
190
208
  if (hasColors) {
191
209
  applyColorOverrides(html, colors)
192
210
  }
193
211
 
194
- // 4. Observe dark mode changes for dark-specific overrides
212
+ // 5. Observe dark mode changes for dark-specific overrides
195
213
  if (hasDarkColors) {
196
214
  const isDark = html.classList.contains('rp-dark')
197
215
  if (isDark) {
@@ -214,7 +232,7 @@ export function ThemeProvider(): React.ReactElement | null {
214
232
 
215
233
  observer.observe(html, { attributes: true, attributeFilter: ['class'] })
216
234
 
217
- // 5. Dismiss loading overlay
235
+ // 6. Dismiss loading overlay
218
236
  const cancelLoader = dismissLoader(html)
219
237
 
220
238
  return () => {
@@ -223,7 +241,7 @@ export function ThemeProvider(): React.ReactElement | null {
223
241
  }
224
242
  }
225
243
 
226
- // 5. Dismiss loading overlay (no dark-color observer needed)
244
+ // 6. Dismiss loading overlay (no dark-color observer needed)
227
245
  return dismissLoader(html)
228
246
  }, [])
229
247
 
@@ -32,6 +32,12 @@ html .rp-button--big {
32
32
  border-radius: 9999px;
33
33
  }
34
34
 
35
+ /* ── Single-mode themes — hide the appearance toggle when only one mode is supported ── */
36
+ html[data-zp-modes='dark'] .rp-appearance,
37
+ html[data-zp-modes='light'] .rp-appearance {
38
+ display: none;
39
+ }
40
+
35
41
  /* ── Tables ──────────────────────────────────────────────── */
36
42
  html table {
37
43
  border-radius: var(--rp-radius);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zpress/ui",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Rspress plugin, theme components, and styles for zpress",
5
5
  "keywords": [
6
6
  "react",
@@ -52,9 +52,9 @@
52
52
  "@iconify-json/vscode-icons": "^1.2.45",
53
53
  "@iconify/react": "^6.0.2",
54
54
  "ts-pattern": "^5.9.0",
55
- "@zpress/config": "0.2.1",
56
- "@zpress/core": "0.6.1",
57
- "@zpress/theme": "0.2.1"
55
+ "@zpress/config": "0.2.2",
56
+ "@zpress/core": "0.6.2",
57
+ "@zpress/theme": "0.3.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@rslib/core": "^0.20.0",
@@ -79,6 +79,7 @@
79
79
  "build": "rslib build",
80
80
  "postbuild": "node scripts/minify-head.mjs",
81
81
  "dev": "rslib build --watch --no-clean",
82
+ "test": "vitest run",
82
83
  "typecheck": "tsc --noEmit"
83
84
  }
84
85
  }
@@ -11,12 +11,25 @@ interface ThemeOption {
11
11
  readonly label: string
12
12
  readonly swatch: string
13
13
  readonly defaultColorMode: 'dark' | 'light' | 'toggle'
14
+ readonly modes: readonly ('dark' | 'light')[]
14
15
  }
15
16
 
16
17
  const THEME_OPTIONS: readonly ThemeOption[] = [
17
- { name: 'base', label: 'Base', swatch: '#a78bfa', defaultColorMode: 'toggle' },
18
- { name: 'midnight', label: 'Midnight', swatch: '#60a5fa', defaultColorMode: 'dark' },
19
- { name: 'arcade', label: 'Arcade', swatch: '#00ff88', defaultColorMode: 'dark' },
18
+ {
19
+ name: 'base',
20
+ label: 'Base',
21
+ swatch: '#a78bfa',
22
+ defaultColorMode: 'toggle',
23
+ modes: ['dark', 'light'],
24
+ },
25
+ {
26
+ name: 'midnight',
27
+ label: 'Midnight',
28
+ swatch: '#60a5fa',
29
+ defaultColorMode: 'dark',
30
+ modes: ['dark'],
31
+ },
32
+ { name: 'arcade', label: 'Arcade', swatch: '#00ff88', defaultColorMode: 'dark', modes: ['dark'] },
20
33
  ]
21
34
 
22
35
  const VALID_THEME_NAMES = new Set(THEME_OPTIONS.map((t) => t.name))
@@ -52,6 +65,8 @@ function applyTheme(theme: ThemeOption): void {
52
65
  html.dataset.zpTheme = theme.name
53
66
  localStorage.setItem('zpress-theme', theme.name)
54
67
 
68
+ html.dataset.zpModes = theme.modes.join(' ')
69
+
55
70
  if (theme.defaultColorMode === 'dark') {
56
71
  // 'rp-dark' is Rspress's dark mode class; 'dark' is added for Tailwind compatibility
57
72
  html.classList.add('rp-dark', 'dark')
@@ -6,6 +6,16 @@ declare const __ZPRESS_COLOR_MODE__: string
6
6
  declare const __ZPRESS_THEME_COLORS__: string
7
7
  declare const __ZPRESS_THEME_DARK_COLORS__: string
8
8
 
9
+ /**
10
+ * Supported color modes per built-in theme — used to set `data-zp-modes`
11
+ * so the appearance toggle is hidden for single-mode themes.
12
+ */
13
+ const THEME_MODES: Readonly<Record<string, string>> = {
14
+ base: 'dark light',
15
+ midnight: 'dark',
16
+ arcade: 'dark',
17
+ }
18
+
9
19
  const COLOR_VAR_MAP: Record<string, readonly string[]> = {
10
20
  brand: ['--zp-c-brand-1', '--rp-c-brand'],
11
21
  brandLight: ['--rp-c-brand-light'],
@@ -167,7 +177,15 @@ export function ThemeProvider(): React.ReactElement | null {
167
177
  // 1. Set theme attribute
168
178
  html.dataset.zpTheme = themeName
169
179
 
170
- // 2. Force color mode if not toggle
180
+ // 2. Set supported modes hides the appearance toggle for single-mode themes
181
+ const modes = THEME_MODES[themeName]
182
+ if (modes) {
183
+ html.dataset.zpModes = modes
184
+ } else {
185
+ html.dataset.zpModes = 'dark light'
186
+ }
187
+
188
+ // 3. Force color mode if not toggle
171
189
  if (colorMode === 'dark') {
172
190
  html.classList.add('rp-dark', 'dark')
173
191
  html.dataset.dark = 'true'
@@ -186,12 +204,12 @@ export function ThemeProvider(): React.ReactElement | null {
186
204
  }
187
205
  }
188
206
 
189
- // 3. Apply base color overrides
207
+ // 4. Apply base color overrides
190
208
  if (hasColors) {
191
209
  applyColorOverrides(html, colors)
192
210
  }
193
211
 
194
- // 4. Observe dark mode changes for dark-specific overrides
212
+ // 5. Observe dark mode changes for dark-specific overrides
195
213
  if (hasDarkColors) {
196
214
  const isDark = html.classList.contains('rp-dark')
197
215
  if (isDark) {
@@ -214,7 +232,7 @@ export function ThemeProvider(): React.ReactElement | null {
214
232
 
215
233
  observer.observe(html, { attributes: true, attributeFilter: ['class'] })
216
234
 
217
- // 5. Dismiss loading overlay
235
+ // 6. Dismiss loading overlay
218
236
  const cancelLoader = dismissLoader(html)
219
237
 
220
238
  return () => {
@@ -223,7 +241,7 @@ export function ThemeProvider(): React.ReactElement | null {
223
241
  }
224
242
  }
225
243
 
226
- // 5. Dismiss loading overlay (no dark-color observer needed)
244
+ // 6. Dismiss loading overlay (no dark-color observer needed)
227
245
  return dismissLoader(html)
228
246
  }, [])
229
247
 
@@ -32,6 +32,12 @@ html .rp-button--big {
32
32
  border-radius: 9999px;
33
33
  }
34
34
 
35
+ /* ── Single-mode themes — hide the appearance toggle when only one mode is supported ── */
36
+ html[data-zp-modes='dark'] .rp-appearance,
37
+ html[data-zp-modes='light'] .rp-appearance {
38
+ display: none;
39
+ }
40
+
35
41
  /* ── Tables ──────────────────────────────────────────────── */
36
42
  html table {
37
43
  border-radius: var(--rp-radius);