Skip to content

Commit

Permalink
feat: select option tooltip (#383)
Browse files Browse the repository at this point in the history
* feat(select): max menu width
* feat(select): select custom option with tooltip
* chore(select): code improvement on custom option

---------

Co-authored-by: Thrasos Kafasis <[email protected]>
  • Loading branch information
ThrasyvoulosKafasis and Thrasos Kafasis authored Aug 27, 2024
1 parent bbfbbc8 commit 1c7c71c
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 14 deletions.
11 changes: 10 additions & 1 deletion src/components/FormElements/Select/Select.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState } from "react";
import { Story } from "@storybook/react";
import { CustomSelectProps, CustomOption } from "./types";
import { defaultOptions, groupedOptions } from "./data";
import { defaultOptions, groupedOptions, menuMaxWidthOptions } from "./data";
import Select from "./Select";
import { selectOptionsWithLevels } from "./constants";
import { formatOptionLabel } from "./helpers";
Expand Down Expand Up @@ -75,6 +75,15 @@ Default.args = {
options: defaultOptions,
};

export const WithMenuMaxWidth = Template.bind({});

WithMenuMaxWidth.args = {
options: menuMaxWidthOptions,
minWidth: "300px",
maxWidth: "300px",
menuMaxWidth: 500,
};

export const WithNestLevels = Template.bind({});

WithNestLevels.args = {
Expand Down
9 changes: 6 additions & 3 deletions src/components/FormElements/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import {
} from "react-select";
import { SerializedStyles } from "@emotion/react";
import { useClickAway } from "ahooks";
import { AddOperatorSVG, InfoCircledSVG } from "../../../icons";
import Label from "../Label/Label";
import Tooltip from "../../Tooltip/Tooltip";
import { AddOperatorSVG, InfoCircledSVG } from "../../../icons";
import CustomValueContainer from "./components/CustomValueContainer";
import { resolveStyles, selectContainer } from "./styles";
import CustomOptionComponent from "./components/CustomOption";
import CustomMenuList from "./components/CustomMenuList";
import { resolveStyles, selectContainer } from "./styles";
import { CustomOption, CustomSelectProps } from "./types";
import {
MAX_MENU_HEIGHT,
Expand Down Expand Up @@ -64,6 +65,7 @@ const Select: ForwardRefRenderFunction<
isInputValid,
checkIfInputIsSelected,
closeMenuOnSelect,
menuMaxWidth,
...rest
} = props;
const hasLabel = Boolean(label);
Expand Down Expand Up @@ -96,7 +98,7 @@ const Select: ForwardRefRenderFunction<
(tooltipContent && typeof tooltipContent === "string" && tooltipContent !== "") ||
isValidElement(tooltipContent);

const styles = resolveStyles(size, hasInnerSearch);
const styles = resolveStyles({ size, hasInnerSearch, menuMaxWidth });

const formatCreateLabel = (inputValue: string) => (
<div>
Expand Down Expand Up @@ -157,6 +159,7 @@ const Select: ForwardRefRenderFunction<
ValueContainerProps<CustomOption, boolean, GroupBase<CustomOption>>
>,
) => CustomValueContainer({ ...props, isFocused }),
Option: CustomOptionComponent,
},
formatCreateLabel,
isSearchable: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,27 +105,51 @@ exports[`<Select /> matches snapshot 1`] = `
>
<div
aria-disabled="false"
class="option-md css-1haxlpt-option"
class="option-md css-bi1k7u-option"
id="react-select-3-option-0"
tabindex="-1"
>
name
<div
aria-expanded="false"
>
<div
class="custom-option"
>
name
</div>
</div>
</div>
<div
aria-disabled="false"
class="option-md css-8anid-option"
class="option-md css-iovxfg-option"
id="react-select-3-option-1"
tabindex="-1"
>
surname
<div
aria-expanded="false"
>
<div
class="custom-option"
>
surname
</div>
</div>
</div>
<div
aria-disabled="false"
class="option-md css-8anid-option"
class="option-md css-iovxfg-option"
id="react-select-3-option-2"
tabindex="-1"
>
age
<div
aria-expanded="false"
>
<div
class="custom-option"
>
age
</div>
</div>
</div>
</div>
</div>
Expand Down
32 changes: 32 additions & 0 deletions src/components/FormElements/Select/components/CustomOption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { FC, useEffect, useRef, useState } from "react";
import { components, OptionProps } from "react-select";
import { CustomOption } from "../types";
import Tooltip from "../../../Tooltip/Tooltip";

const CustomOptionComponent: FC<OptionProps<CustomOption>> = (props) => {
const [isOverflowActive, setIsOverflowActive] = useState(false);
const optionRef = useRef<HTMLDivElement>(null);

const { isDisabled, data } = props;
const { label } = data;
const isTooltipDisabled = isDisabled || !isOverflowActive;

useEffect(() => {
if (optionRef.current) {
const hasOverflow = optionRef.current.scrollWidth > optionRef.current.clientWidth;
setIsOverflowActive(hasOverflow);
}
}, [props.label]);

return (
<components.Option {...props}>
<Tooltip content={label} disabled={isTooltipDisabled}>
<div className="custom-option" ref={optionRef}>
{label}
</div>
</Tooltip>
</components.Option>
);
};

export default CustomOptionComponent;
22 changes: 22 additions & 0 deletions src/components/FormElements/Select/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,25 @@ export const defaultOptions: OptionType[] = [
{ label: "Java", value: "java" },
{ label: "Disabled option", value: "disabled", disabled: true },
];

export const menuMaxWidthOptions: OptionType[] = [
{
label:
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book",
value: "lorem",
},
{ label: "JavaScript is the best in the world", value: "js" },
{ label: "TypeScript", value: "ts" },
{ label: "GoLang", value: "go" },
{ label: "Python", value: "python" },
{ label: "PHP", value: "php" },
{ label: "C++", value: "c++" },
{ label: "C#", value: "c#" },
{ label: "Java", value: "java" },
{
label:
"Disabled Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book",
value: "lorem disabled",
disabled: true,
},
];
28 changes: 24 additions & 4 deletions src/components/FormElements/Select/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,28 @@ export const customMenuList = ({
}
`;

export const resolveStyles = (
size: string,
hasInnerSearch: boolean,
): StylesConfig<CustomOption> => ({
export const resolveStyles = ({
size,
hasInnerSearch,
menuMaxWidth,
}: {
size: string;
hasInnerSearch: boolean;
menuMaxWidth?: number;
}): StylesConfig<CustomOption> => ({
menu: (base: CSSObjectWithLabel) => {
const menuMaxWidthStyles = menuMaxWidth
? {
width: "fit-content",
maxWidth: menuMaxWidth,
minWidth: "100%",
}
: {};

return {
...base,
zIndex: 1060,
...menuMaxWidthStyles,
};
},
placeholder: (
Expand Down Expand Up @@ -284,6 +298,12 @@ export const resolveStyles = (
: "transparent",
},
cursor: isDisabled ? "default" : "pointer",
".custom-option": {
wordBreak: "break-word",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
},
}),
menuList: (base: CSSObjectWithLabel) => ({
...base,
Expand Down
1 change: 1 addition & 0 deletions src/components/FormElements/Select/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export type CustomSelectProps<
checkIfInputIsSelected?: (inputValue: string) => string;
tooltipContent?: string | JSX.Element;
countOptionsForInnerSearch?: number;
menuMaxWidth?: number;
};

export type CustomMenuListProps<
Expand Down

0 comments on commit 1c7c71c

Please sign in to comment.