sprintify-ui 0.0.11 → 0.0.12

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 (67) hide show
  1. package/dist/sprintify-ui.es.js +4906 -3570
  2. package/dist/style.css +1 -1
  3. package/dist/types/src/components/BaseCharacterCounter.vue.d.ts +143 -0
  4. package/dist/types/src/components/BaseInput.vue.d.ts +39 -5
  5. package/dist/types/src/components/BaseLoadingCover.vue.d.ts +72 -0
  6. package/dist/types/src/components/BaseModalCenter.vue.d.ts +8 -8
  7. package/dist/types/src/components/BaseModalSide.vue.d.ts +8 -8
  8. package/dist/types/src/components/BasePagination.vue.d.ts +105 -13
  9. package/dist/types/src/components/BasePaginationSimple.vue.d.ts +2 -2
  10. package/dist/types/src/components/BaseSelect.vue.d.ts +130 -26
  11. package/dist/types/src/components/BaseSwitch.vue.d.ts +15 -8
  12. package/dist/types/src/components/BaseTabItem.vue.d.ts +26 -4
  13. package/dist/types/src/components/BaseTextareaAutoresize.vue.d.ts +175 -21
  14. package/dist/types/src/components/index.d.ts +24 -1
  15. package/dist/types/src/index.d.ts +4 -0
  16. package/package.json +1 -1
  17. package/src/components/BaseCharacterCounter.stories.js +30 -0
  18. package/src/components/BaseCharacterCounter.vue +60 -0
  19. package/src/components/BaseDataIterator.stories.js +2 -2
  20. package/src/components/BaseDataIterator.vue +32 -38
  21. package/src/components/BaseDataTable.stories.js +2 -2
  22. package/src/components/BaseFileUploader.vue +4 -0
  23. package/src/components/BaseInput.stories.js +46 -0
  24. package/src/components/BaseInput.vue +10 -2
  25. package/src/components/BaseInputLabel.stories.js +31 -0
  26. package/src/components/BaseInputLabel.vue +1 -1
  27. package/src/components/BaseLoadingCover.stories.js +55 -0
  28. package/src/components/BaseLoadingCover.vue +19 -1
  29. package/src/components/BaseMenu.stories.js +125 -0
  30. package/src/components/BaseModalCenter.stories.js +61 -0
  31. package/src/components/BaseModalCenter.vue +2 -2
  32. package/src/components/BaseModalSide.stories.js +55 -0
  33. package/src/components/BaseModalSide.vue +2 -2
  34. package/src/components/BaseNavbar.stories.js +150 -0
  35. package/src/components/BaseNavbar.vue +3 -0
  36. package/src/components/BaseNavbarItem.vue +1 -0
  37. package/src/components/BaseNavbarItemContent.vue +3 -0
  38. package/src/components/BasePagination.stories.js +32 -0
  39. package/src/components/BasePagination.vue +126 -40
  40. package/src/components/BasePaginationSimple.vue +3 -3
  41. package/src/components/BasePanel.stories.js +56 -0
  42. package/src/components/BasePassword.stories.js +36 -0
  43. package/src/components/BasePassword.vue +11 -5
  44. package/src/components/BaseProcessRing.stories.js +27 -0
  45. package/src/components/BaseReadMore.stories.js +30 -0
  46. package/src/components/BaseReadMore.vue +1 -1
  47. package/src/components/BaseSelect.stories.js +67 -0
  48. package/src/components/BaseSelect.vue +144 -44
  49. package/src/components/BaseSideNavigation.stories.js +55 -0
  50. package/src/components/BaseSideNavigation.vue +7 -2
  51. package/src/components/BaseSideNavigationItem.vue +11 -3
  52. package/src/components/BaseSkeleton.stories.js +36 -0
  53. package/src/components/BaseSwitch.stories.js +101 -0
  54. package/src/components/BaseSwitch.vue +90 -12
  55. package/src/components/BaseSystemAlert.stories.js +63 -0
  56. package/src/components/BaseTabItem.vue +19 -6
  57. package/src/components/BaseTabs.stories.js +54 -0
  58. package/src/components/BaseTabs.vue +3 -3
  59. package/src/components/BaseTextarea.stories.js +35 -0
  60. package/src/components/BaseTextarea.vue +1 -1
  61. package/src/components/BaseTextareaAutoresize.stories.js +49 -0
  62. package/src/components/BaseTextareaAutoresize.vue +83 -87
  63. package/src/components/index.ts +46 -0
  64. package/src/lang/en.json +1 -0
  65. package/src/lang/fr.json +1 -0
  66. package/dist/types/src/components/BaseWordCount.vue.d.ts +0 -31
  67. package/src/components/BaseWordCount.vue +0 -36
