sprintify-ui 0.0.194 → 0.0.196

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 (46) hide show
  1. package/dist/sprintify-ui.es.js +6517 -6429
  2. package/dist/style.css +1 -1
  3. package/dist/types/src/components/BaseAddressForm.vue.d.ts +2 -2
  4. package/dist/types/src/components/BaseAutocomplete.vue.d.ts +5 -5
  5. package/dist/types/src/components/BaseAutocompleteDrawer.vue.d.ts +2 -2
  6. package/dist/types/src/components/BaseAutocompleteFetch.vue.d.ts +5 -5
  7. package/dist/types/src/components/BaseBelongsTo.vue.d.ts +3 -3
  8. package/dist/types/src/components/BaseButtonGroup.vue.d.ts +2 -2
  9. package/dist/types/src/components/BaseCharacterCounter.vue.d.ts +1 -1
  10. package/dist/types/src/components/BaseColor.vue.d.ts +2 -2
  11. package/dist/types/src/components/BaseDatePicker.vue.d.ts +1 -1
  12. package/dist/types/src/components/BaseDropdownAutocomplete.vue.d.ts +4 -4
  13. package/dist/types/src/components/BaseFieldI18n.vue.d.ts +2 -2
  14. package/dist/types/src/components/BaseFilePicker.vue.d.ts +1 -1
  15. package/dist/types/src/components/BaseFileUploader.vue.d.ts +2 -2
  16. package/dist/types/src/components/BaseForm.vue.d.ts +1 -1
  17. package/dist/types/src/components/BaseHasMany.vue.d.ts +2 -2
  18. package/dist/types/src/components/BaseInput.vue.d.ts +5 -5
  19. package/dist/types/src/components/BaseInputPercent.vue.d.ts +4 -4
  20. package/dist/types/src/components/BaseLoadingCover.vue.d.ts +2 -2
  21. package/dist/types/src/components/BaseMediaLibrary.vue.d.ts +4 -4
  22. package/dist/types/src/components/BaseModalCenter.vue.d.ts +1 -1
  23. package/dist/types/src/components/BaseModalSide.vue.d.ts +1 -1
  24. package/dist/types/src/components/BaseNavbarItemContent.vue.d.ts +1 -1
  25. package/dist/types/src/components/BaseNavbarSideItemContent.vue.d.ts +1 -1
  26. package/dist/types/src/components/BaseNumber.vue.d.ts +16 -7
  27. package/dist/types/src/components/BasePassword.vue.d.ts +2 -2
  28. package/dist/types/src/components/BaseRadioGroup.vue.d.ts +1 -1
  29. package/dist/types/src/components/BaseRichText.vue.d.ts +2 -2
  30. package/dist/types/src/components/BaseSelect.vue.d.ts +3 -3
  31. package/dist/types/src/components/BaseSwitch.vue.d.ts +1 -1
  32. package/dist/types/src/components/BaseTabItem.vue.d.ts +33 -30
  33. package/dist/types/src/components/BaseTabs.vue.d.ts +25 -1
  34. package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +4 -4
  35. package/dist/types/src/components/BaseTagAutocompleteFetch.vue.d.ts +2 -2
  36. package/dist/types/src/components/BaseTextarea.vue.d.ts +3 -3
  37. package/dist/types/src/components/BaseTextareaAutoresize.vue.d.ts +2 -2
  38. package/package.json +1 -1
  39. package/src/components/BaseForm.stories.js +48 -0
  40. package/src/components/BaseForm.vue +10 -5
  41. package/src/components/BaseNumber.stories.js +19 -3
  42. package/src/components/BaseNumber.vue +57 -4
  43. package/src/components/BaseTabItem.vue +53 -22
  44. package/src/components/BaseTabs.stories.js +33 -2
  45. package/src/components/BaseTabs.vue +74 -3
  46. package/src/composables/field.ts +11 -4
@@ -8,7 +8,7 @@
8
8
  leave-from-class="transform scale-100 opacity-100"
9
9
  leave-to-class="transform scale-90 opacity-0"
10
10
  >
