npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xiao-ying/miniapp-ui

v1.3.5

Published

Material 3 flavored UI kit for XiaoYing miniapps, built with React and Tailwind CSS plugin support.

Readme

@xiao-ying/miniapp-ui

面向 XiaoYing miniapp 的 React UI 组件库,遵循 Material 3 设计语言,并提供 Tailwind CSS 插件支持。组件与 @xiao-ying/miniapp-sdk@xiao-ying/miniapp-hooks 打通,可自动获取宿主亮度、主题色 seed、震动等能力。

安装

pnpm add @xiao-ying/miniapp-ui

Tailwind 配置

请添加插件并扫描本包,以确保组件使用到的工具类被正确输出。

  • Tailwind v4(CSS-first):

    /* e.g. src/index.css */
    @import "tailwindcss";
    @source "./src/**/*.{ts,tsx}", "./node_modules/@xiao-ying/miniapp-ui/**/*.{js,ts,tsx}";
    @plugin "@xiao-ying/miniapp-ui/tailwind-plugin";
  • Tailwind v3+(配置文件):

    // tailwind.config.ts
    import { xyMaterial3Plugin } from '@xiao-ying/miniapp-ui/tailwind-plugin'
    
    export default {
      content: ['./index.html', './src/**/*.{ts,tsx}', './node_modules/@xiao-ying/miniapp-ui/**/*.{js,ts,tsx}'],
      plugins: [xyMaterial3Plugin()]
    }

插件默认会在 :root 注入浅色变量、在 [data-theme="dark"] 注入深色变量。插件还会提供 Material 3 的颜色语义、圆角、海拔阴影等 CSS 变量,并映射 Tailwind 的颜色/圆角/阴影别名(例如 text-errorbg-surfacerounded-mdshadow-lg)到对应的 M3 语义。

颜色别名与使用规范

请优先使用 text-* / bg-* / border-* 颜色别名,不要在 className 中直接写 var(...)

  • 推荐文本类: text-on-surfacetext-on-surface-varianttext-primarytext-error
  • 推荐背景类: bg-surfacebg-surface-containerbg-surface-container-highbg-primary-containerbg-error-containerbg-warning-containerbg-success-container
  • 推荐边框类: border-outlineborder-outline-variantborder-borderborder-border-strong
  • 禁止写法: text-[var(--xy-m3-on-surface-variant)]bg-[var(--xy-m3-surface-container-high)]border-[var(--xy-m3-outline-variant)]

业务语义色

业务小程序和 Codex 生成页面时,应优先使用本包 Tailwind 颜色别名,不要为常见状态重复定义项目级颜色。内置语义色会随宿主 themeSeedColor 与 light/dark 自动更新。

| 用途 | Tailwind 颜色 | CSS 变量 | | --- | --- | --- | | 主操作/品牌强调 | primary / on-primary / primary-container / on-primary-container | --xy-sem-accent / --xy-sem-on-accent / --xy-sem-accent-variant / --xy-sem-on-accent-variant | | 错误、失败、删除、危险操作 | error / on-error / error-container / on-error-container | --xy-sem-error / --xy-sem-on-error / --xy-sem-error-container / --xy-sem-on-error-container | | 警告、待处理、需要注意 | warning / on-warning / warning-container / on-warning-container | --xy-sem-warning / --xy-sem-on-warning / --xy-sem-warning-container / --xy-sem-on-warning-container | | 成功、完成、通过 | success / on-success / success-container / on-success-container | --xy-sem-success / --xy-sem-on-success / --xy-sem-success-container / --xy-sem-on-success-container | | 页面、卡片、列表背景 | surface / surface-subtle / surface-strong / surface-disabled | --xy-sem-surface-base / --xy-sem-surface-subtle / --xy-sem-surface-strong / --xy-sem-surface-disabled | | 边框 | border / border-strong / border-disabled | --xy-sem-border / --xy-sem-border-strong / --xy-sem-border-disabled |

Tailwind 颜色不提供 danger / info 别名:危险或破坏性动作统一使用 error,普通信息提示优先使用 primarysurface-* 或组件默认样式。Button / IconButton 可使用 tone="danger" 作为删除等破坏性操作的语义别名,内部仍使用 error 语义色;variant="danger" 是 filled danger 的快捷样式。

Button / IconButtontone 支持 primarysecondarytertiaryerrordangerwarningsuccessBadge / NavBadge / LinearProgress / CircularProgresstone 支持 primarysecondarytertiaryerrorwarningsuccess。业务状态优先用这些 tone 或上表 Tailwind alias,不要再给常见状态手写一套项目内颜色。

