ketekny-ui-kit 1.0.99 → 1.0.101

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.99",
4
+ "version": "1.0.101",
5
5
  "description": "A Vue 3 UI component library with Tailwind CSS styling",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -18,7 +18,7 @@
18
18
  <div
19
19
  v-if="option.icon"
20
20
  class="shrink-0"
21
- :class="iconWrapperClass(option)"
21
+ :class="[iconWrapperClass(option), { [intentStyles(option).text]: isColored(option) }]"
22
22
  >
23
23
  <kIcon
24
24
  v-if="typeof option.icon === 'string'"
@@ -35,7 +35,10 @@
35
35
  <div class="min-w-0 flex-1">
36
36
  <div :class="contentRowClass">
37
37
  <div class="min-w-0">
38
- <div class="truncate text-sm font-semibold">
38
+ <div
39
+ class="truncate text-sm font-semibold"
40
+ :class="{ [intentStyles(option).text]: isColored(option) }"
41
+ >
39
42
  {{ option.label }}
40
43
  </div>
41
44
  <div
@@ -45,11 +48,28 @@
45
48
  {{ option.description }}
46
49
  </div>
47
50
  </div>
48
- <span
49
- :class="checkClass(option)"
50
- >
51
-
52
- </span>
51
+ <div class="flex shrink-0 items-center gap-2">
52
+ <span
53
+ v-if="option.badge !== null && option.badge !== ''"
54
+ :class="badgeClass(option)"
55
+ >
56
+ {{ option.badge }}
57
+ </span>
58
+ <span
59
+ v-if="showIndicator()"
60
+ :class="indicatorClass(option)"
61
+ >
62
+ <span
63
+ v-if="indicator === 'radio'"
64
+ v-show="isSelected(option)"
65
+ class="h-2 w-2 rounded-full"
66
+ :class="radioDotClass(option)"
67
+ />
68
+ <template v-else>
69
+
70
+ </template>
71
+ </span>
72
+ </div>
53
73
  </div>
54
74
  </div>
55
75
  </button>
@@ -59,6 +79,7 @@
59
79
 
60
80
  <script>
61
81
  import kIcon from "./kIcon.vue";
82
+ import { OPTION_INTENT_STYLES, OPTION_INTENTS } from "./themes/kOptionGroup.theme";
62
83
 
