PluginProbe
The Innovative Form Builder – IvyForms / 0.3
The Innovative Form Builder – IvyForms v0.3
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.3, at frontend/src/views/_components/datepicker/IvyDatePicker.vue

1,006 lines 24.6 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-brand-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(--map-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-base-brand-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(--map-skeleton) 25%,
395 var(--map-skeleton-to) 37%,
396 var(--map-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-base-brand-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-base-brand-symbol-0);
542 font-weight: 500;
543 }
544 }
545
546 // Current
547 &.current {
548 background: var(--map-base-brand-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(--map-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(--map-hover);
599 }
600
601 // Hover
602 &.available:hover {
603 cursor: pointer;
604 }
605 }
606 }
607 .el-date-range-picker__time-header {
608 border-bottom: 1px solid var(--map-divider);
609
610 .el-input.el-date-range-picker__editor {
611 .el-input__wrapper {
612 background: var(--map-ground-level-2-foreground);
613 }
614 }
615 }
616 }
617
618 .el-picker-panel__footer {
619 border-top: 1px solid var(--map-divider);
620 }
621 }
622
623 // Date Picker
624 .el-date-picker {
625 width: 280px;
626 padding: 16px;
627 box-sizing: content-box;
628
629 // Panel Body
630 .el-picker-panel__body {
631 display: flex;
632 flex-direction: column;
633
634 // Header
635 .el-date-picker__header {
636 height: 24px;
637 box-sizing: content-box;
638 margin: 0;
639 padding: 0 0 16px 0;
640 display: flex;
641 justify-content: center;
642 align-items: center;
643
644 // Label
645 .el-date-picker__header-label {
646 font-size: 16px;
647 font-style: normal;
648 font-weight: 500;
649 padding: 0 2px;
650 color: var(--map-base-text-0);
651
652 // Hover
653 &:hover {
654 color: var(--map-base-brand-symbol-0);
655 }
656 }
657
658 // Prev & Next Button
659 .el-date-picker__prev-btn,
660 .el-date-picker__next-btn {
661 display: flex;
662 line-height: 24px;
663 height: 24px;
664
665 // Icon
666 i {
667 display: none;
668 }
669
670 // Button
671 button {
672 width: 24px;
673 height: 24px;
674 padding: 0;
675 margin: 0;
676 display: flex;
677 border-radius: 4px;
678 text-align: center;
679 align-items: center;
680 justify-content: center;
681
682 // Hover & Active
683 &:hover,
684 &:active {
685 // Before
686 &:before {
687 color: var(--map-base-brand-symbol-0);
688 }
689 }
690
691 // Before
692 &:before {
693 font-family: 'icomoon', serif;
694 font-size: 20px;
695 color: var(--map-base-brand-symbol-0);
696 }
697 }
698
699 // Previous Year
700 .d-arrow-left:before {
701 content: '';
702 display: inline-block;
703 width: 20px;
704 height: 20px;
705 background-image: url('@/assets/icons/arrows/outline/arrow-double-left.svg');
706 background-size: contain;
707 background-repeat: no-repeat;
708 }
709
710 // Previous Month
711 .arrow-left:before {
712 content: '';
713 display: inline-block;
714 width: 20px;
715 height: 20px;
716 background-image: url('@/assets/icons/arrows/outline/chevron-left.svg');
717 background-size: contain;
718 background-repeat: no-repeat;
719 }
720
721 // Next Year
722 .d-arrow-right:before {
723 content: '';
724 display: inline-block;
725 width: 20px;
726 height: 20px;
727 background-image: url('@/assets/icons/arrows/outline/arrow-double-right.svg');
728 background-size: contain;
729 background-repeat: no-repeat;
730 }
731
732 // Next Month
733 .arrow-right:before {
734 content: '';
735 display: inline-block;
736 width: 20px;
737 height: 20px;
738 background-image: url('@/assets/icons/arrows/outline/chevron-right.svg');
739 background-size: contain;
740 background-repeat: no-repeat;
741 }
742 }
743
744 // Prev Button
745 .el-date-picker__prev-btn {
746 @include flipProperty('margin-right', 'margin-left', auto);
747 }
748
749 // Next Button
750 .el-date-picker__next-btn {
751 @include flipProperty('margin-left', 'margin-right', auto);
752 }
753 }
754
755 // Content
756 .el-picker-panel__content {
757 margin: 0;
758 width: 100%;
759 }
760 }
761 }
762
763 // Date Range Popper
764 &.ivyforms-date-range-popper {
765 @media only screen and (max-width: 640px) {
766 width: calc(100% - 12px);
767 }
768 }
769
770 // Date Range
771 .el-date-range-picker.has-sidebar {
772 width: 100%;
773 }
774 .el-date-range-picker {
775 width: 625px;
776
777 @media only screen and (max-width: 640px) {
778 width: 100%;
779 display: flex;
780 flex-direction: column;
781 }
782
783 // Panel Body
784 .el-picker-panel__body {
785 @media only screen and (max-width: 640px) {
786 width: 100%;
787 min-width: unset;
788 display: flex;
789 flex-direction: column;
790 }
791
792 // Content
793 .el-picker-panel__content {
794 width: 280px;
795 box-sizing: content-box;
796
797 @media only screen and (max-width: 640px) {
798 width: 100%;
799 box-sizing: border-box;
800 }
801
802 // Left
803 &.is-left {
804 border-right: 1px solid var(--map-divider);
805 @media only screen and (max-width: 640px) {
806 border: none;
807 }
808 }
809
810 // Header
811 .el-date-range-picker__header {
812 height: 24px;
813 box-sizing: content-box;
814 padding-bottom: 16px;
815
816 // Label
817 div {
818 font-size: 16px;
819 font-style: normal;
820 font-weight: 500;
821 padding: 0 2px;
822 line-height: 24px;
823 color: var(--map-base-text-0);
824 }
825
826 // Prev & Next Button
827 .el-picker-panel__icon-btn {
828 line-height: 24px;
829 width: 24px;
830 height: 24px;
831 padding: 0;
832 margin: 0;
833 display: flex;
834 border-radius: 4px;
835 text-align: center;
836 align-items: center;
837 justify-content: center;
838
839 // Icon
840 i {
841 display: none;
842 }
843
844 // Hover & Active
845 &:hover,
846 &:active {
847 // Before
848 &:before {
849 color: var(--map-base-brand-symbol-0);
850 }
851 }
852
853 // Before
854 &:before {
855 font-family: 'icomoon', serif;
856 font-size: 20px;
857 color: var(--map-base-brand-symbol-0);
858 }
859
860 /// Previous Year
861 &.d-arrow-left:before {
862 content: '';
863 display: inline-block;
864 width: 20px;
865 height: 20px;
866 background-image: url('@/assets/icons/arrows/outline/arrow-double-left.svg');
867 background-size: contain;
868 background-repeat: no-repeat;
869 }
870
871 // Previous Month
872 &.arrow-left:before {
873 content: '';
874 display: inline-block;
875 width: 20px;
876 height: 20px;
877 background-image: url('@/assets/icons/arrows/outline/chevron-left.svg');
878 background-size: contain;
879 background-repeat: no-repeat;
880 }
881
882 // Next Year
883 &.d-arrow-right:before {
884 content: '';
885 display: inline-block;
886 width: 20px;
887 height: 20px;
888 background-image: url('@/assets/icons/arrows/outline/arrow-double-right.svg');
889 background-size: contain;
890 background-repeat: no-repeat;
891 }
892
893 // Next Month
894 &.arrow-right:before {
895 content: '';
896 display: inline-block;
897 width: 20px;
898 height: 20px;
899 background-image: url('@/assets/icons/arrows/outline/chevron-right.svg');
900 background-size: contain;
901 background-repeat: no-repeat;
902 }
903 }
904 }
905
906 // Table cell
907 .el-date-table-cell {
908 &__text {
909 @media only screen and (max-width: 640px) {
910 width: 100%;
911 border-radius: 12px;
912 }
913 }
914 }
915 }
916 }
917 }
918
919 // Popper Arrow
920 .el-popper__arrow:before {
921 content: none;
922 border: none;
923 }
924 .el-icon svg {
925 display: contents;
926 }
927 .el-button.is-plain {
928 border: 1px solid var(--map-base-brand-fill-0);
929 span {
930 color: var(--map-base-brand-fill-0);
931 }
932 }
933
934 .el-date-range-picker__editors-wrap {
935 > .el-date-range-picker__time-picker-wrap:first-child {
936 width: 100%;
937 }
938
939 > .el-date-range-picker__time-picker-wrap:nth-child(2) {
940 display: none;
941 }
942
943 &.is-right {
944 position: relative;
945 left: -8px;
946 }
947 .el-input {
948 .el-input__wrapper {
949 height: 20px;
950 border: 1px solid var(--map-base-dusk-stroke-1);
951 box-shadow: none;
952 display: flex;
953 border-radius: var(--Radius-border-radius-base, 4px);
954
955 .el-input__inner {
956 justify-content: center;
957 align-items: center;
958 flex: 1 0 0;
959 color: var(--map-base-text--2);
960
961 font-family: Roboto;
962 font-size: 12px;
963 font-style: normal;
964 font-weight: 400;
965 line-height: 16px; /* 133.333% */
966
967 &::placeholder {
968 text-align: center;
969 }
970 }
971
972 &:hover,
973 &.is-hover {
974 border: 1px solid var(--map-base-brand-stroke-0);
975 background: transparent;
976 }
977
978 input {
979 color: var(--map-base-text-0);
980 border: none;
981 box-shadow: none;
982 background-color: transparent;
983 display: flex;
984 }
985
986 &:active,
987 &:focus,
988 &.is-focus,
989 &.is-active {
990 border: 1px solid var(--map-base-brand-stroke-0);
991 background: transparent;
992 box-shadow: none;
993 }
994 }
995 }
996 .el-date-range-picker__time-picker-wrap {
997 display: flex;
998 }
999
1000 .el-input.el-input--small.el-date-range-picker__editor {
1001 flex-grow: 1;
1002 }
1003 }
1004 }
1005 </style>
1006