sprintify-ui 0.0.41 → 0.0.42

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 (89) hide show
  1. package/dist/sprintify-ui.es.js +6033 -5518
  2. package/dist/types/src/components/BaseAutocomplete.vue.d.ts +32 -12
  3. package/dist/types/src/components/BaseAutocompleteFetch.vue.d.ts +28 -28
  4. package/dist/types/src/components/BaseBelongsTo.vue.d.ts +35 -35
  5. package/dist/types/src/components/BaseButtonGroup.vue.d.ts +46 -8
  6. package/dist/types/src/components/BaseDatePicker.vue.d.ts +18 -9
  7. package/dist/types/src/components/BaseDateSelect.vue.d.ts +14 -5
  8. package/dist/types/src/components/BaseField.vue.d.ts +151 -0
  9. package/dist/types/src/components/BaseFieldI18n.vue.d.ts +93 -0
  10. package/dist/types/src/components/BaseForm.vue.d.ts +267 -0
  11. package/dist/types/src/components/BaseFormField.d.ts +81 -0
  12. package/dist/types/src/components/BaseHasMany.vue.d.ts +31 -31
  13. package/dist/types/src/components/BaseInput.vue.d.ts +1 -1
  14. package/dist/types/src/components/BaseInputError.vue.d.ts +48 -0
  15. package/dist/types/src/components/BaseInputPercent.vue.d.ts +1 -1
  16. package/dist/types/src/components/BaseLocaleForm.vue.d.ts +420 -0
  17. package/dist/types/src/components/BaseMediaLibrary.vue.d.ts +46 -24
  18. package/dist/types/src/components/BaseNumberForm.vue.d.ts +382 -0
  19. package/dist/types/src/components/BasePassword.vue.d.ts +10 -14
  20. package/dist/types/src/components/BasePasswordForm.vue.d.ts +365 -0
  21. package/dist/types/src/components/BaseRadioGroup.vue.d.ts +23 -4
  22. package/dist/types/src/components/BaseSelect.vue.d.ts +20 -1
  23. package/dist/types/src/components/BaseSwitch.vue.d.ts +155 -23
  24. package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +31 -12
  25. package/dist/types/src/components/BaseTagAutocompleteFetch.vue.d.ts +20 -20
  26. package/dist/types/src/components/BaseTextarea.vue.d.ts +9 -0
  27. package/dist/types/src/components/BaseTextareaAutoresize.vue.d.ts +18 -0
  28. package/dist/types/src/components/BaseTextareaForm.vue.d.ts +394 -0
  29. package/dist/types/src/components/index.d.ts +4 -1
  30. package/dist/types/src/composables/field.d.ts +17 -0
  31. package/dist/types/src/index.d.ts +3 -0
  32. package/dist/types/src/types/index.d.ts +11 -0
  33. package/package.json +4 -1
  34. package/src/components/BaseAutocomplete.stories.js +56 -51
  35. package/src/components/BaseAutocomplete.vue +25 -8
  36. package/src/components/BaseAutocompleteFetch.stories.js +67 -65
  37. package/src/components/BaseAutocompleteFetch.vue +9 -29
  38. package/src/components/BaseBelongsTo.stories.js +72 -82
  39. package/src/components/BaseBelongsTo.vue +10 -11
  40. package/src/components/BaseButtonGroup.stories.js +11 -10
  41. package/src/components/BaseButtonGroup.vue +22 -9
  42. package/src/components/BaseCharacterCounter.stories.js +1 -1
  43. package/src/components/BaseDatePicker.stories.js +13 -9
  44. package/src/components/BaseDatePicker.vue +25 -8
  45. package/src/components/BaseDateSelect.stories.js +15 -9
  46. package/src/components/BaseDateSelect.vue +20 -8
  47. package/src/components/BaseField.vue +109 -0
  48. package/src/components/BaseFieldI18n.stories.js +38 -0
  49. package/src/components/BaseFieldI18n.vue +162 -0
  50. package/src/components/BaseFileUploader.stories.js +3 -3
  51. package/src/components/BaseFileUploader.vue +3 -3
  52. package/src/components/BaseForm.vue +298 -0
  53. package/src/components/BaseFormField.ts +117 -0
  54. package/src/components/BaseHasMany.stories.js +25 -10
  55. package/src/components/BaseHasMany.vue +9 -9
  56. package/src/components/BaseInput.stories.js +27 -14
  57. package/src/components/BaseInput.vue +17 -8
  58. package/src/components/BaseInputError.vue +7 -0
  59. package/src/components/BaseInputPercent.stories.js +10 -3
  60. package/src/components/BaseInputPercent.vue +2 -1
  61. package/src/components/BaseLocaleForm.vue +142 -0
  62. package/src/components/BaseMediaLibrary.stories.js +7 -6
  63. package/src/components/BaseMediaLibrary.vue +32 -31
  64. package/src/components/BaseMenu.vue +1 -1
  65. package/src/components/BaseNumberForm.vue +67 -0
  66. package/src/components/BasePassword.stories.js +9 -4
  67. package/src/components/BasePassword.vue +49 -44
  68. package/src/components/BasePasswordForm.vue +59 -0
  69. package/src/components/BaseRadioGroup.stories.js +9 -8
  70. package/src/components/BaseRadioGroup.vue +17 -3
  71. package/src/components/BaseSelect.stories.js +15 -2
  72. package/src/components/BaseSelect.vue +26 -10
  73. package/src/components/BaseSwitch.stories.js +7 -0
  74. package/src/components/BaseSwitch.vue +134 -124
  75. package/src/components/BaseTagAutocomplete.stories.js +21 -14
  76. package/src/components/BaseTagAutocomplete.vue +25 -14
  77. package/src/components/BaseTagAutocompleteFetch.stories.js +37 -21
  78. package/src/components/BaseTagAutocompleteFetch.vue +5 -5
  79. package/src/components/BaseTextarea.stories.js +11 -3
  80. package/src/components/BaseTextarea.vue +20 -6
  81. package/src/components/BaseTextareaAutoresize.stories.js +11 -2
  82. package/src/components/BaseTextareaAutoresize.vue +28 -4
  83. package/src/components/BaseTextareaForm.vue +101 -0
  84. package/src/components/BaseTimeline.vue +1 -1
  85. package/src/components/BaseTimelineItem.vue +4 -4
  86. package/src/components/index.ts +6 -0
  87. package/src/composables/field.ts +100 -0
  88. package/src/index.ts +11 -1
  89. package/src/types/index.ts +12 -0
