sprintify-ui 0.8.9 → 0.8.11

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.
@@ -49,7 +49,9 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<{
49
49
  logoUrl: string;
50
50
  logoTo: RouteLocationRaw;
51
51
  }, {}>, {
52
- menu?(_: {}): any;
52
+ menu?(_: {
53
+ collapsed: boolean;
54
+ }): any;
53
55
  navbar?(_: {}): any;
54
56
  default?(_: {}): any;
55
57
  }>;
@@ -13,6 +13,10 @@ declare const _default: import("vue").DefineComponent<{
13
13
  default: string;
14
14
  type: PropType<"xs" | "sm" | "md">;
15
15
  };
16
+ collapsed: {
17
+ default: boolean;
18
+ type: BooleanConstructor;
19
+ };
16
20
  }, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
17
21
  click: (...args: any[]) => void;
18
22
  }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
@@ -28,10 +32,15 @@ declare const _default: import("vue").DefineComponent<{
28
32
  default: string;
29
33
  type: PropType<"xs" | "sm" | "md">;
30
34
  };
35
+ collapsed: {
36
+ default: boolean;
37
+ type: BooleanConstructor;
38
+ };
31
39
  }>> & {
32
40
  onClick?: ((...args: any[]) => any) | undefined;
33
41
  }, {
34
42
  size: "xs" | "sm" | "md";
35
43
  dark: boolean;
44
+ collapsed: boolean;
36
45
  }, {}>;
37
46
  export default _default;
@@ -24,6 +24,10 @@ declare const _default: import("vue").DefineComponent<{
24
24
  default: string;
25
25
  type: PropType<"xs" | "sm" | "md">;
26
26
  };
27
+ collapsed: {
28
+ default: boolean;
29
+ type: BooleanConstructor;
30
+ };
27
31
  }, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