@@ -0,0 +1,125 @@
1
+ import BaseMenu from './BaseMenu.vue';
2
+ import { Icon as BaseIcon } from '@iconify/vue';
3
+ import BaseAvatar from './BaseAvatar.vue';
4
+
5
+ export default {
6
+ title: 'Components/BaseMenu',
7
+ component: BaseMenu,
8
+ args: {
9
+ position: 'bottom-right',
10
+ },
11
+ argTypes: {
12
+ position: {
13
+ control: { type: 'select' },
14
+ options: ['bottom-left', 'bottom-right'],
15
+ },
16
+ },
17
+ };
18
+
19
+ const Template = (args) => ({
20
+ components: {
21
+ BaseMenu,
22
+ BaseIcon,
23
+ BaseAvatar,
24
+ },
25
+ setup() {
26
+ const items = [
27
+ {
28
+ label: 'Export file',
29
+ icon: 'mdi-export',
30
+ action() {
31
+ alert('Export!');
32
+ },
33
+ },
34
+ {
35
+ label: 'Google',
36
+ icon: 'mdi-google',
37
+ href: 'https://google.com',
38
+ count: 1000,
39
+ },
40
+ {
41
+ icon: 'mdi-access-point',
42
+ label: 'Reconnect',
43
+ to: 'home',
44
+ color: 'success',
45
+ },
46
+ {
47
+ icon: 'mdi-archive',
48
+ label: 'Archive',
49
+ href: 'https://google.com',
50
+ color: 'warning',
51
+ },
52
+ {
53
+ icon: 'mdi-trash-can',
54
+ label: 'Delete',
55
+ href: 'https://google.com',
56
+ color: 'danger',
57
+ count: 1,
58
+ },
59
+ ];
60
+
61
+ const user = {
62
+ email: 'jane@witify.io',
63
+ first_name: 'Jane',
64
+ last_name: 'Doe',
65
+ full_name: 'Jane Doe',
66
+ avatar_url:
67
+ 'https://images.unsplash.com/photo-1494790108377-be9c29b29330??auto=format&fit=crop&w=200&h=200&q=80&g=face',
68
+ };
69
+
70
+ args.items = items;
71
+
72
+ return { args, user };
73
+ },
74
+ template: `
75
+ <div class="pb-52">
76
+
77
+ <h2 class="mb-5 font-semibold">Various examples</h2>
78
+
79
+ <p class="text-sm mb-1 text-slate-600">Simple button</p>
80
+
81
+ <BaseMenu v-bind="args" class="inline-block">
82
+ <template #button="{ open }">
83
+ <div
84
+ class="btn"
85
+ :class="[open ? 'ring-2 ring-primary-500 ring-offset-2': '']"
86
+ >
87
+ Click me
88
+ </div>
89
+ </template>
90
+ </BaseMenu>
91
+
92
+ <br>
93
+ <br>
94
+
95
+ <p class="text-sm mb-1 text-slate-600">Contextual action button</p>
96
+
97
+ <BaseMenu v-bind="args" class="inline-block">
98
+ <template #button="{ open }">
99
+ <div
100
+ class="flex h-10 w-10 items-center justify-center rounded-full border border-slate-300 bg-white duration-150 hover:bg-slate-50"
101
+ :class="[open ? 'ring-2 ring-primary-500 ring-offset-2': '']"
102
+ >
103
+ <BaseIcon icon="heroicons-solid:dots-vertical" />
104
+ </div>
105
+ </template>
106
+ </BaseMenu>
107
+
108
+ <br>
109
+ <br>
110
+
111
+ <p class="text-sm mb-1 text-slate-600">With BaseAvatar</p>
112
+
113
+ <BaseMenu v-bind="args" class="inline-block">
114
+ <template #button="{ open }">
115
+ <div class="bg-white">
116
+ <BaseAvatar show-details :user="user" />
117
+ </div>
118
+ </template>
119
+ </BaseMenu>
120
+ </div>
121
+ `,
122
+ });
123
+
124
+ export const Demo = Template.bind({});
125
+ Demo.args = {};
@@ -0,0 +1,61 @@
1
+ import BaseModalCenter from './BaseModalCenter.vue';
2
+ import { Icon as BaseIcon } from '@iconify/vue';
3
+ import BaseAvatar from './BaseAvatar.vue';
4
+
5
+ export default {
6
+ title: 'Components/BaseModalCenter',
7
+ component: BaseModalCenter,
8
+ args: {
9
+ position: 'bottom-right',
10
+ },
11
+ argTypes: {
12
+ verticalAlign: {
13
+ control: { type: 'select' },
14
+ options: ['center', 'top'],
15
+ },
16
+ },
17
+ };
18
+
19
+ const Template = (args) => ({
20
+ components: {
21
+ BaseModalCenter,
22
+ BaseIcon,
23
+ BaseAvatar,
24
+ },
25
+ setup() {
26
+ const show = ref(false);
27
+ return { args, show };
28
+ },
29
+ template: `
30
+ <div class="">
31
+ <button @click="show = true" class="btn">Show modal</button>
32
+ <BaseModalCenter v-model="show" v-bind="args">
33
+ <template #default="{close}">
34
+ <div class="p-8">
35
+ <p class="mb-6">Hello!</p>
36
+
37
+ <button @click="close" class="btn btn-sm">Close</button>
38
+ </div>
39
+ </template>
40
+ </BaseModalCenter>
41
+ </div>
42
+ `,
43
+ });
44
+
45
+ export const Demo = Template.bind({});
46
+ Demo.args = {};
47
+
48
+ export const VerticalAlignTop = Template.bind({});
49
+ VerticalAlignTop.args = {
50
+ verticalAlign: 'top',
51
+ };
52
+
53
+ export const CustomBackdropClass = Template.bind({});
54
+ CustomBackdropClass.args = {
55
+ backdropClass: 'bg-red-500 bg-opacity-70',
56
+ };
57
+
58
+ export const PreventLeave = Template.bind({});
59
+ PreventLeave.args = {
60
+ closeOnOutsideClick: false,
61
+ };
@@ -30,7 +30,7 @@
30
30
  >
