srcdev-nuxt-forms 0.2.0 → 1.0.1

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 (77) hide show
  1. package/assets/styles/brand/_brand.css +150 -0
  2. package/assets/styles/brand/_brand_dark.css +152 -0
  3. package/assets/styles/brand/_palette_dark.css +148 -0
  4. package/assets/styles/brand/_palette_light.css +148 -0
  5. package/assets/styles/brand/_typography.css +176 -0
  6. package/assets/styles/brand/index.css +1 -0
  7. package/assets/styles/forms/index.css +1 -2
  8. package/assets/styles/forms/themes/_default.css +3 -0
  9. package/assets/styles/forms/themes/_error.css +45 -11
  10. package/assets/styles/forms/themes/_ghost.css +42 -10
  11. package/assets/styles/forms/themes/_primary.css +42 -12
  12. package/assets/styles/forms/themes/_secondary.css +42 -12
  13. package/assets/styles/forms/themes/_success.css +42 -11
  14. package/assets/styles/forms/themes/_tertiary.css +42 -10
  15. package/assets/styles/forms/themes/_warning.css +42 -10
  16. package/assets/styles/forms/themes/index.css +1 -0
  17. package/assets/styles/forms/variables/_palette.css +104 -0
  18. package/assets/styles/forms/variables/_theme.css +1 -1
  19. package/assets/styles/forms/variables/index.css +2 -0
  20. package/assets/styles/main.css +2 -0
  21. package/assets/styles/scaffolding/_margin-helpers.css +308 -0
  22. package/assets/styles/scaffolding/_padding-helpers.css +308 -0
  23. package/assets/styles/scaffolding/_page.css +23 -0
  24. package/assets/styles/scaffolding/index.css +3 -0
  25. package/assets/styles/variables/colors/_blue.css +2 -2
  26. package/assets/styles/variables/colors/_gray.css +1 -1
  27. package/assets/styles/variables/colors/_green.css +2 -2
  28. package/assets/styles/variables/colors/_orange.css +2 -2
  29. package/assets/styles/variables/colors/_red.css +2 -2
  30. package/assets/styles/variables/colors/_yellow.css +1 -1
  31. package/components/forms/form-errors/InputError.vue +82 -37
  32. package/components/forms/input-button/InputButtonCore.vue +25 -104
  33. package/components/forms/input-checkbox/InputCheckboxCore.vue +37 -181
  34. package/components/forms/input-checkbox/InputCheckboxWithLabel.vue +42 -51
  35. package/components/forms/input-checkbox/variants/MultipleCheckboxes.vue +42 -69
  36. package/components/forms/input-checkbox/variants/SingleCheckbox.vue +126 -111
  37. package/components/forms/input-number/InputNumberCore.vue +184 -0
  38. package/components/forms/input-number/variants/InputNumberDefault.vue +155 -0
  39. package/components/forms/input-radio/InputRadiobuttonCore.vue +212 -0
  40. package/components/forms/input-radio/InputRadiobuttonWithLabel.vue +103 -0
  41. package/components/forms/input-radio/variants/MultipleRadiobuttons.vue +166 -0
  42. package/components/forms/input-range/InputRangeCore.vue +70 -88
  43. package/components/forms/input-range/variants/InputRangeDefault.vue +74 -46
  44. package/components/forms/input-text/InputTextCore.vue +141 -109
  45. package/components/forms/input-text/variants/material/InputPasswordWithLabel.vue +99 -0
  46. package/components/forms/input-text/variants/material/InputTextAsNumberWithLabel.vue +142 -0
  47. package/components/forms/input-text/variants/material/InputTextWithLabel.vue +125 -0
  48. package/components/forms/input-textarea/InputTextareaCore.vue +96 -105
  49. package/components/forms/input-textarea/variants/InputTextareaWithLabel.vue +106 -0
  50. package/components/scaffolding/footer/NavFooter.vue +62 -0
  51. package/composables/useApiRequest.ts +25 -0
  52. package/composables/useFormControl.ts +2 -0
  53. package/composables/useSleep.ts +2 -2
  54. package/composables/useStyleClassPassthrough.ts +30 -0
  55. package/composables/useZodValidation.ts +120 -0
  56. package/layouts/default.vue +21 -5
  57. package/package.json +13 -9
  58. package/pages/forms/examples/material/cssbattle.vue +60 -0
  59. package/pages/forms/examples/material/text-fields.vue +375 -153
  60. package/pages/index.vue +2 -2
  61. package/pages/typography.vue +83 -0
  62. package/server/data/places/cities.json +7 -1
  63. package/types/types.forms.ts +102 -0
  64. package/types/types.zodFormControl.ts +21 -0
  65. package/assets/styles/forms/utils/_a11y.css +0 -5
  66. package/assets/styles/forms/utils/index.css +0 -1
  67. package/components/forms/input-radio/InputRadioCore.vue +0 -226
  68. package/components/forms/input-radio/InputRadioWithLabel.vue +0 -118
  69. package/components/forms/input-radio/variants/MultipleRadio.vue +0 -183
  70. package/components/forms/input-radio/variants/SingleRadio.vue +0 -131
  71. package/components/forms/input-text/variants/material/InputEmailMaterial.vue +0 -72
  72. package/components/forms/input-text/variants/material/InputPasswordMaterial.vue +0 -114
  73. package/components/forms/input-text/variants/material/InputTextMaterial.vue +0 -68
  74. package/components/forms/input-text/variants/material/InputTextMaterialCore.vue +0 -313
  75. package/components/forms/input-textarea/variants/material/InputTextareaMaterial.vue +0 -75
  76. package/components/forms/input-textarea/variants/material/InputTextareaMaterialCore.vue +0 -290
  77. package/composables/useUpdateStyleClassPassthrough.ts +0 -29