主题与变量

你可以在自己的 index.css 中覆盖变量来自定义配色与圆角:

:root {
  --xy-m3-primary: #0060a9;
  --xy-m3-on-primary: #ffffff;
  /* ...其他 Material 3 tokens... */
  --xy-radius-md: 14px;
}

[data-theme="dark"] {
  --xy-m3-primary: #a2c9ff;
  --xy-m3-on-primary: #00315b;
  /* ...dark tokens... */
}

Tailwind 插件会生成 shadow-xy-*rounded-xy-* 等工具类并读取这些变量。若需完全手动控制,可使用 xyMaterial3Plugin({ emitBase: false }) 关闭默认注入。

@xiao-ying/[email protected] 起,入口会读取 xy.appearance().themeSeedColor,并根据宿主 seed color 生成 Material 3 light/dark 配色;旧引擎或缺失 appearance 注入时保持默认小应蓝 #4da3ffbrightnessChange 只切换 light/dark scheme,不改变 seed color。

按钮、ButtonGroup、ToggleButton 与震动反馈

import { Button, ButtonGroup, IconButton, ToggleButton } from '@xiao-ying/miniapp-ui'

// onPressed 为 undefined/null 时视为禁用(Flutter 风格)
<Button variant="filled" tone="primary" onPressed={() => xy.showToast({ title: 'Clicked' })}>
  触发操作
</Button>

<Button variant="danger" onPressed={() => xy.showToast({ title: '已删除' })}>
  删除
</Button>

<Button variant="outlined" tone="danger" onPressed={() => xy.showToast({ title: '已删除' })}>
  删除
</Button>

<ButtonGroup aria-label="审批操作">
  <Button variant="outlined" onPressed={() => xy.showToast({ title: '已取消' })}>
    取消
  </Button>
  <Button onPressed={() => xy.showToast({ title: '已确认' })}>
    确认
  </Button>
</ButtonGroup>

// IconButton 示例,支持自定义 tone 与 className
<IconButton
  variant="tonal"
  tone="secondary"
  className="shadow-xy-2"
  onPressed={() => xy.vibrate({ impact: 'medium' })}
>
  <SomeIcon />
</IconButton>

<ToggleButton
  pressed={advancedEnabled}
  onPressedChange={setAdvancedEnabled}
  leadingIcon={<IconSparkles aria-hidden />}
>
  高级功能
</ToggleButton>

<ToggleButton
  pressed={advancedEnabled}
  onPressedChange={setAdvancedEnabled}
  states={{
    pressed: { icon: <IconSparkles aria-hidden />, label: '高级功能已开启' },
    unpressed: { icon: <IconAdjustmentsHorizontal aria-hidden />, label: '开启高级功能' }
  }}
/>

ButtonIconButtonToggleButton 默认会在点击时调用 xy.vibrate(可通过 vibrateImpact 指定具体 XYVibrateImpact 或传入 false 禁用)。ButtonGroup 可将直接子级 Button / IconButton / ToggleButton 合并为接壤按钮组,并在组内移除单个按钮的按压缩放,只保留状态层反馈。ToggleButton 使用 aria-pressed 表达开启/关闭状态,支持 pressed / defaultPressed / onPressedChange。固定文本或固定图标用 children / leadingIcon;两种状态文案或图标不同用 states,组件会预留两态最大宽度,避免 wrap 布局跳动。这两种用法在类型层面互斥。

Toolbar

import { Button, ButtonGroup, Dock, Toolbar } from '@xiao-ying/miniapp-ui'

<Dock placement="bottom" offset={12} inset={16} contentClassName="flex justify-center">
  <div className="w-full max-w-xl rounded-[1.75rem] border border-outline-variant/70 bg-surface/45 p-2 shadow-lg backdrop-blur-xl">
    <Toolbar aria-label="详情操作" fullWidth gap={12}>
      <Toolbar.Section>
        <Button key="back" variant="tonal" tone="secondary" onPressed={() => window.history.back()}>
          返回
        </Button>
      </Toolbar.Section>

      <Toolbar.Spacer />

      <Toolbar.Section align="end" gap={8}>
        {canRenew ? (
          <Button key="renew" variant="outlined" onPressed={renew}>
            续期
          </Button>
        ) : null}
        <ButtonGroup key="manage" aria-label="管理操作">
          <Button variant="tonal" onPressed={edit}>
            编辑
          </Button>
          <Button variant="outlined" tone="danger" onPressed={hide}>
            下架
          </Button>
        </ButtonGroup>
      </Toolbar.Section>
    </Toolbar>
  </div>
