Generated by Spark: Too risky making changes without refactoring now. Create hook library, All components <150LOC. Consider orchestrating pages using json. JSON can describe actions, hooks, component tree, seed data you name it.

This commit is contained in:
2026-01-16 19:04:10 +00:00
committed by GitHub
parent e29e8b7361
commit c5e486859f
33 changed files with 4612 additions and 42 deletions
+6 -5
View File
@@ -1,5 +1,6 @@
export { useFiles } from './use-files'
export { useModels } from './use-models'
export { useComponents } from './use-components'
export { useWorkflows } from './use-workflows'
export { useLambdas } from './use-lambdas'
export * from './use-array'
export * from './use-crud'
export * from './use-search'
export * from './use-debounce'
export * from './use-sort'
export * from './use-pagination'
+63
View File
@@ -0,0 +1,63 @@
import { useKV } from '@github/spark/hooks'
import { useCallback } from 'react'
export function useArray<T>(key: string, defaultValue: T[] = []) {
const [items, setItems] = useKV<T[]>(key, defaultValue)
const safeItems = items || []
const add = useCallback((item: T) => {
setItems((current) => [...(current || []), item])
}, [setItems])
const addMany = useCallback((newItems: T[]) => {
setItems((current) => [...(current || []), ...newItems])
}, [setItems])
const remove = useCallback((predicate: (item: T) => boolean) => {
setItems((current) => (current || []).filter((item) => !predicate(item)))
}, [setItems])
const update = useCallback(
(predicate: (item: T) => boolean, updater: (item: T) => T) => {
setItems((current) =>
(current || []).map((item) => (predicate(item) ? updater(item) : item))
)
},
[setItems]
)
const replace = useCallback((newItems: T[]) => {
setItems(newItems)
}, [setItems])
const clear = useCallback(() => {
setItems([])
}, [setItems])
const find = useCallback(
(predicate: (item: T) => boolean) => {
return safeItems.find(predicate)
},
[safeItems]
)
const filter = useCallback(
(predicate: (item: T) => boolean) => {
return safeItems.filter(predicate)
},
[safeItems]
)
return {
items: safeItems,
add,
addMany,
remove,
update,
replace,
clear,
find,
filter,
count: safeItems.length,
}
}
+75
View File
@@ -0,0 +1,75 @@
import { useState, useCallback } from 'react'
export interface Entity {
id: string
[key: string]: any
}
export function useCRUD<T extends Entity>(
items: T[],
setItems: (items: T[] | ((prev: T[]) => T[])) => void
) {
const [selectedId, setSelectedId] = useState<string | null>(null)
const create = useCallback(
(item: T) => {
setItems((current) => [...(current || []), item])
return item.id
},
[setItems]
)
const read = useCallback(
(id: string) => {
return items?.find((item) => item.id === id)
},
[items]
)
const update = useCallback(
(id: string, updates: Partial<T>) => {
setItems((current) =>
(current || []).map((item) =>
item.id === id ? { ...item, ...updates } : item
)
)
},
[setItems]
)
const remove = useCallback(
(id: string) => {
setItems((current) => (current || []).filter((item) => item.id !== id))
if (selectedId === id) {
setSelectedId(null)
}
},
[setItems, selectedId]
)
const duplicate = useCallback(
(id: string, newId: string) => {
const item = read(id)
if (!item) return null
const duplicated = { ...item, id: newId }
create(duplicated)
return newId
},
[read, create]
)
const selected = selectedId ? read(selectedId) : null
return {
items: items || [],
create,
read,
update,
remove,
duplicate,
selectedId,
setSelectedId,
selected,
}
}
+17
View File
@@ -0,0 +1,17 @@
import { useState, useEffect } from 'react'
export function useDebounce<T>(value: T, delay: number = 500): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value)
}, delay)
return () => {
clearTimeout(timer)
}
}, [value, delay])
return debouncedValue
}
+52
View File
@@ -0,0 +1,52 @@
import { useState, useCallback, useMemo } from 'react'
export interface PaginationConfig {
page: number
pageSize: number
total: number
}
export function usePagination<T>(items: T[], initialPageSize: number = 10) {
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(initialPageSize)
const total = items.length
const totalPages = Math.ceil(total / pageSize)
const paginatedItems = useMemo(() => {
const start = (page - 1) * pageSize
const end = start + pageSize
return items.slice(start, end)
}, [items, page, pageSize])
const goToPage = useCallback((newPage: number) => {
setPage(Math.max(1, Math.min(newPage, totalPages)))
}, [totalPages])
const nextPage = useCallback(() => {
goToPage(page + 1)
}, [page, goToPage])
const prevPage = useCallback(() => {
goToPage(page - 1)
}, [page, goToPage])
const changePageSize = useCallback((newSize: number) => {
setPageSize(newSize)
setPage(1)
}, [])
return {
items: paginatedItems,
page,
pageSize,
total,
totalPages,
goToPage,
nextPage,
prevPage,
changePageSize,
hasNext: page < totalPages,
hasPrev: page > 1,
}
}
+42
View File
@@ -0,0 +1,42 @@
import { useState, useEffect } from 'react'
import { useDebounce } from './use-debounce'
export function useSearch<T>(
items: T[],
searchKeys: (keyof T)[],
debounceMs: number = 300
) {
const [query, setQuery] = useState('')
const debouncedQuery = useDebounce(query, debounceMs)
const [results, setResults] = useState<T[]>(items)
useEffect(() => {
if (!debouncedQuery.trim()) {
setResults(items)
return
}
const lowerQuery = debouncedQuery.toLowerCase()
const filtered = items.filter((item) =>
searchKeys.some((key) => {
const value = item[key]
if (typeof value === 'string') {
return value.toLowerCase().includes(lowerQuery)
}
if (typeof value === 'number') {
return value.toString().includes(lowerQuery)
}
return false
})
)
setResults(filtered)
}, [debouncedQuery, items, searchKeys])
return {
query,
setQuery,
results,
isSearching: query.length > 0,
}
}
+49
View File
@@ -0,0 +1,49 @@
import { useState, useCallback } from 'react'
export type SortDirection = 'asc' | 'desc'
export function useSort<T>(items: T[], defaultKey?: keyof T) {
const [sortKey, setSortKey] = useState<keyof T | null>(defaultKey || null)
const [sortDirection, setSortDirection] = useState<SortDirection>('asc')
const toggleSort = useCallback(
(key: keyof T) => {
if (sortKey === key) {
setSortDirection((prev) => (prev === 'asc' ? 'desc' : 'asc'))
} else {
setSortKey(key)
setSortDirection('asc')
}
},
[sortKey]
)
const sortedItems = [...items].sort((a, b) => {
if (!sortKey) return 0
const aVal = a[sortKey]
const bVal = b[sortKey]
if (aVal === bVal) return 0
let comparison = 0
if (typeof aVal === 'string' && typeof bVal === 'string') {
comparison = aVal.localeCompare(bVal)
} else if (typeof aVal === 'number' && typeof bVal === 'number') {
comparison = aVal - bVal
} else {
comparison = String(aVal).localeCompare(String(bVal))
}
return sortDirection === 'asc' ? comparison : -comparison
})
return {
sortedItems,
sortKey,
sortDirection,
toggleSort,
setSortKey,
setSortDirection,
}
}