@@ -23,13 +23,13 @@
23
23
  ref="input"
24
24
  :value="modelValue"
25
25
  :type="type"
26
- :name="name"
26
+ :name="nameInternal"
27
27
  :step="step"
28
28
  :min="min"
29
29
  :max="max"
30
30
  :disabled="disabled"
31
31
  :placeholder="placeholder"
32
- :required="required"
32
+ :required="requiredInternal"
33
33
  class="w-full border-none bg-transparent outline-none focus:z-[1] focus:ring-2 focus:ring-primary-600 focus:ring-offset-1 disabled:cursor-not-allowed disabled:text-slate-300"
34
34
  :class="{
35
35
  'rounded-l': !iconLeft && !prefix,
@@ -37,7 +37,7 @@
37
37
  }"
38
38
  :autocomplete="autocomplete ? 'on' : 'off'"
39
39
  @keydown.enter="onEnter"
40
- @input="$emit('update:modelValue', transformInputValue($event))"
40
+ @input="emitUpdate(transformInputValue($event))"
41
41
  @focus="$emit('focus', $event)"
42
42
  @blur="$emit('blur', $event)"
43
43
  />
@@ -66,7 +66,8 @@
66
66
  <script lang="ts" setup>
67
67
  import { get, isNumber, isString, trim } from 'lodash';
68
68
  import { PropType } from 'vue';
69
- import { BaseIcon } from './index';
69
+ import { BaseIcon } from '@/index';
70
+ import { useField } from '@/composables/field';
70
71
 
