ketekny-ui-kit 1.0.15 → 1.0.16

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.15",
4
+ "version": "1.0.16",
5
5
  "description": "A Vue 3 UI component library with Tailwind CSS styling",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -1,5 +1,5 @@
1
1
  <template lang="">
2
- <footer class="py-4 mt-auto text-gray-500 border-t no-print" :class="class">
2
+ <footer class="py-4 mt-auto text-primary/80 border-t border-primary/20 no-print" :class="class">
3
3
  <div class="container mx-auto" v-if="fullWidth == null">
4
4
  <slot></slot>
5
5
  </div>
@@ -1,5 +1,5 @@
1
1
  <template lang="">
2
- <main>
2
+ <main class="text-primary/90">
3
3
  <div :class="class">
4
4
  <div :class="fullWidth == null ? 'md:container md:mx-auto rounded-xl' : ''">
5
5
  <slot></slot>
@@ -1,5 +1,5 @@
1
1
  <template lang="">
2
- <section class="py-16 text-white border bg-dark rounded-xl">
2
+ <section class="py-16 text-white border rounded-xl bg-dark border-primary/30 shadow-[0_0_0_1px_rgb(34_197_94_/_0.12)]">
3
3
  <div class="container px-4 mx-auto text-center">
4
4
  <slot></slot>
5
5
  </div>
package/src/ui/kAlert.vue CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  <!-- Alert card -->
8
8
  <div
9
- class="relative w-full max-w-sm p-6 space-y-4 text-center bg-white shadow-2xl z-5010 rounded-2xl"
9
+ class="relative w-full max-w-sm p-6 space-y-4 text-center bg-white border shadow-2xl z-5010 rounded-2xl border-primary/20 ring-1 ring-primary/10"
10
10
  :class="['border-semantic-' + type + '-border']"
11
11
  style="z-index: 5010"
12
12
  >
@@ -1,7 +1,7 @@
1
1
  <!-- kInputList.vue -->
2
2
  <template>
3
3
  <div class="w-full">
4
- <label v-if="label" :for="id" class="block mb-1 text-sm font-medium text-gray-700">
4
+ <label v-if="label" :for="id" class="block mb-1 text-sm font-medium text-primary/90">
5
5
  {{ label }}
6
6
  </label>
7
7
 
@@ -13,7 +13,7 @@
13
13
  :disabled="disabled"
14
14
  :class="[
15
15
  'block w-full p-3 transition border border-gray-300 shadow-sm outline-none rounded-xl',
16
- disabled ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white focus:border-gray-400 focus:ring-2 focus:ring-gray-200',
16
+ disabled ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white focus:border-primary focus:ring-2 focus:ring-primary/20',
17
17
  ]"
18
18
  @focus="isFocused = true"
19
19
  @blur="isFocused = false"
@@ -22,7 +22,7 @@
22
22
  ></textarea>
23
23
 
24
24
  <div class="flex items-center justify-between mt-2 text-xs text-gray-500">
25
- <span>Items: <span class="font-semibold text-gray-700">{{ count }}</span></span>
25
+ <span>Items: <span class="font-semibold text-primary">{{ count }}</span></span>
26
26
  <span v-if="hint">{{ hint }}</span>
27
27
  </div>
28
28
  </div>
package/src/ui/kCode.vue CHANGED
@@ -1,6 +1,6 @@
1
1
  <!-- kCode.vue -->
2
2
  <template>
3
- <div class="p-4 mt-3 overflow-hidden text-white whitespace-pre bg-gray-900 border rounded-lg">
3
+ <div class="p-4 mt-3 overflow-hidden text-white whitespace-pre bg-gray-900 border rounded-lg border-primary/30">
4
4
  <div class="overflow-auto">
5
5
  <code>{{ normalizedContent }}</code>
6
6
  </div>
@@ -1,9 +1,9 @@
1
1
  <template>
2
2
  <Teleport to="body">
3
3
  <Transition name="fade">
4
- <div v-if="visible" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-40" style="z-index: 1500">
5
- <div class="w-full max-w-md p-6 bg-white rounded-lg shadow-xl">
6
- <h2 class="mb-2 text-lg font-semibold">{{ title }}</h2>
4
+ <div v-if="visible" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-40" style="z-index: 1500">
5
+ <div class="w-full max-w-md p-6 bg-white border rounded-lg shadow-xl border-primary/20">
6
+ <h2 class="mb-2 text-lg font-semibold text-primary">{{ title }}</h2>
7
7
  <div class="mb-6 text-gray-700"><span v-html="message" /></div>
8
8
  <div class="flex justify-end gap-3">
9
9
  <kButton :disabled="loading" secondary label="Άκυρο" @click="cancel" />
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div :class="disabled ? 'pointer-events-none opacity-60' : ''">
2
+ <div :class="['rounded-lg border border-primary/15', disabled ? 'pointer-events-none opacity-60' : '']">
3
3
  <EasyDataTable
4
4
  v-bind="$attrs"
5
5
  :headers="headers"
