feat: add reusable AsyncSelect and ObjectSelect components with infinite scroll hook support
This commit is contained in:
+120
-2
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user