如何在 semantic-ui-react 中创建完全受控的下拉菜单

How to create fully controlled dropdown in semantic-ui-react

我想创建一个完全受控的下拉菜单,以便使用 react-window 显示其中非常长的项目列表。

我查看了文档,没有任何指定 Dropdown.Item 的受控下拉示例。

这是我的组件的样子:

<Dropdown
  placeholder="Filter Posts"
  clearable={true}
  search={true}
  onChange={this.handleChange}
  text={tagOptions[1].value}
  value={tagOptions[1].value}
  onSearchChange={this.handleChange}
>
  <Dropdown.Menu>
    {tagOptions.map(option => (
      <Dropdown.Item key={option.value} {...option} onClick={this.handleItemClick} />
    ))}
  </Dropdown.Menu>
</Dropdown>;

我遇到了 2 个问题:

  1. 没有出现初始值,我深入研究了代码,发现如果我不通过 options 属性 它不会找到给定的值,因此,它不会被显示。我可以使用 text 属性,但它似乎是一个 hack。
  2. 需要自己实现handleItemClick,看到original handleItemClick里面有逻辑。

有什么建议吗?我在这里错过了什么吗?

  1. 要解决第一个问题,请删除 clearable={true}text={tagOptions[1].value}

  2. handleItemClick函数应该做什么?

我已经能够通过在下拉菜单上使用 ref 并传递原始 handleItemClick 方法来破解它。

目前唯一的缺点是键盘导航不起作用:\

它似乎不是为完全控制而设计的。

https://codesandbox.io/s/ql3q086l5q

下拉模块根本不支持控制其内部组件,据说这是我所获得的最接近 react-window 支持的受控下拉菜单。我将它发布在这里,供将来想要 select 虚拟化下拉菜单而不头疼的任何人使用。

VirtualisedDropdown.js

import React, { forwardRef, useCallback, useRef, useState } from "react"
import { Dropdown, Ref } from "semantic-ui-react"
import { FixedSizeList } from "react-window"
import "./VirtualisedDropdown.scss"

const SUI_DROPDOWN_MENU_HEIGHT = 300
const SUI_DROPDOWN_MENU_ITEM_HEIGHT = 37

const VirtualisedDropdown = ({
  options, value,
  ...restProps
}) => {
  const dropdownRef = useRef()
  const listRef = useRef()

  const [open, setOpen] = useState(false)

  const OuterDiv = useCallback(({ style, ...props }, ref) => {
    const { position, overflow, ...restStyle } = style
    return (
      <Ref innerRef={ref}>
        <Dropdown.Menu open={open} {...props} style={restStyle}>
          {props.children}
        </Dropdown.Menu>
      </Ref>
    )
  }, [open])

  const InnerDiv = useCallback(props => {
    return (
      <Dropdown.Menu className="inner" open={open} style={{ ...props.style, maxHeight: props.style.height }}>
        {props.children}
      </Dropdown.Menu>
    )
  }, [open])

  return (
    <Dropdown
      className="virtualised selection"
      onClose={() => setOpen(false)}
      onOpen={() => {
        setOpen(true)
        listRef.current.scrollToItem(options.findIndex(i => i.value === value))
      }}
      // This causes "Warning: Failed prop type: Prop `children` in `Dropdown` conflicts with props: `options`. They cannot be defined together, choose one or the other."
      // but is necessary for some logic to work e.g. the selected item text.
      options={options}
      ref={dropdownRef}
      selectOnNavigation={false}
      value={value}
      {...restProps}
    >
      <FixedSizeList
        height={options.length * SUI_DROPDOWN_MENU_ITEM_HEIGHT < SUI_DROPDOWN_MENU_HEIGHT ? options.length * SUI_DROPDOWN_MENU_ITEM_HEIGHT + 1 : SUI_DROPDOWN_MENU_HEIGHT}
        innerElementType={InnerDiv}
        itemCount={options.length}
        itemData={{
          options,
          handleClick: (_e, x) => dropdownRef.current.handleItemClick(_e, x),
          selectedIndex: options.findIndex(i => i.value === value),
        }}
        itemSize={SUI_DROPDOWN_MENU_ITEM_HEIGHT}
        outerElementType={forwardRef(OuterDiv)}
        ref={listRef}
      >
        {Row}
      </FixedSizeList>
    </Dropdown>
  )
}

const Row = ({ index, style, data }) => {
  const { options, handleClick, selectedIndex } = data
  const item = options[index]

  return (
    <Dropdown.Item
      active={index === selectedIndex}
      className="ellipsis"
      key={item.value}
      onClick={handleClick}
      selected={index === selectedIndex}
      style={style}
      title={item.text}
      {...item}
    />
  )
}

export default VirtualisedDropdown

VirtualisedDropdown.scss

.ui.dropdown.virtualised .menu {
  &.inner {
    margin: 0 -1px !important;
    left: 0;
    overflow: initial;
    border-radius: 0 !important;
    border: 0;
  }

  > .item {
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
  }
}