Skip to content

Instantly share code, notes, and snippets.

@AdrianMachado
Created December 9, 2022 23:24
Show Gist options
  • Save AdrianMachado/9a3e32f20f7aab9ca5df79561d1e21c2 to your computer and use it in GitHub Desktop.
Save AdrianMachado/9a3e32f20f7aab9ca5df79561d1e21c2 to your computer and use it in GitHub Desktop.
React HTML Select Element with custom arrow and auto-resizing
// NOTE: This sample uses Tailwind CSS
import { SelectorIcon } from "@heroicons/react/outline";
import cn from "classnames";
import { useEffect, useRef } from "react";
type FlexSelectProps = {
className?: string;
value: number | string | undefined;
onChange: React.ChangeEventHandler<HTMLSelectElement> | undefined;
children: React.ReactNode;
};
const getSelectedOptionLength = (selectElem: HTMLSelectElement) => {
const selectedIndex = selectElem.selectedIndex;
const selectedOption: HTMLOptionElement | undefined =
selectElem.options[selectedIndex];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!selectedOption) {
return undefined;
}
if (selectedOption.textContent) {
// Create a fake select element
const tempSelect = document.createElement("select");
const tempOption = document.createElement("option");
// Assign the currently selected option as the only option
// A select will automatically resize to its longest option
tempOption.textContent = selectedOption.textContent;
// Make sure it is hidden
tempSelect.style.cssText += `
visibility: hidden;
position: fixed;
`;
// Add it to the DOM to be rendered
tempSelect.appendChild(tempOption);
selectElem.after(tempSelect);
// Grab the width of the select element
const tempSelectWidth = tempSelect.getBoundingClientRect().width;
tempSelect.remove();
return `${tempSelectWidth}px`;
}
return undefined;
};
/**
*
* @description A version of the HTMLSelectElement that is customized so it
* expands and contracts based on the width of the currently selected option's
* textContent. The way the width is determined is by creating a temporary
* select element with the currently selected element as the only option, and
* measuring the width of that element.
* src: https://stackoverflow.com/a/67239947/18401647
*/
const FlexSelect = ({
value,
onChange,
className,
children,
}: FlexSelectProps) => {
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const textLength = getSelectedOptionLength(e.currentTarget);
if (textLength) {
e.currentTarget.style.width = textLength;
}
onChange?.(e);
};
useEffect(() => {
if (typeof value === "string" && selectRef.current) {
const textLength = getSelectedOptionLength(selectRef.current);
if (textLength) {
selectRef.current.style.width = textLength;
}
}
}, [value]);
const selectRef = useRef<HTMLSelectElement>(null);
return (
<div className="w-full flex items-center relative">
<select
ref={selectRef}
className={cn(
"bg-transparent z-4 py-1 rounded cursor-pointer appearance-none pr-5 static",
className
)}
value={value}
onChange={handleChange}
>
{children}
</select>
<SelectorIcon className="absolute right-0 z-2 pointer-events-none w-5 h-5" />
</div>
);
};
export default FlexSelect;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment