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

@weifuwujs/components

v0.1.1

Published

UI components for weifuwu — 32 composable components built on weifuwu/client primitives

Readme

@weifuwujs/components

33 个 UI 组件 + 5 个浏览器原语 — 基于 weifuwu/client 信号系统,零额外运行时。

npm install @weifuwujs/components
# 14 kB, 零上游依赖 (peer: weifuwu >=0.33.0)

与 React 组件库的核心区别:组件接收 Signal<T> 作为 props,不需要 useState/onChange/value/setValue 配对。

import { Button, Modal, Table, toast } from '@weifuwujs/components'
import { signal } from 'weifuwu/client'

const isOpen = signal(false)
<Button onClick={() => isOpen.value = true}>弹窗</Button>
<Modal open={isOpen} title="提示">内容</Modal>
toast.success('操作成功')

模块总览

| 类别 | 组件 | 依赖原语 | |------|------|----------| | 原语 | createFocusTrap, createClickAway, scrollLock, rovingTabIndex, createFloating | — | | 布局 | Card, Divider, Space, Sidebar | — | | 按钮/操作 | Button, Dropdown, Popover, Tooltip | createFloating, createClickAway | | 表单输入 | Input, Textarea, Select, Checkbox, CheckboxGroup, RadioGroup, Switch, Slider | — | | 导航 | Tabs, Breadcrumb, Pagination, Steps, Sidebar | rovingTabIndex | | 数据展示 | Table, Badge, Avatar, Tag, Tree, Empty | — | | 反馈 | Alert, Toast, Progress, Spinner, Skeleton | — | | 弹出层 | Modal, Drawer, Popover, Tooltip, Dropdown | createFocusTrap, scrollLock, createFloating | | 折叠 | Accordion | — |


快速开始

import { signal, computed } from 'weifuwu/client'
import {
  // 布局
  Card, CardHeader, CardTitle, CardContent, CardFooter,
  Divider, Space,
  // 表单
  Button, Input, Textarea, Select, CheckboxGroup, RadioGroup,
  Switch, Slider,
  // 导航
  Tabs, TabList, Tab, TabPanel, Breadcrumb, Pagination, Steps, Sidebar,
  // 数据
  Table, Badge, Avatar, Tag, Tree, Empty,
  // 反馈
  Alert, toast, ToastContainer, Progress, Spinner, Skeleton,
  // 弹出层
  Modal, Drawer, Dropdown, Tooltip, Popover,
  // 折叠
  Accordion, AccordionItem,
  // 原语
  createFocusTrap, createClickAway, scrollLock, rovingTabIndex, createFloating,
} from '@weifuwujs/components'

// 所有 Signal 驱动的组件直接接收 signal
const count = signal(0)
const isOpen = signal(false)
const tab = signal('overview')

function App() {
  return (
    <Space gap="md">
      <Button onClick={() => count.value++}>{count.value}</Button>
      <Modal open={isOpen} title="提示">内容</Modal>
      <ToastContainer />
    </Space>
  )
}

组件详解

原语层

import { createFocusTrap, createClickAway, scrollLock, rovingTabIndex, createFloating } from '@weifuwujs/components'

| 函数 | 签名 | 说明 | |------|------|------| | createFocusTrap | (el: HTMLElement) => () => void | 焦点锁定在元素内,Tab/Shift+Tab 循环 | | createClickAway | (el: HTMLElement, fn) => () => void | 元素外点击检测 | | scrollLock | (el?: HTMLElement) => () => void | 背景滚动锁定,补偿滚动条宽度 | | rovingTabIndex | (container, getItems, opts?) => () => void | 键盘箭头导航,支持 orientation/wrap | | createFloating | (anchor, floating, opts?) => () => void | 浮动定位,12 方向 + autoFlip |


布局

Card

<Card>
  <CardHeader>
    <CardTitle>标题</CardTitle>
    <CardDescription>描述文字</CardDescription>
  </CardHeader>
  <CardContent>主要内容</CardContent>
  <CardFooter><Button>保存</Button></CardFooter>
</Card>

