forked from
quillmatiq.com/augment
Fork of Chiri for Astro for my blog
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 is:inline>
69 function setupTag() {
70 let currentState = 'add' // 'add', 'input', 'display'
71 let tagValue = localStorage.getItem('tagValue') || ''
72
73 const addButton = document.getElementById('add-button')
74 const inputState = document.getElementById('input-state')
75 const tagDisplay = document.getElementById('tag-display')
76 const tagInput = document.getElementById('tag-input')
77 const confirmButton = document.getElementById('confirm-button')
78 const cancelButton = document.getElementById('cancel-button')
79 const deleteButton = document.getElementById('delete-button')
80 const tagText = document.getElementById('tag-text')
81
82 if (tagValue) {
83 addButton.classList.add('hidden')
84 inputState.classList.remove('active')
85 tagDisplay.classList.add('active')
86 tagText.textContent = truncateText(tagValue)
87 currentState = 'display'
88 } else {
89 addButton.classList.remove('hidden')
90 inputState.classList.remove('active')
91 tagDisplay.classList.remove('active')
92 tagInput.value = ''
93 currentState = 'add'
94 }
95
96 // Switch to input state
97 function switchToInput() {
98 currentState = 'input'
99 addButton.classList.add('hidden')
100 setTimeout(() => {
101 inputState.classList.add('active')
102 tagInput.focus()
103 }, 100)
104 }
105
106 // Switch to display state
107 function switchToDisplay() {
108 currentState = 'display'
109 inputState.classList.remove('active')
110 setTimeout(() => {
111 tagDisplay.classList.add('active')
112 tagText.textContent = truncateText(tagValue)
113 // Store to localStorage
114 localStorage.setItem('tagValue', tagValue)
115 }, 300)
116 }
117
118 // Return to add state
119 function switchToAdd() {
120 if (currentState === 'display') {
121 tagDisplay.classList.remove('active')
122 } else if (currentState === 'input') {
123 inputState.classList.remove('active')
124 }
125 currentState = 'add'
126 setTimeout(() => {
127 addButton.classList.remove('hidden')
128 tagValue = ''
129 tagInput.value = ''
130 localStorage.removeItem('tagValue')
131 updateConfirmButton()
132 }, 250)
133 }
134
135 // Update confirm button state
136 function updateConfirmButton() {
137 if (tagInput.value.trim()) {
138 confirmButton.classList.remove('disabled')
139 } else {
140 confirmButton.classList.add('disabled')
141 }
142 }
143
144 // Truncate text to max 24 characters
145 function truncateText(text, maxLength = 24) {
146 if (text.length <= maxLength) {
147 return text
148 }
149 return text.substring(0, maxLength) + '...'
150 }
151
152 // Bind events
153 addButton.onclick = switchToInput
154
155 tagInput.oninput = function (e) {
156 const inputValue = e.target.value
157 if (inputValue.length > 24) {
158 e.target.value = inputValue.substring(0, 24)
159 tagValue = inputValue
160 } else {
161 tagValue = inputValue
162 }
163 updateConfirmButton()
164 }
165
166 tagInput.onkeydown = function (e) {
167 if (e.key === 'Enter' && tagValue.trim()) {
168 switchToDisplay()
169 } else if (e.key === 'Escape') {
170 switchToAdd()
171 }
172 }
173
174 confirmButton.onclick = function () {
175 if (tagValue.trim()) {
176 switchToDisplay()
177 }
178 }
179
180 cancelButton.onclick = switchToAdd
181 deleteButton.onclick = switchToAdd
182 }
183
184 setupTag()
185
186 document.addEventListener('astro:page-load', setupTag)
187</script>
188
189<style>
190 .tag-card {
191 border: 1px solid var(--border);
192 width: 100%;
193 height: 12rem;
194 display: flex;
195 align-items: center;
196 justify-content: center;
197 border-radius: 10px;
198 }
199
200 .tag-container {
201 display: flex;
202 justify-content: center;
203 align-items: center;
204 width: 100%;
205 padding: 0 2rem;
206 overflow: hidden;
207 }
208
209 .tag-component {
210 display: flex;
211 align-items: center;
212 justify-content: center;
213 gap: 0.5rem;
214 position: relative;
215 width: 100%;
216 min-height: 40px;
217 }
218
219 .add-button {
220 background-color: var(--text-primary);
221 color: var(--bg);
222 border: none;
223 border-radius: 50%;
224 width: 36px;
225 height: 36px;
226 cursor: pointer;
227 display: flex;
228 align-items: center;
229 justify-content: center;
230 transition:
231 all 0.3s ease,
232 visibility 0s;
233 position: absolute;
234 top: 50%;
235 left: 50%;
236 transform: translate(-50%, -50%) scale(1);
237 z-index: 1;
238 opacity: 1;
239 visibility: visible;
240 overflow: visible;
241 will-change: transform;
242 -webkit-transform: translate(-50%, -50%) scale(1);
243 box-sizing: border-box;
244 padding: 0;
245 margin: 0;
246 }
247
248 .add-button.hidden {
249 opacity: 0;
250 transform: translate(-50%, -50%) scale(0.8);
251 pointer-events: none;
252 visibility: hidden;
253 transition:
254 all 0.3s ease,
255 visibility 0s 0.2s;
256 }
257
258 .add-icon,
259 .confirm-icon,
260 .cancel-icon,
261 .delete-icon {
262 fill: currentColor;
263 width: 1rem;
264 height: 1rem;
265 position: relative;
266 display: block;
267 transform: translateZ(0);
268 }
269
270 .add-button:hover {
271 opacity: 0.8;
272 transform: translate(-50%, -50%) scale(1);
273 -webkit-transform: translate(-50%, -50%) scale(1);
274 }
275
276 .input-state {
277 display: flex;
278 align-items: center;
279 gap: 0.125rem;
280 background-color: var(--astro-code-background);
281 border: 1px solid var(--code-bg);
282 border-radius: 18px;
283 padding: 0.28125rem 0.375rem;
284 width: 40px;
285 max-width: 40px;
286 opacity: 0;
287 overflow: hidden;
288 transition: all 0.3s ease;
289 position: relative;
290 z-index: 2;
291 pointer-events: none;
292 }
293
294 .input-state.active {
295 width: 10rem;
296 max-width: 10rem;
297 opacity: 1;
298 pointer-events: all;
299 }
300
301 .tag-input {
302 background: transparent;
303 border: none;
304 outline: none;
305 color: var(--text-primary);
306 font-size: 0.9rem;
307 font-family: var(--mono);
308 min-width: 4rem;
309 padding: 0.25rem 0.5rem;
310 opacity: 0;
311 transform: translateX(-8px);
312 transition:
313 opacity 0.2s ease 0.15s,
314 transform 0.2s ease 0.15s;
315 }
316
317 .input-state.active .tag-input {
318 opacity: 1;
319 transform: translateX(0);
320 }
321
322 .tag-input::placeholder {
323 color: var(--text-tertiary);
324 }
325
326 .confirm-button,
327 .cancel-button {
328 background: none;
329 border: none;
330 border-radius: 50%;
331 width: 24px;
332 height: 24px;
333 cursor: pointer;
334 display: flex;
335 align-items: center;
336 justify-content: center;
337 font-size: 0.8rem;
338 opacity: 0;
339 transform: scale(0.8);
340 transition: all 0.2s ease;
341 position: relative;
342 min-width: 24px;
343 min-height: 24px;
344 }
345
346 .confirm-button::before,
347 .cancel-button::before {
348 content: '';
349 position: absolute;
350 top: 50%;
351 left: 50%;
352 transform: translate(-50%, -50%);
353 border-radius: 50%;
354 background-color: var(--code-bg);
355 opacity: 0;
356 transition: opacity 0.2s ease;
357 z-index: -1;
358 width: 28px;
359 height: 28px;
360 }
361
362 .confirm-button:hover::before,
363 .cancel-button:hover::before {
364 opacity: 1;
365 }
366
367 .input-state.active .confirm-button,
368 .input-state.active .cancel-button {
369 transform: scale(1);
370 }
371
372 .confirm-button {
373 color: var(--text-primary);
374 opacity: 0.6;
375 }
376
377 .confirm-button.disabled {
378 opacity: 0.3;
379 cursor: not-allowed;
380 }
381
382 .confirm-button:not(.disabled):hover {
383 opacity: 1;
384 }
385
386 .confirm-button.disabled:hover::before {
387 opacity: 0;
388 }
389
390 .cancel-button {
391 color: var(--text-primary);
392 opacity: 0.6;
393 }
394
395 .cancel-button:hover {
396 opacity: 1;
397 }
398
399 .tag-display {
400 display: flex;
401 align-items: center;
402 gap: 0.25rem;
403 background-color: var(--astro-code-background);
404 border: 1px solid var(--code-bg);
405 border-radius: 18px;
406 padding: 0.28125rem 0.325rem 0.28125rem 0.75rem;
407 position: absolute;
408 top: 50%;
409 left: 50%;
410 transform: translate(-50%, -50%);
411 z-index: 3;
412 opacity: 0;
413 pointer-events: none;
414 transition: all 0.25s ease-out;
415 will-change: transform, opacity;
416 }
417
418 .tag-display > * {
419 margin-right: 0.125rem;
420 }
421
422 .tag-display > *:last-child {
423 margin-right: 0;
424 }
425
426 .tag-display.active {
427 opacity: 1;
428 transform: translate(-50%, -50%);
429 pointer-events: all;
430 }
431
432 .tag-text {
433 color: var(--text-primary);
434 font-size: 0.9rem;
435 font-family: var(--mono);
436 }
437
438 .delete-button {
439 background: none;
440 border: none;
441 border-radius: 50%;
442 width: 24px;
443 height: 24px;
444 cursor: pointer;
445 display: flex;
446 align-items: center;
447 justify-content: center;
448 font-size: 0.8rem;
449 color: var(--text-primary);
450 opacity: 0.6;
451 transition: all 0.2s ease;
452 position: relative;
453 min-width: 24px;
454 min-height: 24px;
455 }
456
457 .delete-button::before {
458 content: '';
459 position: absolute;
460 top: 50%;
461 left: 50%;
462 transform: translate(-50%, -50%);
463 border-radius: 50%;
464 background-color: var(--code-bg);
465 opacity: 0;
466 transition: opacity 0.2s ease;
467 z-index: -1;
468 width: 28px;
469 height: 28px;
470 }
471
472 .delete-button:hover {
473 opacity: 1;
474 }
475
476 .delete-button:hover::before {
477 opacity: 1;
478 }
479</style>