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

@infly/react-ui

v0.1.12

Published

面向 React 后台应用的通用 UI 包。组件基于 Ant Design,不依赖路由、请求库、TanStack Query 或具体业务领域。

Readme

@infly/react-ui

面向 React 后台应用的通用 UI 包。组件基于 Ant Design,不依赖路由、请求库、TanStack Query 或具体业务领域。

使用方式

import { InflyList, createInflyAntdTheme } from "@infly/react-ui";
import "@infly/react-ui/styles.css";

应用通过 Ant Design ConfigProvider 传入 createInflyAntdTheme() 的结果覆盖品牌 Token;公共包不替业务应用决定主色、布局或权限。

InflyForm

字段通过 InflyFieldConfig 配置。switchProps 除了支持 Ant Design SwitchProps,也支持 React ARIA 属性,可为开关提供明确的无障碍名称:

const fields = [
  {
    name: "canLoginAdmin",
    label: "可登录后台",
    type: "switch",
    switchProps: { "aria-label": "可登录后台" },
  },
] satisfies Array<InflyFieldConfig<EmployeeInput>>;

业务代码需要主动提交、重置、校验或回填错误时,使用 useInflyForm 创建受控实例,不再直接调用 Ant Design Form.useForm()

const [form] = useInflyForm<EmployeeInput>();

<InflyForm
  form={form}
  fields={fields}
  onFinish={(values) => saveEmployee(values)}
>
  <Button type="primary" onClick={() => form.submit()}>
    保存
  </Button>
</InflyForm>;

不需要主动操作时可以省略 formInflyForm 会创建内部实例。children 渲染在字段网格之后,可放提交按钮或动态表单内容。

文本输入框右侧需要验证码按钮等组合控件时使用 controlAfter。组件内部使用 Space.Compact 并保持输入框的表单绑定,不需要使用已废弃的 Input.addonAfter

{
  name: "code",
  label: "验证码",
  controlAfter: <Button onClick={sendCode}>获取验证码</Button>,
}

字段容器的说明、样式、必填标记等属性通过 itemProps 传入:

const quotaFields = [
  {
    name: "totalPoints",
    label: "总额度",
    type: "number",
    required: true,
    numberProps: { min: 1, precision: 0 },
    itemProps: { extra: "请输入服务端允许范围内的整数" },
  },
] satisfies Array<InflyFieldConfig<QuotaInput>>;

不写入提交值的自定义区块可以省略 name,但必须提供稳定的 key

{
  key: "permissions",
  label: "功能权限",
  type: "custom",
  itemProps: { required: true },
  render: () => <Tree checkable treeData={treeData} />,
}

动态数组使用 InflyFormList。它封装 Ant Design Form.List,仅向业务代码暴露行描述和 add/remove/move

<InflyForm
  form={form}
  initialValues={{ items: [{ organizationId: undefined, points: undefined }] }}
  onFinish={saveAllocations}
>
  <InflyFormList name="items">
    {(rows, { add, remove }) => (
      <>
        {rows.map((row) => (
          <AllocationRow key={row.key} name={row.name} onRemove={() => remove(row.name)} />
        ))}
        <Button onClick={() => add()}>继续添加</Button>
      </>
    )}
  </InflyFormList>
</InflyForm>

现有 fieldscolumnslayout 和表单属性语义保持不变;新增 API 均为可选扩展。

InflyList

旧的 dataProvider 模式继续由组件维护请求、分页和 loading:

<InflyList
  rowKey="id"
  columns={columns}
  dataProvider={({ filters, page, pageSize }) => api.list({ filters, page, pageSize })}
/>

需要 TanStack Query、URL 查询参数或其他外部状态时使用受控模式:

<InflyList
  rowKey="id"
  columns={columns}
  searchFields={searchFields}
  dataSource={query.data?.items ?? []}
  loading={query.isFetching}
  filterValues={filters}
  pagination={{ current: page, pageSize, total: query.data?.total ?? 0 }}
  onQueryChange={({ filters: nextFilters, page: nextPage, pageSize: nextPageSize }) => {
    updateUrl({ filters: nextFilters, page: nextPage, pageSize: nextPageSize });
  }}
/>

dataProviderdataSource/loading/pagination/onQueryChange 是 TypeScript 互斥数据源,不能同时传入。公共组件不持有 QueryClient,也不清理业务缓存。

筛选按钮和列表工具栏默认保持原有布局;业务页面需要显式调整时可传入对齐配置:

<InflyList
  filterActionsAlign="start"
  toolbarAlign="end"
  toolbar={<Button>导出</Button>}
  {...listProps}
/>
  • filterActionsAlign"start" | "end",未传时保持原有筛选按钮布局。
  • toolbarAlign"start" | "end" | "between",未传时保持原有工具栏两端布局。
  • showRefresh 默认 false,只有页面显式传入时才显示刷新按钮。