11
- <div v-if="invalidInput" class="absolute left-0 top-full">
11
+ <div v-if="showInvalidInput" class="absolute left-0 top-full z-[1]">
12
12
  <div
13
13
  class="mt-1 ml-1 rounded bg-red-500 px-2 py-1 text-xs font-medium text-white"
14
14
  >
@@ -36,7 +36,7 @@
36
36
  :max="max"
37
37
  :class="[
38
38
  ['full', 'left'].includes(rounded) ? 'rounded-l' : '',
39
- invalidInput ? 'focus:ring-red-400' : 'focus:ring-primary-500',
39
+ showInvalidInput ? 'focus:ring-red-400' : 'focus:ring-primary-500',
40
40
  ]"
41
41
  class="w-full border-none focus:z-[1] focus:border-none focus:border-transparent focus:shadow-none focus:outline-none focus:ring-2 focus:ring-offset-1 disabled:cursor-not-allowed disabled:text-slate-300"
42
42
  type="text"
@@ -73,8 +73,10 @@
73
73
  import { useField } from '@/composables/field';
74
74
  import { isNumber, round } from 'lodash';
75
75
  import { PropType } from 'vue';
76
+ import { BaseIcon } from '.';
76
77
 
77
78
  const AUTO_CORRECT_TIMEOUT = 2000;
79
+ const SHOW_INVALID_INPUT_TIMEOUT = 500;
78
80
 
