sprintify-ui 0.10.24 → 0.10.26

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.
@@ -1,32 +1,50 @@
1
+ import { ComputedRef } from 'vue';
1
2
  import { NavigationFailure, RouteLocationRaw } from 'vue-router';
2
3
  type __VLS_Props = {
3
- to: RouteLocationRaw;
4
+ id?: string;
5
+ to?: RouteLocationRaw;
4
6
  disabled?: boolean;
7
+ /**
8
+ * The strategy to determine if the tab is active. Only used when the
9
+ * `to` prop is provided.
10
+ * - `default`: The tab is active if the route matches the path.
11
+ * - `exact`: The tab is active if the route matches the path exactly.
12
+ */
5
13
  activeStrategy?: 'default' | 'exact';
6
14
  };
7
- declare function onClick(navigate: () => Promise<void | NavigationFailure>): Promise<void | NavigationFailure> | undefined;
15
+ declare const idInternal: ComputedRef<string>;
16
+ declare const currentTabId: ComputedRef<string | null>;
17
+ declare function onClick(navigate?: () => Promise<void | NavigationFailure> | null): Promise<void | NavigationFailure> | null | undefined;
8
18
  declare function isActiveInternal(isActive: boolean, isExactActive: boolean): boolean;
9
19
  declare const baseTabItemRef: import("vue").Ref<HTMLElement | null, HTMLElement | null>;
10
- declare const sizeClassOuter: import("vue").ComputedRef<"" | "px-1" | "px-1.5" | "px-2">;
11
- declare const sizeClassInner: import("vue").ComputedRef<"" | "text-xs py-1 px-1 font-normal" | "text-sm py-1 px-2 font-normal" | "text-sm py-1.5 px-2 font-normal" | "text-base py-1.5 px-3 font-normal">;
20
+ declare const sizeClassOuter: ComputedRef<string>;
21
+ declare const sizeClassInner: ComputedRef<"" | "text-xs py-1 px-1 font-normal" | "text-sm py-1 px-2 font-normal" | "text-sm py-1.5 px-2 font-normal" | "text-base py-1.5 px-3 font-normal">;
12
22
  declare const __VLS_ctx: InstanceType<__VLS_PickNotAny<typeof __VLS_self, new () => {}>>;
13
23
  declare var __VLS_5: {
14
24
  active: boolean;
15
- };
25
+ }, __VLS_7: {};
16
26
  type __VLS_Slots = __VLS_PrettifyGlobal<__VLS_OmitStringIndex<typeof __VLS_ctx.$slots> & {
17
27
  default?: (props: typeof __VLS_5) => any;
28
+ } & {
29
+ default?: (props: typeof __VLS_7) => any;
18
30
  }>;
