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

Fix drop down counter in multi state with single line #503

Merged
merged 13 commits into from
Jan 31, 2022
67 changes: 47 additions & 20 deletions src/components/Dropdown/__stories__/dropdown.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import person3 from "./assets/person3.png";
import person2 from "./assets/person2.png"
import Avatar from "../../Avatar/Avatar.jsx";
import { OptionRenderer } from "./OptionRenderer.js";
import "./dropdown.stories.scss";
import Flex from "../../../components/Flex/Flex";
import "./dropdown.stories.scss"; import {
StoryDescription
} from "../../../storybook/components/story-description/story-description";

<Meta
title="Inputs/Dropdown"
Expand Down Expand Up @@ -88,27 +91,51 @@ There are three sizes available: Small, Medium, and Large
</Story>
</Canvas>

### Close dropdown with multi choose
Pass closeMenuOnSelect to close the multi choose any time an options is chosen.
### Multi line states
The `<Dropdown>` component supports multiple options selection in two different state - single line and multiple lines.
<Canvas>
<Story name="Close dropdown with multi choose">
<Story name="Multi-choice with different states">
{() => {
const options = useMemo(() => [
{ value: "Dor Yehuda", label: "Hadas Farhi", src: person1, type: Avatar.types.IMG, size: Avatar.sizes.SMALL, name: "Dor Yehuda", position: "(Full Stack Developer)" },
{ value: "No", label: "Rotem Dekel", src: person3, type: Avatar.types.IMG, size: Avatar.sizes.SMALL, name: "Rotem Dekel", position: "(Product Designer)" },
{ value: "Yes", label: "Netta Muller", src: person2, type: Avatar.types.IMG, size: Avatar.sizes.SMALL, name: "Netta Muller", position: "(Brand Designer)" },
], []);
return (
<Dropdown
defaultValue={[options[0]]}
options={options}
multi
optionRenderer={OptionRenderer}
className="dropdown-stories-styles_with-chips"
closeMenuOnSelect
/>
)
}}
const options = useMemo( () => [
{
value: "Rotem",
label: "Rotem Dekel"
}, {
value:"Hadas",
label: "Hadas Farhi"
}, {
value: "Netta",
label: "Netta Muller",
},
{
value: "Dor",
label: "Dor Yehuda",
}], []);
return (<Flex gap={Flex.gaps.MEDIUM}>
<StoryDescription description="Single line" vertical>
<div style={{width: "400px"}}>
<Dropdown
defaultValue={[options[0]]}
options={options}
multi
className="dropdown-stories-styles_with-chips"
/>
</div>
</StoryDescription>
<StoryDescription description="Multiple lines" vertical>
<div style={{width: "400px"}}>
<Dropdown
defaultValue={[options[0]]}
options={options}
multi
multiline
className="dropdown-stories-styles_with-chips"
/>
</div>
</StoryDescription>
</Flex>);
}
}
</Story>
</Canvas>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
/* eslint-disable react/no-unstable-nested-components */
import React, { useState, useEffect, useCallback } from "react";
import React, { useState, useCallback } from "react";
import { components } from "react-select";
import { useHiddenOptionsData } from "../../hooks/useHiddenOptionsData";
import Counter from "../../../Counter/Counter";
import Dialog from "../../../Dialog/Dialog";
import DialogContentContainer from "../../../DialogContentContainer/DialogContentContainer";
import Chips from "../../../Chips/Chips";
import classes from "./ValueContainer.module.scss";

const EMPTY_ARRAY = [];

