Select Field

PreviousNext

A component for Wandry UI

Docs
wandry-uicomponent

Preview

Loading preview…
registry/wandry-ui/select-field.tsx
"use client";

import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select";

import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";

import {
  Field,
  FieldDescription,
  FieldError,
  FieldLabel,
} from "@/components/ui/field";
import { useField } from "@wandry/inertia-form";
import { Button } from "@/components/ui/button";
import { XIcon } from "lucide-react";

interface Options {
  value: string;
  label: string;
}
interface GroupedOptions {
  [key: string]: { value: string; label: string }[];
}

export type SelectProps = React.ComponentProps<typeof SelectPrimitive.Root>;
export type SelectGroupProps = React.ComponentProps<
  typeof SelectPrimitive.Group
>;
export type SelectValueProps = React.ComponentProps<
  typeof SelectPrimitive.Value
>;
export type SelectContentProps = React.ComponentProps<
  typeof SelectPrimitive.Content
>;
export type SelectItemProps = React.ComponentProps<typeof SelectPrimitive.Item>;

export type SelectTriggerProps = React.ComponentProps<
  typeof SelectPrimitive.Trigger
> & {
  size?: "sm" | "default";
};

type SelectFieldClasses = {
  field?: string;
  label?: string;
  trigger?: string;
  content?: string;
  description?: string;
  error?: string;
};

export type SelectFieldProps = {
  name: string;
  placeholder?: string;
  label?: string;
  description?: string;
  errorName?: string;
  options: Options[] | GroupedOptions;
  selectProps?: SelectProps;
  contentProps?: SelectContentProps;
  triggerProps?: SelectTriggerProps;
  itemProps?: Omit<SelectItemProps, "value">;
  classes?: SelectFieldClasses;
};

const isGroupedOptions = (
  options: Options[] | GroupedOptions
): options is GroupedOptions => {
  return !Array.isArray(options);
};

const SelectField: React.FC<SelectFieldProps> = ({
  name,
  options,
  label,
  description,
  placeholder,
  selectProps,
  contentProps,
  triggerProps,
  itemProps,
  classes,
  errorName,
}) => {
  const field = useField(name, { errorName });

  return (
    <Field className={classes?.field}>
      <FieldLabel className={classes?.label}>{label}</FieldLabel>
      <Select
        name={name}
        value={field.value}
        onValueChange={field.onChange}
        {...selectProps}
      >
        <SelectTrigger className={classes?.trigger} {...triggerProps}>
          <SelectValue placeholder={placeholder} />
        </SelectTrigger>
        <SelectContent className={classes?.content} {...contentProps}>
          {isGroupedOptions(options) ? (
            <SelectFieldGroupedContent options={options} {...itemProps} />
          ) : (
            <SelectFieldContent options={options} {...itemProps} />
          )}
        </SelectContent>
      </Select>
      <FieldDescription className={classes?.description}>
        {description}
      </FieldDescription>
      <FieldError className={classes?.error}>{field.error}</FieldError>
    </Field>
  );
};

const SelectFieldContent: React.FC<
  { options: Options[] } & Omit<SelectItemProps, "value">
> = ({ options, ...itemProps }) => {
  return (
    <>
      {options.map((option) => (
        <SelectItem {...itemProps} key={option.value} value={option.value}>
          {option.label}
        </SelectItem>
      ))}
    </>
  );
};

const SelectFieldGroupedContent: React.FC<
  { options: GroupedOptions } & Omit<SelectItemProps, "value">
> = ({ options, ...itemProps }) => {
  return (
    <>
      {Object.entries(options).map(([group, items]) => (
        <SelectGroup key={group}>
          <SelectLabel>{group}</SelectLabel>
          {items.map((item) => (
            <SelectItem {...itemProps} key={item.value} value={item.value}>
              {item.label}
            </SelectItem>
          ))}
        </SelectGroup>
      ))}
    </>
  );
};

export default SelectField;

Installation

npx shadcn@latest add @wandry-ui/select-field

Usage

import { SelectField } from "@/components/select-field"
<SelectField />