mcpField
T
theming

theming

github.com/code-with-beto设计与 UI144194 次安装15 周前更新
代码仓库

关于 theming

theming 是一项为 Expo Router 应用添加统一语义颜色系统的技能,提供按平台解析的颜色、明暗模式更新及组件钩子,并指导导航主题集成。

主要功能

  1. 01统一语义颜色令牌
  2. 02解析 iOS 和 Android 原生颜色
  3. 03自动响应明暗模式变化
  4. 04颜色与品牌钩子
  5. 05集成导航主题

适用场景

  1. 01为 Expo Router 应用统一配色
  2. 02让导航界面匹配应用颜色
  3. 03支持明暗外观切换
  4. 04使用原生颜色或固定十六进制值

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/code-with-beto/skills --skill theming

安装后在 Claude Code 中输入/theming确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

assets/
colors.ts
config.ts
ThemeContext.tsx

详细介绍

Expo 统一主题

只需设置一套颜色系统,即可同时适用于 iOS 和 Android。用户无需在整个应用中到处使用 Color.ios.*、Color.android.dynamic.*(或硬编码的十六进制值),而是可以使用语义化标记(background、text、link 等)。这些标记会根据平台解析为对应的原生颜色,自动随深色/浅色模式切换,并通过单个 useColors() hook 读取。

这是 Platano 模板采用的模式,现已打包,可直接用于任何 Expo Router 应用。

安装内容

三个文件(包含在 assets/ 中,请根据项目调整):

  • config.ts — 唯一事实来源。语义系统标记设为 "native" 或十六进制值,另含品牌颜色。这是用户日常唯一需要编辑的文件。
  • colors.ts — 根据平台将标记解析为实际颜色,并提供 getSystemColor、useSystemColors、品牌辅助函数和 getNavigationTheme。
  • ThemeContext.tsx — ColorsProvider,以及基于 React use 构建的 useColors()/useTheme()/useBrand() hooks。

此外,还需稍微修改根目录的 _layout.tsx,以连接各个 provider。

让这一模式生效的两个关键点

请向用户说明以下内容,因为这是整个模式的核心,也是最容易出错的两个地方:

  1. 主题变更时重新渲染。 原生颜色和 Material You 颜色是平台颜色对象,而不是 React 可以进行差异比较的字符串。用于指示主题已变更的信号是 useColorScheme()。因此,即使解析器并不读取颜色方案,也要将其作为参数传入:这样就将深色/浅色模式设为显式依赖项,用户切换模式时,值就会重新计算。ColorsProvider 会在靠近根节点的位置读取一次 useColorScheme(),因此切换模式会触发所有使用方重新渲染。

  2. 两个 ThemeProvider,各司其职。 ColorsProvider(此技能提供)通过 useColors() 为组件提供颜色。导航的 ThemeProvider 则用于设置导航界面(标题栏、标签栏、默认背景)的主题。两者都位于根节点。请使用 getNavigationTheme(dark) 连接导航的 ThemeProvider,使导航界面与标记保持一致。不要混淆两者,也不要遗漏其中任何一个。对于 Expo SDK 56+,导航主题相关内容(ThemeProvider、DarkTheme、DefaultTheme、Theme)从 expo-router/react-navigation 导入;对于 较旧的 SDK,则从 @react-navigation/native 导入(请参阅步骤 1)。

步骤 1:确认项目、SDK 和原生 Color API

此技能适用于 Expo SDK 56 或更新版本上的 Expo Router 应用。SDK 56 开始同时提供 Color API(iOS 系统颜色和 Android Material You)以及模板所用的 expo-router/react-navigation 重新导出。在使用 "native" 标记前,请先检查 SDK 和 Color API。

# Find the project's app dir and root layout (root-level or under src/)
ls app/_layout.tsx src/app/_layout.tsx 2>/dev/null

# Check the installed Expo SDK major version
node -e "console.log('expo', require('expo/package.json').version)" 2>/dev/null

# Confirm expo-router exports the Color API
node -e "console.log('Color export:', !!require('expo-router').Color)" 2>/dev/null
grep -rl "android.dynamic\|ios.systemBackground" node_modules/expo-router/build 2>/dev/null | head -1

根据检查结果进行分支处理:

  • SDK 56+(目标版本): 按原样使用模板。导航主题相关内容从 expo-router/react-navigation 导入。
  • 低于 SDK 56: 需要做两项调整。
    1. 在 colors.ts 和 _layout.tsx 中,将 ThemeProvider、DarkTheme、DefaultTheme 和 type Theme 改为从 @react-navigation/native 导入,而不是从 expo-router/react-navigation 导入。(较早版本的 Expo Router 不会重新导出这些内容,因此需要直接从 React Navigation 导入。)
    2. Color API 可能不存在。可以建议升级到 SDK 56,也可以使用十六进制回退方案:保留这些文件,但将 config.ts 中的 system 标记设为十六进制值,而不是 "native"(无法使用 Material You,但其他功能均可正常运行)。getBrandColors 仍可使用;没有 Color 时,isAndroidDynamic 会解析为 false。