export default function Container({ children, selectProps, ...otherProps }) {
const { placeholder, selectProps: customProps = {} } = selectProps;
const { selectedOptions, onSelectedDelete, setIsDialogShown, isDialogShown, isMultiline } = customProps;
const clickHandler = children[1];
const [ref, setRef] = useState();
const [isCounterShown, setIsCounterShown] = useState(false);
const [overflowingIndex, setOverflowingIndex] = useState(-1);
const isOverflowCalculated = overflowingIndex > -1;
const overflowingChildren = isOverflowCalculated
? selectedOptions.slice(overflowingIndex, selectedOptions.length)
: EMPTY_ARRAY;
const chipClassName = isMultiline ? classes["multiselect-chip-multi-line"] : classes["multiselect-chip-single-line"];

const { overflowIndex, hiddenOptionsCount } = useHiddenOptionsData({
isMultiline,
ref,
chipClassName,
selectedOptionsCount: selectedOptions.length
});
const isCounterShown = hiddenOptionsCount > 0;
const renderOptions = useCallback(
(from = 0, to = selectedOptions.length) =>
selectedOptions.map((option, index) =>
Expand All @@ -43,36 +42,6 @@ export default function Container({ children, selectProps, ...otherProps }) {
[selectedOptions, onSelectedDelete, chipClassName]
);

useEffect(() => {
let index = -1;

if (ref) {
const { bottom: parentBottom } = ref.getBoundingClientRect();
let chipIndex = 0;

for (let i = 0; i < ref.children.length; i++) {
const child = ref.children[i];
const isChip = child.classList.contains(chipClassName);
const { bottom: childBottom } = child.getBoundingClientRect();

if (isChip) {
if (childBottom > parentBottom) {
index = chipIndex;
break;
}

chipIndex++;
}
}
}

setOverflowingIndex(index);
}, [ref, isCounterShown, chipClassName]);

useEffect(() => {
setIsCounterShown(!!overflowingChildren.length);
}, [overflowingChildren.length]);

return (
<components.ValueContainer selectProps={selectProps} {...otherProps}>
<div className={classes["value-container"]}>
Expand All @@ -88,9 +57,9 @@ export default function Container({ children, selectProps, ...otherProps }) {
>
{isCounterShown ? (
<>
{renderOptions(0, overflowingIndex)}
{renderOptions(0, overflowIndex)}
{clickHandler}
{renderOptions(overflowingIndex)}
{renderOptions(overflowIndex)}
</>
) : (
<>
Expand All @@ -104,7 +73,7 @@ export default function Container({ children, selectProps, ...otherProps }) {
<Dialog
content={() => (
<DialogContentContainer className={classes["value-container-dialog-content"]}>
{renderOptions(overflowingIndex)}
{renderOptions(overflowIndex)}
</DialogContentContainer>
)}
tooltip
Expand All @@ -117,7 +86,7 @@ export default function Container({ children, selectProps, ...otherProps }) {
<Counter
kind={Counter.kinds.LINE}
prefix="+"
count={overflowingChildren.length}
count={hiddenOptionsCount}
onMouseDown={e => {
e.stopPropagation();
}}
Expand Down
33 changes: 33 additions & 0 deletions src/components/Dropdown/hooks/useHiddenOptionsData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useEffect, useState } from "react";

export function useHiddenOptionsData({ isMultiline, ref, selectedOptionsCount, chipClassName }) {
const [overflowIndex, setOverflowIndex] = useState(-1);
useEffect(() => {
let finalOverflowingIndex = -1;

if (ref?.children && !isMultiline) {
const { bottom: parentBottom } = ref.getBoundingClientRect();
let optionIndex = 0;
let childIndex = 0;

while (childIndex < ref.children.length && optionIndex < selectedOptionsCount) {
const child = ref.children[childIndex];
const isOption = child.classList.contains(chipClassName);

if (isOption) {
const { bottom: childBottom } = child.getBoundingClientRect();
if (childBottom > parentBottom) {
finalOverflowingIndex = optionIndex;
break;
}
optionIndex++;
}
childIndex++;
}
}

setOverflowIndex(finalOverflowingIndex);
}, [ref, isMultiline, selectedOptionsCount, chipClassName, setOverflowIndex]);
const hiddenOptionsCount = overflowIndex > -1 ? selectedOptionsCount - overflowIndex : 0;
return { overflowIndex, hiddenOptionsCount };
}