</Dock>

Toolbar 是操作布局容器,不提供背景、边框、模糊、阴影、padding 或最大宽度;这些外框视觉与宽度约束由外层 div / Card / Surface / 业务容器决定。Toolbar 默认按内容自适应,传 fullWidth 后占满父容器;使用 Toolbar.Spacer 做左右分隔时,通常应同时让 Toolbar 或外层容器拥有明确宽度。gap 可传 number(按 px 处理)或 CSS 长度字符串;Toolbar.Section 也可单独传 gap 覆盖组内间距。ButtonGroup 仍只接收直接的 Button-like 子级。Toolbar.Section 会自动对自己的直接子项做出现/消失收缩动画,ButtonGroup 会作为一个整体子项动画,不会深入组内按钮。条件渲染的直接子项请提供稳定 key,避免插入/删除时按 index 兜底导致动画识别不准。

Tooltip

import { Tooltip, IconButton } from '@xiao-ying/miniapp-ui'
import { IconInfoCircle } from '@tabler/icons-react'

<Tooltip content="这是一个提示">
  <IconButton onPressed={() => {}}>
    <IconInfoCircle />
  </IconButton>
</Tooltip>

Sheet

Sheet 支持普通受控组件式,也支持通过 OverlayProvider 进行命令式展示。dismissible={false} 会关闭内置关闭入口,命令式内容仍可通过渲染函数里的 close() 主动收起。

import { Button, OverlayProvider, Sheet, useSheet } from '@xiao-ying/miniapp-ui'

<Sheet
  open={open}
  title="选择课表"
  dismissible={false}
  onClose={() => setOpen(false)}
>
  <Button onPressed={() => setOpen(false)}>完成</Button>
</Sheet>

const SheetButton = () => {
  const sheet = useSheet()

  return (
    <Button
      onPressed={() => {
        sheet.show({
          title: '选择课表',
          dismissible: false,
          content: ({ close }) => (
            <Button onPressed={() => close()}>完成</Button>
          )
        })
      }}
    >
      打开 Sheet
    </Button>
  )
}

<OverlayProvider>
  <SheetButton />
</OverlayProvider>

Dialog

Dialog 支持普通受控组件式,也支持通过 OverlayProvider 进行命令式展示。它只负责 HTML 浮层,不替代 xy.showModal 这类原生 SDK 能力。

import { Button, Dialog, DialogActions, OverlayProvider, useDialog } from '@xiao-ying/miniapp-ui'

<Dialog
  open={open}
  title="进入前确认"
  dismissible={false}
  actions={
    <DialogActions>
      <Button variant="outlined" onPressed={() => setOpen(false)}>稍后再玩</Button>
      <Button onPressed={() => confirm()}>我已满 18 周岁</Button>
    </DialogActions>
  }
>
  为了营造更健康的游戏环境,仅面向已满 18 周岁的同学开放。
</Dialog>

const ConfirmButton = () => {
  const dialog = useDialog()

  return (
    <Button
      onPressed={async () => {
        const controller = dialog.show<'confirm' | 'cancel'>({
          title: '进入前确认',
          content: ({ close }) => (
            <div className="space-y-4">
              <div>确认已满 18 周岁后即可继续游玩。</div>
              <DialogActions>
                <Button variant="outlined" onPressed={() => close('cancel')}>取消</Button>
                <Button onPressed={() => close('confirm')}>确认</Button>
              </DialogActions>
            </div>
          )
        })
        const result = await controller.closed
        if (result === 'confirm') {
          // continue
        }
      }}
    >
      打开确认
    </Button>
  )
}

<OverlayProvider>
  <ConfirmButton />
</OverlayProvider>

Toast

Toast 支持受控组件式,也支持通过 OverlayProvider + useToast() 命令式展示。默认样式按 Material 3 snackbar 处理,使用 inverse-surface 容器、inverse-on-surface 文本与 elevation 3。

import { Button, OverlayProvider, useToast } from '@xiao-ying/miniapp-ui'

const SaveButton = () => {
  const toast = useToast()

  return (
    <Button
      onPressed={() => toast.show({
        type: 'success',
        title: '已保存',
        subtitle: '草稿会在下次打开时恢复',
        duration: 'short'
      })}
    >
      保存
    </Button>
  )
}

