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.
- package/dist/sprintify-ui.es.js +4906 -3570
- package/dist/style.css +1 -1
- package/dist/types/src/components/BaseCharacterCounter.vue.d.ts +143 -0
- package/dist/types/src/components/BaseInput.vue.d.ts +39 -5
- package/dist/types/src/components/BaseLoadingCover.vue.d.ts +72 -0
- package/dist/types/src/components/BaseModalCenter.vue.d.ts +8 -8
- package/dist/types/src/components/BaseModalSide.vue.d.ts +8 -8
- package/dist/types/src/components/BasePagination.vue.d.ts +105 -13
- package/dist/types/src/components/BasePaginationSimple.vue.d.ts +2 -2
- package/dist/types/src/components/BaseSelect.vue.d.ts +130 -26
- package/dist/types/src/components/BaseSwitch.vue.d.ts +15 -8
- package/dist/types/src/components/BaseTabItem.vue.d.ts +26 -4
- package/dist/types/src/components/BaseTextareaAutoresize.vue.d.ts +175 -21
- package/dist/types/src/components/index.d.ts +24 -1
- package/dist/types/src/index.d.ts +4 -0
- package/package.json +1 -1
- package/src/components/BaseCharacterCounter.stories.js +30 -0
- package/src/components/BaseCharacterCounter.vue +60 -0
- package/src/components/BaseDataIterator.stories.js +2 -2
- package/src/components/BaseDataIterator.vue +32 -38
- package/src/components/BaseDataTable.stories.js +2 -2
- package/src/components/BaseFileUploader.vue +4 -0
- package/src/components/BaseInput.stories.js +46 -0
- package/src/components/BaseInput.vue +10 -2
- package/src/components/BaseInputLabel.stories.js +31 -0
- package/src/components/BaseInputLabel.vue +1 -1
- package/src/components/BaseLoadingCover.stories.js +55 -0
- package/src/components/BaseLoadingCover.vue +19 -1
- package/src/components/BaseMenu.stories.js +125 -0
- package/src/components/BaseModalCenter.stories.js +61 -0
- package/src/components/BaseModalCenter.vue +2 -2
- package/src/components/BaseModalSide.stories.js +55 -0
- package/src/components/BaseModalSide.vue +2 -2
- package/src/components/BaseNavbar.stories.js +150 -0
- package/src/components/BaseNavbar.vue +3 -0
- package/src/components/BaseNavbarItem.vue +1 -0
- package/src/components/BaseNavbarItemContent.vue +3 -0
- package/src/components/BasePagination.stories.js +32 -0
- package/src/components/BasePagination.vue +126 -40
- package/src/components/BasePaginationSimple.vue +3 -3
- package/src/components/BasePanel.stories.js +56 -0
- package/src/components/BasePassword.stories.js +36 -0
- package/src/components/BasePassword.vue +11 -5
- package/src/components/BaseProcessRing.stories.js +27 -0
- package/src/components/BaseReadMore.stories.js +30 -0
- package/src/components/BaseReadMore.vue +1 -1
- package/src/components/BaseSelect.stories.js +67 -0
- package/src/components/BaseSelect.vue +144 -44
- package/src/components/BaseSideNavigation.stories.js +55 -0
- package/src/components/BaseSideNavigation.vue +7 -2
- package/src/components/BaseSideNavigationItem.vue +11 -3
- package/src/components/BaseSkeleton.stories.js +36 -0
- package/src/components/BaseSwitch.stories.js +101 -0
- package/src/components/BaseSwitch.vue +90 -12
- package/src/components/BaseSystemAlert.stories.js +63 -0
- package/src/components/BaseTabItem.vue +19 -6
- package/src/components/BaseTabs.stories.js +54 -0
- package/src/components/BaseTabs.vue +3 -3
- package/src/components/BaseTextarea.stories.js +35 -0
- package/src/components/BaseTextarea.vue +1 -1
- package/src/components/BaseTextareaAutoresize.stories.js +49 -0
- package/src/components/BaseTextareaAutoresize.vue +83 -87
- package/src/components/index.ts +46 -0
- package/src/lang/en.json +1 -0
- package/src/lang/fr.json +1 -0
- package/dist/types/src/components/BaseWordCount.vue.d.ts +0 -31
- 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="
|
|
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
|
-
|
|
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="
|
|
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
|
-
|
|
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 = {};
|
|
@@ -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
|
-
|
|
4
|
+
ref="paginationNode"
|
|
5
|
+
class="flex items-center justify-between border-t border-slate-200"
|
|
5
6
|
>
|
|
6
|
-
<div class="-mt-px
|
|
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
|
|
21
|
+
<div :class="[mobileLayout ? 'hidden' : 'flex -mt-px']">
|
|
21
22
|
<button
|
|
22
|
-
v-for="i in
|
|
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
|
|
26
|
-
:class="
|
|
27
|
-
i == modelValue
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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 {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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>
|