mirror of
https://github.com/coleam00/Archon.git
synced 2026-01-02 04:39:29 -05:00
The New Archon (Beta) - The Operating System for AI Coding Assistants!
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
import React, { memo, useState, useEffect, useCallback, useRef } from 'react';
|
||||
|
||||
interface DebouncedInputProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
type?: 'text' | 'textarea';
|
||||
rows?: number;
|
||||
}
|
||||
|
||||
// Memoized input component that manages its own state
|
||||
export const DebouncedInput = memo(({
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
className,
|
||||
type = 'text',
|
||||
rows = 5
|
||||
}: DebouncedInputProps) => {
|
||||
const [localValue, setLocalValue] = useState(value);
|
||||
const timeoutRef = useRef<NodeJS.Timeout>();
|
||||
const isFirstRender = useRef(true);
|
||||
|
||||
// Sync with external value only on mount or when externally changed
|
||||
useEffect(() => {
|
||||
if (isFirstRender.current) {
|
||||
isFirstRender.current = false;
|
||||
return;
|
||||
}
|
||||
// Only update if the external value is different from local
|
||||
if (value !== localValue) {
|
||||
setLocalValue(value);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalValue(newValue);
|
||||
|
||||
// Clear existing timeout
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
// Set new timeout for debounced update
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
onChange(newValue);
|
||||
}, 300);
|
||||
}, [onChange]);
|
||||
|
||||
// Cleanup timeout on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (type === 'textarea') {
|
||||
return (
|
||||
<textarea
|
||||
value={localValue}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
className={className}
|
||||
rows={rows}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
value={localValue}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
}, (prevProps, nextProps) => {
|
||||
// Custom comparison - only re-render if external value or onChange changes
|
||||
return prevProps.value === nextProps.value &&
|
||||
prevProps.onChange === nextProps.onChange &&
|
||||
prevProps.placeholder === nextProps.placeholder &&
|
||||
prevProps.className === nextProps.className;
|
||||
});
|
||||
|
||||
DebouncedInput.displayName = 'DebouncedInput';
|
||||
|
||||
interface FeatureInputProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
projectFeatures: any[];
|
||||
isLoadingFeatures: boolean;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// Memoized feature input with datalist
|
||||
export const FeatureInput = memo(({
|
||||
value,
|
||||
onChange,
|
||||
projectFeatures,
|
||||
isLoadingFeatures,
|
||||
placeholder,
|
||||
className
|
||||
}: FeatureInputProps) => {
|
||||
const [localValue, setLocalValue] = useState(value);
|
||||
const timeoutRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
// Sync with external value
|
||||
useEffect(() => {
|
||||
if (value !== localValue) {
|
||||
setLocalValue(value);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalValue(newValue);
|
||||
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
onChange(newValue);
|
||||
}, 300);
|
||||
}, [onChange]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
value={localValue}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
className={className}
|
||||
list="features-list"
|
||||
/>
|
||||
<datalist id="features-list">
|
||||
{projectFeatures.map((feature) => (
|
||||
<option key={feature.id} value={feature.label}>
|
||||
{feature.label} ({feature.type})
|
||||
</option>
|
||||
))}
|
||||
</datalist>
|
||||
{isLoadingFeatures && (
|
||||
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
|
||||
<div className="w-4 h-4 animate-spin rounded-full border-2 border-cyan-400 border-t-transparent" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, (prevProps, nextProps) => {
|
||||
return prevProps.value === nextProps.value &&
|
||||
prevProps.onChange === nextProps.onChange &&
|
||||
prevProps.isLoadingFeatures === nextProps.isLoadingFeatures &&
|
||||
prevProps.projectFeatures === nextProps.projectFeatures;
|
||||
});
|
||||
|
||||
FeatureInput.displayName = 'FeatureInput';
|
||||
Reference in New Issue
Block a user