pimelon-ui 0.1.24 → 0.1.50
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 +0 -8
- package/package.json +5 -8
- package/src/components/Autocomplete.story.vue +92 -14
- package/src/components/Autocomplete.vue +213 -98
- package/src/components/Avatar.vue +1 -0
- package/src/components/Dialog.vue +1 -1
- package/src/components/FormControl.story.vue +10 -9
- package/src/components/ListFilter/ListFilter.vue +1 -1
- package/src/components/ListFilter/SearchComplete.vue +1 -1
- package/src/components/ListView/ListEmptyState.vue +24 -0
- package/src/components/ListView/ListGroupHeader.vue +44 -0
- package/src/components/ListView/ListGroupRows.vue +17 -0
- package/src/components/ListView/ListGroups.vue +22 -0
- package/src/components/ListView/ListHeader.vue +3 -0
- package/src/components/ListView/ListHeaderItem.vue +77 -6
- package/src/components/ListView/ListRow.vue +32 -5
- package/src/components/ListView/ListRowItem.vue +21 -4
- package/src/components/ListView/ListView.vue +38 -6
- package/src/components/ListView.story.md +27 -0
- package/src/components/ListView.story.vue +243 -13
- package/src/components/Popover.vue +29 -7
- package/src/components/Progress.vue +8 -6
- package/src/components/Select.vue +37 -14
- package/src/components/TextEditor/FontColor.vue +1 -1
- package/src/components/Tooltip/Tooltip.story.vue +40 -0
- package/src/components/Tooltip/Tooltip.vue +60 -0
- package/src/icons/DownSolid.vue +8 -0
- package/src/index.js +7 -3
- package/src/resources/listResource.js +3 -3
- package/src/resources/plugin.js +5 -2
- package/src/utils/melonRequest.js +1 -1
- package/vite.js +14 -11
- package/src/components/Tooltip.story.vue +0 -11
- package/src/components/Tooltip.vue +0 -38
package/license.md
CHANGED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
The MIT License (MIT)
|
|
2
|
-
Copyright © 2022 Alphamonak Solutions
|
|
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pimelon-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.50",
|
|
4
4
|
"description": "A set of components and utilities for rapid UI development",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,9 +8,6 @@
|
|
|
8
8
|
"prettier": "npx prettier -w ./src",
|
|
9
9
|
"prepare": "husky install",
|
|
10
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
11
|
"dev": "vite",
|
|
15
12
|
"build": "vite build",
|
|
16
13
|
"preview": "vite preview",
|
|
@@ -53,6 +50,7 @@
|
|
|
53
50
|
"@vueuse/core": "^10.4.1",
|
|
54
51
|
"feather-icons": "^4.28.0",
|
|
55
52
|
"idb-keyval": "^6.2.0",
|
|
53
|
+
"radix-vue": "^1.5.3",
|
|
56
54
|
"showdown": "^2.1.0",
|
|
57
55
|
"socket.io-client": "^4.5.1",
|
|
58
56
|
"tippy.js": "^6.3.7"
|
|
@@ -62,12 +60,12 @@
|
|
|
62
60
|
"vue-router": "^4.1.6"
|
|
63
61
|
},
|
|
64
62
|
"devDependencies": {
|
|
65
|
-
"@histoire/plugin-vue": "^0.
|
|
63
|
+
"@histoire/plugin-vue": "^0.17.14",
|
|
66
64
|
"@vitejs/plugin-vue": "^4.0.0",
|
|
67
65
|
"autoprefixer": "^10.4.13",
|
|
68
66
|
"cross-fetch": "^3.1.5",
|
|
69
|
-
"histoire": "^0.
|
|
70
|
-
"husky": "^9.0.
|
|
67
|
+
"histoire": "^0.17.14",
|
|
68
|
+
"husky": "^9.0.11",
|
|
71
69
|
"lint-staged": ">=10",
|
|
72
70
|
"postcss": "^8.4.21",
|
|
73
71
|
"prettier": "2.7.1",
|
|
@@ -75,7 +73,6 @@
|
|
|
75
73
|
"tailwindcss": "^3.2.7",
|
|
76
74
|
"typescript": "^5.0.2",
|
|
77
75
|
"vite": "^4.1.0",
|
|
78
|
-
"vitepress": "^1.0.0-alpha.29",
|
|
79
76
|
"vue": "^3.2.45",
|
|
80
77
|
"vue-router": "^4.1.6"
|
|
81
78
|
},
|
|
@@ -2,24 +2,102 @@
|
|
|
2
2
|
import { ref } from 'vue'
|
|
3
3
|
import Autocomplete from './Autocomplete.vue'
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const single = ref('')
|
|
6
|
+
const people = ref(null)
|
|
6
7
|
const options = [
|
|
7
|
-
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
{
|
|
8
|
+
{
|
|
9
|
+
label: 'John Doe',
|
|
10
|
+
value: 'john-doe',
|
|
11
|
+
image: 'https://randomuser.me/api/portraits/men/59.jpg',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
label: 'Jane Doe',
|
|
15
|
+
value: 'jane-doe',
|
|
16
|
+
image: 'https://randomuser.me/api/portraits/women/58.jpg',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: 'John Smith',
|
|
20
|
+
value: 'john-smith',
|
|
21
|
+
image: 'https://randomuser.me/api/portraits/men/59.jpg',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: 'Jane Smith',
|
|
25
|
+
value: 'jane-smith',
|
|
26
|
+
image: 'https://randomuser.me/api/portraits/women/59.jpg',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: 'John Wayne',
|
|
30
|
+
value: 'john-wayne',
|
|
31
|
+
image: 'https://randomuser.me/api/portraits/men/57.jpg',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: 'Jane Wayne',
|
|
35
|
+
value: 'jane-wayne',
|
|
36
|
+
image: 'https://randomuser.me/api/portraits/women/51.jpg',
|
|
37
|
+
},
|
|
13
38
|
]
|
|
14
39
|
</script>
|
|
15
40
|
<template>
|
|
16
41
|
<Story :layout="{ width: 500, type: 'grid' }" autoPropsDisabled>
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
42
|
+
<Variant title="Single option">
|
|
43
|
+
<div class="p-2">
|
|
44
|
+
<Autocomplete
|
|
45
|
+
:options="options"
|
|
46
|
+
v-model="single"
|
|
47
|
+
placeholder="Select person"
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
</Variant>
|
|
51
|
+
<Variant title="Single option with prefix slots">
|
|
52
|
+
<div class="p-2">
|
|
53
|
+
<Autocomplete
|
|
54
|
+
:options="options"
|
|
55
|
+
v-model="single"
|
|
56
|
+
placeholder="Select person"
|
|
57
|
+
>
|
|
58
|
+
<template #prefix>
|
|
59
|
+
<img
|
|
60
|
+
v-if="single"
|
|
61
|
+
:src="single.image"
|
|
62
|
+
class="mr-2 h-4 w-4 rounded-full"
|
|
63
|
+
/>
|
|
64
|
+
</template>
|
|
65
|
+
<template #item-prefix="{ option }">
|
|
66
|
+
<img :src="option.image" class="h-4 w-4 rounded-full" />
|
|
67
|
+
</template>
|
|
68
|
+
</Autocomplete>
|
|
69
|
+
</div>
|
|
70
|
+
</Variant>
|
|
71
|
+
<Variant title="Single option without search">
|
|
72
|
+
<div class="p-2">
|
|
73
|
+
<Autocomplete
|
|
74
|
+
:options="options"
|
|
75
|
+
v-model="single"
|
|
76
|
+
placeholder="Select person"
|
|
77
|
+
hide-search="true"
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
</Variant>
|
|
81
|
+
<Variant title="Multiple options">
|
|
82
|
+
<div class="p-2">
|
|
83
|
+
<Autocomplete
|
|
84
|
+
:options="options"
|
|
85
|
+
v-model="people"
|
|
86
|
+
placeholder="Select people"
|
|
87
|
+
multiple="true"
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
</Variant>
|
|
91
|
+
<Variant title="Multiple options without search">
|
|
92
|
+
<div class="p-2">
|
|
93
|
+
<Autocomplete
|
|
94
|
+
:options="options"
|
|
95
|
+
v-model="people"
|
|
96
|
+
placeholder="Select people"
|
|
97
|
+
multiple="true"
|
|
98
|
+
hide-search="true"
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
</Variant>
|
|
24
102
|
</Story>
|
|
25
103
|
</template>
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Combobox
|
|
2
|
+
<Combobox
|
|
3
|
+
v-model="selectedValue"
|
|
4
|
+
:multiple="multiple"
|
|
5
|
+
nullable
|
|
6
|
+
v-slot="{ open: isComboboxOpen }"
|
|
7
|
+
>
|
|
3
8
|
<Popover class="w-full" v-model:show="showOptions">
|
|
4
9
|
<template #target="{ open: openPopover, togglePopover }">
|
|
5
10
|
<slot name="target" v-bind="{ open: openPopover, togglePopover }">
|
|
@@ -9,12 +14,9 @@
|
|
|
9
14
|
:class="{ 'bg-gray-200': isComboboxOpen }"
|
|
10
15
|
@click="() => togglePopover()"
|
|
11
16
|
>
|
|
12
|
-
<div class="flex items-center">
|
|
17
|
+
<div class="flex items-center overflow-hidden">
|
|
13
18
|
<slot name="prefix" />
|
|
14
|
-
<span
|
|
15
|
-
class="overflow-hidden text-ellipsis whitespace-nowrap text-base leading-5"
|
|
16
|
-
v-if="selectedValue"
|
|
17
|
-
>
|
|
19
|
+
<span class="truncate text-base leading-5" v-if="selectedValue">
|
|
18
20
|
{{ displayValue(selectedValue) }}
|
|
19
21
|
</span>
|
|
20
22
|
<span class="text-base leading-5 text-gray-500" v-else>
|
|
@@ -30,97 +32,161 @@
|
|
|
30
32
|
</div>
|
|
31
33
|
</slot>
|
|
32
34
|
</template>
|
|
33
|
-
<template #body="{ isOpen }">
|
|
35
|
+
<template #body="{ isOpen, togglePopover }">
|
|
34
36
|
<div v-show="isOpen">
|
|
35
|
-
<
|
|
36
|
-
class="mt-1
|
|
37
|
-
|
|
37
|
+
<div
|
|
38
|
+
class="relative mt-1 rounded-lg bg-white text-base shadow-2xl"
|
|
39
|
+
:class="bodyClasses"
|
|
38
40
|
>
|
|
39
|
-
<
|
|
40
|
-
class="
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<ComboboxInput
|
|
44
|
-
ref="search"
|
|
45
|
-
class="form-input w-full"
|
|
46
|
-
type="text"
|
|
47
|
-
@change="
|
|
48
|
-
(e) => {
|
|
49
|
-
query = e.target.value
|
|
50
|
-
}
|
|
51
|
-
"
|
|
52
|
-
:value="query"
|
|
53
|
-
autocomplete="off"
|
|
54
|
-
placeholder="Search"
|
|
55
|
-
/>
|
|
56
|
-
<button
|
|
57
|
-
class="absolute right-0 inline-flex h-7 w-7 items-center justify-center"
|
|
58
|
-
@click="selectedValue = null"
|
|
59
|
-
>
|
|
60
|
-
<FeatherIcon name="x" class="w-4" />
|
|
61
|
-
</button>
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
<div
|
|
65
|
-
class="mt-1.5"
|
|
66
|
-
v-for="group in groups"
|
|
67
|
-
:key="group.key"
|
|
68
|
-
v-show="group.items.length > 0"
|
|
41
|
+
<ComboboxOptions
|
|
42
|
+
class="max-h-[15rem] overflow-y-auto px-1.5 pb-1.5"
|
|
43
|
+
:class="{ 'pt-1.5': hideSearch }"
|
|
44
|
+
static
|
|
69
45
|
>
|
|
70
46
|
<div
|
|
71
|
-
v-if="
|
|
72
|
-
class="
|
|
47
|
+
v-if="!hideSearch"
|
|
48
|
+
class="sticky top-0 z-10 flex items-stretch space-x-1.5 bg-white py-1.5"
|
|
73
49
|
>
|
|
74
|
-
|
|
50
|
+
<div class="relative w-full">
|
|
51
|
+
<ComboboxInput
|
|
52
|
+
ref="searchInput"
|
|
53
|
+
class="form-input w-full"
|
|
54
|
+
type="text"
|
|
55
|
+
@change="
|
|
56
|
+
(e) => {
|
|
57
|
+
query = e.target.value
|
|
58
|
+
}
|
|
59
|
+
"
|
|
60
|
+
:value="query"
|
|
61
|
+
autocomplete="off"
|
|
62
|
+
placeholder="Search"
|
|
63
|
+
/>
|
|
64
|
+
<button
|
|
65
|
+
class="absolute right-0 inline-flex h-7 w-7 items-center justify-center"
|
|
66
|
+
@click="selectedValue = null"
|
|
67
|
+
>
|
|
68
|
+
<FeatherIcon name="x" class="w-4" />
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
75
71
|
</div>
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
:value="option"
|
|
81
|
-
v-slot="{ active, selected }"
|
|
72
|
+
<div
|
|
73
|
+
v-for="group in groups"
|
|
74
|
+
:key="group.key"
|
|
75
|
+
v-show="group.items.length > 0"
|
|
82
76
|
>
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
77
|
+
<div
|
|
78
|
+
v-if="group.group && !group.hideLabel"
|
|
79
|
+
class="sticky top-10 truncate bg-white px-2.5 py-1.5 text-sm font-medium text-gray-600"
|
|
80
|
+
>
|
|
81
|
+
{{ group.group }}
|
|
82
|
+
</div>
|
|
83
|
+
<ComboboxOption
|
|
84
|
+
as="template"
|
|
85
|
+
v-for="(option, idx) in group.items.slice(0, 50)"
|
|
86
|
+
:key="option?.value || idx"
|
|
87
|
+
:value="option"
|
|
88
|
+
v-slot="{ active, selected }"
|
|
88
89
|
>
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
<li
|
|
91
|
+
:class="[
|
|
92
|
+
'flex cursor-pointer items-center justify-between rounded px-2.5 py-1.5 text-base',
|
|
93
|
+
{ 'bg-gray-100': active },
|
|
94
|
+
]"
|
|
95
|
+
>
|
|
96
|
+
<div class="flex flex-1 gap-2 overflow-hidden">
|
|
97
|
+
<div
|
|
98
|
+
v-if="$slots['item-prefix'] || $props.multiple"
|
|
99
|
+
class="flex-shrink-0"
|
|
100
|
+
>
|
|
101
|
+
<slot
|
|
102
|
+
name="item-prefix"
|
|
103
|
+
v-bind="{ active, selected, option }"
|
|
104
|
+
>
|
|
105
|
+
<FeatherIcon
|
|
106
|
+
name="check"
|
|
107
|
+
v-if="isOptionSelected(option)"
|
|
108
|
+
class="h-4 w-4 text-gray-700"
|
|
109
|
+
/>
|
|
110
|
+
<div v-else class="h-4 w-4" />
|
|
111
|
+
</slot>
|
|
112
|
+
</div>
|
|
113
|
+
<span class="flex-1 truncate">
|
|
114
|
+
{{ getLabel(option) }}
|
|
115
|
+
</span>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div
|
|
119
|
+
v-if="$slots['item-suffix'] || option?.description"
|
|
120
|
+
class="ml-2 flex-shrink-0"
|
|
121
|
+
>
|
|
122
|
+
<slot
|
|
123
|
+
name="item-suffix"
|
|
124
|
+
v-bind="{ active, selected, option }"
|
|
125
|
+
>
|
|
126
|
+
<div
|
|
127
|
+
v-if="option?.description"
|
|
128
|
+
class="text-sm text-gray-600"
|
|
129
|
+
>
|
|
130
|
+
{{ option.description }}
|
|
131
|
+
</div>
|
|
132
|
+
</slot>
|
|
133
|
+
</div>
|
|
134
|
+
</li>
|
|
135
|
+
</ComboboxOption>
|
|
136
|
+
</div>
|
|
137
|
+
<li
|
|
138
|
+
v-if="groups.length == 0"
|
|
139
|
+
class="rounded-md px-2.5 py-1.5 text-base text-gray-600"
|
|
140
|
+
>
|
|
141
|
+
No results found
|
|
142
|
+
</li>
|
|
143
|
+
</ComboboxOptions>
|
|
144
|
+
|
|
145
|
+
<div v-if="$slots.footer || multiple" class="border-t p-1">
|
|
146
|
+
<slot name="footer" v-bind="{ togglePopover }">
|
|
147
|
+
<div v-if="multiple" class="flex items-center justify-end">
|
|
148
|
+
<Button
|
|
149
|
+
v-if="!areAllOptionsSelected"
|
|
150
|
+
label="Select All"
|
|
151
|
+
@click.stop="selectAll"
|
|
92
152
|
/>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
153
|
+
<Button
|
|
154
|
+
v-if="areAllOptionsSelected"
|
|
155
|
+
label="Clear All"
|
|
156
|
+
@click.stop="clearAll"
|
|
157
|
+
/></div
|
|
158
|
+
></slot>
|
|
96
159
|
</div>
|
|
97
|
-
|
|
98
|
-
v-if="groups.length == 0"
|
|
99
|
-
class="rounded-md px-2.5 py-1.5 text-base text-gray-600"
|
|
100
|
-
>
|
|
101
|
-
No results found
|
|
102
|
-
</li>
|
|
103
|
-
</ComboboxOptions>
|
|
160
|
+
</div>
|
|
104
161
|
</div>
|
|
105
162
|
</template>
|
|
106
163
|
</Popover>
|
|
107
164
|
</Combobox>
|
|
108
165
|
</template>
|
|
166
|
+
|
|
109
167
|
<script>
|
|
110
168
|
import {
|
|
111
169
|
Combobox,
|
|
170
|
+
ComboboxButton,
|
|
112
171
|
ComboboxInput,
|
|
113
|
-
ComboboxOptions,
|
|
114
172
|
ComboboxOption,
|
|
115
|
-
|
|
173
|
+
ComboboxOptions,
|
|
116
174
|
} from '@headlessui/vue'
|
|
175
|
+
import { nextTick } from 'vue'
|
|
117
176
|
import Popover from './Popover.vue'
|
|
118
177
|
import Button from './Button.vue'
|
|
119
178
|
import FeatherIcon from './FeatherIcon.vue'
|
|
120
179
|
|
|
121
180
|
export default {
|
|
122
181
|
name: 'Autocomplete',
|
|
123
|
-
props: [
|
|
182
|
+
props: [
|
|
183
|
+
'modelValue',
|
|
184
|
+
'options',
|
|
185
|
+
'placeholder',
|
|
186
|
+
'bodyClasses',
|
|
187
|
+
'multiple',
|
|
188
|
+
'hideSearch',
|
|
189
|
+
],
|
|
124
190
|
emits: ['update:modelValue', 'update:query', 'change'],
|
|
125
191
|
components: {
|
|
126
192
|
Popover,
|
|
@@ -132,6 +198,7 @@ export default {
|
|
|
132
198
|
ComboboxOption,
|
|
133
199
|
ComboboxButton,
|
|
134
200
|
},
|
|
201
|
+
expose: ['togglePopover'],
|
|
135
202
|
data() {
|
|
136
203
|
return {
|
|
137
204
|
query: '',
|
|
@@ -139,19 +206,25 @@ export default {
|
|
|
139
206
|
}
|
|
140
207
|
},
|
|
141
208
|
computed: {
|
|
142
|
-
valuePropPassed() {
|
|
143
|
-
return 'value' in this.$attrs
|
|
144
|
-
},
|
|
145
209
|
selectedValue: {
|
|
146
210
|
get() {
|
|
147
|
-
|
|
211
|
+
if (!this.multiple) {
|
|
212
|
+
return this.findOption(this.modelValue)
|
|
213
|
+
}
|
|
214
|
+
// in case of `multiple`, modelValue is an array of values
|
|
215
|
+
// if the modelValue is a list of values, convert them to options
|
|
216
|
+
return isOptionOrValue(this.modelValue?.[0]) === 'value'
|
|
217
|
+
? this.modelValue?.map((v) => this.findOption(v))
|
|
218
|
+
: this.modelValue
|
|
148
219
|
},
|
|
149
220
|
set(val) {
|
|
150
221
|
this.query = ''
|
|
151
|
-
if (val)
|
|
152
|
-
|
|
222
|
+
if (val && !this.multiple) this.showOptions = false
|
|
223
|
+
if (!this.multiple) {
|
|
224
|
+
this.$emit('update:modelValue', val)
|
|
225
|
+
return
|
|
153
226
|
}
|
|
154
|
-
this.$emit(
|
|
227
|
+
this.$emit('update:modelValue', val)
|
|
155
228
|
},
|
|
156
229
|
},
|
|
157
230
|
groups() {
|
|
@@ -159,7 +232,7 @@ export default {
|
|
|
159
232
|
|
|
160
233
|
let groups = this.options[0]?.group
|
|
161
234
|
? this.options
|
|
162
|
-
: [{ group: '', items: this.options }]
|
|
235
|
+
: [{ group: '', items: this.sanitizeOptions(this.options) }]
|
|
163
236
|
|
|
164
237
|
return groups
|
|
165
238
|
.map((group, i) => {
|
|
@@ -167,47 +240,89 @@ export default {
|
|
|
167
240
|
key: i,
|
|
168
241
|
group: group.group,
|
|
169
242
|
hideLabel: group.hideLabel || false,
|
|
170
|
-
items: this.filterOptions(group.items),
|
|
243
|
+
items: this.filterOptions(this.sanitizeOptions(group.items)),
|
|
171
244
|
}
|
|
172
245
|
})
|
|
173
246
|
.filter((group) => group.items.length > 0)
|
|
174
247
|
},
|
|
248
|
+
allOptions() {
|
|
249
|
+
return this.groups.flatMap((group) => group.items)
|
|
250
|
+
},
|
|
251
|
+
areAllOptionsSelected() {
|
|
252
|
+
if (!this.multiple) return false
|
|
253
|
+
return this.allOptions.length === this.selectedValue?.length
|
|
254
|
+
},
|
|
175
255
|
},
|
|
176
256
|
watch: {
|
|
177
257
|
query(q) {
|
|
178
258
|
this.$emit('update:query', q)
|
|
179
259
|
},
|
|
180
260
|
showOptions(val) {
|
|
181
|
-
if (val)
|
|
182
|
-
this.$nextTick(() => {
|
|
183
|
-
this.$refs.search.el.focus()
|
|
184
|
-
})
|
|
185
|
-
}
|
|
261
|
+
if (val) nextTick(() => this.$refs.searchInput?.$el?.focus())
|
|
186
262
|
},
|
|
187
263
|
},
|
|
188
264
|
methods: {
|
|
265
|
+
togglePopover(val) {
|
|
266
|
+
this.showOptions = val ?? !this.showOptions
|
|
267
|
+
},
|
|
268
|
+
findOption(option) {
|
|
269
|
+
if (!option) return option
|
|
270
|
+
const value = isOptionOrValue(option) === 'value' ? option : option.value
|
|
271
|
+
return this.allOptions.find((o) => o.value === value)
|
|
272
|
+
},
|
|
189
273
|
filterOptions(options) {
|
|
190
|
-
if (!this.query)
|
|
191
|
-
return options
|
|
192
|
-
}
|
|
274
|
+
if (!this.query) return options
|
|
193
275
|
return options.filter((option) => {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
(
|
|
197
|
-
.toString()
|
|
198
|
-
.toLowerCase()
|
|
199
|
-
.includes(this.query.toLowerCase())
|
|
276
|
+
return (
|
|
277
|
+
option.label.toLowerCase().includes(this.query.toLowerCase()) ||
|
|
278
|
+
option.value.toLowerCase().includes(this.query.toLowerCase())
|
|
200
279
|
)
|
|
201
280
|
})
|
|
202
281
|
},
|
|
203
282
|
displayValue(option) {
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return
|
|
283
|
+
if (!option) return ''
|
|
284
|
+
|
|
285
|
+
if (!this.multiple) {
|
|
286
|
+
return this.getLabel(this.findOption(option))
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!Array.isArray(option)) return ''
|
|
290
|
+
|
|
291
|
+
// in case of `multiple`, option is an array of values
|
|
292
|
+
// so the display value should be comma separated labels
|
|
293
|
+
return option.map((v) => this.getLabel(this.findOption(v))).join(', ')
|
|
294
|
+
},
|
|
295
|
+
getLabel(option) {
|
|
296
|
+
if (isOptionOrValue(option) === 'value') return option
|
|
297
|
+
return option?.label || option?.value || 'No label'
|
|
298
|
+
},
|
|
299
|
+
sanitizeOptions(options) {
|
|
300
|
+
if (!options) return []
|
|
301
|
+
// in case the options are just values, convert them to objects
|
|
302
|
+
return options.map((option) => {
|
|
303
|
+
return isOptionOrValue(option) === 'option'
|
|
304
|
+
? option
|
|
305
|
+
: { label: option, value: option }
|
|
306
|
+
})
|
|
307
|
+
},
|
|
308
|
+
isOptionSelected(option) {
|
|
309
|
+
if (!this.selectedValue) return false
|
|
310
|
+
const value = isOptionOrValue(option) === 'value' ? option : option.value
|
|
311
|
+
if (!this.multiple) {
|
|
312
|
+
return this.selectedValue?.value === value
|
|
208
313
|
}
|
|
209
|
-
return
|
|
314
|
+
return this.selectedValue?.find((v) => v && v.value === value)
|
|
315
|
+
},
|
|
316
|
+
selectAll() {
|
|
317
|
+
this.selectedValue = this.allOptions
|
|
318
|
+
},
|
|
319
|
+
clearAll() {
|
|
320
|
+
this.selectedValue = []
|
|
210
321
|
},
|
|
211
322
|
},
|
|
212
323
|
}
|
|
324
|
+
|
|
325
|
+
function isOptionOrValue(optionOrValue) {
|
|
326
|
+
return typeof optionOrValue === 'object' ? 'option' : 'value'
|
|
327
|
+
}
|
|
213
328
|
</script>
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
<slot name="body-main">
|
|
54
54
|
<div class="bg-white px-4 pb-6 pt-5 sm:px-6">
|
|
55
55
|
<div class="flex">
|
|
56
|
-
<div class="flex-1">
|
|
56
|
+
<div class="w-full flex-1">
|
|
57
57
|
<div class="mb-6 flex items-center justify-between">
|
|
58
58
|
<div class="flex items-center space-x-2">
|
|
59
59
|
<div
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { reactive } from 'vue'
|
|
2
|
+
import { reactive, ref } from 'vue'
|
|
3
3
|
import FormControl from './FormControl.vue'
|
|
4
4
|
import FeatherIcon from './FeatherIcon.vue'
|
|
5
5
|
import Avatar from './Avatar.vue'
|
|
@@ -10,8 +10,11 @@ const state = reactive({
|
|
|
10
10
|
placeholder: 'Placeholder',
|
|
11
11
|
disabled: false,
|
|
12
12
|
label: 'Label',
|
|
13
|
-
modelValue: '',
|
|
14
13
|
})
|
|
14
|
+
const inputValue = ref('')
|
|
15
|
+
const selectValue = ref(null)
|
|
16
|
+
const autocompleteValue = ref(null)
|
|
17
|
+
const checkboxValue = ref(false)
|
|
15
18
|
|
|
16
19
|
const inputTypes = [
|
|
17
20
|
'text',
|
|
@@ -34,11 +37,7 @@ const variants = ['subtle', 'outline']
|
|
|
34
37
|
:title="inputType"
|
|
35
38
|
>
|
|
36
39
|
<div class="p-2">
|
|
37
|
-
<FormControl
|
|
38
|
-
:type="inputType"
|
|
39
|
-
v-bind="state"
|
|
40
|
-
v-model="state.modelValue"
|
|
41
|
-
/>
|
|
40
|
+
<FormControl :type="inputType" v-bind="state" v-model="inputValue" />
|
|
42
41
|
</div>
|
|
43
42
|
</Variant>
|
|
44
43
|
<Variant title="select">
|
|
@@ -51,10 +50,11 @@ const variants = ['subtle', 'outline']
|
|
|
51
50
|
{ label: 'Three', value: '3' },
|
|
52
51
|
]"
|
|
53
52
|
v-bind="state"
|
|
53
|
+
v-model="selectValue"
|
|
54
54
|
/>
|
|
55
55
|
</div>
|
|
56
56
|
</Variant>
|
|
57
|
-
<Variant title="
|
|
57
|
+
<Variant title="autocomplete">
|
|
58
58
|
<div class="p-2">
|
|
59
59
|
<FormControl
|
|
60
60
|
type="autocomplete"
|
|
@@ -64,12 +64,13 @@ const variants = ['subtle', 'outline']
|
|
|
64
64
|
{ label: 'Three', value: '3' },
|
|
65
65
|
]"
|
|
66
66
|
v-bind="state"
|
|
67
|
+
v-model="autocompleteValue"
|
|
67
68
|
/>
|
|
68
69
|
</div>
|
|
69
70
|
</Variant>
|
|
70
71
|
<Variant title="checkbox">
|
|
71
72
|
<div class="p-2">
|
|
72
|
-
<FormControl type="checkbox" v-bind="state" />
|
|
73
|
+
<FormControl type="checkbox" v-bind="state" v-model="checkboxValue" />
|
|
73
74
|
</div>
|
|
74
75
|
</Variant>
|
|
75
76
|
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
</template>
|
|
115
115
|
|
|
116
116
|
<script setup>
|
|
117
|
-
import { Autocomplete, FeatherIcon, FormControl } from '
|
|
117
|
+
import { Autocomplete, FeatherIcon, FormControl } from '../../index'
|
|
118
118
|
import { computed, h } from 'vue'
|
|
119
119
|
import FilterIcon from './FilterIcon.vue'
|
|
120
120
|
import NestedPopover from './NestedPopover.vue'
|