@propelinc/citrus-ui 1.0.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/README.md +51 -14
  2. package/dist/citrus-ui.cdn.css +1 -0
  3. package/dist/citrus-ui.css +1 -0
  4. package/dist/colors/colors.d.ts +31 -0
  5. package/dist/colors/theme.d.ts +3 -0
  6. package/dist/colors/util-classes.d.ts +11 -0
  7. package/dist/components/CAccordion.vue.d.ts +34 -0
  8. package/dist/components/CAccordionItem.vue.d.ts +39 -0
  9. package/dist/components/CAppBar.vue.d.ts +59 -0
  10. package/dist/components/CBadge.vue.d.ts +35 -0
  11. package/dist/components/CBottomSheet.vue.d.ts +90 -0
  12. package/dist/components/CButton/CButton.vue.d.ts +97 -0
  13. package/dist/components/CButton/types.d.ts +5 -0
  14. package/dist/components/CButtonStack.vue.d.ts +27 -0
  15. package/dist/components/CCard.vue.d.ts +53 -0
  16. package/dist/components/CCardFooter.vue.d.ts +20 -0
  17. package/dist/components/CCardHeader.vue.d.ts +22 -0
  18. package/dist/components/CCardSection.vue.d.ts +26 -0
  19. package/dist/components/CCheckbox.vue.d.ts +62 -0
  20. package/dist/components/CCol.vue.d.ts +30 -0
  21. package/dist/components/CDivider.vue.d.ts +9 -0
  22. package/dist/components/CDobField.vue.d.ts +60 -0
  23. package/dist/components/CDobSelect.vue.d.ts +50 -0
  24. package/dist/components/CEmailField.vue.d.ts +48 -0
  25. package/dist/components/CExpandTransition.vue.d.ts +29 -0
  26. package/dist/components/CFadeTransition.vue.d.ts +20 -0
  27. package/dist/components/CFileInput.vue.d.ts +50 -0
  28. package/dist/components/CFixedPageFooter.vue.d.ts +153 -0
  29. package/dist/components/CForm.vue.d.ts +44 -0
  30. package/dist/components/CFormFieldCounter.vue.d.ts +15 -0
  31. package/dist/components/CIconButton.vue.d.ts +97 -0
  32. package/dist/components/CLabel.vue.d.ts +36 -0
  33. package/dist/components/CListItem.vue.d.ts +56 -0
  34. package/dist/components/CListItemContent.vue.d.ts +27 -0
  35. package/dist/components/CListItemIcon.vue.d.ts +28 -0
  36. package/dist/components/CLoader.vue.d.ts +23 -0
  37. package/dist/components/CLogo.vue.d.ts +9 -0
  38. package/dist/components/CMaskedTextField.vue.d.ts +511 -0
  39. package/dist/components/CMenu.vue.d.ts +17 -0
  40. package/dist/components/CMenuItem.vue.d.ts +37 -0
  41. package/dist/components/CMenuLabel.vue.d.ts +20 -0
  42. package/dist/components/CModal.vue.d.ts +59 -0
  43. package/dist/components/CModalLoading.vue.d.ts +36 -0
  44. package/dist/components/CNotification.vue.d.ts +64 -0
  45. package/dist/components/CPhoneField.vue.d.ts +792 -0
  46. package/dist/components/CPill.vue.d.ts +41 -0
  47. package/dist/components/CPillGroup.vue.d.ts +39 -0
  48. package/dist/components/CPopup.vue.d.ts +37 -0
  49. package/dist/components/CProgressLinear.vue.d.ts +21 -0
  50. package/dist/components/CProgressRing.vue.d.ts +48 -0
  51. package/dist/components/CRadio.vue.d.ts +40 -0
  52. package/dist/components/CRadioGroup.vue.d.ts +54 -0
  53. package/dist/components/CRebrand.vue.d.ts +28 -0
  54. package/dist/components/CRow.vue.d.ts +41 -0
  55. package/dist/components/CSafeArea.vue.d.ts +18 -0
  56. package/dist/components/CSectionHeader.vue.d.ts +29 -0
  57. package/dist/components/CSelect.vue.d.ts +96 -0
  58. package/dist/components/CSkeleton.vue.d.ts +3 -0
  59. package/dist/components/CSkeletonLoaderCard.vue.d.ts +9 -0
  60. package/dist/components/CSkeletonLoaderCircle.vue.d.ts +3 -0
  61. package/dist/components/CSkeletonLoaderText.vue.d.ts +16 -0
  62. package/dist/components/CSlideFadeTransition.vue.d.ts +36 -0
  63. package/dist/components/CSplitInput.vue.d.ts +56 -0
  64. package/dist/components/CSquaredIcon.vue.d.ts +33 -0
  65. package/dist/components/CSsnField.vue.d.ts +798 -0
  66. package/dist/components/CStatusDot.vue.d.ts +10 -0
  67. package/dist/components/CSwitch.vue.d.ts +39 -0
  68. package/dist/components/CSwitchListItem.vue.d.ts +48 -0
  69. package/dist/components/CTextArea.vue.d.ts +96 -0
  70. package/dist/components/CTextField.vue.d.ts +129 -0
  71. package/dist/components/CTextLink.vue.d.ts +36 -0
  72. package/dist/components/CThirdPartyLogo.vue.d.ts +22 -0
  73. package/dist/components/CTimeago.vue.d.ts +12 -0
  74. package/dist/components/CToast.vue.d.ts +69 -0
  75. package/dist/components/CToastsList.vue.d.ts +3 -0
  76. package/dist/components/CValidationMessage.vue.d.ts +37 -0
  77. package/dist/components/CZipcodeField.vue.d.ts +796 -0
  78. package/dist/components/index.d.ts +66 -0
  79. package/dist/components/internal/CCloseButton.vue.d.ts +14 -0
  80. package/dist/composables/accessibility.d.ts +1 -0
  81. package/dist/composables/animation.d.ts +12 -0
  82. package/dist/composables/binding.d.ts +19 -0
  83. package/dist/composables/colors.d.ts +13 -0
  84. package/dist/composables/elements.d.ts +3 -0
  85. package/dist/composables/fields.d.ts +10 -0
  86. package/dist/composables/gestures.d.ts +53 -0
  87. package/dist/composables/i18n.d.ts +3 -0
  88. package/dist/composables/id.d.ts +11 -0
  89. package/dist/composables/input-mask.d.ts +18 -0
  90. package/dist/composables/router.d.ts +30 -0
  91. package/dist/composables/slots.d.ts +2 -0
  92. package/dist/composables/toast.d.ts +21 -0
  93. package/dist/composables/validations.d.ts +77 -0
  94. package/dist/icons.cdn.mjs +3 -0
  95. package/dist/icons.cdn.mjs.map +1 -0
  96. package/dist/icons.d.ts +1 -0
  97. package/dist/icons.mjs +6 -0
  98. package/dist/icons.mjs.map +1 -0
  99. package/dist/index.cdn.mjs +9328 -12875
  100. package/dist/index.cdn.mjs.map +1 -1
  101. package/dist/index.cdn2.mjs +55255 -0
  102. package/dist/index.cdn2.mjs.map +1 -0
  103. package/dist/index.d.ts +8 -0
  104. package/dist/index.mjs +3946 -0
  105. package/dist/index.mjs.map +1 -0
  106. package/dist/plugin.d.ts +3 -0
  107. package/dist/services/animation.d.ts +17 -0
  108. package/dist/services/directives/index.d.ts +2 -0
  109. package/dist/services/directives/scroll-into-view.d.ts +7 -0
  110. package/dist/services/directives/tap-animation.d.ts +6 -0
  111. package/dist/services/id.d.ts +22 -0
  112. package/dist/services/injections/accordions.d.ts +3 -0
  113. package/dist/services/injections/animations.d.ts +2 -0
  114. package/dist/services/injections/buttons.d.ts +4 -0
  115. package/dist/services/injections/forms.d.ts +6 -0
  116. package/dist/services/injections/icon-buttons.d.ts +3 -0
  117. package/dist/services/injections/pills.d.ts +4 -0
  118. package/dist/services/injections/radio.d.ts +10 -0
  119. package/dist/{styles/main.css → styles.css} +40 -2
  120. package/dist/theme/icons.d.ts +36 -0
  121. package/dist/types/CForm.d.ts +12 -0
  122. package/dist/types/font-awesome.d.ts +5 -0
  123. package/dist/types.d.ts +13 -0
  124. package/package.json +11 -4
  125. package/src/colors/colors.ts +8 -3
  126. package/src/components/CAccordion.vue +31 -24
  127. package/src/components/CAccordionItem.vue +46 -45
  128. package/src/components/CAppBar.vue +108 -101
  129. package/src/components/CBadge.vue +33 -25
  130. package/src/components/CBottomSheet.vue +212 -199
  131. package/src/components/CButton/CButton.vue +135 -147
  132. package/src/components/CButtonStack.vue +21 -13
  133. package/src/components/CCard.vue +72 -69
  134. package/src/components/CCardFooter.vue +5 -5
  135. package/src/components/CCardHeader.vue +9 -7
  136. package/src/components/CCardSection.vue +15 -8
  137. package/src/components/CCheckbox.vue +68 -69
  138. package/src/components/CCol.vue +21 -22
  139. package/src/components/CDivider.vue +9 -8
  140. package/src/components/CDobField.vue +114 -105
  141. package/src/components/CDobSelect.vue +162 -164
  142. package/src/components/CEmailField.vue +39 -27
  143. package/src/components/CExpandTransition.vue +14 -17
  144. package/src/components/CFadeTransition.vue +3 -3
  145. package/src/components/CFileInput.vue +57 -50
  146. package/src/components/CFixedPageFooter.vue +23 -17
  147. package/src/components/CForm.vue +67 -60
  148. package/src/components/CFormFieldCounter.vue +25 -28
  149. package/src/components/CIconButton.vue +84 -65
  150. package/src/components/CLabel.vue +19 -13
  151. package/src/components/CListItem.vue +67 -66
  152. package/src/components/CListItemContent.vue +14 -16
  153. package/src/components/CListItemIcon.vue +18 -14
  154. package/src/components/CLoader.vue +47 -56
  155. package/src/components/CLogo.vue +13 -12
  156. package/src/components/CMaskedTextField.vue +80 -64
  157. package/src/components/CMenu.vue +14 -6
  158. package/src/components/CMenuItem.vue +28 -22
  159. package/src/components/CMenuLabel.vue +6 -5
  160. package/src/components/CModal.vue +76 -71
  161. package/src/components/CModalLoading.vue +24 -15
  162. package/src/components/CNotification.vue +77 -28
  163. package/src/components/CPhoneField.vue +34 -25
  164. package/src/components/CPill.vue +92 -88
  165. package/src/components/CPillGroup.vue +30 -21
  166. package/src/components/CPopup.vue +46 -37
  167. package/src/components/CProgressLinear.vue +17 -11
  168. package/src/components/CProgressRing.vue +33 -33
  169. package/src/components/CRadio.vue +57 -57
  170. package/src/components/CRadioGroup.vue +85 -72
  171. package/src/components/CRow.vue +22 -20
  172. package/src/components/CSectionHeader.vue +20 -12
  173. package/src/components/CSelect.vue +89 -73
  174. package/src/components/CSkeletonLoaderCard.vue +9 -15
  175. package/src/components/CSkeletonLoaderCircle.vue +1 -9
  176. package/src/components/CSkeletonLoaderText.vue +17 -18
  177. package/src/components/CSlideFadeTransition.vue +12 -34
  178. package/src/components/CSplitInput.vue +46 -45
  179. package/src/components/CSquaredIcon.vue +39 -29
  180. package/src/components/CSsnField.vue +48 -36
  181. package/src/components/CStatusDot.vue +16 -16
  182. package/src/components/CSwitch.vue +31 -22
  183. package/src/components/CSwitchListItem.vue +27 -28
  184. package/src/components/CTextArea.vue +116 -83
  185. package/src/components/CTextField.vue +194 -198
  186. package/src/components/CTextLink.vue +28 -25
  187. package/src/components/CThirdPartyLogo.vue +30 -59
  188. package/src/components/CToast.vue +135 -132
  189. package/src/components/CToastsList.vue +2 -15
  190. package/src/components/CValidationMessage.vue +31 -24
  191. package/src/components/CZipcodeField.vue +40 -27
  192. package/src/composables/elements.ts +1 -1
  193. package/src/composables/fields.ts +4 -4
  194. package/src/composables/router.ts +6 -5
  195. package/src/icons.ts +6 -0
  196. package/src/services/injections/buttons.ts +1 -1
  197. package/src/styles/_core.scss +1 -2
  198. package/src/styles/_reset.scss +1 -1
  199. package/src/styles/main.scss +2 -0
  200. package/src/types.ts +2 -0
  201. package/dist/index.cdn.css +0 -1
  202. package/dist/styles/utils.css +0 -2709
