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