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.
Files changed (93) hide show
  1. package/license.md +0 -0
  2. package/package.json +75 -0
  3. package/readme.md +0 -0
  4. package/src/components/Alert.vue +57 -0
  5. package/src/components/Autocomplete.vue +176 -0
  6. package/src/components/Avatar.vue +62 -0
  7. package/src/components/Badge.vue +42 -0
  8. package/src/components/Button.vue +155 -0
  9. package/src/components/Card.vue +37 -0
  10. package/src/components/DatePicker.vue +252 -0
  11. package/src/components/Dialog.vue +216 -0
  12. package/src/components/Dropdown.vue +145 -0
  13. package/src/components/ErrorMessage.vue +24 -0
  14. package/src/components/FeatherIcon.vue +59 -0
  15. package/src/components/FileUploader.vue +220 -0
  16. package/src/components/GreenCheckIcon.vue +16 -0
  17. package/src/components/Input.vue +214 -0
  18. package/src/components/Link.vue +28 -0
  19. package/src/components/ListItem.vue +28 -0
  20. package/src/components/LoadingIndicator.vue +27 -0
  21. package/src/components/LoadingText.vue +21 -0
  22. package/src/components/Popover.vue +253 -0
  23. package/src/components/Resource.vue +21 -0
  24. package/src/components/TextEditor/FontColor.vue +108 -0
  25. package/src/components/TextEditor/InsertImage.vue +74 -0
  26. package/src/components/TextEditor/InsertLink.vue +67 -0
  27. package/src/components/TextEditor/InsertVideo.vue +94 -0
  28. package/src/components/TextEditor/MentionList.vue +95 -0
  29. package/src/components/TextEditor/Menu.vue +99 -0
  30. package/src/components/TextEditor/TextEditor.vue +275 -0
  31. package/src/components/TextEditor/TextEditorBubbleMenu.vue +69 -0
  32. package/src/components/TextEditor/TextEditorFixedMenu.vue +72 -0
  33. package/src/components/TextEditor/TextEditorFloatingMenu.vue +55 -0
  34. package/src/components/TextEditor/commands.js +272 -0
  35. package/src/components/TextEditor/icons/align-center.vue +14 -0
  36. package/src/components/TextEditor/icons/align-justify.vue +14 -0
  37. package/src/components/TextEditor/icons/align-left.vue +14 -0
  38. package/src/components/TextEditor/icons/align-right.vue +14 -0
  39. package/src/components/TextEditor/icons/arrow-go-back-line.vue +14 -0
  40. package/src/components/TextEditor/icons/arrow-go-forward-line.vue +14 -0
  41. package/src/components/TextEditor/icons/bold.vue +14 -0
  42. package/src/components/TextEditor/icons/code-view.vue +14 -0
  43. package/src/components/TextEditor/icons/double-quotes-r.vue +14 -0
  44. package/src/components/TextEditor/icons/font-color.vue +14 -0
  45. package/src/components/TextEditor/icons/format-clear.vue +14 -0
  46. package/src/components/TextEditor/icons/h-1.vue +14 -0
  47. package/src/components/TextEditor/icons/h-2.vue +14 -0
  48. package/src/components/TextEditor/icons/h-3.vue +14 -0
  49. package/src/components/TextEditor/icons/h-4.vue +14 -0
  50. package/src/components/TextEditor/icons/h-5.vue +14 -0
  51. package/src/components/TextEditor/icons/h-6.vue +14 -0
  52. package/src/components/TextEditor/icons/image-add-line.vue +14 -0
  53. package/src/components/TextEditor/icons/italic.vue +14 -0
  54. package/src/components/TextEditor/icons/link.vue +14 -0
  55. package/src/components/TextEditor/icons/list-ordered.vue +14 -0
  56. package/src/components/TextEditor/icons/list-unordered.vue +14 -0
  57. package/src/components/TextEditor/icons/readme.md +1 -0
  58. package/src/components/TextEditor/icons/separator.vue +14 -0
  59. package/src/components/TextEditor/icons/strikethrough.vue +14 -0
  60. package/src/components/TextEditor/icons/table-2.vue +14 -0
  61. package/src/components/TextEditor/icons/text.vue +11 -0
  62. package/src/components/TextEditor/icons/underline.vue +14 -0
  63. package/src/components/TextEditor/icons/video-add-line.vue +14 -0
  64. package/src/components/TextEditor/image-extension.js +152 -0
  65. package/src/components/TextEditor/index.js +6 -0
  66. package/src/components/TextEditor/mention.js +72 -0
  67. package/src/components/TextEditor/utils.js +11 -0
  68. package/src/components/TextEditor/video-extension.js +60 -0
  69. package/src/components/Toast.vue +83 -0
  70. package/src/components/Tooltip.vue +36 -0
  71. package/src/components/toast.js +98 -0
  72. package/src/directives/onOutsideClick.js +34 -0
  73. package/src/directives/visibility.js +24 -0
  74. package/src/index.js +56 -0
  75. package/src/resources/documentResource.js +194 -0
  76. package/src/resources/index.js +4 -0
  77. package/src/resources/listResource.js +312 -0
  78. package/src/resources/local.js +16 -0
  79. package/src/resources/plugin.js +105 -0
  80. package/src/resources/resources.js +215 -0
  81. package/src/style.css +15 -0
  82. package/src/utils/call.js +98 -0
  83. package/src/utils/config.js +9 -0
  84. package/src/utils/debounce.js +15 -0
  85. package/src/utils/file-to-base64.js +9 -0
  86. package/src/utils/markdown.js +29 -0
  87. package/src/utils/melonRequest.js +105 -0
  88. package/src/utils/pageMeta.js +52 -0
  89. package/src/utils/plugin.js +24 -0
  90. package/src/utils/request.js +49 -0
  91. package/src/utils/socketio.js +11 -0
  92. package/src/utils/tailwind.config.js +117 -0
  93. package/src/utils/vite-dev-server.js +14 -0
