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

filter-chip-bar

v0.1.0

Published

搜索即入口 — Headless filter + command palette component for React

Readme

English | 中文

filter-chip-bar

搜索即入口 — 面向 React 的 Headless 筛选 + 命令面板组件。

把一个搜索框变成应用的第一个入口:筛选导航执行操作——全部从一个输入框完成。受 Linear、Raycast、VS Code Command Palette 启发。

Storybook npm License: MIT


为什么

传统后台给用户的是一排下拉框、几个日期选择器、若干复选框。每多一个控件,用户就多一次"理解 UI 结构"的认知负担。

filter-chip-bar 把所有筛选浓缩进一个搜索框。用户不需要学习 UI 布局——想找什么,就输入什么。

| 层次 | 示例输入 | 效果 | |------|---------|------| | 筛选 | 审核状态:通过 订单量:>=100 | 按条件过滤数据 | | 导航 | 创建工单 | 跳转到对应页面或打开弹窗 | | 自由文本 | kxccaqvx12 | 全文搜索 SKU、产品名等 |

特性

  • 🧠 Headless hookuseFilterChipBar() 提供全部状态/逻辑,零 UI 依赖
  • 🎨 shadcn 渲染器 — 内置渲染器,使用 Radix UI + Tailwind CSS + lucide 图标
  • 🐜 antd6 适配器 — Ant Design 6 项目即插即用
  • ⌨️ 全键盘导航 — ↑↓ Enter Tab Esc Backspace
  • 🏷️ 语法高亮key:value 实时校验着色
  • 🔖 预设 — 保存/恢复/分享搜索配置(支持 URL 分享)
  • 🕐 搜索历史 — 自动记录,localStorage 持久化,命名空间隔离
  • 📋 智能粘贴 — 多行粘贴自动转为逗号分隔
  • 🔄 多值选择 — 输入逗号继续选,空格结束
  • 反选-key:value 排除匹配项
  • 🎯 命令面板 — 注册快捷操作,支持路由跳转/弹窗触发
  • 🌗 暗色模式 — 通过 class="dark" (shadcn CSS 变量)

安装

npm install filter-chip-bar

Peer dependencies(只需 React,antd 是可选的):

{
  "react": ">=17",
  "react-dom": ">=17"
}

如果使用 antd6 适配器,还需安装:

npm install antd @ant-design/icons

快速开始

shadcn / Tailwind(默认)

import { FilterChipBar, type ChipConfig, type FilterChipBarResult } from 'filter-chip-bar';

const chipConfigs: ChipConfig[] = [
  {
    type: 'select',
    label: '审核状态',
    options: [
      { value: 0, label: '未审核' },
      { value: 1, label: '通过' },
      { value: 2, label: '失败' },
    ],
  },
  { type: 'input', label: '虚拟SKU' },
  { type: 'numberRange', label: '订单量', min: 0 },
];

function App() {
  return (
    <FilterChipBar
      chipConfigs={chipConfigs}
      storageNamespace="my-page"
      onFiltersChange={(result: FilterChipBarResult) => {
        console.log(result.chips, result.freeText, result.stat);
      }}
      statusOptions={[
        { value: -1, label: '全部' },
        { value: 0, label: '未审核' },
        { value: 1, label: '通过' },
        { value: 2, label: '失败' },
      ]}
    />
  );
}

需要 Tailwind: shadcn 渲染器使用 Tailwind CSS 类名。请确保项目已配置 Tailwind,并导入包的 CSS 变量:

import 'filter-chip-bar/styles'

antd6

import { FilterChipBarAntd6, type ChipConfig } from 'filter-chip-bar/antd6';

function App() {
  return (
    <FilterChipBarAntd6
      chipConfigs={chipConfigs}
      storageNamespace="my-page"
      onFiltersChange={(result) => { /* ... */ }}
    />
  );
}

Headless(自己写渲染器)

import { useFilterChipBar } from 'filter-chip-bar/headless';

