pimelon-ui 0.1.64 → 0.1.66

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,6 +1,6 @@
1
1
  {
2
2
  "name": "pimelon-ui",
3
- "version": "0.1.64",
3
+ "version": "0.1.66",
4
4
  "description": "A set of components and utilities for rapid UI development",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
@@ -274,8 +274,10 @@ export default {
274
274
  if (!this.query) return options
275
275
  return options.filter((option) => {
276
276
  return (
277
- option.label.toLowerCase().includes(this.query.toLowerCase()) ||
278
- option.value.toLowerCase().includes(this.query.toLowerCase())
277
+ option.label
278
+ .toLowerCase()
279
+ .includes(this.query.trim().toLowerCase()) ||
280
+ option.value.toLowerCase().includes(this.query.trim().toLowerCase())
279
281
  )
280
282
  })
281
283
  },
@@ -0,0 +1,432 @@
1
+ <template>
2
+ <div class="flex h-full flex-col overflow-hidden">
3
+ <slot
4
+ name="header"
5
+ v-bind="{
6
+ currentMonthYear,
7
+ enabledModes,
8
+ activeView,
9
+ decrement,
10
+ increment,
11
+ updateActiveView,
12
+ }"
13
+ >
14
+ <div class="mb-2 flex justify-between">
15
+ <!-- left side -->
16
+ <!-- Year, Month -->
17
+ <span class="text-xl font-medium"> {{ currentMonthYear }}</span>
18
+ <!-- right side -->
19
+ <!-- actions buttons for calendar -->
20
+ <div class="flex gap-x-1">
21
+ <!-- Increment and Decrement Button-->
22
+
23
+ <Button
24
+ @click="decrement()"
25
+ variant="ghost"
26
+ class="h-4 w-4"
27
+ icon="chevron-left"
28
+ />
29
+ <Button
30
+ @click="increment()"
31
+ variant="ghost"
32
+ class="h-4 w-4"
33
+ icon="chevron-right"
34
+ />
35
+
36
+ <!-- View change button, default is months or can be set via props! -->
37
+ <TabButtons
38
+ :buttons="enabledModes"
39
+ class="ml-2"
40
+ v-model="activeView"
41
+ />
42
+ </div>
43
+ </div>
44
+ </slot>
45
+
46
+ <CalendarMonthly
47
+ v-if="activeView === 'Month'"
48
+ :events="events"
49
+ :currentMonth="currentMonth"
50
+ :currentMonthDates="currentMonthDates"
51
+ :config="overrideConfig"
52
+ @setCurrentDate="(d) => updateCurrentDate(d)"
53
+ />
54
+
55
+ <CalendarWeekly
56
+ v-else-if="activeView === 'Week'"
57
+ :events="events"
58
+ :weeklyDates="datesInWeeks[week]"
59
+ :config="overrideConfig"
60
+ />
61
+
62
+ <CalendarDaily
63
+ v-else-if="activeView === 'Day'"
64
+ :events="events"
65
+ :current-date="selectedDay"
66
+ :config="overrideConfig"
67
+ />
68
+
69
+ <NewEventModal
70
+ v-if="showEventModal"
71
+ v-model="showEventModal"
72
+ :event="newEvent"
73
+ />
74
+ </div>
75
+ </template>
76
+ <script setup>
77
+ import { computed, onMounted, onUnmounted, provide, ref, watch } from 'vue'
78
+ import Button from '../Button.vue'
79
+ import TabButtons from '../TabButtons.vue'
80
+ import {
81
+ getCalendarDates,
82
+ monthList,
83
+ handleSeconds,
84
+ parseDate,
85
+ } from './calendarUtils'
86
+ import CalendarMonthly from './CalendarMonthly.vue'
87
+ import CalendarWeekly from './CalendarWeekly.vue'
88
+ import CalendarDaily from './CalendarDaily.vue'
89
+ import NewEventModal from './NewEventModal.vue'
90
+ import useEventModal from './composables/useEventModal'
91
+
92
+ const props = defineProps({
93
+ events: {
94
+ type: Object,
95
+ required: false,
96
+ default: [],
97
+ },
98
+ config: {
99
+ type: Object,
100
+ },
101
+ create: {
102
+ type: Function,
103
+ required: false,
104
+ },
105
+ update: {
106
+ type: Function,
107
+ required: false,
108
+ },
109
+ delete: {
110
+ type: Function,
111
+ required: false,
112
+ },
113
+ onClick: {
114
+ type: Function,
115
+ required: false,
116
+ },
117
+ onDblClick: {
118
+ type: Function,
119
+ required: false,
120
+ },
121
+ onCellDblClick: {
122
+ type: Function,
123
+ required: false,
124
+ },
125
+ })
126
+
127
+ const defaultConfig = {
128
+ scrollToHour: 15,
129
+ disableModes: [],
130
+ defaultMode: 'Month',
131
+ isEditMode: false,
132
+ eventIcons: {},
133
+ redundantCellHeight: 50,
134
+ hourHeight: 50,
135
+ enableShortcuts: true,
136
+ }
137
+
138
+ const overrideConfig = { ...defaultConfig, ...props.config }
139
+ let activeView = ref(overrideConfig.defaultMode)
140
+
141
+ function updateActiveView(value) {
142
+ console.log(value)
143
+ activeView.value = value
144
+ }
145
+
146
+ // shortcuts for changing the active view and navigating through the calendar
147
+ onMounted(() => {
148
+ if (!overrideConfig.enableShortcuts) return
149
+ window.addEventListener('keydown', handleShortcuts)
150
+ })
151
+ onUnmounted(() => {
152
+ window.removeEventListener('keydown', handleShortcuts)
153
+ })
154
+ function handleShortcuts(e) {
155
+ if (e.key === 'm' || e.key === 'M') {
156
+ activeView.value = 'Month'
157
+ }
158
+ if (e.key === 'w' || e.key === 'W') {
159
+ activeView.value = 'Week'
160
+ }
161
+ if (e.key === 'd' || e.key === 'D') {
162
+ activeView.value = 'Day'
163
+ }
164
+ if (e.key === 'ArrowLeft') {
165
+ decrement()
166
+ }
167
+ if (e.key === 'ArrowRight') {
168
+ increment()
169
+ }
170
+ }
171
+
172
+ provide('activeView', activeView)
173
+ provide('config', overrideConfig)
174
+
175
+ const parseEvents = computed(() => {
176
+ return props.events.map((event) => {
177
+ const { fromDate, toDate, ...rest } = event
178
+ const date = parseDate(fromDate)
179
+ const from_time = new Date(fromDate).toLocaleTimeString()
180
+ const to_time = new Date(toDate).toLocaleTimeString()
181
+ if (event.isFullDay) {
182
+ return { ...rest, date }
183
+ }
184
+ return { ...rest, date, from_time, to_time }
185
+ })
186
+ })
187
+ const events = ref(parseEvents.value)
188
+
189
+ events.value.forEach((event) => {
190
+ if (!event.from_time || !event.to_time) {
191
+ return
192
+ }
193
+ event.from_time = handleSeconds(event.from_time)
194
+ event.to_time = handleSeconds(event.to_time)
195
+ })
196
+
197
+ const { showEventModal, newEvent, openNewEventModal } = useEventModal()
198
+
199
+ provide('calendarActions', {
200
+ createNewEvent,
201
+ updateEventState,
202
+ deleteEvent,
203
+ handleCellDblClick,
204
+ props,
205
+ })
206
+
207
+ // CRUD actions on an event
208
+ function createNewEvent(event) {
209
+ events.value.push(event)
210
+ props.create && props.create(event)
211
+ }
212
+
213
+ function updateEventState(event) {
214
+ const eventID = event.id
215
+ let eventIndex = events.value.findIndex((e) => e.id === eventID)
216
+ events.value[eventIndex] = event
217
+ props.update && props.update(events.value[eventIndex])
218
+ }
219
+
220
+ function deleteEvent(eventID) {
221
+ // Delete event
222
+ const eventIndex = events.value.findIndex((event) => event.id === eventID)
223
+ events.value.splice(eventIndex, 1)
224
+ props.delete && props.delete(eventID)
225
+ }
226
+
227
+ function openModal(data) {
228
+ const { e, view, date, time } = data
229
+ const config = overrideConfig.isEditMode
230
+ openNewEventModal(e, view, date, config, time)
231
+ }
232
+
233
+ function handleCellDblClick(e, date, time = '') {
234
+ const data = {
235
+ e,
236
+ view: activeView.value,
237
+ date,
238
+ time,
239
+ }
240
+
241
+ if (props.onCellDblClick) {
242
+ props.onCellDblClick(data)
243
+ return
244
+ }
245
+ openModal(data)
246
+ }
247
+
248
+ // Calendar View Options
249
+ const actionOptions = [
250
+ { label: 'Day', variant: 'solid' },
251
+ { label: 'Week', variant: 'solid' },
252
+ { label: 'Month', variant: 'solid' },
253
+ ]
254
+ let enabledModes = actionOptions.filter(
255
+ (mode) => !overrideConfig.disableModes.includes(mode.label),
256
+ )
257
+
258
+ let currentYear = ref(new Date().getFullYear())
259
+ let currentMonth = ref(new Date().getMonth())
260
+ let currentDate = ref(new Date())
261
+
262
+ let currentMonthDates = computed(() => {
263
+ let dates = getCalendarDates(currentMonth.value, currentYear.value)
264
+ return dates
265
+ })
266
+
267
+ let datesInWeeks = computed(() => {
268
+ let dates = [...currentMonthDates.value]
269
+ let datesInWeeks = []
270
+ while (dates.length) {
271
+ let week = dates.splice(0, 7)
272
+ datesInWeeks.push(week)
273
+ }
274
+ return datesInWeeks
275
+ })
276
+
277
+ function findCurrentWeek(date) {
278
+ return datesInWeeks.value.findIndex((week) =>
279
+ week.find(
280
+ (d) =>
281
+ new Date(d).toLocaleDateString().split('T')[0] ===
282
+ new Date(date).toLocaleDateString().split('T')[0],
283
+ ),
284
+ )
285
+ }
286
+
287
+ let week = ref(findCurrentWeek(currentDate.value))
288
+
289
+ let date = ref(
290
+ currentMonthDates.value.findIndex(
291
+ (d) => new Date(d).toDateString() === currentDate.value.toDateString(),
292
+ ),
293
+ )
294
+ let selectedDay = computed(() => currentMonthDates.value[date.value])
295
+
296
+ function updateCurrentDate(d) {
297
+ activeView.value = 'Day'
298
+ date.value = findIndexOfDate(d)
299
+ week.value = findCurrentWeek(d)
300
+ }
301
+
302
+ const incrementClickEvents = {
303
+ Month: incrementMonth,
304
+ Week: incrementWeek,
305
+ Day: incrementDay,
306
+ }
307
+
308
+ const decrementClickEvents = {
309
+ Month: decrementMonth,
310
+ Week: decrementWeek,
311
+ Day: decrementDay,
312
+ }
313
+
314
+ function incrementMonth() {
315
+ currentMonth.value++
316
+ date.value = findFirstDateOfMonth(currentMonth.value, currentYear.value)
317
+ week.value = findCurrentWeek(currentMonthDates.value[date.value]) + 1
318
+ if (currentMonth.value > 11) {
319
+ currentMonth.value = 0
320
+ currentYear.value++
321
+ }
322
+ }
323
+
324
+ function decrementMonth() {
325
+ currentMonth.value--
326
+ date.value = findLastDateOfMonth(currentMonth.value, currentYear.value)
327
+ week.value = findCurrentWeek(currentMonthDates.value[date.value])
328
+ if (currentMonth.value < 0) {
329
+ currentMonth.value = 11
330
+ currentYear.value--
331
+ }
332
+ }
333
+
334
+ function increment() {
335
+ incrementClickEvents[activeView.value]()
336
+ }
337
+
338
+ function decrement() {
339
+ decrementClickEvents[activeView.value]()
340
+ }
341
+
342
+ function incrementWeek() {
343
+ week.value += 1
344
+ if (week.value < datesInWeeks.value.length) {
345
+ date.value = findIndexOfDate(datesInWeeks.value[week.value][0])
346
+ }
347
+ if (week.value > datesInWeeks.value.length - 1) {
348
+ incrementMonth()
349
+ }
350
+ let nextMonthDates = filterCurrentWeekDates()
351
+ if (nextMonthDates.length > 0) {
352
+ incrementMonth()
353
+ week.value = findCurrentWeek(nextMonthDates[0])
354
+ }
355
+ }
356
+
357
+ function decrementWeek() {
358
+ week.value -= 1
359
+ if (week.value < 0) {
360
+ decrementMonth()
361
+ return
362
+ }
363
+
364
+ if (week.value > 0) {
365
+ date.value = findIndexOfDate(datesInWeeks.value[week.value][0])
366
+ }
367
+
368
+ let previousMonthDates = filterCurrentWeekDates()
369
+ if (previousMonthDates.length > 0) {
370
+ decrementMonth()
371
+ week.value = findCurrentWeek(previousMonthDates[0])
372
+ }
373
+ }
374
+
375
+ function filterCurrentWeekDates() {
376
+ let currentWeekDates = datesInWeeks.value[week.value]
377
+ let differentMonthDates = currentWeekDates.filter(
378
+ (d) => d.getMonth() !== currentMonth.value,
379
+ )
380
+ return differentMonthDates
381
+ }
382
+
383
+ function incrementDay() {
384
+ date.value++
385
+ if (
386
+ date.value > currentMonthDates.value.length - 1 ||
387
+ !isCurrentMonthDate(currentMonthDates.value[date.value])
388
+ ) {
389
+ incrementMonth()
390
+ }
391
+ }
392
+
393
+ function decrementDay() {
394
+ date.value--
395
+ if (
396
+ date.value < 0 ||
397
+ !isCurrentMonthDate(currentMonthDates.value[date.value])
398
+ ) {
399
+ decrementMonth()
400
+ }
401
+ }
402
+
403
+ function findLastDateOfMonth(month, year) {
404
+ let inputDate = new Date(year, month + 1, 0)
405
+ let lastDateIndex = currentMonthDates.value.findIndex(
406
+ (date) => new Date(date).toDateString() === inputDate.toDateString(),
407
+ )
408
+ return lastDateIndex
409
+ }
410
+
411
+ function findFirstDateOfMonth(month, year) {
412
+ let inputDate = new Date(year, month, 1)
413
+ let firstDateIndex = currentMonthDates.value.findIndex(
414
+ (date) => new Date(date).toDateString() === inputDate.toDateString(),
415
+ )
416
+ return firstDateIndex
417
+ }
418
+
419
+ function findIndexOfDate(date) {
420
+ return currentMonthDates.value.findIndex(
421
+ (d) => new Date(d).toDateString() === new Date(date).toDateString(),
422
+ )
423
+ }
424
+ const currentMonthYear = computed(() => {
425
+ return monthList[currentMonth.value] + ', ' + currentYear.value
426
+ })
427
+
428
+ function isCurrentMonthDate(date) {
429
+ date = new Date(date)
430
+ return date.getMonth() === currentMonth.value
431
+ }
432
+ </script>
@@ -0,0 +1,113 @@
1
+ <template>
2
+ <div class="h-[90%] min-h-[500px] min-w-[600px]">
3
+ <p class="pb-2 text-base font-bold text-gray-800">
4
+ {{ parseDateWithDay(currentDate, (fullDay = true)) }}
5
+ </p>
6
+ <div class="h-full overflow-hidden">
7
+ <div
8
+ class="flex h-full w-full overflow-scroll border-b-[1px] border-l-[1px] border-t-[1px]"
9
+ ref="gridRef"
10
+ >
11
+ <!-- Left column -->
12
+ <div class="grid h-full w-16 grid-cols-1">
13
+ <span
14
+ v-for="time in 24"
15
+ class="flex h-[72px] items-end justify-center text-center text-sm font-normal text-gray-600"
16
+ :style="{ height: `${hourHeight}px` }"
17
+ />
18
+ </div>
19
+
20
+ <!-- Calendar Grid / Right Column -->
21
+ <div class="grid h-full w-full grid-cols-1 pb-2">
22
+ <div class="calendar-column relative border-r-[1px]">
23
+ <!-- Top Redundant Cell before time starts for giving the calendar some space -->
24
+ <div
25
+ class="flex h-[50px] w-full flex-wrap gap-2 overflow-y-scroll border-b-[1px] border-gray-200 transition-all"
26
+ :style="{ height: `${config.redundantCellHeight}px` }"
27
+ >
28
+ <CalendarEvent
29
+ v-for="(calendarEvent, idx) in fullDayEvents[
30
+ parseDate(currentDate)
31
+ ]"
32
+ class="mb-1 w-[20%] cursor-pointer"
33
+ :event="{ ...calendarEvent, idx }"
34
+ :key="calendarEvent.id"
35
+ :date="currentDate"
36
+ />
37
+ </div>
38
+ <!-- Day Grid -->
39
+ <div
40
+ class="relative flex"
41
+ v-for="time in twentyFourHoursFormat"
42
+ :data-time-attr="time"
43
+ @dblclick="
44
+ calendarActions.handleCellDblClick($event, currentDate, time)
45
+ "
46
+ >
47
+ <div
48
+ class="w-full border-b-[1px] border-gray-200"
49
+ :style="{ height: `${hourHeight}px` }"
50
+ />
51
+ </div>
52
+ <CalendarEvent
53
+ v-for="(calendarEvent, idx) in timedEvents[
54
+ parseDate(currentDate)
55
+ ]"
56
+ class="absolute mb-2 cursor-pointer"
57
+ :event="calendarEvent"
58
+ :key="calendarEvent.id"
59
+ :date="currentDate"
60
+ />
61
+ <!-- Current time Marker -->
62
+ <CalendarTimeMarker
63
+ :date="currentDate"
64
+ :redundantCellHeight="config.redundantCellHeight"
65
+ />
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </template>
72
+
73
+ <script setup>
74
+ import { computed, inject, onMounted, ref } from 'vue'
75
+ import CalendarEvent from './CalendarEvent.vue'
76
+ import CalendarTimeMarker from './CalendarTimeMarker.vue'
77
+ import {
78
+ parseDate,
79
+ parseDateWithDay,
80
+ twentyFourHoursFormat,
81
+ } from './calendarUtils'
82
+ import useCalendarData from './composables/useCalendarData'
83
+
84
+ const props = defineProps({
85
+ events: {
86
+ type: Object,
87
+ required: false,
88
+ },
89
+ config: {
90
+ type: Object,
91
+ },
92
+ currentDate: {
93
+ type: Object,
94
+ required: true,
95
+ },
96
+ })
97
+ const timedEvents = computed(
98
+ () => useCalendarData(props.events).timedEvents.value,
99
+ )
100
+ const fullDayEvents = computed(
101
+ () => useCalendarData(props.events).fullDayEvents.value,
102
+ )
103
+ const gridRef = ref(null)
104
+ const hourHeight = props.config.hourHeight
105
+ const minuteHeight = hourHeight / 60
106
+
107
+ onMounted(() => {
108
+ const currentHour = new Date().getHours()
109
+ gridRef.value.scrollBy(0, currentHour * 60 * minuteHeight)
110
+ })
111
+
112
+ const calendarActions = inject('calendarActions')
113
+ </script>