| 组件 | Props | 说明 | |------|-------|------| | Card | class? | 白色圆角卡片容器 | | CardHeader | class? | 顶部区域,带下分割线 | | CardTitle | class? | <h3> 标题 | | CardDescription | class? | 灰色说明文字 | | CardContent | class? | 主内容区域 | | CardFooter | class? | 底部区域,带上分割线 |

Divider

<Divider />                    // 简单分割线
<Divider label="或" />         // 带文字分割线

| Props | 默认 | 说明 | |-------|------|------| | label? | — | 分割线中间文字 | | class? | — | 额外 class |

Space

<Space gap="md">
  <Button>保存</Button>
  <Button variant="outline">取消</Button>
</Space>
<Space direction="vertical" gap="lg">...</Space>

| Props | 默认 | 说明 | |-------|------|------| | direction | 'horizontal' | 排列方向 | | gap | 'md' | 间距:xs/sm/md/lgnumber | | align | 'center' | 对齐方式 |

Sidebar

<Sidebar
  collapsed={sidebarOpen}
  items={[
    { key: '/dashboard', icon: '📊', label: '概览' },
    { type: 'group', label: '数据' },
    { key: '/dashboard/users', icon: '👥', label: '用户管理', badge: '3' },
  ]}
/>

自动集成 ctx.route.path 高亮当前项。点击调用 ctx.app.navigate()

| Props | 默认 | 说明 | |-------|------|------| | items | — | 菜单项数组 | | collapsed | — | 折叠状态信号 | | onNavigate | — | 导航回调 |

SidebarItem 类型:{ key?, label, icon?, type?: 'item'|'group', children?, badge? }


按钮/操作

Button

<Button variant="primary" size="md" disabled onClick={() => save()}>
  保存
</Button>
<Button variant="outline" size="sm">取消</Button>
<Button variant="danger" size="lg">删除</Button>
<Button variant="ghost">更多</Button>

| Props | 默认 | 说明 | |-------|------|------| | variant | 'primary' | primary/secondary/outline/ghost/danger | | size | 'md' | sm/md/lg | | disabled | false | 禁用状态 | | type | 'button' | 原生 button type |

Dropdown

<Dropdown
  trigger={<Button variant="outline">操作</Button>}
  items={[
    { label: '编辑', onClick: () => edit(item) },
    { label: '删除', onClick: () => remove(item), variant: 'danger' },
    { type: 'separator' },
    { label: '详情', onClick: () => view(item) },
  ]}
  placement="bottom-end"
/>

| Props | 默认 | 说明 | |-------|------|------| | trigger | — | 触发按钮 (JSX) | | items | — | DropdownItem[] | | placement | 'bottom-end' | 菜单弹出位置 |

DropdownItem: { label?, onClick?, variant?: 'default'|'danger', disabled?, icon?, type?: 'item'|'separator' }

Tooltip

<Tooltip content="保存草稿" placement="top">
  <Button>💾</Button>
</Tooltip>

| Props | 默认 | 说明 | |-------|------|------| | content | — | 提示文字 | | placement | 'top' | 12 个方向 | | delay | 200 | 悬停延迟 (ms) |

Popover

<Popover
  content={<div class="p-4"><p>弹出内容</p></div>}
  placement="bottom"
  trigger="click"
>
  <Button>点击弹出</Button>
</Popover>

| Props | 默认 | 说明 | |-------|------|------| | content | — | 弹出内容 (任意 JSX) | | placement | 'bottom' | 12 个方向 | | trigger | 'click' | clickhover |


表单输入

Input

const name = signal('')
<Input value={name} placeholder="请输入姓名" />

<Input value={email} label="邮箱" error={emailError} required />

| Props | 默认 | 说明 | |-------|------|------| | value | — | Signal<string> | | label | — | 输入框标签 | | error | — | 错误信息 (string 或 Signal) | | required | — | 是否必填 | | type | 'text' | 原生 input type |

Textarea

<Textarea value={bio} placeholder="自我介绍" rows={4} autoResize />
<Textarea label="备注" error={err} />