function MyFilterBar({ chipConfigs, onFiltersChange }) {
  const fcb = useFilterChipBar({
    chipConfigs,
    storageNamespace: 'my-page',
    onFiltersChange,
  });

  return (
    <div>
      <input
        ref={fcb.inputRef}
        value={fcb.searchText}
        onChange={fcb.handleInputChange}
        onKeyDown={fcb.handleKeyDown}
        onFocus={() => fcb.setDropdownOpen(true)}
      />
      {fcb.isDropdownOpen && (
        <ul>
          {fcb.suggestions.map((s, i) => (
            <li
              key={i}
              ref={(el) => { fcb.itemRefs.current[i] = el; }}
              onMouseDown={(e) => {
                e.preventDefault();
                fcb.handleSuggestionClick(s.value);
              }}
            >
              {s.label}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

搜索语法

| 语法 | 含义 | 示例 | |------|------|------| | key:value | 按字段筛选 | 审核状态:通过 | | -key:value | 排除匹配项(反选) | -审核状态:失败 | | key:val1,val2 | 多值选择(select/multiSelect 通用) | 审核状态:通过,失败 | | key:"带空格的值" | 引号包裹含空格的值 | 产品名称:"iPhone 15 Pro" | | key:>=100 | 数值比较(≥ ≤ =) | 订单量:>=100 | | key:100~200 | 数值区间 | 订单量:100~200 | | key:2024-01-01~2024-12-31 | 日期区间 | 日期:2024-01-01~2024-12-31 | | 自由文本 | 非 key:value 格式 → 全文搜索 | kxccaqvx12 | | 空格 | 分隔多个条件 | 审核状态:通过 订单量:>=100 |

交互细节

  • 选择续选: select/multiSelect 选了一个值后,输入逗号继续选或空格结束
  • 反选切换: 在 value 阶段,建议列表提供一键"排除"切换
  • 多行粘贴: 从 Excel/CSV 粘贴自动把换行转为逗号
  • 自动加引号: input 类型输入空格结束 token 时自动包裹引号

命令面板(Command Palette)

注册快捷操作,让用户直接从搜索框导航或触发动作:

<FilterChipBar
  chipConfigs={chipConfigs}
  storageNamespace="my-page"
  onFiltersChange={handleChange}
  commands={[
    {
      keywords: ['创建工单', '新建工单'],
      label: '创建工单',
      hint: '跳转到工单页',
      action: () => navigate('/tickets/new'),
    },
    {
      keywords: ['导出', '下载'],
      label: '导出数据',
      hint: '下载 CSV',
      action: () => exportModal.open(),
    },
  ]}
/>

用户输入匹配关键词时,下拉框出现带 标识的快捷操作。按 Enter 或点击即可执行。

API

<FilterChipBar /> Props

| Prop | 类型 | 必填 | 说明 | |------|------|------|------| | chipConfigs | ChipConfig[] | ✅ | 筛选项配置 | | storageNamespace | string | ✅ | localStorage 命名空间(每个页面必须唯一) | | onFiltersChange | (result: FilterChipBarResult) => void | ✅ | 搜索提交时回调 | | statusOptions | FilterOption[] | | 状态栏选项(空数组 = 不渲染状态栏) | | statusCounts | Record<number, number> | | 各状态对应数量 | | commands | ActionCommand[] | | 命令面板操作 | | dynamicOptions | Record<string, FilterOption[]> | | 动态加载的选项,key 为 chipConfig label | | dynamicOptionsLoading | boolean | | 动态选项加载中状态 | | initialSearchText | string | | 预填搜索文本(如从 URL 恢复) | | initialStat | number | | 预选状态(默认 -1 = 全部) | | placeholder | string | | 输入框占位文本 | | syntaxHelp | ReactNode | | 自定义语法帮助内容 | | onImageSearch | () => void | | 传入则渲染以图搜图按钮 | | rightExtra | ReactNode | | 搜索框右侧自定义内容 | | statusBarExtra | ReactNode | | 状态栏右侧自定义内容(靠右对齐) | | searchResultCount | number | | 搜索结果总数(用于记录搜索历史) | | searchLoading | boolean | | 加载状态(加载结束时自动保存搜索历史) |

FilterChipBarResult

interface FilterChipBarResult {
  searchText: string;                  // 原始搜索文本
  chips: Record<string, unknown>;      // 解析后的条件(label 为 key)
  freeText: string[];                  // 非 key:value 的文本片段
  stat: number;                        // 状态栏选中值(-1 = 全部)
}

ChipConfig

interface ChipConfig {
  type: 'select' | 'multiSelect' | 'input' | 'dateRange' | 'numberRange';
  label: string;
  options?: FilterOption[];
  precision?: number;      // numberRange 小数位数
  min?: number;            // numberRange 最小值
  dynamic?: boolean;       // 选项通过 dynamicOptions 异步加载
}

ActionCommand

interface ActionCommand {
  keywords: string[];      // 与用户输入匹配的关键词
  label: string;           // 显示标签
  hint?: string;           // 右侧提示文本
  action: () => void;      // 点击/Enter 时执行
}

useFilterChipBar(options)

返回构建自定义渲染器所需的全部 state、ref 和 handler。参见快速开始 > Headless

interface UseFilterChipBarReturn {
  // Refs
  inputRef: RefObject<HTMLInputElement | null>;
  itemRefs: MutableRefObject<(HTMLDivElement | null)[]>;

  // 搜索状态
  searchText: string;
  setSearchText: (text: string) => void;
  textTokens: TextToken[];
  activeFilterCount: number;

  // 状态栏
  stat: number;
  setStat: (stat: number) => void;

  // 下拉框
  isDropdownOpen: boolean;
  setDropdownOpen: (open: boolean) => void;
  activeSuggestionIdx: number;
  setActiveSuggestionIdx: (idx: number) => void;
  dropdownOffsetX: number;
  inputScrollLeft: number;
  parsedToken: ParsedToken;
  suggestions: SuggestionItem[];
  filteredHistory: RecentSearch[];
  isLoadingDynamic: boolean;

  // 预设
  isPresetOpen: boolean;
  setPresetOpen: (open: boolean) => void;
  presetName: string;
  setPresetName: (name: string) => void;
  presets: SearchPreset[];

  // Handlers
  handleInputChange: (e: ChangeEvent<HTMLInputElement>) => void;
  handleKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void;
  handlePaste: (e: ClipboardEvent<HTMLInputElement>) => void;
  handleClear: () => void;
  handleSuggestionClick: (value: string) => void;
  handleToggleNegate: () => void;
  executeCommand: (cmd: ActionCommand) => void;
  handleSavePreset: () => void;
  handleLoadPreset: (preset: SearchPreset) => void;
  handleDeletePreset: (id: string) => void;
  buildShareUrl: (preset: SearchPreset) => string;
  clearRecent: () => void;
  onInputScroll: (scrollLeft: number) => void;
}

架构

filter-chip-bar
├── useFilterChipBar()          ← Headless hook(纯逻辑,零 UI 依赖)
│   ├── 状态: searchText / stat / dropdown / presets / recent
│   ├── 解析: parseCurrentToken / parseQuery
│   ├── 键盘: ↑↓ Enter Tab Esc Backspace
│   ├── 建议: 自动补全 + 模糊匹配 + 已选排除
│   └── 持久化: 命名空间化 localStorage(预设 + 历史)
│
├── FilterChipBar               ← shadcn 渲染器(默认导出)
│   Radix Popover + Tailwind CSS + lucide-react
│
└── FilterChipBarAntd6          ← antd6 适配器(filter-chip-bar/antd6)
    Ant Design 6 + @ant-design/icons

包导出

| 导入路径 | 内容 | |---------|------| | filter-chip-bar | shadcn 渲染器 + hook + 全部类型 | | filter-chip-bar/headless | 纯 hook(零 UI 依赖) | | filter-chip-bar/antd6 | antd6 渲染器 + hook + 全部类型 |

开发

# 安装依赖
npm install

# 启动 Storybook 开发服务器
npm run storybook

# 构建产物
npm run build

# 构建 Storybook 静态站点
npm run build-storybook

开源协议

MIT