ketekny-ui-kit 1.0.59 → 1.0.61

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/README.md CHANGED
@@ -155,7 +155,7 @@ Components:
155
155
  - `kMessage`, `kCode`, `kToolbar`, `kTable`, `kTabs`, `kChip`
156
156
  - `kSpinner`, `kDatatable`, `kIcon`, `kMenu`, `kSkeleton`
157
157
  - `kProgressBar`, `kTree`, `kButton`, `kSelect`, `kUploader`
158
- - `kToggle`, `kInput`, `kDateSelector`, `kDateSelectorV2`, `kEditor`
158
+ - `kToggle`, `kInput`, `kDateSelector`, `kEditor`
159
159
  - `kSelectButton`, `kTags`, `kSearch`, `kArrayList`, `kList`, `kTextArea`
160
160
  - `kDialog`, `kDrawer`
161
161
  - `kAppHeader`, `kAppFooter`, `kAppMain`, `kHero`
package/index.js CHANGED
@@ -7,7 +7,6 @@ import kDialog from './src/ui/kDialog.vue'
7
7
  import kDrawer from './src/ui/kDrawer.vue'
8
8
  import kInput from './src/ui/kInput.vue'
9
9
  import kDateSelector from './src/ui/kDateSelector.vue'
10
- import kDateSelectorV2 from './src/ui/kDateSelector_v2.vue'
11
10
  import kToolbar from './src/ui/kToolbar.vue'
12
11
  import kSelect from './src/ui/kSelect.vue'
13
12
  import kTable from './src/ui/kTable.vue'
