feat(Boxer): implement Boxer component with autocomplete and server-side support
This commit is contained in:
159
src/Boxer/Boxer.store.tsx
Normal file
159
src/Boxer/Boxer.store.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import { createSyncStore } from '@warkypublic/zustandsyncstore';
|
||||
import { produce } from 'immer';
|
||||
|
||||
import type { BoxerProps, BoxerStoreState } from './Boxer.types';
|
||||
|
||||
const { Provider: BoxerProvider, useStore: useBoxerStore } = createSyncStore<
|
||||
BoxerStoreState,
|
||||
BoxerProps
|
||||
>(
|
||||
(set, get) => ({
|
||||
boxerData: [],
|
||||
// Data Actions
|
||||
fetchData: async (search?: string, reset?: boolean) => {
|
||||
const state = get();
|
||||
|
||||
// Handle local data
|
||||
if (state.dataSource === 'local' || !state.onAPICall) {
|
||||
const localData = state.data ?? [];
|
||||
|
||||
if (!search) {
|
||||
set({ boxerData: localData, hasMore: false, total: localData.length });
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter local data based on search
|
||||
const filtered = localData.filter((item) =>
|
||||
item.label.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
set({ boxerData: filtered, hasMore: false, total: filtered.length });
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle server-side data
|
||||
if (state.onAPICall) {
|
||||
try {
|
||||
set({ isFetching: true });
|
||||
|
||||
const currentPage = reset ? 0 : state.page;
|
||||
|
||||
const result = await state.onAPICall({
|
||||
page: currentPage,
|
||||
pageSize: state.pageSize,
|
||||
search,
|
||||
});
|
||||
|
||||
set(
|
||||
produce((draft: BoxerStoreState) => {
|
||||
if (reset) {
|
||||
draft.boxerData = result.data;
|
||||
draft.page = 0;
|
||||
} else {
|
||||
draft.boxerData = [...(draft.boxerData ?? []), ...result.data];
|
||||
}
|
||||
draft.total = result.total;
|
||||
draft.hasMore = draft.boxerData.length < result.total;
|
||||
draft.isFetching = false;
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Boxer fetchData error:', error);
|
||||
set({ isFetching: false });
|
||||
}
|
||||
}
|
||||
},
|
||||
fetchMoreOnBottomReached: (target: HTMLDivElement) => {
|
||||
const state = get();
|
||||
|
||||
if (!state.hasMore || state.isFetching) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollPercentage =
|
||||
(target.scrollTop + target.clientHeight) / target.scrollHeight;
|
||||
|
||||
// Load more when scrolled past 80%
|
||||
if (scrollPercentage > 0.8) {
|
||||
state.loadMore();
|
||||
}
|
||||
},
|
||||
// State Management
|
||||
getState: (key) => {
|
||||
const current = get();
|
||||
return current?.[key];
|
||||
},
|
||||
hasMore: true,
|
||||
input: '',
|
||||
isFetching: false,
|
||||
loadMore: async () => {
|
||||
const state = get();
|
||||
|
||||
if (!state.hasMore || state.isFetching) {
|
||||
return;
|
||||
}
|
||||
|
||||
set(
|
||||
produce((draft: BoxerStoreState) => {
|
||||
draft.page = draft.page + 1;
|
||||
})
|
||||
);
|
||||
|
||||
await state.fetchData(state.search);
|
||||
},
|
||||
// Initial State
|
||||
opened: false,
|
||||
page: 0,
|
||||
|
||||
pageSize: 50,
|
||||
|
||||
search: '',
|
||||
|
||||
selectedOptionIndex: -1,
|
||||
|
||||
setInput: (input: string) => {
|
||||
set({ input });
|
||||
},
|
||||
|
||||
// Actions
|
||||
setOpened: (opened: boolean) => {
|
||||
set({ opened });
|
||||
},
|
||||
|
||||
setSearch: (search: string) => {
|
||||
set({ search });
|
||||
},
|
||||
|
||||
setSelectedOptionIndex: (index: number) => {
|
||||
set({ selectedOptionIndex: index });
|
||||
},
|
||||
|
||||
setState: (key, value) => {
|
||||
set(
|
||||
produce((state) => {
|
||||
state[key] = value;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
total: 0,
|
||||
}),
|
||||
({
|
||||
data = [],
|
||||
dataSource = 'local',
|
||||
pageSize = 50,
|
||||
...props
|
||||
}) => {
|
||||
return {
|
||||
...props,
|
||||
boxerData: data, // Initialize with local data if provided
|
||||
data,
|
||||
dataSource,
|
||||
hasMore: dataSource === 'server',
|
||||
pageSize,
|
||||
total: data.length,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export { BoxerProvider };
|
||||
export { useBoxerStore };
|
||||
Reference in New Issue
Block a user