28
32
  label: {
29
33
  default: undefined;
@@ -49,6 +53,10 @@ declare const _default: import("vue").DefineComponent<{
49
53
  default: string;
50
54
  type: PropType<"xs" | "sm" | "md">;
51
55
  };
56
+ collapsed: {
57
+ default: boolean;
58
+ type: BooleanConstructor;
59
+ };
52
60
  }>>, {
53
61
  size: "xs" | "sm" | "md";
54
62
  icon: string;
@@ -56,5 +64,6 @@ declare const _default: import("vue").DefineComponent<{
56
64
  dark: boolean;
57
65
  label: string;
58
66
  active: boolean;
67
+ collapsed: boolean;
59
68
  }, {}>;
60
69
  export default _default;
@@ -1,23 +1,34 @@
1
+ import { UseFloatingOptions } from '@floating-ui/vue';
1
2
  declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<__VLS_WithDefaults<__VLS_TypePropsToOption<{
2
3
  visible?: boolean | undefined;
3
4
  text?: string | null | undefined;
4
5
  class?: string | string[] | null | undefined;
6
+ floatingOptions?: UseFloatingOptions | undefined;
7
+ interactive?: boolean | undefined;
5
8
  }>, {
6
9
  visible: boolean;
7
10
  text: null;
8
11
  class: null;
12
+ floatingOptions: undefined;
13
+ interactive: boolean;
9
14
  }>, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<__VLS_WithDefaults<__VLS_TypePropsToOption<{
10
15
  visible?: boolean | undefined;
11
16
  text?: string | null | undefined;
12
17
  class?: string | string[] | null | undefined;
18
+ floatingOptions?: UseFloatingOptions | undefined;
19
+ interactive?: boolean | undefined;
13
20
  }>, {
14
21
  visible: boolean;
15
22
  text: null;
16
23
  class: null;
24
+ floatingOptions: undefined;
25
+ interactive: boolean;
17
26
  }>>>, {
18
27
  class: string | string[] | null;
19
28
  text: string | null;
20
29
  visible: boolean;
30
+ floatingOptions: UseFloatingOptions;
31
+ interactive: boolean;
21
32
  }, {}>, {
22
33
  default?(_: {}): any;
23
34
  tooltip?(_: {}): any;
@@ -3,5 +3,5 @@ type ReturnType = UseFloatingReturn & {
3
3
  showTooltip: Ref<boolean>;
4
4
  };
5
5
  import { Ref } from "vue";
6
- declare function useTooltip(reference: Readonly<Ref<MaybeElement<any>>>, floating: Readonly<Ref<MaybeElement<HTMLElement>>>, options?: UseFloatingOptions | undefined): ReturnType;
6
+ declare function useTooltip(reference: Readonly<Ref<MaybeElement<any>>>, floating: Readonly<Ref<MaybeElement<HTMLElement>>>, interactive?: boolean, options?: UseFloatingOptions | undefined): ReturnType;
7
7
  export { useTooltip };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sprintify-ui",
3
- "version": "0.8.9",
3
+ "version": "0.8.11",
4
4
  "scripts": {
5
5
  "build": "rimraf dist && vue-tsc && vite build",
6
6
  "build-fast": "rimraf dist && vite build",
@@ -70,6 +70,7 @@
70
70
  :alt="appName"
71
71
  >
72
72
  </RouterLink>
73
+
73
74
  <div
74
75
  data-scroll-lock-scrollable
75
76
  class="flex-1 h-0 mt-5 overflow-y-auto"
@@ -90,7 +91,10 @@
90
91
  </Dialog>
91
92
  </TransitionRoot>
92
93
 
93
- <div class="flex flex-col min-h-full || xl:pl-64 || xl:print:pl-0">
94
+ <div
95
+ class="flex flex-col min-h-full || xl:print:pl-0"
96
+ :class="[collapsed ? 'xl:pl-16' : 'xl:pl-64']"
97
+ >
94
98
  <div class="sticky top-0 left-0 z-10 shadow shrink-0 || print:hidden">
95
99
  <BaseSystemAlert
96
100
  v-for="systemAlert in systemAlerts"
@@ -135,29 +139,56 @@
135
139
  </div>
136
140
 
137
141
  <!-- Static sidebar for desktop -->
138
- <div class="z-10 hidden || xl:fixed xl:inset-y-0 xl:flex xl:w-64 xl:flex-col || xl:print:hidden">
142
+ <div
143
+ class="z-10 hidden || xl:fixed xl:inset-y-0 xl:flex xl:flex-col || xl:print:hidden"
144
+ :class="[collapsed ? 'xl:w-16' : 'xl:w-64']"
145
+ >
139
146
  <!-- Sidebar component, swap this element with another sidebar if you like -->
140
147
  <div
141
148
  data-scroll-lock-scrollable
142
149
  class="flex flex-col flex-1 min-h-0 overflow-y-auto"
143
150
  :class="[dark ? 'bg-slate-800' : 'bg-slate-50 ring-1 ring-black ring-opacity-10']"
144
151
  >
145
- <RouterLink
146
- :to="logoTo"
147
- class="flex items-center flex-shrink-0 px-4"
148
- :style="{ height: navbarHeight + 'px' }"
152
+ <div
153
+ class="flex"
149
154
  :class="[dark ? 'bg-slate-900' : 'border-b']"
150
- @click="closeMenu"
155
+ :style="{ height: navbarHeight + 'px' }"
151
156
  >
152
- <img
153
- class="block w-auto h-8"
154
- :src="logoUrl"
155
- :alt="appName"
157
+ <RouterLink
158
+ v-if="!collapsed"
159
+ :to="logoTo"
160
+ class="flex items-center flex-shrink-0 grow px-4"
161
+
162
+ @click="closeMenu"
156
163
  >
157
- </RouterLink>
164
+ <img
165
+ class="block w-auto h-8"
166
+ :src="logoUrl"
167
+ :alt="appName"
168
+ >
169
+ </RouterLink>
170
+ <button
171
+ type="button"
172
+ class="px-3 flex justify-center items-center"
173
+ :class="[
174
+ dark ? 'text-slate-400 hover:text-slate-50' : 'text-slate-500 hover:text-slate-900',
175
+ collapsed ? 'grow' : 'shrink-0'
176
+ ]"
177
+ @click="toggleCollapse"
178
+ >
179
+ <BaseIcon
180
+ icon="mdi:dock-left"
181
+ class="w-6 h-6"
182
+ />
183
+ </button>
184
+ </div>
185
+
158
186
  <div class="flex flex-1">
159
187
  <nav class="flex-1">
160
- <slot name="menu" />
188
+ <slot
189
+ name="menu"
190
+ :collapsed="collapsed"
191
+ />
161
192
  </nav>
162
193
  </div>
163
194
  </div>
@@ -230,6 +261,16 @@ function closeMenu() {
230
261
  showMobileMenu.value = false;
231
262
  }
232
263
 
264
+ /**
265
+ * Collapse
266
+ */
267
+
268
+ const collapsed = ref(false);
269
+
270
+ function toggleCollapse() {
271
+ collapsed.value = !collapsed.value;
272
+ }
273
+
233
274
  const navbarHeight = computed<number>(() => {
234
275
  if (props.size === 'xs') {
235
276
  return 50;
@@ -6,10 +6,13 @@
6
6
  :dark="dark"
7
7
  :size="size"
8
8
  >
9
- <template #menu>
10
- <div class="px-3 py-6">
11
- <div class="space-y-8">
12
- <div
9
+ <template #menu="{ collapsed }">
10
+ <div
11
+ class="py-6"
12
+ :class="[collapsed ? '' : 'px-3']"
13
+ >
14
+ <div :class="[collapsed ? 'space-y-2' : 'space-y-8']">
15
+ <template
13
16
  v-for="section in menu"
14
17
  :key="section.label"
15
18
  >
@@ -18,19 +21,27 @@
18
21
  :key="section.label"
19
22
  >
20
23
  <h2
24
+ v-if="!collapsed"
21
25
  class="pl-3"
22
26
  :class="sectionLabelClasses"
23
27
  >
24
28
  {{ section.label }}
25
29
  </h2>
26
30
 
27
- <div :class="[size == 'md' ? 'space-y-1' : 'space-y-0.5']">
31
+ <div
32
+ :class="[
33
+ collapsed ? 'space-y-2' : '',
34
+ !collapsed && size == 'md' ? 'space-y-1' : '',
35
+ !collapsed && size != 'md' ? 'space-y-0.5' : '',
36
+ ]"
37
+ >
28
38
  <BaseNavbarSideItem
29
39
  v-for="item in section.actions"
30
40
  :key="item.label"
31
41
  :item="item"
32
42
  :dark="dark"
33
43
  :size="size"
44
+ :collapsed="collapsed"
34
45
  />
35
46
  </div>
36
47
  </div>
@@ -40,8 +51,9 @@
40
51
  :item="section"
41
52
  :dark="dark"
42
53
  :size="size"
54
+ :collapsed="collapsed"
43
55
  />
44
- </div>
56
+ </template>
45
57
  </div>
46
58
  </div>
47
59
  </template>
@@ -14,6 +14,11 @@
14
14
  v-if="logoUrl"
15
15
  :to="logoTo"
16
16
  class="flex flex-shrink-0 items-center p-2 pl-0"
17
+ :class="{
18
+ 'mr-4': size == 'xs',
19
+ 'mr-6': size == 'sm',
20
+ 'mr-8': size == 'md',
21
+ }"
17
22
  >
18
23
  <img
19
24
  class="block h-8 w-auto"
@@ -27,9 +32,8 @@
27
32
  v-if="!mobile"
28
33
  class="flex"
29
34
  :class="{
30
- 'ml-4': size == 'xs',
31
- 'ml-6 space-x-0.5': size == 'sm',
32
- 'ml-8 space-x-1': size == 'md',
35
+ 'space-x-0.5': size == 'sm',
36
+ 'space-x-1': size == 'md',
33
37
  }"
34
38
  >
35
39
  <BaseNavbarItem
@@ -1,25 +1,76 @@
1
1
  <template>
2
2
  <div>
3
- <BaseActionItem
4
- v-if="!item.meta?.line"
5
- :item="item"
6
- class="flex w-full"
7
- @click="onClick"
3
+ <BaseTooltip
4
+ :floating-options="{
5
+ placement: 'right-start',
6
+ }"
7
+ :visible="collapsed"
8
+ :interactive="hasItems"
8
9
  >
9
- <template #default="{ active }">
10
- <BaseNavbarSideItemContent
11
- :label="item.label"
12
- :icon="item.icon"
13
- :active="active"
14
- :count="item.count"
15
- :dark="dark"
16
- :size="size"
17
- />
10
+ <template #default>
11
+ <BaseActionItem
12
+ v-if="!item.meta?.line"
13
+ :item="item"
14
+ class="flex w-full"
15
+ @click="onClick"
16
+ >
17
+ <template #default="{ active }">
18
+ <BaseNavbarSideItemContent
19
+ :label="item.label"
20
+ :icon="item.icon"
21
+ :active="active"
22
+ :count="item.count"
23
+ :dark="dark"
24
+ :size="size"
25
+ :collapsed="collapsed"
26
+ />
27
+ </template>
28
+ </BaseActionItem>
29
+ </template>
30
+
31
+ <template
32
+ v-if="collapsed"
33
+ #tooltip
34
+ >
35
+ <div
36
+ v-if="hasItems"
37
+ class="bg-white px-4 py-3 shadow-lg ring-1 ring-black ring-opacity-10 rounded-md"
38
+ >
39
+ <div
40
+ v-for="subItem in item.actions"
41
+ :key="subItem.label"
42
+ >
43
+ <BaseActionItem
44
+ :item="subItem"
45
+ class="flex gap-1 items-center justify-start"
46
+ >
47
+ <template #default="{ active }">
48
+ <div
49
+ class="text-sm text-slate-600 hover:text-slate-950"
50
+ :class="{ 'font-medium': active }"
51
+ >
52
+ {{ subItem.label }}
53
+ </div>
54
+ <BaseCounter
55
+ v-if="subItem.count"
56
+ :count="subItem.count"
57
+ size="xs"
58
+ />
59
+ </template>
60
+ </BaseActionItem>
61
+ </div>
62
+ </div>
63
+ <div
64
+ v-else
65
+ class="bg-slate-900 text-white rounded shadow-lg px-2 py-1.5 text-xs"
66
+ >
67
+ <span>{{ item.label }}</span>
68
+ </div>
18
69
  </template>
19
- </BaseActionItem>
70
+ </BaseTooltip>
20
71
 
21
72
  <div
22
- v-if="showSubActions && item.actions && item.actions.length"
73
+ v-if="!collapsed && hasItems && showSubActions"
23
74
  class="mt-1 sm:mt-2 mb-3"
24
75
  >
25
76
  <div
@@ -56,6 +107,8 @@ import { ActionItem } from '@/types';
56
107
  import BaseActionItem from './BaseActionItem.vue';
57
108
  import BaseNavbarSideItemContent from './BaseNavbarSideItemContent.vue';
58
109
  import BaseCounter from './BaseCounter.vue';
110
+ import BaseTooltip from './BaseTooltip.vue';
111
+ import { flip, shift } from '@floating-ui/vue';
59
112
 
60
113
  const router = useRouter();
61
114
 
@@ -72,6 +125,10 @@ const props = defineProps({
72
125
  default: 'md',
73
126
  type: String as PropType<'xs' | 'sm' | 'md'>,
74
127
  },
128
+ collapsed: {
129
+ default: false,
130
+ type: Boolean,
131
+ },
75
132
  });