@@ -40,90 +40,89 @@
40
40
  </div>
41
41
  </template>
42
42
 
43
- <script lang="ts">
43
+ <script setup lang="ts">
44
44
  import { faCheck } from '@fortawesome/pro-regular-svg-icons';
45
45
  import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
46
- import type { PropType } from 'vue';
47
- import { defineComponent, ref, toRefs } from 'vue';
46
+ import type { VNode } from 'vue';
47
+ import { ref, toRefs } from 'vue';
48
48
 
49
49
  import CValidationMessage from '@propelinc/citrus-ui/src/components/CValidationMessage.vue';
50
50
  import { useInternalValue } from '@propelinc/citrus-ui/src/composables/binding';
51
51
  import { useId } from '@propelinc/citrus-ui/src/composables/id';
52
52
  import { useValidations } from '@propelinc/citrus-ui/src/composables/validations';
53
53
 
54
- export default defineComponent({
55
- name: 'CCheckbox',
56
- components: { FontAwesomeIcon, CValidationMessage },
57
- props: {
58
- /** Assigns the checkbox's id */
59
- id: { type: String as PropType<string | null>, default: null },
60
- /** The label text for the checkbox. */
61
- label: { type: String, default: undefined },
62
- /** Whether the checkbox is checked */
63
- value: { type: Boolean, default: false },
54
+ const props = withDefaults(
55
+ defineProps<{
64
56
  /** A custom data-test selector */
65
- dataTest: { type: String, default: 'checkbox' },
57
+ dataTest?: string;
58
+ /** Hides the validation message area. */
59
+ hideDetails?: boolean | 'auto';
60
+ /** Assigns the checkbox's id */
61
+ id?: string | null;
62
+ /** The label text for the checkbox */
63
+ label?: string;
66
64
  /**
67
65
  * An array of functions that either return either true / false or a string
68
66
  * containing an error message. The component passes the input value as an
69
67
  * argument. The input field will enter an error state if a function does
70
68
  * not return true.
71
- * */
72
- rules: {
73
- type: Array as PropType<((value: boolean) => string | boolean)[]>,
74
- default: () => [],
75
- },
76
- /** Hides the validation message area. */
77
- hideDetails: {
78
- type: [Boolean, String] as PropType<boolean | 'auto'>,
79
- default: false,
80
- },
81
- /** Controls the appearance of the checkbox. */
82
- variant: {
83
- type: String as PropType<'contained' | 'minimal'>,
84
- default: 'contained',
85
- },
86
- },
87
- emits: ['change', 'focus', 'blur'],
88
- setup(props, { emit }) {
89
- const { value, rules, id } = toRefs(props);
90
- const idWithFallback = useId(id);
91
-
92
- const internalValue = useInternalValue(value, {
93
- onChange: (value) => emit('change', value),
94
- });
95
-
96
- const checkbox = ref<HTMLInputElement | null>(null);
97
-
98
- const {
99
- message: validationMessage,
100
- valid: isValidationValid,
101
- listeners: validationListeners,
102
- } = useValidations({
103
- id: idWithFallback,
104
- value: internalValue,
105
- rules,
106
- validateOn: 'change',
107
- field: checkbox,
108
- });
109
-
110
- const onChange = (event: Event): void => {
111
- const target = event.target as HTMLInputElement;
112
- internalValue.value = !!target.checked;
113
- };
114
-
115
- return {
116
- checkbox,
117
- faCheck,
118
- idWithFallback,
119
- internalValue,
120
- onChange,
121
- validationMessage,
122
- isValidationValid,
123
- validationListeners,
124
- };
125
- },
69
+ */
70
+ rules?: ((value: boolean) => string | boolean)[];
71
+ /** Whether the checkbox is checked */
72
+ value?: boolean;
73
+ /**
74
+ * Controls the appearance of the checkbox.
75
+ * @default 'contained'
76
+ */
77
+ variant?: 'contained' | 'minimal';
78
+ }>(),
79
+ {
80
+ dataTest: 'checkbox',
81
+ hideDetails: false,
82
+ id: null,
83
+ label: undefined,
84
+ rules: () => [],
85
+ value: false,
86
+ variant: 'contained',
87
+ }
88
+ );
89
+
90
+ const emit = defineEmits<{
91
+ (e: 'change', value: boolean): void;
92
+ (e: 'focus'): void;
93
+ (e: 'blur'): void;
94
+ }>();
95
+
96
+ defineSlots<{
97
+ label?: () => VNode[];
98
+ message?: () => VNode[];
99
+ }>();
100
+
101
+ const { value, rules, id } = toRefs(props);
102
+ const idWithFallback = useId(id);
103
+
104
+ const internalValue = useInternalValue(value, {
105
+ onChange: (value) => emit('change', value),
126
106
  });
107
+
108
+ const checkbox = ref<HTMLInputElement | null>(null);
109
+
110
+ const {
111
+ message: validationMessage,
112
+ valid: isValidationValid,
113
+ listeners: validationListeners,
114
+ } = useValidations({
115
+ id: idWithFallback,
116
+ value: internalValue,
117
+ rules,
118
+ validateOn: 'change',
119
+ field: checkbox,
120
+ });
121
+
122
+ const onChange = (event: Event): void => {
123
+ const target = event.target as HTMLInputElement;
124
+ internalValue.value = !!target.checked;
125
+ };
127
126
  </script>
128
127
 
129
128
  <style lang="scss" scoped>
@@ -4,28 +4,27 @@
4
4
  </div>
5
5
  </template>
6
6
 
7
- <script lang="ts">
8
- import { defineComponent } from 'vue';
9
-
10
- export default defineComponent({
11
- props: {
12
- /** Sets the default number of columns the component extends.
13
- * Available options are: 1 -> 12 and auto. */
14
- cols: {
15
- type: String,
16
- default: null,
17
- validator: (value: string) => {
18
- if (value === 'auto' || !value) {
19
- return true;
20
- }
21
-
22
- return Array.from(Array(12))
23
- .map((_, i) => `${i + 1}`)
24
- .includes(value);
25
- },
26
- },
27
- },
28
- });
7
+ <script setup lang="ts">
8
+ import type { VNode } from 'vue';
9
+
10
+ type Cols = 'auto' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12';
11
+
12
+ withDefaults(
13
+ defineProps<{
14
+ /**
15
+ * Sets the default number of columns the component extends.
16
+ * Available options are: 1 -> 12 and auto.
17
+ */
18
+ cols?: Cols | null;
19
+ }>(),
20
+ {
21
+ cols: null,
22
+ }
23
+ );
24
+
25
+ defineSlots<{
26
+ default?: () => VNode[];
27
+ }>();
29
28
  </script>
30
29
 
31
30
  <style lang="scss" scoped>
@@ -6,15 +6,16 @@
6
6
  />
7
7
  </template>
8
8
 
9
- <script lang="ts">
10
- import { defineComponent } from 'vue';
11
-
12
- export default defineComponent({
13
- props: {
9
+ <script setup lang="ts">
10
+ withDefaults(
11
+ defineProps<{
14
12
  /** Displays dividers vertically. */
15
- vertical: { type: Boolean, default: false },
16
- },
17
- });
13
+ vertical?: boolean;
14
+ }>(),
15
+ {
16
+ vertical: false,
17
+ }
18
+ );
18
19
  </script>
19
20
 
20
21
  <style lang="scss" scoped>
@@ -28,10 +28,10 @@
28
28
  </CMaskedTextField>
29
29
  </template>
30
30
 
31
- <script lang="ts">
31
+ <script setup lang="ts">
32
32
  import dayjs from 'dayjs';
33
- import { computed, defineComponent, ref, watch } from 'vue';
34
- import type { PropType } from 'vue';
33
+ import type { VNode } from 'vue';
34
+ import { computed, ref, watch } from 'vue';
35
35
 
36
36
  import CMaskedTextField from '@propelinc/citrus-ui/src/components/CMaskedTextField.vue';
37
37
  import { useTranslation } from '@propelinc/citrus-ui/src/composables/i18n';
@@ -41,113 +41,122 @@ import {
41
41
  minLengthRule as createMinLengthRule,
42
42
  } from '@propelinc/shared-utils';
43
43
 
44
- export default defineComponent({
45
- name: 'CDobField',
46
- components: { CMaskedTextField },
47
- props: {
44
+ const props = withDefaults(
45
+ defineProps<{
48
46
  /** A custom data-test attribute for the input. */
49
- dataTest: { type: String, default: 'dob-field' },
47
+ dataTest?: string;
50
48
  /** A unique id for the input. */
51
- id: { type: String as PropType<string | undefined>, default: undefined },
49
+ id?: string;
52
50
  /** The input's label text. */
53
- label: { type: String as PropType<string | null>, default: null },
51
+ label?: string | null;
54
52
  /** The input's placeholder. */
55
- placeholder: { type: String, default: '' },
53
+ placeholder?: string;
56
54
  /** Additional validation rules. */
57
- rules: { type: Array as PropType<Rules<string>>, default: () => [] },
55
+ rules?: Rules<string>;
58
56
  /** The value of the input, must have the format YYYY-MM-DD or YYYY. */
59
- value: { type: String, default: '' },
60
- /** Toggles the field's year only variant. Changes default label, placeholder, mask and validation
61
- * behavior */
62
- yearOnly: { type: Boolean, default: false },
63
- },
64
- emits: ['input', 'blur', 'focus', 'change'],
65
- setup(props, { emit }) {
66
- const { t } = useTranslation();
67
-
68
- const parseDate = (value: string): string => {
69
- if (props.yearOnly) {
70
- return value;
71
- }
72
-
73
- if (!value) {
74
- return '';
75
- }
76
-
77
- const [year, month, day] = value.split('-');
78
- if (!year || !month || !day) {
79
- console.warn(`Invalid date format. Expected YYYY-MM-DD, received: ${value}.`);
80
- return '';
81
- }
82
-
83
- return `${month}${day}${year}`;
84
- };
85
-
86
- const formattedDate = ref<string>(parseDate(props.value));
87
-
88
- let lastEmittedValue = props.value;
89
-
90
- watch(
91
- () => props.value,
92
- (newValue: string) => {
93
- if (newValue !== lastEmittedValue) {
94
- formattedDate.value = props.yearOnly ? newValue : parseDate(newValue);
95
- }
96
- }
97
- );
98
-
99
- const emitIsoDate = (value: string): void => {
100
- if (props.yearOnly) {
101
- emit('input', value);
102
- } else {
103
- const month = value.substring(0, 2);
104
- const day = value.substring(2, 4);
105
- const year = value.substring(4, 8);
106
- lastEmittedValue = year && month && day ? `${year}-${month}-${day}` : '';
107
- emit('input', lastEmittedValue);
108
- }
109
- };
110
-
111
- const defaultLabel = computed(() => {
112
- return props.yearOnly ? t('Year of birth') : t('Date of birth');
113
- });
114
-
115
- const defaultPlaceholder = computed(() => {
116
- return props.yearOnly ? t('YYYY') : t('MM/DD/YYYY');
117
- });
118
-
119
- const validationMessage = computed(() => {
120
- const inputLabel = props.yearOnly ? t('year of birth') : t('date of birth');
121
- return t('Please enter a valid {inputLabel}', { inputLabel });
122
- });
123
-
124
- const defaultRules = computed(() => {
125
- const minLengthRule = createMinLengthRule(props.yearOnly ? 4 : 10, validationMessage.value);
126
- const maxAgeRule = (v: string): boolean | string =>
127
- isValidDateInAgeRange(0, 125)(v) || validationMessage.value;
128
- const validDateRule = (v: string): boolean | string =>
129
- dayjs(v, 'MM/DD/YYYY', true).isValid() || validationMessage.value;
130
-
131
- return [minLengthRule, maxAgeRule, !props.yearOnly ? validDateRule : undefined].filter(
132
- Boolean
133
- ) as Rules<string>;
134
- });
135
-
136
- const computedLabel = computed(() => props.label || defaultLabel.value);
137
- const computedPlaceholder = computed(() => props.placeholder || defaultPlaceholder.value);
138
- const computedRules = computed(() => [...defaultRules.value, ...props.rules]);
139
- const dateLength = computed(() => (props.yearOnly ? 4 : 8));
140
- const mask = computed(() => (props.yearOnly ? '####' : '##/##/####'));
141
-
142
- return {
143
- computedLabel,
144
- computedPlaceholder,
145
- computedRules,
146
- emitIsoDate,
147
- formattedDate,
148
- dateLength,
149
- mask,
150
- };
151
- },
57
+ value?: string;
58
+ /**
59
+ * Toggles the field's year only variant. Changes default label, placeholder,
60
+ * mask and validation behavior
61
+ */
62
+ yearOnly?: boolean;
63
+ }>(),
64
+ {
65
+ dataTest: 'dob-field',
66
+ id: undefined,
67
+ label: null,
68
+ placeholder: '',
69
+ rules: () => [],
70
+ value: '',
71
+ yearOnly: false,
72
+ }
73
+ );
74
+
75
+ const emit = defineEmits<{
76
+ (e: 'input', value: string): void;
77
+ (e: 'blur', event: Event): void;
78
+ (e: 'focus', event: Event): void;
79
+ (e: 'change', event: Event): void;
80
+ }>();
81
+
82
+ defineSlots<{
83
+ label?: () => VNode[];
84
+ message?: () => VNode[];
85
+ }>();
86
+
87
+ const { t } = useTranslation();
88
+
89
+ const parseDate = (value: string): string => {
90
+ if (props.yearOnly) {
91
+ return value;
92
+ }
93
+
94
+ if (!value) {
95
+ return '';
96
+ }
97
+
98
+ const [year, month, day] = value.split('-');
99
+ if (!year || !month || !day) {
100
+ console.warn(`Invalid date format. Expected YYYY-MM-DD, received: ${value}.`);
101
+ return '';
102
+ }
103
+
104
+ return `${month}${day}${year}`;
105
+ };
106
+
107
+ const formattedDate = ref<string>(parseDate(props.value));
108
+
109
+ let lastEmittedValue = props.value;
110
+
111
+ watch(
112
+ () => props.value,
113
+ (newValue: string) => {
114
+ if (newValue !== lastEmittedValue) {
115
+ formattedDate.value = props.yearOnly ? newValue : parseDate(newValue);
116
+ }
117
+ }
118
+ );
119
+
120
+ const emitIsoDate = (value: string): void => {
121
+ if (props.yearOnly) {
122
+ emit('input', value);
123
+ } else {
124
+ const month = value.substring(0, 2);
125
+ const day = value.substring(2, 4);
126
+ const year = value.substring(4, 8);
127
+ lastEmittedValue = year && month && day ? `${year}-${month}-${day}` : '';
128
+ emit('input', lastEmittedValue);
129
+ }
130
+ };
131
+
132
+ const defaultLabel = computed(() => {
133
+ return props.yearOnly ? t('Year of birth') : t('Date of birth');
152
134
  });
135
+
136
+ const defaultPlaceholder = computed(() => {
137
+ return props.yearOnly ? t('YYYY') : t('MM/DD/YYYY');
138
+ });
139
+
140
+ const validationMessage = computed(() => {
141
+ const inputLabel = props.yearOnly ? t('year of birth') : t('date of birth');
142
+ return t('Please enter a valid {inputLabel}', { inputLabel });
143
+ });
144
+
145
+ const defaultRules = computed(() => {
146
+ const minLengthRule = createMinLengthRule(props.yearOnly ? 4 : 10, validationMessage.value);
147
+ const maxAgeRule = (v: string): boolean | string =>
148
+ isValidDateInAgeRange(0, 125)(v) || validationMessage.value;
149
+ const validDateRule = (v: string): boolean | string =>
150
+ dayjs(v, 'MM/DD/YYYY', true).isValid() || validationMessage.value;
151
+
152
+ return [minLengthRule, maxAgeRule, !props.yearOnly ? validDateRule : undefined].filter(
153
+ Boolean
154
+ ) as Rules<string>;
155
+ });
156
+
157
+ const computedLabel = computed(() => props.label || defaultLabel.value);
158
+ const computedPlaceholder = computed(() => props.placeholder || defaultPlaceholder.value);
159
+ const computedRules = computed(() => [...defaultRules.value, ...props.rules]);
160
+ const dateLength = computed(() => (props.yearOnly ? 4 : 8));
161
+ const mask = computed(() => (props.yearOnly ? '####' : '##/##/####'));
153
162
  </script>