1 <div class="tag-card">
2 <div class="tag-container">
3 <div id="tag-component" class="tag-component">
4 <button id="add-button" class="add-button">
5 <svg
6 class="add-icon"
7 xmlns="http://www.w3.org/2000/svg"
8 viewBox="0 0 16 16"
9 width="16"
10 height="16"
11 >
12 <path
13 d="M7.75 2a.75.75 0 0 1 .75.75V7h4.25a.75.75 0 0 1 0 1.5H8.5v4.25a.75.75 0 0 1-1.5 0V8.5H2.75a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z"
14 ></path>
15 </svg>
16 </button>
17
18 <div id="input-state" class="input-state">
19 <input id="tag-input" type="text" placeholder="Tag Name" class="tag-input" />
20 <button id="confirm-button" class="confirm-button disabled">
21 <svg
22 class="confirm-icon"
23 xmlns="http://www.w3.org/2000/svg"
24 viewBox="0 0 16 16"
25 width="16"
26 height="16"
27 >
28 <path
29 d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
30 ></path>
31 </svg>
32 </button>
33 <button id="cancel-button" class="cancel-button">
34 <svg
35 class="cancel-icon"
36 xmlns="http://www.w3.org/2000/svg"
37 viewBox="0 0 16 16"
38 width="16"
39 height="16"
40 >
41 <path
42 d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"
43 ></path>
44 </svg>
45 </button>
46 </div>
47
48 <div id="tag-display" class="tag-display">
49 <span id="tag-text" class="tag-text"></span>
50 <button id="delete-button" class="delete-button">
51 <svg
52 class="delete-icon"
53 xmlns="http://www.w3.org/2000/svg"
54 viewBox="0 0 16 16"
55 width="16"
56 height="16"
57 >
58 <path
59 d="M11 1.75V3h2.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75ZM4.496 6.675l.66 6.6a.25.25 0 0 0 .249.225h5.19a.25.25 0 0 0 .249-.225l.66-6.6a.75.75 0 0 1 1.492.149l-.66 6.6A1.748 1.748 0 0 1 10.595 15h-5.19a1.75 1.75 0 0 1-1.741-1.575l-.66-6.6a.75.75 0 1 1 1.492-.15ZM6.5 1.75V3h3V1.75a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25Z"
60 ></path>
61 </svg>
62 </button>
63 </div>
64 </div>
65 </div>
66 </div>
67
68 <script>
69 let currentState = 'add' // 'add', 'input', 'display'
70 let tagValue = ''
71
72 const addButton = document.getElementById('add-button') as HTMLButtonElement
73 const inputState = document.getElementById('input-state') as HTMLDivElement
74 const tagDisplay = document.getElementById('tag-display') as HTMLDivElement
75 const tagInput = document.getElementById('tag-input') as HTMLInputElement
76 const confirmButton = document.getElementById('confirm-button') as HTMLButtonElement
77 const cancelButton = document.getElementById('cancel-button') as HTMLButtonElement
78 const deleteButton = document.getElementById('delete-button') as HTMLButtonElement
79 const tagText = document.getElementById('tag-text') as HTMLSpanElement
80
81 // Switch to input state
82 function switchToInput() {
83 currentState = 'input'
84 addButton?.classList.add('hidden')
85
86 setTimeout(() => {
87 inputState?.classList.add('active')
88 tagInput?.focus()
89 }, 100)
90 }
91
92 // Switch to display state
93 function switchToDisplay() {
94 currentState = 'display'
95
96 inputState?.classList.remove('active')
97
98 setTimeout(() => {
99 tagDisplay?.classList.add('active')
100 if (tagText) tagText.textContent = truncateText(tagValue)
101 }, 300)
102 }
103
104 // Return to add state
105 function switchToAdd() {
106 if (currentState === 'display') {
107 tagDisplay?.classList.remove('active')
108 } else if (currentState === 'input') {
109 inputState?.classList.remove('active')
110 }
111
112 currentState = 'add'
113
114 setTimeout(() => {
115 addButton?.classList.remove('hidden')
116 tagValue = ''
117 if (tagInput) tagInput.value = ''
118 updateConfirmButton()
119 }, 250)
120 }
121
122 // Update confirm button state
123 function updateConfirmButton() {
124 if (tagInput?.value.trim()) {
125 confirmButton?.classList.remove('disabled')
126 } else {
127 confirmButton?.classList.add('disabled')
128 }
129 }
130
131 // Truncate text to max 24 characters
132 function truncateText(text: string, maxLength: number = 24): string {
133 if (text.length <= maxLength) {
134 return text
135 }
136 return text.substring(0, maxLength) + '...'
137 }
138
139 addButton?.addEventListener('click', switchToInput)
140
141 tagInput?.addEventListener('input', (e) => {
142 const target = e.target as HTMLInputElement
143 const inputValue = target.value
144
145 // Limit input to 24 characters
146 if (inputValue.length > 24) {
147 target.value = inputValue.substring(0, 24)
148 tagValue = inputValue // Keep original value for display truncation
149 } else {
150 tagValue = inputValue
151 }
152
153 updateConfirmButton()
154 })
155
156 tagInput?.addEventListener('keydown', (e) => {
157 if (e.key === 'Enter' && tagValue.trim()) {
158 switchToDisplay()
159 } else if (e.key === 'Escape') {
160 switchToAdd()
161 }
162 })
163
164 confirmButton?.addEventListener('click', () => {
165 if (tagValue.trim()) {
166 switchToDisplay()
167 }
168 })
169
170 cancelButton?.addEventListener('click', switchToAdd)
171
172 deleteButton?.addEventListener('click', switchToAdd)
173 </script>
174
175 <style>
176 .tag-card {
177 border: 1px solid var(--border);
178 width: 100%;
179 height: 12rem;
180 display: flex;
181 align-items: center;
182 justify-content: center;
183 border-radius: 10px;
184 }
185
186 .tag-container {
187 display: flex;
188 justify-content: center;
189 align-items: center;
190 width: 100%;
191 padding: 0 2rem;
192 overflow: hidden;
193 }
194
195 .tag-component {
196 display: flex;
197 align-items: center;
198 justify-content: center;
199 gap: 0.5rem;
200 position: relative;
201 width: 100%;
202 min-height: 40px;
203 }
204
205 .add-button {
206 background-color: var(--text-primary);
207 color: var(--bg);
208 border: none;
209 border-radius: 50%;
210 width: 36px;
211 height: 36px;
212 cursor: pointer;
213 display: flex;
214 align-items: center;
215 justify-content: center;
216 transition:
217 all 0.3s ease,
218 visibility 0s;
219 position: absolute;
220 top: 50%;
221 left: 50%;
222 transform: translate(-50%, -50%) scale(1);
223 z-index: 1;
224 opacity: 1;
225 visibility: visible;
226 overflow: visible;
227 will-change: transform;
228 -webkit-transform: translate(-50%, -50%) scale(1);
229 box-sizing: border-box;
230 padding: 0;
231 margin: 0;
232 }
233
234 .add-button.hidden {
235 opacity: 0;
236 transform: translate(-50%, -50%) scale(0.8);
237 pointer-events: none;
238 visibility: hidden;
239 transition:
240 all 0.3s ease,
241 visibility 0s 0.2s;
242 }
243
244 .add-icon,
245 .confirm-icon,
246 .cancel-icon,
247 .delete-icon {
248 fill: currentColor;
249 width: 1rem;
250 height: 1rem;
251 position: relative;
252 display: block;
253 transform: translateZ(0);
254 }
255
256 .add-button:hover {
257 opacity: 0.9;
258 transform: translate(-50%, -50%) scale(1);
259 -webkit-transform: translate(-50%, -50%) scale(1);
260 }
261
262 .input-state {
263 display: flex;
264 align-items: center;
265 gap: 0.125rem;
266 background-color: var(--astro-code-background);
267 border: 1px solid var(--code-bg);
268 border-radius: 18px;
269 padding: 0.28125rem 0.375rem;
270 width: 40px;
271 max-width: 40px;
272 opacity: 0;
273 overflow: hidden;
274 transition: all 0.3s ease;
275 position: relative;
276 z-index: 2;
277 pointer-events: none;
278 }
279
280 .input-state.active {
281 width: 10rem;
282 max-width: 10rem;
283 opacity: 1;
284 pointer-events: all;
285 }
286
287 .tag-input {
288 background: transparent;
289 border: none;
290 outline: none;
291 color: var(--text-primary);
292 font-size: 0.9rem;
293 font-family: var(--font-mono);
294 min-width: 4rem;
295 padding: 0.25rem 0.5rem;
296 opacity: 0;
297 transform: translateX(-8px);
298 transition:
299 opacity 0.2s ease 0.15s,
300 transform 0.2s ease 0.15s;
301 }
302
303 .input-state.active .tag-input {
304 opacity: 1;
305 transform: translateX(0);
306 }
307
308 .tag-input::placeholder {
309 color: var(--text-tertiary);
310 }
311
312 .confirm-button,
313 .cancel-button {
314 background: none;
315 border: none;
316 border-radius: 50%;
317 width: 24px;
318 height: 24px;
319 cursor: pointer;
320 display: flex;
321 align-items: center;
322 justify-content: center;
323 font-size: 0.8rem;
324 opacity: 0;
325 transform: scale(0.8);
326 transition: all 0.2s ease;
327 position: relative;
328 min-width: 24px;
329 min-height: 24px;
330 }
331
332 .confirm-button::before,
333 .cancel-button::before {
334 content: '';
335 position: absolute;
336 top: 50%;
337 left: 50%;
338 transform: translate(-50%, -50%);
339 border-radius: 50%;
340 background-color: var(--code-bg);
341 opacity: 0;
342 transition: opacity 0.2s ease;
343 z-index: -1;
344 width: 28px;
345 height: 28px;
346 }
347
348 .confirm-button:hover::before,
349 .cancel-button:hover::before {
350 opacity: 1;
351 }
352
353 .input-state.active .confirm-button,
354 .input-state.active .cancel-button {
355 transform: scale(1);
356 }
357
358 .confirm-button {
359 color: var(--text-primary);
360 opacity: 0.6;
361 }
362
363 .confirm-button.disabled {
364 opacity: 0.3;
365 cursor: not-allowed;
366 }
367
368 .confirm-button:not(.disabled):hover {
369 opacity: 1;
370 }
371
372 .confirm-button.disabled:hover::before {
373 opacity: 0;
374 }
375
376 .cancel-button {
377 color: var(--text-primary);
378 opacity: 0.6;
379 }
380
381 .cancel-button:hover {
382 opacity: 1;
383 }
384
385 .tag-display {
386 display: flex;
387 align-items: center;
388 gap: 0.25rem;
389 background-color: var(--astro-code-background);
390 border: 1px solid var(--code-bg);
391 border-radius: 18px;
392 padding: 0.28125rem 0.325rem 0.28125rem 0.75rem;
393 position: absolute;
394 top: 50%;
395 left: 50%;
396 transform: translate(-50%, -50%);
397 z-index: 3;
398 opacity: 0;
399 pointer-events: none;
400 transition: all 0.25s ease-out;
401 will-change: transform, opacity;
402 }
403
404 .tag-display > * {
405 margin-right: 0.125rem;
406 }
407
408 .tag-display > *:last-child {
409 margin-right: 0;
410 }
411
412 .tag-display.active {
413 opacity: 1;
414 transform: translate(-50%, -50%);
415 pointer-events: all;
416 }
417
418 .tag-text {
419 color: var(--text-primary);
420 font-size: 0.9rem;
421 font-family: var(--font-mono);
422 }
423
424 .delete-button {
425 background: none;
426 border: none;
427 border-radius: 50%;
428 width: 24px;
429 height: 24px;
430 cursor: pointer;
431 display: flex;
432 align-items: center;
433 justify-content: center;
434 font-size: 0.8rem;
435 color: var(--text-primary);
436 opacity: 0.6;
437 transition: all 0.2s ease;
438 position: relative;
439 min-width: 24px;
440 min-height: 24px;
441 }
442
443 .delete-button::before {
444 content: '';
445 position: absolute;
446 top: 50%;
447 left: 50%;
448 transform: translate(-50%, -50%);
449 border-radius: 50%;
450 background-color: var(--code-bg);
451 opacity: 0;
452 transition: opacity 0.2s ease;
453 z-index: -1;
454 width: 28px;
455 height: 28px;
456 }
457
458 .delete-button:hover {
459 opacity: 1;
460 }
461
462 .delete-button:hover::before {
463 opacity: 1;
464 }
465 </style>