ketekny-ui-kit 1.0.96 → 1.0.97
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/index.js +2 -1
- package/package.json +1 -1
- package/src/ui/kList.vue +26 -1
- package/src/ui/kOptionGroup.vue +209 -0
package/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import kTable from './src/ui/kTable.vue'
|
|
|
14
14
|
import kTabs from './src/ui/kTabs.vue'
|
|
15
15
|
import kToggle from './src/ui/kToggle.vue'
|
|
16
16
|
import kForm from './src/ui/kForm.vue'
|
|
17
|
+
import kOptionGroup from './src/ui/kOptionGroup.vue'
|
|
17
18
|
import kUploader from './src/ui/kUploader.vue'
|
|
18
19
|
import kEditor from './src/ui/kEditor.vue'
|
|
19
20
|
import kSpinner from './src/ui/kSpinner.vue'
|
|
@@ -57,7 +58,7 @@ export {
|
|
|
57
58
|
kMessage, kCode, kToolbar, kTable, kTabs, kChip, kSpinner, kDatatable, kIcon, kMenu, kSkeleton, kProgressBar, kPagination, kTree,
|
|
58
59
|
|
|
59
60
|
// Form Components
|
|
60
|
-
kButton, kCheckbox, kSelect, kUploader, kToggle, kInput, kDateSelector, kEditor, kSelectButton, kTags, kSearch, kArrayList, kList, kTextArea, kForm,
|
|
61
|
+
kButton, kCheckbox, kSelect, kUploader, kToggle, kInput, kDateSelector, kEditor, kSelectButton, kTags, kSearch, kArrayList, kList, kTextArea, kForm, kOptionGroup,
|
|
61
62
|
|
|
62
63
|
// Dialogs
|
|
63
64
|
kDialog, kDrawer,
|
package/package.json
CHANGED
package/src/ui/kList.vue
CHANGED
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
<div
|
|
8
8
|
v-for="(stat, index) in normalizedStats"
|
|
9
9
|
:key="resolveKey(stat, index)"
|
|
10
|
-
class="flex items-center gap-3
|
|
10
|
+
class="flex items-center gap-3 border border-gray-200 rounded-xl bg-white p-3 dark:border-slate-700 dark:bg-slate-800"
|
|
11
|
+
:class="entryClass(stat)"
|
|
12
|
+
:role="stat.action ? 'button' : undefined"
|
|
13
|
+
:tabindex="stat.action ? 0 : undefined"
|
|
14
|
+
@click="handleAction(stat, index)"
|
|
15
|
+
@keydown.enter.prevent="handleAction(stat, index)"
|
|
16
|
+
@keydown.space.prevent="handleAction(stat, index)"
|
|
11
17
|
>
|
|
12
18
|
<div
|
|
13
19
|
v-if="stat.icon"
|
|
@@ -39,6 +45,12 @@
|
|
|
39
45
|
v-for="(stat, index) in normalizedStats"
|
|
40
46
|
:key="resolveKey(stat, index)"
|
|
41
47
|
class="border-b border-gray-200 last:border-b-0 dark:border-slate-700"
|
|
48
|
+
:class="entryClass(stat)"
|
|
49
|
+
:role="stat.action ? 'button' : undefined"
|
|
50
|
+
:tabindex="stat.action ? 0 : undefined"
|
|
51
|
+
@click="handleAction(stat, index)"
|
|
52
|
+
@keydown.enter.prevent="handleAction(stat, index)"
|
|
53
|
+
@keydown.space.prevent="handleAction(stat, index)"
|
|
42
54
|
>
|
|
43
55
|
<td class="px-4 py-3 text-sm text-gray-600 dark:text-slate-400">
|
|
44
56
|
<div class="flex items-center gap-2">
|
|
@@ -89,15 +101,18 @@ export default {
|
|
|
89
101
|
validator: (value) => ['col', 'row'].includes(value),
|
|
90
102
|
},
|
|
91
103
|
},
|
|
104
|
+
emits: ['action'],
|
|
92
105
|
computed: {
|
|
93
106
|
isRowFormat() {
|
|
94
107
|
return this.format === 'row'
|
|
95
108
|
},
|
|
96
109
|
normalizedStats() {
|
|
97
110
|
return this.stats.map((stat) => ({
|
|
111
|
+
raw: stat,
|
|
98
112
|
label: stat?.label ?? '',
|
|
99
113
|
value: stat?.value ?? '-',
|
|
100
114
|
icon: stat?.icon ?? null,
|
|
115
|
+
action: typeof stat?.action === 'function' ? stat.action : null,
|
|
101
116
|
}))
|
|
102
117
|
},
|
|
103
118
|
},
|
|
@@ -105,6 +120,16 @@ export default {
|
|
|
105
120
|
resolveKey(stat, index) {
|
|
106
121
|
return `${String(stat.label)}-${index}`
|
|
107
122
|
},
|
|
123
|
+
entryClass(stat) {
|
|
124
|
+
return stat.action
|
|
125
|
+
? 'cursor-pointer transition hover:border-primary/40 hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-primary/20 dark:hover:bg-slate-700/70'
|
|
126
|
+
: ''
|
|
127
|
+
},
|
|
128
|
+
handleAction(stat, index) {
|
|
129
|
+
if (!stat.action) return
|
|
130
|
+
stat.action(stat.raw, index)
|
|
131
|
+
this.$emit('action', stat.raw, index)
|
|
132
|
+
},
|
|
108
133
|
},
|
|
109
134
|
}
|
|
110
135
|
</script>
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full">
|
|
3
|
+
<div
|
|
4
|
+
v-if="title"
|
|
5
|
+
:class="titleClass"
|
|
6
|
+
>
|
|
7
|
+
{{ title }}
|
|
8
|
+
</div>
|
|
9
|
+
<div :class="containerClass">
|
|
10
|
+
<button
|
|
11
|
+
v-for="(option, index) in normalizedOptions"
|
|
12
|
+
:key="resolveKey(option, index)"
|
|
13
|
+
type="button"
|
|
14
|
+
class="flex w-full text-left transition"
|
|
15
|
+
:class="buttonClass(option)"
|
|
16
|
+
@click="toggleOption(option)"
|
|
17
|
+
>
|
|
18
|
+
<div
|
|
19
|
+
v-if="option.icon"
|
|
20
|
+
class="shrink-0"
|
|
21
|
+
:class="iconWrapperClass(option)"
|
|
22
|
+
>
|
|
23
|
+
<kIcon
|
|
24
|
+
v-if="typeof option.icon === 'string'"
|
|
25
|
+
:name="option.icon"
|
|
26
|
+
:size="iconSize"
|
|
27
|
+
/>
|
|
28
|
+
<component
|
|
29
|
+
:is="option.icon"
|
|
30
|
+
v-else
|
|
31
|
+
:class="iconComponentClass"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="min-w-0 flex-1">
|
|
36
|
+
<div :class="contentRowClass">
|
|
37
|
+
<div class="min-w-0">
|
|
38
|
+
<div class="truncate text-sm font-semibold">
|
|
39
|
+
{{ option.label }}
|
|
40
|
+
</div>
|
|
41
|
+
<div
|
|
42
|
+
v-if="option.description"
|
|
43
|
+
:class="descriptionClass"
|
|
44
|
+
>
|
|
45
|
+
{{ option.description }}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<span
|
|
49
|
+
:class="checkClass(option)"
|
|
50
|
+
>
|
|
51
|
+
✓
|
|
52
|
+
</span>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<script>
|
|
61
|
+
import kIcon from "./kIcon.vue";
|
|
62
|
+
|
|
63
|
+
export default {
|
|
64
|
+
name: "kOptionGroup",
|
|
65
|
+
components: {
|
|
66
|
+
kIcon,
|
|
67
|
+
},
|
|
68
|
+
props: {
|
|
69
|
+
modelValue: {
|
|
70
|
+
type: Array,
|
|
71
|
+
default: () => [],
|
|
72
|
+
},
|
|
73
|
+
options: {
|
|
74
|
+
type: Array,
|
|
75
|
+
default: () => [],
|
|
76
|
+
},
|
|
77
|
+
selection: {
|
|
78
|
+
type: String,
|
|
79
|
+
default: "multiple",
|
|
80
|
+
validator: (value) => ["single", "multiple"].includes(value),
|
|
81
|
+
},
|
|
82
|
+
variant: {
|
|
83
|
+
type: String,
|
|
84
|
+
default: "table",
|
|
85
|
+
validator: (value) => ["table", "card"].includes(value),
|
|
86
|
+
},
|
|
87
|
+
title: {
|
|
88
|
+
type: String,
|
|
89
|
+
default: "",
|
|
90
|
+
},
|
|
91
|
+
valueKey: {
|
|
92
|
+
type: String,
|
|
93
|
+
default: "value",
|
|
94
|
+
},
|
|
95
|
+
labelKey: {
|
|
96
|
+
type: String,
|
|
97
|
+
default: "label",
|
|
98
|
+
},
|
|
99
|
+
descriptionKey: {
|
|
100
|
+
type: String,
|
|
101
|
+
default: "description",
|
|
102
|
+
},
|
|
103
|
+
iconKey: {
|
|
104
|
+
type: String,
|
|
105
|
+
default: "icon",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
emits: ["update:modelValue", "change"],
|
|
109
|
+
computed: {
|
|
110
|
+
isMultiple() {
|
|
111
|
+
return this.selection === "multiple";
|
|
112
|
+
},
|
|
113
|
+
isTableVariant() {
|
|
114
|
+
return this.variant === "table";
|
|
115
|
+
},
|
|
116
|
+
normalizedSelection() {
|
|
117
|
+
return Array.isArray(this.modelValue) ? this.modelValue : [];
|
|
118
|
+
},
|
|
119
|
+
normalizedOptions() {
|
|
120
|
+
return this.options.map((option) => ({
|
|
121
|
+
raw: option,
|
|
122
|
+
value: option?.[this.valueKey],
|
|
123
|
+
label: option?.[this.labelKey] ?? "",
|
|
124
|
+
description: option?.[this.descriptionKey] ?? "",
|
|
125
|
+
icon: option?.[this.iconKey] ?? null,
|
|
126
|
+
}));
|
|
127
|
+
},
|
|
128
|
+
containerClass() {
|
|
129
|
+
return this.isTableVariant
|
|
130
|
+
? "overflow-hidden rounded-xl border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-900"
|
|
131
|
+
: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3";
|
|
132
|
+
},
|
|
133
|
+
titleClass() {
|
|
134
|
+
return this.isTableVariant
|
|
135
|
+
? "mb-2 text-xs font-semibold uppercase tracking-[0.12em] text-slate-500 dark:text-slate-400"
|
|
136
|
+
: "mb-3 text-sm font-semibold text-slate-800 dark:text-slate-100";
|
|
137
|
+
},
|
|
138
|
+
contentRowClass() {
|
|
139
|
+
return this.isTableVariant ? "flex items-center justify-between gap-3" : "flex items-start justify-between gap-3";
|
|
140
|
+
},
|
|
141
|
+
descriptionClass() {
|
|
142
|
+
return this.isTableVariant
|
|
143
|
+
? "mt-0.5 text-xs text-gray-500 dark:text-slate-400"
|
|
144
|
+
: "mt-1 text-xs text-gray-500 dark:text-slate-400";
|
|
145
|
+
},
|
|
146
|
+
iconSize() {
|
|
147
|
+
return this.isTableVariant ? 16 : 18;
|
|
148
|
+
},
|
|
149
|
+
iconComponentClass() {
|
|
150
|
+
return this.isTableVariant ? "h-4 w-4" : "h-[18px] w-[18px]";
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
methods: {
|
|
154
|
+
isSelected(option) {
|
|
155
|
+
return this.normalizedSelection.includes(option.value);
|
|
156
|
+
},
|
|
157
|
+
toggleOption(option) {
|
|
158
|
+
let nextSelection = [];
|
|
159
|
+
|
|
160
|
+
if (this.isMultiple) {
|
|
161
|
+
nextSelection = [...this.normalizedSelection];
|
|
162
|
+
const selectedIndex = nextSelection.indexOf(option.value);
|
|
163
|
+
|
|
164
|
+
if (selectedIndex === -1) nextSelection.push(option.value);
|
|
165
|
+
else nextSelection.splice(selectedIndex, 1);
|
|
166
|
+
} else {
|
|
167
|
+
nextSelection = this.isSelected(option) ? [] : [option.value];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.$emit("update:modelValue", nextSelection);
|
|
171
|
+
this.$emit("change", nextSelection);
|
|
172
|
+
},
|
|
173
|
+
resolveKey(option, index) {
|
|
174
|
+
return `${String(option.value ?? option.label)}-${index}`;
|
|
175
|
+
},
|
|
176
|
+
buttonClass(option) {
|
|
177
|
+
if (this.isTableVariant) {
|
|
178
|
+
return this.isSelected(option)
|
|
179
|
+
? "items-center gap-3 border-b border-slate-200 bg-primary/10 px-3 py-3 text-primary last:border-b-0 dark:border-slate-700 dark:bg-primary/15 dark:text-slate-100"
|
|
180
|
+
: "items-center gap-3 border-b border-slate-200 bg-white px-3 py-3 text-gray-700 hover:bg-slate-50 last:border-b-0 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return this.isSelected(option)
|
|
184
|
+
? "items-start gap-3 rounded-xl border border-primary bg-primary/10 p-3 text-primary dark:border-primary dark:bg-primary/15 dark:text-slate-100"
|
|
185
|
+
: "items-start gap-3 rounded-xl border border-gray-200 bg-white p-3 text-gray-700 hover:border-primary/40 hover:bg-primary/5 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 dark:hover:border-primary/50 dark:hover:bg-primary/10";
|
|
186
|
+
},
|
|
187
|
+
iconWrapperClass(option) {
|
|
188
|
+
if (this.isTableVariant) {
|
|
189
|
+
return this.isSelected(option)
|
|
190
|
+
? "flex h-8 w-8 items-center justify-center rounded-md bg-primary/15 text-primary"
|
|
191
|
+
: "flex h-8 w-8 items-center justify-center rounded-md bg-slate-100 text-slate-500 dark:bg-slate-800 dark:text-slate-300";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return "flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary";
|
|
195
|
+
},
|
|
196
|
+
checkClass(option) {
|
|
197
|
+
if (this.isTableVariant) {
|
|
198
|
+
return this.isSelected(option)
|
|
199
|
+
? "inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary text-[11px] font-semibold text-white"
|
|
200
|
+
: "inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-slate-300 text-[11px] font-semibold text-transparent dark:border-slate-600";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return this.isSelected(option)
|
|
204
|
+
? "mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-primary bg-primary text-[11px] font-semibold text-white"
|
|
205
|
+
: "mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-gray-300 text-[11px] font-semibold text-transparent dark:border-slate-600";
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
</script>
|