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

@kne/entry-selector

v0.2.6

Published

EntrySelector是一个用于选择和管理条目列表的 React 组件。它提供了一个直观的界面,允许用户从可选列表中选择条目,并对已选条目进行管理,包括排序、搜索和删除等操作。

Readme

entry-selector

描述

EntrySelector是一个用于选择和管理条目列表的 React 组件。它提供了一个直观的界面,允许用户从可选列表中选择条目,并对已选条目进行管理,包括排序、搜索和删除等操作。

安装

npm i --save @kne/entry-selector

概述

EntrySelector 是一个用于选择和管理条目列表的 React 组件。它提供了一个直观的界面,允许用户从可选列表中选择条目,并对已选条目进行管理,包括排序、搜索和删除等操作。

主要功能

  • 双列表展示:清晰地分离已选条目和可选条目
  • 拖拽排序:支持对已选条目进行拖拽排序
  • 搜索过滤:可以通过关键词搜索过滤条目
  • 自定义渲染:支持自定义条目的渲染方式
  • 国际化支持:内置中文和英文语言支持
  • 添加新条目:支持添加不在预设列表中的新条目

使用场景

EntrySelector 适用于需要从预定义列表中选择多个条目并进行排序的场景,例如:

  • 选择并排序文章分类
  • 管理产品标签
  • 配置用户权限
  • 选择并排序展示项目

基本原理

组件内部维护已选条目列表和可选条目列表两个状态,通过用户交互在这两个列表之间移动条目。组件使用 react-sortablejs 实现拖拽排序功能,并支持通过搜索框过滤条目列表。

示例

示例样式

.ant-card {
  border-color: black;
  text-align: center;
  width: 200px;
}

示例代码

  • 基础用法
  • 展示EntrySelector组件的基本使用,包括条目选择、搜索过滤和拖拽排序功能
  • _EntrySelector(@kne/current-lib_entry-selector)[import * as _EntrySelector from "@kne/entry-selector"],(@kne/current-lib_entry-selector/dist/index.css),antd(antd)
const { default: EntrySelector } = _EntrySelector;
const { useState } = React;
const { message } = antd;

// 模拟面试题库数据
const mockQuestionDatabase = [
  { id: 1, title: '请简单介绍一下你自己', category: '个人介绍', difficulty: '简单' },
  { id: 2, title: '你认为自己最大的优点和缺点是什么?', category: '性格分析', difficulty: '简单' },
  { id: 3, title: '请描述一次你成功解决团队冲突的经历', category: '团队协作', difficulty: '中等' },
  { id: 4, title: '你如何处理工作中的高压情况?请举例说明', category: '压力管理', difficulty: '中等' },
  { id: 5, title: '请分享一次你快速作出决策的经验', category: '决策能力', difficulty: '中等' },
  { id: 6, title: '你如何看待加班?如何平衡工作与生活?', category: '工作态度', difficulty: '简单' },
  { id: 7, title: '描述一次你主动承担责任并超出预期的项目', category: '主动性', difficulty: '困难' },
  { id: 8, title: '你如何让团队成员有效高质完成工作?', category: '团队管理', difficulty: '困难' }
];

