mcpField
B
building-native-ui

building-native-ui

github.com/midudev设计与 UI7.0k57 次安装1 周前更新
代码仓库

关于 building-native-ui

building-native-ui 是面向 Expo 应用开发的 UI 指南 Skill,涵盖原生界面、导航、样式与响应式布局。它提供 Expo Go 使用建议和针对组件、路由及平台能力的参考资料。

主要功能

  1. 01Expo Router 路由与导航规范
  2. 02原生控件、表单与媒体参考
  3. 03动画、图标和渐变指导
  4. 04响应式布局与安全区域建议
  5. 05Expo Go 与自定义构建选择指南

适用场景

  1. 01规划 Expo 应用的路由结构
  2. 02为 iOS 界面选择原生控件和交互
  3. 03检查布局、样式及导航实现

安装

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

$npx skills add https://github.com/midudev/autoskills --skill building-native-ui

安装后在 Claude Code 中输入/building-native-ui确认已加载。

包含的文件

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

references/
animations.md
controls.md
form-sheet.md
gradients.md
icons.md
media.md
route-structure.md
search.md
storage.md
tabs.md
toolbar-and-headers.md
visual-effects.md
webgpu-three.md
zoom-transitions.md

详细介绍

Expo UI 指南

参考资料

按需查阅以下资源:

references/
  animations.md          Reanimated: entering, exiting, layout, scroll-driven, gestures
  controls.md            Native iOS: Switch, Slider, SegmentedControl, DateTimePicker, Picker
  form-sheet.md          Form sheets in expo-router: configuration, footers and background interaction. 
  gradients.md           CSS gradients via experimental_backgroundImage (New Arch only)
  icons.md               SF Symbols via expo-image (sf: source), names, animations, weights
  media.md               Camera, audio, video, and file saving
  route-structure.md     Route conventions, dynamic routes, groups, folder organization
  search.md              Search bar with headers, useSearch hook, filtering patterns
  storage.md             SQLite, AsyncStorage, SecureStore
  tabs.md                NativeTabs, migration from JS tabs, iOS 26 features
  toolbar-and-headers.md Stack headers and toolbar buttons, menus, search (iOS only)
  visual-effects.md      Blur (expo-blur) and liquid glass (expo-glass-effect)
  webgpu-three.md        3D graphics, games, GPU visualizations with WebGPU and Three.js
  zoom-transitions.md    Apple Zoom: fluid zoom transitions with Link.AppleZoom (iOS 18+)

运行应用

重要:始终先尝试使用 Expo Go,再创建自定义构建。

大多数 Expo 应用无需任何自定义原生代码,即可在 Expo Go 中运行。在运行 npx expo run:ios 或 npx expo run:android 之前:

  1. 先使用 Expo Go:运行 npx expo start,并使用 Expo Go 扫描二维码
  2. 检查功能是否可用:在 Expo Go 中全面测试应用
  3. 仅在必要时创建自定义构建——请参阅下文
需要自定义构建的情况

仅当使用以下内容时,才需要运行 npx expo run:ios/android 或 eas build:

  • 本地 Expo 模块(modules/ 中的自定义原生代码)
  • Apple targets(通过 @bacons/apple-targets 实现的小组件、App Clip、扩展)
  • Expo Go 未包含的第三方原生模块
  • 无法通过 app.json 表达的自定义原生配置
Expo Go 适用的情况

Expo Go 开箱即支持大量功能:

  • 所有 expo-* 包(相机、位置、通知等)
  • Expo Router 导航
  • 大多数 UI 库(reanimated、gesture handler 等)
  • 推送通知、深层链接等

如果不确定,请先尝试 Expo Go。 创建自定义构建会增加复杂性、拖慢迭代速度,还需要配置 Xcode/Android Studio。

代码风格

  • 注意字符串是否未正确终止。确保对嵌套反引号进行转义;务必正确转义引号。
  • 始终在文件顶部使用 import 语句。
  • 文件名始终使用 kebab-case,例如 comment-card.tsx
  • 移动或重构导航时,始终删除旧的路由文件
  • 文件名中不得使用特殊字符
  • 在 tsconfig.json 中配置路径别名,重构时优先使用别名而非相对导入。

