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.
Files changed (39) hide show
  1. package/license.md +0 -0
  2. package/package.json +33 -0
  3. package/readme.md +0 -0
  4. package/src/components/Alert.vue +57 -0
  5. package/src/components/Avatar.vue +61 -0
  6. package/src/components/Badge.vue +40 -0
  7. package/src/components/Button.vue +134 -0
  8. package/src/components/Card.vue +37 -0
  9. package/src/components/Dialog.vue +195 -0
  10. package/src/components/Dropdown.vue +113 -0
  11. package/src/components/ErrorMessage.vue +15 -0
  12. package/src/components/FeatherIcon.vue +55 -0
  13. package/src/components/FileUploader.vue +220 -0
  14. package/src/components/GreenCheckIcon.vue +16 -0
  15. package/src/components/Input.vue +169 -0
  16. package/src/components/Link.vue +28 -0
  17. package/src/components/ListItem.vue +28 -0
  18. package/src/components/LoadingIndicator.vue +12 -0
  19. package/src/components/LoadingText.vue +21 -0
  20. package/src/components/Modal.vue +67 -0
  21. package/src/components/Popover.vue +192 -0
  22. package/src/components/Resource.vue +21 -0
  23. package/src/components/Spinner.vue +27 -0
  24. package/src/components/SuccessMessage.vue +15 -0
  25. package/src/components/TextEditor/Menu.vue +32 -0
  26. package/src/components/TextEditor/TextEditor.vue +193 -0
  27. package/src/components/TextEditor/commands.js +79 -0
  28. package/src/components/TextEditor/index.js +1 -0
  29. package/src/components/Toast.vue +167 -0
  30. package/src/directives/onOutsideClick.js +28 -0
  31. package/src/index.js +33 -0
  32. package/src/style.css +15 -0
  33. package/src/utils/call.js +98 -0
  34. package/src/utils/debounce.js +15 -0
  35. package/src/utils/plugin.js +24 -0
  36. package/src/utils/resources.js +510 -0
  37. package/src/utils/socketio.js +9 -0
  38. package/src/utils/tailwind.config.js +110 -0
  39. package/src/utils/vite-dev-server.js +14 -0
