ketekny-ui-kit 1.0.96 → 1.0.98
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
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
|
@@ -196,6 +196,18 @@ export default {
|
|
|
196
196
|
visible: false,
|
|
197
197
|
nodes: null,
|
|
198
198
|
app: null,
|
|
199
|
+
content,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function renderContent(bubble) {
|
|
203
|
+
const content = state.content
|
|
204
|
+
if (content.mode === 'text') bubble.textContent = content.text || ''
|
|
205
|
+
else if (content.mode === 'html') bubble.innerHTML = content.html || ''
|
|
206
|
+
else if (content.mode === 'component') {
|
|
207
|
+
state.app?.unmount?.()
|
|
208
|
+
state.app = createApp({ render: () => h(content.component, content.props || {}) })
|
|
209
|
+
state.app.mount(bubble)
|
|
210
|
+
}
|
|
199
211
|
}
|
|
200
212
|
|
|
201
213
|
function ensureNodes() {
|
|
@@ -204,12 +216,7 @@ export default {
|
|
|
204
216
|
const { wrapper, bubble, arrow } = createNodes()
|
|
205
217
|
document.body.appendChild(wrapper)
|
|
206
218
|
|
|
207
|
-
|
|
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
|
-
}
|
|
219
|
+
renderContent(bubble)
|
|
213
220
|
|
|
214
221
|
state.nodes = { wrapper, bubble, arrow }
|
|
215
222
|
return state.nodes
|
|
@@ -297,17 +304,17 @@ export default {
|
|
|
297
304
|
t.state.placement = nextPlacement
|
|
298
305
|
}
|
|
299
306
|
|
|
300
|
-
|
|
307
|
+
t.state.content = normalizeValue(binding)
|
|
301
308
|
if (!t.state.nodes) return
|
|
302
309
|
|
|
303
310
|
const { bubble } = t.state.nodes
|
|
311
|
+
const content = t.state.content
|
|
304
312
|
if (content.mode === 'text') bubble.textContent = content.text || ''
|
|
305
313
|
else if (content.mode === 'html') bubble.innerHTML = content.html || ''
|
|
306
314
|
else if (content.mode === 'component') {
|
|
307
315
|
t.state.app?.unmount?.()
|
|
308
|
-
|
|
309
|
-
app.mount(bubble)
|
|
310
|
-
t.state.app = app
|
|
316
|
+
t.state.app = createApp({ render: () => h(content.component, content.props || {}) })
|
|
317
|
+
t.state.app.mount(bubble)
|
|
311
318
|
}
|
|
312
319
|
},
|
|
313
320
|
|
|
@@ -239,33 +239,38 @@ export default {
|
|
|
239
239
|
|
|
240
240
|
<template>
|
|
241
241
|
<aside
|
|
242
|
-
class="fixed inset-y-0 left-0 z-40 flex h-full shrink-0 flex-col border-r border-sidebar-border bg-sidebar text-sidebar-foreground transition-[width,transform] duration-200 ease-out lg:static lg:translate-x-0"
|
|
242
|
+
class="group/sidebar fixed inset-y-0 left-0 z-40 flex h-full shrink-0 flex-col border-r border-sidebar-border bg-sidebar text-sidebar-foreground transition-[width,transform] duration-200 ease-out lg:static lg:translate-x-0"
|
|
243
243
|
:style="sidebarWidthStyle"
|
|
244
244
|
:class="[
|
|
245
245
|
mobileOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0',
|
|
246
246
|
]"
|
|
247
247
|
>
|
|
248
|
-
<
|
|
248
|
+
<button
|
|
249
|
+
type="button"
|
|
250
|
+
v-tooltip.right="collapsed ? 'Ανάπτυξη πλαϊνού μενού' : 'Σύμπτυξη πλαϊνού μενού'"
|
|
251
|
+
class="absolute right-0 top-1/2 z-50 hidden h-14 w-6 -translate-y-1/2 translate-x-1/2 items-center justify-center rounded-r-md border border-sidebar-border bg-sidebar-accent text-sidebar-muted opacity-70 shadow-lg shadow-black/30 transition-[color,opacity] hover:opacity-100 hover:text-sidebar-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand lg:inline-flex"
|
|
252
|
+
:aria-label="collapsed ? 'Ανάπτυξη πλαϊνού μενού' : 'Σύμπτυξη πλαϊνού μενού'"
|
|
253
|
+
@click="toggleCollapsed"
|
|
254
|
+
>
|
|
255
|
+
<PanelLeftOpen v-if="collapsed" class="size-4" :stroke-width="2" />
|
|
256
|
+
<PanelLeftClose v-else class="size-4" :stroke-width="2" />
|
|
257
|
+
</button>
|
|
258
|
+
|
|
259
|
+
<div class="relative flex items-center px-4 py-4" :class="collapsed ? 'justify-center' : 'gap-2.5'">
|
|
249
260
|
<img
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
? 'https://s3.ketekny.gr/public/web-apps/logos/logo-icon-dark.png'
|
|
253
|
-
: 'https://support.ketekny.gr/logo-dark.png'
|
|
254
|
-
"
|
|
261
|
+
v-if="collapsed"
|
|
262
|
+
src="https://s3.ketekny.gr/public/web-apps/logos/logo-icon-dark.png"
|
|
255
263
|
alt="Ketekny logo"
|
|
256
|
-
class="object-contain"
|
|
257
|
-
|
|
264
|
+
class="size-9 shrink-0 rounded-lg object-contain"
|
|
265
|
+
@click="handleLogoClick"
|
|
266
|
+
/>
|
|
267
|
+
<img
|
|
268
|
+
v-else
|
|
269
|
+
src="https://support.ketekny.gr/logo-dark.png"
|
|
270
|
+
alt="Ketekny logo"
|
|
271
|
+
class="h-10 min-w-0 flex-1 object-contain"
|
|
258
272
|
@click="handleLogoClick"
|
|
259
273
|
/>
|
|
260
|
-
<button
|
|
261
|
-
v-if="!collapsed"
|
|
262
|
-
type="button"
|
|
263
|
-
class="ml-auto hidden rounded-md p-1.5 text-sidebar-muted hover:bg-sidebar-accent hover:text-sidebar-foreground lg:inline-flex"
|
|
264
|
-
aria-label="Collapse sidebar"
|
|
265
|
-
@click="toggleCollapsed"
|
|
266
|
-
>
|
|
267
|
-
<PanelLeftClose class="size-4" :stroke-width="2" />
|
|
268
|
-
</button>
|
|
269
274
|
</div>
|
|
270
275
|
|
|
271
276
|
<div v-if="title && !collapsed" class="px-4 pb-2">
|
|
@@ -291,18 +296,6 @@ export default {
|
|
|
291
296
|
</div>
|
|
292
297
|
</div>
|
|
293
298
|
|
|
294
|
-
<div v-if="collapsed" class="justify-center hidden px-3 pb-1 lg:flex">
|
|
295
|
-
<button
|
|
296
|
-
type="button"
|
|
297
|
-
v-tooltip.right="'Ανάπτυξη πλαϊνού μενού'"
|
|
298
|
-
class="flex h-10 w-10 items-center justify-center rounded-md text-sidebar-muted hover:bg-sidebar-accent hover:text-sidebar-foreground"
|
|
299
|
-
aria-label="Ανάπτυξη πλαϊνού μενού"
|
|
300
|
-
@click="toggleCollapsed"
|
|
301
|
-
>
|
|
302
|
-
<PanelLeftOpen class="size-4" :stroke-width="2" />
|
|
303
|
-
</button>
|
|
304
|
-
</div>
|
|
305
|
-
|
|
306
299
|
<div v-if="!collapsed" class="px-3 pb-2">
|
|
307
300
|
<div class="relative">
|
|
308
301
|
<Search
|
|
@@ -376,67 +369,69 @@ export default {
|
|
|
376
369
|
<Bell class="size-4" :stroke-width="2" />
|
|
377
370
|
</button>
|
|
378
371
|
</div>
|
|
379
|
-
<
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
activeId === link.id
|
|
389
|
-
? 'bg-brand text-brand-foreground font-medium'
|
|
390
|
-
: link.id === 'sign-out'
|
|
391
|
-
? 'text-orange-300 hover:bg-orange-500/10 hover:text-orange-200'
|
|
392
|
-
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground',
|
|
393
|
-
]"
|
|
394
|
-
:aria-label="collapsed ? link.label : undefined"
|
|
395
|
-
@click="handleFooterSelect(link.id)"
|
|
396
|
-
>
|
|
397
|
-
<component
|
|
398
|
-
:is="link.icon"
|
|
399
|
-
class="size-4 shrink-0"
|
|
400
|
-
:class="
|
|
372
|
+
<template v-if="collapsed">
|
|
373
|
+
<button
|
|
374
|
+
v-for="link in footerLinks"
|
|
375
|
+
:key="link.id"
|
|
376
|
+
type="button"
|
|
377
|
+
v-tooltip.right="link.label"
|
|
378
|
+
class="flex items-center w-full rounded-md transition-colors duration-150"
|
|
379
|
+
:class="[
|
|
380
|
+
'justify-center px-0 py-2',
|
|
401
381
|
activeId === link.id
|
|
402
|
-
? 'text-brand-foreground'
|
|
382
|
+
? 'bg-brand text-brand-foreground font-medium'
|
|
403
383
|
: link.id === 'sign-out'
|
|
404
|
-
? 'text-orange-
|
|
405
|
-
: 'text-sidebar-
|
|
406
|
-
"
|
|
407
|
-
:
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
:
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
:
|
|
428
|
-
|
|
429
|
-
|
|
384
|
+
? 'text-orange-300 hover:bg-orange-500/10 hover:text-orange-200'
|
|
385
|
+
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground',
|
|
386
|
+
]"
|
|
387
|
+
:aria-label="link.label"
|
|
388
|
+
@click="handleFooterSelect(link.id)"
|
|
389
|
+
>
|
|
390
|
+
<component
|
|
391
|
+
:is="link.icon"
|
|
392
|
+
class="size-4 shrink-0"
|
|
393
|
+
:class="
|
|
394
|
+
activeId === link.id
|
|
395
|
+
? 'text-brand-foreground'
|
|
396
|
+
: link.id === 'sign-out'
|
|
397
|
+
? 'text-orange-400'
|
|
398
|
+
: 'text-sidebar-muted'
|
|
399
|
+
"
|
|
400
|
+
:stroke-width="2"
|
|
401
|
+
/>
|
|
402
|
+
</button>
|
|
403
|
+
</template>
|
|
404
|
+
<template v-else>
|
|
405
|
+
<button
|
|
406
|
+
v-for="link in expandedFooterLinks"
|
|
407
|
+
:key="`${link.id}-expanded`"
|
|
408
|
+
type="button"
|
|
409
|
+
class="flex items-center w-full rounded-md transition-colors duration-150"
|
|
410
|
+
:class="[
|
|
411
|
+
'gap-2.5 px-2 py-2 text-left',
|
|
430
412
|
activeId === link.id
|
|
431
|
-
? 'text-brand-foreground'
|
|
413
|
+
? 'bg-brand text-brand-foreground font-medium'
|
|
432
414
|
: link.id === 'sign-out'
|
|
433
|
-
? 'text-orange-
|
|
434
|
-
: 'text-sidebar-
|
|
435
|
-
"
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
415
|
+
? 'text-orange-300 hover:bg-orange-500/10 hover:text-orange-200'
|
|
416
|
+
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground',
|
|
417
|
+
]"
|
|
418
|
+
@click="handleFooterSelect(link.id)"
|
|
419
|
+
>
|
|
420
|
+
<component
|
|
421
|
+
:is="link.icon"
|
|
422
|
+
class="size-4 shrink-0"
|
|
423
|
+
:class="
|
|
424
|
+
activeId === link.id
|
|
425
|
+
? 'text-brand-foreground'
|
|
426
|
+
: link.id === 'sign-out'
|
|
427
|
+
? 'text-orange-400'
|
|
428
|
+
: 'text-sidebar-muted'
|
|
429
|
+
"
|
|
430
|
+
:stroke-width="2"
|
|
431
|
+
/>
|
|
432
|
+
<span class="truncate">{{ link.label }}</span>
|
|
433
|
+
</button>
|
|
434
|
+
</template>
|
|
440
435
|
</div>
|
|
441
436
|
|
|
442
437
|
<div
|
|
@@ -164,6 +164,7 @@ export default {
|
|
|
164
164
|
:level="level + 1"
|
|
165
165
|
:active-id="activeId"
|
|
166
166
|
:open-ids="openIds"
|
|
167
|
+
:collapsed="collapsed"
|
|
167
168
|
@select="$emit('select', $event)"
|
|
168
169
|
@toggle="$emit('toggle', $event)"
|
|
169
170
|
/>
|
|
@@ -207,6 +208,7 @@ export default {
|
|
|
207
208
|
:level="0"
|
|
208
209
|
:active-id="activeId"
|
|
209
210
|
:open-ids="openIds"
|
|
211
|
+
:collapsed="false"
|
|
210
212
|
@select="$emit('select', $event)"
|
|
211
213
|
@toggle="$emit('toggle', $event)"
|
|
212
214
|
/>
|
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>
|