diff --git a/CHANGELOG.md b/CHANGELOG.md index 3927dd0..e238b49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ VekUI WeApp 的重要版本变更会记录在这里,方便跟进 registry、CL - 为全部 95 个公开 registry UI 组件补齐独立单元测试覆盖,校验 API、状态属性、语义 token class 和小程序兼容规则。 - 组件文档目录改为 registry-driven 统计,明确区分公开 UI 组件数、registry item 总数和 shadcn planned 项。 - 在 README 和 GitHub Pages 页脚加入公众号二维码入口。 +- 为小程序 playground 增加主题切换,便于在不同 token 主题下验证 demo 页面。 ### Changed diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx index dbe50ba..014553b 100644 --- a/apps/docs/app/layout.tsx +++ b/apps/docs/app/layout.tsx @@ -1,9 +1,12 @@ import { Footer, Layout, Navbar } from "nextra-theme-docs" import { getPageMap } from "nextra/page-map" -import type * as React from "react" import "nextra-theme-docs/style.css" import "./site.css" +type RootLayoutProps = { + children: Parameters[0]["children"] +} + export const metadata = { title: "VekUI WeApp", description: "Taro React 微信小程序源码分发组件库", @@ -12,7 +15,7 @@ export const metadata = { } } -export default async function RootLayout({ children }: { children: React.ReactNode }) { +export default async function RootLayout({ children }: RootLayoutProps) { return ( diff --git a/apps/miniprogram/src/demo/demo-page.tsx b/apps/miniprogram/src/demo/demo-page.tsx index 63786f1..4aba79f 100644 --- a/apps/miniprogram/src/demo/demo-page.tsx +++ b/apps/miniprogram/src/demo/demo-page.tsx @@ -121,6 +121,8 @@ import { } from "@vekui/weapp" import { getDemoComponent } from "./catalog" +import { getDemoThemeClassName, useDemoTheme } from "./theme" +import { ThemeSwitcher } from "./theme-switcher" type DemoPanelProps = React.PropsWithChildren<{ title: string @@ -145,11 +147,16 @@ function DemoPanel({ children, title }: DemoPanelProps) { } function DemoPageShell({ children, title }: React.PropsWithChildren<{ title: string }>) { + const [themeId, setThemeId] = useDemoTheme() + return ( - + {title} + + + {children} diff --git a/apps/miniprogram/src/demo/theme-switcher.tsx b/apps/miniprogram/src/demo/theme-switcher.tsx new file mode 100644 index 0000000..a29e4cd --- /dev/null +++ b/apps/miniprogram/src/demo/theme-switcher.tsx @@ -0,0 +1,45 @@ +import { Box, Pressable, Text } from "@vekui/weapp" + +import { demoThemes, type DemoThemeId } from "./theme" + +type ThemeSwitcherProps = { + onValueChange: (themeId: DemoThemeId) => void + value: DemoThemeId +} + +export function ThemeSwitcher({ onValueChange, value }: ThemeSwitcherProps) { + return ( + + {demoThemes.map((theme) => { + const selected = theme.id === value + + return ( + onValueChange(theme.id)} + > + + {theme.label} + + + ) + })} + + ) +} diff --git a/apps/miniprogram/src/demo/theme.ts b/apps/miniprogram/src/demo/theme.ts new file mode 100644 index 0000000..30da20a --- /dev/null +++ b/apps/miniprogram/src/demo/theme.ts @@ -0,0 +1,78 @@ +import * as React from "react" +import Taro from "@tarojs/taro" + +const demoThemeStorageKey = "vekui-demo-theme" + +export const demoThemes = [ + { + id: "default", + label: "Default", + description: "Clean registry baseline", + className: "theme-default" + }, + { + id: "learning", + label: "Learning", + description: "Cool study surface", + className: "theme-learning" + }, + { + id: "warm", + label: "Warm", + description: "Warm product surface", + className: "theme-warm" + } +] as const + +export type DemoThemeId = (typeof demoThemes)[number]["id"] + +export const defaultDemoThemeId: DemoThemeId = "learning" + +function isDemoThemeId(value: unknown): value is DemoThemeId { + return demoThemes.some((theme) => theme.id === value) +} + +function readStoredThemeId(): DemoThemeId { + try { + const storedThemeId = Taro.getStorageSync(demoThemeStorageKey) + + if (isDemoThemeId(storedThemeId)) { + return storedThemeId + } + } catch { + // Storage can be unavailable in tests, builds, or constrained runtimes. + } + + return defaultDemoThemeId +} + +function writeStoredThemeId(themeId: DemoThemeId) { + try { + Taro.setStorageSync(demoThemeStorageKey, themeId) + } catch { + // Theme switching should continue even when persistence is unavailable. + } +} + +export function getDemoTheme(themeId: DemoThemeId) { + return demoThemes.find((theme) => theme.id === themeId) ?? demoThemes[0] +} + +export function getDemoThemeClassName(themeId: DemoThemeId) { + return getDemoTheme(themeId).className +} + +export function useDemoTheme() { + const [themeId, setThemeId] = React.useState(readStoredThemeId) + + Taro.useDidShow(() => { + setThemeId(readStoredThemeId()) + }) + + const updateThemeId = React.useCallback((nextThemeId: DemoThemeId) => { + setThemeId(nextThemeId) + writeStoredThemeId(nextThemeId) + }, []) + + return [themeId, updateThemeId] as const +} diff --git a/apps/miniprogram/src/pages/index/index.tsx b/apps/miniprogram/src/pages/index/index.tsx index db5d31a..7d2195e 100644 --- a/apps/miniprogram/src/pages/index/index.tsx +++ b/apps/miniprogram/src/pages/index/index.tsx @@ -1,15 +1,20 @@ import Taro from "@tarojs/taro" import { Box, Icon, Image, Pressable, Text } from "@vekui/weapp" -import { demoCategories, getCategoryComponents } from "../../demo/catalog" +import { demoCategories, demoComponents, getCategoryComponents } from "../../demo/catalog" +import { getDemoThemeClassName, useDemoTheme } from "../../demo/theme" +import { ThemeSwitcher } from "../../demo/theme-switcher" const vekuiLogo = "/assets/brand/vekui-logo-horizontal-transparent.png" export default function IndexPage() { + const [themeId, setThemeId] = useDemoTheme() + const totalDemoComponents = demoComponents.length + return ( - + + + + 组件覆盖 + + + {totalDemoComponents} demos + + + + + diff --git a/apps/miniprogram/src/pages/panel/index.tsx b/apps/miniprogram/src/pages/panel/index.tsx index 0830041..7d20500 100644 --- a/apps/miniprogram/src/pages/panel/index.tsx +++ b/apps/miniprogram/src/pages/panel/index.tsx @@ -2,14 +2,17 @@ import Taro from "@tarojs/taro" import { Box, Icon, List, ListItem, Text } from "@vekui/weapp" import { getCategory, getCategoryComponents } from "../../demo/catalog" +import { getDemoThemeClassName, useDemoTheme } from "../../demo/theme" +import { ThemeSwitcher } from "../../demo/theme-switcher" export default function PanelPage() { + const [themeId, setThemeId] = useDemoTheme() const categoryId = String(Taro.getCurrentInstance().router?.params?.id ?? "basic") const category = getCategory(categoryId) ?? getCategory("basic")! const components = getCategoryComponents(category.id) return ( - + @@ -22,6 +25,9 @@ export default function PanelPage() { + + +