vue-composable-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/.github/workflows/deploy.yml +21 -0
- package/.github/workflows/docs.yml +62 -0
- package/.vscode/extensions.json +7 -0
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/docs/.vitepress/config.mts +40 -0
- package/docs/.vitepress/theme/index.js +4 -0
- package/docs/.vitepress/theme/style.scss +155 -0
- package/docs/components/combobox.md +174 -0
- package/docs/components/demos/combobox.vue +142 -0
- package/docs/components/demos/combobox_autocomplete.vue +75 -0
- package/docs/components/demos/combobox_tags.vue +127 -0
- package/docs/components/demos/input.vue +35 -0
- package/docs/components/demos/listbox.vue +64 -0
- package/docs/components/demos/menu.vue +93 -0
- package/docs/components/demos/popover.vue +16 -0
- package/docs/components/demos/tabs.vue +59 -0
- package/docs/components/demos/tooltip.vue +27 -0
- package/docs/components/listbox.md +9 -0
- package/docs/components/menu.md +121 -0
- package/docs/components/popover.md +82 -0
- package/docs/components/tabs.md +9 -0
- package/docs/components/tooltip.md +78 -0
- package/docs/composables/form-control.md +130 -0
- package/docs/composables/list-option.md +47 -0
- package/docs/composables/list.md +192 -0
- package/docs/composables/popover.md +90 -0
- package/docs/index.md +24 -0
- package/docs/intro.md +0 -0
- package/package.json +35 -0
- package/src/components/combobox/combobox.vue +229 -0
- package/src/components/combobox/comboboxFormInput.vue +33 -0
- package/src/components/combobox/comboboxOption.vue +52 -0
- package/src/components/combobox/comboboxOptions.vue +17 -0
- package/src/components/injectionKeys.ts +50 -0
- package/src/components/listbox/listbox.vue +91 -0
- package/src/components/listbox/listboxOption.vue +37 -0
- package/src/components/menu/menu.vue +100 -0
- package/src/components/menu/menuItem.vue +86 -0
- package/src/components/menu/menuItems.vue +51 -0
- package/src/components/popover/popover.vue +68 -0
- package/src/components/popover/popoverDialog.vue +35 -0
- package/src/components/tabs/tab.vue +35 -0
- package/src/components/tabs/tabContainer.vue +50 -0
- package/src/components/tabs/tabList.vue +52 -0
- package/src/components/tabs/tabPanel.vue +36 -0
- package/src/components/tooltip.vue +115 -0
- package/src/composables/formControl.ts +144 -0
- package/src/composables/list.ts +326 -0
- package/src/composables/listOption.ts +80 -0
- package/src/composables/popover.ts +306 -0
- package/src/main.ts +65 -0
- package/src/utils/element.ts +19 -0
- package/src/utils/id.ts +5 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +27 -0
- package/tsconfig.node.json +10 -0
- package/vite.config.ts +28 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="d-demo">
|
|
3
|
+
<Combobox
|
|
4
|
+
v-model="value"
|
|
5
|
+
:displayName="(i) => i?.name"
|
|
6
|
+
:customValue="(i) => ({ id: -1, name: i })"
|
|
7
|
+
@input="query = $event"
|
|
8
|
+
class="d-input"
|
|
9
|
+
>
|
|
10
|
+
<ComboboxOptions class="d-popup">
|
|
11
|
+
<div v-if="!filteredPeople.length">
|
|
12
|
+
Use <span>{{ query }}</span>
|
|
13
|
+
</div>
|
|
14
|
+
<ComboboxOption
|
|
15
|
+
v-for="opt in filteredPeople"
|
|
16
|
+
:key="opt.id"
|
|
17
|
+
:value="opt"
|
|
18
|
+
v-slot="{ active, selected }"
|
|
19
|
+
><div class="d-menu-item" :class="{ 'd-menu-item--active': active }">
|
|
20
|
+
{{ opt.name }}
|
|
21
|
+
</div></ComboboxOption
|
|
22
|
+
>
|
|
23
|
+
</ComboboxOptions>
|
|
24
|
+
</Combobox>
|
|
25
|
+
|
|
26
|
+
{{ value }}
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup lang="ts">
|
|
31
|
+
import { ref, computed } from "vue";
|
|
32
|
+
import { Combobox, ComboboxOptions, ComboboxOption } from "../../../src/main";
|
|
33
|
+
|
|
34
|
+
const value = ref();
|
|
35
|
+
const query = ref("");
|
|
36
|
+
const options = [
|
|
37
|
+
{
|
|
38
|
+
id: 0,
|
|
39
|
+
name: "John Doe",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 1,
|
|
43
|
+
name: "Cris Doe",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 2,
|
|
47
|
+
name: "Michael Doe",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 3,
|
|
51
|
+
name: "Kim Doe",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 4,
|
|
55
|
+
name: "Boris Doe",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 5,
|
|
59
|
+
name: "Angelina Doe",
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const filteredPeople = computed(() =>
|
|
64
|
+
query.value === ""
|
|
65
|
+
? options
|
|
66
|
+
: options.filter((person) =>
|
|
67
|
+
person.name
|
|
68
|
+
.toLowerCase()
|
|
69
|
+
.replace(/\s+/g, "")
|
|
70
|
+
.includes(query.value.toLowerCase().replace(/\s+/g, ""))
|
|
71
|
+
)
|
|
72
|
+
);
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<style scoped></style>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="d-demo">
|
|
3
|
+
<Combobox v-model="value" :displayValue="() => ''" activatorId="testid">
|
|
4
|
+
<template #input="{ popoverId, attrs, toggle, isOpen }">
|
|
5
|
+
<div id="testid" class="d-combobox">
|
|
6
|
+
<div class="d-combobox__tags">
|
|
7
|
+
<div v-for="item in value.slice(0, 3)" class="d-tag">
|
|
8
|
+
{{ item.name }}
|
|
9
|
+
</div>
|
|
10
|
+
<div v-if="value.length > 3">+{{ value.length - 3 }}</div>
|
|
11
|
+
</div>
|
|
12
|
+
<input
|
|
13
|
+
type="text"
|
|
14
|
+
v-bind="attrs"
|
|
15
|
+
@input="query = $event.target.value"
|
|
16
|
+
@focus="query = ''"
|
|
17
|
+
placeholder="Type to search..."
|
|
18
|
+
autocomplete="off"
|
|
19
|
+
/>
|
|
20
|
+
<button
|
|
21
|
+
@click="toggle"
|
|
22
|
+
:aria-controls="isOpen ? popoverId : undefined"
|
|
23
|
+
:aria-expanded="isOpen"
|
|
24
|
+
aria-haspopup="listbox"
|
|
25
|
+
tabindex="-1"
|
|
26
|
+
>
|
|
27
|
+
<svg
|
|
28
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
29
|
+
width="24"
|
|
30
|
+
height="24"
|
|
31
|
+
viewBox="0 0 20 20"
|
|
32
|
+
>
|
|
33
|
+
<path
|
|
34
|
+
fill="currentColor"
|
|
35
|
+
fill-rule="evenodd"
|
|
36
|
+
d="M10 3a.75.75 0 0 1 .55.24l3.25 3.5a.75.75 0 1 1-1.1 1.02L10 4.852L7.3 7.76a.75.75 0 0 1-1.1-1.02l3.25-3.5A.75.75 0 0 1 10 3Zm-3.76 9.2a.75.75 0 0 1 1.06.04l2.7 2.908l2.7-2.908a.75.75 0 1 1 1.1 1.02l-3.25 3.5a.75.75 0 0 1-1.1 0l-3.25-3.5a.75.75 0 0 1 .04-1.06Z"
|
|
37
|
+
clip-rule="evenodd"
|
|
38
|
+
/>
|
|
39
|
+
</svg>
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<ComboboxOptions class="d-menu">
|
|
45
|
+
<div class="d-no-options" v-if="filteredPeople.length == 0">
|
|
46
|
+
Nothing found for <strong>"{{ query }}"</strong>
|
|
47
|
+
</div>
|
|
48
|
+
<ComboboxOption
|
|
49
|
+
v-for="opt in filteredPeople"
|
|
50
|
+
:key="opt.id"
|
|
51
|
+
:value="opt"
|
|
52
|
+
:disabled="opt.disabled"
|
|
53
|
+
v-slot="{ isSelected }"
|
|
54
|
+
class="d-menu-item"
|
|
55
|
+
>
|
|
56
|
+
{{ opt.name }}
|
|
57
|
+
<svg
|
|
58
|
+
v-if="isSelected"
|
|
59
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
60
|
+
width="1rem"
|
|
61
|
+
height="1rem"
|
|
62
|
+
viewBox="0 0 256 256"
|
|
63
|
+
>
|
|
64
|
+
<path
|
|
65
|
+
fill="currentColor"
|
|
66
|
+
d="m232.49 80.49l-128 128a12 12 0 0 1-17 0l-56-56a12 12 0 1 1 17-17L96 183L215.51 63.51a12 12 0 0 1 17 17Z"
|
|
67
|
+
/>
|
|
68
|
+
</svg>
|
|
69
|
+
</ComboboxOption>
|
|
70
|
+
</ComboboxOptions>
|
|
71
|
+
</Combobox>
|
|
72
|
+
</div>
|
|
73
|
+
</template>
|
|
74
|
+
|
|
75
|
+
<script setup lang="ts">
|
|
76
|
+
import { ref, computed, watch } from "vue";
|
|
77
|
+
import { Combobox, ComboboxOptions, ComboboxOption } from "../../../src/main";
|
|
78
|
+
|
|
79
|
+
const value = ref([]);
|
|
80
|
+
const options = [
|
|
81
|
+
{
|
|
82
|
+
id: 0,
|
|
83
|
+
name: "John Doe",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 1,
|
|
87
|
+
name: "Cris Doe",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 2,
|
|
91
|
+
name: "Michael Doe",
|
|
92
|
+
disabled: true,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 3,
|
|
96
|
+
name: "Kim Doe",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 4,
|
|
100
|
+
name: "Boris Doe",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 5,
|
|
104
|
+
name: "Angelina Doe",
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
const query = ref("");
|
|
109
|
+
const filteredPeople = computed(() =>
|
|
110
|
+
query.value === ""
|
|
111
|
+
? options
|
|
112
|
+
: options.filter((person) =>
|
|
113
|
+
person.name
|
|
114
|
+
.toLowerCase()
|
|
115
|
+
.replace(/\s+/g, "")
|
|
116
|
+
.includes(query.value.toLowerCase().replace(/\s+/g, ""))
|
|
117
|
+
)
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
watch(
|
|
121
|
+
value,
|
|
122
|
+
() => {
|
|
123
|
+
query.value = "";
|
|
124
|
+
},
|
|
125
|
+
{ deep: true }
|
|
126
|
+
);
|
|
127
|
+
</script>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="d-demo">
|
|
3
|
+
<Input
|
|
4
|
+
v-model="value"
|
|
5
|
+
:mask="mask"
|
|
6
|
+
v-model:unmasked="unmaskedValue"
|
|
7
|
+
v-model:isValid="isValid"
|
|
8
|
+
v-model:validationMessage="validationMessage"
|
|
9
|
+
class="d-input"
|
|
10
|
+
type="tel"
|
|
11
|
+
/>
|
|
12
|
+
<div>
|
|
13
|
+
<div>val: {{ value }}</div>
|
|
14
|
+
<div>Unmasked value: {{ unmaskedValue }}</div>
|
|
15
|
+
<div>Is valid: {{ isValid }}</div>
|
|
16
|
+
<div>Validation message: {{ validationMessage }}</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { ref } from "vue";
|
|
23
|
+
import { Input } from "../../../src/main";
|
|
24
|
+
|
|
25
|
+
const value = ref("test");
|
|
26
|
+
const mask = {
|
|
27
|
+
mask: "{+7} (000) 000-00-00",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const unmaskedValue = ref("");
|
|
31
|
+
const isValid = ref();
|
|
32
|
+
const validationMessage = ref("");
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<style scoped></style>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="d-demo">
|
|
3
|
+
<div>
|
|
4
|
+
<input type="checkbox" v-model="multiple" /> Multiselectable
|
|
5
|
+
<input type="checkbox" v-model="loop" /> Loop
|
|
6
|
+
<div>Value: {{ value }}</div>
|
|
7
|
+
<Listbox
|
|
8
|
+
class="list"
|
|
9
|
+
:multiselectable="multiple"
|
|
10
|
+
:loop="loop"
|
|
11
|
+
v-model="value"
|
|
12
|
+
>
|
|
13
|
+
<ListboxOption value="1" class="list-item"> Item 1 </ListboxOption>
|
|
14
|
+
<ListboxOption value="2" class="list-item"> Item 2 </ListboxOption>
|
|
15
|
+
<ListboxOption value="3" class="list-item"> Item 3 </ListboxOption>
|
|
16
|
+
</Listbox>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { ref, watch } from "vue";
|
|
23
|
+
import { Listbox, ListboxOption } from "../../../src/main";
|
|
24
|
+
|
|
25
|
+
const value = ref();
|
|
26
|
+
const multiple = ref(false);
|
|
27
|
+
const loop = ref(false);
|
|
28
|
+
|
|
29
|
+
watch(multiple, (val) => {
|
|
30
|
+
if (val) {
|
|
31
|
+
value.value = [value.value];
|
|
32
|
+
} else {
|
|
33
|
+
value.value = value.value.length ? value.value[0] : undefined;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<style>
|
|
39
|
+
.list {
|
|
40
|
+
padding: 0.5rem;
|
|
41
|
+
border: 2px blue solid;
|
|
42
|
+
border-radius: 0.5rem;
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
gap: 0.25rem;
|
|
46
|
+
}
|
|
47
|
+
.list-item {
|
|
48
|
+
padding: 0.25rem 0.5rem;
|
|
49
|
+
border-radius: 0.25rem;
|
|
50
|
+
background-color: Lavender;
|
|
51
|
+
outline: none;
|
|
52
|
+
}
|
|
53
|
+
.list-item[aria-selected="true"] {
|
|
54
|
+
background-color: blue;
|
|
55
|
+
color: white;
|
|
56
|
+
}
|
|
57
|
+
.list-item[aria-selected="false"]:hover {
|
|
58
|
+
background-color: LightSkyBlue;
|
|
59
|
+
}
|
|
60
|
+
.list[aria-multiselectable="true"] .list-item[data-active="true"] {
|
|
61
|
+
outline: 3px solid DeepPink;
|
|
62
|
+
outline-offset: 2px;
|
|
63
|
+
}
|
|
64
|
+
</style>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="d-demo">
|
|
3
|
+
<Menu v-slot="{ attrs }">
|
|
4
|
+
<button v-bind="attrs" class="d-btn">Basic Example</button>
|
|
5
|
+
|
|
6
|
+
<MenuItems class="d-menu">
|
|
7
|
+
<MenuItem @select="onSelect('Item 1')" class="d-menu-item"
|
|
8
|
+
>Item 1</MenuItem
|
|
9
|
+
>
|
|
10
|
+
<MenuItem @select="onSelect('Item 2')" class="d-menu-item"
|
|
11
|
+
>Item 2</MenuItem
|
|
12
|
+
>
|
|
13
|
+
<MenuItem @select="onSelect('Item 3')" class="d-menu-item"
|
|
14
|
+
>Item 3</MenuItem
|
|
15
|
+
>
|
|
16
|
+
</MenuItems>
|
|
17
|
+
</Menu>
|
|
18
|
+
|
|
19
|
+
<Menu v-model="color" align="right" v-slot="{ attrs }">
|
|
20
|
+
<button v-bind="attrs" :style="{ backgroundColor: color }" class="d-btn">
|
|
21
|
+
Radio Items
|
|
22
|
+
<span v-if="color">({{ color }})</span>
|
|
23
|
+
</button>
|
|
24
|
+
|
|
25
|
+
<MenuItems class="d-menu">
|
|
26
|
+
<MenuItem
|
|
27
|
+
v-for="(color, name) in colors"
|
|
28
|
+
:value="color"
|
|
29
|
+
class="d-menu-item"
|
|
30
|
+
v-slot="{ isSelected }"
|
|
31
|
+
>
|
|
32
|
+
<span :style="{ fontWeight: isSelected ? 'bold' : 'normal' }">
|
|
33
|
+
{{ name }}
|
|
34
|
+
</span>
|
|
35
|
+
</MenuItem>
|
|
36
|
+
</MenuItems>
|
|
37
|
+
</Menu>
|
|
38
|
+
|
|
39
|
+
<Menu v-model="option" align="stretch" v-slot="{ attrs }">
|
|
40
|
+
<button v-bind="attrs" class="d-btn">Checkbox Items</button>
|
|
41
|
+
|
|
42
|
+
<MenuItems class="d-menu">
|
|
43
|
+
<MenuItem
|
|
44
|
+
v-for="(option, name) in options"
|
|
45
|
+
:value="option"
|
|
46
|
+
class="d-menu-item"
|
|
47
|
+
v-slot="{ isSelected }"
|
|
48
|
+
>
|
|
49
|
+
{{ name }}
|
|
50
|
+
<svg
|
|
51
|
+
v-if="isSelected"
|
|
52
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
53
|
+
width="1rem"
|
|
54
|
+
height="1rem"
|
|
55
|
+
viewBox="0 0 256 256"
|
|
56
|
+
>
|
|
57
|
+
<path
|
|
58
|
+
fill="currentColor"
|
|
59
|
+
d="m232.49 80.49l-128 128a12 12 0 0 1-17 0l-56-56a12 12 0 1 1 17-17L96 183L215.51 63.51a12 12 0 0 1 17 17Z"
|
|
60
|
+
/>
|
|
61
|
+
</svg>
|
|
62
|
+
</MenuItem>
|
|
63
|
+
<hr />
|
|
64
|
+
<MenuItem disabled class="d-menu-item">Disabled item</MenuItem>
|
|
65
|
+
</MenuItems>
|
|
66
|
+
</Menu>
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<script setup lang="ts">
|
|
71
|
+
import { ref } from "vue";
|
|
72
|
+
import { Menu, MenuItems, MenuItem } from "../../../src/main";
|
|
73
|
+
|
|
74
|
+
const colors = {
|
|
75
|
+
Blue: "#007bff",
|
|
76
|
+
Purple: "#6f42c1",
|
|
77
|
+
Pink: "#e83e8c",
|
|
78
|
+
Red: "#dc3545",
|
|
79
|
+
};
|
|
80
|
+
const color = ref();
|
|
81
|
+
|
|
82
|
+
const options = {
|
|
83
|
+
"Option 1": 1,
|
|
84
|
+
"Option 2": 2,
|
|
85
|
+
"Option 3": 3,
|
|
86
|
+
"Option 4": 4,
|
|
87
|
+
};
|
|
88
|
+
const option = ref([]);
|
|
89
|
+
|
|
90
|
+
function onSelect(msg: string) {
|
|
91
|
+
alert(msg);
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="d-demo">
|
|
3
|
+
<Popover direction="right" align="center" v-slot="{ attrs, toggle, close }">
|
|
4
|
+
<button @click="toggle" v-bind="attrs" class="d-btn">Toggle</button>
|
|
5
|
+
|
|
6
|
+
<PopoverDialog class="d-popover d-popover--h">
|
|
7
|
+
<div>Any HTML content 😜</div>
|
|
8
|
+
<button @click="close()" class="d-btn">Close</button>
|
|
9
|
+
</PopoverDialog>
|
|
10
|
+
</Popover>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
import { Popover, PopoverDialog } from "../../../src/main";
|
|
16
|
+
</script>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<TabContainer v-model="currentTab">
|
|
3
|
+
<TabList class="tab-list">
|
|
4
|
+
<Tab v-for="(tab, i) in Object.keys(tabs)" :value="i" class="tab">
|
|
5
|
+
{{ tab }}
|
|
6
|
+
</Tab>
|
|
7
|
+
</TabList>
|
|
8
|
+
|
|
9
|
+
<TabPanel
|
|
10
|
+
v-for="(tab, i) in Object.values(tabs)"
|
|
11
|
+
:value="i"
|
|
12
|
+
class="tab-panel"
|
|
13
|
+
>
|
|
14
|
+
{{ tab }}
|
|
15
|
+
</TabPanel>
|
|
16
|
+
</TabContainer>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { ref } from "vue";
|
|
21
|
+
import { TabContainer, TabList, Tab, TabPanel } from "../../../src/main";
|
|
22
|
+
|
|
23
|
+
const tabs = {
|
|
24
|
+
"Tab 1": "Tab Panel 1",
|
|
25
|
+
"Tab 2": "Tab Panel 2",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const currentTab = ref(0);
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
.tab-list {
|
|
33
|
+
padding: 0.25rem;
|
|
34
|
+
display: flex;
|
|
35
|
+
gap: 0.25rem;
|
|
36
|
+
}
|
|
37
|
+
.tab {
|
|
38
|
+
padding: 0.25rem 0.5rem;
|
|
39
|
+
border-radius: 0.25rem 0.25rem 0 0;
|
|
40
|
+
width: 100%;
|
|
41
|
+
background-color: Lavender;
|
|
42
|
+
}
|
|
43
|
+
.tab:focus {
|
|
44
|
+
z-index: 10;
|
|
45
|
+
}
|
|
46
|
+
.tab[aria-selected="false"]:hover {
|
|
47
|
+
background-color: LightSkyBlue;
|
|
48
|
+
}
|
|
49
|
+
.tab[aria-selected="true"] {
|
|
50
|
+
background-color: blue;
|
|
51
|
+
color: white;
|
|
52
|
+
}
|
|
53
|
+
.tab-panel {
|
|
54
|
+
padding: 1.5rem;
|
|
55
|
+
border: 2px blue solid;
|
|
56
|
+
border-radius: 0.5rem;
|
|
57
|
+
text-align: center;
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="d-demo">
|
|
3
|
+
<Tooltip text="Tooltip text" v-slot="{ attrs }" class="d-tooltip">
|
|
4
|
+
<button v-bind="attrs" class="d-btn">Basic</button>
|
|
5
|
+
</Tooltip>
|
|
6
|
+
|
|
7
|
+
<Tooltip
|
|
8
|
+
text="Tooltip text"
|
|
9
|
+
delay="1000"
|
|
10
|
+
v-slot="{ attrs }"
|
|
11
|
+
class="d-tooltip"
|
|
12
|
+
>
|
|
13
|
+
<button v-bind="attrs" class="d-btn">Delayed</button>
|
|
14
|
+
</Tooltip>
|
|
15
|
+
|
|
16
|
+
<Tooltip id="tooltip-demo" class="d-tooltip">
|
|
17
|
+
<template #content>Any HTML content 😜</template>
|
|
18
|
+
</Tooltip>
|
|
19
|
+
<button aria-describedby="tooltip-demo" class="d-btn">
|
|
20
|
+
Custom content
|
|
21
|
+
</button>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup lang="ts">
|
|
26
|
+
import { Tooltip } from "../../../src/main";
|
|
27
|
+
</script>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import MenuDemo from './demos/menu.vue'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
# Menu Components
|
|
6
|
+
|
|
7
|
+
A composite widget that offers a list of choices to the user.
|
|
8
|
+
|
|
9
|
+
Based on:
|
|
10
|
+
|
|
11
|
+
- [usePopover](/composables/popover)
|
|
12
|
+
- [useList](/composables/list)
|
|
13
|
+
- [useListOption](/composables/list-option)
|
|
14
|
+
|
|
15
|
+
## Demo
|
|
16
|
+
|
|
17
|
+
<MenuDemo />
|
|
18
|
+
|
|
19
|
+
::: details Code
|
|
20
|
+
<<< ./demos/menu.vue
|
|
21
|
+
:::
|
|
22
|
+
|
|
23
|
+
## Menu
|
|
24
|
+
|
|
25
|
+
The root component.
|
|
26
|
+
|
|
27
|
+
### Properties
|
|
28
|
+
|
|
29
|
+
| Property | Required | Description | Type | Default |
|
|
30
|
+
| --------------- | -------- | ------------------------------------------------------------- | --------------------------------------------------------------------------- | -------------- |
|
|
31
|
+
| **modelValue** | false | The selected value. Set to an array to enable multiselection. | `any` \| `any[]` | `undefined` |
|
|
32
|
+
| **direction** | false | The direction of the menu. | `'up'` \| `'down'` \| `'left'` \| `'right'` | `'down'` |
|
|
33
|
+
| **align** | false | The alignment of the menu. | `'top'` \| `'bottom'` \| `'left'` \| `'right'` \| `'center'` \| `'stretch'` | `'left'` |
|
|
34
|
+
| **position** | false | CSS position of the menu panel. | `'fixed'` \| `'absolute'` | `'fixed'` |
|
|
35
|
+
| **id** | false | The ID of the menu panel. | `string` | auto-generated |
|
|
36
|
+
| **activatorId** | false | The ID of the activator element. | `string` | auto-generated |
|
|
37
|
+
|
|
38
|
+
### Slots
|
|
39
|
+
|
|
40
|
+
#### default
|
|
41
|
+
|
|
42
|
+
The `default` slot is used to provide an activator and a list of menu items in the [MenuItems](#menuitems) component. It provides the `attrs` property that must be binded to the activating element.
|
|
43
|
+
|
|
44
|
+
```vue-html
|
|
45
|
+
<Menu v-slot="{ attrs }">
|
|
46
|
+
<button v-bind="attrs">Toggle Menu</button>
|
|
47
|
+
|
|
48
|
+
<MenuItems>
|
|
49
|
+
<MenuItem @select="/* do smth. */">Item 1</MenuItem>
|
|
50
|
+
</MenuItems>
|
|
51
|
+
</Menu>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Keyboard interactions
|
|
55
|
+
|
|
56
|
+
Binding the props to the activating element adds the following keyboard interactions:
|
|
57
|
+
|
|
58
|
+
- `Space` / `Enter` - Opens the menu and places focus on the first item. Or closes the menu and returns focus to the activating element.
|
|
59
|
+
- `Down Arrow` - Opens the menu and moves focus to the first item.
|
|
60
|
+
- `Up Arrow` - Opens the menu and moves focus to the last item.
|
|
61
|
+
|
|
62
|
+
### ARIA roles and attributes
|
|
63
|
+
|
|
64
|
+
The following attributes are managed for the activator:
|
|
65
|
+
|
|
66
|
+
- [aria-controls](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-controls)
|
|
67
|
+
- [aria-haspopup](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-haspopup)
|
|
68
|
+
- [aria-expanded](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-expanded)
|
|
69
|
+
|
|
70
|
+
## MenuItems
|
|
71
|
+
|
|
72
|
+
A component to hold a collection of menu items.
|
|
73
|
+
|
|
74
|
+
### Keyboard interactions
|
|
75
|
+
|
|
76
|
+
- `Escape` - Closes the menu and returns focus to the activating element.
|
|
77
|
+
- `Tab` - Closes the menu.
|
|
78
|
+
- `Down Arrow` - Moves focus to the next item.
|
|
79
|
+
- `Up Arrow` - Moves focus to the previous item.
|
|
80
|
+
- `Home` - Moves focus to the first item.
|
|
81
|
+
- `End` - Moves focus to the last item.
|
|
82
|
+
|
|
83
|
+
### ARIA roles and attributes
|
|
84
|
+
|
|
85
|
+
The menu container has the [menu](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/menu_role) role and automatically manages the following attributes:
|
|
86
|
+
|
|
87
|
+
- [aria-activedescendant](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-activedescendant)
|
|
88
|
+
- [aria-labelledby](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-labelledby)
|
|
89
|
+
|
|
90
|
+
## MenuItem
|
|
91
|
+
|
|
92
|
+
### Properties
|
|
93
|
+
|
|
94
|
+
| Property | Required | Description | Type | Default |
|
|
95
|
+
| ------------ | -------- | ----------------------------- | --------- | -------------- |
|
|
96
|
+
| **value** | false | Value assigned to the item. | `any` | `undefined` |
|
|
97
|
+
| **disabled** | false | Whether the item is disabled. | `boolean` | `false` |
|
|
98
|
+
| **id** | false | The ID of the item. | `string` | auto-generated |
|
|
99
|
+
|
|
100
|
+
### Slots
|
|
101
|
+
|
|
102
|
+
#### default
|
|
103
|
+
|
|
104
|
+
| Attribute | Description | Type |
|
|
105
|
+
| -------------- | ------------------------------------- | --------- |
|
|
106
|
+
| **isSelected** | Whether the item is selected. | `boolean` |
|
|
107
|
+
| **isActive** | Whether the item is active (focused). | `boolean` |
|
|
108
|
+
|
|
109
|
+
### Keyboard interactions
|
|
110
|
+
|
|
111
|
+
- `Enter` - Selects the item and closes the menu.
|
|
112
|
+
- `Space` - Selects the item if it has a value set, otherwise closes the menu.
|
|
113
|
+
|
|
114
|
+
### ARIA roles and attributes
|
|
115
|
+
|
|
116
|
+
if an item has no value set, it will be given the [menuitem](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/menuitem_role) role. Otherwise, it will be given the [menuitemradio](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/menuitemradio_role) or [menuitemcheckbox](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/menuitemcheckbox_role) (if `modelValue` is an array) role.
|
|
117
|
+
|
|
118
|
+
The following attributes are automatically managed:
|
|
119
|
+
|
|
120
|
+
- [aria-checked](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-checked)
|
|
121
|
+
- [aria-disabled](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled)
|