frappe-ui 0.1.276 → 0.1.277
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/frappe/drive/components/MoveDialog.vue +1 -1
- package/frappe/drive/components/RenameDialog.vue +14 -6
- package/frappe/drive/components/TagInput/TagInput.vue +1 -1
- package/frappe/drive/js/resources.js +4 -2
- package/frappe/drive/js/utils.js +32 -1
- package/package.json +1 -1
- package/src/components/Combobox/Combobox.cy.ts +21 -0
- package/src/components/Combobox/Combobox.vue +27 -5
- package/src/components/Combobox/stories/CustomValue.vue +20 -0
- package/src/components/Combobox/types.ts +3 -0
- package/src/components/ListView/ListRow.vue +32 -5
- package/src/components/ListView/ListView.vue +21 -15
- package/src/components/ListView/stories/Disabled.vue +57 -0
- package/src/components/Spinner.vue +7 -0
- package/src/index.ts +2 -0
|
@@ -443,13 +443,13 @@ function closeEntity(name) {
|
|
|
443
443
|
}
|
|
444
444
|
|
|
445
445
|
const moveFile = async () => {
|
|
446
|
-
open.value = false
|
|
447
446
|
emit('success')
|
|
448
447
|
await move.submit({
|
|
449
448
|
entity_names: props.entities.map((obj) => obj.name),
|
|
450
449
|
new_parent: selected.value,
|
|
451
450
|
team: chosenTeam.value,
|
|
452
451
|
})
|
|
452
|
+
open.value = false
|
|
453
453
|
emit('complete')
|
|
454
454
|
}
|
|
455
455
|
</script>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
{
|
|
9
9
|
label: 'Confirm',
|
|
10
10
|
variant: 'solid',
|
|
11
|
-
disabled: !newTitle || newTitle === entity.title,
|
|
11
|
+
disabled: !newTitle || newTitle === entity.title || rename.loading,
|
|
12
12
|
onClick: submit,
|
|
13
13
|
},
|
|
14
14
|
],
|
|
@@ -42,7 +42,7 @@ import { Dialog } from '../../../src'
|
|
|
42
42
|
import { rename } from '../js/resources'
|
|
43
43
|
|
|
44
44
|
const props = defineProps({ entity: Object, modelValue: String })
|
|
45
|
-
const emit = defineEmits(['
|
|
45
|
+
const emit = defineEmits(['success', 'complete'])
|
|
46
46
|
const dialogType = defineModel()
|
|
47
47
|
const open = ref(true)
|
|
48
48
|
|
|
@@ -64,10 +64,18 @@ if (props.entity.is_group || props.entity.doc || props.entity.is_link) {
|
|
|
64
64
|
const submit = () => {
|
|
65
65
|
const formattedTitle =
|
|
66
66
|
newTitle.value + (file_ext.value ? '.' + file_ext.value : '')
|
|
67
|
-
rename.submit(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
rename.submit(
|
|
68
|
+
{
|
|
69
|
+
entity_name: props.entity.name,
|
|
70
|
+
new_title: formattedTitle,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
onSuccess: () => {
|
|
74
|
+
open.value = false
|
|
75
|
+
emit('complete')
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
)
|
|
71
79
|
emit('success', {
|
|
72
80
|
name: props.entity.name,
|
|
73
81
|
title: formattedTitle,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createResource, toast } from '../../../src'
|
|
2
|
-
import { prettyData } from '../js/utils'
|
|
2
|
+
import { prettyData, openEntity } from '../js/utils'
|
|
3
|
+
|
|
3
4
|
export const getTeams = createResource({
|
|
4
5
|
url: 'drive.api.permissions.get_teams',
|
|
5
6
|
params: {
|
|
@@ -39,9 +40,10 @@ export const updateMoved = (team, new_parent, special) => {
|
|
|
39
40
|
export const move = createResource({
|
|
40
41
|
url: 'drive.api.files.move',
|
|
41
42
|
onSuccess(data) {
|
|
43
|
+
console.log(data)
|
|
42
44
|
toast.success('Moved to ' + data.title, {
|
|
43
45
|
action: {
|
|
44
|
-
label: 'Go',
|
|
46
|
+
label: 'Go to folder',
|
|
45
47
|
onClick: () => {
|
|
46
48
|
if (!data.special)
|
|
47
49
|
openEntity({
|
package/frappe/drive/js/utils.js
CHANGED
|
@@ -26,7 +26,7 @@ export function getFileLink(entity, copy = true) {
|
|
|
26
26
|
}
|
|
27
27
|
if (!copy) return link
|
|
28
28
|
try {
|
|
29
|
-
copyToClipboard(link).then(() => toast.success('Copied to your clipboard
|
|
29
|
+
copyToClipboard(link).then(() => toast.success('Copied to your clipboard.'))
|
|
30
30
|
} catch (err) {
|
|
31
31
|
console.error('Failed to copy link:', err)
|
|
32
32
|
}
|
|
@@ -108,3 +108,34 @@ export const formatDate = (date) => {
|
|
|
108
108
|
|
|
109
109
|
return `${formattedDate}, ${formattedTime}`
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
export const openEntity = (entity, new_tab = false) => {
|
|
114
|
+
if (new_tab) {
|
|
115
|
+
return window.open(getFileLink(entity, false), '_blank')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (entity.name === '') {
|
|
119
|
+
if (entity.is_private) window.location.href = '/drive/'
|
|
120
|
+
else window.location.href = '/drive/t/' + entity.team
|
|
121
|
+
} else if (entity.is_group) {
|
|
122
|
+
window.location.href = '/drive/d/' + entity.name
|
|
123
|
+
} else if (entity.is_link) {
|
|
124
|
+
const origin = new URL(entity.path).origin
|
|
125
|
+
if (
|
|
126
|
+
confirm(
|
|
127
|
+
`This will open an external link to ${origin} - are you sure you want to open?`,
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
window.open(entity.path, '_blank')
|
|
131
|
+
} else if (entity.mime_type === 'frappe/slides') {
|
|
132
|
+
window.location.href = '/slides/presentation/' + entity.path
|
|
133
|
+
} else if (
|
|
134
|
+
entity.mime_type === 'frappe_doc' ||
|
|
135
|
+
entity.mime_type === 'text/markdown'
|
|
136
|
+
) {
|
|
137
|
+
window.location.href = '/writer/w/' + entity.name
|
|
138
|
+
} else {
|
|
139
|
+
window.location.href = '/drive/f/' + entity.name
|
|
140
|
+
}
|
|
141
|
+
}
|
package/package.json
CHANGED
|
@@ -90,4 +90,25 @@ describe('Combobox', () => {
|
|
|
90
90
|
cy.root().click(0, 0, { force: true })
|
|
91
91
|
cy.get('@onBlurSpy').should('have.been.called')
|
|
92
92
|
})
|
|
93
|
+
|
|
94
|
+
it('custom value', () => {
|
|
95
|
+
cy.mount(Combobox, {
|
|
96
|
+
props: {
|
|
97
|
+
options,
|
|
98
|
+
allowCustomValue: true,
|
|
99
|
+
openOnFocus: true,
|
|
100
|
+
'onUpdate:modelValue': cy.spy().as('onUpdate'),
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
cy.get('[role=option]').should('have.length', 0)
|
|
105
|
+
cy.get('input').focus()
|
|
106
|
+
cy.get('[role=option]').should('have.length', options.length)
|
|
107
|
+
|
|
108
|
+
cy.get('input').type('..')
|
|
109
|
+
cy.get('[role=option]').should('have.length', 1)
|
|
110
|
+
cy.get('[role=option]:first').should('contain.text', 'Create ".."')
|
|
111
|
+
cy.get('[role=option]:first').click()
|
|
112
|
+
cy.get('@onUpdate').should('have.been.calledWith', '..')
|
|
113
|
+
})
|
|
93
114
|
})
|
|
@@ -54,7 +54,6 @@ const emit = defineEmits<{
|
|
|
54
54
|
input: (value: string) => void
|
|
55
55
|
}>()
|
|
56
56
|
|
|
57
|
-
|
|
58
57
|
const searchTerm = ref(getDisplayValue(props.modelValue))
|
|
59
58
|
const internalModelValue = ref(props.modelValue)
|
|
60
59
|
const isOpen = ref(false)
|
|
@@ -76,10 +75,13 @@ watch(
|
|
|
76
75
|
)
|
|
77
76
|
|
|
78
77
|
const onUpdateModelValue = (value: string | null) => {
|
|
79
|
-
|
|
78
|
+
let selectedOpt = value
|
|
80
79
|
? allOptionsFlat.value.find((opt) => getKey(opt) === value) || null
|
|
81
80
|
: null
|
|
82
81
|
|
|
82
|
+
selectedOpt =
|
|
83
|
+
!selectedOpt && props.allowCustomValue && value ? value : selectedOpt
|
|
84
|
+
|
|
83
85
|
if (selectedOpt && isCustomOption(selectedOpt)) {
|
|
84
86
|
const context = { searchTerm: lastSearchTerm.value }
|
|
85
87
|
selectedOpt.onClick(context)
|
|
@@ -311,9 +313,11 @@ defineSlots<{
|
|
|
311
313
|
prefix?: () => any
|
|
312
314
|
|
|
313
315
|
/** Custom slot for individual options, only used if the option has `slotName` */
|
|
314
|
-
[slotName: string]: (props: {
|
|
316
|
+
[slotName: string]: (props: {
|
|
317
|
+
option: SimpleOption
|
|
318
|
+
searchTerm: string
|
|
319
|
+
}) => any
|
|
315
320
|
}>()
|
|
316
|
-
|
|
317
321
|
</script>
|
|
318
322
|
|
|
319
323
|
<template>
|
|
@@ -363,11 +367,29 @@ defineSlots<{
|
|
|
363
367
|
class="max-h-60 overflow-auto pb-1.5"
|
|
364
368
|
:class="{ 'px-1.5 pt-1.5': !isGroup(filteredOptions[0]) }"
|
|
365
369
|
>
|
|
370
|
+
<ComboboxItem
|
|
371
|
+
v-if="
|
|
372
|
+
filteredOptions?.length == 0 && allowCustomValue && searchTerm
|
|
373
|
+
"
|
|
374
|
+
:value="searchTerm"
|
|
375
|
+
class="text-base leading-none text-ink-gray-7 rounded flex items-center h-7 px-2.5 py-1.5 select-none data-[highlighted]:bg-surface-gray-3"
|
|
376
|
+
>
|
|
377
|
+
<span class="flex items-center gap-2">
|
|
378
|
+
Create "{{ searchTerm }}"
|
|
379
|
+
</span>
|
|
380
|
+
</ComboboxItem>
|
|
381
|
+
|
|
366
382
|
<ComboboxEmpty
|
|
367
383
|
class="text-ink-gray-5 text-base text-center py-1.5 px-2.5"
|
|
384
|
+
v-else
|
|
368
385
|
>
|
|
369
|
-
{{
|
|
386
|
+
{{
|
|
387
|
+
searchTerm
|
|
388
|
+
? `No results found for "${searchTerm}"`
|
|
389
|
+
: 'No results found'
|
|
390
|
+
}}
|
|
370
391
|
</ComboboxEmpty>
|
|
392
|
+
|
|
371
393
|
<template
|
|
372
394
|
v-for="(optionOrGroup, index) in filteredOptions"
|
|
373
395
|
:key="index"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { Combobox } from 'frappe-ui'
|
|
4
|
+
|
|
5
|
+
const value = ref('')
|
|
6
|
+
|
|
7
|
+
const options = ['John Doe', 'Jane Doe', 'John Smith', 'Jane Smith']
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<Combobox
|
|
12
|
+
v-model="value"
|
|
13
|
+
:options="options"
|
|
14
|
+
placeholder="Select an option"
|
|
15
|
+
show-cancel
|
|
16
|
+
:allowCustomValue="true"
|
|
17
|
+
/>
|
|
18
|
+
|
|
19
|
+
<div class="text-sm text-gray-600">Selected: {{ value || 'None' }}</div>
|
|
20
|
+
</template>
|
|
@@ -4,17 +4,21 @@
|
|
|
4
4
|
:class="[
|
|
5
5
|
roundedClass,
|
|
6
6
|
isSelected || isActive ? 'bg-surface-gray-2' : '',
|
|
7
|
-
isHoverable ? 'cursor-pointer' : '',
|
|
8
|
-
isHoverable
|
|
7
|
+
isHoverable && !row.disabled ? 'cursor-pointer' : '',
|
|
8
|
+
isHoverable && !row.disabled
|
|
9
9
|
? isSelected || isActive
|
|
10
10
|
? 'hover:bg-surface-gray-3'
|
|
11
11
|
: 'hover:bg-surface-menu-bar'
|
|
12
12
|
: '',
|
|
13
|
+
row.disabled ? 'pointer-events-none' : '',
|
|
13
14
|
]"
|
|
14
15
|
class="flex flex-col transition-all duration-300 ease-in-out"
|
|
15
16
|
v-bind="{
|
|
16
17
|
...getLinkBindings(),
|
|
17
18
|
onClick: onRowClick,
|
|
19
|
+
...(row.disabled
|
|
20
|
+
? { 'aria-disabled': 'true', tabindex: -1 }
|
|
21
|
+
: {}),
|
|
18
22
|
}"
|
|
19
23
|
>
|
|
20
24
|
<component
|
|
@@ -23,6 +27,10 @@
|
|
|
23
27
|
>
|
|
24
28
|
<div
|
|
25
29
|
class="grid items-center gap-4 px-2"
|
|
30
|
+
:class="{
|
|
31
|
+
'cursor-not-allowed': row.disabled,
|
|
32
|
+
'opacity-50': row.disabled,
|
|
33
|
+
}"
|
|
26
34
|
:style="{
|
|
27
35
|
height: rowHeight,
|
|
28
36
|
gridTemplateColumns: getGridTemplateColumns(
|
|
@@ -39,16 +47,19 @@
|
|
|
39
47
|
>
|
|
40
48
|
<Checkbox
|
|
41
49
|
:modelValue="isSelected"
|
|
50
|
+
:disabled="row.disabled"
|
|
42
51
|
class="cursor-pointer duration-300"
|
|
43
52
|
@click.stop="handleCheckboxClick"
|
|
44
53
|
/>
|
|
45
54
|
</div>
|
|
55
|
+
|
|
46
56
|
<div
|
|
47
57
|
v-for="(column, i) in list.columns"
|
|
48
58
|
:key="column.key"
|
|
49
59
|
:class="[
|
|
50
60
|
alignmentMap[column.align],
|
|
51
61
|
i == 0 ? 'text-ink-gray-9' : 'text-ink-gray-7',
|
|
62
|
+
'overflow-x-hidden',
|
|
52
63
|
]"
|
|
53
64
|
>
|
|
54
65
|
<slot v-bind="{ idx: i, column, item: row[column.key], isActive }">
|
|
@@ -72,6 +83,7 @@
|
|
|
72
83
|
</slot>
|
|
73
84
|
</div>
|
|
74
85
|
</div>
|
|
86
|
+
|
|
75
87
|
<div
|
|
76
88
|
v-if="!isLastRow"
|
|
77
89
|
class="h-px border-t"
|
|
@@ -107,17 +119,16 @@ const rowRoute = computed(
|
|
|
107
119
|
|
|
108
120
|
const isExternalRoute = computed(() => {
|
|
109
121
|
if (!rowRoute.value) return false
|
|
110
|
-
// Check if it's a URL (string starting with http/https or /)
|
|
111
122
|
return typeof rowRoute.value === 'string' && rowRoute.value.startsWith('http')
|
|
112
123
|
})
|
|
113
124
|
|
|
114
125
|
const getLinkComponent = () => {
|
|
115
|
-
if (!rowRoute.value) return 'div'
|
|
126
|
+
if (!rowRoute.value || props.row.disabled) return 'div'
|
|
116
127
|
return isExternalRoute.value ? 'a' : 'router-link'
|
|
117
128
|
}
|
|
118
129
|
|
|
119
130
|
const getLinkBindings = () => {
|
|
120
|
-
if (!rowRoute.value) return {}
|
|
131
|
+
if (!rowRoute.value || props.row.disabled) return {}
|
|
121
132
|
return isExternalRoute.value
|
|
122
133
|
? {
|
|
123
134
|
href: rowRoute.value,
|
|
@@ -136,6 +147,7 @@ const isLastRow = computed(() => {
|
|
|
136
147
|
const isSelected = computed(() => {
|
|
137
148
|
return list.value.selections.has(props.row[list.value.rowKey])
|
|
138
149
|
})
|
|
150
|
+
|
|
139
151
|
const isActive = computed(
|
|
140
152
|
() =>
|
|
141
153
|
list.value.options.enableActive &&
|
|
@@ -155,6 +167,7 @@ const rowHeight = computed(() => {
|
|
|
155
167
|
|
|
156
168
|
const roundedClass = computed(() => {
|
|
157
169
|
if (!isSelected.value) return 'rounded'
|
|
170
|
+
|
|
158
171
|
const selections = [...list.value.selections]
|
|
159
172
|
let groups = list.value.rows[0]?.group
|
|
160
173
|
? list.value.rows.map((k) => k.rows)
|
|
@@ -163,15 +176,20 @@ const roundedClass = computed(() => {
|
|
|
163
176
|
for (let rows of groups) {
|
|
164
177
|
let currentIndex = rows.findIndex((k) => k == props.row)
|
|
165
178
|
if (currentIndex === -1) continue
|
|
179
|
+
|
|
166
180
|
let atBottom = !selections.includes(rows[currentIndex + 1]?.name)
|
|
167
181
|
let atTop = !selections.includes(rows[currentIndex - 1]?.name)
|
|
182
|
+
|
|
168
183
|
return (atBottom ? 'rounded-b ' : '') + (atTop ? 'rounded-t' : '')
|
|
169
184
|
}
|
|
170
185
|
})
|
|
171
186
|
|
|
172
187
|
const onRowClick = (event) => {
|
|
188
|
+
if (props.row.disabled) return
|
|
189
|
+
|
|
173
190
|
if (list.value.options.onRowClick)
|
|
174
191
|
list.value.options.onRowClick(props.row, event)
|
|
192
|
+
|
|
175
193
|
if (list.value.activeRow.value === props.row.name) {
|
|
176
194
|
list.value.activeRow.value = null
|
|
177
195
|
} else {
|
|
@@ -180,22 +198,31 @@ const onRowClick = (event) => {
|
|
|
180
198
|
}
|
|
181
199
|
|
|
182
200
|
const handleCheckboxClick = (event) => {
|
|
201
|
+
if (props.row.disabled) return
|
|
202
|
+
|
|
183
203
|
const value = props.row[list.value.rowKey]
|
|
204
|
+
|
|
184
205
|
if (event.shiftKey && !list.value.selections.has(value)) {
|
|
185
206
|
const lastSelected = Array.from(list.value.selections).pop()
|
|
207
|
+
|
|
186
208
|
const rows = list.value.rows.find((k) => k.group)
|
|
187
209
|
? list.value.rows.reduce((acc, curr) => acc.concat(curr.rows), [])
|
|
188
210
|
: list.value.rows
|
|
211
|
+
|
|
189
212
|
const lastIndex = rows.findIndex(
|
|
190
213
|
(k) => lastSelected === k[list.value.rowKey]
|
|
191
214
|
)
|
|
192
215
|
const curIndex = rows.findIndex((k) => value === k[list.value.rowKey])
|
|
216
|
+
|
|
193
217
|
const start = Math.min(lastIndex, curIndex)
|
|
194
218
|
const end = Math.max(lastIndex, curIndex)
|
|
219
|
+
|
|
195
220
|
for (let i = start; i <= end; i++) {
|
|
221
|
+
if (rows[i].disabled) continue
|
|
196
222
|
list.value.selections.add(rows[i][list.value.rowKey])
|
|
197
223
|
}
|
|
198
224
|
} else {
|
|
225
|
+
if (props.row.disabled) return
|
|
199
226
|
list.value.toggleRow(value)
|
|
200
227
|
}
|
|
201
228
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative flex w-full flex-1 flex-col overflow-x-auto">
|
|
3
|
-
<div
|
|
4
|
-
class="flex w-max min-w-full flex-col overflow-y-hidden"
|
|
5
|
-
:class="$attrs.class"
|
|
6
|
-
:style="$attrs.style"
|
|
7
|
-
>
|
|
3
|
+
<div class="flex w-max min-w-full flex-col overflow-y-hidden" :class="$attrs.class" :style="$attrs.style">
|
|
8
4
|
<slot v-bind="{ showGroupedRows, selectable }">
|
|
9
5
|
<ListHeader />
|
|
10
6
|
<template v-if="props.rows.length">
|
|
@@ -17,6 +13,7 @@
|
|
|
17
13
|
</div>
|
|
18
14
|
</div>
|
|
19
15
|
</template>
|
|
16
|
+
|
|
20
17
|
<script setup>
|
|
21
18
|
import ListEmptyState from './ListEmptyState.vue'
|
|
22
19
|
import ListHeader from './ListHeader.vue'
|
|
@@ -103,13 +100,14 @@ let _options = computed(() => {
|
|
|
103
100
|
|
|
104
101
|
const allRowsSelected = computed(() => {
|
|
105
102
|
if (!props.rows.length) return false
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
103
|
+
|
|
104
|
+
const rows = showGroupedRows.value
|
|
105
|
+
? props.rows.flatMap(r => r.rows)
|
|
106
|
+
: props.rows
|
|
107
|
+
|
|
108
|
+
const total = rows.filter(r => !r.disabled).length
|
|
109
|
+
|
|
110
|
+
return total > 0 && selections.size === total
|
|
113
111
|
})
|
|
114
112
|
|
|
115
113
|
const selectable = computed(() => {
|
|
@@ -123,7 +121,7 @@ let showGroupedRows = computed(() => {
|
|
|
123
121
|
})
|
|
124
122
|
|
|
125
123
|
function toggleRow(row) {
|
|
126
|
-
if (!selections.delete(row)) {
|
|
124
|
+
if (!selections.delete(row) && !row.disabled) {
|
|
127
125
|
selections.add(row)
|
|
128
126
|
}
|
|
129
127
|
}
|
|
@@ -135,11 +133,19 @@ function toggleAllRows(select) {
|
|
|
135
133
|
}
|
|
136
134
|
if (showGroupedRows.value) {
|
|
137
135
|
props.rows.forEach((row) => {
|
|
138
|
-
row.rows.forEach((r) =>
|
|
136
|
+
row.rows.forEach((r) => {
|
|
137
|
+
if (!r.disabled) {
|
|
138
|
+
selections.add(r[props.rowKey])
|
|
139
|
+
}
|
|
140
|
+
})
|
|
139
141
|
})
|
|
140
142
|
return
|
|
141
143
|
}
|
|
142
|
-
props.rows.forEach((row) =>
|
|
144
|
+
props.rows.forEach((row) => {
|
|
145
|
+
if (!row.disabled) {
|
|
146
|
+
selections.add(row[props.rowKey])
|
|
147
|
+
}
|
|
148
|
+
})
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
provide(
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { h, reactive } from 'vue'
|
|
3
|
+
|
|
4
|
+
import { Avatar, ListView } from 'frappe-ui'
|
|
5
|
+
|
|
6
|
+
const columns = reactive([
|
|
7
|
+
{
|
|
8
|
+
label: 'Name',
|
|
9
|
+
key: 'name',
|
|
10
|
+
width: 3,
|
|
11
|
+
getLabel: ({ row }) => row.name,
|
|
12
|
+
prefix: ({ row }) =>
|
|
13
|
+
h(Avatar, { shape: 'circle', image: row.user_image, size: 'sm' }),
|
|
14
|
+
},
|
|
15
|
+
{ label: 'Email', key: 'email', width: '200px' },
|
|
16
|
+
{ label: 'Role', key: 'role' },
|
|
17
|
+
{ label: 'Status', key: 'status' },
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
const rows = [
|
|
21
|
+
{
|
|
22
|
+
id: 1,
|
|
23
|
+
disabled: true,
|
|
24
|
+
name: 'John Doe',
|
|
25
|
+
email: 'john@doe.com',
|
|
26
|
+
status: 'Active',
|
|
27
|
+
role: 'Developer',
|
|
28
|
+
user_image: 'https://avatars.githubusercontent.com/u/499550',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 2,
|
|
32
|
+
name: 'Jane Doe',
|
|
33
|
+
email: 'jane@doe.com',
|
|
34
|
+
status: 'Inactive',
|
|
35
|
+
role: 'HR',
|
|
36
|
+
user_image: 'https://avatars.githubusercontent.com/u/499120',
|
|
37
|
+
},
|
|
38
|
+
]
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<ListView
|
|
43
|
+
class="h-[150px]"
|
|
44
|
+
:columns="columns"
|
|
45
|
+
:rows="rows"
|
|
46
|
+
:options="{
|
|
47
|
+
getRowRoute: (row) => ({
|
|
48
|
+
name: 'User',
|
|
49
|
+
params: { userId: row.id },
|
|
50
|
+
}),
|
|
51
|
+
selectable: true,
|
|
52
|
+
showTooltip: true,
|
|
53
|
+
resizeColumn: true,
|
|
54
|
+
}"
|
|
55
|
+
row-key="id"
|
|
56
|
+
/>
|
|
57
|
+
</template>
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ export { default as ListItem } from './components/ListItem.vue'
|
|
|
28
28
|
export { default as LoadingIndicator } from './components/LoadingIndicator.vue'
|
|
29
29
|
export { default as LoadingText } from './components/LoadingText.vue'
|
|
30
30
|
export * from './components/Progress'
|
|
31
|
+
export { default as Spinner } from './components/Spinner.vue'
|
|
31
32
|
export * from './components/Popover'
|
|
32
33
|
export * from './components/Rating'
|
|
33
34
|
export { default as Resource } from './components/Resource.vue'
|
|
@@ -78,6 +79,7 @@ export { default as NumberChart } from './components/Charts/NumberChart.vue'
|
|
|
78
79
|
export { default as DonutChart } from './components/Charts/DonutChart.vue'
|
|
79
80
|
export { default as FunnelChart } from './components/Charts/FunnelChart.vue'
|
|
80
81
|
export { default as ECharts } from './components/Charts/ECharts.vue'
|
|
82
|
+
export { default as useAxisChartOptions } from './components/Charts/axisChartOptions'
|
|
81
83
|
|
|
82
84
|
// directives
|
|
83
85
|
export { default as onOutsideClickDirective } from './directives/onOutsideClick'
|