Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 5 additions & 2 deletions apps/docs/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof Layout>[0]["children"]
}

export const metadata = {
title: "VekUI WeApp",
description: "Taro React 微信小程序源码分发组件库",
Expand All @@ -12,7 +15,7 @@ export const metadata = {
}
}

export default async function RootLayout({ children }: { children: React.ReactNode }) {
export default async function RootLayout({ children }: RootLayoutProps) {
return (
<html lang="zh-CN" suppressHydrationWarning>
<body>
Expand Down
9 changes: 8 additions & 1 deletion apps/miniprogram/src/demo/demo-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -145,11 +147,16 @@ function DemoPanel({ children, title }: DemoPanelProps) {
}

function DemoPageShell({ children, title }: React.PropsWithChildren<{ title: string }>) {
const [themeId, setThemeId] = useDemoTheme()

return (
<Box className="theme-learning min-h-screen bg-background pb-8">
<Box className={`${getDemoThemeClassName(themeId)} min-h-screen bg-background pb-8`}>
<Box className="bg-muted px-6 pb-8 pt-10">
<Text className="block text-xl font-semibold leading-[56rpx] text-foreground">{title}</Text>
<Box className="mt-4 h-[4rpx] w-[96rpx] rounded-full bg-primary" />
<Box className="mt-5">
<ThemeSwitcher value={themeId} onValueChange={setThemeId} />
</Box>
</Box>
{children}
</Box>
Expand Down
45 changes: 45 additions & 0 deletions apps/miniprogram/src/demo/theme-switcher.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Box
className="flex w-full flex-row gap-2 rounded-lg border border-border bg-card p-1 text-card-foreground"
data-slot="demo-theme-switcher"
>
{demoThemes.map((theme) => {
const selected = theme.id === value

return (
<Pressable
key={theme.id}
className={
selected
? "flex min-h-[88rpx] flex-1 items-center justify-center rounded-md bg-primary px-3"
: "flex min-h-[88rpx] flex-1 items-center justify-center rounded-md bg-background px-3"
}
data-state={selected ? "selected" : "default"}
hoverClass={selected ? "bg-primary" : "bg-secondary"}
onClick={() => onValueChange(theme.id)}
>
<Text
className={
selected
? "text-center text-sm font-medium leading-[36rpx] text-primary-foreground"
: "text-center text-sm font-medium leading-[36rpx] text-muted-foreground"
}
>
{theme.label}
</Text>
</Pressable>
)
})}
</Box>
)
}
78 changes: 78 additions & 0 deletions apps/miniprogram/src/demo/theme.ts
Original file line number Diff line number Diff line change
@@ -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<string>(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<DemoThemeId>(readStoredThemeId)

Taro.useDidShow(() => {
setThemeId(readStoredThemeId())
})

const updateThemeId = React.useCallback((nextThemeId: DemoThemeId) => {
setThemeId(nextThemeId)
writeStoredThemeId(nextThemeId)
}, [])

return [themeId, updateThemeId] as const
}
22 changes: 19 additions & 3 deletions apps/miniprogram/src/pages/index/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Box className="theme-learning min-h-screen bg-background pb-8">
<Box className={`${getDemoThemeClassName(themeId)} min-h-screen bg-background pb-8`}>
<Box
className="flex flex-col items-center px-6 pb-[88rpx] pt-[44rpx]"
className="flex flex-col items-center px-6 pb-[72rpx] pt-[44rpx]"
data-slot="brand-hero"
>
<Image
Expand All @@ -19,6 +24,17 @@ export default function IndexPage() {
rounded={false}
src={vekuiLogo}
/>
<Box className="mt-4 flex flex-row items-center gap-2">
<Text className="rounded-full bg-secondary px-3 text-xs leading-[36rpx] text-muted-foreground">
组件覆盖
</Text>
<Text className="rounded-full bg-primary px-3 text-xs leading-[36rpx] text-primary-foreground">
{totalDemoComponents} demos
</Text>
</Box>
<Box className="mt-5 w-full">
<ThemeSwitcher value={themeId} onValueChange={setThemeId} />
</Box>
</Box>

<Box className="-mt-[36rpx] flex flex-col gap-4 px-5">
Expand Down
8 changes: 7 additions & 1 deletion apps/miniprogram/src/pages/panel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Box className="theme-learning min-h-screen bg-background">
<Box className={`${getDemoThemeClassName(themeId)} min-h-screen bg-background`}>
<Box className="bg-muted px-6 pb-8 pt-8">
<Box className="flex flex-row items-center gap-4">
<Box className="flex h-[88rpx] w-[88rpx] shrink-0 items-center justify-center rounded-lg border border-border bg-card shadow-sm">
Expand All @@ -22,6 +25,9 @@ export default function PanelPage() {
</Text>
</Box>
</Box>
<Box className="mt-5">
<ThemeSwitcher value={themeId} onValueChange={setThemeId} />
</Box>
</Box>

<Box className="px-6 py-3">
Expand Down