19
31
  declare const __VLS_self: import("vue").DefineComponent<__VLS_Props, {
32
+ idInternal: typeof idInternal;
33
+ currentTabId: typeof currentTabId;
20
34
  onClick: typeof onClick;
21
35
  isActiveInternal: typeof isActiveInternal;
22
36
  baseTabItemRef: typeof baseTabItemRef;
23
37
  sizeClassOuter: typeof sizeClassOuter;
24
38
  sizeClassInner: typeof sizeClassInner;
25
39
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
40
+ to: RouteLocationRaw;
41
+ id: string;
26
42
  disabled: boolean;
27
43
  activeStrategy: "default" | "exact";
28
44
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
29
45
  declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
46
+ to: RouteLocationRaw;
47
+ id: string;
30
48
  disabled: boolean;
31
49
  activeStrategy: "default" | "exact";
32
50
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -1,4 +1,5 @@
1
1
  type __VLS_Props = {
2
+ modelValue?: string | number | null | undefined;
2
3
  size?: 'xs' | 'sm' | 'md' | 'lg';
3
4
  };
4
5
  declare const scrollable: import("vue").Ref<HTMLElement | null, HTMLElement | null>;
@@ -11,11 +12,21 @@ type __VLS_Slots = __VLS_PrettifyGlobal<__VLS_OmitStringIndex<typeof __VLS_ctx.$
11
12
  declare const __VLS_self: import("vue").DefineComponent<__VLS_Props, {
12
13
  scrollable: typeof scrollable;
13
14
  lineRef: typeof lineRef;
14
- }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
15
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
16
+ "update:modelValue": (...args: any[]) => void;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
18
+ "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
19
+ }>, {
15
20
  size: "xs" | "sm" | "md" | "lg";
21
+ modelValue: string | number | null;
16
22
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
- declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
23
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
24
+ "update:modelValue": (...args: any[]) => void;
25
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
26
+ "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
27
+ }>, {
18
28
  size: "xs" | "sm" | "md" | "lg";
29
+ modelValue: string | number | null;
19
30
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
31
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
21
32
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sprintify-ui",
3
- "version": "0.10.24",
3
+ "version": "0.10.26",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "rimraf dist && vue-tsc && vite build",
@@ -198,7 +198,7 @@ const DEFAULT_QUERY = {
198
198
  </script>
199
199
 
200
200
  <script lang="ts" setup>
201
- import { cloneDeep, debounce, merge, set, sortBy } from 'lodash';
201
+ import { cloneDeep, debounce, merge, set, sortBy, uniqueId } from 'lodash';
202
202
  import hash from 'object-hash';
203
203
  import { PropType } from 'vue';
204
204
  import {
@@ -661,6 +661,8 @@ function fetchWithoutLoading(force = false) {
661
661
  fetch(force, false);
662
662
  }
663
663
 
664
+ let requestId = '';
665
+
664
666
  function fetch(force = false, showLoading = true) {
665
667
  if (willUnmount) {
666
668
  return;
@@ -682,6 +684,9 @@ function fetch(force = false, showLoading = true) {
682
684
  return;
683
685
  }
684
686
 
687
+ const requestIdInternal = uniqueId();
688
+ requestId = requestIdInternal;
689
+
685
690
  if (showLoading) {
686
691
  loading.value = true;
687
692
  }
@@ -691,6 +696,10 @@ function fetch(force = false, showLoading = true) {
691
696
  http
692
697
  .get(fullUrl)
693
698
  .then((response) => {
699
+ if (requestIdInternal !== requestId) {
700
+ return;
701
+ }
702
+
694
703
  data.value = response.data;
695
704
  error.value = false;
696
705
  firstLoad.value = true;
@@ -4,6 +4,7 @@
4
4
  class="[&:first-child_a]:pl-0 [&:last-child_a]:pr-0"
5
5
  >
6
6
  <RouterLink
7
+ v-if="to"
7
8
  v-slot="{ href, navigate, isActive, isExactActive }"
8
9
  :to="to"
9
10
  custom
@@ -16,7 +17,6 @@
16
17
  isActiveInternal(isActive, isExactActive)
17
18
  ? 'active text-primary-600'
18
19
  : 'text-slate-600 hover:text-slate-900',
19
- disabled ? 'cursor-not-allowed opacity-60' : '',
20
20
  sizeClassOuter,
21
21
  ]"
22
22
  @click.prevent="onClick(navigate)"
@@ -35,40 +35,104 @@
35
35
  </div>
36
36
  </a>
37
37
  </RouterLink>
38
+ <button
39
+ v-else
40
+ type="button"
41
+ :disabled="disabled"
42
+ class="group inline-block rounded-t-lg"
43
+ :class="[
44
+ currentTabId === idInternal
45
+ ? 'active text-primary-600'
46
+ : 'text-slate-600 hover:text-slate-900',
47
+ sizeClassOuter,
48
+ ]"
49
+ @click="onClick()"
50
+ >
51
+ <div class="relative flex py-1">
52
+ <div
53
+ :class="[
54
+ 'whitespace-nowrap rounded-md group-hover:bg-black group-hover:bg-opacity-5',
55
+ sizeClassInner
56
+ ]"
57
+ >
58
+ <div class="base-tab-item-slot">
59
+ <slot />
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </button>
38
64
  </li>
39
65
  </template>
40
66
 
41
67
  <script lang="ts" setup>
42
68
  import { useElementBounding } from '@vueuse/core';
69
+ import { uniqueId } from 'lodash';
70
+ import { ComputedRef } from 'vue';
43
71
  import { NavigationFailure, RouteLocationRaw } from 'vue-router';
44
72
 
73
+ const router = useRouter();
74
+
45
75
  const props = withDefaults(
46
76
  defineProps<{
47
- to: RouteLocationRaw;
77
+ id?: string;
78
+ to?: RouteLocationRaw;
48
79
  disabled?: boolean;
80
+ /**
81
+ * The strategy to determine if the tab is active. Only used when the
82
+ * `to` prop is provided.
83
+ * - `default`: The tab is active if the route matches the path.
84
+ * - `exact`: The tab is active if the route matches the path exactly.
85
+ */
49
86
  activeStrategy?: 'default' | 'exact';
50
87
  }>(),
51
88
  {
89
+ id: undefined,
90
+ to: undefined,
52
91
  disabled: false,
53
92
  activeStrategy: 'default',
54
93
  }
55
94
  );
56
95
 
96
+ const idInternal = computed(() => {
97
+ if (props.id) {
98
+ return props.id;
99
+ }
100
+
101
+ if (!router || !props.to) {
102
+ return uniqueId() + '';
103
+ }
104
+
105
+ const route = router.resolve(props.to);
106
+ const path = route.fullPath;
107
+
108
+ return path;
109
+ });
110
+
111
+ const currentTabId = inject<ComputedRef<string | null>>('tabs:currentTabId', computed(() => null));
112
+ const updateTabId = inject<((tabId: string) => void)>('tabs:updateTabId');
113
+
57
114
  const size = inject('tabs:size', ref<'xs' | 'sm' | 'md' | 'lg'>('md'));
58
- const animate = inject('tabs:animate', () => { });
59
115
 
60
- function onClick(navigate: () => Promise<void | NavigationFailure>) {
116
+ function onClick(navigate?: () => Promise<void | NavigationFailure> | null) {
61
117
  if (props.disabled) {
62
118
  return;
63
119
  }
64
120
 
65
- return navigate();
121
+ if (updateTabId) {
122
+ updateTabId(idInternal.value);
123
+ }
124
+
125
+ if (navigate) {
126
+ return navigate();
127
+ }
66
128
  }
67
129
 
68
130
  function isActiveInternal(isActive: boolean, isExactActive: boolean) {
69
131
  return props.activeStrategy == 'default' ? isActive : isExactActive;
70
132
  }
71
133
 
134
+ // Animate
135
+
72
136
  const baseTabItemRef = ref<HTMLElement | null>(null);
73
137
 
74
138
  const { x, y, width } = useElementBounding(baseTabItemRef);
@@ -80,20 +144,35 @@ watch(
80
144
  }
81
145
  );
82
146
 
147
+ const animate = inject('tabs:animate', () => { });
148
+
149
+ // Classes
83
150
 
84
151
  const sizeClassOuter = computed(() => {
152
+
153
+ const classes = [];
154
+
155
+ if (props.disabled) {
156
+ classes.push('cursor-not-allowed opacity-60');
157
+ }
158
+
85
159
  switch (size.value) {
86
160
  case 'xs':
87
- return 'px-1';
161
+ classes.push('px-1');
162
+ break;
88
163
  case 'sm':
89
- return 'px-1.5';
164
+ classes.push('px-1.5');
165
+ break;
90
166
  case 'md':
91
- return 'px-2';
167
+ classes.push('px-2');
168
+ break;
92
169
  case 'lg':
93
- return 'px-2';
94
- default:
95
- return '';
170
+ classes.push('px-3');
171
+ break;
96
172
  }
173
+
174
+ return classes.join(' ');
175
+
97
176
  });
98
177
 
99
178
  const sizeClassInner = computed(() => {
@@ -23,16 +23,18 @@ const Template = (args) => ({
23
23
  setup() {
24
24
  const label = ref("Home");
25
25
 
26
+ const modelValue = ref("setup");
27
+
26
28
  setInterval(() => {
27
29
  label.value = Math.random().toString(36).substring(7);
28
30
  }, 1000);
29
31
 
30
- return { args, label };
32
+ return { args, label, modelValue };
31
33
  },
32
34
  template: `
33
35
  <div class="bg-slate-100 py-10">
34
36
  <BaseContainer>
35
- <BaseTabs v-bind="args">
37
+ <BaseTabs v-model="modelValue" v-bind="args">
36
38
  <BaseTabItem to="/" v-slot="{active}">
37
39
  <div class="flex items-center">
38
40
  <span class="mr-1">{{ label }}</span>
@@ -62,7 +64,9 @@ const Template = (args) => ({
62
64
  <div class="mt-10">
63
65
  <BaseCard>
64
66
  <BaseCardRow>
65
- {{ $route.path }}
67
+ path: {{ $route.path }}
68
+ <br>
69
+ id: {{ modelValue }}
66
70
  </BaseCardRow>
67
71
  </BaseCard>
68
72
  </div>
@@ -93,3 +97,70 @@ export const SizeLG = Template.bind({});
93
97
  SizeLG.args = {
94
98
  size: "lg",
95
99
  };
100
+
101
+ const TemplateStatic = (args) => ({
102
+ components: {
103
+ BaseTabs,
104
+ BaseTabItem,
105
+ BaseContainer,
106
+ BaseCard,
107
+ BaseCardRow,
108
+ BaseCounter,
109
+ },
110
+ setup() {
111
+ const label = ref("Home");
112
+
113
+ const modelValue = ref("setup");
114
+
115
+ setInterval(() => {
116
+ label.value = Math.random().toString(36).substring(7);
117
+ }, 1000);
118
+
119
+ return { args, label, modelValue };
120
+ },
121
+ template: `
122
+ <div class="bg-slate-100 py-10">
123
+ <BaseContainer>
124
+ <BaseTabs v-model="modelValue" v-bind="args">
125
+ <BaseTabItem id="home" v-slot="{active}">
126
+ <div class="flex items-center">
127
+ <span class="mr-1">{{ label }}</span>
128
+ <BaseCounter
129
+ :size="['lg', 'md'].includes(args.size) ? 'sm' : 'xs'"
130
+ :color="active ? 'primary' : 'light'"
131
+ :count="1"
132
+ ></BaseCounter>
133
+ </div>
134
+ </BaseTabItem>
135
+ <BaseTabItem id="setup">
136
+ Setup
137
+ </BaseTabItem>
138
+ <BaseTabItem id="settings">
139
+ Settings
140
+ </BaseTabItem>
141
+ <BaseTabItem id="articles">
142
+ Articles
143
+ </BaseTabItem>
144
+ <BaseTabItem id="misc">
145
+ Miscellaneous
146
+ </BaseTabItem>
147
+ <BaseTabItem id="users">
148
+ Users
149
+ </BaseTabItem>
150
+ </BaseTabs>
151
+
152
+ <div class="mt-10">
153
+ <BaseCard>
154
+ <BaseCardRow>
155
+ id: {{ modelValue }}
156
+ </BaseCardRow>
157
+ </BaseCard>
158
+ </div>
159
+ </BaseContainer>
160
+ </div>
161
+ `,
162
+ });
163
+
164
+ export const Static = TemplateStatic.bind({});
165
+ Static.args = {
166
+ };
@@ -29,23 +29,55 @@ import { debounce } from 'lodash';
29
29
 
30
30
  const props = withDefaults(
31
31
  defineProps<{
32
+ modelValue?: string | number | null | undefined;
32
33
  size?: 'xs' | 'sm' | 'md' | 'lg';
33
34
  }>(),
34
35
  {
36
+ modelValue: null,
35
37
  size: 'md',
36
38
  }
37
39
  );
38
40
 
39
41
  const route = useRoute();
42
+
40
43
  const scrollable = ref<HTMLElement | null>(null);
41
44
 
45
+ const emit = defineEmits(['update:modelValue']);
46
+
42
47
  watch(
43
48
  () => route.fullPath,
44
49
  () => {
45
50
  nextTick(() => {
46
- scrollToCenter();
47
- animateLine();
51
+ updateTab();
52
+ });
53
+ },
54
+ { immediate: true }
55
+ );
56
+
57
+ watch(
58
+ () => props.modelValue,
59
+ () => {
60
+ nextTick(() => {
61
+ updateTab();
48
62
  });
63
+ },
64
+ { immediate: true }
65
+ );
66
+
67
+ function updateTab() {
68
+ scrollToCenter();
69
+ animateLine();
70
+ }
71
+
72
+ provide(
73
+ 'tabs:currentTabId',
74
+ computed(() => props.modelValue)
75
+ );
76
+
77
+ provide(
78
+ 'tabs:updateTabId',
79
+ (tabId: string) => {
80
+ emit('update:modelValue', tabId);
49
81
  }
50
82
  );
51
83