ketekny-ui-kit 1.0.79 → 1.0.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ketekny-ui-kit",
3
3
  "type": "module",
4
- "version": "1.0.79",
4
+ "version": "1.0.80",
5
5
  "description": "A Vue 3 UI component library with Tailwind CSS styling",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -32,18 +32,23 @@ function createNodes() {
32
32
 
33
33
  const bubble = document.createElement('div')
34
34
  bubble.className = `
35
- px-3 py-2 rounded-lg text-sm font-medium leading-5
35
+ px-3 py-2 rounded-lg text-base font-medium leading-5
36
36
  text-slate-50 bg-slate-900 shadow-xl ring-1 ring-black/20
37
37
  opacity-0 scale-95 transition duration-150 ease-out
38
38
  `.replace(/\s+/g, ' ').trim()
39
+ bubble.style.color = '#f8fafc'
40
+ bubble.style.backgroundColor = '#0f172a'
41
+ bubble.style.borderColor = 'rgba(0, 0, 0, 0.2)'
39
42
  bubble.style.whiteSpace = 'normal' // allow wrap when needed
40
43
  bubble.style.maxWidth = 'min(36rem, calc(100vw - 1.5rem))' // clamp width
41
44
  bubble.style.wordBreak = 'break-word'
42
45
  bubble.style.letterSpacing = '0.01em'
43
46
  bubble.style.position = 'relative'
47
+ bubble.setAttribute('role', 'tooltip')
48
+ bubble.setAttribute('aria-hidden', 'true')
44
49
 
45
50
  const arrow = document.createElement('div')
46
- arrow.className = 'absolute w-2 h-2 rotate-45 bg-slate-900'
51
+ arrow.className = 'absolute w-2 h-2 rotate-45 bg-slate-900 opacity-0 transition-opacity duration-150 ease-out'
47
52
  arrow.setAttribute('aria-hidden', 'true')
48
53
 
49
54
  wrapper.appendChild(bubble)
@@ -184,25 +189,35 @@ export default {
184
189
  (content.mode === 'component' && !content.component)
185
190
  ) return
186
191
 
187
- const { wrapper, bubble, arrow } = createNodes()
188
- document.body.appendChild(wrapper)
189
-
190
- // Render content
191
- let app
192
- if (content.mode === 'text') bubble.textContent = content.text
193
- if (content.mode === 'html') bubble.innerHTML = content.html
194
- if (content.mode === 'component') {
195
- app = createApp({ render: () => h(content.component, content.props || {}) })
196
- app.mount(bubble)
197
- }
198
-
199
192
  const state = {
200
193
  placement: placementPref,
201
194
  pad: 8, // viewport padding
202
- gap: 8 // distance from trigger
195
+ gap: 8, // distance from trigger
196
+ visible: false,
197
+ nodes: null,
198
+ app: null,
199
+ }
200
+
201
+ function ensureNodes() {
202
+ if (state.nodes) return state.nodes
203
+
204
+ const { wrapper, bubble, arrow } = createNodes()
205
+ document.body.appendChild(wrapper)
206
+
207
+ if (content.mode === 'text') bubble.textContent = content.text
208
+ if (content.mode === 'html') bubble.innerHTML = content.html
209
+ if (content.mode === 'component') {
210
+ state.app = createApp({ render: () => h(content.component, content.props || {}) })
211
+ state.app.mount(bubble)
212
+ }
213
+
214
+ state.nodes = { wrapper, bubble, arrow }
215
+ return state.nodes
203
216
  }
204
217
 
205
218
  function positionAndShow() {
219
+ const nodes = ensureNodes()
220
+ const { wrapper, bubble, arrow } = nodes
206
221
  const elRect = el.getBoundingClientRect()
207
222
  const bSize = measureBubble(bubble)
208
223
  const pos = computePosition({ elRect, bSize, placement: state.placement, gap: state.gap, pad: state.pad })
@@ -215,18 +230,42 @@ export default {
215
230
  placeArrow({ arrow, bubbleRect: { width: bSize.width, height: bSize.height }, elRect, pos })
216
231
 
217
232
  // Show
233
+ bubble.setAttribute('aria-hidden', 'false')
218
234
  bubble.classList.remove('opacity-0', 'scale-95')
219
235
  bubble.classList.add('opacity-100', 'scale-100')
220
236
  arrow.style.display = 'block'
237
+ arrow.classList.remove('opacity-100')
238
+ arrow.classList.add('opacity-0')
239
+ window.setTimeout(() => {
240
+ if (!state.visible || !state.nodes) return
241
+ arrow.classList.remove('opacity-0')
242
+ arrow.classList.add('opacity-100')
243
+ }, 150)
221
244
 
222
245
  // Save current placement (may be flipped)
223
246
  state.placement = pos.placement
247
+ state.visible = true
224
248
  }
225
249
 
226
250
  function hide() {
251
+ if (!state.nodes) return
252
+
253
+ const { bubble, wrapper } = state.nodes
254
+ bubble.setAttribute('aria-hidden', 'true')
227
255
  bubble.classList.remove('opacity-100', 'scale-100')
228
256
  bubble.classList.add('opacity-0', 'scale-95')
229
- arrow.style.display = 'none'
257
+ state.visible = false
258
+ const { arrow } = state.nodes
259
+ arrow.classList.remove('opacity-100')
260
+ arrow.classList.add('opacity-0')
261
+
262
+ window.setTimeout(() => {
263
+ if (state.visible || !state.nodes) return
264
+ state.app?.unmount?.()
265
+ wrapper.remove()
266
+ state.nodes = null
267
+ state.app = null
268
+ }, 160)
230
269
  }
231
270
 
232
271
  // Events
@@ -244,7 +283,7 @@ export default {
244
283
  window.addEventListener('scroll', onScrollOrResize, true)
245
284
  window.addEventListener('resize', onScrollOrResize)
246
285
 
247
- el._tooltip = { wrapper, bubble, arrow, onEnter, onLeave, onScrollOrResize, app }
286
+ el._tooltip = { onEnter, onLeave, onScrollOrResize, state }
248
287
  },
249
288
 
250
289
  updated(el, binding) {
@@ -255,27 +294,30 @@ export default {
255
294
  if (nextPlacement !== undefined) {
256
295
  // Update preferred placement; flip may still occur on show
257
296
  // We store it on a small state on the element:
258
- t.prefPlacement = nextPlacement
297
+ t.state.placement = nextPlacement
259
298
  }
260
299
 
261
300
  const content = normalizeValue(binding)
262
- if (content.mode === 'text') t.bubble.textContent = content.text || ''
263
- else if (content.mode === 'html') t.bubble.innerHTML = content.html || ''
301
+ if (!t.state.nodes) return
302
+
303
+ const { bubble } = t.state.nodes
304
+ if (content.mode === 'text') bubble.textContent = content.text || ''
305
+ else if (content.mode === 'html') bubble.innerHTML = content.html || ''
264
306
  else if (content.mode === 'component') {
265
- if (t.app) t.app.unmount()
307
+ t.state.app?.unmount?.()
266
308
  const app = createApp({ render: () => h(content.component, content.props || {}) })
267
- app.mount(t.bubble)
268
- t.app = app
309
+ app.mount(bubble)
310
+ t.state.app = app
269
311
  }
270
312
  },
271
313
 
272
314
  unmounted(el) {
273
315
  const t = el._tooltip
274
316
  if (!t) return
275
- if (t.app) t.app.unmount()
276
- t.bubble.remove()
277
- t.arrow.remove()
278
- t.wrapper.remove()
317
+ if (t.state.app) t.state.app.unmount()
318
+ if (t.state.nodes) {
319
+ t.state.nodes.wrapper.remove()
320
+ }
279
321
  el.removeEventListener('mouseenter', t.onEnter)
280
322
  el.removeEventListener('mouseleave', t.onLeave)
281
323
  el.removeEventListener('focus', t.onEnter)
@@ -74,7 +74,7 @@
74
74
  <span
75
75
  v-if="item.description"
76
76
  class="block mt-1 text-sm"
77
- :class="item.active ? 'text-slate-700' : 'text-slate-300'"
77
+ :class="item.active ? 'text-slate-700' : 'text-slate-200'"
78
78
  >
79
79
  {{ item.description }}
80
80
  </span>
@@ -85,7 +85,7 @@
85
85
 
86
86
  <div
87
87
  v-if="account"
88
- class="px-1 pb-4 border-b shrink-0 border-white/10 bg-slate-900/80 text-white/80"
88
+ class="px-1 pb-4 border-b shrink-0 border-white/10 bg-slate-900/80 text-white/90"
89
89
  >
90
90
  <div v-if="collapsed" class="flex flex-col gap-3">
91
91
  <div v-if="account.loggedIn" class="flex flex-col items-center gap-2">
@@ -179,10 +179,10 @@
179
179
 
180
180
  <div
181
181
  v-if="!collapsed"
182
- class="border-t shrink-0 border-white/10 text-white/50"
182
+ class="border-t shrink-0 border-white/10 text-white/70"
183
183
  >
184
184
  <div v-if="showContactInfo" class="flex flex-col gap-2 px-4">
185
- <p class="text-sm font-semibold uppercase tracking-[0.25em] text-white/60">
185
+ <p class="text-sm font-semibold uppercase tracking-[0.25em] text-white/80">
186
186
  KETEKNY AE
187
187
  </p>
188
188
  <div class="flex flex-wrap items-center text-sm gap-x-3">
@@ -240,7 +240,7 @@ const LABELS = {
240
240
  signOut: "Αποσύνδεση",
241
241
  signIn: "Σύνδεση",
242
242
  signUp: "Εγγραφή",
243
- contactAddress: "Βερανζέου 13, 10677, Αθήνα",
243
+ contactAddress: "Βερανζέρου 13, 10677, Αθήνα",
244
244
  };
245
245
 
246
246
  export default {
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="overflow-hidden border rounded-lg shadow-sm component-card cp-card cp-border">
3
3
  <div class="flex items-center justify-between px-4 py-3 border-b cp-border cp-muted-50">
4
- <h3 class="text-lg font-semibold cp-text">{{ componentName }} Description</h3>
4
+ <h2 class="text-lg font-semibold cp-text">{{ componentName }} Description</h2>
5
5
 
6
6
  <div class="flex items-center gap-1">
7
7
  <button
@@ -34,7 +34,7 @@
34
34
  </td>
35
35
  <td class="px-4 py-3"><span class="font-mono text-sm text-blue-600">{{ prop.type }}</span></td>
36
36
  <td class="px-4 py-3">
37
- <code v-if="prop.default !== undefined" class="font-mono text-sm text-emerald-600">{{ prop.default }}</code>
37
+ <code v-if="prop.default !== undefined" class="font-mono text-sm text-slate-700">{{ prop.default }}</code>
38
38
  <span v-else class="cp-text-muted">-</span>
39
39
  </td>
40
40
  <td class="px-4 py-3 cp-text-muted">{{ prop.description }}</td>
@@ -65,7 +65,7 @@
65
65
  <code class="text-sm font-mono px-1.5 py-0.5 rounded cp-chip cp-chip-text">{{ slot.name }}</code>
66
66
  </td>
67
67
  <td class="px-4 py-3">
68
- <span :class="['inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium', slot.scoped ? 'bg-emerald-100 text-emerald-700' : 'cp-badge-muted']">
68
+ <span :class="['inline-flex items-center px-2 py-0.5 rounded-full text-sm font-medium', slot.scoped ? 'bg-emerald-100 text-emerald-700' : 'cp-badge-muted']">
69
69
  {{ slot.scoped ? "Yes" : "No" }}
70
70
  </span>
71
71
  </td>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="overflow-hidden border rounded-lg shadow-sm component-card cp-card cp-border">
3
3
  <div class="flex items-center justify-between px-4 py-3 border-b cp-border cp-muted-50">
4
- <h3 class="text-lg font-semibold cp-text">{{ componentName }} Preview</h3>
4
+ <h2 class="text-lg font-semibold cp-text">{{ componentName }} Preview</h2>
5
5
 
6
6
  <div class="flex items-center gap-1">
7
7
  <button
@@ -22,7 +22,7 @@
22
22
  v-for="vp in previewViewports"
23
23
  :key="vp.id"
24
24
  @click="activeViewport = vp.id"
25
- :class="['px-3 py-1.5 text-xs font-semibold rounded-md transition-colors', activeViewport === vp.id ? 'cp-tab-active' : 'cp-tab']">
25
+ :class="['px-3 py-1.5 text-sm font-semibold rounded-md transition-colors', activeViewport === vp.id ? 'cp-tab-active' : 'cp-tab']">
26
26
  {{ vp.label }}
27
27
  </button>
28
28
  </div>
@@ -1,6 +1,18 @@
1
1
  <template>
2
2
  <div class="relative w-full">
3
- <input type="text" v-model="localValue" @input="onInput" :class="[defaultStyle, disabled ? disabledStyle : '']" :placeholder="placeholder" :disabled="disabled" />
3
+ <label v-if="label" :for="inputId" class="mb-1 block text-sm font-medium text-gray-700 dark:text-slate-300">
4
+ {{ label }}
5
+ </label>
6
+ <input
7
+ :id="inputId"
8
+ type="text"
9
+ v-model="localValue"
10
+ @input="onInput"
11
+ :class="[defaultStyle, disabled ? disabledStyle : '']"
12
+ :placeholder="placeholder"
13
+ :aria-label="!label ? placeholder : null"
14
+ :disabled="disabled"
15
+ />
4
16
  <Search class="absolute w-4 h-4 text-primary/70 -translate-y-1/2 pointer-events-none right-3 top-1/2" />
5
17
  </div>
6
18
  </template>
@@ -16,10 +28,12 @@ export default {
16
28
  modelValue: { type: String, default: "" },
17
29
  debounce: { type: [String, Number], default: 300 },
18
30
  placeholder: { type: String, default: "Αναζήτηση..." },
31
+ label: { type: String, default: "" },
19
32
  disabled: { type: Boolean, default: false },
20
33
  },
21
34
  data() {
22
35
  return {
36
+ inputId: `ksearch-${Math.random().toString(36).slice(2, 10)}`,
23
37
  defaultStyle: "w-full px-3 py-2 border rounded-lg transition shadow-sm focus:outline-none text-gray-700 focus:ring-2 focus:ring-primary/20 focus:border-primary bg-white placeholder-gray-400 dark:bg-slate-800 dark:text-slate-100 dark:border-slate-600 dark:placeholder-slate-500",
24
38
  disabledStyle: "bg-gray-100 text-gray-400 cursor-not-allowed dark:bg-slate-900 dark:text-slate-500",
25
39
 
package/src/ui/kTags.vue CHANGED
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <div class="w-full">
3
- <div v-if="label != null" class="inputLabel" :class="hasError ? 'text-rose-800 dark:text-rose-400' : 'text-primary/90'">
3
+ <label v-if="label != null" :for="inputId" class="inputLabel" :class="hasError ? 'text-rose-800 dark:text-rose-400' : 'text-primary/90'">
4
4
  {{ label }}
5
- </div>
5
+ </label>
6
6
  <div
7
7
  :class="[
8
8
  'flex flex-wrap items-center gap-2 p-2 border rounded-xl shadow-sm min-h-[3rem] transition',
@@ -19,11 +19,13 @@
19
19
  </button>
20
20
  </span>
21
21
  <input
22
+ :id="inputId"
22
23
  v-model="newTag"
23
24
  @keydown.enter.prevent="addTag"
24
25
  @keydown.backspace="handleBackspace"
25
26
  @blur="addTag"
26
27
  :placeholder="placeholder"
28
+ :aria-label="!label ? placeholder || 'Add tag' : null"
27
29
  :disabled="disabled"
28
30
  :class="['flex-1 p-1 bg-transparent border-none focus:outline-none dark:text-slate-100 dark:placeholder-slate-500', disabled ? 'text-gray-400 cursor-not-allowed dark:text-slate-500' : '']"
29
31
  />
@@ -56,6 +58,7 @@ export default {
56
58
  data() {
57
59
  return {
58
60
  newTag: "",
61
+ inputId: `ktags-${Math.random().toString(36).slice(2, 10)}`,
59
62
  };
60
63
  },
61
64
  computed: {