@ouestfrance/sipa-bms-ui 8.20.0 → 8.21.0
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/dist/components/form/BmsFilePicker.vue.d.ts +4 -0
- package/dist/components/form/BmsMultiSelect.vue.d.ts +4 -0
- package/dist/components/form/RawAutocomplete.vue.d.ts +8 -0
- package/dist/mockServiceWorker.js +1 -1
- package/dist/sipa-bms-ui.css +54 -368
- package/dist/sipa-bms-ui.es.js +113 -44
- package/dist/sipa-bms-ui.es.js.map +1 -1
- package/dist/sipa-bms-ui.umd.js +112 -43
- package/dist/sipa-bms-ui.umd.js.map +1 -1
- package/package.json +12 -12
- package/src/assets/scss/_conf.scss +0 -1
- package/src/assets/scss/app.scss +0 -1
- package/src/components/button/BmsAllButtons.stories.js +50 -23
- package/src/components/button/BmsButton.stories.js +153 -59
- package/src/components/form/BmsFilePicker.stories.js +6 -1
- package/src/components/form/BmsFilePicker.vue +10 -5
- package/src/components/form/BmsMultiSelect.vue +32 -25
- package/src/components/form/BmsSelect.vue +18 -16
- package/src/components/form/RawAutocomplete.vue +16 -4
- package/src/components/form/RawInputText.vue +1 -0
- package/src/components/layout/BmsModal.stories.js +2 -1
- package/src/components/layout/BmsSplitWindow.vue +0 -1
- package/src/components/table/BmsTableFilters.vue +1 -1
- package/src/documentation/button/primaryButton.mdx +142 -0
- package/src/documentation/{secondaryButton.mdx → button/secondaryButton.mdx} +2 -2
- package/src/documentation/foundation/contributing.mdx +72 -0
- package/src/documentation/foundation/gettingstarted.mdx +7 -0
- package/src/documentation/{principles.mdx → foundation/principles.mdx} +9 -9
- package/src/documentation/icons.mdx +43 -0
- package/src/showroom/pages/forms.vue +10 -1
- package/src/assets/scss/_formkit.scss +0 -353
- package/src/components/form/Form.stories.js +0 -35
- package/src/documentation/primaryButton.mdx +0 -20
- /package/src/documentation/{button.mdx → button/button.mdx} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouestfrance/sipa-bms-ui",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.21.0",
|
|
4
4
|
"author": "Ouest-France BMS",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"scripts": {
|
|
@@ -30,16 +30,15 @@
|
|
|
30
30
|
"url": "https://gitlab.ouest-france.fr/sipa-ouest-france/platform/platform-library-vuejs-bms.git"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@chromatic-com/storybook": "^4.1.
|
|
33
|
+
"@chromatic-com/storybook": "^4.1.3",
|
|
34
34
|
"@codemirror/lang-html": "6.4.11",
|
|
35
35
|
"@codemirror/lang-json": "6.0.2",
|
|
36
36
|
"@commitlint/cli": "20.1.0",
|
|
37
37
|
"@commitlint/config-conventional": "20.0.0",
|
|
38
|
-
"@formkit/vue": "1.6.9",
|
|
39
38
|
"@mdx-js/react": "3.1.1",
|
|
40
|
-
"@storybook/addon-docs": "10.
|
|
41
|
-
"@storybook/addon-links": "10.
|
|
42
|
-
"@storybook/vue3-vite": "10.
|
|
39
|
+
"@storybook/addon-docs": "10.1.6",
|
|
40
|
+
"@storybook/addon-links": "10.1.6",
|
|
41
|
+
"@storybook/vue3-vite": "10.1.6",
|
|
43
42
|
"@types/lodash": "4.17.21",
|
|
44
43
|
"@types/uuid": "11.0.0",
|
|
45
44
|
"@vitejs/plugin-vue": "6.0.2",
|
|
@@ -61,15 +60,17 @@
|
|
|
61
60
|
"lint-staged": "16.2.7",
|
|
62
61
|
"lodash": "4.17.21",
|
|
63
62
|
"lucide-vue-next": "0.554.0",
|
|
63
|
+
"msw": "^2.12.4",
|
|
64
64
|
"msw-storybook-addon": "^2.0.3",
|
|
65
65
|
"normalize.css": "8.0.1",
|
|
66
66
|
"path": "0.12.7",
|
|
67
67
|
"prettier": "3.6.2",
|
|
68
|
+
"remark-gfm": "^4.0.1",
|
|
68
69
|
"sass": "1.94.2",
|
|
69
70
|
"semantic-release": "25.0.2",
|
|
70
71
|
"start-server-and-test": "2.1.3",
|
|
71
|
-
"storybook": "10.
|
|
72
|
-
"storybook-addon-pseudo-states": "10.
|
|
72
|
+
"storybook": "10.1.6",
|
|
73
|
+
"storybook-addon-pseudo-states": "10.1.6",
|
|
73
74
|
"storybook-addon-tag-badges": "^3.0.2",
|
|
74
75
|
"storybook-vue3-router": "^7.0.0",
|
|
75
76
|
"typescript": "5.2.2",
|
|
@@ -118,9 +119,8 @@
|
|
|
118
119
|
]
|
|
119
120
|
},
|
|
120
121
|
"msw": {
|
|
121
|
-
"workerDirectory":
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
"msw": "^2.3.1"
|
|
122
|
+
"workerDirectory": [
|
|
123
|
+
"public"
|
|
124
|
+
]
|
|
125
125
|
}
|
|
126
126
|
}
|
package/src/assets/scss/app.scss
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import BmsButton from '@/components/button/BmsButton.vue';
|
|
2
2
|
import BmsIconButton from '@/components/button/BmsIconButton.vue';
|
|
3
|
-
import { Heart } from 'lucide-vue-next';
|
|
3
|
+
import { Heart, ArrowLeft, ArrowRight } from 'lucide-vue-next';
|
|
4
4
|
|
|
5
5
|
const types = ['primary', 'secondary', 'tertiary'];
|
|
6
6
|
const modes = ['default', 'danger'];
|
|
7
7
|
|
|
8
8
|
export default {
|
|
9
|
-
title: 'Composants/button',
|
|
9
|
+
title: 'Composants/button/Button',
|
|
10
10
|
component: BmsButton,
|
|
11
11
|
};
|
|
12
12
|
|
|
@@ -14,41 +14,68 @@ const AllButtonsTemplate = () => ({
|
|
|
14
14
|
components: {
|
|
15
15
|
BmsButton,
|
|
16
16
|
BmsIconButton,
|
|
17
|
+
ArrowLeft,
|
|
18
|
+
ArrowRight,
|
|
17
19
|
Heart,
|
|
18
20
|
},
|
|
19
21
|
setup() {
|
|
20
22
|
return { types, modes };
|
|
21
23
|
},
|
|
22
24
|
template: `
|
|
23
|
-
<div style="display:grid;grid-template-columns:repeat(
|
|
25
|
+
<div style="display:grid;grid-template-columns:repeat(4, auto); gap:1em; justify-items: start; align-items: center;">
|
|
24
26
|
<template v-for="mode in modes" :key="mode">
|
|
25
27
|
<template v-for="type in types">
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Button {{ mode }} {{ type }} {{ to ? ' avec une URL ' : '' }}
|
|
28
|
+
<BmsButton :mode="mode" :type="type" small>
|
|
29
|
+
Button {{ mode }} {{ type }} small
|
|
29
30
|
</BmsButton>
|
|
30
|
-
<BmsButton :mode="mode" :type="type"
|
|
31
|
-
Button {{ mode }} {{ type }}
|
|
31
|
+
<BmsButton :mode="mode" :type="type">
|
|
32
|
+
Button {{ mode }} {{ type }}
|
|
32
33
|
</BmsButton>
|
|
33
|
-
<BmsButton :mode="mode" :type="type"
|
|
34
|
-
Button {{ mode }} {{ type }}
|
|
34
|
+
<BmsButton :mode="mode" :type="type" class="_hover">
|
|
35
|
+
Button {{ mode }} {{ type }} hovered
|
|
36
|
+
</BmsButton>
|
|
37
|
+
<BmsButton :mode="mode" :type="type" disabled>
|
|
38
|
+
Button {{ mode }} {{ type }} disabled
|
|
35
39
|
</BmsButton>
|
|
36
|
-
</template>
|
|
37
40
|
</template>
|
|
38
41
|
</template>
|
|
42
|
+
|
|
43
|
+
<BmsButton small>
|
|
44
|
+
<template #start> <ArrowLeft/> </template>
|
|
45
|
+
Button small
|
|
46
|
+
<template #end> <ArrowRight/> </template>
|
|
47
|
+
</BmsButton>
|
|
48
|
+
<BmsButton>
|
|
49
|
+
<template #start> <ArrowLeft/> </template>
|
|
50
|
+
Button
|
|
51
|
+
<template #end> <ArrowRight/> </template>
|
|
52
|
+
</BmsButton>
|
|
53
|
+
<BmsButton class="_hover">
|
|
54
|
+
<template #start> <ArrowLeft/> </template>
|
|
55
|
+
Button hovered
|
|
56
|
+
<template #end> <ArrowRight/> </template>
|
|
57
|
+
</BmsButton>
|
|
58
|
+
<BmsButton disabled>
|
|
59
|
+
<template #start> <ArrowLeft/> </template>
|
|
60
|
+
Button disabled
|
|
61
|
+
<template #end> <ArrowRight/> </template>
|
|
62
|
+
</BmsButton>
|
|
63
|
+
|
|
39
64
|
|
|
40
|
-
<
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
65
|
+
<template v-for="mode in modes" :key="mode">
|
|
66
|
+
<BmsIconButton v-bind="args" :mode="mode" small>
|
|
67
|
+
<Heart />
|
|
68
|
+
</BmsIconButton>
|
|
69
|
+
<BmsIconButton v-bind="args" :mode="mode">
|
|
70
|
+
<Heart />
|
|
71
|
+
</BmsIconButton>
|
|
72
|
+
<BmsIconButton v-bind="args" :mode="mode" class="_hover">
|
|
73
|
+
<Heart />
|
|
74
|
+
</BmsIconButton>
|
|
75
|
+
<BmsIconButton v-bind="args" :mode="mode" disabled>
|
|
76
|
+
<Heart />
|
|
77
|
+
</BmsIconButton>
|
|
78
|
+
</template>
|
|
52
79
|
</div>
|
|
53
80
|
`,
|
|
54
81
|
});
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import BmsButton from '@/components/button/BmsButton.vue';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ArrowLeft,
|
|
4
|
+
ArrowRight,
|
|
5
|
+
Camera,
|
|
6
|
+
Heart,
|
|
7
|
+
Wand,
|
|
8
|
+
Check,
|
|
9
|
+
} from 'lucide-vue-next';
|
|
3
10
|
import { StatusType } from '@/models';
|
|
4
11
|
|
|
5
12
|
export default {
|
|
6
13
|
title: 'Composants/button/Button',
|
|
7
14
|
component: BmsButton,
|
|
8
15
|
tags: ['with_useable_code'],
|
|
16
|
+
parameters: {
|
|
17
|
+
chromatic: { disable: true },
|
|
18
|
+
},
|
|
9
19
|
argTypes: {
|
|
10
20
|
type: {
|
|
11
21
|
control: { type: 'select' },
|
|
@@ -25,78 +35,162 @@ export default {
|
|
|
25
35
|
const Template = (args) => ({
|
|
26
36
|
components: {
|
|
27
37
|
BmsButton,
|
|
28
|
-
ArrowRight,
|
|
29
|
-
ArrowLeft,
|
|
30
|
-
Camera,
|
|
31
|
-
Heart,
|
|
32
|
-
Wand,
|
|
33
38
|
},
|
|
34
39
|
setup() {
|
|
35
40
|
return { args };
|
|
36
41
|
},
|
|
37
42
|
template: `
|
|
38
|
-
<BmsButton v-bind="args">Save me</BmsButton>
|
|
39
|
-
<br>
|
|
40
|
-
<br>
|
|
41
|
-
<BmsButton v-bind="args">
|
|
42
|
-
Next page
|
|
43
|
-
<template #end><ArrowRight /></template>
|
|
44
|
-
</BmsButton>
|
|
45
|
-
<br>
|
|
46
|
-
<br>
|
|
47
|
-
<BmsButton v-bind="args">
|
|
48
|
-
<template #start>
|
|
49
|
-
<Heart/>
|
|
50
|
-
</template>
|
|
51
|
-
Icons left & right
|
|
52
|
-
<template #end>
|
|
53
|
-
<Wand/>
|
|
54
|
-
</template>
|
|
55
|
-
</BmsButton>
|
|
56
|
-
<br>
|
|
57
|
-
<br>
|
|
58
43
|
<BmsButton v-bind="args">
|
|
59
|
-
|
|
60
|
-
Retour
|
|
44
|
+
Save
|
|
61
45
|
</BmsButton>
|
|
62
|
-
|
|
63
|
-
`,
|
|
46
|
+
`,
|
|
64
47
|
});
|
|
65
48
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
default: 'Mon texte',
|
|
69
|
-
end: '<Wand/>',
|
|
70
|
-
};
|
|
49
|
+
export const Playground = Template.bind({});
|
|
50
|
+
Playground.args = {};
|
|
71
51
|
|
|
72
|
-
|
|
73
|
-
|
|
52
|
+
// Stories for documentation examples (Do/Don't)
|
|
53
|
+
export const DoSimple = Template.bind({});
|
|
54
|
+
DoSimple.args = {
|
|
74
55
|
type: 'primary',
|
|
75
|
-
...WITH_DEFAULT_SLOT,
|
|
76
56
|
};
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
...WITH_DEFAULT_SLOT,
|
|
81
|
-
type: 'primary',
|
|
82
|
-
mode: StatusType.Danger,
|
|
57
|
+
export const DoDanger = Template.bind({});
|
|
58
|
+
DoDanger.args = {
|
|
59
|
+
mode: 'danger',
|
|
83
60
|
};
|
|
84
61
|
|
|
85
|
-
export const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
type: 'secondary',
|
|
62
|
+
export const DoSmall = Template.bind({});
|
|
63
|
+
DoSmall.args = {
|
|
64
|
+
small: true,
|
|
89
65
|
};
|
|
90
66
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
67
|
+
// Do: Button with icon at start
|
|
68
|
+
export const DoIconStart = () => ({
|
|
69
|
+
components: { BmsButton, Check },
|
|
70
|
+
template: `
|
|
71
|
+
<BmsButton type="primary">
|
|
72
|
+
<template #start><Check /></template>
|
|
73
|
+
Confirm
|
|
74
|
+
</BmsButton>
|
|
75
|
+
`,
|
|
76
|
+
});
|
|
96
77
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
78
|
+
// Do: Button with icon at end
|
|
79
|
+
export const DoIconEnd = () => ({
|
|
80
|
+
components: { BmsButton, ArrowRight },
|
|
81
|
+
template: `
|
|
82
|
+
<BmsButton type="primary">
|
|
83
|
+
Next
|
|
84
|
+
<template #end><ArrowRight /></template>
|
|
85
|
+
</BmsButton>
|
|
86
|
+
`,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Do: Submit button
|
|
90
|
+
export const DoSubmit = () => ({
|
|
91
|
+
components: { BmsButton },
|
|
92
|
+
template: `
|
|
93
|
+
<form>
|
|
94
|
+
<BmsButton type="primary" :submit="true">Submit form</BmsButton>
|
|
95
|
+
</form>
|
|
96
|
+
`,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Don't: Long labels
|
|
100
|
+
export const DontLongLabel = () => ({
|
|
101
|
+
components: { BmsButton },
|
|
102
|
+
template:
|
|
103
|
+
'<BmsButton type="primary">Click here to save your changes to the document</BmsButton>',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
export const DoShortLabel = () => ({
|
|
107
|
+
components: { BmsButton },
|
|
108
|
+
template: '<BmsButton type="primary">Save changes</BmsButton>',
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Don't: Multiple primary buttons
|
|
112
|
+
export const DontMultiplePrimary = () => ({
|
|
113
|
+
components: { BmsButton },
|
|
114
|
+
template: `
|
|
115
|
+
<div style="display: flex; gap: 8px;">
|
|
116
|
+
<BmsButton type="primary">Save</BmsButton>
|
|
117
|
+
<BmsButton type="primary">Cancel</BmsButton>
|
|
118
|
+
<BmsButton type="primary">Delete</BmsButton>
|
|
119
|
+
</div>
|
|
120
|
+
`,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
export const DoOnePrimary = () => ({
|
|
124
|
+
components: { BmsButton },
|
|
125
|
+
template: `
|
|
126
|
+
<div style="display: flex; gap: 8px;">
|
|
127
|
+
<BmsButton type="primary">Save</BmsButton>
|
|
128
|
+
<BmsButton type="secondary">Cancel</BmsButton>
|
|
129
|
+
<BmsButton type="secondary">Delete</BmsButton>
|
|
130
|
+
</div>
|
|
131
|
+
`,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Don't: Too many icons
|
|
135
|
+
export const DontTooManyIcons = () => ({
|
|
136
|
+
components: { BmsButton, Heart, Wand, Check, ArrowRight },
|
|
137
|
+
template: `
|
|
138
|
+
<BmsButton type="primary">
|
|
139
|
+
<template #start><Heart /><Wand /></template>
|
|
140
|
+
Save changes
|
|
141
|
+
<template #end><Check /><ArrowRight /></template>
|
|
142
|
+
</BmsButton>
|
|
143
|
+
`,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
export const DoOneIcon = () => ({
|
|
147
|
+
components: { BmsButton, ArrowRight },
|
|
148
|
+
template: `
|
|
149
|
+
<BmsButton type="primary">
|
|
150
|
+
<template #end><ArrowRight /></template>
|
|
151
|
+
Next
|
|
152
|
+
</BmsButton>
|
|
153
|
+
`,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Don't: Generic labels
|
|
157
|
+
export const DontGenericLabel = () => ({
|
|
158
|
+
components: { BmsButton },
|
|
159
|
+
template: `
|
|
160
|
+
<div style="display: flex; gap: 8px;">
|
|
161
|
+
<BmsButton type="primary">Click here</BmsButton>
|
|
162
|
+
<BmsButton type="primary">Submit</BmsButton>
|
|
163
|
+
</div>
|
|
164
|
+
`,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
export const DoSpecificLabel = () => ({
|
|
168
|
+
components: { BmsButton },
|
|
169
|
+
template: `
|
|
170
|
+
<div style="display: flex; gap: 8px;">
|
|
171
|
+
<BmsButton type="primary">Save changes</BmsButton>
|
|
172
|
+
<BmsButton type="primary">Confirm order</BmsButton>
|
|
173
|
+
</div>
|
|
174
|
+
`,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Don't: Primary for cancel
|
|
178
|
+
export const DontPrimaryForCancel = () => ({
|
|
179
|
+
components: { BmsButton },
|
|
180
|
+
template: `
|
|
181
|
+
<div style="display: flex; gap: 8px;">
|
|
182
|
+
<BmsButton type="primary">Cancel</BmsButton>
|
|
183
|
+
<BmsButton type="secondary">Save</BmsButton>
|
|
184
|
+
</div>
|
|
185
|
+
`,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
export const DoPrimaryForSave = () => ({
|
|
189
|
+
components: { BmsButton },
|
|
190
|
+
template: `
|
|
191
|
+
<div style="display: flex; gap: 8px;">
|
|
192
|
+
<BmsButton type="secondary">Cancel</BmsButton>
|
|
193
|
+
<BmsButton type="primary">Save</BmsButton>
|
|
194
|
+
</div>
|
|
195
|
+
`,
|
|
196
|
+
});
|
|
@@ -32,7 +32,12 @@ const Template = (args) => ({
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
export const Empty = Template.bind({});
|
|
35
|
-
Empty.args = {
|
|
35
|
+
Empty.args = {
|
|
36
|
+
modelValue: [],
|
|
37
|
+
limit: 5,
|
|
38
|
+
dragOverMessage: 'Déposer vos fichiers ici',
|
|
39
|
+
dragOffMessage: 'Glissez votre fichier ici ou cliquez pour parcourir',
|
|
40
|
+
};
|
|
36
41
|
|
|
37
42
|
export const WithFiles = Template.bind({});
|
|
38
43
|
WithFiles.args = {
|
|
@@ -7,9 +7,16 @@ const files: Ref<File[] | undefined> = ref();
|
|
|
7
7
|
const isDragOver = ref(false);
|
|
8
8
|
|
|
9
9
|
const props = withDefaults(
|
|
10
|
-
defineProps<{
|
|
10
|
+
defineProps<{
|
|
11
|
+
dragOverMessage?: string;
|
|
12
|
+
dragOffMessage?: string;
|
|
13
|
+
modelValue?: File[];
|
|
14
|
+
limit: number;
|
|
15
|
+
}>(),
|
|
11
16
|
{
|
|
12
17
|
limit: 10,
|
|
18
|
+
dragOverMessage: 'Déposer votre image ici',
|
|
19
|
+
dragOffMessage: 'Glissez votre image ici ou cliquez pour parcourir',
|
|
13
20
|
},
|
|
14
21
|
);
|
|
15
22
|
|
|
@@ -101,10 +108,8 @@ function onDeleteFile(file: File) {
|
|
|
101
108
|
@dragover.prevent
|
|
102
109
|
>
|
|
103
110
|
<label class="file-upload__label">
|
|
104
|
-
<template v-if="isDragOver">
|
|
105
|
-
<template v-else
|
|
106
|
-
>Glissez votre image ici ou cliquez pour parcourir</template
|
|
107
|
-
>
|
|
111
|
+
<template v-if="isDragOver">{{ dragOverMessage }}</template>
|
|
112
|
+
<template v-else>{{ dragOffMessage }}</template>
|
|
108
113
|
<input
|
|
109
114
|
data-testid="file-upload-input-file"
|
|
110
115
|
type="file"
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
:model-value="modelValue"
|
|
6
6
|
:open="isDatalistOpen"
|
|
7
7
|
@select="onSelect"
|
|
8
|
-
@click="
|
|
8
|
+
@click="onSelectClick"
|
|
9
9
|
>
|
|
10
10
|
<template #input>
|
|
11
11
|
<div class="tags">
|
|
@@ -24,11 +24,8 @@
|
|
|
24
24
|
v-model="searching"
|
|
25
25
|
class="search"
|
|
26
26
|
:disabled="disabled"
|
|
27
|
-
@
|
|
28
|
-
@click="openDatalist"
|
|
27
|
+
@input="onInput"
|
|
29
28
|
@keyup.down="openDatalist"
|
|
30
|
-
@input="openDatalist"
|
|
31
|
-
@keyup.backspace="onBackspace"
|
|
32
29
|
/>
|
|
33
30
|
</div>
|
|
34
31
|
|
|
@@ -37,16 +34,8 @@
|
|
|
37
34
|
<X class="icon icon-clear" @click.stop="clearInput" />
|
|
38
35
|
</template>
|
|
39
36
|
<template v-else>
|
|
40
|
-
<ChevronUp
|
|
41
|
-
|
|
42
|
-
class="icon icon-toggle-button"
|
|
43
|
-
@click="closeDatalist"
|
|
44
|
-
/>
|
|
45
|
-
<ChevronDown
|
|
46
|
-
v-else
|
|
47
|
-
class="icon icon-toggle-button"
|
|
48
|
-
@click="openDatalist"
|
|
49
|
-
/>
|
|
37
|
+
<ChevronUp v-if="isDatalistOpen" class="icon icon-toggle-button" />
|
|
38
|
+
<ChevronDown v-else class="icon icon-toggle-button" />
|
|
50
39
|
</template>
|
|
51
40
|
</span>
|
|
52
41
|
</template>
|
|
@@ -67,7 +56,7 @@ import { InputOption } from '@/models';
|
|
|
67
56
|
import { searchString } from '@/helpers';
|
|
68
57
|
import { FieldComponentProps } from '@/plugins/field/field-component.model';
|
|
69
58
|
import RawSelect from './RawSelect.vue';
|
|
70
|
-
import { onClickOutside } from '@vueuse/core';
|
|
59
|
+
import { onClickOutside, onKeyDown, onKeyUp } from '@vueuse/core';
|
|
71
60
|
|
|
72
61
|
export interface Props extends FieldComponentProps {
|
|
73
62
|
options: InputOption[] | string[];
|
|
@@ -80,9 +69,16 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
80
69
|
required: false,
|
|
81
70
|
});
|
|
82
71
|
|
|
83
|
-
const
|
|
72
|
+
const emits = defineEmits<{
|
|
73
|
+
select: [option: InputOption | string];
|
|
74
|
+
input: [e: InputEvent];
|
|
75
|
+
}>();
|
|
76
|
+
|
|
77
|
+
const modelValue = defineModel<string[] | null>('modelValue', { default: [] });
|
|
84
78
|
|
|
79
|
+
const inputElement: Ref<HTMLElement | null> = ref(null);
|
|
85
80
|
const isDatalistOpen = ref(false);
|
|
81
|
+
const searching = ref('');
|
|
86
82
|
|
|
87
83
|
const closeDatalist = () => (isDatalistOpen.value = false);
|
|
88
84
|
const openDatalist = () => {
|
|
@@ -91,13 +87,9 @@ const openDatalist = () => {
|
|
|
91
87
|
}
|
|
92
88
|
};
|
|
93
89
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
onClickOutside(inputElement, closeDatalist, {
|
|
99
|
-
ignore: ['.datalist-option', '.icon-toggle-button'],
|
|
100
|
-
});
|
|
90
|
+
const onBlur = () => {
|
|
91
|
+
closeDatalist();
|
|
92
|
+
};
|
|
101
93
|
|
|
102
94
|
const onBackspace = () => {
|
|
103
95
|
if (
|
|
@@ -107,6 +99,21 @@ const onBackspace = () => {
|
|
|
107
99
|
)
|
|
108
100
|
modelValue.value.splice(-1);
|
|
109
101
|
};
|
|
102
|
+
onClickOutside(inputElement, closeDatalist, {
|
|
103
|
+
ignore: ['.datalist-option', '.icon-toggle-button'],
|
|
104
|
+
});
|
|
105
|
+
onKeyUp('Escape', closeDatalist);
|
|
106
|
+
onKeyUp('Tab', closeDatalist);
|
|
107
|
+
|
|
108
|
+
onKeyDown('Backspace', onBackspace);
|
|
109
|
+
|
|
110
|
+
const onSelectClick = () => {
|
|
111
|
+
isDatalistOpen.value = !isDatalistOpen.value;
|
|
112
|
+
};
|
|
113
|
+
const onInput = (e: InputEvent) => {
|
|
114
|
+
emits('input', e);
|
|
115
|
+
openDatalist();
|
|
116
|
+
};
|
|
110
117
|
|
|
111
118
|
const setFocus = () => {
|
|
112
119
|
if (inputElement.value) {
|
|
@@ -122,8 +129,8 @@ const onSelect = (option: InputOption | string) => {
|
|
|
122
129
|
}
|
|
123
130
|
|
|
124
131
|
searching.value = '';
|
|
132
|
+
emits('select', option);
|
|
125
133
|
setFocus();
|
|
126
|
-
closeDatalist();
|
|
127
134
|
};
|
|
128
135
|
|
|
129
136
|
const removeOption = (value: string) => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
v-bind="$props"
|
|
4
4
|
:open="isDatalistOpen"
|
|
5
5
|
@select="onSelect"
|
|
6
|
-
@click="
|
|
6
|
+
@click="onSelectClick"
|
|
7
7
|
>
|
|
8
8
|
<template #input>
|
|
9
9
|
<input
|
|
@@ -15,17 +15,11 @@
|
|
|
15
15
|
:placeholder="placeholder"
|
|
16
16
|
:required="required"
|
|
17
17
|
:disabled="disabled"
|
|
18
|
-
@focus="openDatalist"
|
|
19
|
-
@click="openDatalist"
|
|
20
18
|
@keyup.down="openDatalist"
|
|
21
19
|
/>
|
|
22
20
|
<span class="icon-toggle-container">
|
|
23
|
-
<ChevronUp
|
|
24
|
-
|
|
25
|
-
class="icon-toggle-button"
|
|
26
|
-
@click="closeDatalist"
|
|
27
|
-
/>
|
|
28
|
-
<ChevronDown v-else class="icon-toggle-button" @click="openDatalist" />
|
|
21
|
+
<ChevronUp v-if="isDatalistOpen" class="icon-toggle-button" />
|
|
22
|
+
<ChevronDown v-else class="icon-toggle-button" />
|
|
29
23
|
</span>
|
|
30
24
|
</template>
|
|
31
25
|
</RawSelect>
|
|
@@ -33,12 +27,12 @@
|
|
|
33
27
|
|
|
34
28
|
<script lang="ts" setup>
|
|
35
29
|
import { ChevronDown, ChevronUp } from 'lucide-vue-next';
|
|
36
|
-
import { computed, Ref, ref } from 'vue';
|
|
30
|
+
import { computed, Ref, ref, watch } from 'vue';
|
|
37
31
|
import _ from 'lodash';
|
|
38
32
|
import { InputOption } from '@/models';
|
|
39
33
|
import { FieldComponentProps } from '@/plugins/field/field-component.model';
|
|
40
34
|
import RawSelect from './RawSelect.vue';
|
|
41
|
-
import { onClickOutside } from '@vueuse/core';
|
|
35
|
+
import { onClickOutside, onKeyUp } from '@vueuse/core';
|
|
42
36
|
|
|
43
37
|
export interface Props extends FieldComponentProps {
|
|
44
38
|
options: InputOption[];
|
|
@@ -55,10 +49,13 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
55
49
|
open: false,
|
|
56
50
|
});
|
|
57
51
|
|
|
52
|
+
const emits = defineEmits<{
|
|
53
|
+
select: [value: any];
|
|
54
|
+
}>();
|
|
55
|
+
|
|
58
56
|
const modelValue = defineModel<any>('modelValue', { required: true });
|
|
59
57
|
|
|
60
58
|
const inputElement: Ref<HTMLElement | null> = ref(null);
|
|
61
|
-
|
|
62
59
|
const isDatalistOpen = ref(props.open);
|
|
63
60
|
|
|
64
61
|
const closeDatalist = () => {
|
|
@@ -71,13 +68,11 @@ const openDatalist = () => {
|
|
|
71
68
|
}
|
|
72
69
|
};
|
|
73
70
|
|
|
74
|
-
const emits = defineEmits<{
|
|
75
|
-
select: [value: any];
|
|
76
|
-
}>();
|
|
77
|
-
|
|
78
71
|
onClickOutside(inputElement, closeDatalist, {
|
|
79
72
|
ignore: ['.datalist-option', '.icon-toggle-button'],
|
|
80
73
|
});
|
|
74
|
+
onKeyUp('Escape', closeDatalist);
|
|
75
|
+
onKeyUp('Tab', closeDatalist);
|
|
81
76
|
|
|
82
77
|
const displayValue = computed(() => {
|
|
83
78
|
const option = props.options.find((o) =>
|
|
@@ -100,6 +95,13 @@ const onSelect = (option: any) => {
|
|
|
100
95
|
closeDatalist();
|
|
101
96
|
};
|
|
102
97
|
|
|
98
|
+
const onSelectClick = () => {
|
|
99
|
+
isDatalistOpen.value = !isDatalistOpen.value;
|
|
100
|
+
if (isDatalistOpen) {
|
|
101
|
+
setFocus();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
103
105
|
defineExpose({
|
|
104
106
|
setFocus,
|
|
105
107
|
});
|