Thursday, 21 January 2021

Use `ListBox` from `@headlessui/react` with Mobx?

Before using ListBox:

store/index.ts

import { action, makeObservable, observable } from 'mobx'
import type { IFrameItStore, TrafficSignal } from '@/types/index'

export class FrameItStore implements IFrameItStore {
    trafficSignal: TrafficSignal = {
        shape: 'circle',
    }

    constructor() {
        makeObservable(this, {
            trafficSignal: observable,
            updateTrafficSignal: action.bound,
        })
    }

    updateTrafficSignal({ shape }: TrafficSignal) {
        if (shape) this.trafficSignal.shape = shape
    }
}

Shape.tsx

import { observer } from 'mobx-react'
import * as React from 'react'

import { useFrameItStore } from '@/store/index'
import type { TrafficSignalShape } from '@/types/index'

export const Shape = observer(() => {
    const frameItStore = useFrameItStore()
    return (
        <>
            <label htmlFor="shape" className="mb-1 text-sm font-medium text-blue-gray-500">
                Shape
            </label>
            <select
                id="shape"
                className="block w-full px-3 py-2 mb-2 bg-white border border-gray-300 rounded-md shadow-sm text-blue-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
                value={frameItStore.trafficSignal.shape}
                onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
                    const shape = e.target.value as TrafficSignalShape
                    frameItStore.updateTrafficSignal({ shape })
                }}
            >
                <option value="circle">Circle</option>
                <option value="square">Square</option>
            </select>
        </>
    )
})

App.tsx

<Shape />

After using ListBox:

Select.tsx

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

import { Selector, Check } from '@/components/icons/index'

type Option = {
    id: string
    name: string
    img: string
}

interface IProps {
    label?: string
    options: Array<Option>
}

export const Select = ({ label, options }: IProps) => {
    const [selectedOption, setSelectedOption] = React.useState<Option>(options[0])

    return (
        <Listbox value={selectedOption} onChange={setSelectedOption}>
            {({ open }) => (
                <>
                    <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="flex items-center">
                                <img
                                    src={selectedOption.img}
                                    alt={selectedOption.name}
                                    className="flex-shrink-0 w-6 h-6 rounded-full"
                                />
                                <span className="block ml-3 truncate">{selectedOption.name}</span>
                            </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 w-full mt-1 bg-white rounded-md shadow-lg">
                            <Transition
                                show={open}
                                leave="transition duration-100 ease-in"
                                leaveFrom="opacity-100"
                                leaveTo="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) => (
                                        <Listbox.Option as={React.Fragment} key={option.id} value={option}>
                                            {({ active, selected }) => (
                                                <li
                                                    className={clsx('relative py-2 pl-3 cursor-default select-none pr-9', {
                                                        'text-white bg-indigo-600': active,
                                                        'text-gray-900': !active,
                                                    })}
                                                >
                                                    <div className="flex items-center">
                                                        <img
                                                            src={option.img}
                                                            alt={option.name}
                                                            className="flex-shrink-0 w-6 h-6 rounded-full"
                                                        />
                                                        <span
                                                            className={clsx('ml-3 block truncate', {
                                                                'font-semibold': selected,
                                                                'font-normal': !selected,
                                                            })}
                                                        >
                                                            {option.name}
                                                        </span>
                                                    </div>
                                                    {selected && (
                                                        <span
                                                            className={clsx('absolute inset-y-0 right-0 flex items-center pr-4', {
                                                                'text-white': active,
                                                                'text-indigo-600': !active,
                                                            })}
                                                        >
                                                            <Check />
                                                        </span>
                                                    )}
                                                </li>
                                            )}
                                        </Listbox.Option>
                                    ))}
                                </Listbox.Options>
                            </Transition>
                        </div>
                    </div>
                </>
            )}
        </Listbox>
    )
}

App.tsx

const shapes = [
    {
        id: '1',
        name: 'Circle',
        img:
            'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
    },
    {
        id: '2',
        name: 'Square',
        img:
            'https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
    },
]

<Select label="Shape" options={shapes} />

How do I convert the After part to use MobX like the Before part?

I tried passing value & onChange as it is in the Before part to Select like:

App.tsx

<Select
  label="Shape"
  options={shapes}
  value={frameItStore.trafficSignal.shape}
  onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
    const shape = e.target.value as TrafficSignalShape
    frameItStore.updateTrafficSignal({ shape })
  }}
/>

Select.tsx

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

export const Select = ({ label, options, value, onChange }: IProps) => {
    const [selectedOption, setSelectedOption] = React.useState<Option>(options[0])

    return (
        <Listbox value={value} onChange={onChange}>
        .
        .
        .
        </Listbox>
    )
}

But it doesn't select anything & I don't know what to do of selectedOption?



from Use `ListBox` from `@headlessui/react` with Mobx?

No comments:

Post a Comment