@@ -51,7 +50,7 @@ export {
51
50
  kMessage, kCode, kToolbar, kTable, kTabs, kChip, kSpinner, kDatatable, kIcon, kMenu, kSkeleton, kProgressBar, kTree,
52
51
 
53
52
  // Form Components
54
- kButton, kSelect, kUploader, kToggle, kInput, kDateSelector, kDateSelectorV2, kEditor, kSelectButton, kTags, kSearch, kArrayList, kList, kTextArea,
53
+ kButton, kSelect, kUploader, kToggle, kInput, kDateSelector, kEditor, kSelectButton, kTags, kSearch, kArrayList, kList, kTextArea,
55
54
 
56
55
  // Dialogs
57
56
  kDialog, kDrawer,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ketekny-ui-kit",
3
3
  "type": "module",
4
- "version": "1.0.59",
4
+ "version": "1.0.61",
5
5
  "description": "A Vue 3 UI component library with Tailwind CSS styling",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -1,326 +1,152 @@
1
1
  <template>
2
- <div class="relative w-full">
3
- <label :for="id" class="inputLabel" :class="hasError || inputInvalid ? 'text-red-500' : 'text-primary/90'">
4
- {{ label }}
5
- </label>
6
-
7
- <!-- Date Mode (text input + calendar icon opens Vue Datepicker popup) -->
8
- <div v-if="type === 'date'">
9
- <div class="relative">
10
- <input
11
- :id="id"
12
- type="text"
13
- v-model="textDate"
14
- :placeholder="placeholder || 'dd/mm/yyyy'"
15
- :disabled="disabled"
16
- :class="[defaultStyle, hasError || inputInvalid ? errorStyle : '', disabled ? disabledStyle : '']"
17
- @input="onTextInput"
18
- @blur="commitTextDate"
19
- @keydown.enter.prevent="commitTextDate"
20
- />
21
-
22
- <!-- Right-side calendar icon (opens popup) -->
23
- <button
24
- type="button"
25
- class="absolute p-1 text-primary/70 -translate-y-1/2 right-2 top-1/2 hover:text-primary disabled:opacity-50"
26
- :disabled="disabled"
27
- @click="openCalendar"
28
- aria-label="Open calendar"
29
- >
30
- <Calendar class="w-5 h-5" />
31
- </button>
32
- </div>
33
-
34
- <!-- Hidden input; only used to render the popup -->
35
- <Datepicker ref="dp" v-model="calendarDate" :format="dateFormat" :enable-time-picker="false" auto-apply :disabled="disabled" :hide-input-icon="true" teleport="body" class="sr-only" />
36
- </div>
37
-
38
- <!-- Year-Month Mode -->
39
- <div v-else class="relative">
40
- <div class="relative">
41
- <div ref="trigger" :class="[defaultStyle, hasError ? errorStyle : '', disabled ? disabledStyle : '']" @click="togglePopup">
42
- {{ displayValue || placeholder }}
43
- </div>
44
-
45
- <!-- Right-side calendar icon (opens month grid) -->
46
- <button
47
- type="button"
48
- class="absolute p-1 text-primary/70 -translate-y-1/2 right-2 top-1/2 hover:text-primary disabled:opacity-50"
49
- :disabled="disabled"
50
- @click.stop="togglePopup"
51
- aria-label="Open month selector"
52
- >
53
- <Calendar class="w-5 h-5" />
54
- </button>
55
-
56
- <!-- Clear Button (sits just left of the calendar icon) -->
57
- <button
58
- v-if="displayValue && !disabled"
59
- @click.stop="clearSelection"
60
- type="button"
61
- class="absolute text-sm font-bold text-gray-400 -translate-y-1/2 right-9 top-1/2 hover:text-red-500"
62
- aria-label="Clear"
63
- >
64
- <X class="w-5 h-5" />
65
- </button>
66
- </div>
67
-
68
- <!-- Month Grid Popover (Teleported) -->
69
- <teleport to="body">
70
- <div v-if="showPopup" class="p-4 bg-white dark:bg-slate-800 border border-primary/20 dark:border-slate-600 rounded-lg shadow-lg" :style="popupStyles">
71
- <!-- Year Nav -->
72
- <div class="flex items-center justify-between pb-3 mb-4 border-b border-slate-200 dark:border-slate-600">
73
- <button @click.stop="currentYear--" class="text-primary/80 dark:text-sky-300 hover:text-primary dark:hover:text-sky-200" aria-label="Previous year">
74
- <ChevronLeft />
75
- </button>
76
- <span class="font-medium text-primary dark:text-sky-300">{{ currentYear }}</span>
77
- <button @click.stop="currentYear++" class="text-primary/80 dark:text-sky-300 hover:text-primary dark:hover:text-sky-200" aria-label="Next year">
78
- <ChevronRight />
79
- </button>
80
- </div>
81
-
82
- <!-- Month Grid -->
83
- <div class="grid grid-cols-3 gap-2">
84
- <div
85
- v-for="(month, index) in months"
86
- :key="index"
87
- @click="selectMonth(index + 1)"
88
- class="px-3 py-2 text-center transition rounded-lg cursor-pointer"
89
- :class="[isSelected(index + 1) ? 'bg-primary text-white' : 'text-gray-700 dark:text-slate-200 hover:bg-primary/15 dark:hover:bg-sky-500/30 dark:hover:text-sky-100']"
90
- >
91
- {{ month }}
92
- </div>
93
- </div>
94
- </div>
95
- </teleport>
96
- </div>
97
-
98
- <!-- API-provided error -->
99
- <div class="text-sm text-red-500" v-if="hasError && error !== true && error !== ''">
100
- {{ error }}
101
- </div>
102
-
103
- <!-- Local parse error -->
104
- <div class="text-sm text-red-500" v-if="inputInvalid && !hasError">Μη έγκυρη ημερομηνία. Χρησιμοποιήστε μορφή dd/mm/yyyy.</div>
105
-
106
- <!-- Info message -->
107
- <div class="text-sm text-primary/80" v-if="info != null">
108
- {{ info }}
109
- </div>
2
+ <div>
3
+ <label v-if="label" class="block mb-1 text-sm font-medium text-slate-700 dark:text-slate-300">{{ label }}</label>
4
+ <el-date-picker
5
+ :id="computedId"
6
+ :model-value="normalizedModelValue"
7
+ :type="pickerType"
8
+ :placeholder="placeholder"
9
+ :disabled="disabled"
10
+ :readonly="readonly"
11
+ :clearable="clearable"
12
+ :editable="editable"
13
+ :size="size"
14
+ :format="resolvedFormat"
15
+ :value-format="resolvedValueFormat"
16
+ :disabled-date="disabledDate"
17
+ :start-placeholder="startPlaceholder"
18
+ :end-placeholder="endPlaceholder"
19
+ :range-separator="rangeSeparator"
20
+ class="k-date"
21
+ style="width: 100%"
22
+ @update:model-value="$emit('update:modelValue', $event)"
23
+ @change="$emit('change', $event)"
24
+ />
25
+ <p v-if="hasError" class="mt-1 text-xs text-red-600 dark:text-red-400">{{ typeof error === 'string' ? error : '' }}</p>
26
+ <p v-else-if="info" class="mt-1 text-xs text-slate-500 dark:text-slate-400">{{ info }}</p>
110
27
  </div>
111
28
  </template>
112
29
 
113
- <script>
114
- import Datepicker from "@vuepic/vue-datepicker";
115
- import "@vuepic/vue-datepicker/dist/main.css";
116
- import moment from "moment";
117
- import { ChevronLeft, ChevronRight, X, Calendar } from "lucide-vue-next";
118
-
119
- export default {
120
- name: "kDatePicker",
121
- components: { Datepicker, ChevronLeft, ChevronRight, X, Calendar },
122
- props: {
123
- modelValue: [String, Date],
124
- type: {
125
- type: String,
126
- default: "date",
127
- validator: (val) => ["date", "yearMonth"].includes(val),
128
- },
129
- info: { type: String, default: null },
130
- error: { type: [String, Boolean], default: null },
131
- label: { type: String, default: "Ημερομηνία" },
132
- placeholder: { type: String, default: "Επιλέξτε ημερομηνία" },
133
- disabled: { type: Boolean, default: false },
134
- valueFormat: { type: String, default: null },
135
- id: {
136
- type: String,
137
- default: () => `datepicker-${Math.random().toString(36).substr(2, 9)}`,
138
- },
139
- },
140
- data() {
141
- const parsed = moment(this.modelValue, "YYYY-MM", true);
142
- const current = moment();
143
-
144
- const initText = (() => {
145
- if (!this.modelValue) return "";
146
- const m = moment(this.modelValue);
147
- return m.isValid() ? m.format("DD/MM/YYYY") : "";
148
- })();
30
+ <script setup>
31
+ import { computed } from 'vue'
32
+ import { ElDatePicker } from 'element-plus'
149
33
 
150
- return {
151
- defaultStyle: "w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg transition shadow-sm focus:outline-none text-gray-700 dark:text-slate-200 focus:ring-2 focus:ring-primary/20 focus:border-primary bg-white dark:bg-slate-800 placeholder-gray-400 dark:placeholder-slate-500",
152
- errorStyle: "border-red-500 focus:ring focus:ring-red-300",
153
- disabledStyle: "!bg-gray-100 dark:!bg-slate-700 !text-gray-400 dark:!text-slate-500 !cursor-not-allowed",
154
-
155
- // Year-Month UI state
156
- showPopup: false,
157
- currentYear: parsed.isValid() ? parsed.year() : current.year(),
158
- selectedMonth: parsed.isValid() ? parsed.month() + 1 : null,
159
- months: ["Ιαν", "Φεβ", "Μάρ", "Απρ", "Μάι", "Ιούν", "Ιούλ", "Αυγ", "Σεπ", "Οκτ", "Νοέ", "Δεκ"],
160
- popupStyles: {
161
- position: "absolute",
162
- top: "0px",
163
- left: "0px",
164
- width: "100%",
165
- },
166
-
167
- textDate: initText,
168
- inputInvalid: false,
169
- };
170
- },
171
- computed: {
172
- hasError() {
173
- return this.error != null && this.error !== false;
174
- },
175
- displayValue() {
176
- if (!this.selectedMonth || !this.currentYear) return "";
177
- return moment(`${this.currentYear}-${String(this.selectedMonth).padStart(2, "0")}`).format("MM/YYYY");
178
- },
179
- calendarDate: {
180
- get() {
181
- if (!this.modelValue) return null;
182
- const m = moment(this.modelValue);
183
- return m.isValid() ? m.toDate() : null;
184
- },
185
- set(val) {
186
- const m = moment(val);
187
- if (!m.isValid()) {
188
- this.$emit("update:modelValue", null);
189
- return;
190
- }
191
- const emitValue = this.valueFormat ? m.format(this.valueFormat) : m.toDate();
192
- this.$emit("update:modelValue", emitValue);
193
- },
194
- },
195
- dateFormat() {
196
- return "dd/MM/yyyy";
197
- },
198
- },
199
- watch: {
200
- modelValue(newVal) {
201
- if (!newVal) {
202
- this.textDate = "";
203
- return;
204
- }
205
- const m = moment(newVal);
206
- this.textDate = m.isValid() ? m.format("DD/MM/YYYY") : "";
207
- },
34
+ const props = defineProps({
35
+ modelValue: { type: [String, Date, Array], default: null },
36
+ id: {
37
+ type: [String, Array],
38
+ default: () => `datepicker-${Math.random().toString(36).slice(2, 11)}`,
208
39
  },
209
- mounted() {
210
- this.clickOutsideHandler = (e) => {
211
- if (this.showPopup && !this.$el.contains(e.target)) {
212
- this.showPopup = false;
213
- }
214
- };
215
- document.addEventListener("click", this.clickOutsideHandler);
40
+ label: { type: String, default: '' },
41
+ type: {
42
+ type: String,
43
+ default: 'date',
44
+ validator: (val) => ['year', 'month', 'date', 'dates', 'datetime', 'week', 'datetimerange', 'daterange', 'monthrange', 'yearMonth'].includes(val),
216
45
  },
217
- beforeUnmount() {
218
- document.removeEventListener("click", this.clickOutsideHandler);
219
- },
220
- methods: {
221
- /**
222
- * Live input handler:
223
- * - Keeps digits and slashes
224
- * - Allows user to type `/` manually
225
- * - Auto-inserts missing slashes when needed
226
- * - Emits update when full valid date entered
227
- */
228
- onTextInput(e) {
229
- if (this.disabled) return;
230
-
231
- let value = e.target.value || "";
232
-
233
- // Allow digits and slashes only
234
- value = value.replace(/[^0-9/]/g, "");
235
-
236
- // Auto-add slashes if user omits them
237
- const digits = value.replace(/\D/g, "");
238
- if (digits.length > 2 && !value.includes("/")) {
239
- // add first slash after 2 digits
240
- value = `${digits.slice(0, 2)}/${digits.slice(2)}`;
241
- }
242
- if (digits.length > 4 && (value.match(/\//g) || []).length < 2) {
243
- // ensure second slash exists after month
244
- value = `${digits.slice(0, 2)}/${digits.slice(2, 4)}/${digits.slice(4)}`;
245
- }
246
-
247
- // Prevent double slashes like "12//03"
248
- value = value.replace(/\/{2,}/g, "/");
249
-
250
- this.textDate = value;
251
-
252
- // When 8 digits exist, try to parse automatically
253
- if (digits.length === 8) {
254
- const m = moment(value, "DD/MM/YYYY", true);
255
- if (m.isValid()) {
256
- this.inputInvalid = false;
257
- const emitValue = this.valueFormat ? m.format(this.valueFormat) : m.toDate();
258
- this.$emit("update:modelValue", emitValue);
259
- } else {
260
- this.inputInvalid = true;
261
- }
262
- } else {
263
- this.inputInvalid = false;
264
- }
265
- },
266
-
267
- commitTextDate() {
268
- const raw = (this.textDate || "").trim();
269
- if (!raw) {
270
- this.inputInvalid = false;
271
- this.$emit("update:modelValue", null);
272
- return;
273
- }
274
- const formats = ["DD/MM/YYYY", "D/M/YYYY", "YYYY-MM-DD", moment.ISO_8601];
275
- const m = moment(raw, formats, true);
276
- if (!m.isValid()) {
277
- this.inputInvalid = true;
278
- return;
279
- }
280
- this.inputInvalid = false;
281
- const emitValue = this.valueFormat ? m.format(this.valueFormat) : m.toDate();
282
- this.$emit("update:modelValue", emitValue);
283
- },
284
-
285
- openCalendar() {
286
- if (this.disabled) return;
287
- this.$refs.dp && this.$refs.dp.openMenu && this.$refs.dp.openMenu();
288
- },
289
- togglePopup() {
290
- if (this.disabled) return;
291
- this.showPopup = !this.showPopup;
292
- if (this.showPopup) {
293
- this.$nextTick(() => {
294
- const trigger = this.$refs.trigger;
295
- if (trigger) {
296
- const rect = trigger.getBoundingClientRect();
297
- this.popupStyles = {
298
- position: "absolute",
299
- top: `${rect.bottom + window.scrollY}px`,
300
- left: `${rect.left + window.scrollX}px`,
301
- width: `${rect.width}px`,
302
- zIndex: 9999,
303
- };
304
- }
305
- });
306
- }
307
- },
308
- clearSelection() {
309
- this.selectedMonth = null;
310
- this.$emit("update:modelValue", null);
311
- this.showPopup = false;
312
- },
313
- selectMonth(month) {
314
- this.selectedMonth = month;
315
- const m = moment(`${this.currentYear}-${String(month).padStart(2, "0")}`, "YYYY-MM");
316
- const emitValue = this.valueFormat ? m.format(this.valueFormat) : m.toDate();
317
- this.$emit("update:modelValue", emitValue);
318
- this.showPopup = false;
319
- },
320
- isSelected(month) {
321
- const m = moment(this.modelValue, "YYYY-MM", true);
322
- return this.selectedMonth === month && m.isValid() && m.year() === this.currentYear;
323
- },
46
+ placeholder: { type: String, default: 'Select date' },
47
+ startPlaceholder: { type: String, default: 'Start date' },
48
+ endPlaceholder: { type: String, default: 'End date' },
49
+ rangeSeparator: { type: String, default: 'To' },
50
+ disabled: { type: Boolean, default: false },
51
+ readonly: { type: Boolean, default: false },
52
+ clearable: { type: Boolean, default: true },
53
+ editable: { type: Boolean, default: true },
54
+ size: {
55
+ type: String,
56
+ default: 'default',
57
+ validator: (val) => ['large', 'default', 'small'].includes(val),
324
58
  },
325
- };
59
+ format: { type: String, default: '' },
60
+ valueFormat: { type: String, default: '' },
61
+ disabledDate: { type: Function, default: undefined },
62
+ error: { type: [String, Boolean], default: null },
63
+ info: { type: String, default: '' },
64
+ })
65
+
66
+ defineEmits(['update:modelValue', 'change'])
67
+
68
+ const pickerType = computed(() => props.type === 'yearMonth' ? 'month' : props.type)
69
+ const isRange = computed(() => ['daterange', 'datetimerange', 'monthrange'].includes(pickerType.value))
70
+ const computedId = computed(() => {
71
+ if (Array.isArray(props.id)) return props.id
72
+ return isRange.value ? [`${props.id}-start`, `${props.id}-end`] : props.id
73
+ })
74
+ const hasError = computed(() => props.error != null && props.error !== false)
75
+
76
+ const defaultFormatByType = {
77
+ date: 'YYYY-MM-DD',
78
+ dates: 'YYYY-MM-DD',
79
+ week: 'gggg[w]ww',
80
+ year: 'YYYY',
81
+ month: 'YYYY-MM',
82
+ datetime: 'YYYY-MM-DD HH:mm:ss',
83
+ monthrange: 'YYYY-MM',
84
+ daterange: 'YYYY-MM-DD',
85
+ datetimerange: 'YYYY-MM-DD HH:mm:ss',
86
+ }
87
+
88
+ const resolvedFormat = computed(() => {
89
+ const custom = String(props.format || '').trim()
90
+ if (custom) return custom
91
+ return defaultFormatByType[pickerType.value] || 'YYYY-MM-DD'
92
+ })
93
+
94
+ const resolvedValueFormat = computed(() => {
95
+ const custom = String(props.valueFormat || '').trim()
96
+ if (custom) return custom
97
+ return defaultFormatByType[pickerType.value] || 'YYYY-MM-DD'
98
+ })
99
+
100
+ function normalizeIsoString(value, type) {
101
+ if (typeof value !== 'string' || !value.includes('T')) return value
102
+
103
+ if (type === 'year') return value.slice(0, 4)
104
+ if (type === 'month' || type === 'monthrange') return value.slice(0, 7)
105
+
106
+ if (type === 'datetime' || type === 'datetimerange') {
107
+ const normalized = value.replace('T', ' ').replace(/Z$/, '')
108
+ const fullMatch = normalized.match(/^(\d{4}-\d{2}-\d{2})\s(\d{2}:\d{2}:\d{2})/)
109
+ if (fullMatch) return `${fullMatch[1]} ${fullMatch[2]}`
110
+ const shortMatch = normalized.match(/^(\d{4}-\d{2}-\d{2})\s(\d{2}:\d{2})/)
111
+ if (shortMatch) return `${shortMatch[1]} ${shortMatch[2]}:00`
112
+ return normalized
113
+ }
114
+
115
+ const dateMatch = value.match(/^(\d{4}-\d{2}-\d{2})/)
116
+ return dateMatch ? dateMatch[1] : value
117
+ }
118
+
119
+ const normalizedModelValue = computed(() => {
120
+ if (Array.isArray(props.modelValue)) {
121
+ return props.modelValue.map((item) => normalizeIsoString(item, pickerType.value))
122
+ }
123
+ return normalizeIsoString(props.modelValue, pickerType.value)
124
+ })
326
125
  </script>
126
+
127
+ <style>
128
+ /* Global because Element Plus picker panel is teleported to body. */
129
+ html.dark .el-date-picker,
130
+ html.dark .el-date-range-picker {
131
+ --el-datepicker-inrange-bg-color: rgba(3, 105, 161, 0.22);
132
+ --el-datepicker-inrange-hover-bg-color: rgba(3, 105, 161, 0.32);
133
+ }
134
+
135
+ html.dark .el-date-table td.in-range .el-date-table-cell,
136
+ html.dark .el-month-table td.in-range .el-date-table-cell,
137
+ html.dark .el-year-table td.in-range .el-date-table-cell {
138
+ background-color: rgba(3, 105, 161, 0.22);
139
+ }
140
+
141
+ html.dark .el-date-table td.in-range .el-date-table-cell:hover,
142
+ html.dark .el-month-table td.in-range .el-date-table-cell:hover,
143
+ html.dark .el-year-table td.in-range .el-date-table-cell:hover {
144
+ background-color: rgba(3, 105, 161, 0.32);
145
+ }
146
+
147
+ html.dark .el-month-table td.available:hover .el-date-table-cell,
148
+ html.dark .el-year-table td.available:hover .el-date-table-cell {
149
+ background-color: rgba(148, 163, 184, 0.16);
150
+ border-radius: 24px;
151
+ }
152
+ </style>
package/src/ui/kMenu.vue CHANGED
@@ -36,7 +36,7 @@
36
36
  : 'top-[-7px] border-l border-t border-primary/20',
37
37
  ]"
38
38
  :style="{ left: `${chevronOffset}px` }"></span>
39
- <slot name="items">
39
+ <slot name="items" :close-menu="closeMenu" :close="closeMenu" :select="handleLinkClick">
40
40
  <ul class="p-2">
41
41
  <li v-for="(link, index) in normalizedLinks" :key="link.key || `${link.label}-${index}`">
42
42
  <component
@@ -131,10 +131,20 @@ export default {
131
131
  event.preventDefault();
132
132
  return;
133
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();
134
+
135
+ const runSelectionHandlers = () => {
136
+ if (typeof link?.action === "function") link.action(link, event);
137
+ if (typeof link?.onClick === "function") link.onClick(link, event);
138
+ this.$emit("select", link);
139
+ };
140
+
141
+ if (this.closeOnSelect) {
142
+ this.closeMenu();
143
+ window.setTimeout(runSelectionHandlers, 0);
144
+ return;
145
+ }
146
+
147
+ runSelectionHandlers();
138
148
  },
139
149
  handleClickOutside(event) {
140
150
  const clickedTrigger = this.$refs.menuRef?.contains(event.target);
@@ -12,7 +12,10 @@
12
12
  :class="[defaultStyle, hasError ? errorStyle : '', disabled ? disabledStyle : '']"
13
13
  :aria-label="label || 'Select option'"
14
14
  >
15
- <SelectValue :placeholder="placeholder" class="block pr-10 truncate" />
15
+ <SelectValue
16
+ :placeholder="placeholder"
17
+ class="block pr-10 truncate text-gray-700 data-[placeholder]:text-gray-400 dark:text-slate-100 dark:data-[placeholder]:text-slate-500"
18
+ />
16
19
 
17
20
  <button
18
21
  v-if="clearable && isSelectionSet && !disabled"
@@ -1,124 +0,0 @@
1
- <template>
2
- <el-button
3
- :type="resolvedVisualType"
4
- :native-type="resolvedNativeType"
5
- :size="resolvedSize"
6
- :plain="resolvedPlain"
7
- :round="round"
8
- :circle="resolvedCircle"
9
- :loading="loading"
10
- :disabled="disabled"
11
- :icon="iconComponent"
12
- :autofocus="autofocus"
13
- :title="tooltip"
14
- :aria-label="resolvedAriaLabel"
15
- :class="buttonClass"
16
- @click="$emit('click', $event)"
17
- >
18
- <slot v-if="!resolvedIconOnly">{{ label }}</slot>
19
- </el-button>
20
- </template>
21
-
22
- <script setup>
23
- import { computed, getCurrentInstance } from 'vue'
24
- import { ElButton } from 'element-plus'
25
- import * as lucideIcons from 'lucide-vue-next'
26
-
27
- const STYLE_TYPES = ['default', 'primary', 'success', 'warning', 'danger', 'info']
28
- const NATIVE_TYPES = ['button', 'submit', 'reset']
29
-
30
- const props = defineProps({
31
- label: { type: String, default: '' },
32
- type: {
33
- type: String,
34
- default: 'default',
35
- validator: (val) => ['default', 'primary', 'success', 'warning', 'danger', 'info', 'button', 'submit', 'reset'].includes(val),
36
- },
37
- size: {
38
- type: String,
39
- default: 'default',
40
- validator: (val) => ['large', 'default', 'small', 'normal'].includes(val),
41
- },
42
- plain: { type: Boolean, default: false },
43
- round: { type: Boolean, default: false },
44
- circle: { type: Boolean, default: false },
45
- loading: { type: Boolean, default: false },
46
- disabled: { type: Boolean, default: false },
47
- icon: { type: [String, Object, Function], default: null },
48
- autofocus: { type: Boolean, default: false },
49
- primary: { type: Boolean, default: false },
50
- secondary: { type: Boolean, default: false },
51
- outlined: { type: Boolean, default: false },
52
- danger: { type: Boolean, default: false },
53
- warning: { type: Boolean, default: false },
54
- success: { type: Boolean, default: false },
55
- ariaLabel: { type: String, default: null },
56
- tooltip: { type: String, default: null },
57
- variant: {
58
- type: String,
59
- default: null,
60
- validator: (val) => val == null || ['solid', 'soft', 'outlined', 'plain'].includes(val),
61
- },
62
- small: { type: Boolean, default: false },
63
- fullWidth: { type: Boolean, default: false },
64
- iconOnly: { type: Boolean, default: false },
65
- truncate: { type: Boolean, default: false },
66
- })
67
-
68
- defineEmits(['click'])
69
-
70
- const instance = getCurrentInstance()
71
- const passedProps = new Set(Object.keys(instance?.vnode.props || {}))
72
- const hasProp = (...names) => names.some((name) => passedProps.has(name))
73
-
74
- const resolvedVisualType = computed(() => {
75
- if (props.danger) return 'danger'
76
- if (props.success) return 'success'
77
- if (props.warning) return 'warning'
78
- if (props.secondary) return 'info'
79
- if (props.primary) return 'primary'
80
- return STYLE_TYPES.includes(props.type) ? props.type : 'default'
81
- })
82
-
83
- const resolvedNativeType = computed(() => {
84
- return NATIVE_TYPES.includes(props.type) ? props.type : 'button'
85
- })
86
-
87
- const resolvedSize = computed(() => {
88
- if (props.small) return 'small'
89
- if (props.size === 'normal') return 'default'
90
- return ['large', 'default', 'small'].includes(props.size) ? props.size : 'default'
91
- })
92
-
93
- const resolvedPlain = computed(() => {
94
- if (props.outlined) return true
95
- if (hasProp('variant')) return props.variant === 'outlined' || props.variant === 'plain'
96
- return props.plain
97
- })
98
-
99
- const resolvedCircle = computed(() => {
100
- if (hasProp('iconOnly', 'icon-only')) return props.iconOnly
101
- return props.circle
102
- })
103
-
104
- const resolvedIconOnly = computed(() => props.iconOnly)
105
-
106
- const iconComponent = computed(() => {
107
- if (typeof props.icon === 'string') return lucideIcons[props.icon] || null
108
- return props.icon
109
- })
110
-
111
- const resolvedAriaLabel = computed(() => {
112
- if (props.ariaLabel) return props.ariaLabel
113
- if (props.label && !resolvedIconOnly.value) return props.label
114
- if (typeof props.icon === 'string') return props.icon.replace(/([a-z])([A-Z])/g, '$1 $2')
115
- return null
116
- })
117
-
118
- const buttonClass = computed(() => {
119
- return [
120
- props.fullWidth ? '!w-full justify-center' : '',
121
- props.truncate ? 'max-w-full [&>span]:max-w-full [&>span]:truncate [&>span]:inline-block' : '',
122
- ]
123
- })
124
- </script>