| Props | 默认 | 说明 | |-------|------|------| | value | — | Signal<string> | | autoResize | true | 自动伸缩高度 | | rows | 3 | 初始行数 | | label | — | 标签 |

Select

const city = signal('')
<Select
  value={city}
  options={[
    { value: 'beijing', label: '北京' },
    { value: 'shanghai', label: '上海' },
  ]}
  placeholder="选择城市"
  searchable
/>

| Props | 默认 | 说明 | |-------|------|------| | value | — | Signal<string> | | options | — | SelectOption[] | | searchable | false | 是否可搜索 | | placeholder | '请选择' | 占位文字 |

Checkbox / CheckboxGroup

const checked = signal(false)
<Checkbox value={checked}>同意条款</Checkbox>

const selected = signal<string[]>(['vue'])
<CheckboxGroup value={selected} options={[
  { value: 'react', label: 'React' },
  { value: 'vue', label: 'Vue' },
]} />

Checkbox Props: value?: Signal<boolean>, disabled?, children?

CheckboxGroup Props: value?: Signal<string[]>, options: CheckboxOption[], direction?: 'horizontal'|'vertical'

RadioGroup

const selected = signal('option-1')
<RadioGroup value={selected} options={[
  { value: 'option-1', label: '选项一' },
  { value: 'option-2', label: '选项二' },
]} />

| Props | 默认 | 说明 | |-------|------|------| | value | — | Signal<string> | | options | — | RadioOption[] | | direction | 'vertical' | 排列方向 |

Switch

const enabled = signal(false)
<Switch value={enabled} label="启用通知" />

| Props | 默认 | 说明 | |-------|------|------| | value | — | Signal<boolean> | | label | — | 显示标签 | | disabled | — | 禁用状态 |

Slider

const value = signal(50)
<Slider value={value} min={0} max={100} step={10} showValue />

| Props | 默认 | 说明 | |-------|------|------| | value | — | Signal<number> | | min | 0 | 最小值 | | max | 100 | 最大值 | | step | 1 | 步长 | | showValue | false | 显示当前值 |


导航

Tabs

const tab = signal('overview')
<Tabs value={tab}>
  <TabList>
    <Tab value="overview">概览</Tab>
    <Tab value="details">详情</Tab>
  </TabList>
  <TabPanel value="overview">概览内容</TabPanel>
  <TabPanel value="details">详情内容</TabPanel>
</Tabs>

| 组件 | Props | 说明 | |------|-------|------| | Tabs | value: Signal<string>, children | 容器 | | TabList | children | Tab 按钮容器 | | Tab | value: string, disabled? | 单个标签页按钮 | | TabPanel | value: string, children | 标签页内容 |

Accordion

<Accordion>
  <AccordionItem value="1" title="标题一">内容一</AccordionItem>
  <AccordionItem value="2" title="标题二">内容二</AccordionItem>
</Accordion>

| Props | 默认 | 说明 | |-------|------|------| | value | — | Signal<string\|null> 外部控制 | | defaultValue | — | 默认展开项 |

AccordionItem: value: string, title: string, children

Breadcrumb

<Breadcrumb items={[
  { label: '首页', href: '/' },
  { label: '用户管理', href: '/users' },
  { label: '详情' },
]} separator="·"
/>

| Props | 默认 | 说明 | |-------|------|------| | items | — | BreadcrumbItem[] | | separator | '/' | 分隔符文字 |

BreadcrumbItem: { label: string, href?: string }

Pagination

<Pagination current={page} total={100} onChange={(p) => page.value = p} showTotal />

| Props | 默认 | 说明 | |-------|------|------| | current | — | 当前页 (number 或 Signal) | | total | — | 总条数 | | pageSize | 10 | 每页条数 | | showTotal | false | 显示"共 N 条" |

Steps

const step = signal(1)
<Steps current={step}>
  <Steps.Step title="基本信息" description="填写姓名和邮箱" />
  <Steps.Step title="详细资料" />
  <Steps.Step title="完成" />
</Steps>

