Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set max-content on `ListBox` from `@headlessui/react` to take max width of option while using Tailwind CSS?

I have a sidebar containing 2 select elements & 1 input box in between both of them that looks like:

sidebar with 2 listbox items

The biggest option on the 1st select element is 0.75x & on the 2nd select element is WEBP.

constants.ts

export const pixelRatios = [
    {
        id: "1",
        label: "0.5x",
        value: "0.5",
    },
    {
        id: "2",
        label: "0.75x",
        value: "0.75",
    },
    {
        id: "3",
        label: "1x",
        value: "1",
    },
    {
        id: "4",
        label: "1.5x",
        value: "1.5",
    },
    {
        id: "5",
        label: "2x",
        value: "2",
    },
    {
        id: "6",
        label: "3x",
        value: "3",
    },
    {
        id: "7",
        label: "4x",
        value: "4",
    },
]

export const extensions = [
    {
        id: "1",
        label: "PNG",
        value: "png",
    },
    {
        id: "2",
        label: "JPEG",
        value: "jpeg",
    },
    {
        id: "3",
        label: "WEBP",
        value: "webp",
    },
]

Select.tsx

import * as React from "react"
import { Listbox, Transition } from "@headlessui/react"
import clsx from "clsx"

interface Option {
    id: string
    value: string
    label: string
}

interface IProps {
    className?: string
    label?: string
    selectedOption: Option
    onChange: (selectedOption: Option) => void
    options: Array<Option>
}

const Selector = () => (
    <svg
        className="w-5 h-5 text-indigo-600"
        xmlns="http://www.w3.org/2000/svg"
        viewBox="0 0 20 20"
        fill="currentColor"
        aria-hidden="true"
    >
        <path
            fillRule="evenodd"
            d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
            clipRule="evenodd"
        />
    </svg>
)

export const Select = ({
    className,
    label,
    options,
    selectedOption,
    onChange,
}: IProps) => {
    return (
        <Listbox
            as="div"
            className={className}
            value={selectedOption}
            onChange={(selectedOption: Option) => {
                onChange(selectedOption)
            }}
        >
            {({ open }) => (
                <>
                    {label && (
                        <Listbox.Label className="mb-1 text-sm font-medium text-blue-gray-500">
                            {label}
                        </Listbox.Label>
                    )}
                    <div className="relative mt-1">
                        <Listbox.Button className="relative w-full py-2 pl-3 pr-10 text-left bg-white border border-gray-300 rounded-md shadow-sm cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                            <span className="block ml-1">{selectedOption.label}</span>
                            <span className="absolute inset-y-0 right-0 flex items-center pr-2 ml-3 pointer-events-none">
                                <Selector />
                            </span>
                        </Listbox.Button>

                        <div className="absolute bottom-0 z-10 w-full mt-1 bg-white rounded-md shadow-lg mb-11">
                            {/* bottom-0 will open the select menu up & mb-11 will put the dropup above the select option */}
                            <Transition
                                show={open}
                                leave="transition duration-100 ease-in"
                                leaveFrom="transform opacity-100"
                                leaveTo="transform opacity-0"
                            >
                                <Listbox.Options
                                    static
                                    className="py-1 overflow-auto text-base rounded-md max-h-56 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
                                >
                                    {options.map((option) => {
                                        return (
                                            <Listbox.Option
                                                as={React.Fragment}
                                                key={option.id}
                                                value={option}
                                            >
                                                {({ active, selected }) => {
                                                    return (
                                                        <li
                                                            className={clsx(
                                                                "relative py-2 pl-3 cursor-default select-none pr-9 text-sm",
                                                                {
                                                                    "text-white bg-indigo-600": active,
                                                                    "text-gray-900": !active,
                                                                },
                                                            )}
                                                        >
                                                            <div className="flex items-center">
                                                                <span
                                                                    className={clsx("ml-3 block", {
                                                                        "font-semibold": selected,
                                                                        "font-normal": !selected,
                                                                    })}
                                                                >
                                                                    {option.label}
                                                                </span>
                                                            </div>
                                                        </li>
                                                    )
                                                }}
                                            </Listbox.Option>
                                        )
                                    })}
                                </Listbox.Options>
                            </Transition>
                        </div>
                    </div>
                </>
            )}
        </Listbox>
    )
}

App.tsx

import * as React from "react"

import { Select } from "./Select"
import { pixelRatios, extensions } from "./constants"

export type Extension = "jpeg" | "png" | "webp"
export type PixelRatio = 0.5 | 0.75 | 1 | 1.5 | 2 | 3 | 4

export default function App() {
    const [pixelRatio, setPixelRatio] = React.useState(pixelRatios[0])
    const [extension, setExtension] = React.useState(extensions[0])
    const [suffix, setSuffix] = React.useState("")
    return (
        <div className="w-full">
            <h1 className="text-4xl text-center">Select (Max Content)</h1>
            <div
                id="sidebar"
                className="fixed top-0 right-0 h-full px-3 py-4 overflow-y-auto shadow-md bg-pink-100"
                style={{
                    minWidth: "300px",
                }}
            >
                <div className="absolute bottom-10">
                    <label className="mt-1 text-sm font-medium text-blue-gray-500">
                        Export
                    </label>
                    <div className="flex items-center justify-between w-full space-x-2">
                        <Select
                            className="flex-1"
                            options={pixelRatios}
                            selectedOption={pixelRatio}
                            onChange={(selectedOption) => {
                                setPixelRatio(selectedOption)
                            }}
                        />
                        <input
                            type="text"
                            name="suffix"
                            className="relative flex-1 w-16 px-2 py-2 mt-1 text-sm border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500"
                            placeholder="Suffix"
                            value={suffix}
                            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                                const suffix = e.target.value as string
                                setSuffix(suffix)
                            }}
                        />
                        <Select
                            className="flex-1"
                            options={extensions}
                            selectedOption={extension}
                            onChange={(selectedOption) => {
                                setExtension(selectedOption)
                            }}
                        />
                    </div>
                </div>
            </div>
        </div>
    )
}

When I select the biggest option from either of the sidebar, it moves the sidebar a bit. It also expands the select element a bit.

Check it out live on Codesandbox → https://codesandbox.io/s/react-tailwind-select-max-content-sidebar-vv58m?file=/src/App.tsx

How do I make the select element take the width of the max content? And how do I stop the sidebar & select element from expanding?

like image 448
deadcoder0904 Avatar asked Jan 22 '21 07:01

deadcoder0904


1 Answers

Add flex: 1 only to the item you want to fill all available space.

your export stuff (or footer) inside sidebar is using position: absolute and because of padding you need to specify width of footer like this:

width: calc(100% - 2 * padding)

by this changes, i reached to this codesandbox.

like image 193
armin yahya Avatar answered Nov 03 '22 23:11

armin yahya