ketekny-ui-kit 1.0.16 → 1.0.17

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.16",
4
+ "version": "1.0.17",
5
5
  "description": "A Vue 3 UI component library with Tailwind CSS styling",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -2,24 +2,32 @@
2
2
  <div
3
3
  v-if="visible"
4
4
  :style="{ zIndex: zIndex }"
5
- class="fixed inset-0 flex items-center justify-center overflow-hidden bg-black bg-opacity-50 backdrop-blur-sm"
5
+ class="fixed inset-0 flex items-center justify-center overflow-hidden bg-black/50 backdrop-blur-sm"
6
+ @click.self="onBackdropClick"
6
7
  >
7
8
  <transition name="dialog">
8
9
  <div
9
- class="relative bg-white shadow-lg overflow-hidden rounded-2xl flex flex-col max-h-[90vh] w-full m-4 sm:min-w-[600px] sm:w-auto"
10
+ ref="dialogPanel"
11
+ class="relative bg-white shadow-lg overflow-hidden rounded-2xl flex flex-col max-h-[90vh] m-4"
10
12
  :class="dialogClasses"
13
+ :role="'dialog'"
14
+ :aria-modal="'true'"
15
+ :aria-labelledby="titleId"
16
+ tabindex="-1"
11
17
  v-show="visible"
12
18
  >
13
19
  <!-- Header -->
14
- <div class="flex flex-row items-center p-4 text-white bg-primary shrink-0">
15
- <div class="text-xl font-semibold !text-white">{{ title }}</div>
20
+ <div class="flex flex-row items-center p-4 text-white bg-sky-800 shrink-0">
21
+ <div :id="titleId" class="text-xl font-semibold !text-white">{{ title }}</div>
16
22
  <div class="flex-1" />
17
- <div
18
- class="p-1 text-black transition duration-100 ease-in-out rounded-full cursor-pointer text-secondary hover:bg-white hover:text-primary"
23
+ <button
24
+ type="button"
25
+ class="p-1 text-black transition duration-100 ease-in-out rounded-full cursor-pointer text-secondary hover:bg-white hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
26
+ aria-label="Close dialog"
19
27
  @click="close"
20
28
  >
21
- <x />
22
- </div>
29
+ <X />
30
+ </button>
23
31
  </div>
24
32
 
25
33
  <!-- Fixed content (optional) -->
@@ -46,6 +54,7 @@ import { X } from 'lucide-vue-next'
46
54
 
47
55
  // 🔥 Global z-index tracker
48
56
  let dialogZIndexCounter = 1000
57
+ let bodyScrollLockCounter = 0
49
58
 
