Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add option renderer to combobox #472

Merged
merged 5 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/components/Combobox/Combobox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ const Combobox = forwardRef(
onOptionLeave,
shouldScrollToSelectedItem,
noResultsRenderer,
stickyCategories
stickyCategories,
optionRenderer
},
ref
) => {
Expand Down Expand Up @@ -102,6 +103,7 @@ const Combobox = forwardRef(
index={index}
key={option.id || index}
option={option}
optionRenderer={optionRenderer}
isActive={activeItemIndex === index}
isActiveByKeyboard={isActiveByKeyboard}
onOptionClick={onOptionClick}
Expand All @@ -124,6 +126,7 @@ const Combobox = forwardRef(
index={index}
key={option.id || index}
option={option}
optionRenderer={optionRenderer}
hadasfa marked this conversation as resolved.
Show resolved Hide resolved
isActive={activeItemIndex === index}
isActiveByKeyboard={isActiveByKeyboard}
onOptionClick={onOptionClick}
Expand All @@ -144,7 +147,8 @@ const Combobox = forwardRef(
isActiveByKeyboard,
onOptionClick,
onOptionEnter,
onOptionLeave
onOptionLeave,
optionRenderer
]);

const onChangeCallback = useCallback(
Expand Down Expand Up @@ -269,7 +273,8 @@ Combobox.propTypes = {
onOptionLeave: PropTypes.func,
shouldScrollToSelectedItem: PropTypes.bool,
noResultsRenderer: PropTypes.func,
stickyCategories: PropTypes.bool
stickyCategories: PropTypes.bool,
optionRenderer: PropTypes.func,
};
Combobox.defaultProps = {
className: "",
Expand All @@ -294,7 +299,8 @@ Combobox.defaultProps = {
onOptionLeave: NOOP,
shouldScrollToSelectedItem: true,
noResultsRenderer: undefined,
stickyCategories: false
stickyCategories: false,
optionRenderer: null
};

export default Combobox;
21 changes: 21 additions & 0 deletions src/components/Combobox/__stories__/combobox.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,27 @@ Combobox allowing users to filter longer lists to only the selections matching a
</Story>
</Canvas>

### With optionRenderer
<Canvas>
<Story name="With optionRenderer">
{() => {
const options = useMemo(() => [
{ id: "1", label: "Option 1" },
{ id: "2", label: "Option 2" },
{ id: "3", label: "Option 3" },
{ id: "4", label: "Option 4" },
{ id: "5", label: "Option 5" }
], []);
const optionRenderer = (option) => (<div>I can render anything with {option.label}</div>)
return (
<DialogContentContainer>
<Combobox options={options} optionRenderer={optionRenderer} placeholder="Placeholder text here" />
</DialogContentContainer>
)
}}
</Story>
</Canvas>

### With Button
<Canvas>
<Story name="With Button">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,153 @@ exports[`Combobox renders correctly with optionLineHeight 1`] = `
</div>
`;

exports[`Combobox renders correctly with optionRenderer 1`] = `
<div
className="combobox--wrapper size-medium"
id=""
>
<div
className="combobox--wrapper-list"
role="listbox"
style={
Object {
"maxHeight": undefined,
}
}
>
<div
aria-busy={false}
className="input-component combobox--wrapper-search-wrapper search_component_wrapper"
role="search"
>
<div
className="input-component__label--wrapper"
>
<div
className="input-component__input-wrapper input-component__input-wrapper--size-medium"
>
<input
aria-activedescendant="combobox-item--1"
aria-invalid={null}
aria-label="Search for content"
aria-owns=""
autoComplete="off"
className="combobox--wrapper-search search_component input-component__input input-component__input--has-icon"
disabled={false}
id="combobox-search"
maxLength={null}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder=""
readOnly={false}
required={false}
role=""
type="search"
value=""
/>
<div
className="input-component__icon--container input-component__icon--container-has-icon input-component__icon--container-active"
onClick={[Function]}
onDragStart={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchCancel={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
role="button"
tabIndex="-1"
>
<span
aria-hidden={true}
aria-label="Search"
className="icon_component input-component__icon icon_component--no-focus-style fa fa-search"
onClick={[Function]}
role="img"
/>
</div>
<div
className="input-component__icon--container input-component__icon--container-has-icon"
onClick={[Function]}
onDragStart={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchCancel={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
role="button"
tabIndex="-1"
>
<span
aria-hidden={true}
aria-label="Clear Search"
className="icon_component input-component__icon icon_component--no-focus-style fa fa-close"
onClick={[Function]}
role="img"
/>
</div>
</div>
</div>
</div>
<div
aria-label="Option 1"
aria-selected={false}
className="combobox-option first"
id="combobox-item-0"
onClick={[Function]}
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
role="option"
style={
Object {
"height": 32,
}
}
tabIndex="-1"
>
<div>
some render of
Option 1
</div>
</div>
<div
aria-label="Option 2"
aria-selected={false}
className="combobox-option"
id="combobox-item-1"
onClick={[Function]}
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
role="option"
style={
Object {
"height": 32,
}
}
tabIndex="-1"
>
<div>
some render of
Option 2
</div>
</div>
</div>
</div>
`;

exports[`Combobox renders correctly with optionsListHeight 1`] = `
<div
className="combobox--wrapper size-medium empty"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,11 @@ describe("Combobox renders correctly", () => {
const tree = renderer.create(<Combobox loading />).toJSON();
expect(tree).toMatchSnapshot();
});

it("with optionRenderer", () => {
const options = [{ id: "1", label: "Option 1" }, { id: "2", label: "Option 2" }];
const optionRenderer = (option) => (<div>some render of {option.label}</div>);
const tree = renderer.create(<Combobox options={options} optionRenderer={optionRenderer} />).toJSON();
expect(tree).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef } from "react";
import React, { useCallback, useEffect, useRef, useMemo } from "react";
import cx from "classnames";
import Icon from "../../../Icon/Icon";
import Tooltip from "../../../Tooltip/Tooltip";
Expand All @@ -15,7 +15,8 @@ const ComboboxOption = ({
onOptionLeave,
onOptionHover,
optionLineHeight,
shouldScrollWhenActive
shouldScrollWhenActive,
optionRenderer
}) => {
const {
id,
Expand Down Expand Up @@ -95,6 +96,18 @@ const ComboboxOption = ({
tooltipContent = isOptionOverflowing ? label : null;
}

const optionRendererValue = useMemo(() => optionRenderer && optionRenderer(option), [optionRenderer, option]);

const optionValue = (
<>
{leftIcon && renderIcon(leftIcon, leftIconType, "left")}
<div ref={labelRef} className="option-label">
{label}
</div>
{rightIcon && renderIcon(rightIcon, rightIconType, "right")}
</>
);

return (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
<Tooltip content={tooltipContent}>
Expand All @@ -119,11 +132,9 @@ const ComboboxOption = ({
})}
style={{ height: optionLineHeight }}
>
{leftIcon && renderIcon(leftIcon, leftIconType, "left")}
<div ref={labelRef} className="option-label">
{label}
</div>
{rightIcon && renderIcon(rightIcon, rightIconType, "right")}
{
optionRendererValue || optionValue
}
</div>
</Tooltip>
);
Expand All @@ -135,7 +146,8 @@ ComboboxOption.iconTypes = {
};

ComboboxOption.defaultProps = {
shouldScrollWhenActive: true
shouldScrollWhenActive: true,
optionRenderer: null
};

export default ComboboxOption;