71
72
  const props = defineProps({
72
73
  modelValue: {
@@ -138,7 +139,15 @@ const props = defineProps({
138
139
  },
139
140
  });
140
141
 
141
- defineEmits(['update:modelValue', 'focus', 'blur']);
142
+ const emit = defineEmits(['update:modelValue', 'focus', 'blur']);
143
+
144
+ const { nameInternal, requiredInternal, hasErrorInternal, emitUpdate } =
145
+ useField({
146
+ name: computed(() => props.name),
147
+ required: computed(() => props.required),
148
+ hasError: computed(() => props.hasError),
149
+ emit: emit,
150
+ });
142
151
 
143
152
  function transformInputValue(event: Event | null): string | null {
144
153
  if (event === null) {
@@ -166,14 +175,14 @@ function onEnter(e: Event) {
166
175
  }
167
176
 
168
177
  const borderColor = computed(() => {
169
- return props.hasError ? 'border-red-500' : 'border-slate-300';
178
+ return hasErrorInternal.value ? 'border-red-500' : 'border-slate-300';
170
179
  });
171
180
 
172
181
  const backgroundColor = computed(() => {
173
- return props.hasError ? 'bg-red-100' : 'bg-slate-100';
182
+ return hasErrorInternal.value ? 'bg-red-100' : 'bg-slate-100';
174
183
  });
175
184
 
176
185
  const textColor = computed(() => {
177
- return props.hasError ? 'text-red-800' : 'text-slate-600';
186
+ return hasErrorInternal.value ? 'text-red-800' : 'text-slate-600';
178
187
  });
179
188
  </script>
@@ -0,0 +1,7 @@
1
+ <template>
2
+ <p class="block text-sm font-medium leading-tight text-red-600">
3
+ <slot></slot>
4
+ </p>
5
+ </template>
6
+
7
+ <script lang="ts" setup></script>
@@ -1,11 +1,11 @@
1
1
  import BaseInputPercent from './BaseInputPercent.vue';
2
+ import ShowValue from '../../.storybook/components/ShowValue.vue';
3
+ import { createFieldStory } from '@/../.storybook/utils';
2
4
 
3
5
  export default {
4
6
  title: 'Form/BaseInputPercent',
5
7
  component: BaseInputPercent,
6
8
  args: {
7
- required: true,
8
- name: 'rebate',
9
9
  placeholder: 'Enter rebate, eg: 50%',
10
10
  step: 0.1,
11
11
  min: 0,
@@ -15,6 +15,7 @@ export default {
15
15
 
16
16
  const Template = (args) => ({
17
17
  components: {
18
+ ShowValue,
18
19
  BaseInputPercent,
19
20
  },
20
21
  setup() {
@@ -25,7 +26,7 @@ const Template = (args) => ({
25
26
  <form @submit.prevent="" class="border-none">
26
27
  <BaseInputPercent v-model="value" v-bind="args" class="w-full"></BaseInputPercent>
27
28
  </form>
28
- <pre class="mt-4 bg-slate-800 font-light text-xs p-3 rounded text-white">{{ value }}</pre>
29
+ <ShowValue :value="value" />
29
30
  `,
30
31
  });
31
32
 
@@ -41,3 +42,9 @@ export const Error = Template.bind({});
41
42
  Error.args = {
42
43
  hasError: true,
43
44
  };
45
+
46
+ export const Field = createFieldStory({
47
+ component: BaseInputPercent,
48
+ componentName: 'BaseInputPercent',
49
+ label: 'Rebate',
50
+ });
@@ -3,6 +3,7 @@
3
3
  :model-value="value"
4
4
  suffix="%"
5
5
  type="number"
6
+ :required="required"
6
7
  :prevent-submit="preventSubmit"
7
8
  :name="name"
8
9
  :step="step"
@@ -19,7 +20,7 @@
19
20
  <script lang="ts" setup>
20
21
  import { round } from 'lodash';
21
22
  import { PropType } from 'vue';
22
- import { BaseInput } from '.';
23
+ import BaseInput from './BaseInput.vue';
23
24
 
24
25
  const props = defineProps({
25
26
  /**
@@ -0,0 +1,142 @@
1
+ <template>
2
+ <div class="">
3
+ <BaseInputLabel
4
+ v-if="label"
5
+ :label="label"
6
+ classes="form-input-label mb-2"
7
+ :required="required"
8
+ />
9
+ <div class="space-y-1">
10
+ <div v-for="(locale, key, index) in localesInternal" :key="key">
11
+ <label
12
+ :for="name"
13
+ :class="[
14
+ layout == 'header'
15
+ ? 'border-b-none flex items-center rounded-t border border-slate-300 bg-slate-50 py-1.5 px-2 text-sm text-slate-700'
16
+ : 'mb-1.5 flex items-center text-sm text-slate-700',
17
+ ]"
18
+ >
19
+ <div
20
+ v-if="layout == 'header'"
21
+ class="mr-2 h-2.5 w-2.5 rounded-full"
22
+ :style="{ backgroundColor: colors[index] }"
23
+ />
24
+ <Icon
25
+ v-if="layout == 'header'"
26
+ icon="heroicons:language-solid"
27
+ class="mr-1"
28
+ />
29
+ <span>{{ locale }}</span>
30
+ </label>
31
+ <component
32
+ :is="component"
33
+ :model-value="formattedValue[key]"
34
+ :name="`${name}.${key}`"
35
+ :required="required"
36
+ v-bind="props"
37
+ :placeholder="placeholder"
38
+ :input-class="inputClass + ' border-t-0 rounded-t-none w-full'"
39
+ @update:model-value="onInput($event, key + '')"
40
+ />
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </template>
45
+
46
+ <script lang="ts">
47
+ import { defineComponent, PropType } from 'vue';
48
+ import { get, isPlainObject } from 'lodash';
49
+ import objectHash from 'object-hash';
50
+ import { Locales } from '@/types';
51
+ import { config } from '@/index';
52
+ import BaseFormField from './BaseFormField';
53
+ import BaseInputLabel from './BaseInputLabel.vue';
54
+
55
+ type Value = { [locale: string]: string | number | boolean };
56
+
57
+ export default defineComponent({
58
+ components: { BaseInputLabel },
59
+ extends: BaseFormField,
60
+ props: {
61
+ modelValue: {
62
+ required: true,
63
+ type: [Object, null, undefined] as PropType<Value | null | undefined>,
64
+ },
65
+ locales: {
66
+ default: undefined,
67
+ type: Object as PropType<Locales>,
68
+ },
69
+ component: {
70
+ default: 'VInput',
71
+ type: String,
72
+ },
73
+ props: {
74
+ default: undefined,
75
+ type: Object,
76
+ },
77
+ defaultValue: {
78
+ default: '',
79
+ type: [String, Boolean, Number] as PropType<string | boolean | number>,
80
+ },
81
+ layout: {
82
+ default: 'header',
83
+ type: String,
84
+ },
85
+ inputClass: {
86
+ default: undefined,
87
+ type: String,
88
+ },
89
+ },
90
+ data() {
91
+ return {
92
+ colors: ['#10b981', '#06b6d4', '#a855f7'],
93
+ };
94
+ },
95
+ computed: {
96
+ localesInternal(): Locales {
97
+ if (this.locales) {
98
+ return this.locales;
99
+ }
100
+ return config.locales;
101
+ },
102
+ formattedValue(): Value {
103
+ // Get current value
104
+ let value = {} as Value;
105
+ if (this.modelValue && isPlainObject(this.modelValue)) {
106
+ value = this.modelValue as Value;
107
+ }
108
+ // Fill missing locales
109
+ Object.keys(this.localesInternal).forEach((locale) => {
110
+ const currentValue = get(this.modelValue, locale);
111
+ if (!currentValue) {
112
+ value[locale] = this.defaultValue;
113
+ }
114
+ });
115
+ return value;
116
+ },
117
+ },
118
+ created() {
119
+ // If formatted value is different, send event to parent
120
+ if (
121
+ !this.modelValue ||
122
+ objectHash(this.formattedValue) !== objectHash(this.modelValue)
123
+ ) {
124
+ const formattedValue = this.formattedValue;
125
+ this.inputListener(formattedValue);
126
+ }
127
+ },
128
+ methods: {
129
+ getLabel(locale: string): string {
130
+ if (Object.keys(this.localesInternal).length > 1) {
131
+ return `${this.label} (${locale})`;
132
+ }
133
+ return this.label;
134
+ },
135
+ onInput(value: string, locale: string) {
136
+ const formattedValue = this.formattedValue;
137
+ formattedValue[locale] = value;
138
+ this.inputListener(formattedValue);
139
+ },
140
+ },
141
+ });
142
+ </script>
@@ -1,5 +1,6 @@
1
1
  import BaseApp from './BaseApp.vue';
2
2
  import BaseMediaLibrary from './BaseMediaLibrary.vue';
3
+ import { createFieldStory } from '@/../.storybook/utils';
3
4
 
4
5
  const mediaModel = {
5
6
  id: 'xxxxx',
@@ -13,10 +14,10 @@ export default {
13
14
  title: 'Form/BaseMediaLibrary',
14
15
  component: BaseMediaLibrary,
15
16
  args: {
16
- name: 'media',
17
- max: 2,
17
+ max: 6,
18
18
  min: 2,
19
19
  acceptedExtensions: ['jpg', 'png'],
20
+ uploadUrl: 'https://api.com/upload',
20
21
  maxSize: 500 * 1024,
21
22
  currentMedia: [
22
23
  mediaModel,
@@ -74,7 +75,7 @@ Disabled.args = {
74
75
  disabled: true,
75
76
  };
76
77
 
77
- export const Errors = Template.bind({});
78
- Errors.args = {
79
- errors: ['Whoops, you forgot the name of your mother!'],
80
- };
78
+ export const Field = createFieldStory({
79
+ component: BaseMediaLibrary,
80
+ componentName: 'BaseMediaLibrary',
81
+ });
@@ -7,6 +7,7 @@
7
7
  button-class="w-full"
8
8
  :accept="accept"
9
9
  :accepted-extensions="acceptedExtensions"
10
+ :url="uploadUrl"
10
11
  @upload:start="$emit('upload:start', $event)"
11
12
  @upload:end="$emit('upload:end', $event)"
12
13
  @upload:fail="$emit('upload:fail', $event)"
@@ -49,10 +50,6 @@
49
50
  </template>
50
51
  </BaseFileUploader>
51
52
 
52
- <BaseAlert v-if="globalErrorMessage" class="mt-5" color="danger" bordered>
53
- {{ globalErrorMessage }}
54
- </BaseAlert>
55
-
56
53
  <div
57
54
  v-if="currentMediaInternal.length + normalizedModelValue.to_add.length"
58
55
  class="mt-5"
@@ -91,39 +88,30 @@
91
88
  </template>
92
89
 
93
90
  <script lang="ts" setup>
91
+ import { PropType } from 'vue';
92
+ import { cloneDeep, isArray, isObject, capitalize } from 'lodash';
94
93
  import { UploadedFile } from '@/types/UploadedFile';
95
94
  import { Media } from '@/types/Media';
96
- import { cloneDeep, isArray, isObject } from 'lodash';
97
- import { PropType } from 'vue';
98
95
  import { MediaLibraryPayload } from '@/types';
99
- import { useDialogsStore } from '../stores/dialogs';
100
- import { useNotificationsStore } from '../stores/notifications';
101
- import { capitalize } from 'lodash';
102
- import BaseFileUploader from './BaseFileUploader.vue';
103
- import BaseMediaItem from './BaseMediaItem.vue';
104
96
  import { fileSizeFormat } from '@/utils';
105
- import BaseAlert from './BaseAlert.vue';
97
+ import { useDialogsStore } from '@/stores/dialogs';
98
+ import { useNotificationsStore } from '@/stores/notifications';
99
+ import BaseMediaItem from '@/components/BaseMediaItem.vue';
100
+ import BaseFileUploader from './BaseFileUploader.vue';
101
+ import { useField } from '@/composables/field';
106
102
 
107
103
  const i18n = useI18n();
108
104
 
109
105
  const dialogs = useDialogsStore();
110
106
  const notifications = useNotificationsStore();
111
107
 
112
- const emit = defineEmits([
113
- 'update',
114
- 'upload:start',
115
- 'upload:success',
116
- 'upload:fail',
117
- 'upload:end',
118
- ]);
119
-
120
108
  const props = defineProps({
121
109
  modelValue: {
122
110
  default: undefined,
123
111
  type: Object as PropType<MediaLibraryPayload | null | undefined>,
124
112
  },
125
113
  name: {
126
- required: true,
114
+ default: undefined,
127
115
  type: String,
128
116
  },
129
117
  min: {
@@ -152,9 +140,13 @@ const props = defineProps({
152
140
  },
153
141
  type: Array as PropType<Media[]>,
154
142
  },
155
- errors: {
143
+ uploadUrl: {
156
144
  default: undefined,
157
- type: [Array] as PropType<string[]>,
145
+ type: String,
146
+ },
147
+ hasError: {
148
+ default: false,
149
+ type: Boolean,
158
150
  },
159
151
  disabled: {
160
152
  default: false,
@@ -162,6 +154,22 @@ const props = defineProps({
162
154
  },
163
155
  });
164
156
 
157
+ const emit = defineEmits([
158
+ 'update:modelValue',
159
+ 'upload:start',
160
+ 'upload:success',
161
+ 'upload:fail',
162
+ 'upload:end',
163
+ ]);
164
+
165
+ const { emitUpdate } = useField({
166
+ name: computed(() => props.name),
167
+ required: computed(() => false),
168
+ hasError: computed(() => props.hasError),
169
+ emit: emit,
170
+ errorType: 'alert',
171
+ });
172
+
165
173
  const currentMediaInternal = ref(cloneDeep(props.currentMedia));
166
174
 
167
175
  const normalizedModelValue = computed(() => {
@@ -265,16 +273,9 @@ function removeMedia(index: number) {
265
273
  }
266
274
 
267
275
  function sync(modelValue: MediaLibraryPayload) {
268
- emit('update', modelValue);
276
+ emitUpdate(modelValue);
269
277
  }
270
278
 
271
- const globalErrorMessage = computed(() => {
272
- if (props.errors && props.errors.length) {
273
- return props.errors[0];
274
- }
275
- return '';
276
- });
277
-
278
279
  const maxFileText = computed(() => {
279
280
  return i18n.t('sui.you_can_upload_up_to_n_files', { count: props.max });
280
281
  });
@@ -25,7 +25,7 @@
25
25
  >
26
26
  <slot name="items" :items="items">
27
27
  <template v-for="item in items" :key="item.label + 'link'">
28
- <div v-if="item.line" class="my-1 -mx-1 flex h-px bg-gray-200" />
28
+ <div v-if="item.line" class="my-1 -mx-1 flex h-px bg-slate-200" />
29
29
 
30
30
  <router-link
31
31
  v-else-if="item.to"
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <div>
3
+ <BaseInputLabel
4
+ v-if="labelValue"
5
+ :label="labelValue"
6
+ :required="required"
7
+ />
8
+ <input
9
+ :value="modelValue"
10
+ type="number"
11
+ :name="name"
12
+ :min="min"
13
+ :max="max"
14
+ :step="step"
15
+ :disabled="disabled"
16
+ @input="inputListener(transformInputValue($event))"
17
+ />
18
+ <BaseInputError v-if="hasError()" class="mt-1">
19
+ {{ errorMessage() }}
20
+ </BaseInputError>
21
+ </div>
22
+ </template>
23
+
24
+ <script lang="ts">
25
+ import { get, isNumber } from 'lodash';
26
+ import { defineComponent } from 'vue';
27
+ import BaseFormField from './BaseFormField';
28
+ import BaseInputError from './BaseInputError.vue';
29
+ import BaseInputLabel from './BaseInputLabel.vue';
30
+
31
+ export default defineComponent({
32
+ components: { BaseInputLabel, BaseInputError },
33
+ extends: BaseFormField,
34
+ props: {
35
+ modelValue: {
36
+ required: true,
37
+ validator: (value) => {
38
+ return isNumber(value) || value === null;
39
+ },
40
+ },
41
+ step: {
42
+ default: 1,
43
+ type: Number,
44
+ },
45
+ min: {
46
+ type: Number,
47
+ default: undefined,
48
+ },
49
+ max: {
50
+ type: Number,
51
+ default: undefined,
52
+ },
53
+ },
54
+ methods: {
55
+ transformInputValue(event: Event | null): number | null {
56
+ if (event === null) {
57
+ return null;
58
+ }
59
+ const value = get(event, 'target.value', null);
60
+ if (!isNumber(value)) {
61
+ return null;
62
+ }
63
+ return value as number;
64
+ },
65
+ },
66
+ });
67
+ </script>
@@ -1,12 +1,10 @@
1
1
  import BasePassword from './BasePassword.vue';
2
+ import { createFieldStory } from '@/../.storybook/utils';
2
3
 
3
4
  export default {
4
5
  title: 'Form/BasePassword',
5
6
  component: BasePassword,
6
- args: {
7
- required: true,
8
- name: 'password',
9
- },
7
+ args: {},
10
8
  };
11
9
 
12
10
  const Template = (args) => ({
@@ -26,6 +24,7 @@ const Template = (args) => ({
26
24
 
27
25
  export const Demo = Template.bind({});
28
26
  Demo.args = {
27
+ required: true,
29
28
  placeholder: 'Enter your password',
30
29
  };
31
30
 
@@ -34,3 +33,9 @@ Disabled.args = {
34
33
  disabled: true,
35
34
  placeholder: 'Enter your password',
36
35
  };
36
+
37
+ export const Field = createFieldStory({
38
+ component: BasePassword,
39
+ componentName: 'BasePassword',
40
+ label: 'Password',
41
+ });
@@ -1,18 +1,20 @@
1
1
  <template>
2
2
  <div
3
- class="flex rounded border border-slate-300 bg-white"
4
- :class="[disabled ? 'cursor-not-allowed text-slate-300' : '']"
3
+ class="flex rounded border bg-white"
4
+ :class="[
5
+ disabled ? 'cursor-not-allowed text-slate-300' : '',
6
+ hasErrorInternal ? 'border-red-500' : 'border-slate-300',
7
+ ]"
5
8
  >
6
9
  <input
7
10
  ref="input"
8
11
  :value="modelValue"
9
12
  :type="showPassword ? 'text' : 'password'"
10
- :name="name"
13
+ :name="nameInternal"
11
14
  :disabled="disabled"
12
15
  :placeholder="placeholder"
13
- :required="required"
16
+ :required="requiredInternal"
14
17
  class="grow rounded-l rounded-r-none border-none focus:ring-2 focus:ring-primary-500 disabled:cursor-not-allowed"
15
- :class="inputClass"
16
18
  @input="onInput"
17
19
  />
18
20
  <div class="flex shrink-0 pl-3">
@@ -34,50 +36,53 @@
34
36
  </div>
35
37
  </template>
36
38
 
37
- <script lang="ts">
39
+ <script lang="ts" setup>
38
40
  import { trim } from 'lodash';
39
- import { defineComponent, PropType } from 'vue';
40
41
  import { Icon as BaseIcon } from '@iconify/vue';
42
+ import { PropType } from 'vue';
43
+ import { useField } from '@/composables/field';
41
44
 
42
- export default defineComponent({
43
- components: { BaseIcon },
44
- props: {
45
- modelValue: {
46
- default: '',
47
- type: [String, null] as PropType<string | null>,
48
- },
49
- disabled: {
50
- default: false,
51
- type: Boolean,
52
- },
53
- name: {
54
- default: undefined,
55
- type: String,
56
- },
57
- placeholder: {
58
- default: undefined,
59
- type: String,
60
- },
61
- required: {
62
- default: undefined,
63
- type: Boolean,
64
- },
65
- inputClass: {
66
- default: '',
67
- type: String,
68
- },
45
+ const props = defineProps({
46
+ modelValue: {
47
+ default: '',
48
+ type: [String, null] as PropType<string | null>,
69
49
  },
70
- emits: ['update:modelValue'],
71
- data() {
72
- return {
73
- showPassword: false,
74
- };
50
+ disabled: {
51
+ default: false,
52
+ type: Boolean,
75
53
  },
76
- methods: {
77
- onInput(event: any) {
78
- const value = event.target.value + '';
79
- this.$emit('update:modelValue', trim(value));
80
- },
54
+ name: {
55
+ default: undefined,
56
+ type: String,
57
+ },
58
+ placeholder: {
59
+ default: undefined,
60
+ type: String,
61
+ },
62
+ required: {
63
+ default: false,
64
+ type: Boolean,
65
+ },
66
+ hasError: {
67
+ default: false,
68
+ type: Boolean,
81
69
  },
82
70
  });
71
+
72
+ const emit = defineEmits(['update:modelValue']);
73
+
74
+ const { nameInternal, requiredInternal, hasErrorInternal, emitUpdate } =
75
+ useField({
76
+ name: computed(() => props.name),
77
+ required: computed(() => props.required),
78
+ hasError: computed(() => props.hasError),
79
+ emit: emit,
80
+ });
81
+
82
+ const showPassword = ref(false);
83
+
84
+ function onInput(event: any) {
85
+ const value = event.target.value + '';
86
+ emitUpdate(trim(value));
87
+ }
83
88
  </script>