路由

详细的路由约定请参阅 ./references/route-structure.md。

  • 路由应放在 app 目录中。
  • 不要在 app 目录中与路由文件并置组件、类型或工具。这属于反模式。
  • 确保应用始终有一个与 "/" 匹配的路由,它可以位于分组路由中。

库的选择

  • 不要使用 React Native 中已移除的模块,例如 Picker、WebView、SafeAreaView 或 AsyncStorage
  • 不要使用旧版 expo-permissions
  • 使用 expo-audio,而非 expo-av
  • 使用 expo-video,而非 expo-av
  • 使用 expo-image 并通过 source="sf:name" 显示 SF Symbols,不要使用 expo-symbols 或 @expo/vector-icons
  • 使用 react-native-safe-area-context,而非 react-native SafeAreaView
  • 使用 process.env.EXPO_OS,而非 Platform.OS
  • 使用 React.use,而非 React.useContext
  • 使用 expo-image 的 Image 组件,而非内置元素 img
  • 使用 expo-glass-effect 实现液态玻璃背景

响应式设计

  • 根组件始终应包裹在滚动视图中,以实现响应式布局
  • 使用 <ScrollView contentInsetAdjustmentBehavior="automatic" />,而非 <SafeAreaView>,以更智能地调整安全区域内边距
  • FlatList 和 SectionList 也应设置 contentInsetAdjustmentBehavior="automatic"
  • 使用 flexbox,而非 Dimensions API
  • 测量屏幕尺寸时,始终优先使用 useWindowDimensions,而非 Dimensions.get()

行为

  • 在 iOS 上有条件地使用 expo-haptics,让体验更出色
  • 使用内置触觉反馈的视图,例如 React Native 的 <Switch /> 和 @react-native-community/datetimepicker
  • 路由属于 Stack 时,其第一个子元素通常应为设置了 contentInsetAdjustmentBehavior="automatic" 的 ScrollView
  • 向页面添加 ScrollView 时,它通常应是路由组件中的第一个组件
  • 优先使用 Stack.Screen 选项中的 headerSearchBarOptions 添加搜索栏
  • 对包含可能需要复制的数据的文本,使用 <Text selectable /> 属性
  • 考虑将大数字格式化为 1.4M 或 38k 等形式
  • 除非在 webview 或 Expo DOM 组件中,否则不要使用 'img' 或 'div' 等内置元素

样式

遵循 Apple 人机界面指南。

通用样式规则

  • 优先使用 flex gap,而不是 margin 和 padding 样式
  • 尽可能优先使用 padding,而不是 margin
  • 始终考虑安全区域,可通过堆栈标题、标签页或 ScrollView/FlatList 的 contentInsetAdjustmentBehavior="automatic" 实现
  • 确保同时考虑顶部和底部安全区域插入值
  • 除非复用样式更快捷,否则使用内联样式,而不是 StyleSheet.create
  • 为状态变化添加进入和退出动画
  • 创建圆角时使用 { borderCurve: 'continuous' },胶囊形状除外
  • 始终使用导航堆栈标题,而不是在页面上自定义文本元素
  • 为 ScrollView 添加内边距时,使用 contentContainerStyle 设置 padding 和 gap,而不是在 ScrollView 本身设置 padding(可减少裁切)
  • 不支持 CSS 和 Tailwind,请使用内联样式

文本样式

  • 为每个显示重要数据或错误消息的 <Text/> 元素添加 selectable 属性
  • 计数器应使用 { fontVariant: 'tabular-nums' },以保持对齐

阴影

使用 CSS boxShadow 样式属性。绝不要使用旧版 React Native 阴影或 elevation 样式。

<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />

支持 inset 阴影。

导航

链接

使用来自 'expo-router' 的 <Link href="/path" /> 在路由之间导航。

import { Link } from 'expo-router';

// Basic link
<Link href="/path" />

