ketekny-ui-kit 1.0.83 → 1.0.84
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 +5 -3
- package/package.json +1 -1
- package/src/layout/kSingleColPage.vue +88 -0
- package/src/layout/kTwoColPage.vue +103 -0
- package/src/ui/kForm.vue +88 -0
package/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import kSelect from './src/ui/kSelect.vue'
|
|
|
13
13
|
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
|
+
import kForm from './src/ui/kForm.vue'
|
|
16
17
|
import kUploader from './src/ui/kUploader.vue'
|
|
17
18
|
import kEditor from './src/ui/kEditor.vue'
|
|
18
19
|
import kSpinner from './src/ui/kSpinner.vue'
|
|
@@ -35,6 +36,8 @@ import kAppHeader from './src/layout/kAppHeader.vue'
|
|
|
35
36
|
import kAppFooter from './src/layout/kAppFooter.vue'
|
|
36
37
|
import kAppMain from './src/layout/kAppMain.vue'
|
|
37
38
|
import kHero from './src/layout/kHero.vue'
|
|
39
|
+
import kSingleColPage from './src/layout/kSingleColPage.vue'
|
|
40
|
+
import kTwoColPage from './src/layout/kTwoColPage.vue'
|
|
38
41
|
import twoColLayout from './src/layout/twoColLayout.vue'
|
|
39
42
|
|
|
40
43
|
// Toast/Confirm/Alert Plugins
|
|
@@ -53,13 +56,13 @@ export {
|
|
|
53
56
|
kMessage, kCode, kToolbar, kTable, kTabs, kChip, kSpinner, kDatatable, kIcon, kMenu, kSkeleton, kProgressBar, kPagination, kTree,
|
|
54
57
|
|
|
55
58
|
// Form Components
|
|
56
|
-
kButton, kCheckbox, kSelect, kUploader, kToggle, kInput, kDateSelector, kEditor, kSelectButton, kTags, kSearch, kArrayList, kList, kTextArea,
|
|
59
|
+
kButton, kCheckbox, kSelect, kUploader, kToggle, kInput, kDateSelector, kEditor, kSelectButton, kTags, kSearch, kArrayList, kList, kTextArea, kForm,
|
|
57
60
|
|
|
58
61
|
// Dialogs
|
|
59
62
|
kDialog, kDrawer,
|
|
60
63
|
|
|
61
64
|
// Layout Components
|
|
62
|
-
kAppHeader, kAppFooter, kAppMain, kHero, twoColLayout,
|
|
65
|
+
kAppHeader, kAppFooter, kAppMain, kHero, kSingleColPage, kTwoColPage, twoColLayout,
|
|
63
66
|
|
|
64
67
|
// Plugins
|
|
65
68
|
toastPlugin, confirmPlugin, alertPlugin, tooltipPlugin, inputDialogPlugin,
|
|
@@ -67,4 +70,3 @@ export {
|
|
|
67
70
|
// Tailwind Config
|
|
68
71
|
tailwindPreset
|
|
69
72
|
}
|
|
70
|
-
|
package/package.json
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<main class="flex min-h-screen w-full max-w-none flex-col bg-slate-100">
|
|
3
|
+
<section class="bg-white px-4 pb-4 pb-0 pt-8 sm:px-6 lg:px-8">
|
|
4
|
+
<div class="mx-auto flex w-full max-w-none flex-col gap-5">
|
|
5
|
+
<div
|
|
6
|
+
v-if="pageTitle || pageDescription"
|
|
7
|
+
class="flex flex-wrap items-start justify-between gap-4"
|
|
8
|
+
>
|
|
9
|
+
<div class="min-w-0">
|
|
10
|
+
<p class="text-[10px] font-semibold uppercase tracking-[0.28em] text-slate-500">
|
|
11
|
+
{{ appTitle }}
|
|
12
|
+
</p>
|
|
13
|
+
<h1 class="mt-1 text-2xl font-semibold tracking-tight text-slate-900 sm:text-3xl">
|
|
14
|
+
{{ pageTitle }}
|
|
15
|
+
</h1>
|
|
16
|
+
<p v-if="pageDescription" class="mt-1 text-sm font-medium text-slate-500">
|
|
17
|
+
{{ pageDescription }}
|
|
18
|
+
</p>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
21
|
+
<slot name="header-actions" />
|
|
22
|
+
<kButton
|
|
23
|
+
v-if="externalUrl"
|
|
24
|
+
:label="externalUrlLabel"
|
|
25
|
+
icon="ExternalLink"
|
|
26
|
+
variant="soft"
|
|
27
|
+
@click="openExternalUrl"
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</section>
|
|
33
|
+
|
|
34
|
+
<div class="sticky top-0 z-30 border-b border-t border-slate-200 bg-slate-50 px-4 backdrop-blur sm:px-6 lg:px-8">
|
|
35
|
+
<div class="mx-auto flex w-full max-w-none items-center">
|
|
36
|
+
<slot name="header-extra" />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="flex-1 bg-slate-100 px-4 pb-8 pt-4 sm:px-6 lg:px-8">
|
|
41
|
+
<div class="mx-auto w-full max-w-none">
|
|
42
|
+
<slot />
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</main>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<script>
|
|
49
|
+
import packageJson from '../../package.json'
|
|
50
|
+
import kButton from '../ui/kButton.vue'
|
|
51
|
+
|
|
52
|
+
export default {
|
|
53
|
+
name: 'PageShell',
|
|
54
|
+
components: { kButton },
|
|
55
|
+
props: {
|
|
56
|
+
appTitle: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: packageJson.description || packageJson.name || 'App',
|
|
59
|
+
},
|
|
60
|
+
pageTitle: {
|
|
61
|
+
type: String,
|
|
62
|
+
default: '',
|
|
63
|
+
},
|
|
64
|
+
pageDescription: {
|
|
65
|
+
type: String,
|
|
66
|
+
default: '',
|
|
67
|
+
},
|
|
68
|
+
externalUrl: {
|
|
69
|
+
type: String,
|
|
70
|
+
default: '',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
computed: {
|
|
74
|
+
externalUrlLabel() {
|
|
75
|
+
const target = String(this.externalUrl || '').trim()
|
|
76
|
+
if (!target) return ''
|
|
77
|
+
return target.replace(/^https?:\/\//i, '').replace(/\/$/, '')
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
methods: {
|
|
81
|
+
openExternalUrl() {
|
|
82
|
+
const target = String(this.externalUrl || '').trim()
|
|
83
|
+
if (!target) return
|
|
84
|
+
window.open(target, '_blank', 'noopener,noreferrer')
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
</script>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<main class="flex h-screen w-full max-w-none flex-col overflow-hidden bg-slate-100">
|
|
3
|
+
<section class="shrink-0 bg-white px-4 pb-4 pb-0 pt-8 sm:px-6 lg:px-8">
|
|
4
|
+
<div class="mx-auto flex w-full max-w-none flex-col gap-5">
|
|
5
|
+
<div
|
|
6
|
+
v-if="pageTitle || pageDescription"
|
|
7
|
+
class="flex flex-wrap items-start justify-between gap-4"
|
|
8
|
+
>
|
|
9
|
+
<div class="min-w-0">
|
|
10
|
+
<p class="text-[10px] font-semibold uppercase tracking-[0.28em] text-slate-500">
|
|
11
|
+
{{ appTitle }}
|
|
12
|
+
</p>
|
|
13
|
+
<h1 class="mt-1 text-2xl font-semibold tracking-tight text-slate-900 sm:text-3xl">
|
|
14
|
+
{{ pageTitle }}
|
|
15
|
+
</h1>
|
|
16
|
+
<p v-if="pageDescription" class="mt-1 text-sm font-medium text-slate-500">
|
|
17
|
+
{{ pageDescription }}
|
|
18
|
+
</p>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
21
|
+
<slot name="header-actions" />
|
|
22
|
+
<kButton
|
|
23
|
+
v-if="externalUrl"
|
|
24
|
+
:label="externalUrlLabel"
|
|
25
|
+
icon="ExternalLink"
|
|
26
|
+
variant="soft"
|
|
27
|
+
@click="openExternalUrl"
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</section>
|
|
33
|
+
|
|
34
|
+
<div class="shrink-0 border-b border-t border-slate-200 bg-slate-50 px-4 backdrop-blur">
|
|
35
|
+
<div class="mx-auto flex w-full max-w-none items-center">
|
|
36
|
+
<slot name="header-extra" />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="min-h-0 flex-1 overflow-hidden bg-slate-100 py-4">
|
|
41
|
+
<div class="mx-auto flex h-full min-h-0 w-full max-w-none gap-2">
|
|
42
|
+
<aside class="ml-8 min-h-0 shrink-0 overflow-y-auto pr-4" :style="sidebarStyle">
|
|
43
|
+
<slot name="sidebar" />
|
|
44
|
+
</aside>
|
|
45
|
+
<section class="min-h-0 min-w-0 flex-1 overflow-y-auto px-2">
|
|
46
|
+
<slot name="content" />
|
|
47
|
+
</section>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</main>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<script>
|
|
54
|
+
import packageJson from '../../package.json'
|
|
55
|
+
import kButton from '../ui/kButton.vue'
|
|
56
|
+
|
|
57
|
+
export default {
|
|
58
|
+
name: 'PageTwoColShell',
|
|
59
|
+
components: { kButton },
|
|
60
|
+
props: {
|
|
61
|
+
appTitle: {
|
|
62
|
+
type: String,
|
|
63
|
+
default: packageJson.description || packageJson.name || 'App',
|
|
64
|
+
},
|
|
65
|
+
pageTitle: {
|
|
66
|
+
type: String,
|
|
67
|
+
default: '',
|
|
68
|
+
},
|
|
69
|
+
pageDescription: {
|
|
70
|
+
type: String,
|
|
71
|
+
default: '',
|
|
72
|
+
},
|
|
73
|
+
externalUrl: {
|
|
74
|
+
type: String,
|
|
75
|
+
default: '',
|
|
76
|
+
},
|
|
77
|
+
sidebarWidth: {
|
|
78
|
+
type: String,
|
|
79
|
+
default: '300px',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
computed: {
|
|
83
|
+
externalUrlLabel() {
|
|
84
|
+
const target = String(this.externalUrl || '').trim()
|
|
85
|
+
if (!target) return ''
|
|
86
|
+
return target.replace(/^https?:\/\//i, '').replace(/\/$/, '')
|
|
87
|
+
},
|
|
88
|
+
sidebarStyle() {
|
|
89
|
+
return {
|
|
90
|
+
minWidth: this.sidebarWidth,
|
|
91
|
+
maxWidth: this.sidebarWidth,
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
methods: {
|
|
96
|
+
openExternalUrl() {
|
|
97
|
+
const target = String(this.externalUrl || '').trim()
|
|
98
|
+
if (!target) return
|
|
99
|
+
window.open(target, '_blank', 'noopener,noreferrer')
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
</script>
|
package/src/ui/kForm.vue
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col gap-2">
|
|
3
|
+
<template v-for="(item, idx) in formInputs" :key="item.id ?? idx">
|
|
4
|
+
<component
|
|
5
|
+
:is="components[item.type]"
|
|
6
|
+
v-bind="item.props"
|
|
7
|
+
:label="item.label + (item.required ? '*' : '')"
|
|
8
|
+
:required="item.required"
|
|
9
|
+
:modelValue="formModel[item.id]"
|
|
10
|
+
:error="errors[item.id]?.message"
|
|
11
|
+
@update:modelValue="(val) => updateField(item.id, val)"
|
|
12
|
+
/>
|
|
13
|
+
</template>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script>
|
|
18
|
+
import kButton from './kButton.vue'
|
|
19
|
+
import kEditor from './kEditor.vue'
|
|
20
|
+
import kInput from './kInput.vue'
|
|
21
|
+
import kSelect from './kSelect.vue'
|
|
22
|
+
import kToggle from './kToggle.vue'
|
|
23
|
+
|
|
24
|
+
const componentsMap = { kInput, kToggle, kButton, kSelect, kEditor }
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
name: 'DynamicForm',
|
|
28
|
+
props: {
|
|
29
|
+
formInputs: { type: Array, default: () => [] },
|
|
30
|
+
formModel: { type: Object, default: () => ({}) },
|
|
31
|
+
},
|
|
32
|
+
emits: ['update:formModel', 'validated'],
|
|
33
|
+
data() {
|
|
34
|
+
return {
|
|
35
|
+
errors: {},
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
computed: {
|
|
39
|
+
components() {
|
|
40
|
+
return componentsMap
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
methods: {
|
|
44
|
+
validate() {
|
|
45
|
+
const errors = {}
|
|
46
|
+
|
|
47
|
+
for (const item of this.formInputs) {
|
|
48
|
+
const id = item.id
|
|
49
|
+
const val = this.formModel[id]
|
|
50
|
+
|
|
51
|
+
if (item.required) {
|
|
52
|
+
const empty =
|
|
53
|
+
val === null ||
|
|
54
|
+
val === undefined ||
|
|
55
|
+
(typeof val === 'string' && val.trim() === '') ||
|
|
56
|
+
(Array.isArray(val) && val.length === 0) ||
|
|
57
|
+
(typeof val === 'boolean' && val === false && item.type !== 'kToggle')
|
|
58
|
+
if (empty) {
|
|
59
|
+
errors[id] = { message: item.requiredMessage || 'Απαιτείται πεδίο.' }
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (item.pattern instanceof RegExp && typeof val === 'string' && !item.pattern.test(val)) {
|
|
65
|
+
errors[id] = { message: item.patternMessage || 'Μη έγκυρη τιμή.' }
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeof item.validator === 'function') {
|
|
70
|
+
const res = item.validator(val, this.formModel, item)
|
|
71
|
+
if (res !== true) {
|
|
72
|
+
errors[id] = { message: res || 'Μη έγκυρη τιμή.' }
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.errors = errors
|
|
79
|
+
const valid = Object.keys(errors).length === 0
|
|
80
|
+
this.$emit('validated', { valid, errors })
|
|
81
|
+
return { valid, errors }
|
|
82
|
+
},
|
|
83
|
+
updateField(key, value) {
|
|
84
|
+
this.$emit('update:formModel', { ...this.formModel, [key]: value })
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
</script>
|