DatePicker.vue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. <!--
  2. Sélecteur de dates, à placer dans un composant `UiForm`
  3. -->
  4. <template>
  5. <main>
  6. <div class="d-flex flex-column container mb-6">
  7. <span class="label">
  8. {{ $t(fieldLabel) }}
  9. </span>
  10. <v-validation
  11. v-slot="{ errorMessages }"
  12. v-model="date"
  13. :rules="rules"
  14. :error="error || !!fieldViolations"
  15. :error-messages="
  16. errorMessage || (fieldViolations ? $t(fieldViolations) : '')
  17. "
  18. :validate-on="'lazy input'"
  19. >
  20. <UiDatePicker
  21. v-model="date"
  22. :readonly="readonly"
  23. :with-time-picker="withTimePicker"
  24. class="date-picker"
  25. @update:model-value="onUpdate($event)"
  26. />
  27. <div
  28. v-if="errorMessages.value.length > 0"
  29. class="v-input__details error_message"
  30. >
  31. <div class="v-messages__message">
  32. <span v-for="(msg, i) in errorMessages.value" :key="i">
  33. {{ msg }}
  34. </span>
  35. </div>
  36. </div>
  37. </v-validation>
  38. </div>
  39. </main>
  40. </template>
  41. <script setup lang="ts">
  42. import { formatISO } from 'date-fns'
  43. import type { PropType, Ref } from 'vue'
  44. import { ref } from 'vue'
  45. import { useFieldViolation } from '~/composables/form/useFieldViolation'
  46. const props = defineProps({
  47. /**
  48. * v-model
  49. */
  50. modelValue: {
  51. type: String as PropType<Date | string | null>,
  52. required: false,
  53. default: null,
  54. },
  55. /**
  56. * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
  57. * - Utilisé par la validation
  58. * - Laisser null si le champ ne s'applique pas à une entité
  59. */
  60. field: {
  61. type: String,
  62. required: false,
  63. default: null,
  64. },
  65. /**
  66. * Label du champ
  67. * Si non défini, c'est le nom de propriété qui est utilisé
  68. */
  69. label: {
  70. type: String,
  71. required: false,
  72. default: null,
  73. },
  74. /**
  75. * Définit si le champ est en lecture seule
  76. */
  77. readonly: {
  78. type: Boolean,
  79. required: false,
  80. },
  81. /**
  82. * Règles de validation
  83. * @see https://vuetify.cn/en/components/forms/#validation-with-submit-clear
  84. */
  85. rules: {
  86. type: Array,
  87. required: false,
  88. default: () => [],
  89. },
  90. /**
  91. * Le champ est-il actuellement en état d'erreur
  92. */
  93. error: {
  94. type: Boolean,
  95. required: false,
  96. },
  97. /**
  98. * Si le champ est en état d'erreur, quel est le message d'erreur?
  99. */
  100. errorMessage: {
  101. type: String,
  102. required: false,
  103. default: null,
  104. },
  105. /**
  106. * Si on souhaite avoir le time picker avec
  107. */
  108. withTimePicker: {
  109. type: Boolean,
  110. default: false,
  111. },
  112. })
  113. const { fieldViolations, updateViolationState } = useFieldViolation(props.field)
  114. const fieldLabel = props.label ?? props.field
  115. const emit = defineEmits(['update:model-value', 'change'])
  116. const date: Ref<Date | undefined> = ref(
  117. props.modelValue ? new Date(props.modelValue) : undefined,
  118. )
  119. const onUpdate = (event: string) => {
  120. updateViolationState()
  121. date.value = event ? new Date(event) : undefined
  122. emit('update:model-value', date.value ? formatISO(date.value) : undefined)
  123. }
  124. onBeforeUnmount(() => {
  125. updateViolationState()
  126. })
  127. </script>
  128. <style scoped lang="scss">
  129. .container {
  130. position: relative;
  131. }
  132. .label {
  133. position: absolute;
  134. color: #8e8e8e;
  135. top: -0.7rem;
  136. left: 0.75rem;
  137. background-color: rgb(var(--v-theme-surface));
  138. padding: 0 0.3rem;
  139. font-size: 0.8rem;
  140. z-index: 1;
  141. transition:
  142. color 0.2s,
  143. font-size 0.2s,
  144. top 0.2s;
  145. }
  146. .date-picker:hover {
  147. :deep(.dp__input) {
  148. border-color: #333333;
  149. }
  150. }
  151. .container:focus-within {
  152. .label {
  153. color: #333333;
  154. }
  155. :deep(.dp__input_focus) {
  156. border: solid 2px #333333;
  157. }
  158. :deep(.dp__input_icon) {
  159. color: #333333;
  160. }
  161. }
  162. .error_message {
  163. padding-inline: 16px;
  164. }
  165. .v-messages__message {
  166. color: rgb(var(--v-theme-error));
  167. }
  168. </style>