63
84
  export default {
64
85
  name: "kOptionGroup",
@@ -82,7 +103,25 @@ export default {
82
103
  variant: {
83
104
  type: String,
84
105
  default: "table",
85
- validator: (value) => ["table", "card"].includes(value),
106
+ validator: (value) => ["table", "card", "list"].includes(value),
107
+ },
108
+ indicator: {
109
+ type: String,
110
+ default: "check",
111
+ validator: (value) => ["check", "checkbox", "radio", "none"].includes(value),
112
+ },
113
+ color: {
114
+ type: String,
115
+ default: "primary",
116
+ validator: (value) => OPTION_INTENTS.includes(value),
117
+ },
118
+ colorKey: {
119
+ type: String,
120
+ default: "color",
121
+ },
122
+ badgeKey: {
123
+ type: String,
124
+ default: "badge",
86
125
  },
87
126
  title: {
88
127
  type: String,
@@ -113,6 +152,9 @@ export default {
113
152
  isTableVariant() {
114
153
  return this.variant === "table";
115
154
  },
155
+ isListVariant() {
156
+ return this.variant === "list";
157
+ },
116
158
  normalizedSelection() {
117
159
  return Array.isArray(this.modelValue) ? this.modelValue : [];
118
160
  },
@@ -123,9 +165,13 @@ export default {
123
165
  label: option?.[this.labelKey] ?? "",
124
166
  description: option?.[this.descriptionKey] ?? "",
125
167
  icon: option?.[this.iconKey] ?? null,
168
+ color: option?.[this.colorKey] ?? null,
169
+ badge: option?.[this.badgeKey] ?? null,
126
170
  }));
127
171
  },
128
172
  containerClass() {
173
+ if (this.isListVariant) return "flex flex-col gap-2";
174
+
129
175
  return this.isTableVariant
130
176
  ? "overflow-hidden rounded-xl border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-900"
131
177
  : "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3";
@@ -173,16 +219,59 @@ export default {
173
219
  resolveKey(option, index) {
174
220
  return `${String(option.value ?? option.label)}-${index}`;
175
221
  },
222
+ optionIntent(option) {
223
+ const key = option.color || this.color;
224
+ return OPTION_INTENT_STYLES[key] ? key : "primary";
225
+ },
226
+ intentStyles(option) {
227
+ return OPTION_INTENT_STYLES[this.optionIntent(option)];
228
+ },
229
+ isColored(option) {
230
+ return !!option.color;
231
+ },
232
+ isTinted(option) {
233
+ return !!option.color || this.color !== "primary";
234
+ },
176
235
  buttonClass(option) {
236
+ if (this.isListVariant) {
237
+ const selected = this.isSelected(option);
238
+ const colored = this.isColored(option);
239
+ const styles = this.intentStyles(option);
240
+ const pieces = ["items-center gap-3 rounded-lg border px-3 py-2.5 transition"];
241
+
242
+ if (selected) {
243
+ pieces.push(styles.selBg);
244
+ if (colored) pieces.push(`border-l-2 ${styles.accent}`);
245
+ } else if (colored) {
246
+ pieces.push(styles.text);
247
+ pieces.push(`border-l-2 ${styles.accent}`);
248
+ pieces.push("bg-white border-slate-200 hover:bg-slate-50 dark:bg-slate-900 dark:border-slate-700 dark:hover:bg-slate-800");
249
+ } else {
250
+ pieces.push("bg-white border-slate-200 text-gray-700 hover:bg-slate-50 dark:bg-slate-900 dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800");
251
+ }
252
+
253
+ return pieces.join(" ");
254
+ }
255
+
177
256
  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";
257
+ if (this.isSelected(option)) {
258
+ if (this.isTinted(option)) {
259
+ const styles = this.intentStyles(option);
260
+ return `items-center gap-3 border-b border-slate-200 px-3 py-3 last:border-b-0 dark:border-slate-700 ${styles.softBg} ${styles.text}`;
261
+ }
262
+ return "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";
263
+ }
264
+ return "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
265
  }
182
266
 
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";
267
+ if (this.isSelected(option)) {
268
+ if (this.isTinted(option)) {
269
+ const styles = this.intentStyles(option);
270
+ return `items-start gap-3 rounded-xl border p-3 ${styles.selBg} ${styles.text}`;
271
+ }
272
+ return "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";
273
+ }
274
+ return "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
275
  },
187
276
  iconWrapperClass(option) {
188
277
  if (this.isTableVariant) {
@@ -194,15 +283,52 @@ export default {
194
283
  return "flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary";
195
284
  },
196
285
  checkClass(option) {
286
+ const selected = this.isSelected(option);
287
+ const colored = this.isTinted(option);
288
+ const marker = this.intentStyles(option).marker;
289
+
197
290
  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";
291
+ if (selected) {
292
+ const fill = colored ? marker : "bg-primary";
293
+ return `inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full ${fill} text-[11px] font-semibold text-white`;
294
+ }
295
+ return "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";
296
+ }
297
+
298
+ if (selected) {
299
+ const fill = colored ? marker : "border-primary bg-primary";
300
+ return `mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full border ${fill} text-[11px] font-semibold text-white`;
301
+ }
302
+ return "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";
303
+ },
304
+ showIndicator() {
305
+ return this.indicator !== "none";
306
+ },
307
+ indicatorClass(option) {
308
+ const selected = this.isSelected(option);
309
+ const marker = this.intentStyles(option).marker;
310
+
311
+ if (this.indicator === "checkbox") {
312
+ const base = "inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-md border text-[11px] font-semibold";
313
+ return selected
314
+ ? `${base} ${marker} text-white`
315
+ : `${base} border-slate-300 text-transparent dark:border-slate-600`;
201
316
  }
202
317
 
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";
318
+ if (this.indicator === "radio") {
319
+ const base = "inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full border";
320
+ return selected
321
+ ? `${base} ${marker.split(" ")[1]}`
322
+ : `${base} border-slate-300 dark:border-slate-600`;
323
+ }
324
+
325
+ return this.checkClass(option);
326
+ },
327
+ radioDotClass(option) {
328
+ return this.intentStyles(option).marker.split(" ")[0];
329
+ },
330
+ badgeClass(option) {
331
+ return `text-sm font-semibold ${this.intentStyles(option).text}`;
206
332
  },
207
333
  },
208
334
  };
@@ -0,0 +1,10 @@
1
+ // intent -> pieces the row/marker/badge compose from
2
+ export const OPTION_INTENT_STYLES = {
3
+ primary: { text: "text-sky-700 dark:text-sky-300", accent: "border-l-sky-500", softBg: "bg-sky-100 dark:bg-sky-500/15", selBg: "bg-sky-100 border-sky-300 dark:bg-sky-500/15 dark:border-sky-500/50", marker: "bg-sky-600 border-sky-600" },
4
+ secondary: { text: "text-slate-700 dark:text-slate-200", accent: "border-l-slate-500", softBg: "bg-slate-100 dark:bg-slate-700/50", selBg: "bg-slate-100 border-slate-300 dark:bg-slate-700/50 dark:border-slate-500", marker: "bg-slate-700 border-slate-700" },
5
+ success: { text: "text-emerald-700 dark:text-emerald-300", accent: "border-l-emerald-500", softBg: "bg-emerald-100 dark:bg-emerald-500/15", selBg: "bg-emerald-100 border-emerald-300 dark:bg-emerald-500/15 dark:border-emerald-500/50", marker: "bg-emerald-600 border-emerald-600" },
6
+ warning: { text: "text-amber-700 dark:text-amber-300", accent: "border-l-amber-500", softBg: "bg-amber-100 dark:bg-amber-500/15", selBg: "bg-amber-100 border-amber-300 dark:bg-amber-500/15 dark:border-amber-500/50", marker: "bg-amber-600 border-amber-600" },
7
+ danger: { text: "text-rose-700 dark:text-rose-300", accent: "border-l-rose-500", softBg: "bg-rose-100 dark:bg-rose-500/15", selBg: "bg-rose-100 border-rose-300 dark:bg-rose-500/15 dark:border-rose-500/50", marker: "bg-rose-600 border-rose-600" },
8
+ };
9
+
10
+ export const OPTION_INTENTS = Object.keys(OPTION_INTENT_STYLES);