还要检查项目的 tsconfig.json,确认导入别名(通常是 @/* → src/* 或项目根目录)。编写导入语句时应匹配该别名。如果没有别名,则使用相对路径。

第 2 步:确定文件的存放位置

将这三个文件放在同一个文件夹中,这样它们的相对导入路径 (./config、./colors)就能正常使用。按照项目的目录结构放置:

  • 应用位于 src/app/ 下 → 创建 src/theme/。
  • 应用位于根目录的 app/ 下 → 在根目录创建 theme/。

先检查是否已有 theme/colors 模块或 ThemeProvider。如果应用已经有主题设置,不要覆盖。阅读现有内容,向用户说明重叠之处,然后选择合并到现有设置中,或将这些文件放到名称清晰的文件夹下,让用户自行迁移。绝不要盲目覆盖。

第 3 步:创建文件

将随附的三个模板复制到选定文件夹,并调整:

  • 导入别名/相对路径,使其符合项目设置;
  • config.ts 中的 system 令牌和 brand 颜色,使其符合用户的品牌(询问用户品牌的主色/强调色,或者保留合理的默认值,并告知用户在哪里修改)。

读取每个资源文件,并将其写入项目(不要只创建符号链接——这些文件现在属于用户):

  • assets/config.ts → <theme>/config.ts
  • assets/colors.ts → <theme>/colors.ts
  • assets/ThemeContext.tsx → <theme>/ThemeContext.tsx

保留注释。它们说明了重新渲染技巧以及品牌/动态行为,这正是学习者需要在自己的代码库中了解的内容。

第 4 步:连接根布局

谨慎修改现有的 _layout.tsx——保留已有的每个 provider、screen 和 option。目标结构是在 ColorsProvider 中包裹整个组件树,并在可读取颜色方案的内部组件中设置导航主题:

// SDK 56+: from "expo-router/react-navigation". Older SDKs: "@react-navigation/native".
import { ThemeProvider } from "expo-router/react-navigation";
import { Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
import { useColorScheme } from "react-native";

import { getBrandColors, getNavigationTheme } from "@/theme/colors";
import { ColorsProvider } from "@/theme/ThemeContext";

export default function RootLayout() {
  return (
    <ColorsProvider>
      {/* keep any existing providers (QueryClient, RevenueCat, gestures...) here */}
      <RootLayoutInner />
    </ColorsProvider>
  );
}

function RootLayoutInner() {
  const dark = useColorScheme() === "dark";
  const { primary } = getBrandColors(dark);

  return (
    <>
      <ThemeProvider value={getNavigationTheme(dark, primary)}>
        {/* keep the project's existing <Stack> / screens exactly as they were */}
        <Stack />
      </ThemeProvider>
      <StatusBar style={dark ? "light" : "dark"} />
    </>
  );
}

集成规则:

  • ColorsProvider 必须位于所有调用 useColors() 的内容上层。将它放在 RootLayout 的顶部或附近最稳妥。
  • 如果文件已经拆分为 RootLayout 和一个内部组件,就在该内部组件中添加 ThemeProvider 和 useColorScheme(),不要新建内部组件。
  • 如果未安装 expo-status-bar,且项目也没有以其他方式管理状态栏,可以省略 StatusBar 这一行。

第 5 步:验证

  • 如果可以,进行类型检查:npx tsc --noEmit(预期这三个文件不会产生新错误;最常见的问题是导入别名错误)。
  • 告诉用户运行应用,然后切换操作系统外观(或模拟器的深色/浅色模式),确认颜色和导航栏样式会实时更新。在实体 Android 设备上,更换壁纸应该会改变 Material You 调色板。

第 6 步:展示用法

提供一段简短的用法示例(并表示如果用户想立即查看效果,可以将一个主题示例放入某个屏幕):

import { View, Text } from "react-native";
import { useColors, useBrand } from "@/theme/ThemeContext";

export function Example() {
  const { background, text, secondaryText, separator } = useColors();
  const { primary } = useBrand();

  return (
    <View style={{ flex: 1, backgroundColor: background, padding: 16 }}>
      <Text style={{ color: text, fontSize: 20, fontWeight: "600" }}>
        Unified theming
      </Text>
      <Text style={{ color: secondaryText }}>
        Same tokens on iOS and Android.
      </Text>
      <View style={{ height: 1, backgroundColor: separator, marginVertical: 12 }} />
      <Text style={{ color: primary }}>Brand accent</Text>
    </View>
  );
}

在 React 之外(例如 toast 或一次性样式中),使用非 hook 访问器:

import { getSystemColor } from "@/theme/colors";
const card = getSystemColor("secondaryBackground") as string;

自定义

  • **在所有地方更改某个颜色:**编辑 config.ts。将令牌设为十六进制值即可固定颜色,设回 "native" 则跟随操作系统。
  • 关闭 Material You,在 Android 上使用品牌颜色:设置 brand.useAndroidDynamic: false,这样所有平台都会应用十六进制品牌色。
  • **添加令牌:**在 config.ts 的 system 中添加令牌,然后在 colors.ts 的 getNativeDefault 中添加对应的 iOS/Android/默认值,并在 resolveSystemColors 中添加一行。TypeScript 会指出遗漏的内容。

说明

  • 此设置只安装颜色和品牌配置。它有意不添加间距或圆角预设,也不添加持久化功能;除非用户提出要求,否则请保持精简。
  • 这些文件现在属于用户的代码。鼓励他们阅读注释并进行调整,而不是将其视为黑盒。这正是本技能配套课程的教学目标。

评论

支持 Markdown