<OverlayProvider toast={{ placement: 'bottom', maxVisible: 5 }}>
  <SaveButton />
</OverlayProvider>

Surface 与卡片

Card 使用建议

业务页面默认推荐使用 Cardfilled 变体,也就是 <Card><Card variant="filled">filled 是最稳定的页面分区样式,适合设置、表单、状态摘要、功能面板等大多数业务场景。

outlined 适合弱化背景、强调边界的列表项或嵌入式信息块。outlined 不传 tone 时默认使用 surface 背景,避免普通 outlined 卡片自带与页面背景不同的容器色;只有显式传入 tone 时,才展示对应背景。

尽量不要把 elevated 当作普通卡片默认样式。elevated 主要用于 Card 承载在另一个 Card / Surface 之上、浮层、局部预览,或确实需要用阴影表达层级分离的场景。页面上大量使用 elevated card 会增加视觉噪声。

import { Card, Surface } from '@xiao-ying/miniapp-ui'

<Card title="存储状态" subtitle="Miniapp storage powered by xy">
  <p className="text-sm text-on-surface-variant">将内容放在这里。</p>
</Card>

<Card variant="outlined" title="列表项" subtitle="弱化背景,只强调边界">
  <p className="text-sm text-on-surface-variant">适合嵌入式信息块。</p>
</Card>

<Card variant="elevated" title="浮层中的卡片" subtitle="仅在需要层级分离时使用">
  <p className="text-sm text-on-surface-variant">例如卡片上再次承载卡片。</p>
</Card>

<Surface tone="container-high" elevation="level2" className="p-6">
  <p>自定义外壳与内容区域</p>
</Surface>

TextArea 自动高度

import { TextArea } from '@xiao-ying/miniapp-ui'

<TextArea
  label="描述"
  value={content}
  onValueChange={setContent}
  rows={2}      // autoHeight=true 时表示最小行数
  autoHeight    // 默认 false
  maxRows={8}   // 可选:达到 8 行后内部滚动
/>
  • autoHeight 默认关闭,不影响现有固定高度行为。
  • rowsautoHeight=false 时仍是固定行数;在 autoHeight=true 时为最小行数。
  • maxRows 仅在 autoHeight=true 生效,不传表示不限制增长。

CounterInput

import { CounterInput } from '@xiao-ying/miniapp-ui'

<CounterInput
  label="购买数量"
  value={quantity}
  onValueChange={setQuantity}
  min={0}
  max={12}
  step={1}
  helperText={`当前 ${quantity} 件`}
/>

CounterInput 用于选择整数,支持受控/非受控、min / max / step、长按按钮快速增减、方向键增减、Home / End 跳到边界,以及 filled / outlined 两种视觉变体。输入过程中允许短暂为空,失焦后会归一化为合法整数。

Tabs、PageView 与选择控件

import { PageView, Picker, Tabs, SegmentedButton, Slider, usePageViewTabs } from '@xiao-ying/miniapp-ui'

const pageTabs = usePageViewTabs({
  items: [
    { label: '概览', value: 'overview' },
    { label: '媒体', value: 'media' },
    { label: '存储', value: 'storage' }
  ],
  defaultValue: 'overview'
})

<Tabs
  {...pageTabs.tabsProps}
/>

<PageView
  ref={pageTabs.pageViewRef}
  {...pageTabs.pageViewProps}
  renderPage={(index) => <section>第 {index + 1} 页</section>}
/>

<SegmentedButton
  items={[
    { label: '全部', value: 'all' },
    { label: '进行中', value: 'active' },
    { label: '已完成', value: 'done' }
  ]}
  value={filter}
  onValueChange={setFilter}
  fullWidth
/>

<Slider label="音量" value={volume} onValueChange={setVolume} />

<Picker
  columns={[
    {
      key: 'hour',
      label: '小时',
      options: Array.from({ length: 24 }, (_, hour) => ({
        label: `${String(hour).padStart(2, '0')} 时`,
        value: hour
      }))
    },
    {
      key: 'minute',
      label: '分钟',
      options: [0, 15, 30, 45].map((minute) => ({
        label: `${String(minute).padStart(2, '0')} 分`,
        value: minute
      }))
    }
  ]}
  value={time}
  onValueChange={setTime}
/>