31
31
  <div
32
32
  v-if="modelValue"
33
- :class="modalOverlayClasses"
33
+ :class="backdropClass"
34
34
  class="fixed inset-0 transition-opacity"
35
35
  @click="
36
36
  closeOnOutsideClick
@@ -85,7 +85,7 @@ const props = defineProps({
85
85
  default: 'center',
86
86
  type: String,
87
87
  },
88
- modalOverlayClasses: {
88
+ backdropClass: {
89
89
  default: 'bg-opacity-70 bg-slate-900',
90
90
  type: String,
91
91
  },
@@ -0,0 +1,55 @@
1
+ import BaseModalSide from './BaseModalSide.vue';
2
+ import { Icon as BaseIcon } from '@iconify/vue';
3
+ import BaseAvatar from './BaseAvatar.vue';
4
+
5
+ export default {
6
+ title: 'Components/BaseModalSide',
7
+ component: BaseModalSide,
8
+ args: {
9
+ position: 'bottom-right',
10
+ },
11
+ };
12
+
13
+ const Template = (args) => ({
14
+ components: {
15
+ BaseModalSide,
16
+ BaseIcon,
17
+ BaseAvatar,
18
+ },
19
+ setup() {
20
+ const show = ref(false);
21
+ return { args, show };
22
+ },
23
+ template: `
24
+ <div class="">
25
+ <button @click="show = true" class="btn">Show modal</button>
26
+ <BaseModalSide v-model="show" v-bind="args">
27
+ <template #default="{close}">
28
+ <div class="p-8">
29
+ <p class="mb-6">Hello!</p>
30
+
31
+ <button @click="close" class="btn btn-sm">Close</button>
32
+ </div>
33
+ </template>
34
+ </BaseModalSide>
35
+ </div>
36
+ `,
37
+ });
38
+
39
+ export const Demo = Template.bind({});
40
+ Demo.args = {};
41
+
42
+ export const CustomMaxWidth = Template.bind({});
43
+ CustomMaxWidth.args = {
44
+ maxWidth: '16rem',
45
+ };
46
+
47
+ export const CustomBackdropClass = Template.bind({});
48
+ CustomBackdropClass.args = {
49
+ backdropClass: 'bg-red-500 bg-opacity-70',
50
+ };
51
+
52
+ export const PreventLeave = Template.bind({});
53
+ PreventLeave.args = {
54
+ closeOnOutsideClick: false,
55
+ };
@@ -24,7 +24,7 @@
24
24
  >
25
25
  <div
26
26
  v-show="modelValue"
27
- :class="modalOverlayClasses"
27
+ :class="backdropClass"
28
28
  class="fixed inset-0 transition-opacity"
29
29
  @click="
30
30
  closeOnOutsideClick
@@ -85,7 +85,7 @@ const props = defineProps({
85
85
  default: '32rem',
86
86
  type: String,
87
87
  },
88
- modalOverlayClasses: {
88
+ backdropClass: {
89
89
  default: 'bg-opacity-70 bg-slate-900',
90
90
  type: String,
91
91
  },
@@ -0,0 +1,150 @@
1
+ import BaseNavbar from './BaseNavbar.vue';
2
+ import BaseNavbarItem from './BaseNavbarItem.vue';
3
+ import BaseAvatar from './BaseAvatar.vue';
4
+ import BaseMenu from './BaseMenu.vue';
5
+ import { Icon as BaseIcon } from '@iconify/vue';
6
+
7
+ export default {
8
+ title: 'Layout/BaseNavbar',
9
+ component: BaseNavbar,
10
+ args: {},
11
+ };
12
+
13
+ const Template = (args) => ({
14
+ components: {
15
+ BaseNavbar,
16
+ BaseNavbarItem,
17
+ BaseIcon,
18
+ BaseAvatar,
19
+ BaseMenu,
20
+ },
21
+ setup() {
22
+ const user = {
23
+ email: 'jane@witify.io',
24
+ first_name: 'Jane',
25
+ last_name: 'Doe',
26
+ full_name: 'Jane Doe',
27
+ avatar_url:
28
+ 'https://images.unsplash.com/photo-1494790108377-be9c29b29330??auto=format&fit=crop&w=200&h=200&q=80&g=face',
29
+ };
30
+
31
+ const menu = [
32
+ {
33
+ label: 'Home',
34
+ to: '/',
35
+ type: 'RouterLink',
36
+ },
37
+ {
38
+ label: 'Products',
39
+ to: '/',
40
+ count: 234,
41
+ type: 'RouterLink',
42
+ },
43
+ {
44
+ label: 'Settings',
45
+ to: '/',
46
+ type: 'RouterLink',
47
+ },
48
+ ];
49
+
50
+ const userMenu = [
51
+ {
52
+ label: 'Home',
53
+ icon: 'heroicons:home',
54
+ href: 'https://google.com',
55
+ },
56
+ {
57
+ line: true,
58
+ },
59
+ {
60
+ label: 'Logout',
61
+ icon: 'heroicons:arrow-right-on-rectangle',
62
+ href: 'https://google.com',
63
+ },
64
+ ];
65
+
66
+ return { args, menu, userMenu, user };
67
+ },
68
+ template: `
69
+ <div class="mb-40">
70
+ <BaseNavbar v-bind="args">
71
+ <template #navbar>
72
+ <div class="flex h-16 justify-between">
73
+ <!-- Left -->
74
+
75
+ <div class="flex items-center justify-center">
76
+ <!-- Logo -->
77
+ <router-link to="/" class="flex flex-shrink-0 grow items-center p-2 pl-0">
78
+ <img
79
+ class="block h-8 w-auto"
80
+ src="https://sprintify.witify.io/img/logo/logo-side-dark.svg"
81
+ alt="Sprintify"
82
+ />
83
+ </router-link>
84
+
85
+ <!-- Links (desktop) -->
86
+ <div class="ml-10 hidden items-center space-x-4 md:flex">
87
+ <BaseNavbarItem
88
+ v-for="item in menu"
89
+ :key="item.label"
90
+ :item="item"
91
+ />
92
+ </div>
93
+ </div>
94
+
95
+ <!-- Right -->
96
+
97
+ <div class="hidden md:ml-6 md:flex md:items-center">
98
+ <!-- Profile dropdown -->
99
+ <BaseMenu menu-class="w-52" :items="userMenu">
100
+ <template #button="{ open }">
101
+ <div
102
+ class="flex rounded-full"
103
+ :class="[open ? 'bg-slate-700 ring-2 ring-blue-500 ring-offset-2 ring-offset-slate-700' : '']"
104
+ >
105
+ <BaseAvatar class="text-white" :user="user" />
106
+ </div>
107
+ </template>
108
+ </BaseMenu>
109
+ </div>
110
+ </div>
111
+ </template>
112
+
113
+ <template #mobile>
114
+ <!-- Links mobile -->
115
+ <div class="space-y-1 p-2">
116
+ <BaseNavbarItem
117
+ v-for="item in menu"
118
+ :key="item.label"
119
+ :item="item"
120
+ class="flex w-full"
121
+ />
122
+ </div>
123
+
124
+ <hr class="my-4 border-slate-700" />
125
+
126
+ <!-- Profile links -->
127
+ <div class="p-2 pb-6">
128
+ <BaseAvatar
129
+ :user="user"
130
+ show-details
131
+ size="base"
132
+ class="px-3 text-white"
133
+ />
134
+ <div class="mt-4 space-y-1">
135
+ <BaseNavbarItem
136
+ v-for="item in userMenu"
137
+ :key="item.label"
138
+ :item="item"
139
+ class="flex w-full"
140
+ />
141
+ </div>
142
+ </div>
143
+ </template>
144
+ </BaseNavbar>
145
+ </div>
146
+ `,
147
+ });
148
+
149
+ export const Demo = Template.bind({});
150
+ Demo.args = {};
@@ -45,6 +45,9 @@
45
45
  </template>
46
46
 
47
47
  <script setup lang="ts">
48
+ import { Icon as BaseIcon } from '@iconify/vue';
49
+ import BaseContainer from './BaseContainer.vue';
50
+
48
51
  defineProps({
49
52
  size: {
50
53
  default: '7xl',
@@ -52,6 +52,7 @@
52
52
  <script setup lang="ts">
53
53
  import { PropType } from 'vue';
54
54
  import { ActionItem } from '@/types/types';
55
+ import BaseNavbarItemContent from './BaseNavbarItemContent.vue';
55
56
 
56
57
  defineProps({
57
58
  item: {
@@ -11,6 +11,9 @@
11
11
  </template>
12
12
 
13
13
  <script lang="ts" setup>
14
+ import { Icon as BaseIcon } from '@iconify/vue';
15
+ import BaseCounter from './BaseCounter.vue';
16
+
14
17
  const buttonClasses =
15
18
  'px-3 py-2 text-left rounded-md md:text-sm flex text-base font-normal w-full';
16
19
  const buttonInactiveClasses =
@@ -0,0 +1,32 @@
1
+ import BasePagination from './BasePagination.vue';
2
+
3
+ export default {
4
+ title: 'Components/BasePagination',
5
+ component: BasePagination,
6
+ args: {
7
+ totalVisible: 12,
8
+ lastPage: 20,
9
+ },
10
+ };
11
+
12
+ const Template = (args) => ({
13
+ components: {
14
+ BasePagination,
15
+ },
16
+ setup() {
17
+ const modelValue = ref(10);
18
+
19
+ return { args, modelValue };
20
+ },
21
+ template: `
22
+ <div>
23
+ <BasePagination v-model="modelValue" v-bind="args"></BasePagination>
24
+ </div>
25
+ <div style="max-width: 500px;">
26
+ <BasePagination v-model="modelValue" v-bind="args"></BasePagination>
27
+ </div>
28
+ `,
29
+ });
30
+
31
+ export const Demo = Template.bind({});
32
+ Demo.args = {};
@@ -1,9 +1,10 @@
1
1
  <template>
2
2
  <nav
3
3
  v-if="lastPage > 1 || lastPage < modelValue"
4
- class="flex items-center justify-between border-t border-slate-200 px-4 sm:px-0"
4
+ ref="paginationNode"
5
+ class="flex items-center justify-between border-t border-slate-200"
5
6
  >
6
- <div class="-mt-px flex w-0 flex-1">
7
+ <div class="flex -mt-px w-0 flex-1">
7
8
  <button
8
9
  type="button"
9
10
  :disabled="modelValue == 1"
@@ -17,23 +18,25 @@
17
18
  {{ $t('sui.previous') }}
18
19
  </button>
19
20
  </div>
20
- <div class="hidden md:-mt-px md:flex">
21
+ <div :class="[mobileLayout ? 'hidden' : 'flex -mt-px']">
21
22
  <button
22
- v-for="i in lastPage"
23
- :key="i"
23
+ v-for="(i, index) in items"
24
+ :key="i + (index + '')"
24
25
  type="button"
25
- class="inline-flex items-center border-t-2 px-4 py-4 text-sm font-medium hover:border-slate-300 hover:text-slate-700"
26
- :class="
27
- i == modelValue
28
- ? 'border-primary-500 text-primary-600'
29
- : 'border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700'
30
- "
31
- @click="$emit('model-value:update', i)"
26
+ class="inline-flex items-center border-t-2 px-4 py-4 text-sm font-medium"
27
+ :class="[
28
+ i == modelValue ? 'border-primary-500 text-primary-500' : '',
29
+ i != modelValue ? 'border-transparent text-slate-500' : '',
30
+ i != modelValue && isClickable(i)
31
+ ? 'hover:border-slate-300 hover:text-slate-700'
32
+ : '',
33
+ ]"
34
+ @click="onButtonClick(i)"
32
35
  >
33
36
  {{ i }}
34
37
  </button>
35
38
  </div>
36
- <div class="-mt-px flex w-0 flex-1 justify-end">
39
+ <div class="flex -mt-px w-0 flex-1 justify-end">
37
40
  <button
38
41
  :disabled="modelValue >= lastPage"
39
42
  class="inline-flex items-center border-t-2 border-transparent px-1 py-4 text-sm font-medium text-slate-500 hover:enabled:border-slate-300 hover:enabled:text-slate-700 disabled:cursor-not-allowed disabled:opacity-60"
@@ -49,34 +52,117 @@
49
52
  </nav>
50
53
  </template>
51
54
 
52
- <script lang="ts">
53
- import { defineComponent } from 'vue';
54
-
55
- export default defineComponent({
56
- props: {
57
- modelValue: {
58
- required: true,
59
- type: Number,
60
- },
61
- lastPage: {
62
- required: true,
63
- type: Number,
64
- },
55
+ <script lang="ts" setup>
56
+ import { useResizeObserver } from '@vueuse/core';
57
+ import { range } from 'lodash';
58
+ import { Ref } from 'vue';
59
+
60
+ const props = defineProps({
61
+ modelValue: {
62
+ default: 1,
63
+ required: true,
64
+ type: Number,
65
+ },
66
+ lastPage: {
67
+ required: true,
68
+ type: Number,
65
69
  },
66
- emits: ['model-value:update'],
67
- methods: {
68
- next() {
69
- if (this.modelValue >= this.lastPage) {
70
- return;
71
- }
72
- this.$emit('model-value:update', this.modelValue + 1);
73
- },
74
- previous() {
75
- if (this.modelValue == 1) {
76
- return;
77
- }
78
- this.$emit('model-value:update', this.modelValue - 1);
79
- },
70
+ totalVisible: {
71
+ default: 10,
72
+ type: Number,
80
73
  },
81
74
  });
75
+
76
+ const emit = defineEmits(['update:model-value']);
77
+
78
+ function next() {
79
+ if (props.modelValue >= props.lastPage) {
80
+ return;
81
+ }
82
+ emit('update:model-value', props.modelValue + 1);
83
+ }
84
+
85
+ function previous() {
86
+ if (props.modelValue == 1) {
87
+ return;
88
+ }
89
+ emit('update:model-value', props.modelValue - 1);
90
+ }
91
+
92
+ const paginationNode = ref(null) as Ref<null | HTMLElement>;
93
+ const width = ref(800);
94
+
95
+ useResizeObserver(paginationNode, () => {
96
+ width.value = paginationNode.value?.clientWidth ?? 800;
97
+ maxButtons.value = Math.floor((width.value - 96) / 56);
98
+ });
99
+
100
+ const mobileLayout = computed(() => {
101
+ return width.value < 600;
102
+ });
103
+
104
+ const length = computed(() => {
105
+ return props.lastPage + 1;
106
+ });
107
+
108
+ const maxButtons = ref(0);
109
+
110
+ const items = computed(() => {
111
+ const totalVisible = props.totalVisible + 2;
112
+
113
+ if (
114
+ totalVisible === 0 ||
115
+ isNaN(length.value) ||
116
+ length.value > Number.MAX_SAFE_INTEGER
117
+ ) {
118
+ return [];
119
+ }
120
+
121
+ const maxLength = Math.min(
122
+ Math.max(0, totalVisible) || length.value,
123
+ Math.max(0, maxButtons.value) || length.value,
124
+ length.value
125
+ );
126
+
127
+ if (length.value <= maxLength) {
128
+ return range(1, length.value);
129
+ }
130
+
131
+ const even = maxLength % 2 === 0 ? 1 : 0;
132
+ const left = Math.floor(maxLength / 2);
133
+ const right = length.value - left + 1 + even;
134
+
135
+ if (props.modelValue > left && props.modelValue < right) {
136
+ const firstItem = 1;
137
+ const lastItem = length.value;
138
+ const start = props.modelValue - left + 2;
139
+ const end = props.modelValue + left - 2 - even;
140
+ const secondItem = start - 1 === firstItem + 1 ? 2 : '...';
141
+ const beforeLastItem = end + 1 === lastItem - 1 ? end + 1 : '...';
142
+
143
+ return [1, secondItem, ...range(start, end), beforeLastItem, length.value];
144
+ } else if (props.modelValue === left) {
145
+ const end = props.modelValue + left - 1 - even;
146
+ return [...range(1, end), '...', length.value];
147
+ } else if (props.modelValue === right) {
148
+ const start = props.modelValue - left + 1;
149
+ return [1, '...', ...range(start, length.value)];
150
+ } else {
151
+ return [...range(1, left), '...', ...range(right, length.value)];
152
+ }
153
+ });
154
+
155
+ function isClickable(i: number | string) {
156
+ if (i == '...') {
157
+ return false;
158
+ }
159
+ return true;
160
+ }
161
+
162
+ function onButtonClick(i: number | string) {
163
+ if (!isClickable(i)) {
164
+ return;
165
+ }
166
+ emit('update:model-value', i);
167
+ }
82
168
  </script>