| Props | 默认 | 说明 | |-------|------|------| | current | — | 当前步骤 (number 或 Signal) | | direction | 'vertical' | vertical/horizontal |

Steps.Step: title: string, description?: string


数据展示

Table

<Table
  columns={[
    { key: 'name', title: '姓名', sortable: true },
    { key: 'age', title: '年龄', sortable: true },
    { key: 'city', title: '城市' },
    { key: 'action', title: '操作', render: (_, record) => <Button>编辑</Button> },
  ]}
  data={[
    { id: 1, name: '张三', age: 28, city: '北京' },
    { id: 2, name: '李四', age: 32, city: '上海' },
  ]}
  rowKey="id"
  striped
  bordered
/>

| Props | 默认 | 说明 | |-------|------|------| | columns | — | TableColumn[] | | data | — | 数据数组 | | rowKey | 'id' | 行 key 字段或函数 | | bordered | false | 单元格边框 | | striped | false | 斑马纹 | | size | 'md' | sm/md/lg | | emptyText | '暂无数据' | 空数据提示 |

TableColumn: { key, title, sortable?, width?, render?, align? }

Badge

<Badge variant="success">已通过</Badge>
<Badge variant="danger">错误</Badge>
<Badge variant="outline">草稿</Badge>

| Props | 默认 | 说明 | |-------|------|------| | variant | 'default' | default/secondary/success/warning/danger/outline |

Avatar

<Avatar src="/user.jpg" alt="用户" />
<Avatar fallback="张" />
<AvatarGroup>
  <Avatar src="/a.jpg" />
  <Avatar src="/b.jpg" />
</AvatarGroup>

| Props | 默认 | 说明 | |-------|------|------| | src | — | 图片 URL | | fallback | — | 无图片时显示文字 | | size | 'md' | sm/md/lg |

Tag

<Tag>前端</Tag>
<Tag variant="success" closable onClose={() => remove(tag)}>已通过</Tag>

| Props | 默认 | 说明 | |-------|------|------| | variant | 'default' | default/primary/success/warning/danger | | closable | — | 是否可关闭 | | onClose | — | 关闭回调 | | size | 'md' | sm/md |

Tree

const treeData = [
  {
    key: 'src', title: 'src',
    children: [
      { key: 'src/index.ts', title: 'index.ts' },
      { key: 'src/components', title: 'components', children: [
        { key: 'src/components/button.tsx', title: 'button.tsx' },
      ]},
    ],
  },
]
<Tree data={treeData} defaultExpandedKeys={['src']} onSelect={(k) => console.log(k)} />

| Props | 默认 | 说明 | |-------|------|------| | data | — | TreeNode[] | | defaultExpandedKeys | — | 默认展开节点 key 数组 | | selectedKey | — | Signal<string\|null> 选中控制 | | onSelect | — | 选中回调 |

TreeNode: { key, title, icon?, children?, disabled? }

Empty

<Empty description="暂无数据" />
<Empty icon="📦">
  <Button>新建</Button>
</Empty>

| Props | 默认 | 说明 | |-------|------|------| | icon | '📭' | 图标 | | description | '暂无数据' | 描述文字 |


反馈

Alert

<Alert variant="error" title="操作失败">请稍后重试</Alert>
<Alert variant="success">保存成功</Alert>
<Alert variant="warning">即将到期</Alert>

| Props | 默认 | 说明 | |-------|------|------| | variant | 'info' | info/success/warning/error | | title | — | 标题 |

Toast

// 1. 在 App 根组件挂载一次
<ToastContainer />

// 2. 任意位置调用
toast.success('保存成功')
toast.error('操作失败', { duration: 5000 })
toast.info('提示信息')
toast.warning('警告')

| 函数 | 说明 | |------|------| | toast.success(msg, opts?) | 成功提示 | | toast.error(msg, opts?) | 错误提示 | | toast.info(msg, opts?) | 信息提示 | | toast.warning(msg, opts?) | 警告提示 |

ToastOptions: { duration?: number, description?: string }

Progress

<Progress value={65} />
<Progress value={100} variant="success" showLabel />
<Progress value={30} variant="warning" size="sm" />