79
81
  const props = defineProps({
80
82
  modelValue: {
@@ -124,17 +126,23 @@ const props = defineProps({
124
126
  default: 'full',
125
127
  type: String as PropType<'full' | 'left' | 'right' | 'none'>,
126
128
  },
129
+ autoFix: {
130
+ default: false,
131
+ type: Boolean,
132
+ },
127
133
  });
128
134
 
129
135
  const emit = defineEmits(['update:modelValue', 'focus', 'blur', 'keydown']);
130
136
 
131
- const { hasErrorInternal, emitUpdate } = useField({
137
+ const { hasErrorInternal, emitUpdate, enableForm, disableForm } = useField({
132
138
  name: computed(() => props.name),
133
139
  required: computed(() => props.required),
134
140
  hasError: computed(() => props.hasError),
135
141
  emit: emit,
136
142
  });
137
143
 
144
+ const showInvalidInput = ref(false);
145
+
138
146
  const stepNormalized = computed<number>(() => {
139
147
  if (props.step === undefined) return 1;
140
148
  if (props.step === 0) return 1;
@@ -170,9 +178,11 @@ function convertToNumber(
170
178
  }
171
179
 
172
180
  const valueInternal = ref<null | string | number>(null);
181
+
173
182
  const realValueInternal = computed<number | null>(() => {
174
183
  return convertToNumber(valueInternal.value);
175
184
  });
185
+
176
186
  const invalidInput = computed(() => {
177
187
  if (realValueInternal.value == null && valueInternal.value == '') {
178
188
  return false;
@@ -180,10 +190,12 @@ const invalidInput = computed(() => {
180
190
 
181
191
  return realValueInternal.value != valueInternal.value;
182
192
  });
193
+
183
194
  const tooBig = computed(() => {
184
195
  if (valueInternal.value === null) return false;
185
196
  return hasMax.value && valueInternal.value > (props.max as number);
186
197
  });
198
+
187
199
  const tooSmall = computed(() => {
188
200
  if (valueInternal.value === null) return false;
189
201
  return hasMin.value && valueInternal.value < (props.min as number);
@@ -214,13 +226,37 @@ function onInput(event: any) {
214
226
 
215
227
  emitUpdate(realValueInternal.value);
216
228
 
229
+ nextTick(() => {
230
+ showHideInvalidOnInput();
231
+ });
232
+
217
233
  timeoutId = setTimeout(() => {
218
234
  updateInternalValueToRealValue();
219
235
  }, AUTO_CORRECT_TIMEOUT);
220
236
  }
221
237
 
238
+ let showInvalidInputTimeoutId = undefined as undefined | number;
239
+
240
+ function showHideInvalidOnInput() {
241
+ clearTimeout(showInvalidInputTimeoutId);
242
+
243
+ if (!invalidInput.value) {
244
+ showInvalidInput.value = false;
245
+ return;
246
+ }
247
+
248
+ showInvalidInputTimeoutId = setTimeout(() => {
249
+ showInvalidInput.value = true;
250
+ }, SHOW_INVALID_INPUT_TIMEOUT);
251
+ }
252
+
222
253
  function onBlur(e: Event) {
223
254
  emit('blur', e);
255
+
256
+ if (invalidInput.value) {
257
+ showInvalidInput.value = true;
258
+ }
259
+
224
260
  updateInternalValueToRealValue();
225
261
  }
226
262
 
@@ -244,6 +280,9 @@ const defaultValue = computed<number>(() => {
244
280
  });
245
281
 
246
282
  function updateInternalValueToRealValue() {
283
+ if (!props.autoFix) {
284
+ return;
285
+ }
247
286
  if (realValueInternal.value === null) {
248
287
  valueInternal.value = '';
249
288
  return;
@@ -287,7 +326,21 @@ function decrement() {
287
326
  }
288
327
 
289
328
  const borderColor = computed(() => {
290
- if (hasErrorInternal.value) return 'border-red-500';
329
+ if (hasErrorInternal.value || invalidInput.value) return 'border-red-500';
291
330
  return 'border-slate-300';
292
331
  });
332
+
333
+ /** Disable form */
334
+
335
+ watch(
336
+ () => invalidInput.value,
337
+ () => {
338
+ if (invalidInput.value) {
339
+ disableForm();
340
+ } else {
341
+ enableForm();
342
+ }
343
+ },
344
+ { immediate: true }
345
+ );
293
346
  </script>
@@ -7,25 +7,26 @@
7
7
  >
8
8
  <a
9
9
  :href="href"
10
- class="group relative inline-block rounded-t-lg px-2 py-3 font-medium"
10
+ class="group relative inline-block rounded-t-lg font-medium"
11
11
  :class="[
12
- (activeStrategy == 'default' ? isActive : isExactActive)
13
- ? 'text-primary-600'
12
+ tabIsActive(isActive, isExactActive)
13
+ ? 'active text-primary-600'
14
14
  : 'text-slate-600 hover:text-slate-900',
15
15
  disabled ? 'cursor-not-allowed opacity-60' : '',
16
+ sizeClass,
16
17
  ]"
17
- @click="navigate"
18
+ @click.prevent="onClick(navigate)"
18
19
  >
19
20
  <div
20
21
  class="absolute left-0 bottom-0 w-full"
21
22
  :class="[
22
- (activeStrategy == 'default' ? isActive : isExactActive)
23
+ tabIsActive(isActive, isExactActive)
23
24
  ? 'h-[2px] bg-primary-600'
24
25
  : 'group-hover:h-px group-hover:bg-slate-700',
25
26
  ]"
26
27
  ></div>
27
28
  <div class="whitespace-nowrap">
28
- <slot />
29
+ <slot :active="tabIsActive(isActive, isExactActive)" />
29
30
  </div>
30
31
  </a>
31
32
  </router-link>
@@ -33,21 +34,51 @@
33
34
  </template>
34
35
 
35
36
  <script lang="ts" setup>
36
- import { PropType } from 'vue';
37
- import { RouteLocationRaw } from 'vue-router';
38
-
39
- defineProps({
40
- to: {
41
- required: true,
42
- type: [Object, String] as PropType<RouteLocationRaw>,
43
- },
44
- disabled: {
45
- default: false,
46
- type: Boolean,
47
- },
48
- activeStrategy: {
49
- default: 'default',
50
- type: String as PropType<'default' | 'exact'>,
51
- },
37
+ import { ComputedRef } from 'vue';
38
+ import { NavigationFailure, RouteLocationRaw } from 'vue-router';
39
+
40
+ const props = withDefaults(
41
+ defineProps<{
42
+ to: RouteLocationRaw;
43
+ disabled?: boolean;
44
+ activeStrategy?: 'default' | 'exact';
45
+ }>(),
46
+ {
47
+ disabled: false,
48
+ activeStrategy: 'default',
49
+ }
50
+ );
51
+
52
+ const size = inject('tabs:size', ref('md')) as ComputedRef<
53
+ 'xs' | 'sm' | 'md' | 'lg'
54
+ >;
55
+
56
+ function onClick(navigate: () => Promise<void | NavigationFailure>) {
57
+ if (props.disabled) {
58
+ return;
59
+ }
60
+
61
+ return navigate();
62
+ }
63
+
64
+ function tabIsActive(isActive: boolean, isExactActive: boolean) {
65
+ if (props.activeStrategy == 'default') {
66
+ return isActive;
67
+ }
68
+
69
+ return isExactActive;
70
+ }
71
+
72
+ const sizeClass = computed(() => {
73
+ switch (size.value) {
74
+ case 'xs':
75
+ return 'text-xs px-1.5 py-2';
76
+ case 'sm':
77
+ return 'text-sm px-1.5 py-2';
78
+ case 'md':
79
+ return 'text-base px-2 py-3';
80
+ case 'lg':
81
+ return 'text-lg px-3 py-4';
82
+ }
52
83
  });
53
84
  </script>
@@ -3,6 +3,7 @@ import BaseTabItem from './BaseTabItem.vue';
3
3
  import BaseContainer from './BaseContainer.vue';
4
4
  import BaseCard from './BaseCard.vue';
5
5
  import BaseCardRow from './BaseCardRow.vue';
6
+ import BaseCounter from './BaseCounter.vue';
6
7
 
7
8
  export default {
8
9
  title: 'Layout/BaseTabs',
@@ -17,6 +18,7 @@ const Template = (args) => ({
17
18
  BaseContainer,
18
19
  BaseCard,
19
20
  BaseCardRow,
21
+ BaseCounter,
20
22
  },
21
23
  setup() {
22
24
  return { args };
@@ -25,8 +27,11 @@ const Template = (args) => ({
25
27
  <div class="bg-slate-100 py-10">
26
28
  <BaseContainer>
27
29
  <BaseTabs v-bind="args">
28
- <BaseTabItem to="/">
29
- Home
30
+ <BaseTabItem to="/" v-slot="{active}">
31
+ <div class="flex items-center">
32
+ <span class="mr-1">Home</span>
33
+ <BaseCounter :size="args.size" :color="active ? 'primary' : 'light'" :count="1"></BaseCounter>
34
+ </div>
30
35
  </BaseTabItem>
31
36
  <BaseTabItem to="/setup">
32
37
  Setup
@@ -34,9 +39,15 @@ const Template = (args) => ({
34
39
  <BaseTabItem to="/settings">
35
40
  Settings
36
41
  </BaseTabItem>
42
+ <BaseTabItem to="/articles">
43
+ Articles
44
+ </BaseTabItem>
37
45
  <BaseTabItem to="/misc">
38
46
  Miscellaneous
39
47
  </BaseTabItem>
48
+ <BaseTabItem to="/users">
49
+ Users
50
+ </BaseTabItem>
40
51
  </BaseTabs>
41
52
  <div class="mt-10">
42
53
  <BaseCard>
@@ -52,3 +63,23 @@ const Template = (args) => ({
52
63
 
53
64
  export const Demo = Template.bind({});
54
65
  Demo.args = {};
66
+
67
+ export const SizeXS = Template.bind({});
68
+ SizeXS.args = {
69
+ size: 'xs',
70
+ };
71
+
72
+ export const SizeSM = Template.bind({});
73
+ SizeSM.args = {
74
+ size: 'sm',
75
+ };
76
+
77
+ export const SizeMD = Template.bind({});
78
+ SizeMD.args = {
79
+ size: 'md',
80
+ };
81
+
82
+ export const SizeLG = Template.bind({});
83
+ SizeLG.args = {
84
+ size: 'lg',
85
+ };
@@ -2,14 +2,85 @@
2
2
  <div class="relative">
3
3
  <div class="absolute bottom-0 left-0 h-px w-full bg-slate-300" />
4
4
  <div
5
- class="relative overflow-x-auto overflow-y-hidden"
5
+ ref="scrollable"
6
+ class="scrollable relative overflow-x-auto overflow-y-hidden"
6
7
  data-scroll-lock-scrollable
7
8
  >
8
- <ul class="flex space-x-4 text-center">
9
+ <ul class="flex text-center" :class="[sizeClass]">
9
10
  <slot />
10
11
  </ul>
11
12
  </div>
12
13
  </div>
13
14
  </template>
14
15
 
15
- <script lang="ts" setup></script>
16
+ <script lang="ts" setup>
17
+ const props = withDefaults(
18
+ defineProps<{
19
+ size?: 'xs' | 'sm' | 'md' | 'lg';
20
+ }>(),
21
+ {
22
+ size: 'md',
23
+ }
24
+ );
25
+
26
+ const route = useRoute();
27
+ const scrollable = ref<HTMLElement | null>(null);
28
+
29
+ watch(
30
+ () => route.fullPath,
31
+ () => {
32
+ nextTick(() => {
33
+ scrollToCenter();
34
+ });
35
+ }
36
+ );
37
+
38
+ provide(
39
+ 'tabs:size',
40
+ computed(() => props.size)
41
+ );
42
+
43
+ const sizeClass = computed(() => {
44
+ switch (props.size) {
45
+ case 'xs':
46
+ return 'space-x-1.5';
47
+ case 'sm':
48
+ return 'space-x-3';
49
+ case 'md':
50
+ return 'space-x-3';
51
+ case 'lg':
52
+ return 'space-x-6';
53
+ }
54
+ });
55
+
56
+ function scrollToCenter() {
57
+ if (!scrollable.value) {
58
+ return;
59
+ }
60
+
61
+ const activeTab = scrollable.value.querySelector(
62
+ '.active'
63
+ ) as HTMLElement | null;
64
+
65
+ if (!activeTab) {
66
+ return;
67
+ }
68
+
69
+ const scrollableRect = scrollable.value.getBoundingClientRect();
70
+ const activeTabRect = activeTab.getBoundingClientRect();
71
+
72
+ const scrollLeft =
73
+ activeTab.offsetLeft - (scrollableRect.width - activeTabRect.width) / 2;
74
+
75
+ scrollable.value.scrollTo({
76
+ left: scrollLeft,
77
+ behavior: 'smooth',
78
+ });
79
+ }
80
+ </script>
81
+
82
+ <style scoped>
83
+ .scrollable::-webkit-scrollbar {
84
+ display: none;
85
+ }
86
+ </style>
@@ -1,3 +1,4 @@
1
+ import { uniqueId } from 'lodash';
1
2
  import { Ref } from 'vue';
2
3
 
3
4
  interface Config {
@@ -10,6 +11,7 @@ interface Config {
10
11
  }
11
12
 
12
13
  export function useField(config: Config) {
14
+ const uuid = uniqueId();
13
15
  const name = config.name;
14
16
  const required = config.required;
15
17
  const hasError = config.hasError;
@@ -60,11 +62,15 @@ export function useField(config: Config) {
60
62
 
61
63
  const disableForm = inject('form:disable', () => {
62
64
  return;
63
- }) as () => void;
65
+ }) as (uuid: string) => void;
64
66
 
65
67
  const enableForm = inject('form:enable', () => {
66
68
  return;
67
- }) as () => void;
69
+ }) as (uuid: string) => void;
70
+
71
+ onBeforeUnmount(() => {
72
+ enableForm(uuid);
73
+ });
68
74
 
69
75
  const requiredInternal = computed((): boolean => {
70
76
  if (required.value) {
@@ -98,13 +104,14 @@ export function useField(config: Config) {
98
104
  emit('update:modelValue', value);
99
105
  fieldOnUpdate();
100
106
  }
107
+
101
108
  return {
102
109
  requiredInternal,
103
110
  nameInternal,
104
111
  hasErrorInternal,
105
112
  errorMessageInternal,
106
113
  emitUpdate,
107
- enableForm,
108
- disableForm,
114
+ enableForm: () => enableForm(uuid),
115
+ disableForm: () => disableForm(uuid),
109
116
  };
110
117
  }