Skip to content

Commit

Permalink
feat(select): 列表选择体验优化 (#659)
Browse files Browse the repository at this point in the history
* feat(select): 列表展开时定位置选中项

* test(select): add example and snapshot

* feat(select): 修改useMemo为useCallback

* feat: 缩小选中option范围的查询范围,从popup开始查询
  • Loading branch information
smilebuz authored Apr 24, 2022
1 parent 1bd99b0 commit 3a792bd
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 6 deletions.
8 changes: 8 additions & 0 deletions src/popup/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import useWindowSize from '../_util/useWindowSize';
export interface PopupProps extends TdPopupProps, StyledProps {
// 是否触发展开收起动画,内部下拉式组件使用
expandAnimation?: boolean;
updateScrollTop?: (content: HTMLDivElement) => void;
}

function getPopperPlacement(placement: TdPopupProps['placement']) {
Expand All @@ -54,6 +55,7 @@ const Popup = forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
onVisibleChange,
onScroll,
expandAnimation,
updateScrollTop,
} = props;
const { classPrefix } = useConfig();

Expand Down Expand Up @@ -166,6 +168,12 @@ const Popup = forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
popperRef.current.update?.();
}, [content, visible, windowHeight, windowWidth]);

useEffect(() => {
if (visible && overlayRef) {
updateScrollTop?.(contentRef?.current);
}
}, [visible, overlayRef, updateScrollTop]);

