frappe-ui 0.0.1
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 +8 -0
- package/package.json +25 -0
- package/readme.md +313 -0
- package/src/components/Alert.vue +57 -0
- package/src/components/Avatar.vue +61 -0
- package/src/components/Badge.vue +40 -0
- package/src/components/Button.vue +103 -0
- package/src/components/Card.vue +37 -0
- package/src/components/Dialog.vue +59 -0
- package/src/components/Dropdown.vue +219 -0
- package/src/components/ErrorMessage.vue +15 -0
- package/src/components/FeatherIcon.vue +55 -0
- package/src/components/GreenCheckIcon.vue +16 -0
- package/src/components/Input.vue +158 -0
- package/src/components/Link.vue +28 -0
- package/src/components/ListItem.vue +28 -0
- package/src/components/LoadingIndicator.vue +12 -0
- package/src/components/LoadingText.vue +21 -0
- package/src/components/Modal.vue +67 -0
- package/src/components/Popover.vue +166 -0
- package/src/components/Spinner.vue +27 -0
- package/src/components/SuccessMessage.vue +15 -0
- package/src/directives/onOutsideClick.js +28 -0
- package/src/index.js +29 -0
- package/src/style.css +15 -0
- package/src/utils/call.js +72 -0
- package/src/utils/plugin.js +23 -0
- package/src/utils/resources.js +104 -0
- package/src/utils/socketio.js +9 -0
- package/src/utils/tailwind.config.js +111 -0
- package/src/utils/vite-dev-server.js +14 -0
package/license.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
Copyright © 2022 Frappe Technologies Pvt. Ltd.
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "frappe-ui",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A set of components and utilities for rapid UI development",
|
|
5
|
+
"main": "./src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"prettier": "npx prettier -w ./src"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src"
|
|
11
|
+
],
|
|
12
|
+
"author": "Frappe Technologies Pvt. Ltd.",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@popperjs/core": "^2.11.2",
|
|
16
|
+
"@tailwindcss/forms": "^0.4.0",
|
|
17
|
+
"@tailwindcss/typography": "^0.5.0",
|
|
18
|
+
"autoprefixer": "^10.4.2",
|
|
19
|
+
"feather-icons": "^4.28.0",
|
|
20
|
+
"lodash": "^4.17.21",
|
|
21
|
+
"postcss": "^8.4.5",
|
|
22
|
+
"socket.io-client": "^2.4.0",
|
|
23
|
+
"tailwindcss": "^3.0.12"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# Frappe UI
|
|
2
|
+
|
|
3
|
+
A set of components and utilities for rapid UI development.
|
|
4
|
+
|
|
5
|
+
Frappe UI components are built using Vue 3 and Tailwind. Along with components,
|
|
6
|
+
it also ships with directives and utilities that make UI development easier.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
```sh
|
|
10
|
+
npm install frappe-ui
|
|
11
|
+
# or
|
|
12
|
+
yarn add frappe-ui
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Now, import the FrappeUI plugin and components in your Vue app's `main.js`:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import { createApp } from "vue";
|
|
19
|
+
import { FrappeUI, Button } from "frappe-ui";
|
|
20
|
+
import App from "./App.vue";
|
|
21
|
+
import "./index.css";
|
|
22
|
+
|
|
23
|
+
let app = createApp(App);
|
|
24
|
+
app.use(FrappeUI);
|
|
25
|
+
app.component("Button", Button);
|
|
26
|
+
app.mount("#app");
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
In your `tailwind.config.js` file, include the frappe-ui preset:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
module.exports = {
|
|
33
|
+
presets: [
|
|
34
|
+
require('frappe-ui/src/utils/tailwind.config')
|
|
35
|
+
],
|
|
36
|
+
...
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Components
|
|
41
|
+
|
|
42
|
+
Frappe UI ships with a bunch of components. To use a component, you can import it directly from `frappe-ui`:
|
|
43
|
+
```html
|
|
44
|
+
<template>
|
|
45
|
+
<Button>Click me</Button>
|
|
46
|
+
</template>
|
|
47
|
+
<script>
|
|
48
|
+
import { Button } from 'frappe-ui';
|
|
49
|
+
export default {
|
|
50
|
+
components: {
|
|
51
|
+
Button
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
You can also register components on the root `app` so that you don't have to import them in every component.
|
|
58
|
+
|
|
59
|
+
`main.js`
|
|
60
|
+
```js
|
|
61
|
+
import { createApp } from "vue";
|
|
62
|
+
import { Button, Input } from "frappe-ui";
|
|
63
|
+
|
|
64
|
+
let app = createApp(App);
|
|
65
|
+
app.component("Button", Button);
|
|
66
|
+
app.component("Input", Input);
|
|
67
|
+
app.mount("#app");
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Alert
|
|
71
|
+
```html
|
|
72
|
+
<Alert title="Info">Your account has been created.</Alert>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Avatar
|
|
76
|
+
```html
|
|
77
|
+
<Avatar label="John Doe" />
|
|
78
|
+
<Avatar label="John Doe" imageURL="https://picsum.photos/200" />
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Badge
|
|
82
|
+
```html
|
|
83
|
+
<Badge>Open</Badge>
|
|
84
|
+
<Badge color="green">Completed</Badge>
|
|
85
|
+
<Badge color="red">Error</Badge>
|
|
86
|
+
<Badge color="yellow">Closed</Badge>
|
|
87
|
+
<Badge color="blue">Running</Badge>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Button
|
|
91
|
+
```html
|
|
92
|
+
<Button>Default</Button>
|
|
93
|
+
<Button type="primary">Primary</Button>
|
|
94
|
+
<Button type="danger">Danger</Button>
|
|
95
|
+
<Button type="white">White</Button>
|
|
96
|
+
<Button icon="x" />
|
|
97
|
+
<Button icon-left="menu">Menu</Button>
|
|
98
|
+
<Button icon-right="external-link">Link</Button>
|
|
99
|
+
<Button :loading="true">Loading</Button>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Card
|
|
103
|
+
```html
|
|
104
|
+
<Card title="Heading" subtitle="Sub text">
|
|
105
|
+
<div class="text-base">Card content</div>
|
|
106
|
+
</Card>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Dialog
|
|
110
|
+
The Dialog component uses `teleport` feature and requires `#modals` to exist.
|
|
111
|
+
Make sure you add a `<div id="modals"></div>` before the end of your body tag.
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<Button @click="dialogOpen = true">Open Dialog</Button>
|
|
115
|
+
<Dialog title="This is Dialog" v-model="dialogOpen">
|
|
116
|
+
<div class="text-base">Dialog content</div>
|
|
117
|
+
</Dialog>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Dropdown
|
|
121
|
+
The Dropdown component uses `teleport` feature and requires `#popovers` to exist.
|
|
122
|
+
Make sure you add a `<div id="popovers"></div>` before the end of your body tag.
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<Dropdown :items="[{ label: 'Option 1' }, { label: 'Option 2' }]">
|
|
126
|
+
<template v-slot="{ toggleDropdown }">
|
|
127
|
+
<Button @click="toggleDropdown()">Open Dropdown</Button>
|
|
128
|
+
</template>
|
|
129
|
+
</Dropdown>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### ErrorMessage
|
|
133
|
+
```html
|
|
134
|
+
<ErrorMessage message="There was an error" />
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### FeatherIcon
|
|
138
|
+
Uses [`feather-icons`](https://github.com/feathericons/feather) under the hood.
|
|
139
|
+
|
|
140
|
+
```html
|
|
141
|
+
<FeatherIcon class="w-4 h-4" name="menu" />
|
|
142
|
+
<FeatherIcon class="w-4 h-4" name="circle" />
|
|
143
|
+
<FeatherIcon class="w-4 h-4" name="arrow-left" />
|
|
144
|
+
<FeatherIcon class="w-4 h-4" name="arrow-right" />
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### GreenCheckIcon
|
|
148
|
+
```html
|
|
149
|
+
<GreenCheckIcon class="w-4 h-4" />
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Input
|
|
153
|
+
```html
|
|
154
|
+
<Input label="Text" type="text" value="" placeholder="Text" />
|
|
155
|
+
<Input label="Long Text" type="textarea" value="" placeholder="Textarea" />
|
|
156
|
+
<Input
|
|
157
|
+
label="Select"
|
|
158
|
+
type="select"
|
|
159
|
+
value=""
|
|
160
|
+
:options="['Option 1', 'Option 2']"
|
|
161
|
+
/>
|
|
162
|
+
<Input label="Check" type="checkbox" value="" />
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### ListItem
|
|
166
|
+
```html
|
|
167
|
+
<ListItem title="List Item 1" subtitle="Sub text 1">
|
|
168
|
+
<template #actions>
|
|
169
|
+
<Button icon="more-horizontal" />
|
|
170
|
+
</template>
|
|
171
|
+
</ListItem>
|
|
172
|
+
<ListItem title="List Item 2" subtitle="Sub text 2" />
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### LoadingIndicator
|
|
176
|
+
```html
|
|
177
|
+
<LoadingIndicator />
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### LoadingText
|
|
181
|
+
```html
|
|
182
|
+
<LoadingText />
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Spinner
|
|
186
|
+
```html
|
|
187
|
+
<Spinner class="w-5" />
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### SuccessMessage
|
|
191
|
+
```html
|
|
192
|
+
<SuccessMessage message="Completed successfully" />
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Directives
|
|
196
|
+
|
|
197
|
+
### onOutsideClick
|
|
198
|
+
This directive is used when you want to execute a function when the user clicks outside of a target element. For e.g., when user clicks outside a dropdown, the dropdown should close.
|
|
199
|
+
|
|
200
|
+
```html
|
|
201
|
+
<button v-on-outside-click="handleOutsideClick">Click me</button>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Utilities
|
|
205
|
+
|
|
206
|
+
### call
|
|
207
|
+
This function wraps `fetch` API. It is built for making web requests to a Frappe server.
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
call('frappe.client.get_value', {
|
|
211
|
+
doctype: 'ToDo',
|
|
212
|
+
filters: {name: 'adsfasdf'},
|
|
213
|
+
fieldname: 'description'
|
|
214
|
+
})
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### resources
|
|
218
|
+
This is a helper for managing async data fetching in Vue apps that work with a Frappe backend.
|
|
219
|
+
|
|
220
|
+
```html
|
|
221
|
+
<template>
|
|
222
|
+
<div>
|
|
223
|
+
<LoadingText v-if="$resources.todos.loading" />
|
|
224
|
+
<div
|
|
225
|
+
v-for="todo in $resources.todos.data || []"
|
|
226
|
+
:key="todo.name"
|
|
227
|
+
>
|
|
228
|
+
<div>{{ todo.description }}</div>
|
|
229
|
+
<Badge>{{ todo.status }}</Badge>
|
|
230
|
+
</div>
|
|
231
|
+
<ErrorMessage message="$resources.todos.error" />
|
|
232
|
+
</div>
|
|
233
|
+
</template>
|
|
234
|
+
<script>
|
|
235
|
+
import { Badge, LoadingText, ErrorMessage } from 'frappe-ui';
|
|
236
|
+
|
|
237
|
+
export default {
|
|
238
|
+
name: 'ToDos',
|
|
239
|
+
resources: {
|
|
240
|
+
todos: {
|
|
241
|
+
method: 'frappe.client.get_list',
|
|
242
|
+
params: {
|
|
243
|
+
doctype: 'ToDo',
|
|
244
|
+
fields: ['*']
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
components: {
|
|
249
|
+
Badge,
|
|
250
|
+
LoadingText,
|
|
251
|
+
ErrorMessage
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
</script>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### socketio
|
|
258
|
+
|
|
259
|
+
This module pre-configures a socketio instance on the port 9000. If you install the FrappeUI plugin, `this.$socket` will be available in all Vue components.
|
|
260
|
+
|
|
261
|
+
Usage:
|
|
262
|
+
```js
|
|
263
|
+
this.$socket.on('list_update', (data) => {
|
|
264
|
+
// handle list update event
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### tailwind.config
|
|
269
|
+
This is a [tailwind preset](https://tailwindcss.com/docs/presets) that customizes the standard tailwind config to include Frappe design tokens.
|
|
270
|
+
|
|
271
|
+
Usage:
|
|
272
|
+
```js
|
|
273
|
+
module.exports = {
|
|
274
|
+
presets: [
|
|
275
|
+
require('frappe-ui/src/utils/tailwind.config')
|
|
276
|
+
],
|
|
277
|
+
...
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Vue Plugin
|
|
282
|
+
Vue plugin that installs call, resources and socketio in your Vue app
|
|
283
|
+
|
|
284
|
+
`main.js`
|
|
285
|
+
```js
|
|
286
|
+
import { createApp } from "vue";
|
|
287
|
+
import { FrappeUI } from "frappe-ui";
|
|
288
|
+
import App from "./App.vue";
|
|
289
|
+
|
|
290
|
+
let app = createApp(App);
|
|
291
|
+
app.use(FrappeUI);
|
|
292
|
+
app.mount("#app");
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
You can now use these features in your Vue components.
|
|
296
|
+
```html
|
|
297
|
+
<script>
|
|
298
|
+
export default {
|
|
299
|
+
resources: {
|
|
300
|
+
ping: 'frappe.handler.ping'
|
|
301
|
+
},
|
|
302
|
+
mounted() {
|
|
303
|
+
this.$call('ping');
|
|
304
|
+
this.$socket.on('list_update', (data) => {
|
|
305
|
+
// handle list update event
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
</script>
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## License
|
|
313
|
+
MIT
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="block w-full">
|
|
3
|
+
<div
|
|
4
|
+
class="items-start px-4 md:px-5 py-3.5 text-base rounded-md flex"
|
|
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="w-full ml-2">
|
|
23
|
+
<div class="flex flex-col md:items-baseline md:flex-row">
|
|
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,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="overflow-hidden" :class="styleClasses">
|
|
3
|
+
<img
|
|
4
|
+
v-if="imageURL"
|
|
5
|
+
:src="imageURL"
|
|
6
|
+
class="object-cover"
|
|
7
|
+
:class="styleClasses"
|
|
8
|
+
/>
|
|
9
|
+
<div
|
|
10
|
+
v-else
|
|
11
|
+
class="flex items-center justify-center w-full h-full text-green-800 uppercase bg-green-200"
|
|
12
|
+
:class="{ sm: 'text-xs', md: 'text-base', lg: 'text-lg' }[size]"
|
|
13
|
+
>
|
|
14
|
+
{{ label && label[0] }}
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
const validShapes = ['square', 'circle']
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
name: 'Avatar',
|
|
24
|
+
props: {
|
|
25
|
+
imageURL: String,
|
|
26
|
+
label: String,
|
|
27
|
+
size: {
|
|
28
|
+
default: 'md',
|
|
29
|
+
},
|
|
30
|
+
shape: {
|
|
31
|
+
default: 'circle',
|
|
32
|
+
validator(value) {
|
|
33
|
+
const valid = validShapes.includes(value)
|
|
34
|
+
if (!valid) {
|
|
35
|
+
console.warn(
|
|
36
|
+
`shape property for <Avatar /> must be one of `,
|
|
37
|
+
validShapes
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
return valid
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
computed: {
|
|
45
|
+
styleClasses() {
|
|
46
|
+
const sizeClasses = {
|
|
47
|
+
sm: 'w-4 h-4',
|
|
48
|
+
md: 'w-8 h-8',
|
|
49
|
+
lg: 'w-12 h-12',
|
|
50
|
+
}[this.size]
|
|
51
|
+
|
|
52
|
+
const shapeClass = {
|
|
53
|
+
circle: 'rounded-full',
|
|
54
|
+
square: 'rounded-lg',
|
|
55
|
+
}[this.shape]
|
|
56
|
+
|
|
57
|
+
return `${shapeClass} ${sizeClasses}`
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span
|
|
3
|
+
class="inline-block px-3 py-1 text-xs font-medium rounded-md cursor-default"
|
|
4
|
+
:class="classes"
|
|
5
|
+
>
|
|
6
|
+
<slot>{{ status }}</slot>
|
|
7
|
+
</span>
|
|
8
|
+
</template>
|
|
9
|
+
<script>
|
|
10
|
+
export default {
|
|
11
|
+
name: 'Badge',
|
|
12
|
+
props: ['color', 'status'],
|
|
13
|
+
computed: {
|
|
14
|
+
classes() {
|
|
15
|
+
let color = this.color
|
|
16
|
+
if (!color && this.status) {
|
|
17
|
+
color = {
|
|
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 {
|
|
31
|
+
gray: 'text-gray-700 bg-gray-50',
|
|
32
|
+
green: 'text-green-700 bg-green-50',
|
|
33
|
+
red: 'text-red-700 bg-red-50',
|
|
34
|
+
yellow: 'text-yellow-700 bg-yellow-50',
|
|
35
|
+
blue: 'text-blue-700 bg-blue-50',
|
|
36
|
+
}[color || 'gray']
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
v-bind="$attrs"
|
|
4
|
+
:class="buttonClasses"
|
|
5
|
+
@click="handleClick"
|
|
6
|
+
:disabled="isDisabled"
|
|
7
|
+
>
|
|
8
|
+
<LoadingIndicator
|
|
9
|
+
v-if="loading"
|
|
10
|
+
:class="{
|
|
11
|
+
'text-white': type == 'primary',
|
|
12
|
+
'text-gray-600': type == 'secondary',
|
|
13
|
+
'text-red-200': type == 'danger',
|
|
14
|
+
}"
|
|
15
|
+
/>
|
|
16
|
+
<FeatherIcon v-else-if="iconLeft" :name="iconLeft" class="w-4 h-4 mr-1.5" />
|
|
17
|
+
<template v-if="loading && loadingText">{{ loadingText }}</template>
|
|
18
|
+
<template v-else-if="icon">
|
|
19
|
+
<FeatherIcon :name="icon" class="w-4 h-4" />
|
|
20
|
+
</template>
|
|
21
|
+
<span :class="icon ? 'sr-only' : ''">
|
|
22
|
+
<slot></slot>
|
|
23
|
+
</span>
|
|
24
|
+
<FeatherIcon v-if="iconRight" :name="iconRight" class="w-4 h-4 ml-2" />
|
|
25
|
+
</button>
|
|
26
|
+
</template>
|
|
27
|
+
<script>
|
|
28
|
+
import FeatherIcon from './FeatherIcon.vue'
|
|
29
|
+
import LoadingIndicator from './LoadingIndicator.vue'
|
|
30
|
+
|
|
31
|
+
export default {
|
|
32
|
+
name: 'Button',
|
|
33
|
+
components: {
|
|
34
|
+
FeatherIcon,
|
|
35
|
+
LoadingIndicator,
|
|
36
|
+
},
|
|
37
|
+
props: {
|
|
38
|
+
type: {
|
|
39
|
+
type: String,
|
|
40
|
+
default: 'secondary',
|
|
41
|
+
},
|
|
42
|
+
disabled: {
|
|
43
|
+
type: Boolean,
|
|
44
|
+
default: false,
|
|
45
|
+
},
|
|
46
|
+
iconLeft: {
|
|
47
|
+
type: String,
|
|
48
|
+
default: null,
|
|
49
|
+
},
|
|
50
|
+
iconRight: {
|
|
51
|
+
type: String,
|
|
52
|
+
default: null,
|
|
53
|
+
},
|
|
54
|
+
icon: {
|
|
55
|
+
type: String,
|
|
56
|
+
default: null,
|
|
57
|
+
},
|
|
58
|
+
loading: {
|
|
59
|
+
type: Boolean,
|
|
60
|
+
default: false,
|
|
61
|
+
},
|
|
62
|
+
loadingText: {
|
|
63
|
+
type: String,
|
|
64
|
+
default: null,
|
|
65
|
+
},
|
|
66
|
+
route: {},
|
|
67
|
+
link: {
|
|
68
|
+
type: String,
|
|
69
|
+
default: null,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
computed: {
|
|
73
|
+
buttonClasses() {
|
|
74
|
+
return [
|
|
75
|
+
'inline-flex items-center justify-center text-base leading-5 rounded-md focus:outline-none',
|
|
76
|
+
this.icon ? 'p-1.5' : 'px-3 py-1',
|
|
77
|
+
{
|
|
78
|
+
'opacity-50 cursor-not-allowed pointer-events-none': this.isDisabled,
|
|
79
|
+
'bg-gradient-blue hover:bg-gradient-none hover:bg-blue-500 text-white focus:shadow-outline-blue':
|
|
80
|
+
this.type === 'primary',
|
|
81
|
+
'bg-gray-100 hover:bg-gray-200 text-gray-900 focus:shadow-outline-gray':
|
|
82
|
+
this.type === 'secondary',
|
|
83
|
+
'bg-red-500 hover:bg-red-400 text-white focus:shadow-outline-red':
|
|
84
|
+
this.type === 'danger',
|
|
85
|
+
'bg-white text-gray-900 shadow focus:ring focus:ring-gray-400':
|
|
86
|
+
this.type === 'white',
|
|
87
|
+
},
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
isDisabled() {
|
|
91
|
+
return this.disabled || this.loading
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
methods: {
|
|
95
|
+
handleClick() {
|
|
96
|
+
if (this.route && this.$router) {
|
|
97
|
+
this.route && this.$router.push(this.route)
|
|
98
|
+
}
|
|
99
|
+
this.link ? window.open(this.link, '_blank') : null
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
</script>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col px-6 py-5 bg-white border rounded-lg 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="text-base text-gray-600 mt-1.5" v-if="subtitle">
|
|
15
|
+
{{ subtitle }}
|
|
16
|
+
</p>
|
|
17
|
+
<div
|
|
18
|
+
v-if="loading"
|
|
19
|
+
class="flex flex-col items-center justify-center flex-auto mt-4 rounded-md"
|
|
20
|
+
>
|
|
21
|
+
<LoadingText />
|
|
22
|
+
</div>
|
|
23
|
+
<div class="flex-auto mt-4 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>
|