@@ -1,29 +1,54 @@
1
1
  <template>
2
- <div class="input-range-with-label" :class="[styleClassPassthrough, `theme-${theme}`, { error: fieldHasError }]">
3
- <label class="input-range-label" :for="id">{{ c12.label }}</label>
2
+ <div class="input-range-with-label" :data-form-theme="formTheme" :class="[elementClasses, { error: fieldHasError }]">
3
+ <label class="input-range-label body-normal-bold" :for="id">{{ label }}</label>
4
4
  <template v-if="hasDescription">
5
5
  <slot name="description"></slot>
6
6
  </template>
7
- <InputRangeCore :id :name :required :c12 v-model="modelValue" :theme :size :weight :min :max :step>
7
+
8
+ <InputRangeCore v-model="modelValue" :id :name :min :max :step :theme :required :size :weight :fieldHasError>
9
+ <template v-if="hasDataList" #datalist>
10
+ <slot name="datalist"></slot>
11
+ </template>
8
12
  <template v-if="hasLeftContent" #left>
9
- <slot name="left"></slot>
13
+ <InputButtonCore
14
+ type="button"
15
+ @click.stop.prevent="updateRange(-step, Number(modelValue) > min)"
16
+ :readonly="Number(modelValue) === min"
17
+ :is-pending="false"
18
+ buttonText="Step down"
19
+ theme="ghost"
20
+ size="x-small"
21
+ >
22
+ <template #iconOnly>
23
+ <slot name="left"></slot>
24
+ </template>
25
+ </InputButtonCore>
10
26
  </template>
11
27
  <template v-if="hasRightContent" #right>
12
- <slot name="right"></slot>
28
+ <InputButtonCore
29
+ type="button"
30
+ @click.stop.prevent="updateRange(step, Number(modelValue) < max)"
31
+ :readonly="Number(modelValue) === max"
32
+ :is-pending="false"
33
+ buttonText="Step up"
34
+ theme="ghost"
35
+ size="x-small"
36
+ >
37
+ <template #iconOnly>
38
+ <slot name="right"></slot>
39
+ </template>
40
+ </InputButtonCore>
13
41
  </template>
14
42
  </InputRangeCore>
43
+ <InputError :errorMessage :fieldHasError :id :isDetached="true" :styleClassPassthrough="['mbe-20']" />
15
44
  </div>
16
45
  </template>
17
46
 
18
47
  <script setup lang="ts">
19
48
  import propValidators from '../../c12/prop-validators';