76
133
 
77
134
  const emit = defineEmits(['click']);
@@ -80,6 +137,10 @@ async function onClick() {
80
137
  emit('click');
81
138
  }
82
139
 
140
+ const hasItems = computed((): boolean => {
141
+ return !!props.item.actions && props.item.actions.length > 0;
142
+ });
143
+
83
144
  const routeActive = computed((): boolean => {
84
145
  if (!props.item.to) {
85
146
  return false;
@@ -1,15 +1,18 @@
1
1
  <template>
2
2
  <div :class="classes">
3
- <div class="group flex grow items-center">
3
+ <div
4
+ class="group flex grow items-center"
5
+ :class="[collapsed ? 'justify-center' : 'justify-start']"
6
+ >
4
7
  <BaseIcon
5
8
  v-if="icon"
6
9
  :icon="icon"
7
10
  :class="iconClasses"
8
11
  />
9
- <span>{{ label }}</span>
12
+ <span v-if="!collapsed">{{ label }}</span>
10
13
  </div>
11
14
  <div
12
- v-if="count"
15
+ v-if="count && !collapsed"
13
16
  class="relative -top-px ml-[5px]"
14
17
  >
15
18
  <BaseCounter
@@ -52,10 +55,18 @@ const props = defineProps({
52
55
  default: 'md',
53
56
  type: String as PropType<'xs' | 'sm' | 'md'>,
54
57
  },
58
+ collapsed: {
59
+ default: false,
60
+ type: Boolean,
61
+ },
55
62
  });
56
63
 
57
64
  const classes = computed(() => {
58
- const classList = ['text-left rounded-md flex w-full'];
65
+ const classList = ['text-left flex w-full'];
66
+
67
+ if (!props.collapsed) {
68
+ classList.push('rounded-md');
69
+ }
59
70
 
60
71
  if (props.active) {
61
72
  if (props.dark) {
@@ -95,15 +106,19 @@ const iconClasses = computed((): string[] => {
95
106
  }
96
107
 
97
108
  if (props.size == 'xs') {
98
- classList.push('w-3.5 h-3.5 mr-2');
109
+ classList.push('w-3.5 h-3.5');
99
110
  }
100
111
 
101
112
  if (props.size == 'sm') {
102
- classList.push('w-4 h-4 mr-2');
113
+ classList.push('w-4 h-4');
103
114
  }
104
115
 
105
116
  if (props.size == 'md') {
106
- classList.push('w-5 h-5 mr-2');
117
+ classList.push('w-5 h-5');
118
+ }
119
+
120
+ if (!props.collapsed) {
121
+ classList.push('mr-2');
107
122
  }
108
123
 
109
124
  return classList;
@@ -11,7 +11,8 @@
11
11
  >
12
12
  <div
13
13
  ref="tooltipRef"
14
- class="fixed top-0 left-0 pointer-events-none z-tooltip"
14
+ class="fixed top-0 left-0 z-tooltip"
15
+ :class="[!interactive ? 'pointer-events-none' : '']"
15
16
  :style="floatingStyles"
16
17
  >
17
18
  <transition
@@ -39,6 +40,7 @@
39
40
  <script lang="ts" setup>
40
41
  import { useTooltip } from '@/composables/tooltip';
41
42
  import { unrefElement } from '@vueuse/core';
43
+ import { UseFloatingOptions } from '@floating-ui/vue';
42
44
 
43
45
  defineOptions({
44
46
  inheritAttrs: false,
@@ -48,10 +50,14 @@ const props = withDefaults(defineProps<{
48
50
  visible?: boolean,
49
51
  text?: string | null | undefined;
50
52
  class?: string[] | string | null | undefined;
53
+ floatingOptions?: UseFloatingOptions,
54
+ interactive?: boolean,
51
55
  }>(), {
52
56
  visible: true,
53
57
  text: null,
54
58
  class: null,
59
+ floatingOptions: undefined,
60
+ interactive: false,
55
61
  });
56
62
 
57
63
  const targetRef = ref<HTMLElement | null>(null);
@@ -60,6 +66,6 @@ const targetInternal = computed(() => unrefElement(targetRef));
60
66
 
61
67
  const tooltipRef = ref<HTMLElement | null>(null)
62
68
 
63
- const { floatingStyles, showTooltip } = useTooltip(targetInternal, tooltipRef);
69
+ const { floatingStyles, showTooltip } = useTooltip(targetInternal, tooltipRef, props.interactive, props.floatingOptions);
64
70
 
65
71
  </script>
@@ -1,5 +1,6 @@
1
1
  import { UseFloatingOptions, MaybeElement, UseFloatingReturn, autoUpdate, flip, offset, shift, useFloating } from "@floating-ui/vue";
2
2
  import { unrefElement, useEventListener, } from "@vueuse/core";
3
+ import { debounce } from "lodash";
3
4
 
4
5
  type ReturnType = UseFloatingReturn & {
5
6
  showTooltip: Ref<boolean>;
@@ -10,6 +11,7 @@ import { Ref } from "vue";
10
11
  function useTooltip(
11
12
  reference: Readonly<Ref<MaybeElement<any>>>,
12
13
  floating: Readonly<Ref<MaybeElement<HTMLElement>>>,
14
+ interactive: boolean = false,
13
15
  options: UseFloatingOptions | undefined = undefined
14
16
  ): ReturnType {
15
17
 
@@ -19,14 +21,42 @@ function useTooltip(
19
21
  return unrefElement(reference);
20
22
  });
21
23
 
24
+ const floatingRef = computed(() => {
25
+ return unrefElement(floating);
26
+ });
27
+
28
+ let nextShow = false;
29
+
22
30
  useEventListener(elementRef, 'mouseenter', () => {
23
- show.value = true;
31
+ nextShow = true;
32
+ renderShow();
24
33
  });
25
34
 
26
35
  useEventListener(elementRef, 'mouseleave', () => {
27
- show.value = false;
36
+ nextShow = false;
37
+ renderShow();
38
+ });
39
+
40
+ useEventListener(floatingRef, 'mouseenter', () => {
41
+ if (!interactive) {
42
+ return;
43
+ }
44
+ nextShow = true;
45
+ renderShow();
46
+ });
47
+
48
+ useEventListener(floatingRef, 'mouseleave', () => {
49
+ if (!interactive) {
50
+ return;
51
+ }
52
+ nextShow = false;
53
+ renderShow();
28
54
  });
29
55
 
56
+ const renderShow = debounce(() => {
57
+ show.value = nextShow;
58
+ }, interactive ? 150 : 0);
59
+
30
60
  const config = {
31
61
  placement: 'top',
32
62
  middleware: [offset(6), flip(), shift()],