pimelon-ui 0.0.19
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/license.md +0 -0
- package/package.json +33 -0
- package/readme.md +0 -0
- package/src/components/Alert.vue +57 -0
- package/src/components/Avatar.vue +61 -0
- package/src/components/Badge.vue +40 -0
- package/src/components/Button.vue +134 -0
- package/src/components/Card.vue +37 -0
- package/src/components/Dialog.vue +195 -0
- package/src/components/Dropdown.vue +113 -0
- package/src/components/ErrorMessage.vue +15 -0
- package/src/components/FeatherIcon.vue +55 -0
- package/src/components/FileUploader.vue +220 -0
- package/src/components/GreenCheckIcon.vue +16 -0
- package/src/components/Input.vue +169 -0
- package/src/components/Link.vue +28 -0
- package/src/components/ListItem.vue +28 -0
- package/src/components/LoadingIndicator.vue +12 -0
- package/src/components/LoadingText.vue +21 -0
- package/src/components/Modal.vue +67 -0
- package/src/components/Popover.vue +192 -0
- package/src/components/Resource.vue +21 -0
- package/src/components/Spinner.vue +27 -0
- package/src/components/SuccessMessage.vue +15 -0
- package/src/components/TextEditor/Menu.vue +32 -0
- package/src/components/TextEditor/TextEditor.vue +193 -0
- package/src/components/TextEditor/commands.js +79 -0
- package/src/components/TextEditor/index.js +1 -0
- package/src/components/Toast.vue +167 -0
- package/src/directives/onOutsideClick.js +28 -0
- package/src/index.js +33 -0
- package/src/style.css +15 -0
- package/src/utils/call.js +98 -0
- package/src/utils/debounce.js +15 -0
- package/src/utils/plugin.js +24 -0
- package/src/utils/resources.js +510 -0
- package/src/utils/socketio.js +9 -0
- package/src/utils/tailwind.config.js +110 -0
- package/src/utils/vite-dev-server.js +14 -0
package/license.md
ADDED
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pimelon-ui",
|
|
3
|
+
"version": "0.0.19",
|
|
4
|
+
"description": "A set of components and utilities for rapid UI development",
|
|
5
|
+
"main": "./src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "npx prettier --check ./src",
|
|
8
|
+
"prettier": "npx prettier -w ./src"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/amonak/pimelon-ui.git"
|
|
16
|
+
},
|
|
17
|
+
"author": "Alphamonak Solutions",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@headlessui/vue": "^1.5.0",
|
|
20
|
+
"@popperjs/core": "^2.11.2",
|
|
21
|
+
"@tailwindcss/forms": "^0.4.0",
|
|
22
|
+
"@tailwindcss/typography": "^0.5.0",
|
|
23
|
+
"@tiptap/extension-image": "^2.0.0-beta.27",
|
|
24
|
+
"@tiptap/extension-placeholder": "^2.0.0-beta.48",
|
|
25
|
+
"@tiptap/starter-kit": "^2.0.0-beta.183",
|
|
26
|
+
"@tiptap/vue-3": "^2.0.0-beta.90",
|
|
27
|
+
"autoprefixer": "^10.4.2",
|
|
28
|
+
"feather-icons": "^4.28.0",
|
|
29
|
+
"postcss": "^8.4.5",
|
|
30
|
+
"socket.io-client": "^2.4.0",
|
|
31
|
+
"tailwindcss": "^3.0.12"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/readme.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="block w-full">
|
|
3
|
+
<div
|
|
4
|
+
class="items-start px-4 md:px-5 py-3.5 text-base rounded-md flex"
|
|
5
|
+
:class="classes"
|
|
6
|
+
>
|
|
7
|
+
<svg
|
|
8
|
+
width="24"
|
|
9
|
+
height="24"
|
|
10
|
+
viewBox="0 0 24 24"
|
|
11
|
+
fill="none"
|
|
12
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
13
|
+
>
|
|
14
|
+
<path
|
|
15
|
+
opacity="0.8"
|
|
16
|
+
fill-rule="evenodd"
|
|
17
|
+
clip-rule="evenodd"
|
|
18
|
+
d="M12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2ZM12 10.5C12.5523 10.5 13 10.9477 13 11.5V17C13 17.5523 12.5523 18 12 18C11.4477 18 11 17.5523 11 17V11.5C11 10.9477 11.4477 10.5 12 10.5ZM13 7.99976C13 7.44747 12.5523 6.99976 12 6.99976C11.4477 6.99976 11 7.44747 11 7.99976V8.1C11 8.65228 11.4477 9.1 12 9.1C12.5523 9.1 13 8.65228 13 8.1V7.99976Z"
|
|
19
|
+
fill="#318AD8"
|
|
20
|
+
/>
|
|
21
|
+
</svg>
|
|
22
|
+
<div class="w-full ml-2">
|
|
23
|
+
<div class="flex flex-col md:items-baseline md:flex-row">
|
|
24
|
+
<h3 class="text-lg font-medium text-gray-900" v-if="title">
|
|
25
|
+
{{ title }}
|
|
26
|
+
</h3>
|
|
27
|
+
<div class="mt-1 md:mt-0 md:ml-2">
|
|
28
|
+
<slot></slot>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="mt-3 md:mt-0 md:ml-auto">
|
|
31
|
+
<slot name="actions"></slot>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script>
|
|
40
|
+
export default {
|
|
41
|
+
name: 'Alert',
|
|
42
|
+
props: {
|
|
43
|
+
title: String,
|
|
44
|
+
type: {
|
|
45
|
+
type: String,
|
|
46
|
+
default: 'warning',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
computed: {
|
|
50
|
+
classes() {
|
|
51
|
+
return {
|
|
52
|
+
warning: 'text-gray-700 bg-blue-50',
|
|
53
|
+
}[this.type]
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="overflow-hidden" :class="styleClasses">
|
|
3
|
+
<img
|
|
4
|
+
v-if="imageURL"
|
|
5
|
+
:src="imageURL"
|
|
6
|
+
class="object-cover"
|
|
7
|
+
:class="styleClasses"
|
|
8
|
+
/>
|
|
9
|
+
<div
|
|
10
|
+
v-else
|
|
11
|
+
class="flex items-center justify-center w-full h-full text-gray-600 uppercase bg-gray-200"
|
|
12
|
+
:class="{ sm: 'text-xs', md: 'text-base', lg: 'text-lg' }[size]"
|
|
13
|
+
>
|
|
14
|
+
{{ label && label[0] }}
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
const validShapes = ['square', 'circle']
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
name: 'Avatar',
|
|
24
|
+
props: {
|
|
25
|
+
imageURL: String,
|
|
26
|
+
label: String,
|
|
27
|
+
size: {
|
|
28
|
+
default: 'md',
|
|
29
|
+
},
|
|
30
|
+
shape: {
|
|
31
|
+
default: 'circle',
|
|
32
|
+
validator(value) {
|
|
33
|
+
const valid = validShapes.includes(value)
|
|
34
|
+
if (!valid) {
|
|
35
|
+
console.warn(
|
|
36
|
+
`shape property for <Avatar /> must be one of `,
|
|
37
|
+
validShapes
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
return valid
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
computed: {
|
|
45
|
+
styleClasses() {
|
|
46
|
+
const sizeClasses = {
|
|
47
|
+
sm: 'w-5 h-5',
|
|
48
|
+
md: 'w-8 h-8',
|
|
49
|
+
lg: 'w-12 h-12',
|
|
50
|
+
}[this.size]
|
|
51
|
+
|
|
52
|
+
const shapeClass = {
|
|
53
|
+
circle: 'rounded-full',
|
|
54
|
+
square: 'rounded-lg',
|
|
55
|
+
}[this.shape]
|
|
56
|
+
|
|
57
|
+
return `${shapeClass} ${sizeClasses}`
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span
|
|
3
|
+
class="inline-block px-3 py-1 text-xs font-medium rounded-md cursor-default"
|
|
4
|
+
:class="classes"
|
|
5
|
+
>
|
|
6
|
+
<slot>{{ status }}</slot>
|
|
7
|
+
</span>
|
|
8
|
+
</template>
|
|
9
|
+
<script>
|
|
10
|
+
export default {
|
|
11
|
+
name: 'Badge',
|
|
12
|
+
props: ['color', 'status'],
|
|
13
|
+
computed: {
|
|
14
|
+
classes() {
|
|
15
|
+
let color = this.color
|
|
16
|
+
if (!color && this.status) {
|
|
17
|
+
color = {
|
|
18
|
+
Pending: 'yellow',
|
|
19
|
+
Running: 'yellow',
|
|
20
|
+
Success: 'green',
|
|
21
|
+
Failure: 'red',
|
|
22
|
+
Active: 'green',
|
|
23
|
+
Broken: 'red',
|
|
24
|
+
Updating: 'blue',
|
|
25
|
+
Rejected: 'red',
|
|
26
|
+
Published: 'green',
|
|
27
|
+
Approved: 'green',
|
|
28
|
+
}[this.status]
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
gray: 'text-gray-700 bg-gray-50',
|
|
32
|
+
green: 'text-green-700 bg-green-50',
|
|
33
|
+
red: 'text-red-700 bg-red-50',
|
|
34
|
+
yellow: 'text-yellow-700 bg-yellow-50',
|
|
35
|
+
blue: 'text-blue-700 bg-blue-50',
|
|
36
|
+
}[color || 'gray']
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
v-bind="$attrs"
|
|
4
|
+
:class="buttonClasses"
|
|
5
|
+
@click="handleClick"
|
|
6
|
+
:disabled="isDisabled"
|
|
7
|
+
>
|
|
8
|
+
<LoadingIndicator
|
|
9
|
+
v-if="loading"
|
|
10
|
+
:class="{
|
|
11
|
+
'text-white': appearance == 'primary',
|
|
12
|
+
'text-gray-600': appearance == 'secondary',
|
|
13
|
+
'text-red-200': appearance == 'danger',
|
|
14
|
+
}"
|
|
15
|
+
/>
|
|
16
|
+
<FeatherIcon
|
|
17
|
+
v-else-if="iconLeft"
|
|
18
|
+
:name="iconLeft"
|
|
19
|
+
class="w-4 h-4 mr-1.5"
|
|
20
|
+
aria-hidden="true"
|
|
21
|
+
/>
|
|
22
|
+
<template v-if="loading && loadingText">{{ loadingText }}</template>
|
|
23
|
+
<template v-else-if="icon">
|
|
24
|
+
<FeatherIcon :name="icon" class="w-4 h-4" :aria-label="label" />
|
|
25
|
+
</template>
|
|
26
|
+
<span :class="icon ? 'sr-only' : ''">
|
|
27
|
+
<slot>
|
|
28
|
+
{{ label }}
|
|
29
|
+
</slot>
|
|
30
|
+
</span>
|
|
31
|
+
<FeatherIcon
|
|
32
|
+
v-if="iconRight"
|
|
33
|
+
:name="iconRight"
|
|
34
|
+
class="w-4 h-4 ml-2"
|
|
35
|
+
aria-hidden="true"
|
|
36
|
+
/>
|
|
37
|
+
</button>
|
|
38
|
+
</template>
|
|
39
|
+
<script>
|
|
40
|
+
import FeatherIcon from './FeatherIcon.vue'
|
|
41
|
+
import LoadingIndicator from './LoadingIndicator.vue'
|
|
42
|
+
|
|
43
|
+
const ValidAppearances = ['primary', 'secondary', 'danger', 'white', 'minimal']
|
|
44
|
+
|
|
45
|
+
export default {
|
|
46
|
+
name: 'Button',
|
|
47
|
+
components: {
|
|
48
|
+
FeatherIcon,
|
|
49
|
+
LoadingIndicator,
|
|
50
|
+
},
|
|
51
|
+
props: {
|
|
52
|
+
label: {
|
|
53
|
+
type: String,
|
|
54
|
+
default: null,
|
|
55
|
+
},
|
|
56
|
+
appearance: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: 'secondary',
|
|
59
|
+
validator: (value) => {
|
|
60
|
+
return ValidAppearances.includes(value)
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
disabled: {
|
|
64
|
+
type: Boolean,
|
|
65
|
+
default: false,
|
|
66
|
+
},
|
|
67
|
+
active: {
|
|
68
|
+
type: Boolean,
|
|
69
|
+
default: false,
|
|
70
|
+
},
|
|
71
|
+
iconLeft: {
|
|
72
|
+
type: String,
|
|
73
|
+
default: null,
|
|
74
|
+
},
|
|
75
|
+
iconRight: {
|
|
76
|
+
type: String,
|
|
77
|
+
default: null,
|
|
78
|
+
},
|
|
79
|
+
icon: {
|
|
80
|
+
type: String,
|
|
81
|
+
default: null,
|
|
82
|
+
},
|
|
83
|
+
loading: {
|
|
84
|
+
type: Boolean,
|
|
85
|
+
default: false,
|
|
86
|
+
},
|
|
87
|
+
loadingText: {
|
|
88
|
+
type: String,
|
|
89
|
+
default: null,
|
|
90
|
+
},
|
|
91
|
+
route: {},
|
|
92
|
+
link: {
|
|
93
|
+
type: String,
|
|
94
|
+
default: null,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
computed: {
|
|
98
|
+
buttonClasses() {
|
|
99
|
+
let appearanceClasses = {
|
|
100
|
+
primary:
|
|
101
|
+
'bg-blue-500 hover:bg-blue-600 text-white focus:ring-2 focus:ring-offset-2 focus:ring-blue-500',
|
|
102
|
+
secondary:
|
|
103
|
+
'bg-gray-100 hover:bg-gray-200 text-gray-900 focus:ring-2 focus:ring-offset-2 focus:ring-gray-500',
|
|
104
|
+
danger:
|
|
105
|
+
'bg-red-500 hover:bg-red-400 text-white focus:ring-2 focus:ring-offset-2 focus:ring-red-500',
|
|
106
|
+
white:
|
|
107
|
+
'bg-white text-gray-900 border hover:bg-gray-50 focus:ring-2 focus:ring-offset-2 focus:ring-gray-400',
|
|
108
|
+
minimal: `active:bg-gray-200 focus:bg-gray-200 text-gray-900 ${
|
|
109
|
+
this.active ? 'bg-gray-200' : 'bg-transparent hover:bg-gray-200'
|
|
110
|
+
}`,
|
|
111
|
+
}
|
|
112
|
+
return [
|
|
113
|
+
'inline-flex items-center justify-center text-base leading-5 rounded-md transition-colors focus:outline-none',
|
|
114
|
+
this.icon ? 'p-1.5' : 'px-3 py-1',
|
|
115
|
+
this.isDisabled
|
|
116
|
+
? 'opacity-50 cursor-not-allowed pointer-events-none'
|
|
117
|
+
: '',
|
|
118
|
+
appearanceClasses[this.appearance],
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
isDisabled() {
|
|
122
|
+
return this.disabled || this.loading
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
methods: {
|
|
126
|
+
handleClick() {
|
|
127
|
+
if (this.route && this.$router) {
|
|
128
|
+
this.route && this.$router.push(this.route)
|
|
129
|
+
}
|
|
130
|
+
this.link ? window.open(this.link, '_blank') : null
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
</script>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col px-6 py-5 bg-white border rounded-lg shadow">
|
|
3
|
+
<div class="flex items-baseline justify-between">
|
|
4
|
+
<div class="flex items-baseline space-x-2">
|
|
5
|
+
<div class="flex items-center space-x-2" v-if="$slots['actions-left']">
|
|
6
|
+
<slot name="actions-left"></slot>
|
|
7
|
+
</div>
|
|
8
|
+
<h2 class="text-xl font-semibold">{{ title }}</h2>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="flex items-center space-x-2" v-if="$slots['actions']">
|
|
11
|
+
<slot name="actions"></slot>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
<p class="text-base text-gray-600 mt-1.5" v-if="subtitle">
|
|
15
|
+
{{ subtitle }}
|
|
16
|
+
</p>
|
|
17
|
+
<div
|
|
18
|
+
v-if="loading"
|
|
19
|
+
class="flex flex-col items-center justify-center flex-auto mt-4 rounded-md"
|
|
20
|
+
>
|
|
21
|
+
<LoadingText />
|
|
22
|
+
</div>
|
|
23
|
+
<div class="flex-auto mt-4 overflow-auto" v-else-if="$slots['default']">
|
|
24
|
+
<slot></slot>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
<script>
|
|
29
|
+
import LoadingText from './LoadingText.vue'
|
|
30
|
+
export default {
|
|
31
|
+
name: 'Card',
|
|
32
|
+
props: ['title', 'subtitle', 'loading'],
|
|
33
|
+
components: {
|
|
34
|
+
LoadingText,
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<TransitionRoot as="template" :show="open">
|
|
3
|
+
<HDialog
|
|
4
|
+
as="div"
|
|
5
|
+
class="fixed inset-0 z-10 overflow-y-auto"
|
|
6
|
+
@close="open = false"
|
|
7
|
+
>
|
|
8
|
+
<div
|
|
9
|
+
class="flex items-end justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0"
|
|
10
|
+
>
|
|
11
|
+
<TransitionChild
|
|
12
|
+
as="template"
|
|
13
|
+
enter="ease-out duration-300"
|
|
14
|
+
enter-from="opacity-0"
|
|
15
|
+
enter-to="opacity-100"
|
|
16
|
+
leave="ease-in duration-200"
|
|
17
|
+
leave-from="opacity-100"
|
|
18
|
+
leave-to="opacity-0"
|
|
19
|
+
>
|
|
20
|
+
<DialogOverlay
|
|
21
|
+
class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75"
|
|
22
|
+
/>
|
|
23
|
+
</TransitionChild>
|
|
24
|
+
|
|
25
|
+
<!-- This element is to trick the browser into centering the modal contents. -->
|
|
26
|
+
<span
|
|
27
|
+
class="hidden sm:inline-block sm:align-middle sm:h-screen"
|
|
28
|
+
aria-hidden="true"
|
|
29
|
+
>
|
|
30
|
+
​
|
|
31
|
+
</span>
|
|
32
|
+
<TransitionChild
|
|
33
|
+
as="template"
|
|
34
|
+
enter="ease-out duration-300"
|
|
35
|
+
enter-from="opacity-0 translate-y-4 sm:-translate-y-12 sm:scale-95"
|
|
36
|
+
enter-to="opacity-100 translate-y-0 sm:scale-100"
|
|
37
|
+
leave="ease-in duration-200"
|
|
38
|
+
leave-from="opacity-100 translate-y-0 sm:scale-100"
|
|
39
|
+
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
40
|
+
>
|
|
41
|
+
<div
|
|
42
|
+
class="inline-block overflow-hidden text-left align-bottom transition-all transform bg-white rounded-lg shadow-xl sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
|
|
43
|
+
>
|
|
44
|
+
<slot name="body">
|
|
45
|
+
<slot name="body-main">
|
|
46
|
+
<div class="px-4 py-5 bg-white sm:p-6">
|
|
47
|
+
<div class="flex flex-col sm:flex-row">
|
|
48
|
+
<div
|
|
49
|
+
v-if="icon"
|
|
50
|
+
class="flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto mb-3 rounded-full sm:mx-0 sm:h-9 sm:w-9 sm:mb-0 sm:mr-4"
|
|
51
|
+
:class="{
|
|
52
|
+
'bg-yellow-100': icon.appearance === 'warning',
|
|
53
|
+
'bg-blue-100': icon.appearance === 'info',
|
|
54
|
+
'bg-red-100': icon.appearance === 'danger',
|
|
55
|
+
'bg-green-100': icon.appearance === 'success',
|
|
56
|
+
}"
|
|
57
|
+
>
|
|
58
|
+
<FeatherIcon
|
|
59
|
+
:name="icon.name"
|
|
60
|
+
class="w-6 h-6 text-red-600 sm:w-5 sm:h-5"
|
|
61
|
+
:class="{
|
|
62
|
+
'text-yellow-600': icon.appearance === 'warning',
|
|
63
|
+
'text-blue-600': icon.appearance === 'info',
|
|
64
|
+
'text-red-600': icon.appearance === 'danger',
|
|
65
|
+
'text-green-600': icon.appearance === 'success',
|
|
66
|
+
}"
|
|
67
|
+
aria-hidden="true"
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="flex-1 text-center sm:text-left">
|
|
71
|
+
<DialogTitle as="header">
|
|
72
|
+
<slot name="body-title">
|
|
73
|
+
<h3
|
|
74
|
+
class="mb-2 text-lg font-medium leading-6 text-gray-900"
|
|
75
|
+
>
|
|
76
|
+
{{ options.title || 'Untitled' }}
|
|
77
|
+
</h3>
|
|
78
|
+
</slot>
|
|
79
|
+
</DialogTitle>
|
|
80
|
+
|
|
81
|
+
<slot name="body-content">
|
|
82
|
+
<p class="text-sm text-gray-600" v-if="options.message">
|
|
83
|
+
{{ options.message }}
|
|
84
|
+
</p>
|
|
85
|
+
</slot>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</slot>
|
|
90
|
+
<div
|
|
91
|
+
class="px-4 py-3 space-y-2 sm:space-x-reverse sm:space-x-3 sm:space-y-0 bg-gray-50 sm:px-6 sm:flex sm:flex-row-reverse"
|
|
92
|
+
v-if="options?.actions || $slots.actions"
|
|
93
|
+
>
|
|
94
|
+
<slot name="actions" v-bind="{ close: () => (open = false) }">
|
|
95
|
+
<Button
|
|
96
|
+
class="w-full sm:w-max"
|
|
97
|
+
v-for="action in options.actions"
|
|
98
|
+
:key="action.label"
|
|
99
|
+
:loading="action.loading"
|
|
100
|
+
v-bind="action"
|
|
101
|
+
@click="handleAction(action)"
|
|
102
|
+
>
|
|
103
|
+
{{ action.label }}
|
|
104
|
+
</Button>
|
|
105
|
+
</slot>
|
|
106
|
+
</div>
|
|
107
|
+
</slot>
|
|
108
|
+
</div>
|
|
109
|
+
</TransitionChild>
|
|
110
|
+
</div>
|
|
111
|
+
</HDialog>
|
|
112
|
+
</TransitionRoot>
|
|
113
|
+
</template>
|
|
114
|
+
|
|
115
|
+
<script>
|
|
116
|
+
import { computed } from 'vue'
|
|
117
|
+
import {
|
|
118
|
+
Dialog as HDialog,
|
|
119
|
+
DialogOverlay,
|
|
120
|
+
DialogTitle,
|
|
121
|
+
TransitionChild,
|
|
122
|
+
TransitionRoot,
|
|
123
|
+
} from '@headlessui/vue'
|
|
124
|
+
import { Button, FeatherIcon } from 'pimelon-ui'
|
|
125
|
+
|
|
126
|
+
export default {
|
|
127
|
+
name: 'Dialog',
|
|
128
|
+
props: {
|
|
129
|
+
modelValue: {
|
|
130
|
+
type: Boolean,
|
|
131
|
+
required: true,
|
|
132
|
+
},
|
|
133
|
+
options: {
|
|
134
|
+
type: Object,
|
|
135
|
+
default() {
|
|
136
|
+
return {}
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
emits: ['update:modelValue', 'close'],
|
|
141
|
+
components: {
|
|
142
|
+
HDialog,
|
|
143
|
+
DialogOverlay,
|
|
144
|
+
DialogTitle,
|
|
145
|
+
TransitionChild,
|
|
146
|
+
TransitionRoot,
|
|
147
|
+
Button,
|
|
148
|
+
FeatherIcon,
|
|
149
|
+
},
|
|
150
|
+
setup(props, { emit }) {
|
|
151
|
+
let open = computed({
|
|
152
|
+
get: () => props.modelValue,
|
|
153
|
+
set: (val) => {
|
|
154
|
+
emit('update:modelValue', val)
|
|
155
|
+
if (!val) {
|
|
156
|
+
emit('close')
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
})
|
|
160
|
+
return {
|
|
161
|
+
open,
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
methods: {
|
|
165
|
+
handleAction(action) {
|
|
166
|
+
let close = () => (this.open = false)
|
|
167
|
+
if (action.handler && typeof action.handler === 'function') {
|
|
168
|
+
action.loading = true
|
|
169
|
+
let result = action.handler({ close })
|
|
170
|
+
if (result && result.then) {
|
|
171
|
+
result.then(() => (action.loading = false))
|
|
172
|
+
} else {
|
|
173
|
+
action.loading = false
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
close()
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
computed: {
|
|
181
|
+
icon() {
|
|
182
|
+
if (!this.options?.icon) return null
|
|
183
|
+
|
|
184
|
+
let icon = this.options.icon
|
|
185
|
+
if (typeof icon === 'string') {
|
|
186
|
+
icon = {
|
|
187
|
+
name: icon,
|
|
188
|
+
type: 'info',
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return icon
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
</script>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Menu as="div" class="relative inline-block text-left" v-slot="{ open }">
|
|
3
|
+
<MenuButton as="div">
|
|
4
|
+
<slot v-if="$slots.default" v-bind="{ open }"></slot>
|
|
5
|
+
<Button v-else v-bind="button" :active="open">
|
|
6
|
+
{{ button ? button?.label || null : 'Options' }}
|
|
7
|
+
</Button>
|
|
8
|
+
</MenuButton>
|
|
9
|
+
|
|
10
|
+
<transition
|
|
11
|
+
enter-active-class="transition duration-100 ease-out"
|
|
12
|
+
enter-from-class="transform scale-95 opacity-0"
|
|
13
|
+
enter-to-class="transform scale-100 opacity-100"
|
|
14
|
+
leave-active-class="transition duration-75 ease-in"
|
|
15
|
+
leave-from-class="transform scale-100 opacity-100"
|
|
16
|
+
leave-to-class="transform scale-95 opacity-0"
|
|
17
|
+
>
|
|
18
|
+
<MenuItems
|
|
19
|
+
class="absolute z-10 mt-2 bg-white divide-y divide-gray-100 rounded-md shadow-lg min-w-40 ring-1 ring-black ring-opacity-5 focus:outline-none"
|
|
20
|
+
:class="
|
|
21
|
+
placement === 'left'
|
|
22
|
+
? 'left-0 origin-top-left'
|
|
23
|
+
: 'right-0 origin-top-right'
|
|
24
|
+
"
|
|
25
|
+
>
|
|
26
|
+
<div v-for="group in groups" :key="group.key" class="px-1 py-1">
|
|
27
|
+
<div
|
|
28
|
+
v-if="group.group && !group.hideLabel"
|
|
29
|
+
class="px-2 py-1 text-xs font-semibold tracking-wider text-gray-500 uppercase"
|
|
30
|
+
>
|
|
31
|
+
{{ group.group }}
|
|
32
|
+
</div>
|
|
33
|
+
<MenuItem
|
|
34
|
+
v-for="item in group.items"
|
|
35
|
+
:key="item.label"
|
|
36
|
+
v-slot="{ active }"
|
|
37
|
+
>
|
|
38
|
+
<button
|
|
39
|
+
:class="[
|
|
40
|
+
active ? 'bg-gray-100' : 'text-gray-900',
|
|
41
|
+
'group flex rounded-md items-center w-full px-2 py-2 text-sm',
|
|
42
|
+
]"
|
|
43
|
+
@click="item.onClick"
|
|
44
|
+
>
|
|
45
|
+
<FeatherIcon
|
|
46
|
+
v-if="item.icon"
|
|
47
|
+
:name="item.icon"
|
|
48
|
+
class="flex-shrink-0 w-4 h-4 mr-2 text-gray-500"
|
|
49
|
+
aria-hidden="true"
|
|
50
|
+
/>
|
|
51
|
+
<span class="whitespace-nowrap">
|
|
52
|
+
{{ item.label }}
|
|
53
|
+
</span>
|
|
54
|
+
</button>
|
|
55
|
+
</MenuItem>
|
|
56
|
+
</div>
|
|
57
|
+
</MenuItems>
|
|
58
|
+
</transition>
|
|
59
|
+
</Menu>
|
|
60
|
+
</template>
|
|
61
|
+
|
|
62
|
+
<script>
|
|
63
|
+
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
|
|
64
|
+
import { FeatherIcon } from 'pimelon-ui'
|
|
65
|
+
|
|
66
|
+
export default {
|
|
67
|
+
name: 'NewDropdown',
|
|
68
|
+
props: ['button', 'options', 'placement'],
|
|
69
|
+
components: {
|
|
70
|
+
Menu,
|
|
71
|
+
MenuButton,
|
|
72
|
+
MenuItems,
|
|
73
|
+
MenuItem,
|
|
74
|
+
FeatherIcon,
|
|
75
|
+
},
|
|
76
|
+
methods: {
|
|
77
|
+
normalizeDropdownItem(option) {
|
|
78
|
+
let onClick = option.handler || null
|
|
79
|
+
if (!onClick && option.route && this.$router) {
|
|
80
|
+
onClick = () => this.$router.push(option.route)
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
label: option.label,
|
|
84
|
+
icon: option.icon,
|
|
85
|
+
group: option.group,
|
|
86
|
+
onClick,
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
filterOptions(options) {
|
|
90
|
+
return (options || [])
|
|
91
|
+
.filter(Boolean)
|
|
92
|
+
.filter((option) => (option.condition ? option.condition() : true))
|
|
93
|
+
.map((option) => this.normalizeDropdownItem(option))
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
computed: {
|
|
97
|
+
groups() {
|
|
98
|
+
let groups = this.options[0]?.group
|
|
99
|
+
? this.options
|
|
100
|
+
: [{ group: '', items: this.options }]
|
|
101
|
+
|
|
102
|
+
return groups.map((group, i) => {
|
|
103
|
+
return {
|
|
104
|
+
key: i,
|
|
105
|
+
group: group.group,
|
|
106
|
+
hideLabel: group.hideLabel || false,
|
|
107
|
+
items: this.filterOptions(group.items),
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
</script>
|