20
49
 
21
- import type { InpuTextC12, IFormFieldC12, IFormData } from '@/types/types.forms';
22
- // import { validationConfig } from '@/components/forms/c12/validation-patterns';
23
-
24
- const props = defineProps({
50
+ const { id, name, label, required, min, max, step, theme, size, weight, styleClassPassthrough, errorMessage, fieldHasError } = defineProps({
25
51
  id: {
26
- // type: String as PropType<string>,
27
52
  type: String,
28
53
  required: true,
29
54
  },
@@ -31,30 +56,38 @@ const props = defineProps({
31
56
  type: String,
32
57
  required: true,
33
58
  },
34
- required: {
35
- type: Boolean,
36
- value: false,
59
+ label: {
60
+ type: String,
61
+ required: true,
37
62
  },
38
63
  min: {
39
64
  type: Number,
40
- default: 0,
65
+ required: true,
41
66
  },
42
67
  max: {
43
68
  type: Number,
44
- default: 100,
69
+ required: true,
45
70
  },
46
71
  step: {
47
72
  type: Number,
48
73
  default: 1,
49
74
  },
50
- c12: {
51
- type: Object as PropType<InpuTextC12>,
52
- required: true,
53
- },
54
- styleClassPassthrough: {
75
+ placeholder: {
55
76
  type: String,
56
77
  default: '',
57
78
  },
79
+ errorMessage: {
80
+ type: [Object, String],
81
+ required: true,
82
+ },
83
+ fieldHasError: {
84
+ type: Boolean,
85
+ default: false,
86
+ },
87
+ required: {
88
+ type: Boolean,
89
+ default: false,
90
+ },
58
91
  theme: {
59
92
  type: String as PropType<string>,
60
93
  default: 'primary',
@@ -76,45 +109,41 @@ const props = defineProps({
76
109
  return propValidators.weight.includes(value);
77
110
  },
78
111
  },
112
+ styleClassPassthrough: {
113
+ type: Array as PropType<string[]>,
114
+ default: () => [],
115
+ },
116
+ deepCssClassPassthrough: {
117
+ type: String,
118
+ default: '',
119
+ },
79
120
  });
80
121
 
81
122
  const slots = useSlots();
82
123
  const hasDescription = computed(() => slots.description !== undefined);
124
+ const hasDataList = computed(() => slots.datalist !== undefined);
83
125
  const hasLeftContent = computed(() => slots.left !== undefined);
84
126
  const hasRightContent = computed(() => slots.right !== undefined);
127
+ const { elementClasses, updateElementClasses } = useStyleClassPassthrough(styleClassPassthrough);
85
128
 
86
- const modelValue = defineModel() as Ref<IFormData>;
87
-
88
- const name = computed(() => {
89
- return props.name !== null ? props.name : props.id;
129
+ const formTheme = computed(() => {
130
+ return fieldHasError ? 'error' : theme;
90
131
  });
91
- const fieldHasError = computed(() => {
92
- return modelValue.value!.submitAttempted && !modelValue.value!.formFieldsC12[name.value].isValid;
93
- });
94
- </script>
95
132
 
96
- <style lang="css">
97
- .input-range-with-label {
98
- --_form-theme: var(--theme-form-primary);
99
- --_border-width: var(--input-border-width-default);
100
- --_outline-width: var(--input-outline-width-thin);
101
- --_label-padding-inline: 10px;
102
-
103
- &.theme-secondary {
104
- --_form-theme: var(--theme-form-secondary);
105
- }
133
+ const modelValue = defineModel<number | readonly number[]>();
106
134
 
107
- &.error {
108
- --_form-theme: var(--theme-error);
135
+ const updateRange = (step: number, withinRangeLimit: boolean) => {
136
+ if (withinRangeLimit) {
137
+ modelValue.value = (Number(modelValue.value) + step) as number;
109
138
  }
139
+ };
140
+ </script>
110
141
 
142
+ <style lang="css">
143
+ .input-range-with-label {
111
144
  .input-range-label {
112
- color: var(--_form-theme);
113
145
  display: block;
114
- font-family: var(--font-family);
115
- font-size: 18px;
116
- font-weight: 500;
117
- margin-bottom: 12px;
146
+ margin-block: 8px;
118
147
 
119
148
  &:hover {
120
149
  cursor: pointer;
@@ -125,7 +154,6 @@ const fieldHasError = computed(() => {
125
154
  font-family: var(--font-family);
126
155
  font-size: 16px;
127
156
  margin-top: 12px;
128
- color: var(--theme-form-secondary);
129
157
  }
130
158
  }
131
159
  </style>
@@ -1,48 +1,56 @@
1
1
  <template>
2
- <div class="input-text-wrapper" :class="[{ 'has-left-content': hasLeftContent }, { 'has-right-content': hasRightContent }]">
3
- <template v-if="hasLeftContent">
4
- <span class="left-content">
5
- <slot name="left"></slot>
6
- </span>
7
- </template>
2
+ <div
3
+ class="input-text-wrapper"
4
+ :data-form-theme="formTheme"
5
+ :class="[{ dirty: isDirty }, { active: isActive }, { error: fieldHasError }, { 'has-left-slot': hasLeftSlot }, { 'has-right-slot': hasRightSlot }]"
6
+ >
7
+ <span v-if="hasLeftSlot" class="slot left-slot">
8
+ <slot name="left"></slot>
9
+ </span>
8
10
 
9
11
  <input
10
12
  :type
11
- :placeholder="c12.placeholder"
13
+ :placeholder
12
14
  :id
13
15
  :name
14
- :pattern="componentValidation.pattern"
15
- :maxlength="componentValidation.maxlength"
16
16
  :required
17
- :class="['input-text-core', 'text-normal', styleClassPassthrough, { active: isFocused }, { dirty: fieldIsDirty }, { error: fieldHasError }]"
18
- v-model="modelValue.data[name]"
17
+ :maxlength
18
+ :class="['input-text-core', 'text-normal', elementClasses, { dirty: isDirty }, { active: isActive }]"
19
+ v-model="modelValue"
19
20
  ref="inputField"
20
21
  :aria-invalid="fieldHasError"
21
22
  :aria-describedby="`${id}-error-message`"
22
- @focusin="updateFocus(name, true)"
23
- @focusout="updateFocus(name, false)"
23
+ :pattern="inputPattern"
24
+ :inputmode
25
+ @focusin="updateFocus(true)"
26
+ @focusout="updateFocus(false)"
24
27
  />
25
28
 
26
- <template v-if="hasRightContent">
27
- <span class="right-content">
28
- <slot name="right"></slot>
29
- </span>
30
- </template>
29
+ <span v-if="hasRightSlot" class="slot right-slot">
30
+ <slot name="right"></slot>
31
+ </span>
31
32
  </div>
32
33
  </template>
33
34
 
34
35
  <script setup lang="ts">
35
- import type { InpuTextC12, IFormFieldC12, IFormData } from '@/types/types.forms';
36
- import { validationConfig } from '@/components/forms/c12/validation-patterns';
37
36
  import propValidators from '../c12/prop-validators';
38
37
 
39
- const props = defineProps({
38
+ const { type, inputmode, maxlength, id, name, placeholder, required, fieldHasError, styleClassPassthrough, theme } = defineProps({
40
39
  type: {
41
- type: String,
40
+ type: String as PropType<'text' | 'email' | 'password' | 'number' | 'tel' | 'url'>,
41
+ default: 'text',
42
42
  validator(value: string) {
43
43
  return propValidators.inputTypesText.includes(value);
44
44
  },
45
45
  },
46
+ inputmode: {
47
+ type: String as PropType<'text' | 'email' | 'tel' | 'url' | 'search' | 'numeric' | 'none' | 'decimal'>,
48
+ default: 'text',
49
+ },
50
+ maxlength: {
51
+ type: Number,
52
+ default: 255,
53
+ },
46
54
  id: {
47
55
  type: String,
48
56
  required: true,
@@ -51,131 +59,155 @@ const props = defineProps({
51
59
  type: String,
52
60
  required: true,
53
61
  },
54
- validation: {
55
- type: String,
56
- default: null,
57
- },
58
62
  required: {
59
63
  type: Boolean,
60
- value: false,
64
+ default: false,
61
65
  },
62
- c12: {
63
- type: Object as PropType<InpuTextC12>,
64
- required: true,
65
- },
66
- styleClassPassthrough: {
66
+ placeholder: {
67
67
  type: String,
68
68
  default: '',
69
69
  },
70
+ fieldHasError: {
71
+ type: Boolean,
72
+ default: false,
73
+ },
74
+ styleClassPassthrough: {
75
+ type: Array as PropType<string[]>,
76
+ default: () => [],
77
+ },
78
+ theme: {
79
+ type: String as PropType<string>,
80
+ default: 'primary',
81
+ validator(value: string) {
82
+ return propValidators.theme.includes(value);
83
+ },
84
+ },
70
85
  });
71
86
 
72
87
  const slots = useSlots();
73
- const hasLeftContent = computed(() => slots.left !== undefined);
74
- const hasRightContent = computed(() => slots.right !== undefined);
75
-
76
- const modelValue = defineModel() as Ref<IFormData>;
88
+ const hasLeftSlot = computed(() => slots.left !== undefined);
89
+ const hasRightSlot = computed(() => slots.right !== undefined);
77
90
 
78
- const updateFocus = (name: string, isFocused: boolean) => {
79
- modelValue.value.focusedField = isFocused ? name : '';
80
- };
81
-
82
- const isFocused = computed(() => {
83
- return modelValue.value.focusedField == name.value;
84
- });
85
-
86
- const name = computed(() => {
87
- return props.name !== null ? props.name : props.id;
91
+ const formTheme = computed(() => {
92
+ return fieldHasError ? 'error' : theme;
88
93
  });
89
94
 
90
- const validatorLocale = toRef(useRuntimeConfig().public.validatorLocale);
91
-
92
- const componentValidation = validationConfig[validatorLocale.value][props.validation];
93
- const inputField = ref<HTMLInputElement | null>(null);
95
+ const modelValue = defineModel();
96
+ const isDirty = defineModel('isDirty');
97
+ const isActive = defineModel('isActive');
94
98
 
95
- const fieldIsDirty = computed(() => {
96
- return modelValue.value!.formFieldsC12[name.value].isDirty;
97
- });
98
- const fieldHasError = computed(() => {
99
- return modelValue.value!.submitAttempted && !modelValue.value!.formFieldsC12[name.value].isValid;
99
+ const inputPattern = computed(() => {
100
+ return inputmode === 'numeric' ? '[0-9]+' : undefined;
100
101
  });
101
102
 
102
- // const { fieldHasError } = useFormControl(name.value);
103
-
104
- const formFieldC12 = <IFormFieldC12>{
105
- label: props.c12.label,
106
- placeholder: props.c12.placeholder,
107
- errorMessage: props.c12.errorMessage,
108
- useCustomError: false,
109
- customErrors: {},
110
- isValid: false,
111
- isDirty: false,
112
- type: 'string',
113
- previousValue: null,
103
+ const updateFocus = (isFocused: boolean) => {
104
+ isActive.value = isFocused;
114
105
  };
115
- modelValue.value!.formFieldsC12[name.value] = formFieldC12;
116
-
117
- const { initFormFieldsC12 } = useFormControl();
118
- initFormFieldsC12(props.name, formFieldC12);
119
-
120
- const fieldValue = computed(() => {
121
- return modelValue.value.data[name.value];
122
- });
123
106
 
124
- watch(fieldValue, () => {
125
- if (!modelValue.value!.formFieldsC12[name.value].isDirty) {
126
- modelValue.value!.formFieldsC12[name.value].isDirty = modelValue.value.data[name.value] !== '';
127
- }
128
- modelValue.value!.formFieldsC12[name.value].isValid = inputField.value?.validity.valid ?? false;
129
- modelValue.value!.validityState[name.value] = inputField.value?.validity.valid ?? false;
107
+ const inputField = ref<HTMLInputElement | null>(null);
130
108
 
131
- if (modelValue.value!.formFieldsC12[name.value].useCustomError && modelValue.value.data[props.name] === modelValue.value.formFieldsC12[props.name].previousValue) {
132
- modelValue.value!.validityState[name.value] = false;
133
- modelValue.value!.formFieldsC12[name.value].isValid = false;
134
- modelValue.value.displayErrorMessages = true;
109
+ const { elementClasses, updateElementClasses } = useStyleClassPassthrough(styleClassPassthrough);
110
+
111
+ // TODO: Move this to a utility function to allow removeEventListener on unmounted
112
+ // Leaving like this could lead to memory leaks
113
+ const validateInput = () => {
114
+ if (inputField.value !== null) {
115
+ inputField.value.addEventListener('beforeinput', (event: any) => {
116
+ let beforeValue = modelValue.value;
117
+ event.target.addEventListener(
118
+ 'input',
119
+ () => {
120
+ if (inputField.value !== null && inputField.value.validity.patternMismatch) {
121
+ inputField.value.value = beforeValue as string;
122
+ console.log('you did some bad chars');
123
+ }
124
+ },
125
+ { once: true }
126
+ );
127
+ });
135
128
  }
136
- });
137
-
138
- const isValid = () => {
139
- setTimeout(() => {
140
- modelValue.value!.validityState[name.value] = inputField.value?.validity.valid ?? false;
141
- modelValue.value!.formFieldsC12[name.value].isValid = inputField.value?.validity.valid ?? false;
142
- if (!modelValue.value!.formFieldsC12[name.value].isDirty) {
143
- modelValue.value!.formFieldsC12[name.value].isDirty = modelValue.value.data[name.value] !== '';
144
- }
145
- }, 0);
146
129
  };
147
130
 
148
131
  onMounted(() => {
149
- isValid();
132
+ updateElementClasses(['deep-bristol', 'deep-london', 'deep-bath']);
133
+ if (inputmode === 'numeric') validateInput();
150
134
  });
151
135
  </script>
152
136
 
153
137
  <style lang="css">
154
138
  .input-text-wrapper {
139
+ --_gutter: 12px;
140
+ --_border-width: var(--input-border-width-thin);
141
+ --_outline-width: var(--input-border-width-thin);
142
+
143
+ display: flex;
155
144
  align-items: center;
156
- display: grid;
157
- grid-template-columns: 1fr;
158
145
 
159
- &.has-left-content {
160
- grid-template-columns: auto 1fr;
161
- margin-left: var(--_gutter);
146
+ background-color: var(--theme-form-input-bg);
147
+ border-radius: var(--input-border-width-default);
148
+ border: var(--_border-width) solid var(--theme-form-input-border);
149
+
150
+ &:focus-within {
151
+ border: var(--_border-width) solid var(--theme-form-input-border-focus);
152
+ outline: var(--_outline-width) solid hsl(from var(--theme-form-input-outline-focus) h s 50%);
153
+ box-shadow: var(--theme-form-focus-box-shadow);
154
+ }
155
+
156
+ .slot {
157
+ display: inline-block;
158
+ padding-inline: 8px;
159
+
160
+ .icon {
161
+ color: var(--theme-form-input-text);
162
+ }
163
+ }
162
164
 
163
- .left-content {
165
+ &.has-left-slot {
166
+ .left-slot {
164
167
  display: flex;
165
168
  align-items: center;
166
169
  }
167
170
  }
168
171
 
169
- &.has-right-content {
170
- display: grid;
171
- grid-template-columns: 1fr auto;
172
- /* display: flex; */
173
- margin-right: var(--_gutter);
174
-
175
- .right-content {
172
+ &.has-right-slot {
173
+ .right-slot {
176
174
  display: flex;
177
175
  align-items: center;
178
176
  }
179
177
  }
178
+
179
+ .input-text-core {
180
+ background-color: transparent;
181
+ border: none;
182
+ outline: none;
183
+ box-shadow: none;
184
+ flex-grow: 1;
185
+
186
+ color: var(--theme-form-input-text);
187
+ font-family: var(--font-family);
188
+ font-size: var(--theme-form-button-font-size-normal);
189
+ line-height: var(--line-height);
190
+ padding: 8px 12px;
191
+
192
+ &::placeholder,
193
+ &::-webkit-input-placeholder {
194
+ font-family: var(--font-family);
195
+ font-size: var(--font-size);
196
+ font-style: italic;
197
+ font-weight: 400;
198
+ }
199
+ }
200
+ }
201
+
202
+ input:autofill,
203
+ input:-webkit-autofill-strong-password,
204
+ input:-webkit-autofill-strong-password-viewable,
205
+ input:-webkit-autofill-and-obscured {
206
+ background-color: var(--theme-form-input-bg) !important;
207
+ background-image: none !important;
208
+ color: var(--_input-text-color) !important;
209
+ -webkit-box-shadow: 0 0 0px 1000px var(--theme-form-input-bg) inset;
210
+ /* -webkit-text-fill-color: black; */
211
+ transition: background-color 5000s ease-in-out 0s;
180
212
  }
181
213
  </style>
@@ -0,0 +1,99 @@
1
+ <template>
2
+ <InputTextWithLabel v-model="modelValue" :data-form-theme="formTheme" :type="inputType" :maxlength :id :name :placeholder :label :errorMessage :fieldHasError :required :styleClassPassthrough :theme>
3
+ <template #right>
4
+ <InputButtonCore
5
+ type="button"
6
+ @click.stop.prevent="toggleDisplayPassword"
7
+ :is-pending="false"
8
+ :buttonText
9
+ theme="ghost"
10
+ size="x-small"
11
+ @focusin="updateFocus(name, true)"
12
+ @focusout="updateFocus(name, false)"
13
+ >
14
+ <template #iconOnly>
15
+ <Icon v-if="displayPassword" name="radix-icons:eye-none" class="icon" />
16
+ <Icon v-else name="radix-icons:eye-open" class="icon" />
17
+ </template>
18
+ </InputButtonCore>
19
+ </template>
20
+ </InputTextWithLabel>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ import propValidators from '../../../c12/prop-validators';
25
+
26
+ const { type, maxlength, id, name, placeholder, label, errorMessage, fieldHasError, required, styleClassPassthrough, theme } = defineProps({
27
+ type: {
28
+ type: String,
29
+ default: 'password',
30
+ },
31
+ maxlength: {
32
+ type: Number,
33
+ default: 255,
34
+ },
35
+ id: {
36
+ type: String,
37
+ required: true,
38
+ },
39
+ name: {
40
+ type: String,
41
+ required: true,
42
+ },
43
+ placeholder: {
44
+ type: String,
45
+ default: '',
46
+ },
47
+ label: {
48
+ type: String,
49
+ required: true,
50
+ },
51
+ errorMessage: {
52
+ type: [Object, String],
53
+ required: true,
54
+ },
55
+ fieldHasError: {
56
+ type: Boolean,
57
+ default: false,
58
+ },
59
+ required: {
60
+ type: Boolean,
61
+ default: false,
62
+ },
63
+ styleClassPassthrough: {
64
+ type: Array as PropType<string[]>,
65
+ default: () => [],
66
+ },
67
+ theme: {
68
+ type: String as PropType<string>,
69
+ default: 'primary',
70
+ validator(value: string) {
71
+ return propValidators.theme.includes(value);
72
+ },
73
+ },
74
+ });
75
+
76
+ const formTheme = computed(() => {
77
+ return fieldHasError ? 'error' : theme;
78
+ });
79
+
80
+ const modelValue = defineModel();
81
+
82
+ const updateFocus = (name: string, isFocused: boolean) => {
83
+ // console.log('updateFocus', name, isFocused);
84
+ // modelValue.value.focusedField = isFocused ? name : '';
85
+ };
86
+
87
+ const inputType = ref(type);
88
+
89
+ const displayPassword = ref(false);
90
+ const buttonText = computed(() => {
91
+ inputType.value = displayPassword.value ? 'text' : 'password';
92
+ return displayPassword.value ? 'Hide password' : 'Show password';
93
+ });
94
+ const toggleDisplayPassword = () => {
95
+ displayPassword.value = !displayPassword.value;
96
+ };
97
+ </script>
98
+
99
+ <style lang="css"></style>