| Props | 默认 | 说明 | |-------|------|------| | value | 0 | 当前值 | | max | 100 | 最大值 | | variant | 'default' | default/success/warning | | size | 'md' | sm/md | | showLabel | false | 显示百分比 |

Spinner

<Spinner />
<Spinner size="lg" />
<Spinner size="sm" class="text-blue-600" />

| Props | 默认 | 说明 | |-------|------|------| | size | 'md' | sm/md/lg |

Skeleton

<Skeleton class="h-4 w-32" />
<Skeleton variant="circle" size="lg" />
<Skeleton variant="rect" class="h-32 w-full" />

| Props | 默认 | 说明 | |-------|------|------| | variant | 'text' | text/circle/rect | | size | 'md' | sm/md/lg (circle 模式) |


弹出层

Modal

const isOpen = signal(false)
<Button onClick={() => isOpen.value = true}>打开弹窗</Button>

<Modal open={isOpen} title="确认删除" size="sm" onClose={() => isOpen.value = false}>
  <p>确定要删除这条记录吗?</p>
  <Space>
    <Button variant="danger" onClick={onDelete}>删除</Button>
    <Button variant="outline" onClick={() => isOpen.value = false}>取消</Button>
  </Space>
</Modal>

| Props | 默认 | 说明 | |-------|------|------| | open | — | Signal<boolean> 控制显示 | | title | — | 对话框标题 | | size | 'md' | sm/md/lg/full | | closeOnOverlay | true | 点击遮罩关闭 |

功能:焦点锁定、背景滚动锁定、Escape 关闭。

Drawer

const isOpen = signal(false)
<Drawer open={isOpen} title="详情" placement="right" size="md" onClose={() => isOpen.value = false}>
  <p>抽屉内容</p>
</Drawer>

| Props | 默认 | 说明 | |-------|------|------| | open | — | Signal<boolean> | | title | — | 标题 | | placement | 'right' | left/right | | size | 'md' | sm/md/lg | | closeOnOverlay | true | 点击遮罩关闭 |

功能:焦点锁定、背景滚动锁定、Escape 关闭。


架构设计

@weifuwujs/components
│
├── Primitive Layer          ← 5 个 DOM 行为原语
│   ├── createFocusTrap      <Modal>, <Drawer>
│   ├── createClickAway      <Dropdown>, <Popover>
│   ├── scrollLock           <Modal>, <Drawer>
│   ├── rovingTabIndex       <Tabs>, <Select>
│   └── createFloating       <Tooltip>, <Popover>, <Dropdown>, <Select>
│
├── Component Layer          ← 33 个组件
│   ├── Pure CSS (12)        无 JS 行为
│   ├── Signal-driven (8)    signal + computed 控制
│   └── Portal + Primitive (13) createPortal + 原语层
│
└── Runtime Dependency
    └── weifuwu/client       signal, computed, createPortal, Show, For

组件不引入额外运行时。所有状态管理使用 weifuwu/client 的信号系统,与核心框架共享同一套响应式模型。


不包含(应在应用层实现)

  • DatePicker / TimePicker — 日期逻辑复杂,推荐使用 wrap() 封装现有库
  • Upload — 业务逻辑差异大
  • ColorPicker — 使用场景有限
  • Chart — 推荐 wrap(echarts)wrap(recharts)
  • Video / Audio — 直接使用原生 HTML5 元素

React 开发者对照

| React + shadcn/ui | @weifuwujs/components | |-------------------|----------------------| | useState(false) + <Dialog open={open} onOpenChange={setOpen}> | const open = signal(false) + <Modal open={open}> | | useFormik({...}) | useForm({...}) (weifuwu/client) | | <Table data={data} /> + 手动排序 | <Table columns={cols} data={data} sortable /> | | toast() (sonner) | toast.success() | | useNavigate() | ctx.app.navigate() | | <Select value={v} onValueChange={setV}> | <Select value={signalV}> |


构建

npm run build       # esbuild → dist/index.js (62.9kB)
npm run typecheck   # tsc --noEmit