// Wrapping custom components
<Link href="/path" asChild>
  <Pressable>...</Pressable>
</Link>

尽可能添加 <Link.Preview>,遵循 iOS 规范。经常添加上下文菜单和预览,以增强导航体验。

堆栈

  • 始终使用 _layout.tsx 文件定义堆栈
  • 使用来自 'expo-router/stack' 的 Stack 创建原生导航堆栈
页面标题

在 Stack.Screen 选项中设置页面标题:

<Stack.Screen options={{ title: "Home" }} />

上下文菜单

为 Link 组件添加长按上下文菜单:

import { Link } from "expo-router";

<Link href="/settings" asChild>
  <Link.Trigger>
    <Pressable>
      <Card />
    </Pressable>
  </Link.Trigger>
  <Link.Menu>
    <Link.MenuAction
      title="Share"
      icon="square.and.arrow.up"
      onPress={handleSharePress}
    />
    <Link.MenuAction
      title="Block"
      icon="nosign"
      destructive
      onPress={handleBlockPress}
    />
    <Link.Menu title="More" icon="ellipsis">
      <Link.MenuAction title="Copy" icon="doc.on.doc" onPress={() => {}} />
      <Link.MenuAction
        title="Delete"
        icon="trash"
        destructive
        onPress={() => {}}
      />
    </Link.Menu>
  </Link.Menu>
</Link>;

链接预览

经常使用链接预览,以增强导航体验:

<Link href="/settings">
  <Link.Trigger>
    <Pressable>
      <Card />
    </Pressable>
  </Link.Trigger>
  <Link.Preview />
</Link>

链接预览可与上下文菜单配合使用。

模态页面

以模态页面的形式呈现屏幕:

<Stack.Screen name="modal" options={{ presentation: "modal" }} />

建议使用此方式,而不是构建自定义模态组件。

表单页

以动态表单页的形式呈现屏幕:

<Stack.Screen
  name="sheet"
  options={{
    presentation: "formSheet",
    sheetGrabberVisible: true,
    sheetAllowedDetents: [0.5, 1.0],
    contentStyle: { backgroundColor: "transparent" },
  }}
/>
  • 使用 contentStyle: { backgroundColor: "transparent" } 可使背景在 iOS 26+ 上呈现液态玻璃效果。

常见路由结构

在每个标签页中包含堆栈的标准应用布局:

app/
  _layout.tsx — <NativeTabs />
  (index,search)/
    _layout.tsx — <Stack />
    index.tsx — Main list
    search.tsx — Search view
// app/_layout.tsx
import { NativeTabs, Icon, Label } from "expo-router/unstable-native-tabs";
import { Theme } from "../components/theme";

export default function Layout() {
  return (
    <Theme>
      <NativeTabs>
        <NativeTabs.Trigger name="(index)">
          <Icon sf="list.dash" />
          <Label>Items</Label>
        </NativeTabs.Trigger>
        <NativeTabs.Trigger name="(search)" role="search" />
      </NativeTabs>
    </Theme>
  );
}

创建共享分组路由,以便两个标签页都能推送共同的屏幕:

// app/(index,search)/_layout.tsx
import { Stack } from "expo-router/stack";
import { PlatformColor } from "react-native";

export default function Layout({ segment }) {
  const screen = segment.match(/\((.*)\)/)?.[1]!;
  const titles: Record<string, string> = { index: "Items", search: "Search" };

  return (
    <Stack
      screenOptions={{
        headerTransparent: true,
        headerShadowVisible: false,
        headerLargeTitleShadowVisible: false,
        headerLargeStyle: { backgroundColor: "transparent" },
        headerTitleStyle: { color: PlatformColor("label") },
        headerLargeTitle: true,
        headerBlurEffect: "none",
        headerBackButtonDisplayMode: "minimal",
      }}
    >
      <Stack.Screen name={screen} options={{ title: titles[screen] }} />
      <Stack.Screen name="i/[id]" options={{ headerLargeTitle: false }} />
    </Stack>
  );
}

评论

支持 Markdown