PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.8
Booking for Appointments and Events Calendar – Amelia v2.4.8
2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / v3 / src / views / _components / date-picker-full / AmDatePickerFull.vue
ameliabooking / v3 / src / views / _components / date-picker-full Last commit date
AmDatePickerFull.vue 1 month ago
AmDatePickerFull.vue
949 lines
1 <template>
2 <!-- Date Picker -->
3 <el-popover
4 ref="popoverRef"
5 :visible="visible"
6 popper-class="am-popover-calendar"
7 placement="bottom"
8 :width="'100%'"
9 :popper-style="cssPopVars"
10 :disabled="props.disabled"
11 :persistent="props.persistent"
12 :show-arrow="false"
13 trigger="click"
14 @after-enter="reRenderCalendar"
15 >
16 <template #reference>
17 <div ref="inputWrapperRef">
18 <AmInput
19 v-model="selectedDate"
20 class="am-dp__input"
21 :class="{ 'am-dp__input-focused': visible }"
22 :disabled="props.disabled"
23 :prefix-icon="calendarIcon"
24 :readonly="props.readonly"
25 :clearable="props.clearable"
26 :placeholder="inputPlaceholder"
27 :aria-label="ariaLabel"
28 :aria-expanded="visible"
29 aria-haspopup="dialog"
30 @click="selectCalendar"
31 @clear="clearCalendar"
32 @keydown="handleInputKeydown"
33 />
34 </div>
35 </template>
36 <div
37 class="am-dp__wrapper"
38 role="dialog"
39 aria-modal="true"
40 :aria-label="ariaLabel"
41 @keydown="handleCalendarKeydown"
42 @focusin="handleCalendarFocusin"
43 >
44 <FullCalendar
45 ref="popCalendarRef"
46 v-click-outside="onClickOutside"
47 class="am-dp"
48 :options="options"
49 />
50 </div>
51 </el-popover>
52 <!-- /Date Picker -->
53 </template>
54
55 <script setup>
56 // * Libraries
57 import FullCalendar from '@fullcalendar/vue3'
58 import dayGridPlugin from '@fullcalendar/daygrid'
59 import interactionPlugin from '@fullcalendar/interaction'
60 import allLocales from '@fullcalendar/core/locales-all'
61 import moment from 'moment'
62
63 // * Import from Vue
64 import { onBeforeMount, onMounted, computed, inject, ref, nextTick, watch } from 'vue'
65
66 // * Sttings
67 import { shortLocale } from '../../../plugins/settings'
68
69 // * Composables
70 import { useColorTransparency } from '../../../assets/js/common/colorManipulation'
71 import { getFrontedFormattedDate } from '../../../assets/js/common/date'
72
73 // * _components
74 import AmInput from '../input/AmInput'
75 import IconComponent from '../icons/IconComponent.vue'
76
77 // * Import from Element Plus
78 import { ClickOutside as vClickOutside } from 'element-plus'
79
80 /**
81 * Component Props
82 */
83 const props = defineProps({
84 initialView: {
85 type: String,
86 default: 'dayGridMonth',
87 },
88 weekDaysVisibility: {
89 type: Boolean,
90 default: true,
91 },
92 calendarMinimumDate: {
93 type: String,
94 default: '',
95 },
96 calendarMaximumDate: {
97 type: String,
98 default: '',
99 },
100 id: {
101 type: Number,
102 default: 0,
103 },
104 disabled: {
105 type: Boolean,
106 default: true,
107 },
108 inputPlaceholder: {
109 type: String,
110 default: '',
111 },
112 existingDate: {
113 type: [String, Object],
114 default: '',
115 },
116 persistent: {
117 type: Boolean,
118 default: true,
119 },
120 weekStartsFromDay: {
121 type: [String, Number],
122 default: 1,
123 },
124 refreshValue: {
125 type: Boolean,
126 default: false,
127 },
128 clearable: {
129 type: Boolean,
130 default: false,
131 },
132 readonly: {
133 type: Boolean,
134 default: true,
135 },
136 ariaLabel: {
137 type: String,
138 default: 'date picker',
139 },
140 })
141
142 let popoverRef = ref(null)
143 let popCalendarRef = ref(null)
144 let inputWrapperRef = ref(null)
145
146 const emits = defineEmits(['selectedDate', 'clearDate'])
147
148 const visible = ref(false)
149
150 let nonFormattedSelectedDate = ref(props.existingDate)
151
152 let selectedDate = ref('')
153
154 let minimumDate = ref(null)
155 let maximumDate = ref(null)
156
157 // * Icons
158 let calendarIcon = {
159 components: { IconComponent },
160 template: `<IconComponent icon='calendar'/>`,
161 }
162
163 function selectCalendar() {
164 if (!props.disabled) {
165 visible.value = true
166 }
167 }
168
169 // * Keyboard: open/close calendar from the input field
170 function handleInputKeydown(event) {
171 if (event.key === 'Enter' || event.key === ' ') {
172 event.preventDefault()
173 if (!props.disabled) {
174 visible.value = !visible.value
175 }
176 } else if (event.key === 'Escape') {
177 event.preventDefault()
178 if (visible.value) {
179 closeCalendar()
180 }
181 } else if (event.key !== 'Tab') {
182 // Block text input for all other non-navigation keys (field is readonly)
183 event.preventDefault()
184 }
185 }
186
187 // * ARIA grid structure helpers
188
189 /**
190 * Upgrades the FullCalendar day-cell table from role="presentation" to
191 * role="grid" and makes sure every week row has role="row".
192 * FullCalendar sets role="presentation" on its layout tables, which strips
193 * all row/column context from the accessibility tree. Setting role="grid"
194 * here restores that context so screen readers can announce
195 * "row 2, column 4 of 7" when a user navigates with arrow keys.
196 * The grid is labelled by the visible month/year title via aria-labelledby.
197 */
198 function initGridAriaRoles() {
199 const calendarEl = popCalendarRef.value?.$el
200 if (!calendarEl) return
201
202 // Target the table that contains the day cells (.fc-daygrid-body table).
203 const bodyTable = calendarEl.querySelector('.fc-daygrid-body table')
204 if (bodyTable) {
205 bodyTable.setAttribute('role', 'grid')
206
207 // Label the grid with the visible month/year toolbar title
208 const titleEl = calendarEl.querySelector('.fc-toolbar-title')
209 if (titleEl) {
210 // Assign a stable id so aria-labelledby survives month changes
211 if (!titleEl.id) {
212 titleEl.id = `am-dp-title-${props.id !== 0 ? props.id : 'default'}`
213 }
214 bodyTable.setAttribute('aria-labelledby', titleEl.id)
215 }
216 }
217
218 // Explicitly set role="row" on every week row.
219 // The prior role="presentation" on the ancestor table would have stripped
220 // the implicit row semantics from <tr> elements; we restore them here.
221 calendarEl.querySelectorAll('.fc-daygrid-body table tr').forEach((row) => {
222 row.setAttribute('role', 'row')
223 })
224 }
225
226 // * Roving tabindex helpers
227
228 /**
229 * Returns all day-cell <td> elements in the current calendar view.
230 * Both selectable and disabled cells are included so arrow keys traverse
231 * the full grid (matching the WCAG grid navigation pattern).
232 */
233 function getDayCells() {
234 const calendarEl = popCalendarRef.value?.$el
235 if (!calendarEl) return []
236 return Array.from(calendarEl.querySelectorAll('td.fc-day'))
237 }
238
239 /**
240 * Moves the roving tabindex seat to `targetEl` and focuses it.
241 * All other day cells are set to tabindex="-1".
242 */
243 function setRovingFocus(targetEl) {
244 getDayCells().forEach((cell) => {
245 cell.setAttribute('tabindex', cell === targetEl ? '0' : '-1')
246 })
247 targetEl.focus()
248 }
249
250 /**
251 * Sets the roving tabindex "home" cell after a render or month navigation
252 * WITHOUT stealing focus (focus stays on whichever element has it now).
253 * Priority: selected day → today (non-disabled) → first non-disabled,
254 * non-other-month cell → first cell.
255 */
256 function initRovingTabindex() {
257 const calendarEl = popCalendarRef.value?.$el
258 if (!calendarEl) return
259
260 const cells = getDayCells()
261 if (!cells.length) return
262
263 cells.forEach((cell) => cell.setAttribute('tabindex', '-1'))
264
265 const popCalendar = popCalendarRef.value.getApi()
266 const viewType = popCalendar.currentData.currentViewType
267 const selectedClass = `am-dp__${viewType}-selected`
268 const disabledClass = `am-dp__${viewType}-disabled`
269
270 const homeCell =
271 calendarEl.querySelector(`td.fc-day.${selectedClass}`) ||
272 calendarEl.querySelector(`td.fc-day-today:not(.${disabledClass})`) ||
273 cells.find(
274 (c) => !c.classList.contains(disabledClass) && !c.classList.contains('fc-day-other'),
275 ) ||
276 cells[0]
277
278 if (homeCell) {
279 homeCell.setAttribute('tabindex', '0')
280 }
281 }
282
283 // * Update roving seat whenever any day cell receives focus (e.g. via mouse click)
284 function handleCalendarFocusin(event) {
285 const dayCell = event.target.closest?.('td.fc-day')
286 if (!dayCell) return
287
288 getDayCells().forEach((cell) => {
289 cell.setAttribute('tabindex', cell === dayCell ? '0' : '-1')
290 })
291 }
292
293 // * Keyboard: close calendar or navigate the day grid with arrow keys
294 function handleCalendarKeydown(event) {
295 if (event.key === 'Escape') {
296 event.preventDefault()
297 event.stopPropagation()
298 closeCalendar()
299 return
300 }
301
302 // Arrow-key grid navigation (roving tabindex)
303 const arrowKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']
304 if (!arrowKeys.includes(event.key)) return
305
306 const dayCell = event.target.closest?.('td.fc-day')
307 if (!dayCell) return
308
309 event.preventDefault()
310
311 const cells = getDayCells()
312 const currentIndex = cells.indexOf(dayCell)
313 if (currentIndex === -1) return
314
315 const offsets = {
316 ArrowRight: 1,
317 ArrowLeft: -1,
318 ArrowDown: 7,
319 ArrowUp: -7,
320 }
321 const nextIndex = currentIndex + offsets[event.key]
322
323 if (nextIndex >= 0 && nextIndex < cells.length) {
324 setRovingFocus(cells[nextIndex])
325 }
326 }
327
328 // * Close calendar and return focus to the input
329 function closeCalendar() {
330 visible.value = false
331 nextTick(() => {
332 const input = inputWrapperRef.value?.querySelector('input')
333 if (input) input.focus()
334 })
335 }
336
337 watch(
338 () => props.refreshValue,
339 (newValue) => {
340 if (newValue) {
341 selectedDate.value = getFrontedFormattedDate(
342 props.existingDate ? moment(props.existingDate).format('YYYY-MM-DD') : '',
343 )
344 }
345 },
346 )
347
348 const options = ref({
349 initialDate: props.calendarMinimumDate
350 ? moment(props.calendarMinimumDate, 'YYYY-MM-DD HH:mm').toDate()
351 : null,
352 locales: allLocales,
353 locale: shortLocale,
354 plugins: [dayGridPlugin, interactionPlugin],
355 initialView: props.initialView,
356 dayCellDidMount: function (info) {
357 // Start every cell at -1; initRovingTabindex() promotes the "home" cell to 0
358 info.el.setAttribute('tabindex', '-1')
359 info.el.setAttribute('aria-label', `Select date ${info.date.toDateString()}`)
360
361 // role="gridcell" (not "button") preserves row/column context in the
362 // role="grid" table so screen readers can announce position information.
363 // role="button" tells AT this is a standalone button and drops all
364 // grid context ("row 2, column 4 of 7").
365 info.el.setAttribute('role', 'gridcell')
366
367 // Reflect initial selection and disabled state to AT
368 const viewType = info.view.type
369 const selectedClass = `am-dp__${viewType}-selected`
370 const disabledClass = `am-dp__${viewType}-disabled`
371 info.el.setAttribute(
372 'aria-selected',
373 info.el.classList.contains(selectedClass) ? 'true' : 'false',
374 )
375 if (info.el.classList.contains(disabledClass)) {
376 info.el.setAttribute('aria-disabled', 'true')
377 }
378
379 // Enhanced keyboard navigation
380 info.el.addEventListener('keydown', (e) => {
381 const calendarApi = info.view.calendar
382
383 if (e.key === 'Enter' || e.key === ' ') {
384 e.preventDefault()
385
386 // Use moment to format the date the same way as mouse clicks do
387 // This avoids timezone issues with toISOString()
388 const localDateStr = moment(info.date).format('YYYY-MM-DD')
389
390 // Build the event object manually (as FullCalendar would)
391 const dateClickEvent = {
392 date: info.date,
393 dateStr: localDateStr, // Use local date string instead of ISO string
394 allDay: true,
395 dayEl: info.el,
396 jsEvent: e,
397 view: info.view,
398 }
399
400 // First trigger dateClick
401 calendarApi.trigger('dateClick', dateClickEvent)
402 }
403 })
404 },
405 headerToolbar: {
406 start: 'title',
407 center: '',
408 end: 'prevYear,prev,next,nextYear',
409 },
410 weekends: props.weekDaysVisibility,
411 views: {
412 dayGridMonth: {},
413 },
414 slotLabelFormat: {
415 hour: 'numeric',
416 minute: '2-digit',
417 hour12: false,
418 },
419 eventTimeFormat: {
420 hour: 'numeric',
421 minute: '2-digit',
422 hour12: false,
423 },
424 aspectRatio: 1.45,
425 firstDay: props.weekStartsFromDay,
426 dayMaxEvents: true,
427 selectLongPressDelay: 0,
428 datesSet: function () {
429 // Re-establish the roving tabindex seat and ARIA grid roles after every
430 // month/view change (new cells are mounted on each navigation).
431 nextTick(() => {
432 initRovingTabindex()
433 initGridAriaRoles()
434 })
435 },
436 dayHeaderClassNames: calendarDayHeaderClassBuilder,
437 dayCellClassNames: calendarDayClassBuilder,
438 dateClick: calendarDateClick,
439 })
440
441 function calendarDayHeaderClassBuilder(data) {
442 let classCollector = [`am-dp__${data.view.type}-header-cell`]
443
444 // * Week days class
445 if (data.date.getDay() === 0 || data.date.getDay() === 6) {
446 classCollector.push(`am-dp__${data.view.type}-header-weekend`)
447 }
448
449 return classCollector
450 }
451
452 function calendarDayClassBuilder(data) {
453 let classCollector = [`am-dp__${data.view.type}-cell`]
454
455 if (
456 moment(data.date).isSameOrBefore(minimumDate.value) ||
457 moment(data.date).isSameOrAfter(maximumDate.value)
458 ) {
459 classCollector.push(`am-dp__${data.view.type}-disabled`)
460 }
461
462 if (
463 moment(data.date).format('YYYY-MM-DD') ===
464 moment(nonFormattedSelectedDate.value).format('YYYY-MM-DD')
465 ) {
466 classCollector.push(`am-dp__${data.view.type}-selected`)
467 }
468
469 return classCollector
470 }
471
472 function calendarDateClick(data) {
473 const popCalendar = popCalendarRef.value.getApi()
474 const popCalendarType = popCalendar.currentData.currentViewType
475 const disabledDayClass = `am-dp__${popCalendarType}-disabled`
476 const selectedDayClass = `am-dp__${popCalendarType}-selected`
477
478 // Remove selected class and clear aria-selected on the previously selected cell
479 popCalendar.el.querySelectorAll(`.${selectedDayClass}`).forEach((el) => {
480 el.classList.remove(selectedDayClass)
481 el.setAttribute('aria-selected', 'false')
482 })
483
484 if (!data.dayEl.classList.contains(disabledDayClass)) {
485 emits('selectedDate', data.dateStr)
486 nonFormattedSelectedDate.value = data.date
487 selectedDate.value = getFrontedFormattedDate(data.dateStr)
488 data.dayEl.classList.add(selectedDayClass)
489 data.dayEl.setAttribute('aria-selected', 'true')
490 closeCalendar()
491 }
492 }
493
494 function onClickOutside() {
495 visible.value = false
496 }
497
498 function reRenderCalendar() {
499 nextTick(() => {
500 popCalendarRef.value.getApi().render()
501 // Initialise roving tabindex and ARIA grid structure, then move focus to
502 // the first nav button so keyboard users can Tab forward into the grid.
503 nextTick(() => {
504 initRovingTabindex()
505 initGridAriaRoles()
506 const calendarEl = popCalendarRef.value?.$el
507 if (calendarEl) {
508 const firstBtn = calendarEl.querySelector('.fc-button')
509 if (firstBtn) firstBtn.focus()
510 }
511 })
512 })
513 }
514
515 function selectDate(date) {
516 if (date) {
517 selectedDate.value = getFrontedFormattedDate(moment(date).format('YYYY-MM-DD'))
518 }
519 }
520
521 function clearCalendar() {
522 emits('clearDate')
523 setTimeout(() => {
524 const popCalendar = popCalendarRef.value.getApi()
525 const popCalendarType = popCalendar.currentData.currentViewType
526 const selectedDayClass = `am-dp__${popCalendarType}-selected`
527
528 // Remove visual selection and aria-selected from all previously selected cells
529 popCalendar.el.querySelectorAll(`.${selectedDayClass}`).forEach((el) => {
530 el.classList.remove(selectedDayClass)
531 el.setAttribute('aria-selected', 'false')
532 })
533
534 popCalendar.unselect()
535 }, 200)
536 }
537
538 // * Color Vars
539 let amColors = inject('amColors', {
540 amColors: {
541 value: {
542 colorPrimary: '#1246D6',
543 colorSuccess: '#019719',
544 colorError: '#B4190F',
545 colorWarning: '#CCA20C',
546 colorMainBgr: '#FFFFFF',
547 colorMainHeadingText: '#33434C',
548 colorMainText: '#1A2C37',
549 colorSbBgr: '#17295A',
550 colorSbText: '#FFFFFF',
551 colorInpBgr: '#FFFFFF',
552 colorInpBorder: '#D1D5D7',
553 colorInpText: '#1A2C37',
554 colorInpPlaceHolder: '#1A2C37',
555 colorDropBgr: '#FFFFFF',
556 colorDropBorder: '#D1D5D7',
557 colorDropText: '#0E1920',
558 colorCalCell: '#1246D6',
559 colorCalCellText: '#1246D6',
560 colorCalCellLow: '#1246D6',
561 colorCalCellLowText: '#1246D6',
562 colorCalCellHigh: '#1246D6',
563 colorCalCellHighText: '#1246D6',
564 colorCalCellSelected: '#1246D6',
565 colorCalCellSelectedText: '#FFFFFF',
566 colorCalCellDisabled: '#B4190F',
567 colorCalCellDisabledText: '#1A2C37',
568 colorBtnPrim: '#265CF2',
569 colorBtnPrimText: '#FFFFFF',
570 colorBtnSec: '#1A2C37',
571 colorBtnSecText: '#FFFFFF',
572 },
573 },
574 })
575
576 let cssPopVars = computed(() => {
577 return {
578 // dpf - date picker full / nije omrazeni dpf filter iz auta :)
579 '--am-c-primary': amColors.value.colorPrimary,
580 '--am-c-primary-op80': useColorTransparency(amColors.value.colorPrimary, 0.8),
581 '--am-c-dpf-bgr': amColors.value.colorDropBgr,
582 '--am-c-dpf-border': amColors.value.colorDropBorder,
583 '--am-c-dpf-text': amColors.value.colorDropText,
584 '--am-c-dpf-text-op60': useColorTransparency(amColors.value.colorDropText, 0.6),
585 '--am-c-dpf-text-op20': useColorTransparency(amColors.value.colorDropText, 0.2),
586 '--am-c-dpf-text-op10': useColorTransparency(amColors.value.colorDropText, 0.1),
587 }
588 })
589
590 onBeforeMount(() => {
591 if (props.calendarMinimumDate) {
592 minimumDate.value = moment(props.calendarMinimumDate, 'YYYY-MM-DD HH:mm')
593 }
594 if (props.calendarMaximumDate) {
595 maximumDate.value = moment(props.calendarMaximumDate, 'YYYY-MM-DD HH:mm')
596 }
597 })
598
599 onMounted(() => {
600 nextTick(() => {
601 selectDate(props.existingDate)
602 })
603 })
604 </script>
605
606 <script></script>
607 <style lang="scss">
608 //Amelia Calendar
609 $amCalClass: am-dp;
610 @mixin am-dp-block {
611 .am-dp {
612 margin-bottom: 4px;
613
614 &__wrapper {
615 --am-fs-dpf: 15px;
616 --am-c-advsc-text: var(--am-c-main-text);
617 // Calendar cell
618 --am-c-dpf-cell-bgr: transparent;
619 --am-c-dpf-cell-border: transparent;
620 --am-c-dpf-cell-text: var(--am-c-dpf-text);
621
622 * {
623 font-family: var(--am-font-family);
624 box-sizing: border-box;
625 }
626
627 // element animation
628 & > div {
629 $count: 5;
630 @for $i from 0 through $count {
631 &:nth-child(#{$i + 1}) {
632 animation: 600ms cubic-bezier(0.45, 1, 0.4, 1.2) #{$i * 100}ms am-animation-slide-up;
633 animation-fill-mode: both;
634 }
635 }
636 }
637 }
638
639 table,
640 tr,
641 th {
642 background-color: transparent;
643 margin: 0;
644 }
645
646 &.fc {
647 &-theme-standard {
648 .fc {
649 &-scrollgrid {
650 border: none;
651
652 &-section {
653 &-header {
654 table {
655 border: none;
656 }
657 }
658 }
659 }
660
661 &-toolbar {
662 margin-bottom: 8px;
663
664 &-title {
665 font-size: 20px;
666 color: var(--am-c-dpf-text);
667 }
668
669 &-chunk {
670 .fc-button {
671 background-color: transparent;
672 border: none;
673 color: var(--am-c-dpf-text);
674 padding: 2px 4px;
675 margin: 0 4px;
676 transition: color 0.3s ease-in-out;
677
678 &:hover {
679 color: var(--am-c-dpf-text-op60);
680 }
681
682 // Visible keyboard focus ring for navigation buttons
683 &:focus {
684 outline: 2px solid var(--am-c-dpf-text);
685 outline-offset: 2px;
686 border: none;
687 box-shadow: none;
688 }
689 }
690 }
691 }
692 }
693
694 th,
695 td {
696 border: none;
697 }
698
699 // Calendar header cell
700 th.#{$amCalClass} {
701 // Month View
702 &__dayGridMonth {
703 &-header {
704 &-cell {
705 font-size: 16px;
706 line-height: 1.5;
707 color: var(--am-c-dpf-text);
708 padding: 4px 6px;
709
710 .fc-col-header-cell-cushion {
711 font-size: var(--am-fs-dpf);
712 text-transform: initial;
713 text-decoration: none;
714 line-height: 1;
715 letter-spacing: 0;
716 color: var(--am-c-dpf-text);
717 padding: 6px 0;
718 white-space: nowrap;
719 }
720 }
721 &-weekend {
722 .fc-col-header-cell-cushion {
723 color: var(--am-c-dpf-text);
724 }
725 }
726 }
727 }
728 }
729
730 // Calendar cell
731 td.#{$amCalClass} {
732 // Month view
733 &__dayGridMonth {
734 // Calendar cell
735 &-cell {
736 position: relative;
737 border: none;
738 padding: 4px 6px;
739
740 // Calendar today day
741 &.fc-day-today {
742 position: relative;
743 background: none;
744
745 // Calendar cell state
746 &.#{$amCalClass} {
747 // Selected cell
748 &__dayGridMonth-selected {
749 .fc-daygrid-day-frame {
750 background-color: var(--am-c-dpf-cell-bgr);
751 border-color: var(--am-c-dpf-cell-border);
752 &:after {
753 background-color: var(--am-c-dpf-cell-text);
754 }
755 }
756 }
757
758 // Disabled cell
759 &__dayGridMonth-disabled {
760 .fc-daygrid-day {
761 &-frame {
762 --am-c-dpf-cell-bgr: var(--am-c-dpf-text-op10);
763 --am-c-dpf-cell-border: transparent;
764 &:hover {
765 --am-c-dpf-cell-bgr: var(--am-c-dpf-text-op10);
766 --am-c-dpf-cell-border: transparent;
767 cursor: not-allowed;
768 }
769 }
770 &-number {
771 --am-c-dpf-cell-text: var(--am-c-dpf-text-op60);
772 }
773 }
774 }
775 }
776
777 .fc-daygrid-day-frame {
778 // Today marker - dot
779 &:after {
780 content: '';
781 display: block;
782 position: absolute;
783 top: 4px;
784 right: 4px;
785 width: 4px;
786 height: 4px;
787 border-radius: 50%;
788 background-color: var(--am-c-primary);
789 }
790 }
791 }
792
793 // Calendar cell state
794 &.#{$amCalClass} {
795 // Disabled cell
796 &__dayGridMonth-disabled {
797 .fc-daygrid-day {
798 &-frame {
799 --am-c-dpf-cell-bgr: var(--am-c-dpf-text-op10);
800 --am-c-dpf-cell-border: transparent;
801 &:hover {
802 --am-c-dpf-cell-bgr: var(--am-c-dpf-text-op10);
803 --am-c-dpf-cell-border: transparent;
804 cursor: not-allowed;
805 }
806 }
807 &-number {
808 --am-c-dpf-cell-text: var(--am-c-dpf-text-op60);
809 }
810 }
811 }
812
813 // Selected cell
814 &__dayGridMonth-selected {
815 .fc-daygrid-day-frame {
816 --am-c-dpf-cell-text: var(--am-c-dpf-bgr);
817 --am-c-dpf-cell-bgr: var(--am-c-primary);
818 --am-c-dpf-cell-border: var(--am-c-primary);
819 &:hover {
820 --am-c-dpf-cell-bgr: var(--am-c-primary-op80);
821 --am-c-dpf-cell-border: var(--am-c-primary-op80);
822 }
823 }
824
825 // Not current month days
826 &.fc-day-other {
827 .fc-daygrid-day-top {
828 opacity: 1;
829 }
830 }
831 }
832
833 // Weekend days cell
834 &__dayGridMonth-weekend {
835 .fc-daygrid-day {
836 &-frame {
837 background-color: transparent;
838 border-color: transparent;
839 &:hover {
840 border-color: transparent;
841 cursor: not-allowed;
842 }
843 }
844 }
845 }
846 }
847
848 // Calendar inner cell items
849 .fc-daygrid-day {
850 // Calendar inner cell wrapper
851 &-frame {
852 position: absolute;
853 width: calc(100% - 9px);
854 height: calc(100% - 5px);
855 min-height: auto;
856 top: 2px;
857 left: 4px;
858 background-color: var(--am-c-dpf-cell-bgr);
859 border: 1px solid var(--am-c-dpf-cell-border);
860 border-radius: 4px;
861 cursor: pointer;
862 &:hover {
863 --am-c-dpf-cell-bgr: var(--am-c-dpf-text);
864 --am-c-dpf-cell-text: var(--am-c-dpf-bgr);
865 --am-c-dpf-cell-border: var(--am-c-dpf-text);
866 transition: all 0.3s ease-in-out;
867 }
868 }
869
870 // Calendar date slot availability wrapper
871 &-bg {
872 .fc-bg-event {
873 background: none;
874 opacity: 1;
875 }
876 }
877
878 // Inner cell date wrapper
879 &-top {
880 position: absolute;
881 top: 50%;
882 left: 50%;
883 transform: translate(-50%, -50%);
884 }
885
886 // Inner cell date holder
887 &-number {
888 color: var(--am-c-dpf-cell-text);
889 line-height: 1;
890 padding: 0;
891 white-space: nowrap;
892 text-decoration: none;
893 }
894 }
895
896 // Not current month days
897 &.fc-day-other {
898 .fc-daygrid-day-top {
899 opacity: 0.7;
900 }
901 }
902
903 // Keyboard focus ring on individual day cells
904 &:focus {
905 outline: none;
906 .fc-daygrid-day-frame {
907 outline: 2px solid var(--am-c-dpf-text);
908 outline-offset: -2px;
909 }
910 }
911 }
912 }
913 }
914 }
915 }
916 }
917 }
918
919 .am-popover-calendar {
920 z-index: 999999999 !important;
921 &.el-popover.el-popper {
922 min-width: auto;
923 background-color: var(--am-c-dpf-bgr);
924 border-color: var(--am-c-dpf-border);
925 padding: 12px;
926 max-width: 460px;
927 }
928
929 @include am-dp-block;
930 }
931
932 @mixin am-dp-input-block {
933 .am-dp__input {
934 &-focused {
935 pointer-events: none;
936 }
937 }
938 }
939 // Public
940 .amelia-v2-booking #amelia-container {
941 @include am-dp-input-block;
942 }
943
944 // Admin
945 #amelia-app-backend-new {
946 @include am-dp-input-block;
947 }
948 </style>
949