backend_extra_exercise_start
  1. formSelect.tsx
import React, {type HTMLProps, useId} from 'react'
import {cn} from '@/lib/utils'
import {Label} from '@/components/ui/label'
import FormError from '@/components/form/formError'
import {Controller} from 'react-hook-form'
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select'

interface BaseProps<T> extends HTMLProps<HTMLSelectElement> {
  name: string
  label?: string
  options: T[]
}

interface PropsWithLabel<T extends {label: string}> {
  labelExtractor?: (option: T) => string
}

interface PropsWithoutLabel<T> {
  labelExtractor: (option: T) => string
}

interface PropsWithValue<T extends {value: string}> {
  valueExtractor?: (option: T) => string
}

interface PropsWithoutValue<T> {
  valueExtractor: (option: T) => string
}

type FormSelectProps<T> = (T extends {label: string} ? PropsWithLabel<T> : PropsWithoutLabel<T>) &
  (T extends {value: string} ? PropsWithValue<T> : PropsWithoutValue<T>) &
  BaseProps<T>

function FormSelect<T>({
  label,
  name,
  className,
  options,
  labelExtractor,
  valueExtractor,
  ...inputProps
}: FormSelectProps<T>) {
  const id = useId()
  labelExtractor ??= (option: T) => (option as {label: string}).label
  valueExtractor ??= (option: T) => (option as {value: string}).value

  return (
    <div className={cn('flex flex-col gap-2 mb-2 grow', className)}>
      <Label htmlFor={id} hidden={label === undefined}>
        {label}
      </Label>

      <Controller
        name={name}
        render={({field}) => (
          <>
            <input type="hidden" id={id} value={(field.value as string) ?? ''} name={name} onChange={() => {}} />
            <Select value={`${field.value}`} onValueChange={field.onChange}>
              <SelectTrigger className="w-full">
                <SelectValue placeholder={inputProps.placeholder} />
              </SelectTrigger>
              <SelectContent>
                {options.map(options => (
                  <SelectItem key={valueExtractor(options)} value={valueExtractor(options)}>
                    {labelExtractor(options)}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </>
        )}
      />

      <FormError path={name} />
    </div>
  )
}

export default FormSelect