rds_agi

Async Select

Component 128

About

Async Select component built with React & shadcn/ui

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Async Select to your project and fits it to your theme.

Code

How Async Select is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import React, { useState } from "react";
import Image from "next/image";
import { AsyncSelect } from "@/components/ui/async-select";

// Types
interface User {
  id: string;
  name: string;
  role: string;
  avatar: string;
  email: string;
}

// Mock data
const mockUsers: User[] = [
  {
    id: "1",
    name: "John Doe",
    role: "Software Engineer",
    email: "john@example.com",
    avatar: "https://cdn.21st.dev/assets/mirror/72/7291bf3a6381ca5bc39bf4e00fadbad2a4d9249aaee9ecd5c38747ed018f3c11.svg",
  },
  {
    id: "2",
    name: "Jane Smith",
    role: "Product Manager",
    email: "jane@example.com",
    avatar: "https://cdn.21st.dev/assets/mirror/4a/4a6bd33ee49557e788dbf925f6ccc7fc132f3daff29df582dbe1deaf5e50ae2d.svg",
  },
  {
    id: "3",
    name: "Bob Johnson",
    role: "Designer",
    email: "bob@example.com",
    avatar: "https://cdn.21st.dev/assets/mirror/57/57e41047e6905dd06bd1662e36f7da967875521e7f8cb63faeea289835b62134.svg",
  },
  {
    id: "4",
    name: "Alice Brown",
    role: "Developer",
    email: "alice@example.com",
    avatar: "https://cdn.21st.dev/assets/mirror/91/91552c89c71e2afe35041dd1db8f6c5e862747aa5240efe2c8056eb1c3b4dc6d.svg",
  },
];

// Mock API call
const searchUsers = async (query?: string): Promise<User[]> => {
  // Simulate API delay
  await new Promise(resolve => setTimeout(resolve, 500));

  if (!query) return mockUsers;

  return mockUsers.filter(user => 
    user.name.toLowerCase().includes(query.toLowerCase()) ||
    user.role.toLowerCase().includes(query.toLowerCase()) ||
    user.email.toLowerCase().includes(query.toLowerCase())
  );
};

// Basic Demo
function BasicAsyncSelectDemo() {
  const [selectedUser, setSelectedUser] = useState("");

  return (
    <div className="flex flex-col gap-2">
      <h3 className="text-lg font-medium">Basic Usage</h3>
      <AsyncSelect<User>
        fetcher={searchUsers}
        renderOption={(user) => (
          <div className="flex items-center gap-2">
            <Image
              src={user.avatar}
              alt={user.name}
              width={24}
              height={24}
              className="rounded-full"
            />
            <div className="flex flex-col">
              <div className="font-medium">{user.name}</div>
              <div className="text-xs text-muted-foreground">{user.role}</div>
            </div>
          </div>
        )}
        getOptionValue={(user) => user.id}
        getDisplayValue={(user) => (
          <div className="flex items-center gap-2 text-left">
            <Image
              src={user.avatar}
              alt={user.name}
              width={24}
              height={24}
              className="rounded-full"
            />
            <div className="flex flex-col leading-tight">
              <div className="font-medium">{user.name}</div>
              <div className="text-xxs text-muted-foreground">{user.role}</div>
            </div>
          </div>
        )}
        notFound={<div className="py-6 text-center text-sm">No users found</div>}
        label="User"
        placeholder="Search users..."
        value={selectedUser}
        onChange={setSelectedUser}
        width="375px"
      />
      <p className="text-sm text-muted-foreground">
        Selected user ID: {selectedUser || "none"}
      </p>
    </div>
  );
}

// Preloaded Demo
function PreloadedAsyncSelectDemo() {
  const [selectedUser, setSelectedUser] = useState("");

  return (
    <div className="flex flex-col gap-2">
      <h3 className="text-lg font-medium">Preloaded Data</h3>
      <AsyncSelect<User>
        fetcher={searchUsers}
        preload={true}
        filterFn={(user, query) => 
          user.name.toLowerCase().includes(query.toLowerCase()) ||
          user.role.toLowerCase().includes(query.toLowerCase())
        }
        renderOption={(user) => (
          <div className="flex items-center gap-2">
            <Image
              src={user.avatar}
              alt={user.name}
              width={24}
              height={24}
              className="rounded-full"
            />
            <div className="flex flex-col">
              <div className="font-medium">{user.name}</div>
              <div className="text-xs text-muted-foreground">{user.email}</div>
            </div>
          </div>
        )}
        getOptionValue={(user) => user.id}
        getDisplayValue={(user) => user.name}
        label="User"
        placeholder="Select user..."
        value={selectedUser}
        onChange={setSelectedUser}
        width="300px"
      />
    </div>
  );
}

// Disabled Demo
function DisabledAsyncSelectDemo() {
  const [selectedUser, setSelectedUser] = useState("");

  return (
    <div className="flex flex-col gap-2">
      <h3 className="text-lg font-medium">Disabled State</h3>
      <AsyncSelect<User>
        fetcher={searchUsers}
        renderOption={(user) => user.name}
        getOptionValue={(user) => user.id}
        getDisplayValue={(user) => user.name}
        label="User"
        placeholder="Select user..."
        value={selectedUser}
        onChange={setSelectedUser}
        disabled={true}
        width="250px"
      />
    </div>
  );
}

// Custom Loading Demo
function CustomLoadingAsyncSelectDemo() {
  const [selectedUser, setSelectedUser] = useState("");

  return (
    <div className="flex flex-col gap-2">
      <h3 className="text-lg font-medium">Custom Loading State</h3>
      <AsyncSelect<User>
        fetcher={searchUsers}
        renderOption={(user) => user.name}
        getOptionValue={(user) => user.id}
        getDisplayValue={(user) => user.name}
        loadingSkeleton={
          <div className="p-4 text-center">
            <div className="animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full mx-auto" />
            <p className="mt-2 text-sm text-muted-foreground">Loading users...</p>
          </div>
        }
        label="User"
        placeholder="Select user..."
        value={selectedUser}
        onChange={setSelectedUser}
        width="250px"
      />
    </div>
  );
}

export { BasicAsyncSelectDemo, PreloadedAsyncSelectDemo, DisabledAsyncSelectDemo, CustomLoadingAsyncSelectDemo }

Dependencies