PluginProbe
The Innovative Form Builder – IvyForms / 0.2
The Innovative Form Builder – IvyForms v0.2
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / views / _components / datepicker / IvyDatePicker.vue

IvyDatePicker.vue in The Innovative Form Builder – IvyForms 0.2, at frontend/src/views/_components/datepicker/IvyDatePicker.vue

1,000 lines 24.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div ref="ivyDatePickerRef" class="ivyforms-date-picker">
3 <ElConfigProvider>
4 <ElDatePicker
5 v-model="localModelValue"
6 class="ivyforms-input ivyforms-input__size__default"
7 :class="{
8 'has-value': Array.isArray(localModelValue)
9 ? localModelValue.length > 0
10 : localModelValue,
11 'is-clearable': props.clearable,
12 }"
13 v-bind="{ ...$props, ...$attrs }"
14 :popper-class="`ivyforms-date-picker-popper ${props.type === 'daterange' ? 'ivyforms-date-range-popper' : ''} ${props.popperBackground}`"
15 :popper-options="{ placement: 'bottom-start' }"
16 :prefix-icon="prefixIconComponent"
17 :range-separator="props.rangeSeparator"
18 :shortcuts="props.shortcuts ? localShortcuts : undefined"
19 :cell-class-name="
20 (date: Date) => {
21 const baseClass = props.cellClassName?.(date) || ''
22 const loadingClass = props.loading ? 'is-loading' : ''
23
24 return [baseClass, loadingClass].filter(Boolean).join(' ')
25 }
26 "
27 @change="() => emit('change', localModelValue)"
28 />
29 </ElConfigProvider>
30 </div>
31 </template>
32
33 <script setup lang="ts">
34 import IvyIcon from '@/views/_components/icon/IvyIcon.vue'
35 import { dayjs } from 'element-plus'
36 import { computed, h, nextTick, ref, shallowRef } from 'vue'
37
38 interface Props {
39 modelValue?: string | number | Date | [Date, Date] | [string, string] | null
40 disabled?: boolean
41 clearable?: boolean
42 weekStart?: number
43 prefixIcon?: string
44 prefixIconColor?: string
45 prefixIconColorWhenActive?: string
46 clearIconType?: 'fill' | 'outline'
47 type?:
48 | 'year'
49 | 'years'
50 | 'month'
51 | 'months'
52 | 'date'
53 | 'dates'
54 | 'datetime'
55 | 'week'
56 | 'datetimerange'
57 | 'daterange'
58 | 'monthrange'
59 | 'yearrange'
60 popperBackground?: 'level-2-foreground' | 'level-1-foreground'
61 rangeSeparator?: string
62 loading?: boolean
63 cellClassName?: (date: Date) => string
64 shortcuts?: Array<{ text: string; value: Date }>
65 }
66
67 const props = withDefaults(defineProps<Props>(), {
68 modelValue: null,
69 weekStart: 1,
70 prefixIconColorWhenActive: undefined,
71 cellClassName: undefined,
72 shortcuts: undefined,
73 prefixIcon: 'calendar-mark',
74 prefixIconColor: 'var(--map-base-danube-symbol-2)',
75 clearIconType: 'outline',
76 type: 'date',
77 popperBackground: 'level-2-foreground',
78 rangeSeparator: '-',
79 })
80 const localShortcuts = [
81 {
82 text: 'Today',
83 value: new Date(),
84 },
85 {
86 text: 'Yesterday',
87 value: () => {
88 const date = new Date()
89 date.setTime(date.getTime() - 3600 * 1000 * 24)
90 return date
91 },
92 },
93 {
94 text: 'Last week',
95 value: () => {
96 const end = new Date()
97 const start = new Date()
98 start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
99 return [start, end]
100 },
101 },
102 {
103 text: 'Last month',
104 value: () => {
105 const end = new Date()
106 const start = new Date()
107 start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
108 return [start, end]
109 },
110 },
111 {
112 text: 'Last 3 months',
113 value: () => {
114 const end = new Date()
115 const start = new Date()
116 start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
117 return [start, end]
118 },
119 },
120 {
121 text: 'This month',
122 value: [new Date(), new Date()],
123 },
124 {
125 text: 'This year',
126 value: () => {
127 const end = new Date()
128 const start = new Date(new Date().getFullYear(), 0)
129 return [start, end]
130 },
131 },
132 {
133 text: 'Last 6 months',
134 value: () => {
135 const end = new Date()
136 const start = new Date()
137 start.setMonth(start.getMonth() - 6)
138 return [start, end]
139 },
140 },
141 ]
142
143 const emit = defineEmits(['update:modelValue', 'change'])
144
145 const ivyDatePickerRef = ref()
146
147 const prefixIconComponent = props.prefixIcon
148 ? shallowRef({
149 render() {
150 return h(IvyIcon, {
151 name: props.prefixIcon,
152 color: props.prefixIconColor,
153 })
154 },
155 })
156 : null
157
158 dayjs['en'].weekStart = props.weekStart !== undefined ? props.weekStart : 1
159
160 const localModelValue = computed({
161 get() {
162 return props.modelValue
163 },
164 set(value) {
165 emit('update:modelValue', value)
166 nextTick(() => {
167 const datePickerInput = ivyDatePickerRef.value.querySelector('.el-input__inner')
168 const dateRangePickerInputs = ivyDatePickerRef.value.querySelectorAll('.el-range-input')
169
170 if (datePickerInput) {
171 datePickerInput.blur()
172 } else if (dateRangePickerInputs.length) {
173 dateRangePickerInputs.forEach((input: { blur: () => void }) => input.blur())
174 }
175 })
176 },
177 })
178
179 const prefixIconColorWhenActiveComputed = computed(() => {
180 return props.prefixIconColorWhenActive || props.prefixIconColor
181 })
182 </script>
183
184 <style lang="scss">
185 @use 'sass:list' as *;
186 @use 'sass:meta' as *;
187 // Date Picker
188 .ivyforms-date-picker {
189 width: 100%;
190 height: 40px;
191
192 // Date Editor
193 .el-date-editor {
194 // Input
195 &.el-input {
196 width: 100%;
197 height: 40px;
198 }
199 }
200
201 // Input
202 .ivyforms-input {
203 width: 100%;
204 height: 40px;
205
206 // Input Wrapper
207 .el-input__wrapper,
208 &.el-input__wrapper {
209 display: flex;
210 border-radius: 8px;
211 box-shadow: none;
212 padding: 0 12px;
213 overflow: hidden;
214 background-color: transparent;
215 outline: none;
216 gap: 4px;
217 height: 40px;
218 box-sizing: border-box;
219 width: 100%;
220
221 // Hover
222 &:hover {
223 box-shadow: none;
224 }
225
226 // Input
227 input {
228 padding: 0;
229 font-size: 16px;
230 font-style: normal;
231 font-weight: 400;
232 line-height: 22px;
233 border: none;
234 box-shadow: none;
235 background-color: transparent;
236 }
237
238 // Range Input
239 .el-range-input {
240 text-overflow: ellipsis;
241 overflow: hidden;
242 white-space: nowrap;
243 }
244
245 // Icon
246 .el-input__icon {
247 width: 24px;
248 }
249 }
250
251 // Prefix
252 .el-input__prefix {
253 display: none;
254 }
255
256 // Range Separator
257 .el-range-separator {
258 display: none;
259 }
260 }
261
262 // Has Value
263 .has-value {
264 // Input Wrapper
265 .el-input__wrapper,
266 &.el-input__wrapper {
267 // Range Icon
268 .el-range__icon svg {
269 fill: v-bind(prefixIconColorWhenActiveComputed);
270 }
271 }
272
273 // Clearable
274 &.is-clearable {
275 // Input Wrapper
276 .el-input__wrapper,
277 &.el-input__wrapper {
278 // Close Icon
279 .el-range__close-icon--hidden {
280 opacity: 1;
281 visibility: visible;
282
283 // Tablet & Up
284 @include tablet-and-up {
285 opacity: 0;
286 visibility: hidden;
287 }
288 }
289 }
290 }
291 }
292 }
293
294 // Date Picker Popper
295 .ivyforms-date-picker-popper.el-popper {
296 display: flex;
297 flex-direction: column;
298 align-items: center;
299 border: none !important;
300 box-shadow:
301 0px 12px 32px 4px rgba(0, 0, 0, 0.04),
302 0px 8px 20px 0px rgba(0, 0, 0, 0.08) !important;
303 border-radius: 4px;
304 background: var(--map-ground-level-2-foreground);
305
306 // Level 1 Foreground
307 &.level-1-foreground {
308 background: var(--map-ground-level-1-foreground);
309
310 // Date Picker Panel
311 .el-picker-panel {
312 background: var(--map-ground-level-1-foreground);
313 }
314 }
315
316 // Level 2 Foreground
317 &.level-2-foreground {
318 background: var(--map-ground-level-2-foreground);
319
320 // Date Picker Panel
321 .el-picker-panel {
322 background: var(--map-ground-level-2-foreground);
323 &__footer {
324 background: var(--map-ground-level-2-foreground);
325 }
326 }
327 }
328
329 // Date Picker Panel
330 .el-picker-panel {
331 border-radius: 4px;
332
333 // Panel Body
334 .el-picker-panel__body {
335 // Content
336 .el-picker-panel__content {
337 // Date Table
338 .el-date-table {
339 // Table Body
340 tbody {
341 display: flex;
342 flex-direction: column;
343
344 // Table Row
345 tr {
346 display: flex;
347
348 // Header
349 th {
350 font-size: 12px;
351 font-style: normal;
352 font-weight: 400;
353 color: var(--map-base-text--1);
354 border-bottom: solid 1px var(--theme-divider);
355 padding: 3px 8px;
356 box-sizing: border-box;
357 flex: 1;
358 }
359
360 // Days
361 td {
362 width: 40px;
363 height: 36px;
364 padding: 0;
365 flex: 1;
366 color: var(--map-base-text-0);
367 border-radius: 2px;
368 @include transition(0.1s);
369
370 // Hover
371 &:hover {
372 color: var(--map-brand-primary-symbol-0) !important;
373 }
374
375 // Prev & Next Month
376 &.prev-month,
377 &.next-month {
378 color: var(--map-base-text-0);
379 opacity: 0.5;
380 }
381
382 // Loading
383 &.is-loading {
384 opacity: 1 !important;
385
386 // Date Table Cell
387 .el-date-table-cell {
388 background-color: unset !important;
389
390 // Date Table Cell Text
391 .el-date-table-cell__text {
392 background: linear-gradient(
393 90deg,
394 var(--theme-skeleton) 25%,
395 var(--theme-skeleton-to) 37%,
396 var(--theme-skeleton) 63%
397 );
398 background-color: unset !important;
399 background-size: 400% 100%;
400 cursor: not-allowed;
401 content-visibility: hidden;
402 border-radius: 4px;
403 animation: el-skeleton-loading 1.4s ease infinite;
404 color: var(--map-base-text-0);
405 }
406 }
407 }
408
409 // Disabled
410 &.disabled {
411 // Cell
412 .el-date-table-cell {
413 background: var(--map-base-dusk-o90);
414
415 // Cell Text
416 .el-date-table-cell__text {
417 color: var(--map-base-text-0);
418 }
419 }
420 }
421 }
422 }
423
424 // Date Row
425 .el-date-table__row {
426 display: flex;
427
428 // TD
429 td {
430 height: 36px;
431 display: flex;
432 justify-content: center;
433 align-items: center;
434
435 // Today
436 &.today {
437 // Cell
438 .el-date-table-cell {
439 // Cell Text
440 .el-date-table-cell__text {
441 font-weight: 500;
442 color: var(--map-brand-primary-symbol-0);
443 }
444 }
445 }
446
447 // Current
448 &.current {
449 color: $primitive-white;
450 // Cell
451 .el-date-table-cell {
452 // Cell Text
453 .el-date-table-cell__text {
454 color: $primitive-white;
455 background-color: var(--map-base-brand-symbol-0);
456 }
457 }
458 }
459
460 // Date Cell
461 .el-date-table-cell {
462 padding: 0;
463 height: 28px;
464 display: flex;
465 align-items: center;
466 justify-content: center;
467 line-height: 36px;
468 width: 100%;
469
470 // Cell Text
471 .el-date-table-cell__text {
472 font-size: 12px;
473 font-style: normal;
474 font-weight: 400;
475 }
476 }
477
478 // In Range
479 &.in-range {
480 // Cell
481 .el-date-table-cell {
482 background-color: var(--map-base-brand-o10);
483 }
484 }
485
486 // Start & End Dates
487 &.start-date,
488 &.end-date {
489 // Cell
490 .el-date-table-cell__text {
491 background-color: var(--map-base-brand-symbol-0);
492 color: $primitive-white;
493 }
494
495 // Today
496 &.today {
497 // Cell
498 .el-date-table-cell {
499 // Cell Text
500 .el-date-table-cell__text {
501 color: $primitive-white;
502 }
503 }
504 }
505 }
506 }
507 }
508 }
509 }
510
511 // Moth & Year Tables
512 .el-month-table,
513 .el-year-table {
514 display: flex;
515 padding-top: 4px;
516
517 // Body
518 tbody {
519 flex-direction: column;
520 display: flex;
521 flex: 1;
522 gap: 8px;
523
524 // Table Row
525 tr {
526 display: flex;
527
528 // Data Cell
529 td {
530 padding: 0;
531 border-radius: 4px;
532 display: flex;
533 cursor: default;
534 width: 70px;
535 @include transition(0.1s);
536
537 // Today
538 &.today {
539 // Cell Text
540 .el-date-table-cell__text {
541 color: var(--map-brand-primary-symbol-0);
542 font-weight: 500;
543 }
544 }
545
546 // Current
547 &.current {
548 background: var(--map-brand-primary-fill-0);
549
550 // Cell Text
551 .el-date-table-cell__text {
552 color: $primitive-white;
553 background: var(--map-base-brand-symbol-0);
554 }
555 }
556
557 // Div
558 div {
559 padding: 0;
560 height: 28px;
561 }
562
563 // Span
564 span {
565 line-height: 28px;
566 height: 28px;
567 color: var(--map-base-text-0);
568 padding: 0;
569 display: flex;
570 align-items: center;
571 font-size: 12px;
572 font-style: normal;
573 font-weight: 400;
574 margin: 0;
575 justify-content: center;
576 flex: 1;
577 border-radius: 8px;
578 }
579 }
580 }
581 }
582 }
583
584 // Moth Table Cell
585 .el-month-table td {
586 // Hover
587 &:hover {
588 background: var(--theme-hover);
589 cursor: pointer;
590 }
591 }
592
593 // Year Table Cell
594 .el-year-table td {
595 // Hover
596 &.available:not(.current):hover {
597 cursor: pointer;
598 background: var(--theme-hover);
599 }
600
601 // Hover
602 &.available:hover {
603 cursor: pointer;
604 }
605 }
606 }
607 .el-date-range-picker__time-header {
608 .el-input.el-date-range-picker__editor {
609 .el-input__wrapper {
610 background: var(--map-ground-level-2-foreground);
611 }
612 }
613 }
614 }
615 }
616
617 // Date Picker
618 .el-date-picker {
619 width: 280px;
620 padding: 16px;
621 box-sizing: content-box;
622
623 // Panel Body
624 .el-picker-panel__body {
625 display: flex;
626 flex-direction: column;
627
628 // Header
629 .el-date-picker__header {
630 height: 24px;
631 box-sizing: content-box;
632 margin: 0;
633 padding: 0 0 16px 0;
634 display: flex;
635 justify-content: center;
636 align-items: center;
637
638 // Label
639 .el-date-picker__header-label {
640 font-size: 16px;
641 font-style: normal;
642 font-weight: 500;
643 padding: 0 2px;
644 color: var(--map-base-text-0);
645
646 // Hover
647 &:hover {
648 color: var(--map-brand-primary-symbol-0);
649 }
650 }
651
652 // Prev & Next Button
653 .el-date-picker__prev-btn,
654 .el-date-picker__next-btn {
655 display: flex;
656 line-height: 24px;
657 height: 24px;
658
659 // Icon
660 i {
661 display: none;
662 }
663
664 // Button
665 button {
666 width: 24px;
667 height: 24px;
668 padding: 0;
669 margin: 0;
670 display: flex;
671 border-radius: 4px;
672 text-align: center;
673 align-items: center;
674 justify-content: center;
675
676 // Hover & Active
677 &:hover,
678 &:active {
679 // Before
680 &:before {
681 color: var(--map-brand-primary-symbol-0);
682 }
683 }
684
685 // Before
686 &:before {
687 font-family: 'icomoon', serif;
688 font-size: 20px;
689 color: var(--map-brand-primary-symbol-0);
690 }
691 }
692
693 // Previous Year
694 .d-arrow-left:before {
695 content: '';
696 display: inline-block;
697 width: 20px;
698 height: 20px;
699 background-image: url('@/assets/icons/arrows/outline/arrow-double-left.svg');
700 background-size: contain;
701 background-repeat: no-repeat;
702 }
703
704 // Previous Month
705 .arrow-left:before {
706 content: '';
707 display: inline-block;
708 width: 20px;
709 height: 20px;
710 background-image: url('@/assets/icons/arrows/outline/chevron-left.svg');
711 background-size: contain;
712 background-repeat: no-repeat;
713 }
714
715 // Next Year
716 .d-arrow-right:before {
717 content: '';
718 display: inline-block;
719 width: 20px;
720 height: 20px;
721 background-image: url('@/assets/icons/arrows/outline/arrow-double-right.svg');
722 background-size: contain;
723 background-repeat: no-repeat;
724 }
725
726 // Next Month
727 .arrow-right:before {
728 content: '';
729 display: inline-block;
730 width: 20px;
731 height: 20px;
732 background-image: url('@/assets/icons/arrows/outline/chevron-right.svg');
733 background-size: contain;
734 background-repeat: no-repeat;
735 }
736 }
737
738 // Prev Button
739 .el-date-picker__prev-btn {
740 @include flipProperty('margin-right', 'margin-left', auto);
741 }
742
743 // Next Button
744 .el-date-picker__next-btn {
745 @include flipProperty('margin-left', 'margin-right', auto);
746 }
747 }
748
749 // Content
750 .el-picker-panel__content {
751 margin: 0;
752 width: 100%;
753 }
754 }
755 }
756
757 // Date Range Popper
758 &.ivyforms-date-range-popper {
759 @media only screen and (max-width: 640px) {
760 width: calc(100% - 12px);
761 }
762 }
763
764 // Date Range
765 .el-date-range-picker.has-sidebar {
766 width: 100%;
767 }
768 .el-date-range-picker {
769 width: 625px;
770
771 @media only screen and (max-width: 640px) {
772 width: 100%;
773 display: flex;
774 flex-direction: column;
775 }
776
777 // Panel Body
778 .el-picker-panel__body {
779 @media only screen and (max-width: 640px) {
780 width: 100%;
781 min-width: unset;
782 display: flex;
783 flex-direction: column;
784 }
785
786 // Content
787 .el-picker-panel__content {
788 width: 280px;
789 box-sizing: content-box;
790
791 @media only screen and (max-width: 640px) {
792 width: 100%;
793 box-sizing: border-box;
794 }
795
796 // Left
797 &.is-left {
798 border-right: 1px solid var(--theme-divider);
799 @media only screen and (max-width: 640px) {
800 border: none;
801 }
802 }
803
804 // Header
805 .el-date-range-picker__header {
806 height: 24px;
807 box-sizing: content-box;
808 padding-bottom: 16px;
809
810 // Label
811 div {
812 font-size: 16px;
813 font-style: normal;
814 font-weight: 500;
815 padding: 0 2px;
816 line-height: 24px;
817 color: var(--map-base-text-0);
818 }
819
820 // Prev & Next Button
821 .el-picker-panel__icon-btn {
822 line-height: 24px;
823 width: 24px;
824 height: 24px;
825 padding: 0;
826 margin: 0;
827 display: flex;
828 border-radius: 4px;
829 text-align: center;
830 align-items: center;
831 justify-content: center;
832
833 // Icon
834 i {
835 display: none;
836 }
837
838 // Hover & Active
839 &:hover,
840 &:active {
841 // Before
842 &:before {
843 color: var(--map-brand-primary-symbol-0);
844 }
845 }
846
847 // Before
848 &:before {
849 font-family: 'icomoon', serif;
850 font-size: 20px;
851 color: var(--map-brand-primary-symbol-0);
852 }
853
854 /// Previous Year
855 &.d-arrow-left:before {
856 content: '';
857 display: inline-block;
858 width: 20px;
859 height: 20px;
860 background-image: url('@/assets/icons/arrows/outline/arrow-double-left.svg');
861 background-size: contain;
862 background-repeat: no-repeat;
863 }
864
865 // Previous Month
866 &.arrow-left:before {
867 content: '';
868 display: inline-block;
869 width: 20px;
870 height: 20px;
871 background-image: url('@/assets/icons/arrows/outline/chevron-left.svg');
872 background-size: contain;
873 background-repeat: no-repeat;
874 }
875
876 // Next Year
877 &.d-arrow-right:before {
878 content: '';
879 display: inline-block;
880 width: 20px;
881 height: 20px;
882 background-image: url('@/assets/icons/arrows/outline/arrow-double-right.svg');
883 background-size: contain;
884 background-repeat: no-repeat;
885 }
886
887 // Next Month
888 &.arrow-right:before {
889 content: '';
890 display: inline-block;
891 width: 20px;
892 height: 20px;
893 background-image: url('@/assets/icons/arrows/outline/chevron-right.svg');
894 background-size: contain;
895 background-repeat: no-repeat;
896 }
897 }
898 }
899
900 // Table cell
901 .el-date-table-cell {
902 &__text {
903 @media only screen and (max-width: 640px) {
904 width: 100%;
905 border-radius: 12px;
906 }
907 }
908 }
909 }
910 }
911 }
912
913 // Popper Arrow
914 .el-popper__arrow:before {
915 content: none;
916 border: none;
917 }
918 .el-icon svg {
919 display: contents;
920 }
921 .el-button.is-plain {
922 border: 1px solid var(--map-base-brand-fill-0);
923 span {
924 color: var(--map-base-brand-fill-0);
925 }
926 }
927
928 .el-date-range-picker__editors-wrap {
929 > .el-date-range-picker__time-picker-wrap:first-child {
930 width: 100%;
931 }
932
933 > .el-date-range-picker__time-picker-wrap:nth-child(2) {
934 display: none;
935 }
936
937 &.is-right {
938 position: relative;
939 left: -8px;
940 }
941 .el-input {
942 .el-input__wrapper {
943 height: 20px;
944 border: 1px solid var(--map-base-dusk-stroke-1);
945 box-shadow: none;
946 display: flex;
947 border-radius: var(--Radius-border-radius-base, 4px);
948
949 .el-input__inner {
950 justify-content: center;
951 align-items: center;
952 flex: 1 0 0;
953 color: var(--map-base-text--2);
954
955 font-family: Roboto;
956 font-size: 12px;
957 font-style: normal;
958 font-weight: 400;
959 line-height: 16px; /* 133.333% */
960
961 &::placeholder {
962 text-align: center;
963 }
964 }
965
966 &:hover,
967 &.is-hover {
968 border: 1px solid var(--map-base-brand-stroke-0);
969 background: transparent;
970 }
971
972 input {
973 color: var(--map-base-text-0);
974 border: none;
975 box-shadow: none;
976 background-color: transparent;
977 display: flex;
978 }
979
980 &:active,
981 &:focus,
982 &.is-focus,
983 &.is-active {
984 border: 1px solid var(--map-base-brand-stroke-0);
985 background: transparent;
986 box-shadow: none;
987 }
988 }
989 }
990 .el-date-range-picker__time-picker-wrap {
991 display: flex;
992 }
993
994 .el-input.el-input--small.el-date-range-picker__editor {
995 flex-grow: 1;
996 }
997 }
998 }
999 </style>
1000