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
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>