// 初次不渲染.
const portal =
visible || overlayRef ? (
Expand Down
5 changes: 4 additions & 1 deletion src/select-input/SelectInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import useOverlayStyle from './useOverlayStyle';
import { TdSelectInputProps } from './type';
import { StyledProps } from '../common';

export interface SelectInputProps extends TdSelectInputProps, StyledProps {}
export interface SelectInputProps extends TdSelectInputProps, StyledProps {
updateScrollTop?: (content: HTMLDivElement) => void;
}

const SelectInput = forwardRef((props: SelectInputProps, ref) => {
const selectInputRef = useRef();
Expand Down Expand Up @@ -53,6 +55,7 @@ const SelectInput = forwardRef((props: SelectInputProps, ref) => {
{...popupProps}
disabled={disabled}
overlayStyle={tOverlayStyle}
updateScrollTop={props.updateScrollTop}
>
{multiple
? renderSelectMultiple({
Expand Down
50 changes: 50 additions & 0 deletions src/select/__tests__/__snapshots__/select.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,56 @@ exports[`remote-search.jsx 1`] = `
</DocumentFragment>
`;

exports[`scroll-top.jsx 1`] = `
<DocumentFragment>
<div
class="t-select__wrap"
style="width: 200px;"
>
<div
class="t-popup__reference t-select t-select-input"
>
<div
class="t-input__wrap"
>
<div
class="t-input t-is-readonly t-input--prefix t-input--suffix"
>
<div
class="t-input__prefix"
/>
<input
class="t-input__inner"
placeholder="请选择"
readonly=""
value="第 50 项"
/>
<span
class="t-input__suffix t-input__suffix-icon"
>
<svg
class="t-fake-arrow t-select__right-icon"
fill="none"
height="16"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921"
stroke="black"
stroke-opacity="0.9"
stroke-width="1.3"
/>
</svg>
</span>
</div>
</div>
</div>
</div>
</DocumentFragment>
`;

exports[`size.jsx 1`] = `
<DocumentFragment>
<div
Expand Down
28 changes: 28 additions & 0 deletions src/select/_example/scroll-top.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { useState } from "react";

import { Select } from "tdesign-react";

const options = [];
for (let i = 0; i < 100; i++) {
options.push({ label: `第 ${i} 项`, value: i });
}

const SingleSelect = () => {
const [value, setValue] = useState(50);

const onChange = (value) => {
setValue(value);
};

return (
<Select
options={options}
defaultValue={value}
onChange={onChange}
style={{ width: 200 }}
// multiple
></Select>
);
};

export default SingleSelect;
7 changes: 4 additions & 3 deletions src/select/base/PopupContent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Children, isValidElement, cloneElement } from 'react';
import React, { Children, Ref, forwardRef, isValidElement, cloneElement } from 'react';
import classNames from 'classnames';
import { useLocaleReceiver } from '../../locale/LocalReceiver';
import { getSelectValueArr } from '../util/helper';
Expand Down Expand Up @@ -36,7 +36,7 @@ interface SelectPopupProps
children?: React.ReactNode;
}

const PopupContent = (props: SelectPopupProps) => {
const PopupContent = forwardRef((props: SelectPopupProps, ref: Ref<HTMLDivElement>) => {
const {
onChange,
value,
Expand Down Expand Up @@ -131,6 +131,7 @@ const PopupContent = (props: SelectPopupProps) => {

return (
<div
ref={ref}
className={classNames(`${classPrefix}-select__dropdown-inner`, {
[`${classPrefix}-select__dropdown-inner--size-s`]: size === 'small',
[`${classPrefix}-select__dropdown-inner--size-l`]: size === 'large',
Expand All @@ -144,6 +145,6 @@ const PopupContent = (props: SelectPopupProps) => {
{panelBottomContent}
</div>
);
};
});

export default PopupContent;
37 changes: 35 additions & 2 deletions src/select/base/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, Ref, useMemo } from 'react';
import React, { useState, useEffect, Ref, useMemo, useCallback, useRef } from 'react';
import classNames from 'classnames';
import isFunction from 'lodash/isFunction';
import get from 'lodash/get';
Expand Down Expand Up @@ -81,6 +81,8 @@ const Select = forwardRefWithStatics(
tagProps,
} = props;

const selectPopupRef = useRef();

const [value, onChange] = useDefault(props.value, props.defaultValue, props.onChange);
const { classPrefix } = useConfig();
const { overlayClassName, ...restPopupProps } = popupProps || {};
Expand Down Expand Up @@ -296,7 +298,11 @@ const Select = forwardRefWithStatics(
panelBottomContent,
panelTopContent,
};
return <PopupContent {...popupContentProps}>{children}</PopupContent>;
return (
<PopupContent {...popupContentProps} ref={selectPopupRef}>
{children}
</PopupContent>
);
};

const renderValueDisplay = () => {
Expand All @@ -323,6 +329,32 @@ const Select = forwardRefWithStatics(
[selectedLabel, collapsedItems, minCollapsedNum],
);

// 将第一个选中的option置于列表可见范围的最后一位
const updateScrollTop = useCallback(
(content: HTMLDivElement) => {
if (!selectPopupRef?.current) {
return;
}
const firstSelectedNode: HTMLDivElement = (selectPopupRef?.current as HTMLUListElement).querySelector(
`.${classPrefix}-is-selected`,
);
if (firstSelectedNode && content) {
const { paddingBottom } = getComputedStyle(firstSelectedNode);
const { marginBottom } = getComputedStyle(content);
const elementBottomHeight = parseInt(paddingBottom, 10) + parseInt(marginBottom, 10);
// 小于0时不需要特殊处理,会被设为0
const updateValue =
firstSelectedNode.offsetTop -
content.offsetTop -
(content.clientHeight - firstSelectedNode.clientHeight) +
elementBottomHeight;
// eslint-disable-next-line no-param-reassign
content.scrollTop = updateValue;
}
},
[classPrefix],
);

return (
<div className={classNames(`${name}__wrap`, className)} style={style}>
<SelectInput
Expand Down Expand Up @@ -366,6 +398,7 @@ const Select = forwardRefWithStatics(
onClear={(context) => {
onClearValue(context);
}}
updateScrollTop={updateScrollTop}
{...selectInputProps}
></SelectInput>
</div>
Expand Down
2 changes: 2 additions & 0 deletions test/ssr/__snapshots__/ssr.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,8 @@ exports[`ssr snapshot test renders ./src/select/_example/prefix.jsx correctly 1`

exports[`ssr snapshot test renders ./src/select/_example/remote-search.jsx correctly 1`] = `"<div class=\\"t-select__wrap\\" style=\\"width:40%\\"><div class=\\"t-popup__reference t-select t-select-input t-select-input--empty\\"><div class=\\"t-input__wrap\\"><div class=\\"t-input t-input--prefix t-input--suffix\\"><div class=\\"t-input__prefix\\"></div><input placeholder=\\"请选择\\" class=\\"t-input__inner\\" value=\\"\\"/><span class=\\"t-input__suffix t-input__suffix-icon\\"><svg class=\\"t-fake-arrow t-select__right-icon\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" fill=\\"none\\" xmlns=\\"http://www.w3.org/2000/svg\\"><path d=\\"M3.75 5.7998L7.99274 10.0425L12.2361 5.79921\\" stroke=\\"black\\" stroke-opacity=\\"0.9\\" stroke-width=\\"1.3\\"></path></svg></span></div></div></div></div>"`;

exports[`ssr snapshot test renders ./src/select/_example/scroll-top.jsx correctly 1`] = `"<div class=\\"t-select__wrap\\" style=\\"width:200px\\"><div class=\\"t-popup__reference t-select t-select-input t-select-input--empty\\"><div class=\\"t-input__wrap\\"><div class=\\"t-input t-is-readonly t-input--prefix t-input--suffix\\"><div class=\\"t-input__prefix\\"></div><input placeholder=\\"请选择\\" class=\\"t-input__inner\\" value=\\"\\" readonly=\\"\\"/><span class=\\"t-input__suffix t-input__suffix-icon\\"><svg class=\\"t-fake-arrow t-select__right-icon\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" fill=\\"none\\" xmlns=\\"http://www.w3.org/2000/svg\\"><path d=\\"M3.75 5.7998L7.99274 10.0425L12.2361 5.79921\\" stroke=\\"black\\" stroke-opacity=\\"0.9\\" stroke-width=\\"1.3\\"></path></svg></span></div></div></div></div>"`;

exports[`ssr snapshot test renders ./src/select/_example/size.jsx correctly 1`] = `"<div class=\\"t-select__wrap\\" style=\\"width:30%;margin-right:20px;display:inline-block\\"><div class=\\"t-popup__reference t-select t-select-input t-select-input--empty\\"><div class=\\"t-input__wrap\\"><div class=\\"t-input t-is-readonly t-size-s t-input--prefix t-input--suffix\\"><div class=\\"t-input__prefix\\"></div><input placeholder=\\"请选择\\" class=\\"t-input__inner\\" value=\\"\\" readonly=\\"\\"/><span class=\\"t-input__suffix t-input__suffix-icon\\"><svg class=\\"t-fake-arrow t-select__right-icon\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" fill=\\"none\\" xmlns=\\"http://www.w3.org/2000/svg\\"><path d=\\"M3.75 5.7998L7.99274 10.0425L12.2361 5.79921\\" stroke=\\"black\\" stroke-opacity=\\"0.9\\" stroke-width=\\"1.3\\"></path></svg></span></div></div></div></div><div class=\\"t-select__wrap\\" style=\\"width:30%;margin-right:20px;display:inline-block\\"><div class=\\"t-popup__reference t-select t-select-input t-select-input--empty\\"><div class=\\"t-input__wrap\\"><div class=\\"t-input t-is-readonly t-input--prefix t-input--suffix\\"><div class=\\"t-input__prefix\\"></div><input placeholder=\\"请选择\\" class=\\"t-input__inner\\" value=\\"\\" readonly=\\"\\"/><span class=\\"t-input__suffix t-input__suffix-icon\\"><svg class=\\"t-fake-arrow t-select__right-icon\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" fill=\\"none\\" xmlns=\\"http://www.w3.org/2000/svg\\"><path d=\\"M3.75 5.7998L7.99274 10.0425L12.2361 5.79921\\" stroke=\\"black\\" stroke-opacity=\\"0.9\\" stroke-width=\\"1.3\\"></path></svg></span></div></div></div></div><div class=\\"t-select__wrap\\" style=\\"width:30%;margin-right:20px;display:inline-block\\"><div class=\\"t-popup__reference t-select t-select-input t-select-input--empty\\"><div class=\\"t-input__wrap\\"><div class=\\"t-input t-is-readonly t-size-l t-input--prefix t-input--suffix\\"><div class=\\"t-input__prefix\\"></div><input placeholder=\\"请选择\\" class=\\"t-input__inner\\" value=\\"\\" readonly=\\"\\"/><span class=\\"t-input__suffix t-input__suffix-icon\\"><svg class=\\"t-fake-arrow t-select__right-icon\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" fill=\\"none\\" xmlns=\\"http://www.w3.org/2000/svg\\"><path d=\\"M3.75 5.7998L7.99274 10.0425L12.2361 5.79921\\" stroke=\\"black\\" stroke-opacity=\\"0.9\\" stroke-width=\\"1.3\\"></path></svg></span></div></div></div></div>"`;

exports[`ssr snapshot test renders ./src/select/_example/status.jsx correctly 1`] = `"<div style=\\"display:flex\\" data-reactroot=\\"\\"><div class=\\"t-select__wrap\\" style=\\"width:200px;margin-right:20px\\"><div class=\\"t-popup__reference t-select t-select-input t-select-input--empty\\"><div class=\\"t-input__wrap\\"><div class=\\"t-input t-is-readonly t-input--prefix t-input--suffix\\"><div class=\\"t-input__prefix\\"></div><input placeholder=\\"请选择\\" class=\\"t-input__inner\\" value=\\"\\" readonly=\\"\\"/><span class=\\"t-input__suffix t-input__suffix-icon\\"><svg class=\\"t-fake-arrow t-select__right-icon\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" fill=\\"none\\" xmlns=\\"http://www.w3.org/2000/svg\\"><path d=\\"M3.75 5.7998L7.99274 10.0425L12.2361 5.79921\\" stroke=\\"black\\" stroke-opacity=\\"0.9\\" stroke-width=\\"1.3\\"></path></svg></span></div></div></div></div><div class=\\"t-select__wrap\\" style=\\"width:200px;margin-right:20px\\"><div class=\\"t-popup__reference t-select t-select-input t-select-input--empty\\"><div class=\\"t-input__wrap\\"><div class=\\"t-input t-is-readonly t-is-disabled t-input--prefix t-input--suffix\\"><div class=\\"t-input__prefix\\"></div><input placeholder=\\"请选择\\" class=\\"t-input__inner\\" value=\\"\\" readonly=\\"\\" disabled=\\"\\"/><span class=\\"t-input__suffix t-input__suffix-icon\\"><svg class=\\"t-fake-arrow t-select__right-icon\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" fill=\\"none\\" xmlns=\\"http://www.w3.org/2000/svg\\"><path d=\\"M3.75 5.7998L7.99274 10.0425L12.2361 5.79921\\" stroke=\\"black\\" stroke-opacity=\\"0.9\\" stroke-width=\\"1.3\\"></path></svg></span></div></div></div></div><div class=\\"t-select__wrap\\" style=\\"width:200px\\"><div class=\\"t-popup__reference t-select t-select-input t-select-input--empty\\"><div class=\\"t-input__wrap\\"><div class=\\"t-input t-is-readonly t-input--prefix t-input--suffix\\"><div class=\\"t-input__prefix\\"></div><input placeholder=\\"请选择\\" class=\\"t-input__inner\\" value=\\"\\" readonly=\\"\\"/><span class=\\"t-input__suffix t-input__suffix-icon\\"><div class=\\"t-loading t-loading--center t-size-s t-select__right-icon t-select__active-icon\\"><svg class=\\"t-loading__gradient t-icon-loading\\" viewBox=\\"0 0 14 14\\" version=\\"1.1\\" width=\\"1em\\" height=\\"1em\\" xmlns=\\"http://www.w3.org/2000/svg\\"><foreignObject x=\\"1\\" y=\\"1\\" width=\\"12\\" height=\\"12\\"><div class=\\"t-loading__gradient-conic\\"></div></foreignObject></svg></div></span></div></div></div></div></div>"`;
Expand Down

0 comments on commit 3a792bd

Please sign in to comment.