Skip to content

Commit 0bc92f6

Browse files
refactor: Simplify tag handling in GlobalTaskForm and remove unused components
1 parent 0bd727d commit 0bc92f6

File tree

3 files changed

+68
-271
lines changed

3 files changed

+68
-271
lines changed

src/App.test.jsx

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/components/AddTask.jsx

Lines changed: 0 additions & 156 deletions
This file was deleted.

src/components/GlobalTaskForm.jsx

Lines changed: 68 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ function GlobalTaskForm({ onAdd, onCancel, availableTags = [] }) {
66
const [text, setText] = useState('');
77
const [selectedTags, setSelectedTags] = useState([]);
88
const [newTagInput, setNewTagInput] = useState('');
9-
const [showTagSelector, setShowTagSelector] = useState(false);
109
const inputRef = useRef(null);
11-
const tagInputRef = useRef(null);
12-
const dropdownRef = useRef(null);
1310

1411
useEffect(() => {
1512
// Auto-focus input when component mounts
@@ -22,59 +19,48 @@ function GlobalTaskForm({ onAdd, onCancel, availableTags = [] }) {
2219
e.preventDefault();
2320
if (!text.trim()) return;
2421

25-
onAdd({ text, isCompleted: false, tags: selectedTags });
22+
// Add current tag input if it exists and not already added
23+
const finalTagInput = newTagInput.trim();
24+
if (finalTagInput && !selectedTags.includes(finalTagInput)) {
25+
onAdd({ text, isCompleted: false, tags: [...selectedTags, finalTagInput] });
26+
} else {
27+
onAdd({ text, isCompleted: false, tags: selectedTags });
28+
}
29+
2630
setText('');
2731
setSelectedTags([]);
2832
setNewTagInput('');
2933
};
3034

31-
const toggleTag = (tag) => {
32-
if (selectedTags.includes(tag)) {
33-
setSelectedTags(selectedTags.filter(t => t !== tag));
34-
} else {
35-
setSelectedTags([...selectedTags, tag]);
36-
}
35+
const addTag = (tag) => {
36+
if (!tag.trim() || selectedTags.includes(tag.trim())) return;
37+
setSelectedTags([...selectedTags, tag.trim()]);
38+
setNewTagInput('');
3739
};
3840

3941
const removeTag = (tag) => {
4042
setSelectedTags(selectedTags.filter(t => t !== tag));
4143
};
4244

43-
const addNewTag = () => {
44-
const tagToAdd = newTagInput.trim();
45-
if (!tagToAdd) return;
46-
47-
if (!selectedTags.includes(tagToAdd)) {
48-
setSelectedTags([...selectedTags, tagToAdd]);
45+
// Handle input key events (add tag on Enter or comma)
46+
const handleTagKeyDown = (e) => {
47+
if ((e.key === 'Enter' || e.key === ',') && newTagInput.trim()) {
48+
e.preventDefault();
49+
addTag(newTagInput.trim());
4950
}
50-
51-
setNewTagInput('');
5251
};
5352

54-
// Filter available tags based on search input
55-
const getFilteredAvailableTags = () => {
56-
const searchLower = newTagInput.toLowerCase();
53+
// Get matching tags based on the current input
54+
const getMatchingTags = () => {
55+
if (!newTagInput.trim()) return [];
56+
57+
const inputLower = newTagInput.toLowerCase();
5758
return availableTags.filter(tag =>
5859
!selectedTags.includes(tag) &&
59-
tag.toLowerCase().includes(searchLower)
60+
tag.toLowerCase().includes(inputLower)
6061
);
6162
};
6263

63-
const handleClickOutside = (e) => {
64-
if (dropdownRef.current && !dropdownRef.current.contains(e.target) &&
65-
tagInputRef.current && !tagInputRef.current.contains(e.target)) {
66-
setShowTagSelector(false);
67-
}
68-
};
69-
70-
// Add event listener for clicking outside
71-
useEffect(() => {
72-
document.addEventListener('mousedown', handleClickOutside);
73-
return () => {
74-
document.removeEventListener('mousedown', handleClickOutside);
75-
};
76-
}, []);
77-
7864
return (
7965
<form className="global-task-form mb-6" onSubmit={onSubmit}>
8066
<div className="relative mb-4">
@@ -111,74 +97,50 @@ function GlobalTaskForm({ onAdd, onCancel, availableTags = [] }) {
11197
</div>
11298
</div>
11399

114-
{/* Tag selector */}
115-
<div className="relative mb-3">
116-
<div className="absolute left-3 top-1/2 -translate-y-1/2">
117-
<TagIcon className="h-4 w-4 text-neutral-500" />
100+
{/* Tag input field */}
101+
<div className="mb-3">
102+
<div className="relative">
103+
<div className="absolute left-3 top-1/2 -translate-y-1/2">
104+
<TagIcon className="h-4 w-4 text-neutral-500" />
105+
</div>
106+
<input
107+
type="text"
108+
placeholder="Enter a new tag (press Enter or comma to add)"
109+
value={newTagInput}
110+
onChange={(e) => setNewTagInput(e.target.value)}
111+
onKeyDown={handleTagKeyDown}
112+
className="w-full py-3 px-4 pl-9 text-sm text-neutral-800 rounded-lg border border-neutral-200 focus:border-primary-400 focus:ring-1 focus:ring-primary-200 outline-none transition-all"
113+
autoComplete="off"
114+
/>
115+
116+
{/* Add button */}
117+
{newTagInput.trim() && !selectedTags.includes(newTagInput.trim()) && (
118+
<button
119+
type="button"
120+
onClick={() => addTag(newTagInput.trim())}
121+
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 bg-primary-500 hover:bg-primary-600 text-white rounded-full"
122+
>
123+
<PlusIcon className="h-4 w-4" />
124+
</button>
125+
)}
118126
</div>
119-
<input
120-
ref={tagInputRef}
121-
type="text"
122-
placeholder={selectedTags.length > 0 ? "Add more tags..." : "Add tags (e.g. work, urgent)"}
123-
value={newTagInput}
124-
onChange={(e) => setNewTagInput(e.target.value)}
125-
onFocus={() => setShowTagSelector(true)}
126-
className="w-full py-3 px-4 pl-9 text-sm text-neutral-800 rounded-lg border border-neutral-200 focus:border-primary-400 focus:ring-1 focus:ring-primary-200 outline-none transition-all"
127-
autoComplete="off"
128-
/>
129127

130-
{/* Tag selector dropdown */}
131-
{showTagSelector && (
132-
<div
133-
ref={dropdownRef}
134-
className="absolute z-50 left-0 right-0 mt-2 bg-white rounded-lg shadow-xl border border-neutral-200 max-h-64 overflow-y-auto"
135-
>
136-
<div className="py-1">
137-
<div className="sticky top-0 px-4 py-2.5 bg-neutral-50 border-b border-neutral-200">
138-
<span className="text-sm font-medium text-neutral-700">Select Tags</span>
139-
</div>
140-
141-
{/* Add new tag option */}
142-
{newTagInput.trim() && !availableTags.includes(newTagInput.trim()) && !selectedTags.includes(newTagInput.trim()) && (
143-
<div
144-
className="flex items-center justify-between px-4 py-3 text-sm hover:bg-green-50 cursor-pointer border-b border-neutral-100"
145-
onClick={addNewTag}
128+
{/* Matching tag suggestions - only shown when input matches existing tags */}
129+
{getMatchingTags().length > 0 && (
130+
<div className="mt-2">
131+
<p className="text-xs font-medium text-neutral-500 mb-1.5">Select matching tag:</p>
132+
<div className="flex flex-wrap gap-2">
133+
{getMatchingTags().map((tag, index) => (
134+
<button
135+
key={index}
136+
type="button"
137+
onClick={() => addTag(tag)}
138+
className="inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-primary-50 text-primary-700 hover:bg-primary-100 transition-colors"
146139
>
147-
<div className="flex items-center">
148-
<PlusIcon className="h-4 w-4 mr-2 text-green-600" />
149-
<span className="font-medium text-green-700">Create tag: "{newTagInput.trim()}"</span>
150-
</div>
151-
</div>
152-
)}
153-
154-
{/* Existing tags list */}
155-
{getFilteredAvailableTags().length > 0 ? (
156-
<div className="py-1">
157-
{getFilteredAvailableTags().map((tag, index) => (
158-
<div
159-
key={index}
160-
className="flex items-center justify-between px-4 py-3 text-sm hover:bg-primary-50 cursor-pointer transition-colors"
161-
onClick={() => toggleTag(tag)}
162-
>
163-
<div className="flex items-center">
164-
<TagIcon className="h-4 w-4 mr-2.5 text-primary-600" />
165-
<span className="font-medium text-neutral-800">{tag}</span>
166-
</div>
167-
<span className="text-xs px-2 py-1 bg-primary-50 rounded text-primary-700">Click to add</span>
168-
</div>
169-
))}
170-
</div>
171-
) : newTagInput && (
172-
<div className="px-4 py-4 text-sm text-neutral-500 text-center">
173-
No matching tags
174-
</div>
175-
)}
176-
177-
{!newTagInput && availableTags.length === 0 && (
178-
<div className="px-4 py-4 text-sm text-neutral-500 text-center">
179-
No tags available. Type to create a new tag.
180-
</div>
181-
)}
140+
<TagIcon className="h-3 w-3 mr-1" />
141+
{tag}
142+
</button>
143+
))}
182144
</div>
183145
</div>
184146
)}
@@ -187,18 +149,18 @@ function GlobalTaskForm({ onAdd, onCancel, availableTags = [] }) {
187149
{/* Selected tags display */}
188150
{selectedTags.length > 0 && (
189151
<div className="mb-1">
190-
<p className="text-xs text-neutral-500 mb-2">Selected tags:</p>
191-
<div className="flex flex-wrap gap-2">
152+
<p className="text-xs font-medium text-neutral-500 mb-2">Selected tags:</p>
153+
<div className="flex flex-wrap gap-2 p-2 bg-neutral-50 rounded-lg">
192154
{selectedTags.map((tag, index) => (
193155
<div
194156
key={index}
195-
className="inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-primary-100 text-primary-700 group"
157+
className="inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-primary-500 text-white group"
196158
>
197159
<TagIcon className="h-3 w-3 mr-1" />
198160
<span>{tag}</span>
199161
<button
200162
type="button"
201-
className="ml-1 p-0.5 rounded-full hover:bg-primary-200 group-hover:text-primary-800"
163+
className="ml-1 p-0.5 rounded-full hover:bg-primary-600 group-hover:text-white"
202164
onClick={(e) => {
203165
e.preventDefault();
204166
removeTag(tag);

0 commit comments

Comments
 (0)