feat: add reusable AsyncSelect and ObjectSelect components with infinite scroll hook support

This commit is contained in:
Firman Ramdhani
2026-06-16 01:23:36 +07:00
parent 19066dde89
commit 195928d56a
14 changed files with 1706 additions and 80 deletions
@@ -6,10 +6,33 @@ import {
FieldMultiSelect, FieldNativeSelect, FieldTagsInput, FieldCheckbox,
FieldRadioGroup, FieldSwitch, FieldChipGroup, FieldSegmentedControl,
FieldSlider, FieldRangeSlider, FieldRating, FieldColorInput,
FieldColorPicker, FieldFileInput
FieldColorPicker, FieldFileInput, FieldObjectSelect, FieldAsyncSelect
} from '@repo/ui/form';
import { useFormDemoTranslation } from '../i18n/useFormDemoTranslation';
const MOCK_POKEMON = Array.from({ length: 100 }, (_, i) => ({
id: i + 1,
name: `Pokemon ${i + 1}`,
}));
const fetchMockPokemon = async (url: string) => {
// Parse the query params sent by useInfiniteScroll
const urlObj = new URL(url, 'http://localhost');
const page = parseInt(urlObj.searchParams.get('page') || '1', 10);
const pageSize = parseInt(urlObj.searchParams.get('pageSize') || '20', 10);
const search = urlObj.searchParams.get('search')?.toLowerCase() || '';
// Fake network delay
await new Promise((resolve) => setTimeout(resolve, 500));
// Filter and paginate
const filtered = MOCK_POKEMON.filter((p) => p.name.toLowerCase().includes(search));
const start = (page - 1) * pageSize;
const paginated = filtered.slice(start, start + pageSize);
return { results: paginated };
};
export default function AllFieldsDemo() {
const t = useFormDemoTranslation();
@@ -37,7 +60,13 @@ export default function AllFieldsDemo() {
rating: 0,
themeColor: '',
colorPicker: '#1c7ed6',
avatar: null
avatar: null,
objectSelect: null,
asyncSelect: null,
multiObjectSelect: [],
multiAsyncSelect: [],
realPokeSelect: null,
multiRealPokeSelect: []
}
});
@@ -103,6 +132,95 @@ export default function AllFieldsDemo() {
data={['Electronics', 'Fashion', 'Food']}
/>
</Group>
<Group grow align="flex-start" mb="md">
<FieldObjectSelect
name="objectSelect"
control={control}
label="Object Select"
placeholder="Select a complex object"
data={[
{ id: 1, name: 'Apple', type: 'Fruit' },
{ id: 2, name: 'Carrot', type: 'Vegetable' },
{ id: 3, name: 'Banana', type: 'Fruit' }
]}
valueKey="id"
labelKey="name"
clearable
/>
<FieldObjectSelect
multiple
name="multiObjectSelect"
control={control}
label="Multi Object Select"
placeholder="Select multiple objects"
data={[
{ id: 1, name: 'Red', hex: '#f00' },
{ id: 2, name: 'Green', hex: '#0f0' },
{ id: 3, name: 'Blue', hex: '#00f' }
]}
valueKey="id"
renderLabel={(item) => `${item.name} (${item.hex})`}
clearable
/>
</Group>
<Group grow align="flex-start" mb="md">
<FieldAsyncSelect
name="asyncSelect"
control={control}
label="Async Select (Mock API)"
placeholder="Search pokemon..."
apiEndpoint="/api/mock/pokemon"
fetchFn={fetchMockPokemon}
transformResponse={(res) => res.results}
valueKey="id"
labelKey="name"
clearable
/>
<FieldAsyncSelect
multiple
name="multiAsyncSelect"
control={control}
label="Multi Async Select"
placeholder="Select multiple pokemon..."
apiEndpoint="/api/mock/pokemon"
fetchFn={fetchMockPokemon}
transformResponse={(res) => res.results}
valueKey="id"
labelKey="name"
clearable
/>
</Group>
<Group grow align="flex-start" mb="md">
<FieldAsyncSelect
name="realPokeSelect"
control={control}
label="Real PokeAPI (Single - Tests Deduplication)"
placeholder="Scroll to test deduplication..."
apiEndpoint="https://pokeapi.co/api/v2/pokemon"
transformResponse={(res) => ({
data: res.results.map((p: any, i: number) => ({ id: i + 1, ...p })),
hasMore: !!res.next
})}
valueKey="id"
labelKey="name"
clearable
/>
<FieldAsyncSelect
multiple
name="multiRealPokeSelect"
control={control}
label="Real PokeAPI (Multi - Tests Deduplication)"
placeholder="Scroll to test deduplication..."
apiEndpoint="https://pokeapi.co/api/v2/pokemon"
transformResponse={(res) => ({
data: res.results.map((p: any, i: number) => ({ id: i + 1, ...p })),
hasMore: !!res.next
})}
valueKey="id"
labelKey="name"
clearable
/>
</Group>
<FieldTagsInput name="tags" control={control} label={t.fields.tags} />
</div>