mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-05-07 03:49:34 +00:00
Generated by Spark: Load more of UI from JSON declarations and break up large components into atomic and create hooks as needed
This commit is contained in:
+11
-10
@@ -1,13 +1,14 @@
|
||||
export { useJSONData } from './use-json-data'
|
||||
export { useDataSources } from './use-data-sources'
|
||||
export { useKVDataSource, useComputedDataSource, useStaticDataSource, useMultipleDataSources } from './use-data-source'
|
||||
export { useCRUD } from './use-crud'
|
||||
export { useSearch } from './use-search'
|
||||
export { useSearchFilter } from './use-search-filter'
|
||||
export { useSort } from './use-sort'
|
||||
export { useFilter } from './use-filter'
|
||||
export { useLocalStorage } from './use-local-storage'
|
||||
export { usePagination } from './use-pagination'
|
||||
export type { UseJSONDataOptions } from './use-json-data'
|
||||
export type { UseCRUDOptions } from './use-crud'
|
||||
export type { UseSearchOptions } from './use-search'
|
||||
export type { UseSortOptions, SortDirection } from './use-sort'
|
||||
export type { FilterConfig, UseFilterOptions } from './use-filter'
|
||||
export { useSelection } from './use-selection'
|
||||
export { useSeedData } from './use-seed-data'
|
||||
|
||||
export type { DataSourceConfig, DataSourceType } from './use-data-source'
|
||||
export type { CRUDOperations, CRUDConfig } from './use-crud'
|
||||
export type { SearchFilterConfig } from './use-search-filter'
|
||||
export type { SortConfig, SortDirection } from './use-sort'
|
||||
export type { PaginationConfig } from './use-pagination'
|
||||
export type { SelectionConfig } from './use-selection'
|
||||
|
||||
+30
-34
@@ -1,55 +1,51 @@
|
||||
import { useState, useCallback } from 'react'
|
||||
import { useKV } from '@github/spark/hooks'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export interface UseCRUDOptions<T> {
|
||||
key: string
|
||||
defaultValue?: T[]
|
||||
persist?: boolean
|
||||
getId?: (item: T) => string | number
|
||||
export interface CRUDOperations<T> {
|
||||
create: (item: T) => void
|
||||
read: (id: string | number) => T | undefined
|
||||
update: (id: string | number, updates: Partial<T>) => void
|
||||
delete: (id: string | number) => void
|
||||
list: () => T[]
|
||||
}
|
||||
|
||||
export function useCRUD<T>(options: UseCRUDOptions<T>) {
|
||||
const { key, defaultValue = [], persist = true, getId = (item: any) => item.id } = options
|
||||
|
||||
const [persistedItems, setPersistedItems] = useKV<T[]>(key, defaultValue)
|
||||
const [localItems, setLocalItems] = useState<T[]>(defaultValue)
|
||||
|
||||
const items = persist ? persistedItems : localItems
|
||||
const setItems = persist ? setPersistedItems : setLocalItems
|
||||
export interface CRUDConfig<T> {
|
||||
items: T[]
|
||||
setItems: (updater: (items: T[]) => T[]) => void
|
||||
idField?: keyof T
|
||||
}
|
||||
|
||||
export function useCRUD<T extends Record<string, any>>({
|
||||
items,
|
||||
setItems,
|
||||
idField = 'id' as keyof T,
|
||||
}: CRUDConfig<T>): CRUDOperations<T> {
|
||||
const create = useCallback((item: T) => {
|
||||
setItems((current: T[]) => [...current, item])
|
||||
setItems(current => [...current, item])
|
||||
}, [setItems])
|
||||
|
||||
const read = useCallback((id: string | number): T | undefined => {
|
||||
return items.find(item => getId(item) === id)
|
||||
}, [items, getId])
|
||||
const read = useCallback((id: string | number) => {
|
||||
return items.find(item => item[idField] === id)
|
||||
}, [items, idField])
|
||||
|
||||
const update = useCallback((id: string | number, updates: Partial<T>) => {
|
||||
setItems((current: T[]) =>
|
||||
setItems(current =>
|
||||
current.map(item =>
|
||||
getId(item) === id ? { ...item, ...updates } : item
|
||||
item[idField] === id ? { ...item, ...updates } : item
|
||||
)
|
||||
)
|
||||
}, [setItems, getId])
|
||||
}, [setItems, idField])
|
||||
|
||||
const remove = useCallback((id: string | number) => {
|
||||
setItems((current: T[]) =>
|
||||
current.filter(item => getId(item) !== id)
|
||||
)
|
||||
}, [setItems, getId])
|
||||
const deleteItem = useCallback((id: string | number) => {
|
||||
setItems(current => current.filter(item => item[idField] !== id))
|
||||
}, [setItems, idField])
|
||||
|
||||
const clear = useCallback(() => {
|
||||
setItems([])
|
||||
}, [setItems])
|
||||
const list = useCallback(() => items, [items])
|
||||
|
||||
return {
|
||||
items,
|
||||
create,
|
||||
read,
|
||||
update,
|
||||
remove,
|
||||
clear,
|
||||
setItems,
|
||||
delete: deleteItem,
|
||||
list,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useKV } from '@github/spark/hooks'
|
||||
|
||||
export type DataSourceType = 'kv' | 'static' | 'computed'
|
||||
|
||||
export interface DataSourceConfig<T = any> {
|
||||
id: string
|
||||
type: DataSourceType
|
||||
key?: string
|
||||
defaultValue?: T
|
||||
compute?: (allData: Record<string, any>) => T
|
||||
dependencies?: string[]
|
||||
}
|
||||
|
||||
export function useKVDataSource<T>(key: string, defaultValue: T) {
|
||||
const [value, setValue, deleteValue] = useKV<T>(key, defaultValue)
|
||||
|
||||
return {
|
||||
data: value,
|
||||
setData: setValue,
|
||||
deleteData: deleteValue,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
}
|
||||
}
|
||||
|
||||
export function useComputedDataSource<T>(
|
||||
compute: (allData: Record<string, any>) => T,
|
||||
allData: Record<string, any>,
|
||||
dependencies: string[],
|
||||
defaultValue?: T
|
||||
) {
|
||||
const [computed, setComputed] = useState<T>(defaultValue as T)
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const newValue = compute(allData)
|
||||
setComputed(newValue)
|
||||
} catch (error) {
|
||||
console.error('Error computing data source:', error)
|
||||
}
|
||||
}, dependencies.map(dep => allData[dep]))
|
||||
|
||||
return {
|
||||
data: computed,
|
||||
setData: () => {},
|
||||
deleteData: () => {},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
}
|
||||
}
|
||||
|
||||
export function useStaticDataSource<T>(value: T) {
|
||||
return {
|
||||
data: value,
|
||||
setData: () => {},
|
||||
deleteData: () => {},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
}
|
||||
}
|
||||
|
||||
export function useMultipleDataSources(
|
||||
configs: DataSourceConfig[],
|
||||
onUpdate?: (data: Record<string, any>) => void
|
||||
) {
|
||||
const [allData, setAllData] = useState<Record<string, any>>({})
|
||||
|
||||
const updateData = useCallback((id: string, value: any) => {
|
||||
setAllData(prev => {
|
||||
const next = { ...prev, [id]: value }
|
||||
onUpdate?.(next)
|
||||
return next
|
||||
})
|
||||
}, [onUpdate])
|
||||
|
||||
return {
|
||||
allData,
|
||||
updateData,
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,55 @@
|
||||
import { useState, useCallback, useMemo } from 'react'
|
||||
import { useState, useMemo, useCallback } from 'react'
|
||||
|
||||
export interface PaginationConfig {
|
||||
page: number
|
||||
pageSize: number
|
||||
total: number
|
||||
items: any[]
|
||||
pageSize?: number
|
||||
initialPage?: number
|
||||
}
|
||||
|
||||
export function usePagination<T>(items: T[], initialPageSize: number = 10) {
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(initialPageSize)
|
||||
export function usePagination<T>({
|
||||
items,
|
||||
pageSize = 10,
|
||||
initialPage = 1,
|
||||
}: PaginationConfig) {
|
||||
const [currentPage, setCurrentPage] = useState(initialPage)
|
||||
|
||||
const total = items.length
|
||||
const totalPages = Math.ceil(total / pageSize)
|
||||
const totalPages = Math.ceil(items.length / pageSize)
|
||||
|
||||
const paginatedItems = useMemo(() => {
|
||||
const start = (page - 1) * pageSize
|
||||
const start = (currentPage - 1) * pageSize
|
||||
const end = start + pageSize
|
||||
return items.slice(start, end)
|
||||
}, [items, page, pageSize])
|
||||
}, [items, currentPage, pageSize])
|
||||
|
||||
const goToPage = useCallback((newPage: number) => {
|
||||
setPage(Math.max(1, Math.min(newPage, totalPages)))
|
||||
const goToPage = useCallback((page: number) => {
|
||||
setCurrentPage(Math.max(1, Math.min(page, totalPages)))
|
||||
}, [totalPages])
|
||||
|
||||
const nextPage = useCallback(() => {
|
||||
goToPage(page + 1)
|
||||
}, [page, goToPage])
|
||||
goToPage(currentPage + 1)
|
||||
}, [currentPage, goToPage])
|
||||
|
||||
const prevPage = useCallback(() => {
|
||||
goToPage(page - 1)
|
||||
}, [page, goToPage])
|
||||
goToPage(currentPage - 1)
|
||||
}, [currentPage, goToPage])
|
||||
|
||||
const changePageSize = useCallback((newSize: number) => {
|
||||
setPageSize(newSize)
|
||||
setPage(1)
|
||||
}, [])
|
||||
const reset = useCallback(() => {
|
||||
setCurrentPage(initialPage)
|
||||
}, [initialPage])
|
||||
|
||||
return {
|
||||
items: paginatedItems,
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
currentPage,
|
||||
totalPages,
|
||||
pageSize,
|
||||
goToPage,
|
||||
nextPage,
|
||||
prevPage,
|
||||
changePageSize,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1,
|
||||
reset,
|
||||
hasNext: currentPage < totalPages,
|
||||
hasPrev: currentPage > 1,
|
||||
startIndex: (currentPage - 1) * pageSize + 1,
|
||||
endIndex: Math.min(currentPage * pageSize, items.length),
|
||||
totalItems: items.length,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useState, useMemo, useCallback } from 'react'
|
||||
|
||||
export interface SearchFilterConfig<T> {
|
||||
items: T[]
|
||||
searchFields?: (keyof T)[]
|
||||
filterFn?: (item: T, filters: Record<string, any>) => boolean
|
||||
}
|
||||
|
||||
export function useSearchFilter<T extends Record<string, any>>({
|
||||
items,
|
||||
searchFields = [],
|
||||
filterFn,
|
||||
}: SearchFilterConfig<T>) {
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [filters, setFilters] = useState<Record<string, any>>({})
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
let result = items
|
||||
|
||||
if (searchQuery && searchFields.length > 0) {
|
||||
const query = searchQuery.toLowerCase()
|
||||
result = result.filter(item =>
|
||||
searchFields.some(field => {
|
||||
const value = item[field]
|
||||
return String(value).toLowerCase().includes(query)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (filterFn && Object.keys(filters).length > 0) {
|
||||
result = result.filter(item => filterFn(item, filters))
|
||||
}
|
||||
|
||||
return result
|
||||
}, [items, searchQuery, searchFields, filters, filterFn])
|
||||
|
||||
const setFilter = useCallback((key: string, value: any) => {
|
||||
setFilters(prev => ({ ...prev, [key]: value }))
|
||||
}, [])
|
||||
|
||||
const clearFilters = useCallback(() => {
|
||||
setSearchQuery('')
|
||||
setFilters({})
|
||||
}, [])
|
||||
|
||||
return {
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
filters,
|
||||
setFilter,
|
||||
clearFilters,
|
||||
filtered,
|
||||
count: filtered.length,
|
||||
total: items.length,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { useState, useCallback } from 'react'
|
||||
|
||||
export interface SelectionConfig<T> {
|
||||
items: T[]
|
||||
multiple?: boolean
|
||||
idField?: keyof T
|
||||
}
|
||||
|
||||
export function useSelection<T extends Record<string, any>>({
|
||||
items,
|
||||
multiple = false,
|
||||
idField = 'id' as keyof T,
|
||||
}: SelectionConfig<T>) {
|
||||
const [selected, setSelected] = useState<Set<string | number>>(new Set())
|
||||
|
||||
const toggle = useCallback((id: string | number) => {
|
||||
setSelected(prev => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(id)) {
|
||||
next.delete(id)
|
||||
} else {
|
||||
if (!multiple) {
|
||||
next.clear()
|
||||
}
|
||||
next.add(id)
|
||||
}
|
||||
return next
|
||||
})
|
||||
}, [multiple])
|
||||
|
||||
const select = useCallback((id: string | number) => {
|
||||
setSelected(prev => {
|
||||
const next: Set<string | number> = multiple ? new Set(prev) : new Set<string | number>()
|
||||
next.add(id)
|
||||
return next
|
||||
})
|
||||
}, [multiple])
|
||||
|
||||
const deselect = useCallback((id: string | number) => {
|
||||
setSelected(prev => {
|
||||
const next = new Set(prev)
|
||||
next.delete(id)
|
||||
return next
|
||||
})
|
||||
}, [])
|
||||
|
||||
const selectAll = useCallback(() => {
|
||||
if (multiple) {
|
||||
setSelected(new Set(items.map(item => item[idField])))
|
||||
}
|
||||
}, [items, idField, multiple])
|
||||
|
||||
const deselectAll = useCallback(() => {
|
||||
setSelected(new Set())
|
||||
}, [])
|
||||
|
||||
const isSelected = useCallback((id: string | number) => {
|
||||
return selected.has(id)
|
||||
}, [selected])
|
||||
|
||||
const getSelected = useCallback(() => {
|
||||
return items.filter(item => selected.has(item[idField]))
|
||||
}, [items, selected, idField])
|
||||
|
||||
return {
|
||||
selected,
|
||||
toggle,
|
||||
select,
|
||||
deselect,
|
||||
selectAll,
|
||||
deselectAll,
|
||||
isSelected,
|
||||
getSelected,
|
||||
count: selected.size,
|
||||
hasSelection: selected.size > 0,
|
||||
}
|
||||
}
|
||||
+32
-39
@@ -1,61 +1,54 @@
|
||||
import { useState, useMemo } from 'react'
|
||||
import { useState, useMemo, useCallback } from 'react'
|
||||
|
||||
export type SortDirection = 'asc' | 'desc' | null
|
||||
export type SortDirection = 'asc' | 'desc'
|
||||
|
||||
export interface UseSortOptions<T> {
|
||||
export interface SortConfig<T> {
|
||||
items: T[]
|
||||
initialField?: keyof T
|
||||
initialDirection?: SortDirection
|
||||
defaultField?: keyof T
|
||||
defaultDirection?: SortDirection
|
||||
}
|
||||
|
||||
export function useSort<T>(options: UseSortOptions<T>) {
|
||||
const { items, initialField, initialDirection = 'asc' } = options
|
||||
const [field, setField] = useState<keyof T | null>(initialField || null)
|
||||
const [direction, setDirection] = useState<SortDirection>(initialDirection)
|
||||
export function useSort<T extends Record<string, any>>({
|
||||
items,
|
||||
defaultField,
|
||||
defaultDirection = 'asc',
|
||||
}: SortConfig<T>) {
|
||||
const [sortField, setSortField] = useState<keyof T | undefined>(defaultField)
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>(defaultDirection)
|
||||
|
||||
const sorted = useMemo(() => {
|
||||
if (!field || !direction) return items
|
||||
if (!sortField) return items
|
||||
|
||||
return [...items].sort((a, b) => {
|
||||
const aVal = a[field]
|
||||
const bVal = b[field]
|
||||
const aVal = a[sortField]
|
||||
const bVal = b[sortField]
|
||||
|
||||
if (aVal == null && bVal == null) return 0
|
||||
if (aVal == null) return 1
|
||||
if (bVal == null) return -1
|
||||
if (aVal === bVal) return 0
|
||||
|
||||
if (aVal < bVal) return direction === 'asc' ? -1 : 1
|
||||
if (aVal > bVal) return direction === 'asc' ? 1 : -1
|
||||
return 0
|
||||
const comparison = aVal < bVal ? -1 : 1
|
||||
return sortDirection === 'asc' ? comparison : -comparison
|
||||
})
|
||||
}, [items, field, direction])
|
||||
}, [items, sortField, sortDirection])
|
||||
|
||||
const toggleSort = (newField: keyof T) => {
|
||||
if (field === newField) {
|
||||
setDirection(prev =>
|
||||
prev === 'asc' ? 'desc' : prev === 'desc' ? null : 'asc'
|
||||
)
|
||||
if (direction === 'desc') {
|
||||
setField(null)
|
||||
}
|
||||
const toggleSort = useCallback((field: keyof T) => {
|
||||
if (sortField === field) {
|
||||
setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc')
|
||||
} else {
|
||||
setField(newField)
|
||||
setDirection('asc')
|
||||
setSortField(field)
|
||||
setSortDirection('asc')
|
||||
}
|
||||
}
|
||||
}, [sortField])
|
||||
|
||||
const reset = () => {
|
||||
setField(null)
|
||||
setDirection(null)
|
||||
}
|
||||
const resetSort = useCallback(() => {
|
||||
setSortField(defaultField)
|
||||
setSortDirection(defaultDirection)
|
||||
}, [defaultField, defaultDirection])
|
||||
|
||||
return {
|
||||
sorted,
|
||||
field,
|
||||
direction,
|
||||
sortField,
|
||||
sortDirection,
|
||||
toggleSort,
|
||||
setField,
|
||||
setDirection,
|
||||
reset,
|
||||
resetSort,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user