manolis-ui 1.1.0 → 1.1.2
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/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -13,7 +13,7 @@ const __filename = __cjs_url__.fileURLToPath(import.meta.url);
|
|
|
13
13
|
const __dirname = __cjs_path__.dirname(__filename);
|
|
14
14
|
const require = __cjs_mod__.createRequire(import.meta.url);
|
|
15
15
|
const name = "manolis-ui";
|
|
16
|
-
const version = "1.1.
|
|
16
|
+
const version = "1.1.2";
|
|
17
17
|
|
|
18
18
|
function installTailwind(moduleOptions, nuxt = useNuxt(), resolve = createResolver(import.meta.url).resolve) {
|
|
19
19
|
const runtimeDir = resolve("./runtime");
|
|
@@ -1,39 +1,126 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { ref, defineProps, defineEmits, onMounted, onBeforeUnmount, defineAsyncComponent, nextTick } from 'vue';
|
|
2
3
|
import { Search } from 'lucide-vue-next';
|
|
3
|
-
import { ref } from 'vue';
|
|
4
4
|
|
|
5
|
+
interface Tab {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
type: "date" | "time" | "datetime";
|
|
9
|
+
range: boolean;
|
|
10
|
+
props?: Object;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SearchTab {
|
|
14
|
+
category: string;
|
|
15
|
+
tabs: Array<Tab>;
|
|
16
|
+
}
|
|
5
17
|
|
|
6
18
|
interface Props {
|
|
7
|
-
|
|
19
|
+
searchOptions: Array<SearchTab>;
|
|
8
20
|
}
|
|
9
|
-
|
|
10
|
-
const
|
|
21
|
+
|
|
22
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
23
|
+
searchOptions: () => [],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const currentCategory = ref(props.searchOptions[0]?.category || '');
|
|
27
|
+
const activeTab = ref<Tab | null>(null);
|
|
11
28
|
|
|
12
29
|
const emit = defineEmits(['category-changed']);
|
|
13
30
|
|
|
14
31
|
function setCurrentCategory(category: string) {
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
currentCategory.value = category;
|
|
33
|
+
emit('category-changed', category);
|
|
17
34
|
}
|
|
18
35
|
|
|
36
|
+
// Dynamic component loader
|
|
37
|
+
const componentMap = {
|
|
38
|
+
datetime: defineAsyncComponent(() => import('./datetimePicker.vue')),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Refs for tabs and popup positioning
|
|
42
|
+
const tabRefs = ref<Record<string, HTMLElement | null>>({});
|
|
43
|
+
const popupStyle = ref({ left: '0px', top: '0px' });
|
|
44
|
+
|
|
45
|
+
const searchContainer = ref<HTMLElement | null>(null);
|
|
46
|
+
|
|
47
|
+
function handleOutsideClick(event: MouseEvent) {
|
|
48
|
+
if (searchContainer.value && !searchContainer.value.contains(event.target as Node)) {
|
|
49
|
+
activeTab.value = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function openTab(tab: Tab) {
|
|
54
|
+
activeTab.value = tab;
|
|
55
|
+
await nextTick(); // Ensure DOM updates
|
|
56
|
+
|
|
57
|
+
const tabElement = tabRefs.value[tab.name];
|
|
58
|
+
if (tabElement) {
|
|
59
|
+
const rect = tabElement.getBoundingClientRect();
|
|
60
|
+
const parentRect = searchContainer.value?.getBoundingClientRect() || { left: 0, top: 0 };
|
|
61
|
+
|
|
62
|
+
// Center the popup
|
|
63
|
+
popupStyle.value = {
|
|
64
|
+
left: `${rect.left + (rect.width / 2) - (parentRect.left)}px`,
|
|
65
|
+
top: `${rect.bottom - parentRect.top}px`,
|
|
66
|
+
transform: `translateX(-50%)`, // Center horizontally
|
|
67
|
+
};
|
|
68
|
+
} else {
|
|
69
|
+
console.error('Tab element not found for:', tab.name);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
function setDynamicData(data: any) {
|
|
75
|
+
console.log(data);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onMounted(() => {
|
|
79
|
+
document.addEventListener('click', handleOutsideClick);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
onBeforeUnmount(() => {
|
|
83
|
+
document.removeEventListener('click', handleOutsideClick);
|
|
84
|
+
});
|
|
19
85
|
</script>
|
|
20
86
|
|
|
21
87
|
<template>
|
|
22
|
-
<div
|
|
23
|
-
|
|
24
|
-
|
|
88
|
+
<div>
|
|
89
|
+
<!-- Category Navigation -->
|
|
90
|
+
<div class="flex gap-4 place-content-center w-full"
|
|
91
|
+
:class="{ 'flex': props.searchOptions.length > 1, 'hidden': props.searchOptions.length <= 1 }">
|
|
92
|
+
<button v-for="searchOption in props.searchOptions" :key="searchOption.category" type="button"
|
|
93
|
+
:class="{ 'font-medium': searchOption.category === currentCategory }"
|
|
94
|
+
@click="setCurrentCategory(searchOption.category)">
|
|
95
|
+
{{ searchOption.category }}
|
|
96
|
+
</button>
|
|
97
|
+
</div>
|
|
25
98
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
99
|
+
<!-- Tab Navigation and Content -->
|
|
100
|
+
<div ref="searchContainer"
|
|
101
|
+
class="relative my-8 rounded border-2 border-opacity-25 group/search flex shadow-md transition-all p-2 place-items-center">
|
|
102
|
+
<div class="tabs tabs-boxed bg-base-100 flex gap-4 w-full">
|
|
103
|
+
<button v-for="tab in props.searchOptions.find(opt => opt.category === currentCategory)?.tabs || []"
|
|
104
|
+
:key="tab.name" @click="openTab(tab)" :class="{ 'tab-active': activeTab?.name === tab.name }"
|
|
105
|
+
class="group/searchitem text-start relative p-1 hover:bg-base-200 rounded"
|
|
106
|
+
:ref="(el) => tabRefs[tab.name] = el">
|
|
107
|
+
<p class="text-sm">{{ tab.name }}</p>
|
|
108
|
+
<p class="text-xs opacity-35">{{ tab.description }}</p>
|
|
32
109
|
</button>
|
|
33
|
-
|
|
34
|
-
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<!-- Dynamic Component Rendering -->
|
|
113
|
+
<div class="tab-content absolute w-fit max-w-full flex mt-4 transition-all" v-if="activeTab" :style="popupStyle">
|
|
114
|
+
<component :is="componentMap[activeTab.type]" @updated="setDynamicData" v-bind="activeTab.props ? activeTab.props : null" />
|
|
115
|
+
</div>
|
|
116
|
+
<!-- Search Button -->
|
|
117
|
+
<button title="search" type="submit" class="btn btn-primary btn-square ml-1">
|
|
118
|
+
<Search :size="24" color="white" />
|
|
119
|
+
</button>
|
|
35
120
|
</div>
|
|
36
|
-
<!-- search button -->
|
|
37
|
-
<button title="search" type="submit" class="btn btn-primary btn-square ml-1"><Search :size="24" color="white" /></button>
|
|
38
121
|
</div>
|
|
39
122
|
</template>
|
|
123
|
+
|
|
124
|
+
<style scoped>
|
|
125
|
+
.tabs button{cursor:pointer}.tab-content{position:absolute;transform-origin:top center;transition:opacity .3s ease;z-index:10}.tabs-boxed :is(.tab-active,[aria-selected=true]):not(.tab-disabled):not([disabled]),.tabs-boxed :is(input:checked){@apply bg-base-300 text-base-content}
|
|
126
|
+
</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative inline-block" ref="pickerContainer">
|
|
3
3
|
<div class="flex items-center gap-2 cursor-pointer" @click="togglePopup">
|
|
4
|
-
<slot>
|
|
4
|
+
<slot v-if="popup">
|
|
5
5
|
<input
|
|
6
6
|
type="text"
|
|
7
7
|
class="input input-bordered w-full cursor-pointer"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
</slot>
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
<div v-if="showPopup" class="
|
|
19
|
+
<div v-if="showPopup || !popup" class="z-50 bg-base-100 shadow-xl rounded-md p-4 mt-2 w-[360px]" :class="[{'absolute': popup}]" :id="popupId">
|
|
20
20
|
<div v-if="showDate" class="flex items-center justify-between mb-4 place-content-center">
|
|
21
21
|
<button class="btn btn-sm btn-primary btn-outline" @click="previousMonth">
|
|
22
22
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
@@ -109,8 +109,8 @@
|
|
|
109
109
|
</div>
|
|
110
110
|
|
|
111
111
|
<div class="mt-4 flex gap-2">
|
|
112
|
-
<button class="btn btn-secondary w-fit" @click="clearSelection">Clear</button>
|
|
113
|
-
<button class="btn btn-primary btn-wide" @click="closeAndEmit">Close</button>
|
|
112
|
+
<button class="btn btn-secondary" :class="popup ? 'w-fit' : 'w-full'" @click="clearSelection">Clear</button>
|
|
113
|
+
<button v-if="popup" class="btn btn-primary btn-wide" @click="closeAndEmit">Close</button>
|
|
114
114
|
</div>
|
|
115
115
|
</div>
|
|
116
116
|
</div>
|
|
@@ -134,6 +134,10 @@ const props = defineProps({
|
|
|
134
134
|
type: String,
|
|
135
135
|
default: 'Select date and time',
|
|
136
136
|
},
|
|
137
|
+
popup: {
|
|
138
|
+
type: Boolean,
|
|
139
|
+
default: false,
|
|
140
|
+
},
|
|
137
141
|
id: {
|
|
138
142
|
type: String,
|
|
139
143
|
default: 'datetimepicker',
|
|
@@ -141,7 +145,7 @@ const props = defineProps({
|
|
|
141
145
|
})
|
|
142
146
|
|
|
143
147
|
// Emits
|
|
144
|
-
const emit = defineEmits(['
|
|
148
|
+
const emit = defineEmits(['updated'])
|
|
145
149
|
|
|
146
150
|
// State
|
|
147
151
|
const showPopup = ref(false)
|
|
@@ -169,8 +173,9 @@ const monthIndex = computed(() => {
|
|
|
169
173
|
const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
170
174
|
const firstDay = computed(() => new Date(currentYear.value, monthIndex.value).getDay())
|
|
171
175
|
const daysInMonth = computed(() => {
|
|
172
|
-
|
|
173
|
-
})
|
|
176
|
+
const days = new Date(currentYear.value, monthIndex.value + 1, 0).getDate();
|
|
177
|
+
return Array.from({ length: days }, (_, i) => new Date(currentYear.value, monthIndex.value, i + 1));
|
|
178
|
+
});
|
|
174
179
|
const emptyDays = computed(() => Array.from({ length: firstDay.value }, (_, i) => i))
|
|
175
180
|
const hours = Array.from({ length: 12 }, (_, i) => (i + 1).toString().padStart(2, '0'))
|
|
176
181
|
const minutes = Array.from({ length: 60 }, (_, i) => i.toString().padStart(2, '0'))
|
|
@@ -359,10 +364,12 @@ const emitDateTimeChanged = () => {
|
|
|
359
364
|
}
|
|
360
365
|
|
|
361
366
|
// Emit the formatted string
|
|
362
|
-
emit('
|
|
367
|
+
emit('updated', result.trim());
|
|
363
368
|
};
|
|
364
369
|
|
|
365
|
-
|
|
370
|
+
if(props.popup) {
|
|
371
|
+
onMounted(() => document.addEventListener('click', handleClickOutside))
|
|
372
|
+
}
|
|
366
373
|
onUnmounted(() => document.removeEventListener('click', handleClickOutside))
|
|
367
374
|
</script>
|
|
368
375
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const useLocalStorage: <T>(key: string, initialValue: T) =>
|
|
1
|
+
export declare const useLocalStorage: <T>(key: string, initialValue: T) => any;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "manolis-ui",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "My new Nuxt module",
|
|
5
5
|
"repository": "manolis-trading/manolis-ui",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"@nuxt/test-utils": "^3.14.4",
|
|
45
45
|
"@nuxtjs/tailwindcss": "^6.12.2",
|
|
46
46
|
"@types/node": "latest",
|
|
47
|
+
"@types/vue": "^1.0.31",
|
|
47
48
|
"autoprefixer": "^10.4.20",
|
|
48
49
|
"changelogen": "^0.5.7",
|
|
49
50
|
"eslint": "^9.15.0",
|