@@ -5,6 +5,7 @@ import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from "vue"
55import { defineModel } from " vue" ;
66import axios from " axios" ;
77import { Icon } from " @iconify/vue" ;
8+ import Tag from " @/Components/Tag.vue" ;
89
910const DEBOUNCE_TIME = 450 ; // milliseconds
1011
@@ -13,6 +14,15 @@ const props = defineProps({
1314 type: String ,
1415 required: true ,
1516 },
17+ mode: {
18+ type: String ,
19+ default: ' create' , // 'create' or 'search'
20+ validator : (value ) => [' create' , ' search' ].includes (value),
21+ },
22+ allowEverything: {
23+ type: Boolean ,
24+ default: true ,
25+ },
1626});
1727
1828const model = defineModel ();
@@ -90,6 +100,9 @@ const availableTags = computed(() => {
90100});
91101
92102const canCreateNew = computed (() => {
103+ // In search mode, cannot create new tags
104+ if (props .mode === ' search' ) return false ;
105+
93106 const query = searchQuery .value .trim ();
94107 if (! query || isLoading .value ) return false ;
95108
@@ -102,6 +115,20 @@ const canCreateNew = computed(() => {
102115 );
103116});
104117
118+ const tooltipText = computed (() => {
119+ const parts = [];
120+
121+ if (props .mode === ' create' ) {
122+ parts .push (" Add multiple tags using commas" );
123+ }
124+
125+ if (props .allowEverything ) {
126+ parts .push (" 'everything' tag covers all possible tags" );
127+ }
128+
129+ return parts .join (' . ' );
130+ });
131+
105132// sync model
106133watch (
107134 () => model .value ,
@@ -176,7 +203,8 @@ function onKeydown(event) {
176203 canCreateNew .value
177204 ) {
178205 selectTag (searchQuery .value .trim ());
179- } else if (searchQuery .value .trim ()) {
206+ } else if (props .mode === ' create' && searchQuery .value .trim ()) {
207+ // Only allow adding tags in create mode
180208 // Handle comma-separated tags
181209 if (searchQuery .value .includes (' ,' )) {
182210 addMultipleTags (searchQuery .value );
@@ -258,20 +286,15 @@ onMounted(async () => {
258286 < div class = " mb-2" >
259287 <!-- list of selected tags -->
260288 < div class = " mb-2 flex flex-wrap" v- if = " selectedTags.length > 0" >
261- < span
289+ < Tag
262290 v- for = " tag in selectedTags"
263291 : key= " tag"
264- class = " inline-flex items-center mr-2 my-1 bg-secondary text-primaryDark dark:bg-gray-700 dark:text-white px-3 py-1 rounded-full text-sm font-medium transition-colors"
265- >
266- < button
267- @click= " removeTag(tag)"
268- class = " mr-2 text-primaryDark dark:text-white"
269- type= " button"
270- >
271- < Icon : icon= " 'mdi:close'" / >
272- < / button>
273- < span> {{ tag }}< / span>
274- < / span>
292+ : tag= " tag"
293+ variant= " selected"
294+ removable
295+ @remove= " removeTag"
296+ class = " mr-2 my-1"
297+ / >
275298 < / div>
276299
277300 <!-- search input container -->
@@ -289,13 +312,14 @@ onMounted(async () => {
289312 : class = " {
290313 'rounded-b-none border-b-0':
291314 showDropdown &&
292- (availableTags.length > 0 || canCreateNew || isLoading),
315+ (availableTags.length > 0 || canCreateNew || isLoading || searchQuery.trim() ),
293316 }"
294317 / >
295318 < Icon
319+ v- if = " tooltipText"
296320 icon= " mdi:information-outline"
297321 class = " absolute right-3 size-5 text-gray-400 dark:text-gray-500 cursor-help"
298- v- tooltip .top = " 'You can add multiple tags at once by separating them with commas (e.g., tag1, tag2, tag3)' "
322+ v- tooltip .top = " tooltipText "
299323 / >
300324 < / div>
301325
@@ -304,7 +328,7 @@ onMounted(async () => {
304328 < div
305329 v- show= "
306330 showDropdown &&
307- (availableTags.length > 0 || canCreateNew || isLoading)
331+ (availableTags.length > 0 || canCreateNew || isLoading || searchQuery.trim() )
308332 "
309333 class = " z-[9999] bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-800 border-t-0 rounded-b-lg shadow-lg max-h-60 overflow-y-auto"
310334 : style= " dropdownStyles"
@@ -325,6 +349,20 @@ onMounted(async () => {
325349
326350 <!-- available tags -->
327351 < template v- else >
352+ <!-- No results message -->
353+ < div
354+ v- if = " availableTags.length === 0 && !canCreateNew"
355+ class = " px-4 py-3 text-sm text-gray-500 dark:text-gray-400 text-center"
356+ >
357+ < Icon icon= " mdi:information-outline" class = " inline-block w-4 h-4 mr-1" / >
358+ < span v- if = " searchQuery.trim()" >
359+ No tags found matching " {{ searchQuery.trim() }}"
360+ < / span>
361+ < span v- else - if = " props.mode === 'search'" >
362+ No tags available
363+ < / span>
364+ < / div>
365+
328366 < div
329367 v- for = " (tag, index) in availableTags"
330368 : key= " tag.name"
@@ -334,18 +372,15 @@ onMounted(async () => {
334372 : class = " {
335373 'bg-secondary text-primaryDark dark:bg-gray-800 dark:text-primaryLight': highlightedIndex === index,
336374 'hover:bg-gray-50 dark:hover:bg-gray-900': highlightedIndex !== index,
375+ 'bg-orange-50 dark:bg-orange-900/20 border-l-2 border-orange-400': tag.name === 'everything' && highlightedIndex !== index,
376+ 'bg-orange-100 dark:bg-orange-900/30 border-l-2 border-orange-500': tag.name === 'everything' && highlightedIndex === index,
337377 }"
338378 >
339- < span class = " text-sm" > {{ tag .name }}< / span>
340- < span
341- v- if = " tag.count"
342- class = " text-xs bg-gray-100 text-gray-600 dark:bg-gray-900 dark:text-gray-300 px-2 py-1 rounded-full"
343- : class = " {
344- 'bg-secondary text-primaryDark dark:bg-gray-800 dark:text-primaryLight': highlightedIndex === index,
345- }"
346- >
347- {{ tag .count }}
348- < / span>
379+ < Tag
380+ : tag= " tag.name"
381+ : variant= " highlightedIndex === index ? 'highlighted' : 'default'"
382+ : count= " tag.count || null"
383+ / >
349384 < / div>
350385
351386 <!-- create new tag option -->
0 commit comments