50
59
  export default {
51
60
  name: 'kDialog',
@@ -54,6 +63,14 @@ export default {
54
63
  visible: Boolean,
55
64
  title: String,
56
65
  maximized: Boolean,
66
+ closeOnBackdrop: {
67
+ type: Boolean,
68
+ default: true,
69
+ },
70
+ closeOnEsc: {
71
+ type: Boolean,
72
+ default: true,
73
+ },
57
74
  width: {
58
75
  type: String,
59
76
  default: '',
@@ -62,39 +79,77 @@ export default {
62
79
  data() {
63
80
  return {
64
81
  zIndex: 1000, // Default fallback
82
+ titleId: `dialog-title-${Math.random().toString(36).slice(2, 10)}`,
83
+ previousActiveElement: null,
65
84
  }
66
85
  },
67
86
  watch: {
68
- visible(val) {
69
- if (val) {
70
- dialogZIndexCounter += 1
71
- this.zIndex = dialogZIndexCounter
72
- }
87
+ visible: {
88
+ immediate: true,
89
+ handler(val) {
90
+ if (val) {
91
+ dialogZIndexCounter += 1
92
+ this.zIndex = dialogZIndexCounter
93
+ this.previousActiveElement = document.activeElement
94
+ this.lockBodyScroll()
95
+ document.addEventListener('keydown', this.handleKeydown)
96
+ this.$nextTick(() => {
97
+ this.$refs.dialogPanel?.focus()
98
+ })
99
+ } else {
100
+ this.unlockBodyScroll()
101
+ document.removeEventListener('keydown', this.handleKeydown)
102
+ this.$nextTick(() => {
103
+ this.previousActiveElement?.focus?.()
104
+ this.previousActiveElement = null
105
+ })
106
+ }
107
+ },
73
108
  },
74
109
  },
75
- // mounted() {
76
- // // ✅ Assign a new z-index when component is created
77
- // dialogZIndexCounter += 1
78
- // this.zIndex = dialogZIndexCounter
79
- // },
80
110
  computed: {
81
111
  computedWidth() {
82
- return this.width ? this.width : 'sm:w-[600px]'
112
+ return 'w-[600px] max-w-[calc(100vw-2rem)]'
83
113
  },
84
114
  dialogClasses() {
85
115
  if (this.width) {
86
- return this.computedWidth
116
+ return this.width
87
117
  } else if (this.maximized) {
88
- return 'max-w-none h-[90vh]'
118
+ return 'w-[calc(100vw-2rem)] max-w-none h-[90vh]'
89
119
  } else {
90
120
  return this.computedWidth + ' max-h-[90vh]'
91
121
  }
92
122
  },
93
123
  },
94
124
  methods: {
125
+ onBackdropClick() {
126
+ if (!this.closeOnBackdrop) return
127
+ this.close()
128
+ },
129
+ handleKeydown(event) {
130
+ if (!this.visible || !this.closeOnEsc) return
131
+ if (event.key === 'Escape') {
132
+ event.preventDefault()
133
+ this.close()
134
+ }
135
+ },
136
+ lockBodyScroll() {
137
+ bodyScrollLockCounter += 1
138
+ if (bodyScrollLockCounter > 1) return
139
+ document.body.style.overflow = 'hidden'
140
+ },
141
+ unlockBodyScroll() {
142
+ bodyScrollLockCounter = Math.max(0, bodyScrollLockCounter - 1)
143
+ if (bodyScrollLockCounter > 0) return
144
+ document.body.style.overflow = ''
145
+ },
95
146
  close() {
96
147
  this.$emit('update:visible', false)
97
148
  },
98
149
  },
150
+ beforeUnmount() {
151
+ this.unlockBodyScroll()
152
+ document.removeEventListener('keydown', this.handleKeydown)
153
+ },
99
154
  }
100
155
  </script>
package/src/ui/kInput.vue CHANGED
@@ -1,6 +1,10 @@
1
1
  <template>
2
2
  <div class="w-full text-primary/90">
3
- <label v-if="label != null" :for="inputId" class="inputLabel" :class="hasError ? theme.labelError : theme.label">
3
+ <label
4
+ v-if="label != null"
5
+ :for="inputId"
6
+ class="inputLabel"
7
+ :class="hasError ? theme.labelError : disabled ? theme.labelDisabled : theme.label">
4
8
  {{ label }}
5
9
  </label>
6
10
  <div class="relative">
@@ -22,11 +26,12 @@
22
26
  :aria-invalid="hasError ? 'true' : 'false'"
23
27
  :aria-describedby="describedById"
24
28
  />
25
- <component v-if="iconComponent" :is="iconComponent" :class="theme.trailingIcon" />
29
+ <component v-if="iconComponent" :is="iconComponent" :class="[theme.trailingIcon, disabled ? theme.trailingIconDisabled : '']" />
26
30
  <div v-if="isPassword" :class="theme.passwordToggle">
27
31
  <button
28
32
  type="button"
29
- :class="theme.passwordToggleButton"
33
+ :disabled="disabled"
34
+ :class="[theme.passwordToggleButton, disabled ? theme.passwordToggleButtonDisabled : '']"
30
35
  :aria-label="showPassword ? 'Hide password' : 'Show password'"
31
36
  :aria-pressed="showPassword ? 'true' : 'false'"
32
37
  @click="showPassword = !showPassword">
@@ -44,7 +44,7 @@
44
44
  v-if="isOpen"
45
45
  ref="dropdown"
46
46
  :id="dropdownId"
47
- class="absolute z-[9999] mt-1 overflow-auto bg-white border border-gray-200 rounded-lg shadow-lg"
47
+ class="absolute z-[9999] mt-1 overflow-auto bg-white border border-gray-200 rounded-lg shadow-[0_20px_45px_-15px_rgba(15,23,42,0.35),0_8px_18px_-10px_rgba(15,23,42,0.25)] ring-1 ring-slate-900/10"
48
48
  :class="dropdownHeight"
49
49
  :style="dropdownPositionStyle"
50
50
  role="listbox"
@@ -71,12 +71,19 @@
71
71
  :id="`${dropdownId}-opt-${option[optionValue]}`"
72
72
  :key="option[optionValue]"
73
73
  @click="selectOption(option)"
74
- class="px-3 py-2 rounded cursor-pointer"
75
- :class="option[optionValue] === selectedValue ? 'bg-primary text-white' : 'hover:bg-blue-100'"
74
+ class="px-3 py-2 rounded cursor-pointer transition-colors"
75
+ :class="
76
+ option[optionValue] === selectedValue
77
+ ? 'bg-primary/10 text-primary font-medium'
78
+ : 'text-slate-700 hover:bg-primary/5'
79
+ "
76
80
  role="option"
77
81
  :aria-selected="(option[optionValue] === selectedValue).toString()"
78
82
  >
79
- {{ option[optionLabel] }}
83
+ <div class="flex items-center justify-between gap-2">
84
+ <span class="truncate">{{ option[optionLabel] }}</span>
85
+ <Check v-if="option[optionValue] === selectedValue" class="w-4 h-4 shrink-0 text-primary" />
86
+ </div>
80
87
  </div>
81
88
  </div>
82
89
 
@@ -97,7 +104,7 @@
97
104
  </template>
98
105
 
99
106
  <script setup>
100
- import { X, ChevronDown, ChevronUp } from "lucide-vue-next";
107
+ import { X, ChevronDown, ChevronUp, Check } from "lucide-vue-next";
101
108
  </script>
102
109
 
103
110
  <script>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div :class="labelStyle === 'inline' ? 'flex items-center' : ''">
3
3
  <!-- Block-style label -->
4
- <div v-if="showLabel && labelStyle !== 'inline'" class="block mb-1 text-sm font-bold text-primary/90" for="toggle">
4
+ <div v-if="showLabel && labelStyle !== 'inline'" :class="['block mb-1 text-sm font-bold', disabled ? 'text-slate-500' : 'text-primary/90']" for="toggle">
5
5
  {{ label }}
6
6
  </div>
7
7
 
@@ -13,15 +13,27 @@
13
13
  :aria-disabled="disabled.toString()"
14
14
  :disabled="disabled"
15
15
  :class="[
16
- 'w-16 h-8 flex items-center rounded-full p-1 transition duration-300',
17
- modelValue ? 'bg-primary' : 'bg-gray-400',
18
- disabled ? 'opacity-50 cursor-not-allowed' : '',
16
+ 'w-16 h-8 flex items-center rounded-full p-1 transition duration-300 border',
17
+ disabled
18
+ ? 'bg-slate-200 border-slate-300 cursor-not-allowed'
19
+ : modelValue
20
+ ? 'bg-primary border-primary shadow-sm shadow-primary/30'
21
+ : 'bg-gray-400 border-gray-400',
19
22
  ]">
20
- <div :class="['w-6 h-6 bg-white rounded-full shadow-md transform transition duration-300', modelValue ? 'translate-x-8' : 'translate-x-0']"></div>
23
+ <div
24
+ :class="[
25
+ 'w-6 h-6 rounded-full shadow-md transform transition duration-300',
26
+ disabled ? 'bg-slate-100' : modelValue ? 'bg-white ring-2 ring-primary/20' : 'bg-white',
27
+ modelValue ? 'translate-x-8' : 'translate-x-0',
28
+ ]"></div>
21
29
  </button>
22
30
 
23
31
  <!-- Inline-style label -->
24
- <div v-if="showLabel && labelStyle === 'inline'" class="ml-2 text-sm font-bold cursor-pointer text-primary/90" :for="computedId" @click="toggle">
32
+ <div
33
+ v-if="showLabel && labelStyle === 'inline'"
34
+ :class="['ml-2 text-sm font-bold', disabled ? 'text-slate-500 cursor-not-allowed' : 'text-primary/90 cursor-pointer']"
35
+ :for="computedId"
36
+ @click="toggle">
25
37
  {{ label }}
26
38
  </div>
27
39
  </div>
@@ -1,16 +1,19 @@
1
1
  export const K_INPUT_THEME = {
2
2
  label: "text-primary/90",
3
+ labelDisabled: "text-slate-500",
3
4
  labelError: "text-rose-800",
4
5
  baseInput:
5
6
  "w-full px-3 py-2 border rounded-lg transition shadow-sm focus:outline-none text-slate-900 bg-white placeholder-gray-400 focus:ring-2 focus:ring-primary/25 focus:border-primary",
6
7
  withRightAdornment: "pr-10",
7
8
  withPasswordToggle: "pr-14",
8
- disabled: "bg-slate-100 text-slate-500 cursor-not-allowed border-slate-300",
9
+ disabled: "!bg-slate-50 !text-slate-400 !border-slate-200 !shadow-none cursor-not-allowed placeholder-slate-400",
9
10
  errorInput: "border-rose-500 bg-rose-50/40 focus:border-rose-600 focus:ring-rose-500/20",
10
11
  infoText: "text-sm text-slate-600",
11
12
  errorText: "text-sm text-rose-700",
12
13
  trailingIcon: "absolute w-4 h-4 text-primary/70 -translate-y-1/2 pointer-events-none right-3 top-1/2",
14
+ trailingIconDisabled: "text-slate-400",
13
15
  passwordToggle: "absolute inset-y-0 right-0 flex items-center pr-3 text-slate-700",
14
16
  passwordToggleButton:
15
17
  "text-xs font-medium select-none rounded px-1 py-0.5 hover:bg-primary/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
18
+ passwordToggleButtonDisabled: "text-slate-400 cursor-not-allowed hover:bg-transparent",
16
19
  };
@@ -8,7 +8,7 @@ const config = {
8
8
  header: '#256D96',
9
9
  background: '#FEFCF6',
10
10
  accent: '#8C1F1F',
11
- primary: '#1E5F7C',
11
+ primary: '#0369A1',
12
12
  secondary: '#f5f7fa',
13
13
  success: '#228B22',
14
14
  danger: '#A32626',
@@ -50,4 +50,4 @@ const config = {
50
50
  plugins: [],
51
51
  }
52
52
 
53
- export default config
53
+ export default config
@@ -22,7 +22,7 @@ export default {
22
22
  header: "#256D96",
23
23
  background: "#FEFCF6",
24
24
  accent: "#8C1F1F",
25
- primary: "#1E5F7C",
25
+ primary: "#0369A1",
26
26
  secondary: "#f5f7fa",
27
27
  success: "#228B22",
28
28
  danger: "#A32626",