Skip to content

Commit 3b8a325

Browse files
johnleiderJohn LeiderKaelWDikushum
authored
feat(VIconBtn): create new component (#21114)
Co-authored-by: John Leider <[email protected]> Co-authored-by: Kael <[email protected]> Co-authored-by: Ishan Subedi <[email protected]>
1 parent a477e71 commit 3b8a325

File tree

17 files changed

+1425
-0
lines changed

17 files changed

+1425
-0
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"props": {
3+
"active": "When undefined (default), the component utilizes its default variant, otherwise it will use the activeVariant if active is true, or the baseVariant if active is false.",
4+
"activeIcon": "When active is a boolean, this icon is used when active is true.",
5+
"activeVariant": "When active is a boolean, this variant is used when active is true.",
6+
"baseVariant": "When active is a boolean, this variant is used when active is false.",
7+
"hideOverlay": "Hides overlay from being displayed when active or focused.",
8+
"iconColor": "Explicit color applied to the icon.",
9+
"iconSize": "The specific size of the icon, can use named sizes.",
10+
"iconSizes": "An array of tuples that define the icon sizes for each named size.",
11+
"rotate": "The rotation of the icon in degrees.",
12+
"sizes": "An array of tuples that define the button sizes for each named size."
13+
}
14+
}

packages/docs/src/data/nav.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@
256256
"title": "file-upload",
257257
"subfolder": "components"
258258
},
259+
{
260+
"title": "icon-buttons",
261+
"subfolder": "components"
262+
},
259263
{
260264
"title": "pull-to-refresh",
261265
"subfolder": "components"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<template>
2+
<div class="text-center">
3+
<v-btn>
4+
Open Dialog
5+
6+
<v-dialog v-model="dialog" activator="parent" max-width="500" persistent>
7+
<v-card subtitle="Click the X to close" title="Dialog">
8+
<template v-slot:append>
9+
<v-icon-btn icon="$close" @click="dialog = false"></v-icon-btn>
10+
</template>
11+
</v-card>
12+
</v-dialog>
13+
</v-btn>
14+
</div>
15+
</template>
16+
17+
<script setup>
18+
import { shallowRef } from 'vue'
19+
20+
const dialog = shallowRef(false)
21+
</script>
22+
23+
<script>
24+
export default {
25+
data () {
26+
return {
27+
dialog: false,
28+
}
29+
},
30+
}
31+
</script>
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
<template>
2+
<v-container class="pa-md-12">
3+
<v-card
4+
class="mx-auto"
5+
color="blue-grey-darken-2"
6+
max-width="640"
7+
rounded="lg"
8+
variant="tonal"
9+
>
10+
<v-toolbar
11+
density="compact"
12+
rounded="t-lg"
13+
title="Editor"
14+
border
15+
>
16+
<template v-slot:append>
17+
<div class="d-flex align-center pa-1">
18+
<div ref="desktopTarget"></div>
19+
20+
<v-icon-btn
21+
v-if="display.mobile.value"
22+
icon="mdi-dots-vertical"
23+
rounded="lg"
24+
size="32"
25+
>
26+
<v-icon></v-icon>
27+
28+
<v-menu activator="parent" width="200">
29+
<v-card rounded="lg">
30+
<div ref="mobileTarget"></div>
31+
</v-card>
32+
</v-menu>
33+
</v-icon-btn>
34+
</div>
35+
</template>
36+
</v-toolbar>
37+
38+
<div class="px-4 pb-4">
39+
<v-textarea
40+
ref="textarea"
41+
v-model="model"
42+
variant="plain"
43+
auto-grow
44+
hide-details
45+
></v-textarea>
46+
47+
<input
48+
ref="images"
49+
class="d-none"
50+
type="file"
51+
multiple
52+
>
53+
</div>
54+
</v-card>
55+
56+
<Teleport v-if="teleportTarget" :to="teleportTarget">
57+
<div class="d-flex ga-2 pa-2 flex-wrap justify-space-between">
58+
<template v-for="(item, i) in actions" :key="i">
59+
<v-icon-btn
60+
v-if="!item.divider"
61+
v-bind="item"
62+
rounded="lg"
63+
size="32"
64+
></v-icon-btn>
65+
66+
<v-divider v-else-if="!display.mobile.value" class="mx-1" vertical></v-divider>
67+
</template>
68+
</div>
69+
</Teleport>
70+
</v-container>
71+
</template>
72+
73+
<script setup>
74+
import { computed, nextTick, ref, shallowRef } from 'vue'
75+
import { useDisplay } from 'vuetify'
76+
77+
const display = useDisplay()
78+
79+
const images = ref()
80+
const textarea = ref()
81+
const mobileTarget = ref()
82+
const desktopTarget = ref()
83+
const model = shallowRef('')
84+
const teleportTarget = computed(() => display.mobile.value ? mobileTarget.value : desktopTarget.value)
85+
86+
function onClickHeading () {
87+
if (!model.value) {
88+
model.value = '# '
89+
} else if (model.value === '# ') {
90+
model.value = ''
91+
} else {
92+
model.value += ' # '
93+
}
94+
95+
textarea.value.focus()
96+
}
97+
98+
async function onClickBold () {
99+
textarea.value.focus()
100+
101+
if (!model.value) {
102+
model.value = '**'
103+
await nextTick()
104+
textarea.value.setSelectionRange(1, 1)
105+
} else if (model.value === '**') {
106+
model.value = ''
107+
} else {
108+
const start = model.value.length
109+
model.value += ' **'
110+
await nextTick()
111+
textarea.value.setSelectionRange(start + 2, start + 2)
112+
}
113+
}
114+
115+
async function onClickItalic () {
116+
textarea.value.focus()
117+
118+
if (!model.value) {
119+
model.value = '__'
120+
await nextTick()
121+
textarea.value.setSelectionRange(1, 1)
122+
} else if (model.value === '__') {
123+
model.value = ''
124+
} else {
125+
const start = model.value.length
126+
model.value += ' __'
127+
await nextTick()
128+
textarea.value.setSelectionRange(start + 2, start + 2)
129+
}
130+
}
131+
132+
function onClickListNumbered () {
133+
if (!model.value) {
134+
model.value = '1. '
135+
} else if (model.value === '1. ') {
136+
model.value = ''
137+
} else {
138+
model.value += ' 1. '
139+
}
140+
141+
textarea.value.focus()
142+
}
143+
144+
function onClickListUnordered () {
145+
if (!model.value) {
146+
model.value = '- '
147+
} else if (model.value === '- ') {
148+
model.value = ''
149+
} else {
150+
model.value += ' - '
151+
}
152+
153+
textarea.value.focus()
154+
}
155+
156+
function onClickListTask () {
157+
if (!model.value) {
158+
model.value = '- [] '
159+
} else {
160+
model.value += '\n\n- [] '
161+
}
162+
163+
textarea.value.focus()
164+
}
165+
166+
function onClickAttachFiles () {
167+
images.value.click()
168+
}
169+
170+
async function onClickDetails () {
171+
textarea.value.focus()
172+
173+
if (!model.value.startsWith('<details>')) {
174+
model.value = `<details>\n<summary>Details</summary>\n\n\n\n</details>`
175+
await nextTick()
176+
textarea.value.setSelectionRange(38, 38)
177+
} else {
178+
model.value = ''
179+
}
180+
}
181+
182+
const actions = [
183+
{ icon: 'mdi-format-header-1', onClick: onClickHeading },
184+
{ icon: 'mdi-format-bold', onClick: onClickBold },
185+
{ icon: 'mdi-format-italic', onClick: onClickItalic },
186+
{ divider: true },
187+
{ icon: 'mdi-format-list-numbered', onClick: onClickListNumbered },
188+
{ icon: 'mdi-format-list-bulleted', onClick: onClickListUnordered },
189+
{ icon: 'mdi-format-list-checks', onClick: onClickListTask },
190+
{ divider: true },
191+
{ icon: 'mdi-paperclip', onClick: onClickAttachFiles },
192+
{ divider: true },
193+
{ icon: 'mdi-format-vertical-align-bottom', onClick: onClickDetails },
194+
]
195+
</script>

0 commit comments

Comments
 (0)