InflyDetail

InflyDetail 统一后台详情页的只读和编辑行为:默认文案态只有一个“修改”入口;编辑态只有“取消”和一个主按钮“保存”。所有分组共用同一张表单和一次提交。

只读态的 colSpan 会自动归一化:当某行已占列数加上新字段 colSpan 超过 columns 时,当前行最后一个字段自动扩展为剩余列数(与 antd 行末自动填充等价,布局不变),避免 antd Descriptions 的布局警告。开发者无需为奇偶错配手工凑列。

const sections: Array<InflyDetailSection<ActivityValues>> = [
  {
    key: "basic",
    title: "基本信息",
    fields: [
      { name: "id", label: "活动ID", editable: false },
      { name: "name", label: "活动名称", editable: true, required: true },
      { name: "description", label: "活动说明", editable: true, type: "textarea" },
      { name: "status", label: "状态", editable: false },
    ],
  },
];

<InflyDetail
  mode={mode}
  values={activity}
  sections={sections}
  canEdit={permissions.includes("activity.edit")}
  submitting={updateMutation.isPending}
  extra={<Button onClick={pauseActivity}>暂停活动</Button>}
  onModeChange={setMode}
  onRequestCancel={(dirty) => dirty ? confirmDiscard() : setMode("read")}
  onSubmit={(values) => updateMutation.mutateAsync(values)}
/>

约束:

  • editable: true 才渲染输入控件,ID、状态、计算值和创建信息保持只读。
  • extra 仅承载状态流转等独立业务动作,不能再放第二个资料修改入口。
  • 组件不处理接口、权限码、业务状态机或缓存;页面负责 canEdit、提交和冲突处理。
  • onRequestCancel 接收表单是否已修改,页面决定是否弹出放弃确认。

InflyAdminShell

InflyAdminShell 提供不绑定路由和业务 Session 的后台布局。应用传入品牌、菜单、当前用户和回调,公共包负责响应式侧栏、Header 和绿色运营后台视觉。

<InflyAdminShell
  brand={{ mark: "积", title: "积分商城", subtitle: "运营后台" }}
  menuItems={[{ key: "activities", label: "活动管理" }]}
  selectedKeys={["activities"]}
  user={{ name: "运营管理员", organizationName: "积分商城总公司" }}
  onMenuSelect={(key) => navigate(`/${key}`)}
  onLogout={logout}
>
  <Outlet />
</InflyAdminShell>

品牌区默认由 mark 徽标与 title / subtitle 文字组成。若品牌方提供完整品牌图(已含徽标与文字),可改用 brand.image 整图渲染,此时忽略 mark / title / subtitle

<InflyAdminShell
  brand={{ image: <img alt="积分商城运营后台" src={brandImage} /> }}
  {...otherProps}
/>

侧栏折叠后整张品牌图隐藏(与默认模式的文字隐藏行为一致)。

响应式行为

  • >= 1200px:使用固定侧栏和最大宽度为 1440px 的内容容器。
  • 768px - 1199px:收紧页面间距,InflyForm 筛选和编辑网格自动降为两列。
  • 375px - 767px:侧栏切换为 Drawer,页面标题、筛选和详情改为单列;宽表格只在 .infly-table-panel 内横向滚动。

应用不需要传入新的 props。菜单选择仍调用 onMenuSelect,手机 Drawer 会在选择后自动关闭;桌面折叠行为和旧回调语义保持不变。

菜单展开状态会跟随 selectedKeys 同步:选中二级菜单时自动展开其父级;切换到其他一级菜单时自动收起旧父级。用户在当前路由内仍可手动展开或收起菜单分组。

如需复用与 points-mall-admin-web 一致的顶部栏毛玻璃滚动态,可传入:

<InflyAdminShell
  headerEffect="frosted"
  headerScrollTrigger={48}
  {...otherProps}
/>
  • headerEffect"default" | "frosted",默认 "default"
  • headerScrollTrigger:滚动阈值(像素),仅在 headerEffect="frosted" 时生效,默认 48

应用必须导入 @infly/react-ui/styles.css,并避免在 htmlbody 或根节点重新设置大于 375px 的 min-width,否则手机断点无法生效。

公共样式同时提供“跳到主要内容”的键盘入口、可见的 :focus-visible 状态和 prefers-reduced-motion 降级。业务应用仍负责具体字段、状态、权限和移动端是否需要专用卡片视图。

页面模块间距

InflyPage 的直接业务子模块默认按 16px 纵向间距排列。详情页的汇总卡、信息卡、明细列表应作为直接子模块使用,不要在业务页面通过临时 margin 重复定义模块间距。

验证

pnpm -C packages/infly-react-ui test --run
pnpm -C packages/infly-react-ui typecheck

公共组件变更必须保持旧调用兼容,并为新增行为补充 Testing Library 测试。