DatePicker.vue 3.8 KB

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