@veritree/ui 0.0.1 → 0.1.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/index.js +43 -8
- package/package.json +1 -1
- package/src/{vtAccordion/vtAccordion.vue → Accordion/VTAccordion.vue} +1 -1
- package/src/{vtAccordion/vtAccordionButton.vue → Accordion/VTAccordionButton.vue} +1 -1
- package/src/{vtAccordion/vtAccordionGroup.vue → Accordion/VTAccordionGroup.vue} +1 -1
- package/src/{vtAccordion/vtAccordionPanel.vue → Accordion/VTAccordionPanel.vue} +1 -3
- package/src/Button/VTButton.vue +86 -0
- package/src/Input/VTInput.vue +82 -0
- package/src/Input/VTInputDate.vue +36 -0
- package/src/Input/VTInputFile.vue +60 -0
- package/src/Input/VTInputUpload.vue +54 -0
- package/src/Listbox/VTListbox.vue +168 -0
- package/src/Listbox/VTListboxButton.vue +82 -0
- package/src/Listbox/VTListboxOption.vue +121 -0
- package/src/Listbox/VTListboxOptions.vue +120 -0
- package/src/Modal/VTModal.vue +69 -0
- package/src/Tabs/VTTab.vue +126 -0
- package/src/Tabs/VTTabGroup.vue +100 -0
- package/src/Tabs/VTTabList.vue +11 -0
- package/src/Tabs/VTTabPanel.vue +51 -0
- package/src/Tabs/VTTabPanels.vue +11 -0
- package/src/Textarea/VTTextarea.vue +77 -0
- package/src/utils/ids.js +34 -0
- package/src/utils/objects.js +139 -0
package/index.js
CHANGED
|
@@ -1,12 +1,47 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import VTButton from './src/Button/VTButton.vue';
|
|
2
|
+
|
|
3
|
+
import VTInput from './src/Input/VTInput.vue';
|
|
4
|
+
import VTInputDate from './src/Input/VTInputDate.vue';
|
|
5
|
+
import VTInputFile from './src/Input/VTInputFile.vue';
|
|
6
|
+
// import VTInputUpload from './src/Input/VTInputUpload.vue';
|
|
7
|
+
|
|
8
|
+
// import VTListbox from './src/Listbox/VTListbox.vue';
|
|
9
|
+
// import VTListboxButton from './src/Listbox/VTListboxButton.vue';
|
|
10
|
+
// import VTListboxOption from './src/Listbox/VTListboxOption.vue';
|
|
11
|
+
// import VTListboxOptions from './src/Listbox/VTListboxOptions.vue';
|
|
12
|
+
|
|
13
|
+
// import VTModal from './src/Modal/VTModal.vue';
|
|
14
|
+
|
|
15
|
+
import VTAccordion from './src/Accordion/VTAccordion.vue';
|
|
16
|
+
import VTAccordionButton from './src/Accordion/VTAccordionButton.vue';
|
|
17
|
+
import VTAccordionGroup from './src/Accordion/VTAccordionGroup.vue';
|
|
18
|
+
import VTAccordionPanel from './src/Accordion/VTAccordionPanel.vue';
|
|
19
|
+
|
|
20
|
+
import VTTab from './src/Tabs/VTTab.vue';
|
|
21
|
+
import VTTabGroup from './src/Tabs/VTTabGroup.vue';
|
|
22
|
+
import VTTabList from './src/Tabs/VTTabList.vue';
|
|
23
|
+
import VTTabPanel from './src/Tabs/VTTabPanel.vue';
|
|
24
|
+
import VTTabPanels from './src/Tabs/VTTabPanels.vue';
|
|
5
25
|
|
|
6
26
|
export {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
27
|
+
VTButton,
|
|
28
|
+
VTInput,
|
|
29
|
+
VTInputDate,
|
|
30
|
+
VTInputFile,
|
|
31
|
+
// VTInputUpload,
|
|
32
|
+
// VTListbox,
|
|
33
|
+
// VTListboxButton,
|
|
34
|
+
// VTListboxOption,
|
|
35
|
+
// VTListboxOptions,
|
|
36
|
+
// VTModal,
|
|
37
|
+
VTAccordion,
|
|
38
|
+
VTAccordionButton,
|
|
39
|
+
VTAccordionGroup,
|
|
40
|
+
VTAccordionPanel,
|
|
41
|
+
VTTab,
|
|
42
|
+
VTTabGroup,
|
|
43
|
+
VTTabList,
|
|
44
|
+
VTTabPanel,
|
|
45
|
+
VTTabPanels,
|
|
11
46
|
}
|
|
12
47
|
|
package/package.json
CHANGED
|
@@ -22,7 +22,7 @@ import IconChevronDown from '../icons/IconChevronDown.vue';
|
|
|
22
22
|
import IconChevronUp from '../icons/IconChevronUp.vue';
|
|
23
23
|
|
|
24
24
|
export default {
|
|
25
|
-
name: '
|
|
25
|
+
name: 'VTAccordionButton',
|
|
26
26
|
components: { IconChevronDown, IconChevronUp },
|
|
27
27
|
inject: ['api'],
|
|
28
28
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="tag"
|
|
4
|
+
:class="classes"
|
|
5
|
+
:data-theme="theme"
|
|
6
|
+
:to="to"
|
|
7
|
+
class="Button"
|
|
8
|
+
type="button"
|
|
9
|
+
v-on="$listeners"
|
|
10
|
+
>
|
|
11
|
+
<slot></slot>
|
|
12
|
+
</component>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script>
|
|
16
|
+
export default {
|
|
17
|
+
name: 'VTButton',
|
|
18
|
+
|
|
19
|
+
props: {
|
|
20
|
+
variant: {
|
|
21
|
+
type: String,
|
|
22
|
+
default: 'primary',
|
|
23
|
+
validator(value) {
|
|
24
|
+
return [
|
|
25
|
+
'primary',
|
|
26
|
+
'secondary',
|
|
27
|
+
'tertiary',
|
|
28
|
+
'danger',
|
|
29
|
+
'custom',
|
|
30
|
+
'icon',
|
|
31
|
+
].includes(value);
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
size: {
|
|
35
|
+
type: String,
|
|
36
|
+
default: 'large',
|
|
37
|
+
validator(value) {
|
|
38
|
+
return ['large', 'small'].includes(value);
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
pill: {
|
|
42
|
+
type: Boolean,
|
|
43
|
+
default: false,
|
|
44
|
+
},
|
|
45
|
+
theme: {
|
|
46
|
+
type: String,
|
|
47
|
+
default: null,
|
|
48
|
+
validator(value) {
|
|
49
|
+
return ['dark'].includes(value);
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
to: {
|
|
53
|
+
type: [String, Object],
|
|
54
|
+
default: null,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
computed: {
|
|
59
|
+
classes() {
|
|
60
|
+
const classes = {};
|
|
61
|
+
|
|
62
|
+
if (this.variant) {
|
|
63
|
+
classes[`Button--${this.variant}`] = true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (this.size) {
|
|
67
|
+
classes[`Button--${this.size}`] = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (this.pill) {
|
|
71
|
+
classes['Button--pill'] = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return classes;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
tag() {
|
|
78
|
+
if (this.to) {
|
|
79
|
+
return 'NuxtLink';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return 'button';
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
</script>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<input
|
|
3
|
+
:class="classes"
|
|
4
|
+
class="form-control"
|
|
5
|
+
:data-theme="theme"
|
|
6
|
+
:type="type"
|
|
7
|
+
:value="value"
|
|
8
|
+
v-on="listeners"
|
|
9
|
+
/>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script>
|
|
13
|
+
export default {
|
|
14
|
+
name: 'VTInput',
|
|
15
|
+
|
|
16
|
+
props: {
|
|
17
|
+
lazy: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
default: false,
|
|
20
|
+
},
|
|
21
|
+
type: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: 'text',
|
|
24
|
+
},
|
|
25
|
+
theme: {
|
|
26
|
+
type: String,
|
|
27
|
+
default: null,
|
|
28
|
+
validator(value) {
|
|
29
|
+
return ['dark'].includes(value);
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
variant: {
|
|
33
|
+
type: [String, Object],
|
|
34
|
+
default: '',
|
|
35
|
+
validator(value) {
|
|
36
|
+
if (value === '' || typeof value === 'object') {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return ['success', 'warning', 'error'].includes(value);
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
value: {
|
|
44
|
+
type: [String, Number, Object, Array],
|
|
45
|
+
default: null,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
computed: {
|
|
50
|
+
classes() {
|
|
51
|
+
const classes = {};
|
|
52
|
+
|
|
53
|
+
if (this.variant) {
|
|
54
|
+
classes[`form-control--${this.variant}`] = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return classes;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
listeners() {
|
|
61
|
+
// `Object.assign` merges objects together to form a new object
|
|
62
|
+
return Object.assign(
|
|
63
|
+
{},
|
|
64
|
+
// We add all the listeners from the parent
|
|
65
|
+
this.$listeners,
|
|
66
|
+
// Then we can add custom listeners or override the
|
|
67
|
+
// behavior of some listeners.
|
|
68
|
+
{
|
|
69
|
+
// This ensures that the component works with v-model
|
|
70
|
+
input: (event) => {
|
|
71
|
+
if (this.lazy) return;
|
|
72
|
+
this.$emit('input', event.target.value);
|
|
73
|
+
},
|
|
74
|
+
blur: (event) => {
|
|
75
|
+
this.$emit('blur', event);
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
</script>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<VTInput v-model="date" type="date" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script>
|
|
6
|
+
import VTInput from './VTInput.vue';
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: 'VTInputDate',
|
|
10
|
+
|
|
11
|
+
components: { VTInput },
|
|
12
|
+
|
|
13
|
+
model: {
|
|
14
|
+
prop: 'value',
|
|
15
|
+
event: 'input',
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
props: {
|
|
19
|
+
value: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: '',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
computed: {
|
|
26
|
+
date: {
|
|
27
|
+
get() {
|
|
28
|
+
return this.$date.format(this.value, 'YYYY-MM-DD');
|
|
29
|
+
},
|
|
30
|
+
set(newDate) {
|
|
31
|
+
this.$emit('input', newDate);
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
</script>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-stretch gap-2">
|
|
3
|
+
<VTInput
|
|
4
|
+
ref="input"
|
|
5
|
+
type="file"
|
|
6
|
+
:value="value"
|
|
7
|
+
:theme="theme"
|
|
8
|
+
v-bind="$attrs"
|
|
9
|
+
@change="onChange"
|
|
10
|
+
/>
|
|
11
|
+
<VTButton :theme="theme" @click.stop="onButtonClick">Browse</VTButton>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script>
|
|
16
|
+
import VTButton from '../Button/VTButton.vue';
|
|
17
|
+
import VTInput from './VTInput.vue';
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
name: 'VTInputFile',
|
|
21
|
+
|
|
22
|
+
components: {
|
|
23
|
+
VTInput,
|
|
24
|
+
VTButton,
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
inheritAttrs: false,
|
|
28
|
+
|
|
29
|
+
props: {
|
|
30
|
+
theme: {
|
|
31
|
+
type: String,
|
|
32
|
+
default: null,
|
|
33
|
+
validator(value) {
|
|
34
|
+
return ['dark'].includes(value);
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
multiple: {
|
|
38
|
+
type: Boolean,
|
|
39
|
+
default: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
data() {
|
|
44
|
+
return {
|
|
45
|
+
value: null,
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
methods: {
|
|
50
|
+
onChange(event) {
|
|
51
|
+
this.value = this.$refs.input.$el.value;
|
|
52
|
+
this.$emit('change', event);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
onButtonClick() {
|
|
56
|
+
this.$refs.input.$el.click();
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
</script>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<label
|
|
3
|
+
class="flex h-full w-full flex-col items-center justify-center rounded border-2 border-dotted border-white p-4 text-center hover:border-fl-500 hover:bg-fd-500"
|
|
4
|
+
:class="{ 'border-fl-500 bg-fd-500': isDraggingOver }"
|
|
5
|
+
@drop.prevent="onDrop"
|
|
6
|
+
@dragover.prevent="onDragOver"
|
|
7
|
+
@dragleave.prevent="onDragLeave"
|
|
8
|
+
>
|
|
9
|
+
<IconImagePlaceholder class="mb-3" />
|
|
10
|
+
<span>Drop your images here, or click to browse</span>
|
|
11
|
+
<VTInput type="file" class="sr-only" v-bind="$attrs" @change="onChange" />
|
|
12
|
+
</label>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script>
|
|
16
|
+
import { IconImagePlaceholder } from '@veritree/icons';
|
|
17
|
+
import VTInput from './VTInput.vue';
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
name: 'VTInputFile',
|
|
21
|
+
|
|
22
|
+
components: {
|
|
23
|
+
VTInput,
|
|
24
|
+
IconImagePlaceholder,
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
inheritAttrs: false,
|
|
28
|
+
|
|
29
|
+
data() {
|
|
30
|
+
return {
|
|
31
|
+
isDraggingOver: false,
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
methods: {
|
|
36
|
+
onDrop(event) {
|
|
37
|
+
this.isDraggingOver = false;
|
|
38
|
+
this.$emit('drop', event);
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
onDragOver() {
|
|
42
|
+
this.isDraggingOver = true;
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
onDragLeave() {
|
|
46
|
+
this.isDraggingOver = false;
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
onChange(event) {
|
|
50
|
+
this.$emit('change', event);
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
</script>
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="Listbox"><slot></slot></div>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script>
|
|
6
|
+
export default {
|
|
7
|
+
name: 'VTListbox',
|
|
8
|
+
|
|
9
|
+
provide() {
|
|
10
|
+
return {
|
|
11
|
+
api: () => {
|
|
12
|
+
// Get children components
|
|
13
|
+
const getListbox = () => this.listbox;
|
|
14
|
+
const getListboxValue = () => this.value;
|
|
15
|
+
const getListboxButton = () => this.listboxButton;
|
|
16
|
+
|
|
17
|
+
// Handle registering and unregistering
|
|
18
|
+
const registerListboxButton = (button) => {
|
|
19
|
+
if (!button) return;
|
|
20
|
+
this.listboxButton = button;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const registerListbox = (listbox) => {
|
|
24
|
+
if (!listbox) return;
|
|
25
|
+
this.listbox = listbox;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const registerOption = (option) => {
|
|
29
|
+
if (!option) return;
|
|
30
|
+
_register(this.listboxOptions, option);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const unregisterOption = (id) => {
|
|
34
|
+
_unregister(this.listboxOptions, id);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Register helper functions
|
|
38
|
+
const _register = (arr, item) => {
|
|
39
|
+
arr.push(item);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const _unregister = (arr, id) => {
|
|
43
|
+
const index = _getIndex(arr, id);
|
|
44
|
+
arr.splice(index, 1);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Handle focus and unfocus
|
|
48
|
+
const getFocusedIndex = () => {
|
|
49
|
+
let index = 0;
|
|
50
|
+
|
|
51
|
+
this.listboxOptions.forEach((option, i) => {
|
|
52
|
+
if (option.focused) index = i;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return index;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const focusOptionByFilter = (filter) => {
|
|
59
|
+
const optionIndex = this.listboxOptions.findIndex((option) => {
|
|
60
|
+
const text = option.$el.innerText;
|
|
61
|
+
return text.toLowerCase().indexOf(filter.toLowerCase()) === 0;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (optionIndex >= 0) _focusOption(optionIndex);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const focusFirstOption = () => {
|
|
68
|
+
_focusOption(0);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const focusLastOption = () => {
|
|
72
|
+
_focusOption(this.listboxOptions.length - 1);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const focusPreviousOption = () => {
|
|
76
|
+
const focusedIndex = getFocusedIndex();
|
|
77
|
+
const previousIndex = focusedIndex - 1;
|
|
78
|
+
|
|
79
|
+
if (previousIndex >= 0) _focusOption(previousIndex);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const focusNextOption = () => {
|
|
83
|
+
const focusedIndex = getFocusedIndex();
|
|
84
|
+
const nextIndex = focusedIndex + 1;
|
|
85
|
+
|
|
86
|
+
if (nextIndex < this.listboxOptions.length) _focusOption(nextIndex);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const unfocusOptions = () => {
|
|
90
|
+
this.listboxOptions.forEach((option) => {
|
|
91
|
+
option.unfocus();
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const _focusOption = (index) => {
|
|
96
|
+
unfocusOptions();
|
|
97
|
+
this.listboxOptions[index].focus();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const _getFocusedOption = () => {
|
|
101
|
+
return this.listboxOptions.filter((option) => option.focused)[0];
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// handle selecting and unselecting
|
|
105
|
+
const selectOption = () => {
|
|
106
|
+
const focusedOption = _getFocusedOption();
|
|
107
|
+
|
|
108
|
+
// do nothing if option is already selected
|
|
109
|
+
if (focusedOption.selected) return;
|
|
110
|
+
|
|
111
|
+
// unselect all options
|
|
112
|
+
this.listboxOptions.forEach((option) => option.unselect());
|
|
113
|
+
|
|
114
|
+
// select focused option
|
|
115
|
+
if (focusedOption) {
|
|
116
|
+
focusedOption.select();
|
|
117
|
+
_onSelectedOption(focusedOption.value);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const _onSelectedOption = (value) => {
|
|
122
|
+
this.listbox.hide();
|
|
123
|
+
this.$emit('input', value);
|
|
124
|
+
this.$emit('change');
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Index helper functions
|
|
128
|
+
const _getIndex = (arr, id) => {
|
|
129
|
+
return arr.findIndex((item) => item.id === id);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
getListbox,
|
|
134
|
+
getListboxButton,
|
|
135
|
+
registerListboxButton,
|
|
136
|
+
registerListbox,
|
|
137
|
+
registerOption,
|
|
138
|
+
unregisterOption,
|
|
139
|
+
focusOptionByFilter,
|
|
140
|
+
focusFirstOption,
|
|
141
|
+
focusLastOption,
|
|
142
|
+
focusPreviousOption,
|
|
143
|
+
focusNextOption,
|
|
144
|
+
unfocusOptions,
|
|
145
|
+
selectOption,
|
|
146
|
+
getFocusedIndex,
|
|
147
|
+
getListboxValue,
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
props: {
|
|
154
|
+
value: {
|
|
155
|
+
type: [String, Number, Object],
|
|
156
|
+
default: null,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
data() {
|
|
161
|
+
return {
|
|
162
|
+
listbox: null,
|
|
163
|
+
listboxButton: null,
|
|
164
|
+
listboxOptions: [],
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
</script>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
:aria-expanded="expanded"
|
|
4
|
+
aria-haspopup="listbox"
|
|
5
|
+
class="Listbox-button debugger"
|
|
6
|
+
:data-theme="theme"
|
|
7
|
+
type="button"
|
|
8
|
+
@click.prevent="onClick"
|
|
9
|
+
@keydown="onKeyDown"
|
|
10
|
+
>
|
|
11
|
+
<span class="Listbox-button__text"><slot></slot></span>
|
|
12
|
+
<span class="Listbox-button__icon">
|
|
13
|
+
<IconChevronUp v-if="expanded" />
|
|
14
|
+
<IconChevronDown v-else />
|
|
15
|
+
</span>
|
|
16
|
+
</button>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
import { IconChevronDown, IconChevronUp } from '@veritree/icons';
|
|
21
|
+
import { keys } from '../utils/keyboard';
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
name: 'VTListboxButton',
|
|
25
|
+
|
|
26
|
+
components: { IconChevronDown, IconChevronUp },
|
|
27
|
+
|
|
28
|
+
inject: ['api'],
|
|
29
|
+
|
|
30
|
+
props: {
|
|
31
|
+
theme: {
|
|
32
|
+
type: String,
|
|
33
|
+
default: null,
|
|
34
|
+
validator(value) {
|
|
35
|
+
return ['dark'].includes(value);
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
data() {
|
|
41
|
+
return {
|
|
42
|
+
api: this.api(),
|
|
43
|
+
expanded: false,
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
mounted() {
|
|
48
|
+
this.api.registerListboxButton(this);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
methods: {
|
|
52
|
+
focus() {
|
|
53
|
+
this.$el.focus();
|
|
54
|
+
this.toggleExpanded();
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
toggleExpanded() {
|
|
58
|
+
this.expanded = !this.expanded;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
onClick() {
|
|
62
|
+
const listbox = this.api.getListbox();
|
|
63
|
+
this.expanded ? listbox.hide() : listbox.show();
|
|
64
|
+
|
|
65
|
+
this.toggleExpanded();
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
onKeyDown(event) {
|
|
69
|
+
const key = event.key;
|
|
70
|
+
const listbox = this.api.getListbox();
|
|
71
|
+
|
|
72
|
+
switch (key) {
|
|
73
|
+
case keys.down:
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
listbox.show();
|
|
76
|
+
this.toggleExpanded();
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
</script>
|