pimelon-ui 0.0.98
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 +75 -0
- package/readme.md +0 -0
- package/src/components/Alert.vue +57 -0
- package/src/components/Autocomplete.vue +176 -0
- package/src/components/Avatar.vue +62 -0
- package/src/components/Badge.vue +42 -0
- package/src/components/Button.vue +155 -0
- package/src/components/Card.vue +37 -0
- package/src/components/DatePicker.vue +252 -0
- package/src/components/Dialog.vue +216 -0
- package/src/components/Dropdown.vue +145 -0
- package/src/components/ErrorMessage.vue +24 -0
- package/src/components/FeatherIcon.vue +59 -0
- package/src/components/FileUploader.vue +220 -0
- package/src/components/GreenCheckIcon.vue +16 -0
- package/src/components/Input.vue +214 -0
- package/src/components/Link.vue +28 -0
- package/src/components/ListItem.vue +28 -0
- package/src/components/LoadingIndicator.vue +27 -0
- package/src/components/LoadingText.vue +21 -0
- package/src/components/Popover.vue +253 -0
- package/src/components/Resource.vue +21 -0
- package/src/components/TextEditor/FontColor.vue +108 -0
- package/src/components/TextEditor/InsertImage.vue +74 -0
- package/src/components/TextEditor/InsertLink.vue +67 -0
- package/src/components/TextEditor/InsertVideo.vue +94 -0
- package/src/components/TextEditor/MentionList.vue +95 -0
- package/src/components/TextEditor/Menu.vue +99 -0
- package/src/components/TextEditor/TextEditor.vue +275 -0
- package/src/components/TextEditor/TextEditorBubbleMenu.vue +69 -0
- package/src/components/TextEditor/TextEditorFixedMenu.vue +72 -0
- package/src/components/TextEditor/TextEditorFloatingMenu.vue +55 -0
- package/src/components/TextEditor/commands.js +272 -0
- package/src/components/TextEditor/icons/align-center.vue +14 -0
- package/src/components/TextEditor/icons/align-justify.vue +14 -0
- package/src/components/TextEditor/icons/align-left.vue +14 -0
- package/src/components/TextEditor/icons/align-right.vue +14 -0
- package/src/components/TextEditor/icons/arrow-go-back-line.vue +14 -0
- package/src/components/TextEditor/icons/arrow-go-forward-line.vue +14 -0
- package/src/components/TextEditor/icons/bold.vue +14 -0
- package/src/components/TextEditor/icons/code-view.vue +14 -0
- package/src/components/TextEditor/icons/double-quotes-r.vue +14 -0
- package/src/components/TextEditor/icons/font-color.vue +14 -0
- package/src/components/TextEditor/icons/format-clear.vue +14 -0
- package/src/components/TextEditor/icons/h-1.vue +14 -0
- package/src/components/TextEditor/icons/h-2.vue +14 -0
- package/src/components/TextEditor/icons/h-3.vue +14 -0
- package/src/components/TextEditor/icons/h-4.vue +14 -0
- package/src/components/TextEditor/icons/h-5.vue +14 -0
- package/src/components/TextEditor/icons/h-6.vue +14 -0
- package/src/components/TextEditor/icons/image-add-line.vue +14 -0
- package/src/components/TextEditor/icons/italic.vue +14 -0
- package/src/components/TextEditor/icons/link.vue +14 -0
- package/src/components/TextEditor/icons/list-ordered.vue +14 -0
- package/src/components/TextEditor/icons/list-unordered.vue +14 -0
- package/src/components/TextEditor/icons/readme.md +1 -0
- package/src/components/TextEditor/icons/separator.vue +14 -0
- package/src/components/TextEditor/icons/strikethrough.vue +14 -0
- package/src/components/TextEditor/icons/table-2.vue +14 -0
- package/src/components/TextEditor/icons/text.vue +11 -0
- package/src/components/TextEditor/icons/underline.vue +14 -0
- package/src/components/TextEditor/icons/video-add-line.vue +14 -0
- package/src/components/TextEditor/image-extension.js +152 -0
- package/src/components/TextEditor/index.js +6 -0
- package/src/components/TextEditor/mention.js +72 -0
- package/src/components/TextEditor/utils.js +11 -0
- package/src/components/TextEditor/video-extension.js +60 -0
- package/src/components/Toast.vue +83 -0
- package/src/components/Tooltip.vue +36 -0
- package/src/components/toast.js +98 -0
- package/src/directives/onOutsideClick.js +34 -0
- package/src/directives/visibility.js +24 -0
- package/src/index.js +56 -0
- package/src/resources/documentResource.js +194 -0
- package/src/resources/index.js +4 -0
- package/src/resources/listResource.js +312 -0
- package/src/resources/local.js +16 -0
- package/src/resources/plugin.js +105 -0
- package/src/resources/resources.js +215 -0
- package/src/style.css +15 -0
- package/src/utils/call.js +98 -0
- package/src/utils/config.js +9 -0
- package/src/utils/debounce.js +15 -0
- package/src/utils/file-to-base64.js +9 -0
- package/src/utils/markdown.js +29 -0
- package/src/utils/melonRequest.js +105 -0
- package/src/utils/pageMeta.js +52 -0
- package/src/utils/plugin.js +24 -0
- package/src/utils/request.js +49 -0
- package/src/utils/socketio.js +11 -0
- package/src/utils/tailwind.config.js +117 -0
- package/src/utils/vite-dev-server.js +14 -0
package/license.md
ADDED
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pimelon-ui",
|
|
3
|
+
"version": "0.0.98",
|
|
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
|
+
"prepare": "husky install",
|
|
10
|
+
"bump-and-release": "git pull --rebase origin main && yarn version --patch && git push && git push --tags",
|
|
11
|
+
"docs:dev": "vitepress dev docs",
|
|
12
|
+
"docs:build": "vitepress build docs",
|
|
13
|
+
"docs:serve": "vitepress serve docs"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/amonak/pimelon-ui.git"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@headlessui/vue": "^1.5.0",
|
|
24
|
+
"@popperjs/core": "^2.11.2",
|
|
25
|
+
"@tailwindcss/forms": "^0.5.3",
|
|
26
|
+
"@tailwindcss/typography": "^0.5.0",
|
|
27
|
+
"@tiptap/extension-color": "^2.0.0-beta.205",
|
|
28
|
+
"@tiptap/extension-highlight": "^2.0.0-beta.205",
|
|
29
|
+
"@tiptap/extension-image": "^2.0.0-beta.205",
|
|
30
|
+
"@tiptap/extension-link": "^2.0.0-beta.205",
|
|
31
|
+
"@tiptap/extension-mention": "^2.0.0-beta.205",
|
|
32
|
+
"@tiptap/extension-placeholder": "^2.0.0-beta.205",
|
|
33
|
+
"@tiptap/extension-table": "^2.0.0-beta.205",
|
|
34
|
+
"@tiptap/extension-table-cell": "^2.0.0-beta.205",
|
|
35
|
+
"@tiptap/extension-table-header": "^2.0.0-beta.205",
|
|
36
|
+
"@tiptap/extension-table-row": "^2.0.0-beta.205",
|
|
37
|
+
"@tiptap/extension-text-align": "^2.0.0-beta.205",
|
|
38
|
+
"@tiptap/extension-text-style": "^2.0.0-beta.205",
|
|
39
|
+
"@tiptap/extension-typography": "^2.0.0-beta.205",
|
|
40
|
+
"@tiptap/prosemirror-tables": "^1.1.3",
|
|
41
|
+
"@tiptap/starter-kit": "^2.0.0-beta.205",
|
|
42
|
+
"@tiptap/suggestion": "^2.0.0-beta.205",
|
|
43
|
+
"@tiptap/vue-3": "^2.0.0-beta.205",
|
|
44
|
+
"autoprefixer": "^10.4.2",
|
|
45
|
+
"feather-icons": "^4.28.0",
|
|
46
|
+
"idb-keyval": "^6.2.0",
|
|
47
|
+
"postcss": "^8.4.5",
|
|
48
|
+
"prosemirror-commands": "^1.5.0",
|
|
49
|
+
"prosemirror-dropcursor": "1.5.0",
|
|
50
|
+
"prosemirror-gapcursor": "^1.3.1",
|
|
51
|
+
"prosemirror-history": "^1.3.0",
|
|
52
|
+
"prosemirror-keymap": "^1.2.0",
|
|
53
|
+
"prosemirror-model": "^1.18.3",
|
|
54
|
+
"prosemirror-schema-list": "^1.2.2",
|
|
55
|
+
"prosemirror-state": "^1.4.2",
|
|
56
|
+
"prosemirror-transform": "^1.7.0",
|
|
57
|
+
"prosemirror-view": "^1.29.1",
|
|
58
|
+
"showdown": "^2.1.0",
|
|
59
|
+
"socket.io-client": "^4.5.1",
|
|
60
|
+
"tailwindcss": "^3.0.12",
|
|
61
|
+
"tippy.js": "^6.3.7"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"cross-fetch": "^3.1.5",
|
|
65
|
+
"husky": "^8.0.2",
|
|
66
|
+
"lint-staged": ">=10",
|
|
67
|
+
"prettier": "2.7.1",
|
|
68
|
+
"prettier-plugin-tailwindcss": "^0.1.13",
|
|
69
|
+
"vitepress": "^1.0.0-alpha.29",
|
|
70
|
+
"vue": "^3.2.45"
|
|
71
|
+
},
|
|
72
|
+
"lint-staged": {
|
|
73
|
+
"*.{js,css,md,vue}": "prettier --write"
|
|
74
|
+
}
|
|
75
|
+
}
|
package/readme.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="block w-full">
|
|
3
|
+
<div
|
|
4
|
+
class="flex items-start rounded-md px-4 py-3.5 text-base md:px-5"
|
|
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="ml-2 w-full">
|
|
23
|
+
<div class="flex flex-col md:flex-row md:items-baseline">
|
|
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,176 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Combobox v-model="selectedValue" nullable v-slot="{ open: isComboboxOpen }">
|
|
3
|
+
<Popover class="w-full">
|
|
4
|
+
<template #target="{ open: openPopover }">
|
|
5
|
+
<div class="w-full">
|
|
6
|
+
<ComboboxButton
|
|
7
|
+
class="flex w-full items-center justify-between rounded-md bg-gray-100 py-1 pl-3 pr-2"
|
|
8
|
+
:class="{ 'rounded-b-none': isComboboxOpen }"
|
|
9
|
+
@click="() => openPopover()"
|
|
10
|
+
>
|
|
11
|
+
<span
|
|
12
|
+
class="overflow-hidden text-ellipsis text-base leading-5"
|
|
13
|
+
v-if="selectedValue"
|
|
14
|
+
>
|
|
15
|
+
{{ displayValue(selectedValue) }}
|
|
16
|
+
</span>
|
|
17
|
+
<span class="text-base leading-5 text-gray-500" v-else>
|
|
18
|
+
{{ placeholder || '' }}
|
|
19
|
+
</span>
|
|
20
|
+
<FeatherIcon
|
|
21
|
+
name="chevron-down"
|
|
22
|
+
class="h-4 w-4 text-gray-500"
|
|
23
|
+
aria-hidden="true"
|
|
24
|
+
/>
|
|
25
|
+
</ComboboxButton>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
<template #body>
|
|
29
|
+
<ComboboxOptions
|
|
30
|
+
class="max-h-[15rem] overflow-y-auto rounded-md rounded-t-none bg-white px-1.5 pb-1.5 shadow-md"
|
|
31
|
+
static
|
|
32
|
+
v-show="isComboboxOpen"
|
|
33
|
+
>
|
|
34
|
+
<div
|
|
35
|
+
class="items-st sticky top-0 mb-1.5 flex items-stretch space-x-1.5 bg-white pt-1.5"
|
|
36
|
+
>
|
|
37
|
+
<ComboboxInput
|
|
38
|
+
class="form-input w-full placeholder-gray-500"
|
|
39
|
+
type="text"
|
|
40
|
+
@change="
|
|
41
|
+
(e) => {
|
|
42
|
+
query = e.target.value
|
|
43
|
+
}
|
|
44
|
+
"
|
|
45
|
+
:value="query"
|
|
46
|
+
autocomplete="off"
|
|
47
|
+
placeholder="Search by keyword"
|
|
48
|
+
/>
|
|
49
|
+
<Button icon="x" @click="selectedValue = null" />
|
|
50
|
+
</div>
|
|
51
|
+
<div
|
|
52
|
+
v-for="group in groups"
|
|
53
|
+
:key="group.key"
|
|
54
|
+
v-show="group.items.length > 0"
|
|
55
|
+
>
|
|
56
|
+
<div
|
|
57
|
+
v-if="group.group && !group.hideLabel"
|
|
58
|
+
class="px-2 py-1 text-xs font-semibold uppercase tracking-wider text-gray-500"
|
|
59
|
+
>
|
|
60
|
+
{{ group.group }}
|
|
61
|
+
</div>
|
|
62
|
+
<ComboboxOption
|
|
63
|
+
as="template"
|
|
64
|
+
v-for="option in group.items"
|
|
65
|
+
:key="option.value"
|
|
66
|
+
:value="option"
|
|
67
|
+
v-slot="{ active, selected }"
|
|
68
|
+
>
|
|
69
|
+
<li
|
|
70
|
+
:class="[
|
|
71
|
+
'rounded-md px-2.5 py-1.5 text-base',
|
|
72
|
+
{ 'bg-gray-100': active },
|
|
73
|
+
]"
|
|
74
|
+
>
|
|
75
|
+
{{ option.label }}
|
|
76
|
+
</li>
|
|
77
|
+
</ComboboxOption>
|
|
78
|
+
</div>
|
|
79
|
+
<li
|
|
80
|
+
v-if="groups.length == 0"
|
|
81
|
+
class="rounded-md px-2.5 py-1.5 text-base text-gray-600"
|
|
82
|
+
>
|
|
83
|
+
No results found
|
|
84
|
+
</li>
|
|
85
|
+
</ComboboxOptions>
|
|
86
|
+
</template>
|
|
87
|
+
</Popover>
|
|
88
|
+
</Combobox>
|
|
89
|
+
</template>
|
|
90
|
+
<script>
|
|
91
|
+
import {
|
|
92
|
+
Combobox,
|
|
93
|
+
ComboboxInput,
|
|
94
|
+
ComboboxOptions,
|
|
95
|
+
ComboboxOption,
|
|
96
|
+
ComboboxButton,
|
|
97
|
+
} from '@headlessui/vue'
|
|
98
|
+
import Popover from './Popover.vue'
|
|
99
|
+
import Button from './Button.vue'
|
|
100
|
+
import FeatherIcon from './FeatherIcon.vue'
|
|
101
|
+
|
|
102
|
+
export default {
|
|
103
|
+
name: 'Autocomplete',
|
|
104
|
+
props: ['modelValue', 'options', 'placeholder'],
|
|
105
|
+
emits: ['update:modelValue', 'change'],
|
|
106
|
+
components: {
|
|
107
|
+
Popover,
|
|
108
|
+
Button,
|
|
109
|
+
FeatherIcon,
|
|
110
|
+
Combobox,
|
|
111
|
+
ComboboxInput,
|
|
112
|
+
ComboboxOptions,
|
|
113
|
+
ComboboxOption,
|
|
114
|
+
ComboboxButton,
|
|
115
|
+
},
|
|
116
|
+
data() {
|
|
117
|
+
return {
|
|
118
|
+
query: '',
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
computed: {
|
|
122
|
+
valuePropPassed() {
|
|
123
|
+
return 'value' in this.$attrs
|
|
124
|
+
},
|
|
125
|
+
selectedValue: {
|
|
126
|
+
get() {
|
|
127
|
+
return this.valuePropPassed ? this.$attrs.value : this.modelValue
|
|
128
|
+
},
|
|
129
|
+
set(val) {
|
|
130
|
+
this.query = ''
|
|
131
|
+
this.$emit(this.valuePropPassed ? 'change' : 'update:modelValue', val)
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
groups() {
|
|
135
|
+
if (!this.options || this.options.length == 0) return []
|
|
136
|
+
|
|
137
|
+
let groups = this.options[0]?.group
|
|
138
|
+
? this.options
|
|
139
|
+
: [{ group: '', items: this.options }]
|
|
140
|
+
|
|
141
|
+
return groups
|
|
142
|
+
.map((group, i) => {
|
|
143
|
+
return {
|
|
144
|
+
key: i,
|
|
145
|
+
group: group.group,
|
|
146
|
+
hideLabel: group.hideLabel || false,
|
|
147
|
+
items: this.filterOptions(group.items),
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
.filter((group) => group.items.length > 0)
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
methods: {
|
|
154
|
+
filterOptions(options) {
|
|
155
|
+
if (!this.query) {
|
|
156
|
+
return options
|
|
157
|
+
}
|
|
158
|
+
return options.filter((option) => {
|
|
159
|
+
let searchTexts = [option.label, option.value]
|
|
160
|
+
return searchTexts.some((text) =>
|
|
161
|
+
(text || '')
|
|
162
|
+
.toString()
|
|
163
|
+
.toLowerCase()
|
|
164
|
+
.includes(this.query.toLowerCase())
|
|
165
|
+
)
|
|
166
|
+
})
|
|
167
|
+
},
|
|
168
|
+
displayValue(option) {
|
|
169
|
+
if (typeof option === 'string') {
|
|
170
|
+
return option
|
|
171
|
+
}
|
|
172
|
+
return option?.label
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
</script>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="shrink-0 overflow-hidden" :class="styleClasses">
|
|
3
|
+
<img
|
|
4
|
+
v-if="imageURL"
|
|
5
|
+
:src="imageURL"
|
|
6
|
+
class="object-cover"
|
|
7
|
+
:class="styleClasses"
|
|
8
|
+
loading="lazy"
|
|
9
|
+
/>
|
|
10
|
+
<div
|
|
11
|
+
v-else
|
|
12
|
+
class="flex h-full w-full items-center justify-center bg-gray-200 uppercase text-gray-600"
|
|
13
|
+
:class="{ sm: 'text-xs', md: 'text-base', lg: 'text-lg' }[size]"
|
|
14
|
+
>
|
|
15
|
+
{{ label && label[0] }}
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script>
|
|
21
|
+
const validShapes = ['square', 'circle']
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
name: 'Avatar',
|
|
25
|
+
props: {
|
|
26
|
+
imageURL: String,
|
|
27
|
+
label: String,
|
|
28
|
+
size: {
|
|
29
|
+
default: 'md',
|
|
30
|
+
},
|
|
31
|
+
shape: {
|
|
32
|
+
default: 'circle',
|
|
33
|
+
validator(value) {
|
|
34
|
+
const valid = validShapes.includes(value)
|
|
35
|
+
if (!valid) {
|
|
36
|
+
console.warn(
|
|
37
|
+
`shape property for <Avatar /> must be one of `,
|
|
38
|
+
validShapes
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
return valid
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
computed: {
|
|
46
|
+
styleClasses() {
|
|
47
|
+
const sizeClasses = {
|
|
48
|
+
sm: 'w-5 h-5',
|
|
49
|
+
md: 'w-8 h-8',
|
|
50
|
+
lg: 'w-12 h-12',
|
|
51
|
+
}[this.size]
|
|
52
|
+
|
|
53
|
+
const shapeClass = {
|
|
54
|
+
circle: 'rounded-full',
|
|
55
|
+
square: 'rounded-lg',
|
|
56
|
+
}[this.shape]
|
|
57
|
+
|
|
58
|
+
return `${shapeClass} ${sizeClasses}`
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
</script>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span
|
|
3
|
+
class="inline-block cursor-default rounded-md px-3 py-1 text-xs font-medium"
|
|
4
|
+
:class="classes"
|
|
5
|
+
>
|
|
6
|
+
<slot>{{ label }}</slot>
|
|
7
|
+
</span>
|
|
8
|
+
</template>
|
|
9
|
+
<script>
|
|
10
|
+
let validColors = ['gray', 'red', 'yellow', 'green', 'blue']
|
|
11
|
+
export default {
|
|
12
|
+
name: 'Badge',
|
|
13
|
+
props: ['color', 'label', 'colorMap'],
|
|
14
|
+
computed: {
|
|
15
|
+
classes() {
|
|
16
|
+
let color = this.getBadgeColor()
|
|
17
|
+
|
|
18
|
+
let cssClasses = {
|
|
19
|
+
gray: 'text-gray-700 bg-gray-100',
|
|
20
|
+
green: 'text-green-700 bg-green-50',
|
|
21
|
+
red: 'text-red-700 bg-red-50',
|
|
22
|
+
yellow: 'text-yellow-700 bg-yellow-50',
|
|
23
|
+
blue: 'text-blue-700 bg-blue-50',
|
|
24
|
+
}[color]
|
|
25
|
+
|
|
26
|
+
return cssClasses
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
methods: {
|
|
30
|
+
getBadgeColor() {
|
|
31
|
+
let color = this.color
|
|
32
|
+
if (this.colorMap) {
|
|
33
|
+
color = this.colorMap[this.label]
|
|
34
|
+
}
|
|
35
|
+
if (!color || !validColors.includes(color)) {
|
|
36
|
+
color = 'gray'
|
|
37
|
+
}
|
|
38
|
+
return color
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
v-bind="$attrs"
|
|
4
|
+
:class="buttonClasses"
|
|
5
|
+
@click="handleClick"
|
|
6
|
+
:disabled="isDisabled"
|
|
7
|
+
:aria-label="ariaLabel"
|
|
8
|
+
>
|
|
9
|
+
<LoadingIndicator
|
|
10
|
+
v-if="loading"
|
|
11
|
+
class="h-3 w-3"
|
|
12
|
+
:class="{
|
|
13
|
+
'mr-2 -ml-1': !icon,
|
|
14
|
+
'm-0.5': icon,
|
|
15
|
+
'text-white': appearance == 'primary',
|
|
16
|
+
'text-gray-600': appearance == 'secondary',
|
|
17
|
+
'text-red-200': appearance == 'danger',
|
|
18
|
+
'text-green-200': appearance == 'success',
|
|
19
|
+
'text-yellow-200': appearance == 'warning',
|
|
20
|
+
}"
|
|
21
|
+
/>
|
|
22
|
+
<FeatherIcon
|
|
23
|
+
v-else-if="iconLeft"
|
|
24
|
+
:name="iconLeft"
|
|
25
|
+
class="mr-1.5 h-4 w-4"
|
|
26
|
+
aria-hidden="true"
|
|
27
|
+
/>
|
|
28
|
+
<template v-if="loading && loadingText">{{ loadingText }}</template>
|
|
29
|
+
<template v-else-if="icon && !loading">
|
|
30
|
+
<FeatherIcon :name="icon" class="h-4 w-4" :aria-label="label" />
|
|
31
|
+
</template>
|
|
32
|
+
<span v-else :class="icon ? 'sr-only' : ''">
|
|
33
|
+
<slot>
|
|
34
|
+
{{ label }}
|
|
35
|
+
</slot>
|
|
36
|
+
</span>
|
|
37
|
+
<FeatherIcon
|
|
38
|
+
v-if="iconRight"
|
|
39
|
+
:name="iconRight"
|
|
40
|
+
class="ml-2 h-4 w-4"
|
|
41
|
+
aria-hidden="true"
|
|
42
|
+
/>
|
|
43
|
+
</button>
|
|
44
|
+
</template>
|
|
45
|
+
<script>
|
|
46
|
+
import FeatherIcon from './FeatherIcon.vue'
|
|
47
|
+
import LoadingIndicator from './LoadingIndicator.vue'
|
|
48
|
+
|
|
49
|
+
const ValidAppearances = [
|
|
50
|
+
'primary',
|
|
51
|
+
'secondary',
|
|
52
|
+
'danger',
|
|
53
|
+
'success',
|
|
54
|
+
'warning',
|
|
55
|
+
'white',
|
|
56
|
+
'minimal',
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
export default {
|
|
60
|
+
name: 'Button',
|
|
61
|
+
components: {
|
|
62
|
+
FeatherIcon,
|
|
63
|
+
LoadingIndicator,
|
|
64
|
+
},
|
|
65
|
+
props: {
|
|
66
|
+
label: {
|
|
67
|
+
type: String,
|
|
68
|
+
default: null,
|
|
69
|
+
},
|
|
70
|
+
appearance: {
|
|
71
|
+
type: String,
|
|
72
|
+
default: 'secondary',
|
|
73
|
+
validator: (value) => {
|
|
74
|
+
return ValidAppearances.includes(value)
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
disabled: {
|
|
78
|
+
type: Boolean,
|
|
79
|
+
default: false,
|
|
80
|
+
},
|
|
81
|
+
active: {
|
|
82
|
+
type: Boolean,
|
|
83
|
+
default: false,
|
|
84
|
+
},
|
|
85
|
+
iconLeft: {
|
|
86
|
+
type: String,
|
|
87
|
+
default: null,
|
|
88
|
+
},
|
|
89
|
+
iconRight: {
|
|
90
|
+
type: String,
|
|
91
|
+
default: null,
|
|
92
|
+
},
|
|
93
|
+
icon: {
|
|
94
|
+
type: String,
|
|
95
|
+
default: null,
|
|
96
|
+
},
|
|
97
|
+
loading: {
|
|
98
|
+
type: Boolean,
|
|
99
|
+
default: false,
|
|
100
|
+
},
|
|
101
|
+
loadingText: {
|
|
102
|
+
type: String,
|
|
103
|
+
default: null,
|
|
104
|
+
},
|
|
105
|
+
route: {},
|
|
106
|
+
link: {
|
|
107
|
+
type: String,
|
|
108
|
+
default: null,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
computed: {
|
|
112
|
+
buttonClasses() {
|
|
113
|
+
let appearanceClasses = {
|
|
114
|
+
primary:
|
|
115
|
+
'bg-blue-500 hover:bg-blue-600 border-transparent text-white focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500',
|
|
116
|
+
secondary:
|
|
117
|
+
'bg-gray-100 hover:bg-gray-200 border-transparent text-gray-900 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500',
|
|
118
|
+
danger:
|
|
119
|
+
'bg-red-500 hover:bg-red-400 border-transparent text-white focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-red-500',
|
|
120
|
+
success:
|
|
121
|
+
'bg-green-500 hover:bg-green-400 border-transparent text-white focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-green-500',
|
|
122
|
+
warning:
|
|
123
|
+
'bg-yellow-500 hover:bg-yellow-400 border-transparent text-white focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-yellow-500',
|
|
124
|
+
white:
|
|
125
|
+
'bg-white text-gray-900 border-gray-200 hover:bg-gray-50 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-400',
|
|
126
|
+
minimal: `active:bg-gray-200 border-transparent focus:bg-gray-200 text-gray-900 ${
|
|
127
|
+
this.active ? 'bg-gray-200' : 'bg-transparent hover:bg-gray-200'
|
|
128
|
+
}`,
|
|
129
|
+
}
|
|
130
|
+
return [
|
|
131
|
+
'inline-flex items-center justify-center text-base leading-5 rounded-md border transition-colors focus:outline-none',
|
|
132
|
+
this.icon ? 'p-1.5' : 'px-3 py-1',
|
|
133
|
+
this.isDisabled
|
|
134
|
+
? 'opacity-50 cursor-not-allowed pointer-events-none'
|
|
135
|
+
: '',
|
|
136
|
+
appearanceClasses[this.appearance],
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
isDisabled() {
|
|
140
|
+
return this.disabled || this.loading
|
|
141
|
+
},
|
|
142
|
+
ariaLabel() {
|
|
143
|
+
return this.icon ? this.label : null
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
methods: {
|
|
147
|
+
handleClick() {
|
|
148
|
+
if (this.route && this.$router) {
|
|
149
|
+
this.route && this.$router.push(this.route)
|
|
150
|
+
}
|
|
151
|
+
this.link ? window.open(this.link, '_blank') : null
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
</script>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col rounded-lg border bg-white px-6 py-5 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="mt-1.5 text-base text-gray-600" v-if="subtitle">
|
|
15
|
+
{{ subtitle }}
|
|
16
|
+
</p>
|
|
17
|
+
<div
|
|
18
|
+
v-if="loading"
|
|
19
|
+
class="mt-4 flex flex-auto flex-col items-center justify-center rounded-md"
|
|
20
|
+
>
|
|
21
|
+
<LoadingText />
|
|
22
|
+
</div>
|
|
23
|
+
<div class="mt-4 flex-auto 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>
|