You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于React HeadlessUI实现带‘全选’功能的单选下拉选择组件的技术咨询

基于React HeadlessUI实现带‘全选’功能的单选下拉选择组件的技术咨询

Hey 各位前端小伙伴,最近我在用Headless UI的Listbox组件实现一个带单选逻辑的下拉选择器,核心需求是要支持“全选”选项——当用户选了“全选”时,需要把所有可选项的code都作为数组传给父组件;选单个选项的话就只传该选项的code。这个组件后续要用来做国家和语言选择,下面是我目前写的实现代码,想请大家帮忙看看有没有可以优化的地方,或者有没有我没考虑到的边界情况?

import { Listbox } from '@headlessui/react';
import { ChevronDown } from 'lucide-react';
import { Fragment } from 'react';

interface Option {
  name: string;
  code: string;
}

interface RadioSingleSelectProps {
  options: Option[];
  selectedCodes: string[];
  onChange: (codes: string[]) => void;
  placeholder?: string;
}

export default function RadioSingleSelect({
  options,
  selectedCodes,
  onChange,
  placeholder = 'Select an option',
}: RadioSingleSelectProps) {
  const selectOptions: Option[] = [
    { name: 'Select All', code: '__ALL__' },
    ...options,
  ];

  const allCodes = options.map((o) => o.code);
  const isAllSelected = selectedCodes.length === allCodes.length;
  const selectedCode = isAllSelected ? '__ALL__' : selectedCodes[0];
  const selectedOption = selectOptions.find((opt) => opt.code === selectedCode) || { name: placeholder, code: '' };

  const handleChange = (option: Option) => {
    if (option.code === '__ALL__') {
      onChange(allCodes);
    } else {
      onChange([option.code]);
    }
  };

  return (
    <div className="w-full text-sm">
      <Listbox value={selectedOption} onChange={handleChange}>
        <div className="relative">
          <Listbox.Button className="relative w-full cursor-pointer rounded-md bg-gray-100 py-2 pl-4 pr-10 text-left border border-gray-300 h-12 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500">
            <span className="block truncate font-normal text-gray-800">
              {selectedOption.code ? selectedOption.name : placeholder}
            </span>
            <span className="pointer-events-none absolute inset-y-0 right-3 flex items-center">
              <ChevronDown className="h-4 w-4 text-gray-400" />
            </span>
          </Listbox.Button>
          <Listbox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white border border-gray-200 shadow-lg z-50">
            {selectOptions.map((option) => (
              <Listbox.Option key={option.code} value={option} as={Fragment}>
                {({ selected, active }) => (
                  <li
                    className={`relative cursor-pointer select-none py-2 pl-10 pr-4 text-sm ${
                      active ? 'bg-purple-100 text-purple-900' : 'text-gray-900'
                    }`}
                  >
                    <span
                      className={`block truncate ${
                        selected ? 'font-medium' : 'font-normal'
                      }`}
                    >
                      {option.name}
                    </span>
                    {selected && (
                      <span className="absolute inset-y-0 left-3 flex items-center">
                        <input type="radio" checked={selected} readOnly className="h-4 w-4 text-purple-600" />
                      </span>
                    )}
                  </li>
                )}
              </Listbox.Option>
            ))}
          </Listbox.Options>
        </div>
      </Listbox>
    </div>
  );
}

我大概的实现逻辑是这样的:

  • 先在原选项数组前面加了一个“Select All”的选项,用__ALL__作为标记code
  • 提前计算好所有选项的code数组,通过判断选中的code长度是否等于总选项数,来确定是否处于全选状态
  • 处理选择事件时,如果点击的是全选选项,就把所有code数组传给onChange;否则只传当前选中的单个code
  • 下拉按钮的显示内容会根据选中状态自动切换:全选时显示“Select All”,单选时显示对应选项名称,未选中任何内容时显示占位符

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 14:18:06