feat: enhance TagInput component with popular tags loading
- Added `loadPopularTags` function to fetch and display popular tags when the input is empty. - Updated `TagInput` to show suggestions based on input focus and improved suggestion display with a grid layout. - Adjusted styles for better visual clarity and user experience.
This commit is contained in:
@@ -26,7 +26,7 @@ export function TagInput({
|
|||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const suggestionsRef = useRef<HTMLDivElement>(null);
|
const suggestionsRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { suggestions, loading, searchTags, clearSuggestions } = useTagsAutocomplete();
|
const { suggestions, loading, searchTags, clearSuggestions, loadPopularTags } = useTagsAutocomplete();
|
||||||
|
|
||||||
// Rechercher des suggestions quand l'input change
|
// Rechercher des suggestions quand l'input change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -93,6 +93,18 @@ export function TagInput({
|
|||||||
}, 150);
|
}, 150);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleFocus = () => {
|
||||||
|
if (inputValue.trim()) {
|
||||||
|
// Si il y a du texte, afficher les suggestions existantes
|
||||||
|
setShowSuggestions(true);
|
||||||
|
} else {
|
||||||
|
// Si l'input est vide, charger les tags populaires
|
||||||
|
loadPopularTags(20);
|
||||||
|
setShowSuggestions(true);
|
||||||
|
}
|
||||||
|
setSelectedIndex(-1);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`relative ${className}`}>
|
<div className={`relative ${className}`}>
|
||||||
{/* Container des tags et input */}
|
{/* Container des tags et input */}
|
||||||
@@ -126,7 +138,7 @@ export function TagInput({
|
|||||||
onChange={(e) => setInputValue(e.target.value)}
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
onFocus={() => inputValue && setShowSuggestions(true)}
|
onFocus={handleFocus}
|
||||||
placeholder={tags.length === 0 ? placeholder : ""}
|
placeholder={tags.length === 0 ? placeholder : ""}
|
||||||
className="flex-1 min-w-[120px] bg-transparent border-none outline-none text-slate-100 placeholder-slate-400 text-sm"
|
className="flex-1 min-w-[120px] bg-transparent border-none outline-none text-slate-100 placeholder-slate-400 text-sm"
|
||||||
/>
|
/>
|
||||||
@@ -138,37 +150,38 @@ export function TagInput({
|
|||||||
{showSuggestions && (suggestions.length > 0 || loading) && (
|
{showSuggestions && (suggestions.length > 0 || loading) && (
|
||||||
<div
|
<div
|
||||||
ref={suggestionsRef}
|
ref={suggestionsRef}
|
||||||
className="absolute top-full left-0 right-0 mt-1 bg-slate-800 border border-slate-600 rounded-lg shadow-lg z-50 max-h-48 overflow-y-auto"
|
className="absolute top-full left-0 right-0 mt-1 bg-slate-800 border border-slate-600 rounded-lg shadow-lg z-50 max-h-64 overflow-y-auto"
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="p-3 text-center text-slate-400 text-sm">
|
<div className="p-3 text-center text-slate-400 text-sm">
|
||||||
Recherche...
|
Recherche...
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
suggestions.map((tag, index) => (
|
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-2 p-3">
|
||||||
<button
|
{suggestions.map((tag, index) => (
|
||||||
key={tag.id}
|
<button
|
||||||
type="button"
|
key={tag.id}
|
||||||
onClick={() => handleSuggestionClick(tag)}
|
type="button"
|
||||||
className={`w-full text-left px-3 py-2 text-sm transition-colors ${
|
onClick={() => handleSuggestionClick(tag)}
|
||||||
index === selectedIndex
|
className={`flex items-center gap-2 px-2 py-1.5 text-xs rounded-md transition-colors ${
|
||||||
? 'bg-slate-700 text-cyan-300'
|
index === selectedIndex
|
||||||
: 'text-slate-200 hover:bg-slate-700'
|
? 'bg-slate-700 text-cyan-300 ring-1 ring-cyan-400'
|
||||||
} ${tags.includes(tag.name) ? 'opacity-50 cursor-not-allowed' : ''}`}
|
: 'text-slate-200 hover:bg-slate-700'
|
||||||
disabled={tags.includes(tag.name)}
|
} ${tags.includes(tag.name) ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||||
>
|
disabled={tags.includes(tag.name)}
|
||||||
<div className="flex items-center gap-2">
|
title={tags.includes(tag.name) ? 'Déjà ajouté' : `Ajouter ${tag.name}`}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="w-3 h-3 rounded-full"
|
className="w-2 h-2 rounded-full flex-shrink-0"
|
||||||
style={{ backgroundColor: tag.color }}
|
style={{ backgroundColor: tag.color }}
|
||||||
/>
|
/>
|
||||||
<span>{tag.name}</span>
|
<span className="truncate">{tag.name}</span>
|
||||||
{tags.includes(tag.name) && (
|
{tags.includes(tag.name) && (
|
||||||
<span className="text-xs text-slate-400 ml-auto">Déjà ajouté</span>
|
<span className="text-slate-400 ml-auto">✓</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</button>
|
||||||
</button>
|
))}
|
||||||
))
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -214,10 +214,24 @@ export function useTagsAutocomplete() {
|
|||||||
setSuggestions([]);
|
setSuggestions([]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const loadPopularTags = useCallback(async (limit: number = 20) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await tagsClient.getPopularTags(limit);
|
||||||
|
setSuggestions(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors du chargement des tags populaires:', error);
|
||||||
|
setSuggestions([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
suggestions,
|
suggestions,
|
||||||
loading,
|
loading,
|
||||||
searchTags,
|
searchTags,
|
||||||
clearSuggestions
|
clearSuggestions,
|
||||||
|
loadPopularTags
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user