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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "manolis-ui",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "configKey": "manolisUI",
5
5
  "compatibility": {
6
6
  "nuxt": ">=3.10.0"
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.0";
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
- categories: Array<string>;
19
+ searchOptions: Array<SearchTab>;
8
20
  }
9
- const props = defineProps<Props>();
10
- const currentCategory = ref(props.categories[0]);
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
- currentCategory.value = category;
16
- emit('category-changed', category);
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 class="flex gap-4 place-content-center" :class="categories.length > 1 ? 'flex' : 'hidden'">
23
- <button type="button" v-for="category in categories" :class="[{['font-medium']: category == currentCategory }]" @click="setCurrentCategory(category)">{{ category }}</button>
24
- </div>
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
- <div class="my-8 rounded border-2 border-opacity-25 group/search flex shadow-md hover:shadow-lg transition-all p-2 place-items-center">
27
- <!-- search fields -->
28
- <div class="flex-auto flex gap-4">
29
- <button type="button" class="group/searchitem text-start relative p-1 hover:bg-base-200 rounded after:h-10 after:bg-base-200 after:absolute after:-right-2 after:w-[1px] after:top-0 last-of-type:after:content-none last-of-type:flex-auto first-of-type:flex-auto">
30
- <p class="text-sm">Wie</p>
31
- <p class="text-xs opacity-35 group-hover/searchitem:text-base-500">Aantal personen</p>
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
- <!-- <div class="divider divider-horizontal h-10"></div> -->
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="absolute z-50 bg-base-100 shadow-xl rounded-md p-4 mt-2 w-[360px]" :id="popupId">
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(['update:datetimeChanged'])
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
- return Array.from({ length: new Date(currentYear.value, monthIndex.value + 1, 0).getDate() }, (_, i) => new Date(currentYear.value, monthIndex.value, i + 1))
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('update:datetimeChanged', result.trim());
367
+ emit('updated', result.trim());
363
368
  };
364
369
 
365
- onMounted(() => document.addEventListener('click', handleClickOutside))
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) => [T | null] extends [import("vue").Ref<any, any>] ? import("@vue/shared").IfAny<import("vue").Ref<any, any> & T, import("vue").Ref<import("vue").Ref<any, any> & T, import("vue").Ref<any, any> & T>, import("vue").Ref<any, any> & T> : import("vue").Ref<import("vue").UnwrapRef<T> | null, T | import("vue").UnwrapRef<T> | null>;
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.0",
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",