manolis-ui 1.0.16 → 1.1.1
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 +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/actions/theme-controller.vue +1 -3
- package/dist/runtime/components/data-display/avatar.vue +1 -1
- package/dist/runtime/components/data-display/card.vue +40 -34
- package/dist/runtime/components/data-input/advancedSearch.vue +39 -0
- package/dist/runtime/components/data-input/datetimePicker.vue +372 -0
- package/dist/runtime/components/navigation/navigation-bar.vue +4 -6
- package/package.json +3 -1
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.
|
|
16
|
+
const version = "1.1.1";
|
|
17
17
|
|
|
18
18
|
function installTailwind(moduleOptions, nuxt = useNuxt(), resolve = createResolver(import.meta.url).resolve) {
|
|
19
19
|
const runtimeDir = resolve("./runtime");
|
|
@@ -24,9 +24,7 @@ function updateTheme(theme: string) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// Apply the theme on mounted (client-side only)
|
|
27
|
-
onMounted(() =>
|
|
28
|
-
updateTheme(themeValue.value);
|
|
29
|
-
});
|
|
27
|
+
onMounted(() => updateTheme(themeValue.value));
|
|
30
28
|
|
|
31
29
|
// Watch themeValue changes and update localStorage
|
|
32
30
|
watch(themeValue, (newValue) => {
|
|
@@ -2,49 +2,55 @@
|
|
|
2
2
|
import { ref, watch } from 'vue';
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
imgUrl: string;
|
|
6
|
+
alt?: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
outlined?: boolean;
|
|
10
|
+
loading: boolean;
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
13
|
-
imgUrl: "",
|
|
14
|
-
alt: "A beautiful picture that says something about this card",
|
|
15
|
-
title: '',
|
|
16
|
-
outlined: false,
|
|
17
|
-
description: ""
|
|
18
|
-
});
|
|
19
12
|
|
|
20
|
-
|
|
13
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
14
|
+
imgUrl: "",
|
|
15
|
+
alt: "A beautiful picture that says something about this card",
|
|
16
|
+
title: '',
|
|
17
|
+
outlined: false,
|
|
18
|
+
description: "",
|
|
19
|
+
loading: false
|
|
20
|
+
});
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
const outlined = ref<boolean>(props.outlined);
|
|
23
|
+
|
|
24
|
+
watch(
|
|
25
|
+
() => props.outlined,
|
|
26
|
+
(newValue) => {
|
|
27
|
+
outlined.value = newValue;
|
|
28
|
+
}
|
|
29
|
+
);
|
|
28
30
|
</script>
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
<template>
|
|
32
|
-
|
|
34
|
+
<div class="card card-compact bg-base-100 max-w-80 min-w-64 shadow-xl rounded group h-full"
|
|
35
|
+
:class="outlined ? 'border-4 border-primary' : ''">
|
|
33
36
|
<figure>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
:alt="alt"
|
|
37
|
-
class="object-cover w-full rounded"
|
|
38
|
-
v-if="imgUrl"
|
|
39
|
-
/>
|
|
37
|
+
<img :src="imgUrl" :alt="alt" class="object-cover w-full rounded" v-if="imgUrl || imgUrl && !loading" />
|
|
38
|
+
<div class="skeleton h-32 w-full" v-if="loading"></div>
|
|
40
39
|
</figure>
|
|
41
|
-
<div class="card-body">
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
<div class="card-body" v-if="!loading">
|
|
41
|
+
<h2 class="card-title text-lg text-left group-hover:text-primary cursor-pointer" v-if="title">{{ title }}</h2>
|
|
42
|
+
<slot name="details"></slot>
|
|
43
|
+
<p class="text-left" v-if="description">{{ description }}</p>
|
|
44
|
+
<div class="card-actions flex-col flex-nowrap">
|
|
45
|
+
<slot name="actions" />
|
|
46
|
+
</div>
|
|
48
47
|
</div>
|
|
48
|
+
<div class="card-body">
|
|
49
|
+
<div class="flex w-52 flex-col gap-4">
|
|
50
|
+
<div class="skeleton h-4 w-28"></div>
|
|
51
|
+
<div class="skeleton h-4 w-full"></div>
|
|
52
|
+
<div class="skeleton h-4 w-full"></div>
|
|
53
|
+
</div>
|
|
49
54
|
</div>
|
|
55
|
+
</div>
|
|
50
56
|
</template>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Search } from 'lucide-vue-next';
|
|
3
|
+
import { ref } from 'vue';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
categories: Array<string>;
|
|
8
|
+
}
|
|
9
|
+
const props = defineProps<Props>();
|
|
10
|
+
const currentCategory = ref(props.categories[0]);
|
|
11
|
+
|
|
12
|
+
const emit = defineEmits(['category-changed']);
|
|
13
|
+
|
|
14
|
+
function setCurrentCategory(category: string) {
|
|
15
|
+
currentCategory.value = category;
|
|
16
|
+
emit('category-changed', category);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<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>
|
|
25
|
+
|
|
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>
|
|
32
|
+
</button>
|
|
33
|
+
<!-- <div class="divider divider-horizontal h-10"></div> -->
|
|
34
|
+
|
|
35
|
+
</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
|
+
</div>
|
|
39
|
+
</template>
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="relative inline-block" ref="pickerContainer">
|
|
3
|
+
<div class="flex items-center gap-2 cursor-pointer" @click="togglePopup">
|
|
4
|
+
<slot>
|
|
5
|
+
<input
|
|
6
|
+
type="text"
|
|
7
|
+
class="input input-bordered w-full cursor-pointer"
|
|
8
|
+
:placeholder="placeholder"
|
|
9
|
+
:value="formattedValue"
|
|
10
|
+
readonly
|
|
11
|
+
:id="inputId"
|
|
12
|
+
/>
|
|
13
|
+
<button class="btn btn-ghost">
|
|
14
|
+
📅
|
|
15
|
+
</button>
|
|
16
|
+
</slot>
|
|
17
|
+
</div>
|
|
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">
|
|
20
|
+
<div v-if="showDate" class="flex items-center justify-between mb-4 place-content-center">
|
|
21
|
+
<button class="btn btn-sm btn-primary btn-outline" @click="previousMonth">
|
|
22
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
23
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
|
24
|
+
</svg>
|
|
25
|
+
</button>
|
|
26
|
+
|
|
27
|
+
<select class="select border-none w-fit" v-model="currentMonth" @change="emitDateTimeChanged">
|
|
28
|
+
<option v-for="(month, index) in months" :key="index" :value="month">{{ month }}</option>
|
|
29
|
+
</select>
|
|
30
|
+
|
|
31
|
+
<select class="select w-24 border-none" v-model="currentYear" @change="emitDateTimeChanged">
|
|
32
|
+
<option v-for="year in years" :key="year" :value="year">{{ year }}</option>
|
|
33
|
+
</select>
|
|
34
|
+
|
|
35
|
+
<button class="btn btn-sm btn-primary btn-outline" @click="nextMonth">
|
|
36
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
37
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
38
|
+
</svg>
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div v-if="showDate" class="grid grid-cols-7 gap-2">
|
|
43
|
+
<div v-for="day in daysOfWeek" :key="day" class="text-center">{{ day }}</div>
|
|
44
|
+
<div v-for="emptyDay in emptyDays" :key="emptyDay" class="text-center"></div>
|
|
45
|
+
<div v-for="day in daysInMonth" :key="day" class="text-center cursor-pointer py-1 rounded-full hover:bg-primary/10"
|
|
46
|
+
:class="{
|
|
47
|
+
'bg-primary text-primary-content': isDateSelected(day),
|
|
48
|
+
'today': isToday(day),
|
|
49
|
+
'range-start': isRangeStart(day),
|
|
50
|
+
'range-end': isRangeEnd(day),
|
|
51
|
+
'in-range': isInDateRange(day),
|
|
52
|
+
}"
|
|
53
|
+
@click="selectDate(day)">
|
|
54
|
+
{{ day.getDate() }}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div v-if="showTime" class="mt-4">
|
|
59
|
+
<h3 class="text-lg font-bold mb-2">Select Time</h3>
|
|
60
|
+
<div v-if="!props.range">
|
|
61
|
+
<input v-if="isMobile" type="time" class="input input-bordered w-full" v-model="selectedTime" @change="emitDateTimeChanged" />
|
|
62
|
+
<div v-else class="flex gap-4">
|
|
63
|
+
<select class="select select-bordered w-full" v-model="selectedHour" @change="emitDateTimeChanged">
|
|
64
|
+
<option v-for="hour in hours" :key="hour" :value="hour">{{ hour }}</option>
|
|
65
|
+
</select>
|
|
66
|
+
<select class="select select-bordered w-full" v-model="selectedMinute" @change="emitDateTimeChanged">
|
|
67
|
+
<option v-for="minute in minutes" :key="minute" :value="minute">{{ minute }}</option>
|
|
68
|
+
</select>
|
|
69
|
+
<select class="select select-bordered w-full" v-model="selectedPeriod" @change="emitDateTimeChanged">
|
|
70
|
+
<option value="AM">AM</option>
|
|
71
|
+
<option value="PM">PM</option>
|
|
72
|
+
</select>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
<div v-else class="flex gap-4 flex-col">
|
|
76
|
+
<div>
|
|
77
|
+
<p class="font-bold">Start Time</p>
|
|
78
|
+
<input v-if="isMobile" type="time" class="input input-bordered w-full" v-model="selectedTime.start" @change="emitDateTimeChanged" />
|
|
79
|
+
<div v-else class="flex gap-2">
|
|
80
|
+
<select class="select select-bordered w-full" v-model="selectedHour.start" @change="emitDateTimeChanged">
|
|
81
|
+
<option v-for="hour in hours" :key="hour" :value="hour">{{ hour }}</option>
|
|
82
|
+
</select>
|
|
83
|
+
<select class="select select-bordered w-full" v-model="selectedMinute.start" @change="emitDateTimeChanged">
|
|
84
|
+
<option v-for="minute in minutes" :key="minute" :value="minute">{{ minute }}</option>
|
|
85
|
+
</select>
|
|
86
|
+
<select class="select select-bordered w-full" v-model="selectedPeriod.start" @change="emitDateTimeChanged">
|
|
87
|
+
<option value="AM">AM</option>
|
|
88
|
+
<option value="PM">PM</option>
|
|
89
|
+
</select>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div>
|
|
93
|
+
<p class="font-bold">End Time</p>
|
|
94
|
+
<input v-if="isMobile" type="time" class="input input-bordered w-full" v-model="selectedTime.end" @change="emitDateTimeChanged" />
|
|
95
|
+
<div v-else class="flex gap-2">
|
|
96
|
+
<select class="select select-bordered w-full" v-model="selectedHour.end" @change="emitDateTimeChanged">
|
|
97
|
+
<option v-for="hour in hours" :key="hour" :value="hour">{{ hour }}</option>
|
|
98
|
+
</select>
|
|
99
|
+
<select class="select select-bordered w-full" v-model="selectedMinute.end" @change="emitDateTimeChanged">
|
|
100
|
+
<option v-for="minute in minutes" :key="minute" :value="minute">{{ minute }}</option>
|
|
101
|
+
</select>
|
|
102
|
+
<select class="select select-bordered w-full" v-model="selectedPeriod.end" @change="emitDateTimeChanged">
|
|
103
|
+
<option value="AM">AM</option>
|
|
104
|
+
<option value="PM">PM</option>
|
|
105
|
+
</select>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
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>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</template>
|
|
118
|
+
|
|
119
|
+
<script setup>
|
|
120
|
+
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
|
121
|
+
|
|
122
|
+
// Props
|
|
123
|
+
const props = defineProps({
|
|
124
|
+
mode: {
|
|
125
|
+
type: String,
|
|
126
|
+
default: 'datetime', // 'datetime', 'date', 'time'
|
|
127
|
+
validator: (value) => ['datetime', 'date', 'time'].includes(value),
|
|
128
|
+
},
|
|
129
|
+
range: {
|
|
130
|
+
type: Boolean,
|
|
131
|
+
default: false,
|
|
132
|
+
},
|
|
133
|
+
placeholder: {
|
|
134
|
+
type: String,
|
|
135
|
+
default: 'Select date and time',
|
|
136
|
+
},
|
|
137
|
+
id: {
|
|
138
|
+
type: String,
|
|
139
|
+
default: 'datetimepicker',
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Emits
|
|
144
|
+
const emit = defineEmits(['update:datetimeChanged'])
|
|
145
|
+
|
|
146
|
+
// State
|
|
147
|
+
const showPopup = ref(false)
|
|
148
|
+
const selectedDate = ref(props.range ? { start: null, end: null } : null)
|
|
149
|
+
const selectedTime = ref(props.range ? { start: null, end: null } : null)
|
|
150
|
+
const currentMonth = ref(new Date().toLocaleString('default', { month: 'long' }))
|
|
151
|
+
const currentYear = ref(new Date().getFullYear())
|
|
152
|
+
const selectedHour = ref(props.range ? { start: new Date().getHours() % 12 || 12, end: new Date().getHours() % 12 || 12 } : new Date().getHours() % 12 || 12)
|
|
153
|
+
const selectedMinute = ref(props.range ? { start: new Date().getMinutes().toString().padStart(2, '0'), end: new Date().getMinutes().toString().padStart(2, '0') } : new Date().getMinutes().toString().padStart(2, '0'))
|
|
154
|
+
const selectedPeriod = ref(props.range ? { start: new Date().getHours() < 12 ? 'AM' : 'PM', end: new Date().getHours() < 12 ? 'AM' : 'PM' } : new Date().getHours() < 12 ? 'AM' : 'PM')
|
|
155
|
+
|
|
156
|
+
const pickerContainer = ref(null)
|
|
157
|
+
|
|
158
|
+
// Computed properties for IDs
|
|
159
|
+
const inputId = computed(() => `${props.id}-input`);
|
|
160
|
+
const popupId = computed(() => `${props.id}-popup`);
|
|
161
|
+
|
|
162
|
+
// Computed properties
|
|
163
|
+
const showDate = computed(() => props.mode === 'date' || props.mode === 'datetime')
|
|
164
|
+
const showTime = computed(() => props.mode === 'time' || props.mode === 'datetime')
|
|
165
|
+
const currentMonthYear = computed(() => new Date(currentYear.value, monthIndex.value))
|
|
166
|
+
const monthIndex = computed(() => {
|
|
167
|
+
return months.indexOf(currentMonth.value)
|
|
168
|
+
})
|
|
169
|
+
const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
170
|
+
const firstDay = computed(() => new Date(currentYear.value, monthIndex.value).getDay())
|
|
171
|
+
const daysInMonth = computed(() => {
|
|
172
|
+
const days = new Date(currentYear.value, monthIndex.value + 1, 0).getDate();
|
|
173
|
+
return Array.from({ length: days }, (_, i) => new Date(currentYear.value, monthIndex.value, i + 1));
|
|
174
|
+
});
|
|
175
|
+
const emptyDays = computed(() => Array.from({ length: firstDay.value }, (_, i) => i))
|
|
176
|
+
const hours = Array.from({ length: 12 }, (_, i) => (i + 1).toString().padStart(2, '0'))
|
|
177
|
+
const minutes = Array.from({ length: 60 }, (_, i) => i.toString().padStart(2, '0'))
|
|
178
|
+
const isMobile = computed(() => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent))
|
|
179
|
+
const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
|
|
180
|
+
const years = computed(() => {
|
|
181
|
+
const currentYear = new Date().getFullYear()
|
|
182
|
+
return Array.from({ length: 100 }, (_, i) => currentYear - 50 + i)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const formattedDate = computed(() => {
|
|
186
|
+
if (!showDate.value || !selectedDate.value) return '';
|
|
187
|
+
|
|
188
|
+
if (props.range && selectedDate.value.start && selectedDate.value.end) {
|
|
189
|
+
const startDate = selectedDate.value.start.toLocaleDateString('en-GB');
|
|
190
|
+
const endDate = selectedDate.value.end.toLocaleDateString('en-GB');
|
|
191
|
+
return `${startDate} - ${endDate}`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!props.range && selectedDate.value) {
|
|
195
|
+
return selectedDate.value.toLocaleDateString('en-GB');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return '';
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const formattedTime = computed(() => {
|
|
202
|
+
if (!showTime.value) return '';
|
|
203
|
+
|
|
204
|
+
if (props.range && selectedTime.value.start && selectedTime.value.end) {
|
|
205
|
+
const formattedStartTime = isMobile.value
|
|
206
|
+
? selectedTime.value.start
|
|
207
|
+
: `${selectedHour.value.start}:${selectedMinute.value.start} ${selectedPeriod.value.start}`;
|
|
208
|
+
const formattedEndTime = isMobile.value
|
|
209
|
+
? selectedTime.value.end
|
|
210
|
+
: `<span class="math-inline">\{selectedHour\.value\.end\}\:</span>{selectedMinute.value.end} ${selectedPeriod.value.end}`;
|
|
211
|
+
return `${formattedStartTime} - ${formattedEndTime}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!props.range && selectedTime.value) {
|
|
215
|
+
return isMobile.value
|
|
216
|
+
? selectedTime.value
|
|
217
|
+
: `<span class="math-inline">\{selectedHour\.value\}\:</span>{selectedMinute.value} ${selectedPeriod.value}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return '';
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const formattedValue = computed(() => {
|
|
224
|
+
const date = formattedDate.value;
|
|
225
|
+
const time = formattedTime.value;
|
|
226
|
+
|
|
227
|
+
if (date && time) return `${date} ${time}`;
|
|
228
|
+
if (date) return date;
|
|
229
|
+
if (time) return time;
|
|
230
|
+
|
|
231
|
+
return '';
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
// Methods
|
|
236
|
+
const togglePopup = () => (showPopup.value = !showPopup.value)
|
|
237
|
+
|
|
238
|
+
const closeAndEmit = () => {
|
|
239
|
+
emitDateTimeChanged();
|
|
240
|
+
closePopup();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const closePopup = () => (showPopup.value = false)
|
|
244
|
+
|
|
245
|
+
const clearSelection = () => {
|
|
246
|
+
selectedDate.value = props.range ? { start: null, end: null } : null
|
|
247
|
+
selectedTime.value = props.range ? { start: null, end: null } : null
|
|
248
|
+
emitDateTimeChanged()
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const selectDate = (date) => {
|
|
252
|
+
if (props.range) {
|
|
253
|
+
if (selectedDate.value.start && selectedDate.value.end) {
|
|
254
|
+
selectedDate.value = { start: date, end: null }
|
|
255
|
+
} else if (!selectedDate.value.start) {
|
|
256
|
+
selectedDate.value.start = date
|
|
257
|
+
} else {
|
|
258
|
+
selectedDate.value.end = date
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
selectedDate.value = date
|
|
262
|
+
}
|
|
263
|
+
emitDateTimeChanged()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const isDateSelected = (date) => {
|
|
267
|
+
if (props.range) {
|
|
268
|
+
return (selectedDate.value.start && date.getTime() === selectedDate.value.start.getTime()) || (selectedDate.value.end && date.getTime() === selectedDate.value.end.getTime())
|
|
269
|
+
} else {
|
|
270
|
+
return selectedDate.value && date.getTime() === selectedDate.value.getTime()
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const isToday = (date) => {
|
|
275
|
+
const today = new Date()
|
|
276
|
+
return date.getDate() === today.getDate() &&
|
|
277
|
+
date.getMonth() === today.getMonth() &&
|
|
278
|
+
date.getFullYear() === today.getFullYear()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const isRangeStart = (date) => {
|
|
282
|
+
return props.range && selectedDate.value.start && date.getTime() === selectedDate.value.start.getTime()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const isRangeEnd = (date) => {
|
|
286
|
+
return props.range && selectedDate.value.end && date.getTime() === selectedDate.value.end.getTime()
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const isInDateRange = (date) => {
|
|
290
|
+
if (props.range && selectedDate.value.start && selectedDate.value.end) {
|
|
291
|
+
const start = selectedDate.value.start.getTime()
|
|
292
|
+
const end = selectedDate.value.end.getTime()
|
|
293
|
+
const current = date.getTime()
|
|
294
|
+
return current > start && current < end
|
|
295
|
+
}
|
|
296
|
+
return false
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
// Month navigation
|
|
301
|
+
const previousMonth = () => {
|
|
302
|
+
let newMonth = monthIndex.value - 1
|
|
303
|
+
if (newMonth < 0) {
|
|
304
|
+
newMonth = 11
|
|
305
|
+
currentYear.value--
|
|
306
|
+
}
|
|
307
|
+
currentMonth.value = months[newMonth]
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const nextMonth = () => {
|
|
311
|
+
let newMonth = monthIndex.value + 1
|
|
312
|
+
if (newMonth > 11) {
|
|
313
|
+
newMonth = 0
|
|
314
|
+
currentYear.value++
|
|
315
|
+
}
|
|
316
|
+
currentMonth.value = months[newMonth]
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Close on outside click
|
|
320
|
+
const handleClickOutside = (event) => {
|
|
321
|
+
if (pickerContainer.value && !pickerContainer.value.contains(event.target) && showPopup.value == true) {
|
|
322
|
+
closeAndEmit()
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const emitDateTimeChanged = () => {
|
|
327
|
+
let dateStr = '';
|
|
328
|
+
let timeStr = '';
|
|
329
|
+
|
|
330
|
+
// Handle date formatting
|
|
331
|
+
if (selectedDate.value) {
|
|
332
|
+
if (props.range) {
|
|
333
|
+
const start = selectedDate.value.start?.toLocaleDateString('en-GB') || '';
|
|
334
|
+
const end = selectedDate.value.end?.toLocaleDateString('en-GB') || '';
|
|
335
|
+
dateStr = `${start} - ${end}`;
|
|
336
|
+
} else {
|
|
337
|
+
dateStr = selectedDate.value.toLocaleDateString('en-GB');
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Handle time formatting
|
|
342
|
+
if (showTime.value) {
|
|
343
|
+
if (props.range) {
|
|
344
|
+
const start = selectedTime.value.start || `${selectedHour.value.start}:${selectedMinute.value.start} ${selectedPeriod.value.start}`;
|
|
345
|
+
const end = selectedTime.value.end || `${selectedHour.value.end}:${selectedMinute.value.end} ${selectedPeriod.value.end}`;
|
|
346
|
+
timeStr = `${start} - ${end}`;
|
|
347
|
+
} else {
|
|
348
|
+
timeStr = `${selectedHour.value}:${selectedMinute.value} ${selectedPeriod.value}`;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Combine date and time based on mode
|
|
353
|
+
let result = '';
|
|
354
|
+
if (props.mode === 'datetime') {
|
|
355
|
+
result = props.range ? `${dateStr} ${timeStr}` : `${dateStr} ${timeStr}`;
|
|
356
|
+
} else if (props.mode === 'date') {
|
|
357
|
+
result = dateStr;
|
|
358
|
+
} else if (props.mode === 'time') {
|
|
359
|
+
result = timeStr;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Emit the formatted string
|
|
363
|
+
emit('update:datetimeChanged', result.trim());
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
onMounted(() => document.addEventListener('click', handleClickOutside))
|
|
367
|
+
onUnmounted(() => document.removeEventListener('click', handleClickOutside))
|
|
368
|
+
</script>
|
|
369
|
+
|
|
370
|
+
<style scoped>
|
|
371
|
+
.input{cursor:pointer}.in-range{@apply bg-primary/20}.in-range:hover{@apply bg-primary/10}.range-end,.range-start{@apply bg-primary text-primary-content}
|
|
372
|
+
</style>
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
<script lang="ts" setup></script>
|
|
2
2
|
|
|
3
3
|
<template>
|
|
4
|
-
<nav class="navbar border-b-4 border-primary px-5 py-5">
|
|
4
|
+
<nav class="navbar border-b-4 border-primary px-5 py-5 place-items-start">
|
|
5
5
|
<div class="navbar-start flex-1">
|
|
6
|
-
|
|
6
|
+
<slot name="start"></slot>
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
|
-
<div class="navbar-center flex-
|
|
10
|
-
<
|
|
11
|
-
<input type="text" placeholder="Search" class="input input-bordered w-24 md:w-auto" />
|
|
12
|
-
</div>
|
|
9
|
+
<div class="navbar-center flex-auto hidden md:block">
|
|
10
|
+
<slot name="center"></slot>
|
|
13
11
|
</div>
|
|
14
12
|
|
|
15
13
|
<div class="navbar-end flex-1">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "manolis-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "My new Nuxt module",
|
|
5
5
|
"repository": "manolis-trading/manolis-ui",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
"@nuxt/kit": "^3.14.1592",
|
|
33
33
|
"daisyui": "^4.12.14",
|
|
34
34
|
"defu": "^6.1.4",
|
|
35
|
+
"lucide": "^0.462.0",
|
|
36
|
+
"lucide-vue-next": "^0.462.0",
|
|
35
37
|
"pathe": "^1.1.2"
|
|
36
38
|
},
|
|
37
39
|
"devDependencies": {
|