@@ -0,0 +1,192 @@
1
+ <template>
2
+ <div ref="reference">
3
+ <div
4
+ class="h-full"
5
+ ref="target"
6
+ @click="updatePosition"
7
+ @focusin="updatePosition"
8
+ @keydown="updatePosition"
9
+ >
10
+ <slot
11
+ name="target"
12
+ v-bind="{ togglePopover, updatePosition, open, close }"
13
+ ></slot>
14
+ </div>
15
+ <teleport to="#melonui-popper-root">
16
+ <div
17
+ ref="popover"
18
+ :class="popoverClass"
19
+ class="relative z-[100] popover-container"
20
+ :style="{ minWidth: targetWidth ? targetWidth + 'px' : null }"
21
+ >
22
+ <div v-if="!hideArrow" class="popover-arrow" ref="popover-arrow"></div>
23
+ <slot
24
+ name="content"
25
+ v-bind="{ togglePopover, updatePosition, open, close }"
26
+ ></slot>
27
+ </div>
28
+ </teleport>
29
+ </div>
30
+ </template>
31
+
32
+ <script>
33
+ import { createPopper } from '@popperjs/core'
34
+
35
+ export default {
36
+ name: 'Popover',
37
+ props: {
38
+ hideArrow: {
39
+ type: Boolean,
40
+ default: true,
41
+ },
42
+ show: {
43
+ default: null,
44
+ },
45
+ right: Boolean,
46
+ placement: {
47
+ type: String,
48
+ default: 'bottom-start',
49
+ },
50
+ popoverClass: [String, Object, Array],
51
+ },
52
+ emits: ['init', 'open', 'close'],
53
+ watch: {
54
+ show: {
55
+ immediate: true,
56
+ handler(val) {
57
+ if (val) {
58
+ this.open()
59
+ } else {
60
+ this.close()
61
+ }
62
+ },
63
+ },
64
+ },
65
+ data() {
66
+ return {
67
+ isOpen: false,
68
+ targetWidth: null,
69
+ }
70
+ },
71
+ created() {
72
+ if (!document.getElementById('melonui-popper-root')) {
73
+ const root = document.createElement('div')
74
+ root.id = 'melonui-popper-root'
75
+ document.body.appendChild(root)
76
+ }
77
+ },
78
+ mounted() {
79
+ this.listener = (e) => {
80
+ let $els = [this.$refs.reference, this.$refs.popover]
81
+ let insideClick = $els.some(
82
+ ($el) => $el && (e.target === $el || $el.contains(e.target))
83
+ )
84
+ if (insideClick) {
85
+ return
86
+ }
87
+ this.close()
88
+ }
89
+ if (this.show == null) {
90
+ document.addEventListener('click', this.listener)
91
+ }
92
+ this.$nextTick(() => {
93
+ this.targetWidth = this.$refs['target'].clientWidth
94
+ })
95
+ },
96
+ beforeDestroy() {
97
+ this.popper && this.popper.destroy()
98
+ document.removeEventListener('click', this.listener)
99
+ },
100
+ methods: {
101
+ setupPopper() {
102
+ if (!this.popper) {
103
+ this.popper = createPopper(this.$refs.reference, this.$refs.popover, {
104
+ placement: this.placement,
105
+ modifiers: !this.hideArrow
106
+ ? [
107
+ {
108
+ name: 'arrow',
109
+ options: {
110
+ element: this.$refs['popover-arrow'],
111
+ },
112
+ },
113
+ {
114
+ name: 'offset',
115
+ options: {
116
+ offset: [0, 10],
117
+ },
118
+ },
119
+ ]
120
+ : [],
121
+ })
122
+ } else {
123
+ this.updatePosition()
124
+ }
125
+ this.$emit('init')
126
+ },
127
+ updatePosition() {
128
+ this.popper && this.popper.update()
129
+ },
130
+ togglePopover(flag) {
131
+ if (flag == null) {
132
+ flag = !this.isOpen
133
+ }
134
+ flag = Boolean(flag)
135
+ if (flag) {
136
+ this.open()
137
+ } else {
138
+ this.close()
139
+ }
140
+ },
141
+ open() {
142
+ if (this.isOpen) {
143
+ return
144
+ }
145
+ this.isOpen = true
146
+ this.$nextTick(() => this.setupPopper())
147
+ this.$emit('open')
148
+ },
149
+ close() {
150
+ if (!this.isOpen) {
151
+ return
152
+ }
153
+ this.isOpen = false
154
+ this.$emit('close')
155
+ },
156
+ },
157
+ }
158
+ </script>
159
+ <style scoped>
160
+ .popover-arrow,
161
+ .popover-arrow::after {
162
+ position: absolute;
163
+ width: theme('spacing.4');
164
+ height: theme('spacing.4');
165
+ z-index: -1;
166
+ }
167
+
168
+ .popover-arrow::after {
169
+ content: '';
170
+ background: white;
171
+ transform: rotate(45deg);
172
+ border-top: 1px solid theme('borderColor.gray.400');
173
+ border-left: 1px solid theme('borderColor.gray.400');
174
+ border-top-left-radius: 6px;
175
+ }
176
+
177
+ .popover-container[data-popper-placement^='top'] > .popover-arrow {
178
+ bottom: calc(theme('spacing.2') * -1);
179
+ }
180
+
181
+ .popover-container[data-popper-placement^='bottom'] > .popover-arrow {
182
+ top: calc(theme('spacing.2') * -1);
183
+ }
184
+
185
+ .popover-container[data-popper-placement^='left'] > .popover-arrow {
186
+ right: calc(theme('spacing.2') * -1);
187
+ }
188
+
189
+ .popover-container[data-popper-placement^='right'] > .popover-arrow {
190
+ left: calc(theme('spacing.2') * -1);
191
+ }
192
+ </style>
@@ -0,0 +1,21 @@
1
+ <script>
2
+ export default {
3
+ name: 'Resource',
4
+ props: ['options'],
5
+ resources: {
6
+ resource() {
7
+ return this.options
8
+ },
9
+ },
10
+ render() {
11
+ return this.$slots.default({
12
+ resource: this.$resources.resource,
13
+ data: this.$resources.resource.data,
14
+ error: this.$resources.resource.error,
15
+ loading: this.$resources.resource.loading,
16
+ fetch: (params) => this.$resources.resource.fetch(params),
17
+ submit: (params) => this.$resources.resource.submit(params),
18
+ })
19
+ },
20
+ }
21
+ </script>
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <svg
3
+ class="animate-spin"
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ fill="none"
6
+ viewBox="0 0 24 24"
7
+ >
8
+ <circle
9
+ class="opacity-25"
10
+ cx="12"
11
+ cy="12"
12
+ r="10"
13
+ stroke="currentColor"
14
+ stroke-width="4"
15
+ ></circle>
16
+ <path
17
+ class="opacity-75"
18
+ fill="currentColor"
19
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
20
+ ></path>
21
+ </svg>
22
+ </template>
23
+ <script>
24
+ export default {
25
+ name: 'Spinner',
26
+ }
27
+ </script>
@@ -0,0 +1,15 @@
1
+ <template>
2
+ <div
3
+ v-if="message"
4
+ class="text-sm text-green-600 whitespace-pre-line"
5
+ role="success"
6
+ v-html="message"
7
+ ></div>
8
+ </template>
9
+
10
+ <script>
11
+ export default {
12
+ name: 'SuccessMessage',
13
+ props: ['message'],
14
+ }
15
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <div class="inline-flex px-1 py-1 bg-white">
3
+ <div class="inline-flex items-center gap-1">
4
+ <template v-for="button in buttons" :key="button.label">
5
+ <div
6
+ class="border-l w-[2px] h-4"
7
+ v-if="button.type === 'separator'"
8
+ ></div>
9
+ <button
10
+ v-else
11
+ class="flex p-1 text-gray-800 transition-colors rounded"
12
+ :class="button.isActive(editor) ? 'bg-gray-100' : 'hover:bg-gray-100'"
13
+ @click="() => button.action(editor)"
14
+ :title="button.label"
15
+ >
16
+ <FeatherIcon v-if="button.icon" :name="button.icon" class="w-4" />
17
+ <span class="inline-block h-4 text-sm leading-4 min-w-[1rem]" v-else>
18
+ {{ button.text }}
19
+ </span>
20
+ </button>
21
+ </template>
22
+ </div>
23
+ </div>
24
+ </template>
25
+ <script>
26
+ import { FeatherIcon } from 'pimelon-ui'
27
+ export default {
28
+ name: 'TipTapMenu',
29
+ props: ['editor', 'buttons'],
30
+ components: { FeatherIcon },
31
+ }
32
+ </script>
@@ -0,0 +1,193 @@
1
+ <template>
2
+ <div class="relative w-full" :class="$attrs.class" v-if="editor">
3
+ <BubbleMenu
4
+ v-if="bubbleMenuButtons"
5
+ class="bubble-menu"
6
+ :tippy-options="{ duration: 100 }"
7
+ :editor="editor"
8
+ >
9
+ <Menu
10
+ :editor="editor"
11
+ class="border border-gray-100 rounded-md shadow-sm"
12
+ :buttons="bubbleMenuButtons"
13
+ />
14
+ </BubbleMenu>
15
+
16
+ <Menu
17
+ v-if="fixedMenuButtons"
18
+ class="w-full border rounded-t-lg border-gray-50 border-b-gray-100"
19
+ :editor="editor"
20
+ :buttons="fixedMenuButtons"
21
+ />
22
+
23
+ <FloatingMenu
24
+ v-if="floatingMenuButtons"
25
+ :tippy-options="{ duration: 100 }"
26
+ :editor="editor"
27
+ class="flex"
28
+ >
29
+ <button
30
+ v-for="button in floatingMenuButtons"
31
+ :key="button.label"
32
+ class="flex p-1 text-gray-800 transition-colors rounded"
33
+ :class="button.isActive(editor) ? 'bg-gray-100' : 'hover:bg-gray-100'"
34
+ @click="() => button.action(editor)"
35
+ :title="button.label"
36
+ >
37
+ <FeatherIcon v-if="button.icon" :name="button.icon" class="w-4" />
38
+ <span class="inline-block h-4 text-sm leading-4 min-w-[1rem]" v-else>
39
+ {{ button.text }}
40
+ </span>
41
+ </button>
42
+ </FloatingMenu>
43
+ <editor-content :editor="editor" />
44
+ </div>
45
+ </template>
46
+
47
+ <script>
48
+ import { Editor, EditorContent, BubbleMenu, FloatingMenu } from '@tiptap/vue-3'
49
+ import StarterKit from '@tiptap/starter-kit'
50
+ import Placeholder from '@tiptap/extension-placeholder'
51
+ import Image from '@tiptap/extension-image'
52
+ import Menu from './Menu.vue'
53
+ import commands from './commands'
54
+
55
+ export default {
56
+ name: 'TextEditor',
57
+ inheritAttrs: false,
58
+ components: {
59
+ EditorContent,
60
+ BubbleMenu,
61
+ FloatingMenu,
62
+ Menu,
63
+ },
64
+ props: [
65
+ 'content',
66
+ 'placeholder',
67
+ 'editorClass',
68
+ 'fixedMenu',
69
+ 'bubbleMenu',
70
+ 'floatingMenu',
71
+ 'extensions',
72
+ ],
73
+ emits: ['change'],
74
+ expose: ['editor'],
75
+ data() {
76
+ return {
77
+ editor: null,
78
+ }
79
+ },
80
+ mounted() {
81
+ this.editor = new Editor({
82
+ content: this.content || '<p></p>',
83
+ editorProps: {
84
+ attributes: {
85
+ class: ['prose prose-sm prose-p:my-1', this.editorClass].join(' '),
86
+ },
87
+ },
88
+ extensions: [
89
+ StarterKit,
90
+ Image,
91
+ Placeholder.configure({
92
+ placeholder: this.placeholder || 'Write something...',
93
+ }),
94
+ ...(this.extensions || []),
95
+ ],
96
+ onUpdate: ({ editor }) => {
97
+ this.$emit('change', editor.getHTML())
98
+ },
99
+ })
100
+ },
101
+ beforeUnmount() {
102
+ this.editor.destroy()
103
+ },
104
+ computed: {
105
+ fixedMenuButtons() {
106
+ if (!this.fixedMenu) return false
107
+
108
+ let buttons
109
+ if (Array.isArray(this.fixedMenu)) {
110
+ buttons = this.fixedMenu
111
+ } else {
112
+ buttons = [
113
+ 'Paragraph',
114
+ 'Heading 2',
115
+ 'Heading 3',
116
+ 'Separator',
117
+ 'Bold',
118
+ 'Italic',
119
+ 'Separator',
120
+ 'Bullet List',
121
+ 'Numbered List',
122
+ 'Blockquote',
123
+ 'Code',
124
+ 'Horizontal Rule',
125
+ 'Separator',
126
+ 'Undo',
127
+ 'Redo',
128
+ ]
129
+ }
130
+ return buttons.map(createEditorButton)
131
+ },
132
+ bubbleMenuButtons() {
133
+ if (!this.bubbleMenu) return false
134
+
135
+ let buttons
136
+ if (Array.isArray(this.bubbleMenu)) {
137
+ buttons = this.bubbleMenu
138
+ } else {
139
+ buttons = [
140
+ 'Paragraph',
141
+ 'Heading 2',
142
+ 'Heading 3',
143
+ 'Separator',
144
+ 'Bold',
145
+ 'Italic',
146
+ 'Separator',
147
+ 'Bullet List',
148
+ 'Numbered List',
149
+ 'Blockquote',
150
+ 'Code',
151
+ ]
152
+ }
153
+ return buttons.map(createEditorButton)
154
+ },
155
+ floatingMenuButtons() {
156
+ if (!this.floatingMenu) return false
157
+
158
+ let buttons
159
+ if (Array.isArray(this.floatingMenu)) {
160
+ buttons = this.floatingMenu
161
+ } else {
162
+ buttons = [
163
+ 'Paragraph',
164
+ 'Heading 2',
165
+ 'Heading 3',
166
+ 'Bullet List',
167
+ 'Numbered List',
168
+ 'Blockquote',
169
+ 'Code',
170
+ 'Horizontal Rule',
171
+ ]
172
+ }
173
+ return buttons.map(createEditorButton)
174
+ },
175
+ },
176
+ }
177
+
178
+ function createEditorButton(option) {
179
+ if (typeof option == 'object') {
180
+ return option
181
+ }
182
+ return commands[option]
183
+ }
184
+ </script>
185
+ <style>
186
+ .ProseMirror:not(.ProseMirror-focused) p.is-editor-empty:first-child::before {
187
+ content: attr(data-placeholder);
188
+ float: left;
189
+ color: theme('colors.gray.500');
190
+ pointer-events: none;
191
+ height: 0;
192
+ }
193
+ </style>
@@ -0,0 +1,79 @@
1
+ export default {
2
+ Paragraph: {
3
+ label: 'Paragraph',
4
+ icon: 'type',
5
+ action: (editor) => editor.chain().focus().setParagraph().run(),
6
+ isActive: (editor) => editor.isActive('paragraph'),
7
+ },
8
+ 'Heading 2': {
9
+ label: 'Heading 2',
10
+ text: 'H2',
11
+ action: (editor) =>
12
+ editor.chain().focus().toggleHeading({ level: 2 }).run(),
13
+ isActive: (editor) => editor.isActive('heading', { level: 2 }),
14
+ },
15
+ 'Heading 3': {
16
+ label: 'Heading 3',
17
+ text: 'H3',
18
+ action: (editor) =>
19
+ editor.chain().focus().toggleHeading({ level: 3 }).run(),
20
+ isActive: (editor) => editor.isActive('heading', { level: 3 }),
21
+ },
22
+ Bold: {
23
+ label: 'Bold',
24
+ icon: 'bold',
25
+ action: (editor) => editor.chain().focus().toggleBold().run(),
26
+ isActive: (editor) => editor.isActive('bold'),
27
+ },
28
+ Italic: {
29
+ label: 'Italic',
30
+ icon: 'italic',
31
+ action: (editor) => editor.chain().focus().toggleItalic().run(),
32
+ isActive: (editor) => editor.isActive('italic'),
33
+ },
34
+ 'Bullet List': {
35
+ label: 'Bullet List',
36
+ icon: 'list',
37
+ action: (editor) => editor.chain().focus().toggleBulletList().run(),
38
+ isActive: (editor) => editor.isActive('bulletList'),
39
+ },
40
+ 'Numbered List': {
41
+ label: 'Numbered List',
42
+ text: '1.',
43
+ action: (editor) => editor.chain().focus().toggleOrderedList().run(),
44
+ isActive: (editor) => editor.isActive('orderedList'),
45
+ },
46
+ Blockquote: {
47
+ label: 'Blockquote',
48
+ icon: 'chevron-right',
49
+ action: (editor) => editor.chain().focus().toggleBlockquote().run(),
50
+ isActive: (editor) => editor.isActive('blockquote'),
51
+ },
52
+ Code: {
53
+ label: 'Code',
54
+ icon: 'code',
55
+ action: (editor) => editor.chain().focus().toggleCodeBlock().run(),
56
+ isActive: (editor) => editor.isActive('codeBlock'),
57
+ },
58
+ 'Horizontal Rule': {
59
+ label: 'Horizontal Rule',
60
+ icon: 'minus',
61
+ action: (editor) => editor.chain().focus().setHorizontalRule().run(),
62
+ isActive: (editor) => false,
63
+ },
64
+ Undo: {
65
+ label: 'Undo',
66
+ icon: 'corner-up-left',
67
+ action: (editor) => editor.chain().focus().undo().run(),
68
+ isActive: (editor) => false,
69
+ },
70
+ Redo: {
71
+ label: 'Redo',
72
+ icon: 'corner-up-right',
73
+ action: (editor) => editor.chain().focus().redo().run(),
74
+ isActive: (editor) => false,
75
+ },
76
+ Separator: {
77
+ type: 'separator',
78
+ },
79
+ }
@@ -0,0 +1 @@
1
+ export { default } from './TextEditor.vue'