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:
2026-01-17 12:07:39 +00:00
committed by GitHub
parent 9ca2f9ce44
commit cd4164deb8
10 changed files with 850 additions and 247 deletions
+55
View File
@@ -0,0 +1,55 @@
import { useState, useCallback } from 'react'
export interface ConfirmDialogOptions {
title: string
description: string
confirmText?: string
cancelText?: string
variant?: 'default' | 'destructive'
}
export interface ConfirmDialogState {
isOpen: boolean
options: ConfirmDialogOptions | null
resolve: ((value: boolean) => void) | null
}
export function useConfirmDialog() {
const [state, setState] = useState<ConfirmDialogState>({
isOpen: false,
options: null,
resolve: null,
})
const confirm = useCallback((options: ConfirmDialogOptions): Promise<boolean> => {
return new Promise((resolve) => {
setState({
isOpen: true,
options,
resolve,
})
})
}, [])
const handleConfirm = useCallback(() => {
if (state.resolve) {
state.resolve(true)
}
setState({ isOpen: false, options: null, resolve: null })
}, [state.resolve])
const handleCancel = useCallback(() => {
if (state.resolve) {
state.resolve(false)
}
setState({ isOpen: false, options: null, resolve: null })
}, [state.resolve])
return {
isOpen: state.isOpen,
options: state.options,
confirm,
handleConfirm,
handleCancel,
}
}
+131
View File
@@ -0,0 +1,131 @@
import { useState, useCallback } from 'react'
export interface FormFieldConfig<T = any> {
name: string
defaultValue: T
validate?: (value: T) => string | null
required?: boolean
}
export interface FormState<T extends Record<string, any>> {
values: T
errors: Partial<Record<keyof T, string>>
touched: Partial<Record<keyof T, boolean>>
isValid: boolean
isDirty: boolean
}
export function useFormState<T extends Record<string, any>>(
fields: FormFieldConfig[],
initialValues?: Partial<T>
) {
const defaultValues = fields.reduce((acc, field) => {
acc[field.name] = initialValues?.[field.name] ?? field.defaultValue
return acc
}, {} as T)
const [state, setState] = useState<FormState<T>>({
values: defaultValues,
errors: {},
touched: {},
isValid: true,
isDirty: false,
})
const validateField = useCallback(
(name: keyof T, value: any): string | null => {
const field = fields.find((f) => f.name === name)
if (!field) return null
if (field.required && !value) {
return 'This field is required'
}
if (field.validate) {
return field.validate(value)
}
return null
},
[fields]
)
const validateAll = useCallback((): boolean => {
const newErrors: Partial<Record<keyof T, string>> = {}
let isValid = true
fields.forEach((field) => {
const error = validateField(field.name as keyof T, state.values[field.name])
if (error) {
newErrors[field.name as keyof T] = error
isValid = false
}
})
setState((prev) => ({ ...prev, errors: newErrors, isValid }))
return isValid
}, [fields, state.values, validateField])
const setValue = useCallback(
(name: keyof T, value: any) => {
setState((prev) => {
const newValues = { ...prev.values, [name]: value }
const error = validateField(name, value)
const newErrors = { ...prev.errors }
if (error) {
newErrors[name] = error
} else {
delete newErrors[name]
}
return {
...prev,
values: newValues,
errors: newErrors,
isDirty: true,
isValid: Object.keys(newErrors).length === 0,
}
})
},
[validateField]
)
const setTouched = useCallback((name: keyof T) => {
setState((prev) => ({
...prev,
touched: { ...prev.touched, [name]: true },
}))
}, [])
const reset = useCallback(() => {
setState({
values: defaultValues,
errors: {},
touched: {},
isValid: true,
isDirty: false,
})
}, [defaultValues])
const setValues = useCallback((newValues: Partial<T>) => {
setState((prev) => ({
...prev,
values: { ...prev.values, ...newValues },
isDirty: true,
}))
}, [])
return {
values: state.values,
errors: state.errors,
touched: state.touched,
isValid: state.isValid,
isDirty: state.isDirty,
setValue,
setTouched,
setValues,
reset,
validateAll,
}
}
+155
View File
@@ -0,0 +1,155 @@
import { useState, useCallback } from 'react'
export interface ListOperationsOptions<T> {
initialItems?: T[]
getId?: (item: T) => string | number
onItemsChange?: (items: T[]) => void
}
export function useListOperations<T>({
initialItems = [],
getId = (item: any) => item.id,
onItemsChange,
}: ListOperationsOptions<T> = {}) {
const [items, setItemsState] = useState<T[]>(initialItems)
const [selectedIds, setSelectedIds] = useState<Set<string | number>>(new Set())
const setItems = useCallback(
(newItems: T[] | ((prev: T[]) => T[])) => {
setItemsState((prev) => {
const updated = typeof newItems === 'function' ? newItems(prev) : newItems
onItemsChange?.(updated)
return updated
})
},
[onItemsChange]
)
const addItem = useCallback(
(item: T, position?: number) => {
setItems((prev) => {
if (position !== undefined && position >= 0 && position <= prev.length) {
const newItems = [...prev]
newItems.splice(position, 0, item)
return newItems
}
return [...prev, item]
})
},
[setItems]
)
const updateItem = useCallback(
(id: string | number, updates: Partial<T> | ((item: T) => T)) => {
setItems((prev) =>
prev.map((item) => {
if (getId(item) === id) {
return typeof updates === 'function' ? updates(item) : { ...item, ...updates }
}
return item
})
)
},
[getId, setItems]
)
const removeItem = useCallback(
(id: string | number) => {
setItems((prev) => prev.filter((item) => getId(item) !== id))
setSelectedIds((prev) => {
const newSet = new Set(prev)
newSet.delete(id)
return newSet
})
},
[getId, setItems]
)
const removeItems = useCallback(
(ids: (string | number)[]) => {
const idSet = new Set(ids)
setItems((prev) => prev.filter((item) => !idSet.has(getId(item))))
setSelectedIds((prev) => {
const newSet = new Set(prev)
ids.forEach((id) => newSet.delete(id))
return newSet
})
},
[getId, setItems]
)
const moveItem = useCallback(
(fromIndex: number, toIndex: number) => {
setItems((prev) => {
if (
fromIndex < 0 ||
fromIndex >= prev.length ||
toIndex < 0 ||
toIndex >= prev.length
) {
return prev
}
const newItems = [...prev]
const [movedItem] = newItems.splice(fromIndex, 1)
newItems.splice(toIndex, 0, movedItem)
return newItems
})
},
[setItems]
)
const toggleSelection = useCallback((id: string | number) => {
setSelectedIds((prev) => {
const newSet = new Set(prev)
if (newSet.has(id)) {
newSet.delete(id)
} else {
newSet.add(id)
}
return newSet
})
}, [])
const selectAll = useCallback(() => {
setSelectedIds(new Set(items.map(getId)))
}, [items, getId])
const clearSelection = useCallback(() => {
setSelectedIds(new Set())
}, [])
const removeSelected = useCallback(() => {
removeItems(Array.from(selectedIds))
}, [selectedIds, removeItems])
const findById = useCallback(
(id: string | number) => {
return items.find((item) => getId(item) === id)
},
[items, getId]
)
const clear = useCallback(() => {
setItems([])
setSelectedIds(new Set())
}, [setItems])
return {
items,
selectedIds: Array.from(selectedIds),
selectedCount: selectedIds.size,
isEmpty: items.length === 0,
setItems,
addItem,
updateItem,
removeItem,
removeItems,
moveItem,
toggleSelection,
selectAll,
clearSelection,
removeSelected,
findById,
clear,
}
}