Picker 是 inline 滚轮选择器,不内置弹层;需要底部弹出时可自行放进 Sheet。一个 Picker 可配置单列或多列,也可以在同一页面放置多个实例。

Scaffold 与悬浮底部导航

BottomNavigationBar 默认保持固定底栏样式;传入 variant="floating" 后会切换为底部悬浮的半透明毛玻璃样式。导航栏会按导航项数量自适应收缩并居中,避免宽屏下 item 被拉得过宽;如需恢复铺满父容器,可传 fullWidth,或用 maxWidth 指定最大宽度。配合 Scaffold 使用时,需要显式设置 bottomBarLayout="floating",由 Scaffold 负责定位、安全区和内容底部预留。

import { BottomNavigationBar, Scaffold } from '@xiao-ying/miniapp-ui'
import { IconCalendarWeek, IconHome, IconSchool, IconUserCircle } from '@tabler/icons-react'

<Scaffold
  title="课程表"
  bottomBarLayout="floating"
  bottomBar={
    <BottomNavigationBar
      variant="floating"
      items={[
        { label: '主页', icon: <IconHome />, path: '/' },
        { label: '课程表', icon: <IconCalendarWeek />, path: '/schedule' },
        { label: '校园', icon: <IconSchool />, path: '/campus' },
        { label: '我的', icon: <IconUserCircle />, path: '/me' }
      ]}
    />
  }
>
  {children}
</Scaffold>

自定义底部栏也可以通过 ScaffoldbottomBarLayoutbottomBarHeightbottomBarFloatingGap 调整同一套安全区与内容预留逻辑。standard 布局下若自定义底栏背景不是默认容器色,可传 bottomBarBackgroundColor 让 safe-area 背景保持一致。

Skeleton

Skeleton 是原子化骨架占位组件,推荐用 className 自由组合页面、卡片、列表和图片墙占位。

import { Skeleton } from '@xiao-ying/miniapp-ui'

<Skeleton className="h-4 w-32" />
<Skeleton variant="circle" className="h-12 w-12" />
<Skeleton variant="rect" className="aspect-video h-auto rounded-xl" />
<Skeleton animation="pulse" className="h-3 w-full" />

animation 支持 wave / pulse / false,默认是 wavevisible={false} 时会直接渲染 children,便于在同一处切换加载态和真实内容。

Divider

import { Divider } from '@xiao-ying/miniapp-ui'

<Divider />
<Divider>OR</Divider>
<Divider inset />

Host 托管图片

import { XyHostImage } from '@xiao-ying/miniapp-ui'

<XyHostImage
  src="https://example.edu/avatar/123"
  alt="avatar"
  headers={{ authorization: 'Bearer token' }}
  cacheKey="user-avatar-123"
  fallbackSrc="/images/avatar-fallback.png"
  className="h-12 w-12 rounded-full object-cover"
/>

XyHostImage 会按运行时自动选择图片加载策略:

  • App runtime 且 engineVersion >= 1.3.0:使用 xyimg://(宿主托管加载)。
  • App runtime 且低版本引擎:回退到 xy.downloadFile(组件内置去重缓存)。
  • 浏览器 runtime:直接使用原始 src

工具与组件清单

  • xyMaterial3Plugin(默认导出):Tailwind 插件,输出 Material 3 tokens、圆角、海拔阴影和颜色变量(默认注入,可 emitBase: false 关闭)。
  • defaultMaterial3Theme / schemeToCssVars / shapeToCssVars / xyElevations / defaultStateOpacity:主题与变量工具。
  • cn:基于 clsx + tailwind-merge 的 className 合并工具。
  • Button / ButtonGroup / IconButton / Toolbar / FloatingActionButton:按钮、接壤按钮组、操作条与悬浮按钮。
  • Surface / Card / Scaffold:容器与结构组件。
  • TextField / TextArea / CounterInput / Select / Picker / Checkbox / Radio / Switch / Slider / SegmentedButton:表单与输入控件。
  • Tabs / PageView / ListTile / BottomNavigationBar / NavBadge / Badge / Divider:导航与展示组件。
  • LinearProgress / CircularProgress / Skeleton:进度与加载占位。
  • Tooltip:悬浮提示,默认使用 portal 置于最顶层。
  • XyHostImage:宿主托管图片组件,支持 headers / cacheKey 与老引擎回退。
  • SafeArea:安全区内边距封装(CSS 变量 + env() 回退)。
  • useHapticFeedback(别名 useHapticPress):对 xy.vibrate 的通用封装。