@@ -120,7 +120,7 @@ export default {
120
120
  --easy-table-body-row-height: 3.2rem;
121
121
  --easy-table-body-row-font-size: 12px;
122
122
  --easy-table-body-row-hover-font-color: #1f2937;
123
- --easy-table-body-row-hover-background-color: #f9fafb;
123
+ --easy-table-body-row-hover-background-color: #ecfdf3;
124
124
  --easy-table-body-item-padding: 0.5rem 0.75rem;
125
125
 
126
126
  --easy-table-footer-background-color: transparent;
@@ -137,7 +137,7 @@ export default {
137
137
 
138
138
  --easy-table-scrollbar-track-color: transparent;
139
139
  --easy-table-scrollbar-color: transparent;
140
- --easy-table-scrollbar-thumb-color: #d1d5db;
140
+ --easy-table-scrollbar-thumb-color: #86efac;
141
141
  --easy-table-scrollbar-corner-color: transparent;
142
142
 
143
143
  --easy-table-loading-mask-background-color: rgba(255, 255, 255, 0.6);
@@ -155,7 +155,7 @@ export default {
155
155
  background-color: #fff; /* white dropdown background */
156
156
  color: #1f2937; /* gray-800 text */
157
157
  padding: 0.25rem 0.5rem; /* small padding */
158
- border: 1px solid #d1d5db;
158
+ border: 1px solid #bbf7d0;
159
159
  border-radius: 0.25rem;
160
160
  }
161
161
 
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="relative w-full">
3
- <label :for="id" class="inputLabel" :class="hasError || inputInvalid ? 'text-red-500' : 'text-gray-700'">
3
+ <label :for="id" class="inputLabel" :class="hasError || inputInvalid ? 'text-red-500' : 'text-primary/90'">
4
4
  {{ label }}
5
5
  </label>
6
6
 
@@ -22,7 +22,7 @@
22
22
  <!-- Right-side calendar icon (opens popup) -->
23
23
  <button
24
24
  type="button"
25
- class="absolute p-1 text-gray-500 -translate-y-1/2 right-2 top-1/2 hover:text-black disabled:opacity-50"
25
+ class="absolute p-1 text-primary/70 -translate-y-1/2 right-2 top-1/2 hover:text-primary disabled:opacity-50"
26
26
  :disabled="disabled"
27
27
  @click="openCalendar"
28
28
  aria-label="Open calendar"
@@ -45,7 +45,7 @@
45
45
  <!-- Right-side calendar icon (opens month grid) -->
46
46
  <button
47
47
  type="button"
48
- class="absolute p-1 text-gray-500 -translate-y-1/2 right-2 top-1/2 hover:text-black disabled:opacity-50"
48
+ class="absolute p-1 text-primary/70 -translate-y-1/2 right-2 top-1/2 hover:text-primary disabled:opacity-50"
49
49
  :disabled="disabled"
50
50
  @click.stop="togglePopup"
51
51
  aria-label="Open month selector"
@@ -67,14 +67,14 @@
67
67
 
68
68
  <!-- Month Grid Popover (Teleported) -->
69
69
  <teleport to="body">
70
- <div v-if="showPopup" class="p-4 bg-white border border-gray-200 rounded-lg shadow-lg" :style="popupStyles">
70
+ <div v-if="showPopup" class="p-4 bg-white border rounded-lg shadow-lg border-primary/20" :style="popupStyles">
71
71
  <!-- Year Nav -->
72
72
  <div class="flex items-center justify-between pb-3 mb-4 border-b">
73
- <button @click.stop="currentYear--" class="text-gray-600 hover:text-black" aria-label="Previous year">
73
+ <button @click.stop="currentYear--" class="text-primary/80 hover:text-primary" aria-label="Previous year">
74
74
  <ChevronLeft />
75
75
  </button>
76
- <span class="font-medium text-gray-700">{{ currentYear }}</span>
77
- <button @click.stop="currentYear++" class="text-gray-600 hover:text-black" aria-label="Next year">
76
+ <span class="font-medium text-primary">{{ currentYear }}</span>
77
+ <button @click.stop="currentYear++" class="text-primary/80 hover:text-primary" aria-label="Next year">
78
78
  <ChevronRight />
79
79
  </button>
80
80
  </div>
@@ -86,7 +86,7 @@
86
86
  :key="index"
87
87
  @click="selectMonth(index + 1)"
88
88
  class="px-3 py-2 text-center transition rounded-lg cursor-pointer"
89
- :class="[isSelected(index + 1) ? 'bg-blue-600 text-white' : 'hover:bg-gray-100 text-gray-700']"
89
+ :class="[isSelected(index + 1) ? 'bg-primary text-white' : 'hover:bg-primary/10 text-gray-700']"
90
90
  >
91
91
  {{ month }}
92
92
  </div>
@@ -104,7 +104,7 @@
104
104
  <div class="text-sm text-red-500" v-if="inputInvalid && !hasError">Μη έγκυρη ημερομηνία. Χρησιμοποιήστε μορφή dd/mm/yyyy.</div>
105
105
 
106
106
  <!-- Info message -->
107
- <div class="text-sm text-gray-500" v-if="info != null">
107
+ <div class="text-sm text-primary/80" v-if="info != null">
108
108
  {{ info }}
109
109
  </div>
110
110
  </div>
@@ -148,7 +148,7 @@ export default {
148
148
  })();
149
149
 
150
150
  return {
151
- defaultStyle: "w-full px-3 py-2 border rounded-lg transition shadow-sm focus:outline-none text-gray-700 focus:ring-0 focus:border-green-500 bg-white placeholder-gray-400",
151
+ defaultStyle: "w-full px-3 py-2 border rounded-lg transition shadow-sm focus:outline-none text-gray-700 focus:ring-2 focus:ring-primary/20 focus:border-primary bg-white placeholder-gray-400",
152
152
  errorStyle: "border-red-500 focus:ring focus:ring-red-300",
153
153
  disabledStyle: "!bg-gray-100 !text-gray-400 !cursor-not-allowed",
154
154
 
@@ -1,10 +1,10 @@
1
1
  <template>
2
- <div class="inputLabel" :class="hasError ? 'text-red-500' : 'text-gray-700'">
2
+ <div class="inputLabel" :class="hasError ? 'text-red-500' : 'text-primary/90'">
3
3
  {{ label }}
4
4
  </div>
5
5
  <div class="w-full">
6
6
  <!-- Toolbar -->
7
- <div class="flex items-center gap-2 px-2 py-1 border rounded-t-md bg-gray-50">
7
+ <div class="flex items-center gap-2 px-2 py-1 border rounded-t-md bg-primary/5 border-primary/20">
8
8
  <button
9
9
  :disabled="disabled"
10
10
  v-for="(cmd, i) in toolbar"
@@ -12,7 +12,7 @@
12
12
  @click.prevent="exec(cmd.command, cmd.value)"
13
13
  :title="cmd.title"
14
14
  class="text-gray-600 p-1.5 rounded"
15
- :class="disabled == false ? 'hover:text-black hover:bg-gray-200' : ''"
15
+ :class="disabled == false ? 'hover:text-primary hover:bg-primary/10' : ''"
16
16
  >
17
17
  <component :is="cmd.icon" class="w-5 h-5" />
18
18
  </button>
@@ -32,7 +32,7 @@
32
32
  </div>
33
33
 
34
34
  <!-- Info message -->
35
- <div class="text-sm text-gray-500" v-if="info != null">
35
+ <div class="text-sm text-primary/80" v-if="info != null">
36
36
  {{ info }}
37
37
  </div>
38
38
  </div>
@@ -65,7 +65,7 @@ export default {
65
65
  data() {
66
66
  return {
67
67
  defaultStyle:
68
- "w-full px-3 py-2 border rounded-b-lg transition shadow-sm focus:outline-none focus:ring-0 min-h-[150px] focus:border-green-500 bg-white placeholder-gray-400 !list-disc !list-inside prose",
68
+ "w-full px-3 py-2 border rounded-b-lg transition shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/20 min-h-[150px] focus:border-primary bg-white placeholder-gray-400 !list-disc !list-inside prose",
69
69
  errorStyle: "border-red-500 focus:ring focus:ring-red-300' : 'border-gray-300 focus:ring focus:ring-blue-300",
70
70
  disabledStyle: "!bg-gray-100 !text-gray-400 !cursor-not-allowed editor pointer-events-none select-none",
71
71
  toolbar: [
package/src/ui/kIcon.vue CHANGED
@@ -3,7 +3,7 @@
3
3
  <component
4
4
  :is="iconComponent"
5
5
  v-bind="iconProps"
6
- class="inline-block"
6
+ class="inline-block text-primary"
7
7
  />
8
8
  </template>
9
9
 
package/src/ui/kInput.vue CHANGED
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="w-full">
2
+ <div class="w-full text-primary/90">
3
3
  <label v-if="label != null" :for="inputId" class="inputLabel" :class="hasError ? theme.labelError : theme.label">
4
4
  {{ label }}
5
5
  </label>
package/src/ui/kMenu.vue CHANGED
@@ -1,51 +1,209 @@
1
1
  <template>
2
- <div class="relative inline-block text-left">
3
- <div @click="toggleMenu">
4
- <slot name="trigger"></slot>
5
- </div>
2
+ <div ref="menuRef" class="relative inline-block text-left">
6
3
  <div
7
- v-if="isOpen"
8
- class="absolute right-0 z-10 w-56 mt-2 bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5"
9
- >
10
- <slot name="items"></slot>
4
+ ref="triggerRef"
5
+ role="button"
6
+ tabindex="0"
7
+ :aria-expanded="isOpen.toString()"
8
+ :aria-haspopup="'menu'"
9
+ :aria-label="ariaLabel"
10
+ class="rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30"
11
+ @click="toggleMenu"
12
+ @keydown.enter.prevent="toggleMenu"
13
+ @keydown.space.prevent="toggleMenu">
14
+ <slot name="trigger">
15
+ <button
16
+ type="button"
17
+ class="inline-flex items-center justify-center px-3 py-2 text-base font-medium text-center transition border rounded-lg shadow-sm bg-white border-slate-200 text-slate-700 hover:bg-slate-50">
18
+ Menu
19
+ </button>
20
+ </slot>
11
21
  </div>
22
+
23
+ <Teleport to="body">
24
+ <div
25
+ v-if="isOpen"
26
+ ref="menuPanelRef"
27
+ class="z-[9999] overflow-visible border rounded-lg shadow-lg bg-white/95 backdrop-blur-sm border-primary/20 ring-1 ring-primary/10"
28
+ :style="panelStyle"
29
+ role="menu">
30
+ <span
31
+ aria-hidden="true"
32
+ class="absolute w-3 h-3 -translate-x-1/2 rotate-45 bg-white"
33
+ :class="[
34
+ placement === 'top'
35
+ ? 'bottom-[-7px] border-r border-b border-primary/20'
36
+ : 'top-[-7px] border-l border-t border-primary/20',
37
+ ]"
38
+ :style="{ left: `${chevronOffset}px` }"></span>
39
+ <slot name="items">
40
+ <ul class="p-2">
41
+ <li v-for="(link, index) in normalizedLinks" :key="link.key || `${link.label}-${index}`">
42
+ <component
43
+ :is="resolveLinkTag(link)"
44
+ class="flex items-center w-full gap-2 px-3 py-2 min-h-11 text-base rounded-md text-slate-700"
45
+ :class="[
46
+ link.disabled
47
+ ? 'cursor-not-allowed opacity-50'
48
+ : 'cursor-pointer hover:bg-primary/10 hover:text-primary active:bg-primary/15',
49
+ ]"
50
+ :to="isRouterLink(link) ? link.to : undefined"
51
+ :href="!isRouterLink(link) && link.href ? link.href : undefined"
52
+ :target="!isRouterLink(link) && link.target ? link.target : undefined"
53
+ :rel="!isRouterLink(link) ? resolvedRel(link) : undefined"
54
+ :disabled="resolveLinkTag(link) === 'button' ? !!link.disabled : undefined"
55
+ role="menuitem"
56
+ @click="handleLinkClick(link, $event)">
57
+ <kIcon v-if="link.icon" :name="link.icon" :size="16" class="shrink-0 self-center text-primary/80" />
58
+ <span class="leading-none">{{ link.label }}</span>
59
+ </component>
60
+ </li>
61
+ </ul>
62
+ </slot>
63
+ </div>
64
+ </Teleport>
12
65
  </div>
13
66
  </template>
14
67
 
15
68
  <script>
69
+ import kIcon from "./kIcon.vue";
70
+
16
71
  export default {
17
- name: 'kMenu',
72
+ name: "kMenu",
73
+ components: {
74
+ kIcon,
75
+ },
76
+ props: {
77
+ links: {
78
+ type: Array,
79
+ default: () => [],
80
+ },
81
+ closeOnSelect: {
82
+ type: Boolean,
83
+ default: true,
84
+ },
85
+ ariaLabel: {
86
+ type: String,
87
+ default: "Toggle menu",
88
+ },
89
+ },
90
+ emits: ["select"],
18
91
  data() {
19
92
  return {
20
93
  isOpen: false,
21
- }
94
+ placement: "bottom",
95
+ chevronOffset: 24,
96
+ panelStyle: {
97
+ position: "absolute",
98
+ top: "0px",
99
+ left: "0px",
100
+ width: "224px",
101
+ },
102
+ };
22
103
  },
23
- methods: {
24
- toggleMenu() {
25
- this.isOpen = !this.isOpen
104
+ computed: {
105
+ normalizedLinks() {
106
+ return this.links.filter((link) => link && typeof link === "object" && typeof link.label === "string" && link.label.length > 0);
26
107
  },
27
- closeMenu() {
28
- this.isOpen = false
108
+ hasRouterLink() {
109
+ return Boolean(this.$?.appContext?.components?.RouterLink || this.$router);
29
110
  },
30
111
  },
31
- mounted() {
32
- document.addEventListener('click', this.handleClickOutside)
33
- },
34
- beforeUnmount() {
35
- document.removeEventListener('click', this.handleClickOutside)
36
- },
37
112
  methods: {
38
113
  toggleMenu() {
39
- this.isOpen = !this.isOpen
114
+ this.isOpen = !this.isOpen;
115
+ if (this.isOpen) this.$nextTick(() => this.updatePanelPosition());
116
+ },
117
+ isRouterLink(link) {
118
+ return this.hasRouterLink && link && link.to !== undefined;
119
+ },
120
+ resolveLinkTag(link) {
121
+ if (this.isRouterLink(link)) return "RouterLink";
122
+ if (link && link.href) return "a";
123
+ return "button";
124
+ },
125
+ resolvedRel(link) {
126
+ if (!link?.target || link.target !== "_blank") return link?.rel || undefined;
127
+ return link?.rel || "noopener noreferrer";
128
+ },
129
+ handleLinkClick(link, event) {
130
+ if (link?.disabled) {
131
+ event.preventDefault();
132
+ return;
133
+ }
134
+ if (typeof link?.action === "function") link.action(link, event);
135
+ if (typeof link?.onClick === "function") link.onClick(link, event);
136
+ this.$emit("select", link);
137
+ if (this.closeOnSelect) this.closeMenu();
40
138
  },
41
139
  handleClickOutside(event) {
42
- if (!this.$el.contains(event.target)) {
43
- this.closeMenu()
140
+ const clickedTrigger = this.$refs.menuRef?.contains(event.target);
141
+ const clickedPanel = this.$refs.menuPanelRef?.contains(event.target);
142
+ if (!clickedTrigger && !clickedPanel) {
143
+ this.closeMenu();
44
144
  }
45
145
  },
146
+ handleEscapeKey(event) {
147
+ if (event.key === "Escape") this.closeMenu();
148
+ },
149
+ updatePanelPosition() {
150
+ const trigger = this.$refs.triggerRef;
151
+ if (!trigger) return;
152
+
153
+ const rect = trigger.getBoundingClientRect();
154
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
155
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
156
+ const panelWidth = this.$refs.menuPanelRef?.offsetWidth || 224;
157
+ const viewportWidth = window.innerWidth;
158
+ const viewportHeight = window.innerHeight;
159
+ const pad = 8;
160
+ const gap = 8;
161
+ const panelHeight = this.$refs.menuPanelRef?.offsetHeight || 0;
162
+
163
+ // bottom-end (default): align panel right edge with trigger right edge, below trigger.
164
+ const preferredLeft = rect.right + scrollLeft - panelWidth;
165
+ const minLeft = scrollLeft + pad;
166
+ const maxLeft = scrollLeft + viewportWidth - panelWidth - pad;
167
+ const left = Math.max(minLeft, Math.min(preferredLeft, maxLeft));
168
+
169
+ const bottomTop = rect.bottom + scrollTop + gap;
170
+ const topTop = rect.top + scrollTop - panelHeight - gap;
171
+ const maxVisibleTop = scrollTop + viewportHeight - panelHeight - pad;
172
+ const minVisibleTop = scrollTop + pad;
173
+
174
+ // Flip to top-end only when bottom placement overflows and top has room.
175
+ const shouldFlipTop = bottomTop > maxVisibleTop && topTop >= minVisibleTop;
176
+ const top = shouldFlipTop
177
+ ? topTop
178
+ : Math.max(minVisibleTop, Math.min(bottomTop, maxVisibleTop));
179
+ this.placement = shouldFlipTop ? "top" : "bottom";
180
+
181
+ const triggerCenterX = rect.left + scrollLeft + rect.width / 2;
182
+ const relativeChevronX = triggerCenterX - left;
183
+ this.chevronOffset = Math.max(14, Math.min(relativeChevronX, panelWidth - 14));
184
+
185
+ this.panelStyle = {
186
+ position: "absolute",
187
+ top: `${top}px`,
188
+ left: `${left}px`,
189
+ width: `${panelWidth}px`,
190
+ };
191
+ },
46
192
  closeMenu() {
47
- this.isOpen = false
193
+ this.isOpen = false;
48
194
  },
49
195
  },
196
+ mounted() {
197
+ document.addEventListener("click", this.handleClickOutside);
198
+ document.addEventListener("keydown", this.handleEscapeKey);
199
+ window.addEventListener("resize", this.updatePanelPosition);
200
+ window.addEventListener("scroll", this.updatePanelPosition, true);
201
+ },
202
+ beforeUnmount() {
203
+ document.removeEventListener("click", this.handleClickOutside);
204
+ document.removeEventListener("keydown", this.handleEscapeKey);
205
+ window.removeEventListener("resize", this.updatePanelPosition);
206
+ window.removeEventListener("scroll", this.updatePanelPosition, true);
207
+ },
50
208
  }
51
209
  </script>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div
3
- class="flex rounded-lg overflow-hidden min-h-[50px] border-l-8"
3
+ class="flex rounded-lg overflow-hidden min-h-[50px] border-l-8 ring-1 ring-primary/10"
4
4
  :class="['border-semantic-' + type + '-border', 'bg-semantic-' + type + '-bg']"
5
5
  >
6
6
  <!-- Icon Column -->
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="relative w-full">
3
3
  <input type="text" v-model="localValue" @input="onInput" :class="[defaultStyle, disabled ? disabledStyle : '']" :placeholder="placeholder" :disabled="disabled" />
4
- <Search class="absolute w-4 h-4 text-gray-400 -translate-y-1/2 pointer-events-none right-3 top-1/2" />
4
+ <Search class="absolute w-4 h-4 text-primary/70 -translate-y-1/2 pointer-events-none right-3 top-1/2" />
5
5
  </div>
6
6
  </template>
7
7
 
@@ -20,7 +20,7 @@ export default {
20
20
  },
21
21
  data() {
22
22
  return {
23
- defaultStyle: "w-full px-3 py-2 border rounded-lg transition shadow-sm focus:outline-none text-gray-700 focus:ring-0 focus:border-green-500 bg-white placeholder-gray-400",
23
+ defaultStyle: "w-full px-3 py-2 border rounded-lg transition shadow-sm focus:outline-none text-gray-700 focus:ring-2 focus:ring-primary/20 focus:border-primary bg-white placeholder-gray-400",
24
24
  disabledStyle: "bg-gray-100 text-gray-400 cursor-not-allowed",
25
25
 
26
26
  localValue: this.modelValue,
@@ -123,7 +123,7 @@ export default {
123
123
  searchQuery: "",
124
124
  dropdownPositionStyle: {},
125
125
  generatedId: `select-${Math.random().toString(36).substr(2, 9)}`,
126
- defaultStyle: "w-full px-3 py-2 border rounded-lg transition shadow-sm focus:outline-none text-gray-700 focus:ring-0 focus:border-green-500 bg-white placeholder-gray-400",
126
+ defaultStyle: "w-full px-3 py-2 border rounded-lg transition shadow-sm focus:outline-none text-gray-700 focus:ring-2 focus:ring-primary/20 focus:border-primary bg-white placeholder-gray-400",
127
127
  errorStyle: "border-red-500 focus:ring focus:ring-red-300",
128
128
  disabledStyle: "!bg-gray-100 !text-gray-400 !cursor-not-allowed",
129
129
  };
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="inline-flex overflow-hidden border border-gray-300 rounded-md" style="width: fit-content;" :aria-disabled="disabled.toString()">
2
+ <div class="inline-flex overflow-hidden border rounded-md border-primary/25" style="width: fit-content;" :aria-disabled="disabled.toString()">
3
3
 
4
4
  <button
5
5
  v-for="(option, index) in options"
@@ -34,13 +34,13 @@ export default {
34
34
  emits: ["update:modelValue"],
35
35
  computed: {
36
36
  baseClasses() {
37
- return "px-4 py-2 text-sm font-medium border-none focus:outline-none";
37
+ return "px-4 py-2 text-sm font-medium border-none focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 focus-visible:ring-inset";
38
38
  },
39
39
  activeClasses() {
40
40
  return "bg-primary text-white";
41
41
  },
42
42
  inactiveClasses() {
43
- return this.disabled ? "bg-white text-gray-500" : "bg-white text-gray-700 hover:bg-gray-100";
43
+ return this.disabled ? "bg-white text-gray-500" : "bg-white text-primary/90 hover:bg-primary/10";
44
44
  },
45
45
  },
46
46
  methods: {
@@ -1,7 +1,7 @@
1
1
  <template lang="">
2
2
  <template v-if="type === 'text'">
3
3
  <div class="space-y-2 animate-pulse">
4
- <div v-for="n in normalizedRows" :key="n" class="w-full h-4 bg-gray-300 rounded"></div>
4
+ <div v-for="n in normalizedRows" :key="n" class="w-full h-4 rounded bg-primary/20"></div>
5
5
  </div>
6
6
  </template>
7
7
  </template>
@@ -1,10 +1,10 @@
1
1
  <template>
2
2
  <div class="flex items-center justify-center w-16 space-x-1" v-if="type == 'line'">
3
- <i class="ml-1 text-blue-500 pi pi-wave-pulse" />
4
- <div class="w-2 h-2 bg-blue-500 rounded-full animate-fade" :style="{ animationDelay: '0s' }"></div>
5
- <div class="w-2 h-2 bg-blue-500 rounded-full animate-fade" :style="{ animationDelay: '0.15s' }"></div>
6
- <div class="w-2 h-2 bg-blue-500 rounded-full animate-fade" :style="{ animationDelay: '0.3s' }"></div>
7
- <div class="w-2 h-2 bg-blue-500 rounded-full animate-fade" :style="{ animationDelay: '0.45s' }"></div>
3
+ <i class="ml-1 text-primary pi pi-wave-pulse" />
4
+ <div class="w-2 h-2 rounded-full bg-primary animate-fade" :style="{ animationDelay: '0s' }"></div>
5
+ <div class="w-2 h-2 rounded-full bg-primary animate-fade" :style="{ animationDelay: '0.15s' }"></div>
6
+ <div class="w-2 h-2 rounded-full bg-primary animate-fade" :style="{ animationDelay: '0.3s' }"></div>
7
+ <div class="w-2 h-2 rounded-full bg-primary animate-fade" :style="{ animationDelay: '0.45s' }"></div>
8
8
  </div>
9
9
 
10
10
  <Loader class="w-6 h-6 mr-2 animate-spin" style="animation-duration: 1.5s" v-else-if="type == 'circle'" />
package/src/ui/kTags.vue CHANGED
@@ -1,20 +1,20 @@
1
1
  <template>
2
2
  <div class="w-full">
3
- <div v-if="label != null" class="inputLabel" :class="hasError ? 'text-rose-800' : 'text-gray-700'">
3
+ <div v-if="label != null" class="inputLabel" :class="hasError ? 'text-rose-800' : 'text-primary/90'">
4
4
  {{ label }}
5
5
  </div>
6
6
  <div
7
7
  :class="[
8
8
  'flex flex-wrap items-center gap-2 p-2 border rounded-xl shadow-sm min-h-[3rem] transition',
9
- hasError ? 'border-rose-500 bg-rose-50/40 focus-within:border-rose-600 focus-within:ring-2 focus-within:ring-rose-500/20' : 'border-gray-300',
9
+ hasError ? 'border-rose-500 bg-rose-50/40 focus-within:border-rose-600 focus-within:ring-2 focus-within:ring-rose-500/20' : 'border-gray-300 focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/20',
10
10
  disabled ? 'bg-gray-100 cursor-not-allowed' : 'bg-white',
11
11
  ]">
12
- <span v-for="(tag, index) in internalTags" :key="index" class="flex items-center px-2 py-1 text-blue-700 bg-blue-100 rounded-full">
12
+ <span v-for="(tag, index) in internalTags" :key="index" class="flex items-center px-2 py-1 rounded-full text-primary bg-primary/10">
13
13
  {{ tag }}
14
14
  <button
15
15
  @click="removeTag(index)"
16
16
  :disabled="disabled"
17
- :class="['ml-1', disabled ? 'text-blue-300 cursor-not-allowed' : 'text-blue-500 hover:text-blue-700']">
17
+ :class="['ml-1', disabled ? 'text-primary/30 cursor-not-allowed' : 'text-primary/70 hover:text-primary']">
18
18
  &times;
19
19
  </button>
20
20
  </span>
@@ -4,7 +4,7 @@
4
4
  v-if="label != null"
5
5
  :for="inputId"
6
6
  class="inputLabel"
7
- :class="hasError ? 'text-red-500' : 'text-gray-700'"
7
+ :class="hasError ? 'text-red-500' : 'text-primary/90'"
8
8
  >
9
9
  {{ label }}
10
10
  </label>
@@ -26,7 +26,7 @@
26
26
  {{ error }}
27
27
  </div>
28
28
 
29
- <div :id="infoId" class="mt-1 text-gray-500" v-if="info != null">
29
+ <div :id="infoId" class="mt-1 text-primary/80" v-if="info != null">
30
30
  {{ info }}
31
31
  </div>
32
32
  </div>
@@ -74,7 +74,7 @@ export default {
74
74
  },
75
75
  textareaClass() {
76
76
  return [
77
- 'w-full px-3 py-2 border rounded-lg transition shadow-sm focus:outline-none text-gray-700 focus:ring-0 focus:border-green-500 bg-white placeholder-gray-400',
77
+ 'w-full px-3 py-2 border rounded-lg transition shadow-sm focus:outline-none text-gray-700 focus:ring-2 focus:ring-primary/20 focus:border-primary bg-white placeholder-gray-400',
78
78
  this.hasError ? 'border-red-500 focus:ring focus:ring-red-300' : '',
79
79
  this.disabled ? '!bg-gray-100 !text-gray-400 !cursor-not-allowed' : '',
80
80
  ]
package/src/ui/kToast.vue CHANGED
@@ -24,7 +24,7 @@
24
24
  <div v-if="toast.actionLabel && typeof toast.onAction === 'function'" class="mt-2">
25
25
  <button
26
26
  type="button"
27
- class="px-2.5 py-1.5 text-xs font-semibold rounded-md ring-1 ring-black/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-slate-900"
27
+ class="px-2.5 py-1.5 text-xs font-semibold rounded-md ring-1 ring-primary/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary"
28
28
  :class="'text-semantic-' + toast.type + '-text'"
29
29
  @click="handleAction(toast)">
30
30
  {{ toast.actionLabel }}
@@ -38,7 +38,7 @@
38
38
  v-if="toast.closable !== false"
39
39
  type="button"
40
40
  @click="remove(toast.id)"
41
- class="absolute text-xl leading-none text-gray-500 rounded-md top-2 right-2 hover:text-black focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-slate-900"
41
+ class="absolute text-xl leading-none text-gray-500 rounded-md top-2 right-2 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary"
42
42
  :aria-label="`Close ${toast.type} toast`">
43
43
  <X class="w-5 h-5" />
44
44
  </button>
@@ -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-gray-700" for="toggle">
4
+ <div v-if="showLabel && labelStyle !== 'inline'" class="block mb-1 text-sm font-bold text-primary/90" for="toggle">
5
5
  {{ label }}
6
6
  </div>
7
7
 
@@ -14,14 +14,14 @@
14
14
  :disabled="disabled"
15
15
  :class="[
16
16
  'w-16 h-8 flex items-center rounded-full p-1 transition duration-300',
17
- modelValue ? 'bg-green-500' : 'bg-gray-400',
17
+ modelValue ? 'bg-primary' : 'bg-gray-400',
18
18
  disabled ? 'opacity-50 cursor-not-allowed' : '',
19
19
  ]">
20
20
  <div :class="['w-6 h-6 bg-white rounded-full shadow-md transform transition duration-300', modelValue ? 'translate-x-8' : 'translate-x-0']"></div>
21
21
  </button>
22
22
 
23
23
  <!-- Inline-style label -->
24
- <div v-if="showLabel && labelStyle === 'inline'" class="ml-2 text-sm font-bold text-gray-700 cursor-pointer" :for="computedId" @click="toggle">
24
+ <div v-if="showLabel && labelStyle === 'inline'" class="ml-2 text-sm font-bold cursor-pointer text-primary/90" :for="computedId" @click="toggle">
25
25
  {{ label }}
26
26
  </div>
27
27
  </div>
@@ -7,13 +7,13 @@
7
7
  :class="[
8
8
  'k-toolbar relative w-full',
9
9
  dense ? 'p-0 md:p-0' : 'px-3 md:px-4 py-2.5',
10
- variant === 'plain' ? 'bg-transparent border-0 rounded-none shadow-none px-0 md:px-0' : 'border border-slate-200 rounded-lg bg-white shadow-sm',
10
+ variant === 'plain' ? 'bg-transparent border-0 rounded-none shadow-none px-0 md:px-0' : 'border rounded-lg bg-white shadow-sm border-primary/20',
11
11
  disabled ? 'pointer-events-none opacity-60' : '',
12
12
  ]">
13
13
  <div class="flex flex-wrap items-center gap-2 md:gap-3">
14
14
  <div :class="['flex items-center min-w-0 gap-2 shrink-0', isCompact ? 'w-full' : 'w-auto']">
15
15
  <slot name="leading" />
16
- <h1 v-if="title" class="text-base font-semibold leading-6 text-slate-900 truncate">{{ title }}</h1>
16
+ <h1 v-if="title" class="text-base font-semibold leading-6 truncate text-primary">{{ title }}</h1>
17
17
  </div>
18
18
 
19
19
  <div :class="['flex items-center min-w-0', isCompact ? 'w-full' : 'w-auto flex-1 min-w-[180px]']">
@@ -29,7 +29,7 @@
29
29
  ref="menuTriggerRef"
30
30
  type="button"
31
31
  v-show="isCompact"
32
- class="inline-flex items-center justify-center p-2 ml-auto border rounded-md border-slate-300 text-slate-700 hover:bg-slate-50"
32
+ class="inline-flex items-center justify-center p-2 ml-auto border rounded-md border-primary/30 text-slate-700 hover:bg-primary/10"
33
33
  :aria-label="mobileMenuOpen ? 'Close toolbar actions menu' : 'Open toolbar actions menu'"
34
34
  :aria-expanded="mobileMenuOpen.toString()"
35
35
  @click="toggleMobileMenu">
@@ -46,7 +46,8 @@
46
46
  :disabled="disabled || !!action.disabled"
47
47
  :aria-label="action.ariaLabel || action.label"
48
48
  :tooltip="action.tooltip"
49
- :small="action.small !== false"
49
+ :size="resolveActionButtonSize(action)"
50
+ :small="action.small === true"
50
51
  :icon-only="!!action.iconOnly"
51
52
  :variant="action.variant || 'outlined'"
52
53
  :secondary="!!action.secondary"
@@ -68,11 +69,11 @@
68
69
  role="dialog"
69
70
  aria-modal="true"
70
71
  aria-label="Toolbar actions"
71
- class="absolute bottom-0 left-0 right-0 p-3 border-t shadow-2xl rounded-t-2xl bg-white/95 backdrop-blur-sm border-slate-200"
72
+ class="absolute bottom-0 left-0 right-0 p-3 border-t shadow-2xl rounded-t-2xl bg-white/95 backdrop-blur-sm border-primary/20"
72
73
  style="padding-bottom: calc(0.75rem + env(safe-area-inset-bottom));">
73
- <div class="flex items-center justify-between pb-2 mb-2 border-b border-slate-200">
74
- <h3 class="text-sm font-semibold text-slate-900">Actions</h3>
75
- <button type="button" class="p-2 rounded-md hover:bg-slate-100" aria-label="Close actions menu" @click="closeMobileMenu()">
74
+ <div class="flex items-center justify-between pb-2 mb-2 border-b border-primary/20">
75
+ <h3 class="text-sm font-semibold text-primary">Actions</h3>
76
+ <button type="button" class="p-2 rounded-md hover:bg-primary/10" aria-label="Close actions menu" @click="closeMobileMenu()">
76
77
  <kIcon name="X" :size="18" />
77
78
  </button>
78
79
  </div>
@@ -85,7 +86,7 @@
85
86
  :disabled="disabled || !!action.disabled || !!action.loading"
86
87
  :class="[
87
88
  'flex items-center w-full gap-3 px-2 py-3 text-left rounded-lg min-h-11',
88
- disabled || action.disabled || action.loading ? 'opacity-50 cursor-not-allowed' : 'hover:bg-slate-100 active:bg-slate-200',
89
+ disabled || action.disabled || action.loading ? 'opacity-50 cursor-not-allowed' : 'hover:bg-primary/10 active:bg-primary/15',
89
90
  ]"
90
91
  @click="handleMobileAction(action)">
91
92
  <kIcon v-if="action.icon" :name="action.icon" :size="18" class="shrink-0 text-slate-600" />
@@ -138,6 +139,8 @@ const menuTriggerRef = ref(null);
138
139
  const isCompact = ref(false);
139
140
  let resizeObserver = null;
140
141
 
142
+ const VALID_BUTTON_SIZES = ["small", "normal", "large"];
143
+
141
144
  const handleActionClick = (action) => {
142
145
  if (typeof action?.onClick === "function") action.onClick();
143
146
  };
@@ -147,6 +150,13 @@ const handleMobileAction = (action) => {
147
150
  closeMobileMenu();
148
151
  };
149
152
 
153
+ const resolveActionButtonSize = (action) => {
154
+ if (VALID_BUTTON_SIZES.includes(action?.size)) return action.size;
155
+ // Legacy support: `small: true` maps to the new `size="small"`.
156
+ if (action?.small === true) return "small";
157
+ return "normal";
158
+ };
159
+
150
160
  const updateCompactMode = () => {
151
161
  if (!toolbarRef.value) return;
152
162
  isCompact.value = toolbarRef.value.clientWidth < 768;
package/src/ui/kTree.vue CHANGED
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="k-tree" :class="disabled ? 'k-tree--disabled' : ''">
2
+ <div class="k-tree text-primary/90" :class="disabled ? 'k-tree--disabled' : ''">
3
3
  <HETree
4
4
  ref="treeRef"
5
5
  class="k-tree__root"
@@ -212,8 +212,8 @@ export default {
212
212
  }
213
213
 
214
214
  .k-tree-node-content--selected {
215
- background: #e0ecff;
216
- color: #1d4ed8;
215
+ background: #ecfdf3;
216
+ color: #15803d;
217
217
  }
218
218
 
219
219
  .k-tree-node-toggler {
@@ -7,16 +7,16 @@
7
7
  @drop.prevent="onDrop"
8
8
  :class="[
9
9
  'border-2 border-dashed rounded-lg p-8 text-center transition-colors mb-8',
10
- dragover ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-gray-400',
10
+ dragover ? 'border-primary bg-primary/10' : 'border-gray-300 hover:border-primary/50',
11
11
  ]"
12
12
  >
13
13
  <div class="flex flex-col items-center justify-center space-y-4">
14
- <UploadCloudIcon class="w-12 h-12 text-gray-400" />
14
+ <UploadCloudIcon class="w-12 h-12 text-primary/60" />
15
15
  <p class="text-lg font-medium text-gray-700">
16
16
  Σύρετε το αρχείο εδώ, ή
17
17
  <label
18
18
  for="file-input"
19
- class="underline cursor-pointer text-dark hover:text-blue-600 focus:outline-none"
19
+ class="underline cursor-pointer text-primary hover:text-primary/80 focus:outline-none"
20
20
  >
21
21
  αναζητήστε
22
22
  </label>
@@ -45,7 +45,7 @@
45
45
  class="flex items-center justify-between p-4 bg-white rounded-lg shadow"
46
46
  >
47
47
  <div class="flex items-center space-x-4">
48
- <FileIcon class="w-8 h-8 text-gray-400" />
48
+ <FileIcon class="w-8 h-8 text-primary/60" />
49
49
  <div>
50
50
  <p class="text-sm font-medium text-gray-700">{{ file.fileName }}</p>
51
51
  <p class="text-xs text-gray-500">{{ formatFileSize(file.fileSize) }}</p>
@@ -1,16 +1,16 @@
1
1
  export const K_INPUT_THEME = {
2
- label: "text-gray-700",
2
+ label: "text-primary/90",
3
3
  labelError: "text-rose-800",
4
4
  baseInput:
5
- "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-sky-700/30 focus:border-sky-700",
5
+ "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
6
  withRightAdornment: "pr-10",
7
7
  withPasswordToggle: "pr-14",
8
8
  disabled: "bg-slate-100 text-slate-500 cursor-not-allowed border-slate-300",
9
9
  errorInput: "border-rose-500 bg-rose-50/40 focus:border-rose-600 focus:ring-rose-500/20",
10
10
  infoText: "text-sm text-slate-600",
11
11
  errorText: "text-sm text-rose-700",
12
- trailingIcon: "absolute w-4 h-4 text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2",
12
+ trailingIcon: "absolute w-4 h-4 text-primary/70 -translate-y-1/2 pointer-events-none right-3 top-1/2",
13
13
  passwordToggle: "absolute inset-y-0 right-0 flex items-center pr-3 text-slate-700",
14
14
  passwordToggleButton:
15
- "text-xs font-medium select-none rounded px-1 py-0.5 hover:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-900",
15
+ "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",
16
16
  };