pimelon-ui 0.0.19 → 0.0.57
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/package.json +28 -8
- package/src/components/Alert.vue +3 -3
- package/src/components/Autocomplete.vue +146 -0
- package/src/components/Avatar.vue +2 -2
- package/src/components/Badge.vue +38 -19
- package/src/components/Button.vue +26 -11
- package/src/components/Card.vue +4 -4
- package/src/components/DatePicker.vue +247 -0
- package/src/components/Dialog.vue +14 -17
- package/src/components/Dropdown.vue +9 -9
- package/src/components/ErrorMessage.vue +1 -1
- package/src/components/FeatherIcon.vue +8 -4
- package/src/components/Input.vue +31 -17
- package/src/components/Link.vue +1 -1
- package/src/components/LoadingText.vue +1 -1
- package/src/components/Modal.vue +1 -1
- package/src/components/Popover.vue +124 -81
- package/src/components/SuccessMessage.vue +1 -1
- package/src/components/TextEditor/InsertImage.vue +72 -0
- package/src/components/TextEditor/MentionList.vue +93 -0
- package/src/components/TextEditor/Menu.vue +113 -8
- package/src/components/TextEditor/TextEditor.vue +180 -25
- package/src/components/TextEditor/commands.js +185 -10
- 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/image-extension.js +152 -0
- package/src/components/TextEditor/mention.js +72 -0
- package/src/components/Toast.vue +26 -20
- package/src/components/Tooltip.vue +35 -0
- package/src/index.js +9 -0
- package/src/style.css +2 -2
- package/src/utils/file-to-base64.js +9 -0
- package/src/utils/pageMeta.js +50 -0
- package/src/utils/plugin.js +2 -2
- package/src/utils/resources.js +176 -25
- package/src/utils/socketio.js +10 -8
- package/src/utils/vite-dev-server.js +1 -1
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pimelon-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.57",
|
|
4
4
|
"description": "A set of components and utilities for rapid UI development",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "npx prettier --check ./src",
|
|
8
|
-
"prettier": "npx prettier -w ./src"
|
|
8
|
+
"prettier": "npx prettier -w ./src",
|
|
9
|
+
"prepare": "husky install",
|
|
10
|
+
"bump-and-release": "yarn version --patch && git push && git push --tags"
|
|
9
11
|
},
|
|
10
12
|
"files": [
|
|
11
13
|
"src"
|
|
@@ -15,19 +17,37 @@
|
|
|
15
17
|
"url": "https://github.com/amonak/pimelon-ui.git"
|
|
16
18
|
},
|
|
17
19
|
"author": "Alphamonak Solutions",
|
|
20
|
+
"license": "MIT",
|
|
18
21
|
"dependencies": {
|
|
19
22
|
"@headlessui/vue": "^1.5.0",
|
|
20
23
|
"@popperjs/core": "^2.11.2",
|
|
21
24
|
"@tailwindcss/forms": "^0.4.0",
|
|
22
25
|
"@tailwindcss/typography": "^0.5.0",
|
|
23
|
-
"@tiptap/extension-image": "^2.0.0-beta.
|
|
24
|
-
"@tiptap/extension-
|
|
25
|
-
"@tiptap/
|
|
26
|
-
"@tiptap/
|
|
26
|
+
"@tiptap/extension-image": "^2.0.0-beta.30",
|
|
27
|
+
"@tiptap/extension-link": "^2.0.0-beta.43",
|
|
28
|
+
"@tiptap/extension-mention": "^2.0.0-beta.102",
|
|
29
|
+
"@tiptap/extension-placeholder": "^2.0.0-beta.53",
|
|
30
|
+
"@tiptap/extension-table": "^2.0.0-beta.54",
|
|
31
|
+
"@tiptap/extension-table-cell": "^2.0.0-beta.23",
|
|
32
|
+
"@tiptap/extension-table-header": "^2.0.0-beta.25",
|
|
33
|
+
"@tiptap/extension-table-row": "^2.0.0-beta.22",
|
|
34
|
+
"@tiptap/extension-text-align": "^2.0.0-beta.31",
|
|
35
|
+
"@tiptap/starter-kit": "^2.0.0-beta.191",
|
|
36
|
+
"@tiptap/vue-3": "^2.0.0-beta.96",
|
|
27
37
|
"autoprefixer": "^10.4.2",
|
|
28
38
|
"feather-icons": "^4.28.0",
|
|
29
39
|
"postcss": "^8.4.5",
|
|
30
|
-
"socket.io-client": "^
|
|
31
|
-
"tailwindcss": "^3.0.12"
|
|
40
|
+
"socket.io-client": "^4.5.1",
|
|
41
|
+
"tailwindcss": "^3.0.12",
|
|
42
|
+
"tippy.js": "^6.3.7"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"husky": "^8.0.3",
|
|
46
|
+
"lint-staged": ">=10",
|
|
47
|
+
"prettier": "2.7.1",
|
|
48
|
+
"prettier-plugin-tailwindcss": "^0.1.13"
|
|
49
|
+
},
|
|
50
|
+
"lint-staged": {
|
|
51
|
+
"*.{js,css,md,vue}": "prettier --write"
|
|
32
52
|
}
|
|
33
53
|
}
|
package/src/components/Alert.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="block w-full">
|
|
3
3
|
<div
|
|
4
|
-
class="items-start
|
|
4
|
+
class="flex items-start rounded-md px-4 py-3.5 text-base md:px-5"
|
|
5
5
|
:class="classes"
|
|
6
6
|
>
|
|
7
7
|
<svg
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
fill="#318AD8"
|
|
20
20
|
/>
|
|
21
21
|
</svg>
|
|
22
|
-
<div class="w-full
|
|
23
|
-
<div class="flex flex-col md:
|
|
22
|
+
<div class="ml-2 w-full">
|
|
23
|
+
<div class="flex flex-col md:flex-row md:items-baseline">
|
|
24
24
|
<h3 class="text-lg font-medium text-gray-900" v-if="title">
|
|
25
25
|
{{ title }}
|
|
26
26
|
</h3>
|
|
@@ -0,0 +1,146 @@
|
|
|
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.5 pl-3 pr-2"
|
|
8
|
+
:class="{ 'rounded-b-none': isComboboxOpen }"
|
|
9
|
+
@click="
|
|
10
|
+
() => {
|
|
11
|
+
openPopover()
|
|
12
|
+
}
|
|
13
|
+
"
|
|
14
|
+
>
|
|
15
|
+
<span
|
|
16
|
+
class="overflow-hidden text-ellipsis text-base"
|
|
17
|
+
v-if="selectedValue"
|
|
18
|
+
>
|
|
19
|
+
{{ displayValue(selectedValue) }}
|
|
20
|
+
</span>
|
|
21
|
+
<span class="text-base text-gray-500" v-else>
|
|
22
|
+
{{ placeholder || '' }}
|
|
23
|
+
</span>
|
|
24
|
+
<FeatherIcon
|
|
25
|
+
name="chevron-down"
|
|
26
|
+
class="h-4 w-4 text-gray-500"
|
|
27
|
+
aria-hidden="true"
|
|
28
|
+
/>
|
|
29
|
+
</ComboboxButton>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
<template #body>
|
|
33
|
+
<ComboboxOptions
|
|
34
|
+
class="max-h-[11rem] overflow-y-auto rounded-md rounded-t-none bg-white px-1.5 pb-1.5 shadow-md"
|
|
35
|
+
static
|
|
36
|
+
v-show="isComboboxOpen"
|
|
37
|
+
>
|
|
38
|
+
<div
|
|
39
|
+
class="items-st sticky top-0 mb-1.5 flex items-stretch space-x-1.5 bg-white pt-1.5"
|
|
40
|
+
>
|
|
41
|
+
<ComboboxInput
|
|
42
|
+
class="form-input w-full placeholder-gray-500"
|
|
43
|
+
type="text"
|
|
44
|
+
@change="
|
|
45
|
+
(e) => {
|
|
46
|
+
query = e.target.value
|
|
47
|
+
}
|
|
48
|
+
"
|
|
49
|
+
:value="query"
|
|
50
|
+
autocomplete="off"
|
|
51
|
+
placeholder="Search by keyword"
|
|
52
|
+
/>
|
|
53
|
+
<Button icon="x" @click="selectedValue = null" />
|
|
54
|
+
</div>
|
|
55
|
+
<ComboboxOption
|
|
56
|
+
as="template"
|
|
57
|
+
v-for="option in filteredOptions"
|
|
58
|
+
:key="option.value"
|
|
59
|
+
:value="option"
|
|
60
|
+
v-slot="{ active, selected }"
|
|
61
|
+
>
|
|
62
|
+
<li
|
|
63
|
+
:class="[
|
|
64
|
+
'rounded-md px-2.5 py-1.5 text-base',
|
|
65
|
+
{ 'bg-gray-100': active },
|
|
66
|
+
]"
|
|
67
|
+
>
|
|
68
|
+
{{ option.label }}
|
|
69
|
+
</li>
|
|
70
|
+
</ComboboxOption>
|
|
71
|
+
<li
|
|
72
|
+
v-if="filteredOptions.length == 0"
|
|
73
|
+
class="rounded-md px-2.5 py-1.5 text-base text-gray-600"
|
|
74
|
+
>
|
|
75
|
+
No results found
|
|
76
|
+
</li>
|
|
77
|
+
</ComboboxOptions>
|
|
78
|
+
</template>
|
|
79
|
+
</Popover>
|
|
80
|
+
</Combobox>
|
|
81
|
+
</template>
|
|
82
|
+
<script>
|
|
83
|
+
import {
|
|
84
|
+
Combobox,
|
|
85
|
+
ComboboxInput,
|
|
86
|
+
ComboboxOptions,
|
|
87
|
+
ComboboxOption,
|
|
88
|
+
ComboboxButton,
|
|
89
|
+
} from '@headlessui/vue'
|
|
90
|
+
import Popover from './Popover.vue'
|
|
91
|
+
|
|
92
|
+
export default {
|
|
93
|
+
name: 'Autocomplete',
|
|
94
|
+
props: ['modelValue', 'options', 'placeholder'],
|
|
95
|
+
emits: ['update:modelValue', 'change'],
|
|
96
|
+
components: {
|
|
97
|
+
Popover,
|
|
98
|
+
Combobox,
|
|
99
|
+
ComboboxInput,
|
|
100
|
+
ComboboxOptions,
|
|
101
|
+
ComboboxOption,
|
|
102
|
+
ComboboxButton,
|
|
103
|
+
},
|
|
104
|
+
data() {
|
|
105
|
+
return {
|
|
106
|
+
query: '',
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
computed: {
|
|
110
|
+
valuePropPassed() {
|
|
111
|
+
return 'value' in this.$attrs
|
|
112
|
+
},
|
|
113
|
+
selectedValue: {
|
|
114
|
+
get() {
|
|
115
|
+
return this.valuePropPassed ? this.$attrs.value : this.modelValue
|
|
116
|
+
},
|
|
117
|
+
set(val) {
|
|
118
|
+
this.query = ''
|
|
119
|
+
this.$emit(this.valuePropPassed ? 'change' : 'update:modelValue', val)
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
filteredOptions() {
|
|
123
|
+
if (!this.query) {
|
|
124
|
+
return this.options
|
|
125
|
+
}
|
|
126
|
+
return this.options.filter((option) => {
|
|
127
|
+
let searchTexts = [option.label, option.value]
|
|
128
|
+
return searchTexts.some((text) =>
|
|
129
|
+
(text || '')
|
|
130
|
+
.toString()
|
|
131
|
+
.toLowerCase()
|
|
132
|
+
.includes(this.query.toLowerCase())
|
|
133
|
+
)
|
|
134
|
+
})
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
methods: {
|
|
138
|
+
displayValue(option) {
|
|
139
|
+
if (typeof option === 'string') {
|
|
140
|
+
return option
|
|
141
|
+
}
|
|
142
|
+
return option?.label
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="overflow-hidden" :class="styleClasses">
|
|
2
|
+
<div class="shrink-0 overflow-hidden" :class="styleClasses">
|
|
3
3
|
<img
|
|
4
4
|
v-if="imageURL"
|
|
5
5
|
:src="imageURL"
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
/>
|
|
9
9
|
<div
|
|
10
10
|
v-else
|
|
11
|
-
class="flex items-center justify-center
|
|
11
|
+
class="flex h-full w-full items-center justify-center bg-gray-200 uppercase text-gray-600"
|
|
12
12
|
:class="{ sm: 'text-xs', md: 'text-base', lg: 'text-lg' }[size]"
|
|
13
13
|
>
|
|
14
14
|
{{ label && label[0] }}
|
package/src/components/Badge.vue
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<span
|
|
3
|
-
class="inline-block px-3 py-1 text-xs font-medium
|
|
3
|
+
class="inline-block cursor-default rounded-md px-3 py-1 text-xs font-medium"
|
|
4
4
|
:class="classes"
|
|
5
5
|
>
|
|
6
6
|
<slot>{{ status }}</slot>
|
|
@@ -9,31 +9,50 @@
|
|
|
9
9
|
<script>
|
|
10
10
|
export default {
|
|
11
11
|
name: 'Badge',
|
|
12
|
-
props: ['color', 'status'],
|
|
12
|
+
props: ['color', 'status', 'colorMap'],
|
|
13
|
+
data: {
|
|
14
|
+
defaultColorMap: {
|
|
15
|
+
Pending: 'yellow',
|
|
16
|
+
Running: 'yellow',
|
|
17
|
+
Success: 'green',
|
|
18
|
+
Failure: 'red',
|
|
19
|
+
Active: 'green',
|
|
20
|
+
Broken: 'red',
|
|
21
|
+
Updating: 'blue',
|
|
22
|
+
Rejected: 'red',
|
|
23
|
+
Published: 'green',
|
|
24
|
+
Approved: 'green',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
13
27
|
computed: {
|
|
14
28
|
classes() {
|
|
15
|
-
let color = this.
|
|
16
|
-
|
|
17
|
-
|
|
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 {
|
|
29
|
+
let color = this.getBadgeColor()
|
|
30
|
+
|
|
31
|
+
let cssClasses = {
|
|
31
32
|
gray: 'text-gray-700 bg-gray-50',
|
|
32
33
|
green: 'text-green-700 bg-green-50',
|
|
33
34
|
red: 'text-red-700 bg-red-50',
|
|
34
35
|
yellow: 'text-yellow-700 bg-yellow-50',
|
|
35
36
|
blue: 'text-blue-700 bg-blue-50',
|
|
36
|
-
}[color
|
|
37
|
+
}[color]
|
|
38
|
+
|
|
39
|
+
return cssClasses
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
methods: {
|
|
43
|
+
getBadgeColor() {
|
|
44
|
+
let color = this.color
|
|
45
|
+
if (color) {
|
|
46
|
+
return color
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let statusColorMap = Object.assign(
|
|
50
|
+
this.defaultColorMap,
|
|
51
|
+
this.colorMap || {}
|
|
52
|
+
)
|
|
53
|
+
color = statusColorMap[this.status] || 'gray'
|
|
54
|
+
|
|
55
|
+
return color
|
|
37
56
|
},
|
|
38
57
|
},
|
|
39
58
|
}
|
|
@@ -7,23 +7,26 @@
|
|
|
7
7
|
>
|
|
8
8
|
<LoadingIndicator
|
|
9
9
|
v-if="loading"
|
|
10
|
+
class="mr-2 -ml-1 h-3 w-3"
|
|
10
11
|
:class="{
|
|
11
12
|
'text-white': appearance == 'primary',
|
|
12
13
|
'text-gray-600': appearance == 'secondary',
|
|
13
14
|
'text-red-200': appearance == 'danger',
|
|
15
|
+
'text-green-200': appearance == 'success',
|
|
16
|
+
'text-yellow-200': appearance == 'warning',
|
|
14
17
|
}"
|
|
15
18
|
/>
|
|
16
19
|
<FeatherIcon
|
|
17
20
|
v-else-if="iconLeft"
|
|
18
21
|
:name="iconLeft"
|
|
19
|
-
class="
|
|
22
|
+
class="mr-1.5 h-4 w-4"
|
|
20
23
|
aria-hidden="true"
|
|
21
24
|
/>
|
|
22
25
|
<template v-if="loading && loadingText">{{ loadingText }}</template>
|
|
23
26
|
<template v-else-if="icon">
|
|
24
|
-
<FeatherIcon :name="icon" class="
|
|
27
|
+
<FeatherIcon :name="icon" class="h-4 w-4" :aria-label="label" />
|
|
25
28
|
</template>
|
|
26
|
-
<span :class="icon ? 'sr-only' : ''">
|
|
29
|
+
<span v-else :class="icon ? 'sr-only' : ''">
|
|
27
30
|
<slot>
|
|
28
31
|
{{ label }}
|
|
29
32
|
</slot>
|
|
@@ -31,7 +34,7 @@
|
|
|
31
34
|
<FeatherIcon
|
|
32
35
|
v-if="iconRight"
|
|
33
36
|
:name="iconRight"
|
|
34
|
-
class="
|
|
37
|
+
class="ml-2 h-4 w-4"
|
|
35
38
|
aria-hidden="true"
|
|
36
39
|
/>
|
|
37
40
|
</button>
|
|
@@ -40,7 +43,15 @@
|
|
|
40
43
|
import FeatherIcon from './FeatherIcon.vue'
|
|
41
44
|
import LoadingIndicator from './LoadingIndicator.vue'
|
|
42
45
|
|
|
43
|
-
const ValidAppearances = [
|
|
46
|
+
const ValidAppearances = [
|
|
47
|
+
'primary',
|
|
48
|
+
'secondary',
|
|
49
|
+
'danger',
|
|
50
|
+
'success',
|
|
51
|
+
'warning',
|
|
52
|
+
'white',
|
|
53
|
+
'minimal',
|
|
54
|
+
]
|
|
44
55
|
|
|
45
56
|
export default {
|
|
46
57
|
name: 'Button',
|
|
@@ -98,19 +109,23 @@ export default {
|
|
|
98
109
|
buttonClasses() {
|
|
99
110
|
let appearanceClasses = {
|
|
100
111
|
primary:
|
|
101
|
-
'bg-blue-500 hover:bg-blue-600 text-white focus:ring-2 focus:ring-offset-2 focus:ring-blue-500',
|
|
112
|
+
'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',
|
|
102
113
|
secondary:
|
|
103
|
-
'bg-gray-100 hover:bg-gray-200 text-gray-900 focus:ring-2 focus:ring-offset-2 focus:ring-gray-500',
|
|
114
|
+
'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',
|
|
104
115
|
danger:
|
|
105
|
-
'bg-red-500 hover:bg-red-400 text-white focus:ring-2 focus:ring-offset-2 focus:ring-red-500',
|
|
116
|
+
'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',
|
|
117
|
+
success:
|
|
118
|
+
'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',
|
|
119
|
+
warning:
|
|
120
|
+
'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',
|
|
106
121
|
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 ${
|
|
122
|
+
'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',
|
|
123
|
+
minimal: `active:bg-gray-200 border-transparent focus:bg-gray-200 text-gray-900 ${
|
|
109
124
|
this.active ? 'bg-gray-200' : 'bg-transparent hover:bg-gray-200'
|
|
110
125
|
}`,
|
|
111
126
|
}
|
|
112
127
|
return [
|
|
113
|
-
'inline-flex items-center justify-center text-base leading-5 rounded-md transition-colors focus:outline-none',
|
|
128
|
+
'inline-flex items-center justify-center text-base leading-5 rounded-md border transition-colors focus:outline-none',
|
|
114
129
|
this.icon ? 'p-1.5' : 'px-3 py-1',
|
|
115
130
|
this.isDisabled
|
|
116
131
|
? 'opacity-50 cursor-not-allowed pointer-events-none'
|
package/src/components/Card.vue
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex flex-col
|
|
2
|
+
<div class="flex flex-col rounded-lg border bg-white px-6 py-5 shadow">
|
|
3
3
|
<div class="flex items-baseline justify-between">
|
|
4
4
|
<div class="flex items-baseline space-x-2">
|
|
5
5
|
<div class="flex items-center space-x-2" v-if="$slots['actions-left']">
|
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
<slot name="actions"></slot>
|
|
12
12
|
</div>
|
|
13
13
|
</div>
|
|
14
|
-
<p class="text-base text-gray-600
|
|
14
|
+
<p class="mt-1.5 text-base text-gray-600" v-if="subtitle">
|
|
15
15
|
{{ subtitle }}
|
|
16
16
|
</p>
|
|
17
17
|
<div
|
|
18
18
|
v-if="loading"
|
|
19
|
-
class="flex flex-col items-center justify-center
|
|
19
|
+
class="mt-4 flex flex-auto flex-col items-center justify-center rounded-md"
|
|
20
20
|
>
|
|
21
21
|
<LoadingText />
|
|
22
22
|
</div>
|
|
23
|
-
<div class="flex-auto
|
|
23
|
+
<div class="mt-4 flex-auto overflow-auto" v-else-if="$slots['default']">
|
|
24
24
|
<slot></slot>
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Popover @open="selectCurrentMonthYear" transition="default">
|
|
3
|
+
<template #target="{ togglePopover }">
|
|
4
|
+
<Input
|
|
5
|
+
type="text"
|
|
6
|
+
:class="inputClass"
|
|
7
|
+
:value="
|
|
8
|
+
modelValue && formatValue ? formatValue(modelValue) : modelValue || ''
|
|
9
|
+
"
|
|
10
|
+
:placeholder="placeholder"
|
|
11
|
+
@focus="!readonly ? togglePopover() : null"
|
|
12
|
+
readonly
|
|
13
|
+
/>
|
|
14
|
+
</template>
|
|
15
|
+
<template #body-main="{ togglePopover }">
|
|
16
|
+
<div class="mt-1 select-none p-3 text-left">
|
|
17
|
+
<div class="flex items-center justify-between">
|
|
18
|
+
<span class="text-base font-medium text-blue-500">
|
|
19
|
+
{{ formatMonth }}
|
|
20
|
+
</span>
|
|
21
|
+
<span class="flex">
|
|
22
|
+
<div
|
|
23
|
+
class="grid h-5 w-5 cursor-pointer place-items-center rounded-md hover:bg-gray-100"
|
|
24
|
+
>
|
|
25
|
+
<FeatherIcon
|
|
26
|
+
@click="prevMonth"
|
|
27
|
+
name="chevron-left"
|
|
28
|
+
class="h-4 w-4"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
<div
|
|
32
|
+
class="ml-2 grid h-5 w-5 cursor-pointer place-items-center rounded-md hover:bg-gray-100"
|
|
33
|
+
>
|
|
34
|
+
<FeatherIcon
|
|
35
|
+
@click="nextMonth"
|
|
36
|
+
name="chevron-right"
|
|
37
|
+
class="h-4 w-4"
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
</span>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="mt-2 text-sm">
|
|
43
|
+
<div class="grid w-full grid-cols-7 place-items-center text-gray-600">
|
|
44
|
+
<div
|
|
45
|
+
class="grid h-6 w-6 place-items-center gap-1 text-center"
|
|
46
|
+
v-for="(d, i) in ['S', 'M', 'T', 'W', 'T', 'F', 'S']"
|
|
47
|
+
:key="i"
|
|
48
|
+
>
|
|
49
|
+
{{ d }}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div v-for="(week, i) in datesAsWeeks" :key="i" class="mt-1">
|
|
53
|
+
<div class="grid w-full grid-cols-7 place-items-center gap-1">
|
|
54
|
+
<div
|
|
55
|
+
v-for="date in week"
|
|
56
|
+
:key="toValue(date)"
|
|
57
|
+
class="grid h-6 w-6 cursor-pointer place-items-center rounded-md hover:bg-blue-100 hover:text-blue-700"
|
|
58
|
+
:class="{
|
|
59
|
+
'text-gray-600': date.getMonth() !== currentMonth - 1,
|
|
60
|
+
'text-blue-500': toValue(date) === toValue(today),
|
|
61
|
+
'bg-blue-100 font-semibold text-blue-500':
|
|
62
|
+
toValue(date) === modelValue,
|
|
63
|
+
}"
|
|
64
|
+
@click="
|
|
65
|
+
() => {
|
|
66
|
+
selectDate(date)
|
|
67
|
+
togglePopover()
|
|
68
|
+
}
|
|
69
|
+
"
|
|
70
|
+
>
|
|
71
|
+
{{ date.getDate() }}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="mt-2 flex w-full justify-end">
|
|
77
|
+
<div
|
|
78
|
+
class="cursor-pointer rounded-md px-2 py-1 text-sm hover:bg-gray-100"
|
|
79
|
+
@click="
|
|
80
|
+
() => {
|
|
81
|
+
selectDate('')
|
|
82
|
+
togglePopover()
|
|
83
|
+
}
|
|
84
|
+
"
|
|
85
|
+
>
|
|
86
|
+
Clear
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</template>
|
|
91
|
+
</Popover>
|
|
92
|
+
</template>
|
|
93
|
+
|
|
94
|
+
<script>
|
|
95
|
+
import Popover from './Popover.vue'
|
|
96
|
+
|
|
97
|
+
export default {
|
|
98
|
+
name: 'DatePicker',
|
|
99
|
+
props: ['modelValue', 'placeholder', 'readonly', 'formatValue', 'inputClass'],
|
|
100
|
+
emits: ['update:modelValue'],
|
|
101
|
+
components: {
|
|
102
|
+
Popover,
|
|
103
|
+
},
|
|
104
|
+
data() {
|
|
105
|
+
return {
|
|
106
|
+
currentYear: null,
|
|
107
|
+
currentMonth: null,
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
created() {
|
|
111
|
+
this.selectCurrentMonthYear()
|
|
112
|
+
},
|
|
113
|
+
computed: {
|
|
114
|
+
today() {
|
|
115
|
+
return this.getDate()
|
|
116
|
+
},
|
|
117
|
+
datesAsWeeks() {
|
|
118
|
+
let datesAsWeeks = []
|
|
119
|
+
let dates = this.dates.slice()
|
|
120
|
+
while (dates.length) {
|
|
121
|
+
let week = dates.splice(0, 7)
|
|
122
|
+
datesAsWeeks.push(week)
|
|
123
|
+
}
|
|
124
|
+
return datesAsWeeks
|
|
125
|
+
},
|
|
126
|
+
dates() {
|
|
127
|
+
if (!(this.currentYear && this.currentMonth)) {
|
|
128
|
+
return []
|
|
129
|
+
}
|
|
130
|
+
let monthIndex = this.currentMonth - 1
|
|
131
|
+
let year = this.currentYear
|
|
132
|
+
|
|
133
|
+
let firstDayOfMonth = this.getDate(year, monthIndex, 1)
|
|
134
|
+
let lastDayOfMonth = this.getDate(year, monthIndex + 1, 0)
|
|
135
|
+
let leftPaddingCount = firstDayOfMonth.getDay()
|
|
136
|
+
let rightPaddingCount = 6 - lastDayOfMonth.getDay()
|
|
137
|
+
|
|
138
|
+
let leftPadding = this.getDatesAfter(firstDayOfMonth, -leftPaddingCount)
|
|
139
|
+
let rightPadding = this.getDatesAfter(lastDayOfMonth, rightPaddingCount)
|
|
140
|
+
let daysInMonth = this.getDaysInMonth(monthIndex, year)
|
|
141
|
+
let datesInMonth = this.getDatesAfter(firstDayOfMonth, daysInMonth - 1)
|
|
142
|
+
|
|
143
|
+
let dates = [
|
|
144
|
+
...leftPadding,
|
|
145
|
+
firstDayOfMonth,
|
|
146
|
+
...datesInMonth,
|
|
147
|
+
...rightPadding,
|
|
148
|
+
]
|
|
149
|
+
if (dates.length < 42) {
|
|
150
|
+
const finalPadding = this.getDatesAfter(dates.at(-1), 42 - dates.length)
|
|
151
|
+
dates = dates.concat(...finalPadding)
|
|
152
|
+
}
|
|
153
|
+
return dates
|
|
154
|
+
},
|
|
155
|
+
formatMonth() {
|
|
156
|
+
let date = this.getDate(this.currentYear, this.currentMonth - 1, 1)
|
|
157
|
+
return date.toLocaleString('en-US', { month: 'short', year: 'numeric' })
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
methods: {
|
|
161
|
+
selectDate(date) {
|
|
162
|
+
this.$emit('update:modelValue', this.toValue(date))
|
|
163
|
+
},
|
|
164
|
+
selectCurrentMonthYear() {
|
|
165
|
+
let date = this.modelValue
|
|
166
|
+
? this.getDate(this.modelValue)
|
|
167
|
+
: this.getDate()
|
|
168
|
+
this.currentYear = date.getFullYear()
|
|
169
|
+
this.currentMonth = date.getMonth() + 1
|
|
170
|
+
},
|
|
171
|
+
prevMonth() {
|
|
172
|
+
this.changeMonth(-1)
|
|
173
|
+
},
|
|
174
|
+
nextMonth() {
|
|
175
|
+
this.changeMonth(1)
|
|
176
|
+
},
|
|
177
|
+
changeMonth(adder) {
|
|
178
|
+
this.currentMonth = this.currentMonth + adder
|
|
179
|
+
if (this.currentMonth < 1) {
|
|
180
|
+
this.currentMonth = 12
|
|
181
|
+
this.currentYear = this.currentYear - 1
|
|
182
|
+
}
|
|
183
|
+
if (this.currentMonth > 12) {
|
|
184
|
+
this.currentMonth = 1
|
|
185
|
+
this.currentYear = this.currentYear + 1
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
getDatesAfter(date, count) {
|
|
189
|
+
let incrementer = 1
|
|
190
|
+
if (count < 0) {
|
|
191
|
+
incrementer = -1
|
|
192
|
+
count = Math.abs(count)
|
|
193
|
+
}
|
|
194
|
+
let dates = []
|
|
195
|
+
while (count) {
|
|
196
|
+
date = this.getDate(
|
|
197
|
+
date.getFullYear(),
|
|
198
|
+
date.getMonth(),
|
|
199
|
+
date.getDate() + incrementer
|
|
200
|
+
)
|
|
201
|
+
dates.push(date)
|
|
202
|
+
count--
|
|
203
|
+
}
|
|
204
|
+
if (incrementer === -1) {
|
|
205
|
+
return dates.reverse()
|
|
206
|
+
}
|
|
207
|
+
return dates
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
getDaysInMonth(monthIndex, year) {
|
|
211
|
+
let daysInMonthMap = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
212
|
+
let daysInMonth = daysInMonthMap[monthIndex]
|
|
213
|
+
if (monthIndex === 1 && this.isLeapYear(year)) {
|
|
214
|
+
return 29
|
|
215
|
+
}
|
|
216
|
+
return daysInMonth
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
isLeapYear(year) {
|
|
220
|
+
if (year % 400 === 0) return true
|
|
221
|
+
if (year % 100 === 0) return false
|
|
222
|
+
if (year % 4 === 0) return true
|
|
223
|
+
return false
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
toValue(date) {
|
|
227
|
+
if (!date) {
|
|
228
|
+
return ''
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// toISOString is buggy and reduces the day by one
|
|
232
|
+
// this is because it considers the UTC timestamp
|
|
233
|
+
// in order to circumvent that we need to use luxon/moment
|
|
234
|
+
// but that refactor could take some time, so fixing the time difference
|
|
235
|
+
// as suggested in this answer.
|
|
236
|
+
// https://stackoverflow.com/a/16084846/3541205
|
|
237
|
+
date.setHours(0, -date.getTimezoneOffset(), 0, 0)
|
|
238
|
+
return date.toISOString().slice(0, 10)
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
getDate(...args) {
|
|
242
|
+
let d = new Date(...args)
|
|
243
|
+
return d
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
}
|
|
247
|
+
</script>
|