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 +1 -1
- package/src/ui/kDialog.vue +76 -21
- package/src/ui/kInput.vue +8 -3
- package/src/ui/kSelect.vue +12 -5
- package/src/ui/kToggle.vue +18 -6
- package/src/ui/themes/kInput.theme.js +4 -1
- package/tailwind-preset.js +2 -2
- package/tailwind.config.js +1 -1
package/package.json
CHANGED
package/src/ui/kDialog.vue
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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-
|
|
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
|
-
<
|
|
18
|
-
|
|
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
|
-
<
|
|
22
|
-
</
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
112
|
+
return 'w-[600px] max-w-[calc(100vw-2rem)]'
|
|
83
113
|
},
|
|
84
114
|
dialogClasses() {
|
|
85
115
|
if (this.width) {
|
|
86
|
-
return this.
|
|
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
|
|
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
|
-
:
|
|
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">
|
package/src/ui/kSelect.vue
CHANGED
|
@@ -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-
|
|
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="
|
|
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
|
-
|
|
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>
|
package/src/ui/kToggle.vue
CHANGED
|
@@ -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
|
-
|
|
18
|
-
|
|
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
|
|
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
|
|
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-
|
|
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
|
};
|
package/tailwind-preset.js
CHANGED
|
@@ -8,7 +8,7 @@ const config = {
|
|
|
8
8
|
header: '#256D96',
|
|
9
9
|
background: '#FEFCF6',
|
|
10
10
|
accent: '#8C1F1F',
|
|
11
|
-
primary: '#
|
|
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
|