@@ -0,0 +1,252 @@
1
+ <template>
2
+ <Popover @open="selectCurrentMonthYear" transition="default">
3
+ <template #target="{ togglePopover }">
4
+ <Input
5
+ type="text"
6
+ icon-left="calendar"
7
+ :class="inputClass"
8
+ :value="
9
+ modelValue && formatValue ? formatValue(modelValue) : modelValue || ''
10
+ "
11
+ :placeholder="placeholder"
12
+ @focus="!readonly ? togglePopover() : null"
13
+ readonly
14
+ />
15
+ </template>
16
+ <template #body-main="{ togglePopover }">
17
+ <div class="mt-1 select-none p-3 text-left">
18
+ <div class="flex items-center justify-between">
19
+ <span class="text-base font-medium text-blue-500">
20
+ {{ formatMonth }}
21
+ </span>
22
+ <span class="flex">
23
+ <div
24
+ class="grid h-5 w-5 cursor-pointer place-items-center rounded-md hover:bg-gray-100"
25
+ >
26
+ <FeatherIcon
27
+ @click="prevMonth"
28
+ name="chevron-left"
29
+ class="h-4 w-4"
30
+ />
31
+ </div>
32
+ <div
33
+ class="ml-2 grid h-5 w-5 cursor-pointer place-items-center rounded-md hover:bg-gray-100"
34
+ >
35
+ <FeatherIcon
36
+ @click="nextMonth"
37
+ name="chevron-right"
38
+ class="h-4 w-4"
39
+ />
40
+ </div>
41
+ </span>
42
+ </div>
43
+ <div class="mt-2 text-sm">
44
+ <div class="grid w-full grid-cols-7 place-items-center text-gray-600">
45
+ <div
46
+ class="grid h-6 w-6 place-items-center gap-1 text-center"
47
+ v-for="(d, i) in ['S', 'M', 'T', 'W', 'T', 'F', 'S']"
48
+ :key="i"
49
+ >
50
+ {{ d }}
51
+ </div>
52
+ </div>
53
+ <div v-for="(week, i) in datesAsWeeks" :key="i" class="mt-1">
54
+ <div class="grid w-full grid-cols-7 place-items-center gap-1">
55
+ <div
56
+ v-for="date in week"
57
+ :key="toValue(date)"
58
+ class="grid h-6 w-6 cursor-pointer place-items-center rounded-md hover:bg-blue-100 hover:text-blue-700"
59
+ :class="{
60
+ 'text-gray-600': date.getMonth() !== currentMonth - 1,
61
+ 'text-blue-500': toValue(date) === toValue(today),
62
+ 'bg-blue-100 font-semibold text-blue-500':
63
+ toValue(date) === modelValue,
64
+ }"
65
+ @click="
66
+ () => {
67
+ selectDate(date)
68
+ togglePopover()
69
+ }
70
+ "
71
+ >
72
+ {{ date.getDate() }}
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ <div class="mt-2 flex w-full justify-end">
78
+ <div
79
+ class="cursor-pointer rounded-md px-2 py-1 text-sm hover:bg-gray-100"
80
+ @click="
81
+ () => {
82
+ selectDate('')
83
+ togglePopover()
84
+ }
85
+ "
86
+ >
87
+ Clear
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </template>
92
+ </Popover>
93
+ </template>
94
+
95
+ <script>
96
+ import Input from './Input.vue'
97
+ import FeatherIcon from './FeatherIcon.vue'
98
+ import Popover from './Popover.vue'
99
+
100
+ export default {
101
+ name: 'DatePicker',
102
+ props: ['modelValue', 'placeholder', 'readonly', 'formatValue', 'inputClass'],
103
+ emits: ['update:modelValue'],
104
+ components: {
105
+ Input,
106
+ FeatherIcon,
107
+ Popover,
108
+ },
109
+ data() {
110
+ return {
111
+ currentYear: null,
112
+ currentMonth: null,
113
+ }
114
+ },
115
+ created() {
116
+ this.selectCurrentMonthYear()
117
+ },
118
+ computed: {
119
+ today() {
120
+ return this.getDate()
121
+ },
122
+ datesAsWeeks() {
123
+ let datesAsWeeks = []
124
+ let dates = this.dates.slice()
125
+ while (dates.length) {
126
+ let week = dates.splice(0, 7)
127
+ datesAsWeeks.push(week)
128
+ }
129
+ return datesAsWeeks
130
+ },
131
+ dates() {
132
+ if (!(this.currentYear && this.currentMonth)) {
133
+ return []
134
+ }
135
+ let monthIndex = this.currentMonth - 1
136
+ let year = this.currentYear
137
+
138
+ let firstDayOfMonth = this.getDate(year, monthIndex, 1)
139
+ let lastDayOfMonth = this.getDate(year, monthIndex + 1, 0)
140
+ let leftPaddingCount = firstDayOfMonth.getDay()
141
+ let rightPaddingCount = 6 - lastDayOfMonth.getDay()
142
+
143
+ let leftPadding = this.getDatesAfter(firstDayOfMonth, -leftPaddingCount)
144
+ let rightPadding = this.getDatesAfter(lastDayOfMonth, rightPaddingCount)
145
+ let daysInMonth = this.getDaysInMonth(monthIndex, year)
146
+ let datesInMonth = this.getDatesAfter(firstDayOfMonth, daysInMonth - 1)
147
+
148
+ let dates = [
149
+ ...leftPadding,
150
+ firstDayOfMonth,
151
+ ...datesInMonth,
152
+ ...rightPadding,
153
+ ]
154
+ if (dates.length < 42) {
155
+ const finalPadding = this.getDatesAfter(dates.at(-1), 42 - dates.length)
156
+ dates = dates.concat(...finalPadding)
157
+ }
158
+ return dates
159
+ },
160
+ formatMonth() {
161
+ let date = this.getDate(this.currentYear, this.currentMonth - 1, 1)
162
+ return date.toLocaleString('en-US', { month: 'short', year: 'numeric' })
163
+ },
164
+ },
165
+ methods: {
166
+ selectDate(date) {
167
+ this.$emit('update:modelValue', this.toValue(date))
168
+ },
169
+ selectCurrentMonthYear() {
170
+ let date = this.modelValue
171
+ ? this.getDate(this.modelValue)
172
+ : this.getDate()
173
+ this.currentYear = date.getFullYear()
174
+ this.currentMonth = date.getMonth() + 1
175
+ },
176
+ prevMonth() {
177
+ this.changeMonth(-1)
178
+ },
179
+ nextMonth() {
180
+ this.changeMonth(1)
181
+ },
182
+ changeMonth(adder) {
183
+ this.currentMonth = this.currentMonth + adder
184
+ if (this.currentMonth < 1) {
185
+ this.currentMonth = 12
186
+ this.currentYear = this.currentYear - 1
187
+ }
188
+ if (this.currentMonth > 12) {
189
+ this.currentMonth = 1
190
+ this.currentYear = this.currentYear + 1
191
+ }
192
+ },
193
+ getDatesAfter(date, count) {
194
+ let incrementer = 1
195
+ if (count < 0) {
196
+ incrementer = -1
197
+ count = Math.abs(count)
198
+ }
199
+ let dates = []
200
+ while (count) {
201
+ date = this.getDate(
202
+ date.getFullYear(),
203
+ date.getMonth(),
204
+ date.getDate() + incrementer
205
+ )
206
+ dates.push(date)
207
+ count--
208
+ }
209
+ if (incrementer === -1) {
210
+ return dates.reverse()
211
+ }
212
+ return dates
213
+ },
214
+
215
+ getDaysInMonth(monthIndex, year) {
216
+ let daysInMonthMap = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
217
+ let daysInMonth = daysInMonthMap[monthIndex]
218
+ if (monthIndex === 1 && this.isLeapYear(year)) {
219
+ return 29
220
+ }
221
+ return daysInMonth
222
+ },
223
+
224
+ isLeapYear(year) {
225
+ if (year % 400 === 0) return true
226
+ if (year % 100 === 0) return false
227
+ if (year % 4 === 0) return true
228
+ return false
229
+ },
230
+
231
+ toValue(date) {
232
+ if (!date) {
233
+ return ''
234
+ }
235
+
236
+ // toISOString is buggy and reduces the day by one
237
+ // this is because it considers the UTC timestamp
238
+ // in order to circumvent that we need to use luxon/moment
239
+ // but that refactor could take some time, so fixing the time difference
240
+ // as suggested in this answer.
241
+ // https://stackoverflow.com/a/16084846/3541205
242
+ date.setHours(0, -date.getTimezoneOffset(), 0, 0)
243
+ return date.toISOString().slice(0, 10)
244
+ },
245
+
246
+ getDate(...args) {
247
+ let d = new Date(...args)
248
+ return d
249
+ },
250
+ },
251
+ }
252
+ </script>
@@ -0,0 +1,216 @@
1
+ <template>
2
+ <TransitionRoot
3
+ as="template"
4
+ :show="open"
5
+ @after-leave="$emit('after-leave')"
6
+ >
7
+ <HDialog
8
+ as="div"
9
+ class="fixed inset-0 z-10 overflow-y-auto"
10
+ @close="open = false"
11
+ >
12
+ <div
13
+ class="flex min-h-screen flex-col items-center px-4 py-4 text-center"
14
+ :class="dialogPositionClasses"
15
+ >
16
+ <TransitionChild
17
+ as="template"
18
+ enter="ease-out duration-300"
19
+ enter-from="opacity-0"
20
+ enter-to="opacity-100"
21
+ leave="ease-in duration-200"
22
+ leave-from="opacity-100"
23
+ leave-to="opacity-0"
24
+ >
25
+ <DialogOverlay
26
+ class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
27
+ />
28
+ </TransitionChild>
29
+
30
+ <TransitionChild
31
+ as="template"
32
+ enter="ease-out duration-300"
33
+ enter-from="opacity-0 translate-y-4 sm:-translate-y-12 sm:scale-95"
34
+ enter-to="opacity-100 translate-y-0 sm:scale-100"
35
+ leave="ease-in duration-200"
36
+ leave-from="opacity-100 translate-y-0 sm:scale-100"
37
+ leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
38
+ >
39
+ <div
40
+ class="my-8 inline-block w-full transform overflow-hidden rounded-lg bg-white text-left align-middle shadow-xl transition-all"
41
+ :class="{
42
+ 'max-w-7xl': options.size === '7xl',
43
+ 'max-w-6xl': options.size === '6xl',
44
+ 'max-w-5xl': options.size === '5xl',
45
+ 'max-w-4xl': options.size === '4xl',
46
+ 'max-w-3xl': options.size === '3xl',
47
+ 'max-w-2xl': options.size === '2xl',
48
+ 'max-w-xl': options.size === 'xl',
49
+ 'max-w-lg': options.size === 'lg' || !options.size,
50
+ 'max-w-md': options.size === 'md',
51
+ 'max-w-sm': options.size === 'sm',
52
+ 'max-w-xs': options.size === 'xs',
53
+ }"
54
+ >
55
+ <slot name="body">
56
+ <slot name="body-main">
57
+ <div class="bg-white px-4 py-5 sm:p-6">
58
+ <div class="flex flex-col sm:flex-row">
59
+ <div
60
+ v-if="icon"
61
+ class="mx-auto mb-3 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:mb-0 sm:mr-4 sm:h-9 sm:w-9"
62
+ :class="{
63
+ 'bg-gray-100': !icon.appearance,
64
+ 'bg-yellow-100': icon.appearance === 'warning',
65
+ 'bg-blue-100': icon.appearance === 'info',
66
+ 'bg-red-100': icon.appearance === 'danger',
67
+ 'bg-green-100': icon.appearance === 'success',
68
+ }"
69
+ >
70
+ <FeatherIcon
71
+ :name="icon.name"
72
+ class="h-6 w-6 sm:h-5 sm:w-5"
73
+ :class="{
74
+ 'text-gray-600': !icon.appearance,
75
+ 'text-yellow-600': icon.appearance === 'warning',
76
+ 'text-blue-600': icon.appearance === 'info',
77
+ 'text-red-600': icon.appearance === 'danger',
78
+ 'text-green-600': icon.appearance === 'success',
79
+ }"
80
+ aria-hidden="true"
81
+ />
82
+ </div>
83
+ <div class="flex-1">
84
+ <DialogTitle as="header">
85
+ <slot name="body-title">
86
+ <h3
87
+ class="mb-2 text-lg font-medium leading-6 text-gray-900"
88
+ >
89
+ {{ options.title || 'Untitled' }}
90
+ </h3>
91
+ </slot>
92
+ </DialogTitle>
93
+
94
+ <slot name="body-content">
95
+ <p
96
+ class="text-base text-gray-600"
97
+ v-if="options.message"
98
+ >
99
+ {{ options.message }}
100
+ </p>
101
+ </slot>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </slot>
106
+ <div
107
+ class="space-y-2 bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:space-x-3 sm:space-y-0 sm:space-x-reverse sm:px-6"
108
+ v-if="options?.actions || $slots.actions"
109
+ >
110
+ <slot name="actions" v-bind="{ close: () => (open = false) }">
111
+ <Button
112
+ class="w-full sm:w-max"
113
+ v-for="action in options.actions"
114
+ :key="action.label"
115
+ :loading="action.loading"
116
+ v-bind="action"
117
+ @click="handleAction(action)"
118
+ >
119
+ {{ action.label }}
120
+ </Button>
121
+ </slot>
122
+ </div>
123
+ </slot>
124
+ </div>
125
+ </TransitionChild>
126
+ </div>
127
+ </HDialog>
128
+ </TransitionRoot>
129
+ </template>
130
+
131
+ <script>
132
+ import { computed } from 'vue'
133
+ import {
134
+ Dialog as HDialog,
135
+ DialogOverlay,
136
+ DialogTitle,
137
+ TransitionChild,
138
+ TransitionRoot,
139
+ } from '@headlessui/vue'
140
+ import Button from './Button.vue'
141
+ import FeatherIcon from './FeatherIcon.vue'
142
+
143
+ export default {
144
+ name: 'Dialog',
145
+ props: {
146
+ modelValue: {
147
+ type: Boolean,
148
+ required: true,
149
+ },
150
+ options: {
151
+ type: Object,
152
+ default() {
153
+ return {}
154
+ },
155
+ },
156
+ },
157
+ emits: ['update:modelValue', 'close', 'after-leave'],
158
+ components: {
159
+ HDialog,
160
+ DialogOverlay,
161
+ DialogTitle,
162
+ TransitionChild,
163
+ TransitionRoot,
164
+ Button,
165
+ FeatherIcon,
166
+ },
167
+ setup(props, { emit }) {
168
+ let open = computed({
169
+ get: () => props.modelValue,
170
+ set: (val) => {
171
+ emit('update:modelValue', val)
172
+ if (!val) {
173
+ emit('close')
174
+ }
175
+ },
176
+ })
177
+ return {
178
+ open,
179
+ }
180
+ },
181
+ methods: {
182
+ handleAction(action) {
183
+ let close = () => (this.open = false)
184
+ if (action.handler && typeof action.handler === 'function') {
185
+ action.loading = true
186
+ let result = action.handler({ close })
187
+ if (result && result.then) {
188
+ result.then(() => (action.loading = false))
189
+ } else {
190
+ action.loading = false
191
+ }
192
+ } else {
193
+ close()
194
+ }
195
+ },
196
+ },
197
+ computed: {
198
+ icon() {
199
+ if (!this.options?.icon) return null
200
+
201
+ let icon = this.options.icon
202
+ if (typeof icon === 'string') {
203
+ icon = { name: icon }
204
+ }
205
+ return icon
206
+ },
207
+ dialogPositionClasses() {
208
+ let position = this.options?.position || 'center'
209
+ return {
210
+ 'justify-center': position === 'center',
211
+ 'pt-[20vh]': position === 'top',
212
+ }
213
+ },
214
+ },
215
+ }
216
+ </script>
@@ -0,0 +1,145 @@
1
+ <template>
2
+ <Menu as="div" class="relative inline-block text-left" v-slot="{ open }">
3
+ <Popover :transition="dropdownTransition" :show="open">
4
+ <template #target>
5
+ <MenuButton as="div">
6
+ <slot v-if="$slots.default" v-bind="{ open }" />
7
+ <Button v-else :active="open" v-bind="button">
8
+ {{ button ? button?.label || null : 'Options' }}
9
+ </Button>
10
+ </MenuButton>
11
+ </template>
12
+
13
+ <template #body>
14
+ <MenuItems
15
+ class="min-w-40 mt-2 divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
16
+ :class="{
17
+ 'left-0 origin-top-left': placement == 'left',
18
+ 'right-0 origin-top-right': placement == 'right',
19
+ 'inset-x-0 origin-top': placement == 'center',
20
+ }"
21
+ >
22
+ <div v-for="group in groups" :key="group.key" class="px-1 py-1">
23
+ <div
24
+ v-if="group.group && !group.hideLabel"
25
+ class="px-2 py-1 text-xs font-semibold uppercase tracking-wider text-gray-500"
26
+ >
27
+ {{ group.group }}
28
+ </div>
29
+ <MenuItem
30
+ v-for="item in group.items"
31
+ :key="item.label"
32
+ v-slot="{ active }"
33
+ >
34
+ <component
35
+ v-if="item.component"
36
+ :is="item.component"
37
+ :active="active"
38
+ />
39
+ <button
40
+ v-else
41
+ :class="[
42
+ active ? 'bg-gray-100' : 'text-gray-900',
43
+ 'group flex w-full items-center rounded-md px-2 py-2 text-sm',
44
+ ]"
45
+ @click="item.onClick"
46
+ >
47
+ <FeatherIcon
48
+ v-if="item.icon"
49
+ :name="item.icon"
50
+ class="mr-2 h-4 w-4 flex-shrink-0 text-gray-500"
51
+ aria-hidden="true"
52
+ />
53
+ <span class="whitespace-nowrap">
54
+ {{ item.label }}
55
+ </span>
56
+ </button>
57
+ </MenuItem>
58
+ </div>
59
+ </MenuItems>
60
+ </template>
61
+ </Popover>
62
+ </Menu>
63
+ </template>
64
+
65
+ <script>
66
+ import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
67
+ import Popover from './Popover.vue'
68
+ import Button from './Button.vue'
69
+ import FeatherIcon from './FeatherIcon.vue'
70
+
71
+ export default {
72
+ name: 'Dropdown',
73
+ props: {
74
+ button: {
75
+ type: Object,
76
+ default: null,
77
+ },
78
+ options: {
79
+ type: Array,
80
+ default: () => [],
81
+ },
82
+ placement: {
83
+ type: String,
84
+ default: 'left',
85
+ },
86
+ },
87
+ components: {
88
+ Menu,
89
+ MenuButton,
90
+ MenuItems,
91
+ MenuItem,
92
+ Button,
93
+ FeatherIcon,
94
+ Popover,
95
+ },
96
+ methods: {
97
+ normalizeDropdownItem(option) {
98
+ let onClick = option.handler || null
99
+ if (!onClick && option.route && this.$router) {
100
+ onClick = () => this.$router.push(option.route)
101
+ }
102
+
103
+ return {
104
+ label: option.label,
105
+ icon: option.icon,
106
+ group: option.group,
107
+ component: option.component,
108
+ onClick,
109
+ }
110
+ },
111
+ filterOptions(options) {
112
+ return (options || [])
113
+ .filter(Boolean)
114
+ .filter((option) => (option.condition ? option.condition() : true))
115
+ .map((option) => this.normalizeDropdownItem(option))
116
+ },
117
+ },
118
+ computed: {
119
+ groups() {
120
+ let groups = this.options[0]?.group
121
+ ? this.options
122
+ : [{ group: '', items: this.options }]
123
+
124
+ return groups.map((group, i) => {
125
+ return {
126
+ key: i,
127
+ group: group.group,
128
+ hideLabel: group.hideLabel || false,
129
+ items: this.filterOptions(group.items),
130
+ }
131
+ })
132
+ },
133
+ dropdownTransition() {
134
+ return {
135
+ enterActiveClass: 'transition duration-100 ease-out',
136
+ enterFromClass: 'transform scale-95 opacity-0',
137
+ enterToClass: 'transform scale-100 opacity-100',
138
+ leaveActiveClass: 'transition duration-75 ease-in',
139
+ leaveFromClass: 'transform scale-100 opacity-100',
140
+ leaveToClass: 'transform scale-95 opacity-0',
141
+ }
142
+ },
143
+ },
144
+ }
145
+ </script>
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <div
3
+ v-if="message"
4
+ class="whitespace-pre-line text-sm text-red-600"
5
+ role="alert"
6
+ v-html="errorMessage"
7
+ ></div>
8
+ </template>
9
+
10
+ <script>
11
+ export default {
12
+ name: 'ErrorMessage',
13
+ props: ['message'],
14
+ computed: {
15
+ errorMessage() {
16
+ if (!this.message) return ''
17
+ if (this.message instanceof Error) {
18
+ return this.message.messages || this.message.message
19
+ }
20
+ return this.message
21
+ },
22
+ },
23
+ }
24
+ </script>
@@ -0,0 +1,59 @@
1
+ <script>
2
+ import { h, mergeProps } from 'vue'
3
+ import feather from 'feather-icons'
4
+
5
+ const validIcons = Object.keys(feather.icons)
6
+
7
+ export default {
8
+ props: {
9
+ name: {
10
+ type: String,
11
+ required: true,
12
+ validator(value) {
13
+ const valid = validIcons.includes(value)
14
+ if (!valid) {
15
+ console.groupCollapsed(
16
+ '[pimelon-ui] name property for feather-icon must be one of '
17
+ )
18
+ console.dir(validIcons)
19
+ console.groupEnd()
20
+ }
21
+ return valid
22
+ },
23
+ },
24
+ color: {
25
+ type: String,
26
+ default: null,
27
+ },
28
+ strokeWidth: {
29
+ type: Number,
30
+ default: 1.5,
31
+ },
32
+ },
33
+ render() {
34
+ let icon = feather.icons[this.name]
35
+ if (!icon) {
36
+ icon = feather.icons['circle']
37
+ }
38
+ return h(
39
+ 'svg',
40
+ mergeProps(
41
+ icon.attrs,
42
+ {
43
+ fill: 'none',
44
+ stroke: 'currentColor',
45
+ color: this.color,
46
+ 'stroke-linecap': 'round',
47
+ 'stroke-linejoin': 'round',
48
+ 'stroke-width': this.strokeWidth,
49
+ width: null,
50
+ height: null,
51
+ class: [icon.attrs.class, 'shrink-0'],
52
+ innerHTML: icon.contents,
53
+ },
54
+ this.$attrs
55
+ )
56
+ )
57
+ },
58
+ }
59
+ </script>