const BaseExample = () => {
  const [selectedQuestions, setSelectedQuestions] = useState([
    { id: 1, title: '请简单介绍一下你自己' }
  ]);

  return (
    <EntrySelector
      value={selectedQuestions}
      onChange={value => {
        setSelectedQuestions(value);
        message.success(&#96;已选择 ${value.length} 道题目&#96;);
      }}
      pagination={{ paramsType: 'params' }}
      api={{
        loader: async ({ params }) => {
          // 模拟API请求延迟
          await new Promise(resolve => setTimeout(resolve, 300));
          
          const { title } = params || {};
          let filteredData = mockQuestionDatabase;
          
          // 根据搜索关键词过滤
          if (title) {
            filteredData = filteredData.filter(
              item => item.title.includes(title) || item.category.includes(title)
            );
          }
          
          return {
            totalCount: filteredData.length,
            pageData: filteredData
          };
        }
      }}
      getSearchProps={({ searchText }) => {
        return { title: searchText };
      }}
      searchPlaceholder="搜索题目或分类"
      selectedTitle="已选题目(可拖拽排序)"
      listTitle="题目库"
      maxScrollerHeight={600}
    />
  );
};

render(<BaseExample />);
  • 自定义渲染
  • 展示如何自定义渲染已选条目和可选条目,实现更丰富的交互功能
  • _EntrySelector(@kne/current-lib_entry-selector)[import * as _EntrySelector from "@kne/entry-selector"],(@kne/current-lib_entry-selector/dist/index.css),antd(antd)
const { default: EntrySelector } = _EntrySelector;
const { useState } = React;
const { Tag, Switch, Flex, message } = antd;

// 模拟产品特性数据
const mockFeatures = [
  { id: 1, title: '用户注册登录', status: '已完成', priority: 'high', category: '基础功能' },
  { id: 2, title: '商品搜索与筛选', status: '开发中', priority: 'high', category: '核心功能' },
  { id: 3, title: '购物车功能', status: '已完成', priority: 'high', category: '核心功能' },
  { id: 4, title: '订单管理', status: '待开发', priority: 'medium', category: '核心功能' },
  { id: 5, title: '支付集成', status: '待开发', priority: 'high', category: '核心功能' },
  { id: 6, title: '用户评价系统', status: '开发中', priority: 'medium', category: '增值功能' },
  { id: 7, title: '数据统计面板', status: '待开发', priority: 'low', category: '增值功能' },
  { id: 8, title: '消息推送', status: '已完成', priority: 'medium', category: '增值功能' }
];

const priorityColors = {
  high: 'red',
  medium: 'orange',
  low: 'blue'
};

const statusColors = {
  '已完成': 'green',
  '开发中': 'blue',
  '待开发': 'default'
};

const CustomRenderExample = () => {
  const [selectedFeatures, setSelectedFeatures] = useState([]);

  return (
    <EntrySelector
      value={selectedFeatures}
      onChange={setSelectedFeatures}
      pagination={{ paramsType: 'params' }}
      api={{
        loader: async ({ params }) => {
          await new Promise(resolve => setTimeout(resolve, 300));
          
          const { title } = params || {};
          let filteredData = mockFeatures;
          
          if (title) {
            filteredData = filteredData.filter(
              item => item.title.includes(title) || item.category.includes(title)
            );
          }
          
          return {
            totalCount: filteredData.length,
            pageData: filteredData
          };
        }
      }}
      getSearchProps={({ searchText }) => ({ title: searchText })}
      searchPlaceholder="搜索特性名称或分类"
      selectedTitle="已选特性(需排期开发)"
      // 自定义渲染已选条目:展示优先级开关
      renderSelectedItem={(item, { el, onReplace }) => {
        return (
          <Flex vertical gap={4} style={{ width: '100%' }}>
            {el}
            <Flex align="center" gap={8}>
              <span style={{ fontSize: '12px' }}>高优先级</span>
              <Switch
                size="small"
                checked={item.isPriority}
                onChange={checked => {
                  onReplace({ ...item, isPriority: checked });
                  message.info(&#96;已${checked ? '开启' : '关闭'} "${item.title}" 的优先级&#96;);
                }}
              />
            </Flex>
          </Flex>
        );
      }}
      // 自定义渲染可选条目:展示标签和状态
      renderItem={(item, { el }) => {
        return (
          <Flex vertical gap={4}>
            {el}
            <Flex gap={4}>
              <Tag color={statusColors[item.status]} style={{ margin: 0 }}>
                {item.status}
              </Tag>
              <Tag color={priorityColors[item.priority]} style={{ margin: 0 }}>
                {item.priority === 'high' ? '高' : item.priority === 'medium' ? '中' : '低'}
              </Tag>
              <Tag color="purple" style={{ margin: 0 }}>
                {item.category}
              </Tag>
            </Flex>
          </Flex>
        );
      }}
      // 自定义渲染操作选项
      renderOptions={(item, { fetchApi }) => {
        return [
          {
            children: '查看详情',
            onClick: () => {
              message.info(&#96;查看特性:${item.title}&#96;);
            }
          },
          {
            children: '编辑',
            onClick: () => {
              message.info(&#96;编辑特性:${item.title}&#96;);
            }
          }
        ];
      }}
      maxScrollerHeight={600}
    />
  );
};

render(<CustomRenderExample />);
  • 完整功能
  • 展示EntrySelector的完整功能,包括添加新条目、操作选项、自定义标题等
  • _EntrySelector(@kne/current-lib_entry-selector)[import * as _EntrySelector from "@kne/entry-selector"],(@kne/current-lib_entry-selector/dist/index.css),antd(antd),remoteLoader(@kne/remote-loader)
const { default: EntrySelector } = _EntrySelector;
const { createWithRemoteLoader } = remoteLoader;
const { useState } = React;
const { Button, Modal, Form, Input, Select, message, Tag, Flex } = antd;

// 模拟候选人技能数据
const mockSkillsDatabase = [
  { id: 1, title: 'JavaScript', level: 'advanced', category: '前端开发' },
  { id: 2, title: 'TypeScript', level: 'advanced', category: '前端开发' },
  { id: 3, title: 'React', level: 'advanced', category: '前端框架' },
  { id: 4, title: 'Vue.js', level: 'intermediate', category: '前端框架' },
  { id: 5, title: 'Node.js', level: 'advanced', category: '后端开发' },
  { id: 6, title: 'Python', level: 'intermediate', category: '后端开发' },
  { id: 7, title: 'MySQL', level: 'advanced', category: '数据库' },
  { id: 8, title: 'MongoDB', level: 'intermediate', category: '数据库' },
  { id: 9, title: 'Docker', level: 'intermediate', category: '运维工具' },
  { id: 10, title: 'Git', level: 'advanced', category: '版本控制' }
];

const levelColors = {
  advanced: 'green',
  intermediate: 'blue',
  beginner: 'orange'
};

const levelLabels = {
  advanced: '精通',
  intermediate: '熟练',
  beginner: '了解'
};

const FullFeaturesExample = createWithRemoteLoader({
  modules: ['components-core:Global@PureGlobal']
})(({ remoteModules }) => {
  const [PureGlobal] = remoteModules;
  const [selectedSkills, setSelectedSkills] = useState([]);
  const [modalVisible, setModalVisible] = useState(false);
  const [form] = Form.useForm();

  // 处理添加新技能
  const handleAdd = ({ fetchApi, onChange }) => {
    setModalVisible(true);
  };

  // 提交新技能
  const handleSubmit = async () => {
    try {
      const values = await form.validateFields();
      const newSkill = {
        id: Date.now(),
        ...values,
        isNew: true
      };
      onChange(prev => [...prev, newSkill]);
      message.success(&#96;已添加技能:${values.title}&#96;);
      setModalVisible(false);
      form.resetFields();
    } catch (error) {
      console.error('表单验证失败:', error);
    }
  };

  return (
    <PureGlobal
      preset={{
        ajax: async api => {
          return { data: { code: 0, data: api.loader() } };
        }
      }}
    >
      <EntrySelector
        value={selectedSkills}
        onChange={setSelectedSkills}
        // 添加新条目功能
        onAdd={handleAdd}
        // API配置
        pagination={{ paramsType: 'params' }}
        api={{
          loader: async ({ params }) => {
            await new Promise(resolve => setTimeout(resolve, 300));
            
            const { title } = params || {};
            let filteredData = mockSkillsDatabase;
            
            if (title) {
              filteredData = filteredData.filter(
                item => item.title.toLowerCase().includes(title.toLowerCase()) || 
                       item.category.includes(title)
              );
            }
            
            return {
              totalCount: filteredData.length,
              pageData: filteredData
            };
          }
        }}
        // 搜索配置
        getSearchProps={({ searchText }) => ({ title: searchText })}
        searchPlaceholder="搜索技能名称或分类"
        // 自定义标题
        selectedTitle="候选人技能清单(可拖拽调整顺序)"
        renderListTitle={({ fetchApi, searchProps, setSearchProps }) => (
          <Flex justify="space-between" align="center" style={{ width: '100%' }}>
            <span style={{ fontWeight: 'bold', fontSize: '14px' }}>
              技能库 ({fetchApi.data?.totalCount || 0} 项)
            </span>
          </Flex>
        )}
        // 自定义渲染已选条目
        renderSelectedItem={(item, { el, onReplace }) => (
          <Flex vertical gap={4} style={{ width: '100%' }}>
            {el}
            {item.isNew && <Tag color="cyan" style={{ margin: 0 }}>新增</Tag>}
          </Flex>
        )}
        // 自定义渲染可选条目
        renderItem={(item, { el }) => (
          <Flex vertical gap={4}>
            {el}
            <Flex gap={4}>
              <Tag color={levelColors[item.level]} style={{ margin: 0 }}>
                {levelLabels[item.level]}
              </Tag>
              <Tag color="purple" style={{ margin: 0 }}>
                {item.category}
              </Tag>
            </Flex>
          </Flex>
        )}
        // 操作选项
        options={[
          {
            children: '查看详情',
            onClick: (e, item) => {
              Modal.info({
                title: item.title,
                content: (
                  <div>
                    <p>技能等级:{levelLabels[item.level]}</p>
                    <p>所属分类:{item.category}</p>
                  </div>
                )
              });
            }
          },
          {
            children: '添加到收藏',
            onClick: (e, item) => {
              message.success(&#96;已收藏技能:${item.title}&#96;);
            }
          }
        ]}
        maxScrollerHeight={600}
      />

      {/* 添加新技能的弹窗 */}
      <Modal
        title="添加新技能"
        open={modalVisible}
        onOk={handleSubmit}
        onCancel={() => {
          setModalVisible(false);
          form.resetFields();
        }}
        okText="确定"
        cancelText="取消"
      >
        <Form form={form} layout="vertical">
          <Form.Item
            label="技能名称"
            name="title"
            rules={[{ required: true, message: '请输入技能名称' }]}
          >
            <Input placeholder="例如:GraphQL" />
          </Form.Item>
          <Form.Item
            label="熟练程度"
            name="level"
            rules={[{ required: true, message: '请选择熟练程度' }]}
          >
            <Select placeholder="请选择">
              <Select.Option value="beginner">了解</Select.Option>
              <Select.Option value="intermediate">熟练</Select.Option>
              <Select.Option value="advanced">精通</Select.Option>
            </Select>
          </Form.Item>
          <Form.Item
            label="所属分类"
            name="category"
            rules={[{ required: true, message: '请输入所属分类' }]}
          >
            <Input placeholder="例如:前端开发、后端开发" />
          </Form.Item>
        </Form>
      </Modal>
    </PureGlobal>
  );
});

render(<FullFeaturesExample />);

API

组件属性

| 属性名 | 类型 | 默认值 | 说明 | |--------------------|----------|-----|-----------------------------------------------------------------------| | value | Array | [] | 已选条目列表,每个条目应包含唯一id属性 | | onChange | Function | - | 值变化时的回调函数,参数为新的value值 | | onAdd | Function | - | 添加新条目的回调函数,参数为包含fetchApi、value和onChange的对象 | | api | Function | - | 获取条目列表的API函数,用于加载可选条目数据 | | options | Array | - | 条目操作选项列表,用于ButtonGroup组件 | | renderSelectedItem | Function | - | 自定义渲染已选条目的函数,参数为条目数据和包含el、target、fetchApi、onChange的对象 | | renderItem | Function | - | 自定义渲染可选条目的函数,参数为条目数据和包含fetchApi、el的对象 | | renderOptions | Function | - | 自定义渲染操作选项的函数,参数为条目数据和包含searchProps、setSearchProps、fetchApi、options的对象 | | getSearchProps | Function | - | 获取搜索属性的函数,用于配置搜索功能 | | searchPlaceholder | String | - | 搜索框占位文本,未设置时使用国际化文本 | | selectedTitle | String | - | 自定义已选列表的标题,未设置时使用国际化文本 | | listTitle | String | - | 自定义可选列表的标题,未设置时使用国际化文本 | | renderListTitle | Function | - | 自定义渲染列表标题的函数,参数为包含fetchApi、searchProps、setSearchProps的对象 | | maxScrollerHeight | Number | 800 | 设置滚动区域的最大高度(单位:像素) | | showClearButton | Boolean | true | 是否显示清空按钮,默认显示 |

国际化支持

组件内置中文和英文两种语言,默认使用中文。可通过createWithIntlProvider配置国际化。

| 语言 | 代码 | |----|-------| | 中文 | zh-CN | | 英文 | en-US |

国际化文本键值

| 键名 | 中文 | 英文 | |-------------------|--------|----------------------| | add | 添加 | Add | | selected | 已选 | Selected | | list | 列表 | List | | searchPlaceholder | 请输入关键字 | Please input keyword | | clear | 清空 | Clear |