@dnax/ui 0.0.31 → 0.0.33
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/components/QBtnActions.vue +257 -0
- package/components/QSidebarMenuButton.vue +58 -8
- package/index.ts +1 -0
- package/lib/icons.ts +2 -0
- package/package.json +1 -1
- package/styles/main.css +114 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// QBtnActions — bouton + menu d'actions intégré (type QBtnDropdown Quasar, mais
|
|
3
|
+
// piloté par données : plus besoin de compose un QMenu/QList séparé).
|
|
4
|
+
// <q-btn-actions label="Actions" :actions="acts" @select-action="onSelect" />
|
|
5
|
+
// Chaque action : { label, value?, icon?, color?, description?, onClick? }.
|
|
6
|
+
|
|
7
|
+
export interface BtnAction {
|
|
8
|
+
/** Libellé affiché dans le menu */
|
|
9
|
+
label: string
|
|
10
|
+
/** Valeur émise par @select-action ; si absente, l'action elle-même est émise */
|
|
11
|
+
value?: any
|
|
12
|
+
/** Icône Iconify à gauche (ex. : "lucide:pencil") */
|
|
13
|
+
icon?: string
|
|
14
|
+
/** Icône Iconify à droite */
|
|
15
|
+
iconRight?: string
|
|
16
|
+
/** Couleur du texte/icône (token — ex. "negative" pour une suppression — ou hex) */
|
|
17
|
+
color?: string
|
|
18
|
+
/** Sous-texte descriptif sous le label */
|
|
19
|
+
description?: string
|
|
20
|
+
/** Ligne de séparation affichée au-dessus de l'action */
|
|
21
|
+
separator?: boolean
|
|
22
|
+
/** Action désactivée */
|
|
23
|
+
disable?: boolean
|
|
24
|
+
/** Rappel local au clic (en plus de l'événement @select-action) */
|
|
25
|
+
onClick?: (action: BtnAction) => void
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<script setup lang="ts">
|
|
30
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue"
|
|
31
|
+
import { Icon } from "@iconify/vue"
|
|
32
|
+
import { colorValue } from "../lib/colors"
|
|
33
|
+
import { icons } from "../lib/icons"
|
|
34
|
+
import { radiusStyle, useRadius } from "../lib/useComponentProps"
|
|
35
|
+
import type { RadiusProp } from "../lib/useComponentProps"
|
|
36
|
+
import QBtn from "./QBtn.vue"
|
|
37
|
+
|
|
38
|
+
interface Props {
|
|
39
|
+
/** Liste des actions du menu */
|
|
40
|
+
actions?: BtnAction[]
|
|
41
|
+
/** Libellé du bouton déclencheur (sinon icône seule) */
|
|
42
|
+
label?: string
|
|
43
|
+
/** Icône Iconify du déclencheur (défaut "lucide:ellipsis" si pas de label) */
|
|
44
|
+
icon?: string
|
|
45
|
+
/** Masque la flèche déroulante (n'apparaît de toute façon que s'il y a un label) */
|
|
46
|
+
noCaret?: boolean
|
|
47
|
+
/** Icône de la flèche déroulante */
|
|
48
|
+
dropdownIcon?: string
|
|
49
|
+
/** Alignement du menu sous le bouton */
|
|
50
|
+
align?: "left" | "right"
|
|
51
|
+
/** Largeur minimale du menu (ex. "200px") */
|
|
52
|
+
menuWidth?: string
|
|
53
|
+
// — API déclencheur (transmise à QBtn) —
|
|
54
|
+
color?: string
|
|
55
|
+
textColor?: string
|
|
56
|
+
size?: string
|
|
57
|
+
radius?: RadiusProp
|
|
58
|
+
flat?: boolean
|
|
59
|
+
outline?: boolean
|
|
60
|
+
unelevated?: boolean
|
|
61
|
+
dense?: boolean
|
|
62
|
+
round?: boolean
|
|
63
|
+
square?: boolean
|
|
64
|
+
noCaps?: boolean
|
|
65
|
+
stretch?: boolean
|
|
66
|
+
loading?: boolean
|
|
67
|
+
disable?: boolean
|
|
68
|
+
dark?: boolean
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
72
|
+
actions: () => [],
|
|
73
|
+
noCaret: false,
|
|
74
|
+
dropdownIcon: "lucide:chevron-down",
|
|
75
|
+
align: "right",
|
|
76
|
+
menuWidth: "180px",
|
|
77
|
+
size: "md",
|
|
78
|
+
color: "primary",
|
|
79
|
+
flat: false,
|
|
80
|
+
outline: false,
|
|
81
|
+
unelevated: false,
|
|
82
|
+
dense: false,
|
|
83
|
+
round: false,
|
|
84
|
+
square: false,
|
|
85
|
+
noCaps: false,
|
|
86
|
+
stretch: false,
|
|
87
|
+
loading: false,
|
|
88
|
+
disable: false,
|
|
89
|
+
dark: false,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const emit = defineEmits<{
|
|
93
|
+
/** Action sélectionnée — payload : `value` de l'action (ou l'action si pas de value) */
|
|
94
|
+
"select-action": [value: any]
|
|
95
|
+
}>()
|
|
96
|
+
|
|
97
|
+
const open = ref(false)
|
|
98
|
+
const rootEl = ref<HTMLElement | null>(null)
|
|
99
|
+
|
|
100
|
+
const canOpen = computed(() => !props.disable && !props.loading && props.actions.length > 0)
|
|
101
|
+
|
|
102
|
+
// Icône par défaut : « … » si le déclencheur est en icône seule (pas de label)
|
|
103
|
+
const triggerIcon = computed(() => props.icon ?? (props.label ? undefined : icons.ellipsis))
|
|
104
|
+
|
|
105
|
+
// Flèche déroulante : seulement avec un label (bouton icône seule = pas de caret)
|
|
106
|
+
const showCaret = computed(() => !props.noCaret && !!props.label)
|
|
107
|
+
|
|
108
|
+
const triggerClasses = computed(() => [
|
|
109
|
+
"q-btn-actions__trigger",
|
|
110
|
+
open.value && "q-btn-actions__trigger--open",
|
|
111
|
+
])
|
|
112
|
+
|
|
113
|
+
const effectiveRadius = useRadius("QBtnActions", () => props.radius)
|
|
114
|
+
const roundedStyle = computed(() => radiusStyle(effectiveRadius.value))
|
|
115
|
+
|
|
116
|
+
const panelStyle = computed<Record<string, string>>(() => ({
|
|
117
|
+
minWidth: props.menuWidth,
|
|
118
|
+
...(roundedStyle.value ?? {}),
|
|
119
|
+
}))
|
|
120
|
+
|
|
121
|
+
// ═════════ Ouverture / fermeture ═════════
|
|
122
|
+
const toggle = () => (open.value ? close() : openMenu())
|
|
123
|
+
|
|
124
|
+
const openMenu = async () => {
|
|
125
|
+
if (!canOpen.value) return
|
|
126
|
+
open.value = true
|
|
127
|
+
await nextTick()
|
|
128
|
+
;(rootEl.value?.querySelector(".q-btn-actions__item:not(:disabled)") as HTMLElement | null)?.focus()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const close = () => {
|
|
132
|
+
if (!open.value) return
|
|
133
|
+
open.value = false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Clic extérieur → ferme
|
|
137
|
+
const onDocMousedown = (e: MouseEvent) => {
|
|
138
|
+
if (rootEl.value && !rootEl.value.contains(e.target as Node)) close()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Clavier : flèches/Home/End dans le menu, Échap ferme (focus revient au trigger)
|
|
142
|
+
const onKeydown = (e: KeyboardEvent) => {
|
|
143
|
+
if (!open.value) return
|
|
144
|
+
if (e.key === "Escape") {
|
|
145
|
+
e.preventDefault()
|
|
146
|
+
close()
|
|
147
|
+
;(rootEl.value?.querySelector(".q-btn-actions__trigger") as HTMLElement | null)?.focus()
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(e.key)) return
|
|
151
|
+
const items = Array.from(
|
|
152
|
+
rootEl.value?.querySelectorAll(".q-btn-actions__item:not(:disabled)") ?? [],
|
|
153
|
+
) as HTMLElement[]
|
|
154
|
+
if (!items.length) return
|
|
155
|
+
const current = items.indexOf(document.activeElement as HTMLElement)
|
|
156
|
+
let next = current
|
|
157
|
+
if (e.key === "ArrowDown") next = (current + 1) % items.length
|
|
158
|
+
else if (e.key === "ArrowUp") next = (current - 1 + items.length) % items.length
|
|
159
|
+
else if (e.key === "Home") next = 0
|
|
160
|
+
else if (e.key === "End") next = items.length - 1
|
|
161
|
+
e.preventDefault()
|
|
162
|
+
items[next]?.focus()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const onSelect = (action: BtnAction) => {
|
|
166
|
+
if (action.disable) return
|
|
167
|
+
action.onClick?.(action)
|
|
168
|
+
close()
|
|
169
|
+
emit("select-action", action.value !== undefined ? action.value : action)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Entrée « séparateur seul » ({ separator: true } sans label/icône) : simple trait, pas de bouton cliquable */
|
|
173
|
+
const isSeparatorOnly = (action: BtnAction): boolean =>
|
|
174
|
+
!!action.separator && !action.label && !action.icon && !action.iconRight && !action.description
|
|
175
|
+
|
|
176
|
+
const itemStyle = (action: BtnAction) =>
|
|
177
|
+
action.color ? { color: colorValue(action.color) } : undefined
|
|
178
|
+
|
|
179
|
+
onMounted(() => {
|
|
180
|
+
if (typeof document !== "undefined") document.addEventListener("mousedown", onDocMousedown)
|
|
181
|
+
})
|
|
182
|
+
onBeforeUnmount(() => {
|
|
183
|
+
if (typeof document !== "undefined") document.removeEventListener("mousedown", onDocMousedown)
|
|
184
|
+
})
|
|
185
|
+
</script>
|
|
186
|
+
|
|
187
|
+
<template>
|
|
188
|
+
<div
|
|
189
|
+
ref="rootEl"
|
|
190
|
+
class="q-btn-actions"
|
|
191
|
+
:class="[`q-btn-actions--align-${align}`, dark && 'dark']"
|
|
192
|
+
@keydown="onKeydown"
|
|
193
|
+
>
|
|
194
|
+
<QBtn
|
|
195
|
+
class="q-btn-actions__trigger"
|
|
196
|
+
:class="triggerClasses"
|
|
197
|
+
:label="label"
|
|
198
|
+
:icon="triggerIcon"
|
|
199
|
+
:color="color"
|
|
200
|
+
:text-color="textColor"
|
|
201
|
+
:size="size"
|
|
202
|
+
:radius="radius"
|
|
203
|
+
:flat="flat"
|
|
204
|
+
:outline="outline"
|
|
205
|
+
:unelevated="unelevated"
|
|
206
|
+
:dense="dense"
|
|
207
|
+
:round="round"
|
|
208
|
+
:square="square"
|
|
209
|
+
:no-caps="noCaps"
|
|
210
|
+
:stretch="stretch"
|
|
211
|
+
:loading="loading"
|
|
212
|
+
:disable="disable"
|
|
213
|
+
:aria-haspopup="'menu'"
|
|
214
|
+
:aria-expanded="open ? 'true' : 'false'"
|
|
215
|
+
@click="toggle"
|
|
216
|
+
>
|
|
217
|
+
<Icon
|
|
218
|
+
v-if="showCaret"
|
|
219
|
+
:icon="dropdownIcon"
|
|
220
|
+
class="q-btn-actions__caret"
|
|
221
|
+
:class="{ 'q-btn-actions__caret--open': open }"
|
|
222
|
+
aria-hidden="true"
|
|
223
|
+
/>
|
|
224
|
+
</QBtn>
|
|
225
|
+
|
|
226
|
+
<Transition name="q-popup">
|
|
227
|
+
<div
|
|
228
|
+
v-if="open"
|
|
229
|
+
class="q-btn-actions__panel"
|
|
230
|
+
role="menu"
|
|
231
|
+
:aria-label="label ?? 'Actions'"
|
|
232
|
+
:style="panelStyle"
|
|
233
|
+
>
|
|
234
|
+
<template v-for="(action, i) in actions" :key="i">
|
|
235
|
+
<div v-if="action.separator" class="q-btn-actions__separator" role="separator" />
|
|
236
|
+
<button
|
|
237
|
+
v-if="!isSeparatorOnly(action)"
|
|
238
|
+
type="button"
|
|
239
|
+
role="menuitem"
|
|
240
|
+
class="q-btn-actions__item"
|
|
241
|
+
:class="{ 'q-btn-actions__item--disabled': action.disable }"
|
|
242
|
+
:disabled="action.disable"
|
|
243
|
+
:style="itemStyle(action)"
|
|
244
|
+
@click="onSelect(action)"
|
|
245
|
+
>
|
|
246
|
+
<Icon :icon="action.icon" v-if="action.icon" class="q-btn-actions__item-icon" aria-hidden="true" />
|
|
247
|
+
<span class="q-btn-actions__item-text">
|
|
248
|
+
<span class="q-btn-actions__item-label">{{ action.label }}</span>
|
|
249
|
+
<span v-if="action.description" class="q-btn-actions__item-desc">{{ action.description }}</span>
|
|
250
|
+
</span>
|
|
251
|
+
<Icon :icon="action.iconRight" v-if="action.iconRight" class="q-btn-actions__item-icon" aria-hidden="true" />
|
|
252
|
+
</button>
|
|
253
|
+
</template>
|
|
254
|
+
</div>
|
|
255
|
+
</Transition>
|
|
256
|
+
</div>
|
|
257
|
+
</template>
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
// QSidebarMenuButton — bouton/lien de menu (équivalent SidebarMenuButton).
|
|
3
|
+
// Avec `to`, le bouton devient un lien routeur : navigation au clic + état actif
|
|
4
|
+
// automatique selon la route courante (pattern QRouteTab).
|
|
3
5
|
import { computed } from "vue"
|
|
4
6
|
import { Icon } from "@iconify/vue"
|
|
5
7
|
import { cn } from "../lib/utils"
|
|
8
|
+
import { useRoute, useRouter } from "vue-router"
|
|
9
|
+
import type { RouteLocationRaw } from "vue-router"
|
|
6
10
|
|
|
7
11
|
interface Props {
|
|
8
12
|
label?: string
|
|
9
13
|
/** Icône Iconify (ex. : "lucide:settings") */
|
|
10
14
|
icon?: string
|
|
11
|
-
/** Lien natif (<a>)
|
|
15
|
+
/** Lien natif (<a href>) — sans routeur */
|
|
12
16
|
href?: string
|
|
13
|
-
/**
|
|
17
|
+
/** Destination routeur : rend un lien et active le bouton selon la route courante */
|
|
18
|
+
to?: RouteLocationRaw
|
|
19
|
+
/** Match exact (path + hash) — sinon préfixe de segment de chemin */
|
|
20
|
+
exact?: boolean
|
|
21
|
+
/** router.replace au lieu de router.push */
|
|
22
|
+
replace?: boolean
|
|
23
|
+
/** État actif manuel (ignoré quand `to` est fourni — l'activation suit la route) */
|
|
14
24
|
active?: boolean
|
|
15
25
|
activeClass?: string
|
|
16
26
|
disable?: boolean
|
|
@@ -20,35 +30,75 @@ interface Props {
|
|
|
20
30
|
|
|
21
31
|
const props = withDefaults(defineProps<Props>(), {
|
|
22
32
|
active: false,
|
|
33
|
+
exact: false,
|
|
34
|
+
replace: false,
|
|
23
35
|
activeClass: "",
|
|
24
36
|
disable: false,
|
|
25
37
|
})
|
|
26
38
|
|
|
27
39
|
const emit = defineEmits<{ click: [event: MouseEvent] }>()
|
|
28
40
|
|
|
41
|
+
// Routeur optionnel : sans router installé, `to` dégrade en simple bouton (@click)
|
|
42
|
+
let router: ReturnType<typeof useRouter> | undefined
|
|
43
|
+
let route: ReturnType<typeof useRoute> | undefined
|
|
44
|
+
try {
|
|
45
|
+
router = useRouter()
|
|
46
|
+
route = useRoute()
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
router = undefined
|
|
50
|
+
route = undefined
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// La route courante matche-t-elle `to` ? (même logique que QRouteTab)
|
|
54
|
+
const matches = computed(() => {
|
|
55
|
+
if (!router || !route || !props.to) return false
|
|
56
|
+
const resolved = router.resolve(props.to)
|
|
57
|
+
if (props.exact) {
|
|
58
|
+
return resolved.path === route.path && resolved.hash === route.hash
|
|
59
|
+
}
|
|
60
|
+
const prefix = resolved.path.endsWith("/") ? resolved.path : resolved.path + "/"
|
|
61
|
+
return route.path === resolved.path || route.path.startsWith(prefix)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const effectiveActive = computed(() => (props.to ? matches.value : props.active))
|
|
65
|
+
|
|
66
|
+
const resolvedHref = computed(() => {
|
|
67
|
+
if (props.href) return props.href
|
|
68
|
+
if (props.to && router) return router.resolve(props.to).href
|
|
69
|
+
return undefined
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const tag = computed(() => (resolvedHref.value ? "a" : "button"))
|
|
73
|
+
|
|
29
74
|
const buttonClasses = computed(() =>
|
|
30
75
|
cn(
|
|
31
76
|
"q-sidebar__menu-button",
|
|
32
|
-
|
|
33
|
-
|
|
77
|
+
effectiveActive.value && "q-sidebar__menu-button--active",
|
|
78
|
+
effectiveActive.value && props.activeClass,
|
|
34
79
|
props.disable && "q-sidebar__menu-button--disabled",
|
|
35
80
|
),
|
|
36
81
|
)
|
|
37
82
|
|
|
38
83
|
const onClick = (e: MouseEvent) => {
|
|
39
84
|
if (props.disable) return
|
|
85
|
+
if (props.to && router && !e.defaultPrevented) {
|
|
86
|
+
e.preventDefault()
|
|
87
|
+
if (props.replace) router.replace(props.to)
|
|
88
|
+
else router.push(props.to)
|
|
89
|
+
}
|
|
40
90
|
emit("click", e)
|
|
41
91
|
}
|
|
42
92
|
</script>
|
|
43
93
|
|
|
44
94
|
<template>
|
|
45
95
|
<component
|
|
46
|
-
:is="
|
|
47
|
-
:href="
|
|
48
|
-
:disabled="
|
|
96
|
+
:is="tag"
|
|
97
|
+
:href="resolvedHref"
|
|
98
|
+
:disabled="tag === 'button' ? disable : undefined"
|
|
49
99
|
class="q-sidebar__menu-button"
|
|
50
100
|
:class="buttonClasses"
|
|
51
|
-
:aria-current="
|
|
101
|
+
:aria-current="effectiveActive ? 'page' : undefined"
|
|
52
102
|
@click="onClick"
|
|
53
103
|
>
|
|
54
104
|
<Icon :icon="icon" v-if="icon" class="q-sidebar__menu-button-icon" aria-hidden="true" />
|
package/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export { default as QBottomSheetHeader } from "./components/QBottomSheetHeader.v
|
|
|
17
17
|
export { default as QBottomSheetProvider } from "./components/QBottomSheetProvider.vue"
|
|
18
18
|
export { default as QBottomSheetTrigger } from "./components/QBottomSheetTrigger.vue"
|
|
19
19
|
export { default as QBtn } from "./components/QBtn.vue"
|
|
20
|
+
export { default as QBtnActions } from "./components/QBtnActions.vue"
|
|
20
21
|
export { default as QBtnGroup } from "./components/QBtnGroup.vue"
|
|
21
22
|
export { default as QBubble } from "./components/QBubble.vue"
|
|
22
23
|
export { default as QBubbleContent } from "./components/QBubbleContent.vue"
|
package/lib/icons.ts
CHANGED
|
@@ -14,6 +14,8 @@ export const icons = {
|
|
|
14
14
|
chevronUp: "lucide:chevron-up",
|
|
15
15
|
chevronsLeft: "lucide:chevrons-left",
|
|
16
16
|
chevronsRight: "lucide:chevrons-right",
|
|
17
|
+
ellipsis: "lucide:ellipsis",
|
|
18
|
+
ellipsisVertical: "lucide:ellipsis-vertical",
|
|
17
19
|
copy: "lucide:copy",
|
|
18
20
|
eye: "lucide:eye",
|
|
19
21
|
eyeOff: "lucide:eye-off",
|
package/package.json
CHANGED
package/styles/main.css
CHANGED
|
@@ -306,6 +306,7 @@ html.dark body {
|
|
|
306
306
|
.dark .q-select__popup,
|
|
307
307
|
.dark .q-autocomplete__popup,
|
|
308
308
|
.dark .q-nav-menu__panel,
|
|
309
|
+
.dark .q-btn-actions__panel,
|
|
309
310
|
.dark .q-dialog__content,
|
|
310
311
|
.dark .q-loading__box,
|
|
311
312
|
.dark .q-file-picker__row {
|
|
@@ -313,6 +314,15 @@ html.dark body {
|
|
|
313
314
|
color: var(--foreground);
|
|
314
315
|
}
|
|
315
316
|
|
|
317
|
+
.dark .q-btn-actions__item:hover,
|
|
318
|
+
.dark .q-btn-actions__item:focus-visible {
|
|
319
|
+
background-color: rgb(255 255 255 / 0.08);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.dark .q-btn-actions__separator {
|
|
323
|
+
background-color: rgb(255 255 255 / 0.14);
|
|
324
|
+
}
|
|
325
|
+
|
|
316
326
|
.dark .q-table {
|
|
317
327
|
color: var(--foreground);
|
|
318
328
|
}
|
|
@@ -2434,6 +2444,110 @@ html.dark body {
|
|
|
2434
2444
|
flex: 1 1 auto;
|
|
2435
2445
|
}
|
|
2436
2446
|
|
|
2447
|
+
/* ===== QBtnActions ===== */
|
|
2448
|
+
.q-btn-actions {
|
|
2449
|
+
position: relative;
|
|
2450
|
+
display: inline-flex;
|
|
2451
|
+
vertical-align: middle;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
.q-btn-actions__caret {
|
|
2455
|
+
width: 1em;
|
|
2456
|
+
height: 1em;
|
|
2457
|
+
flex-shrink: 0;
|
|
2458
|
+
transition: transform 0.2s ease;
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
.q-btn-actions__caret--open {
|
|
2462
|
+
transform: rotate(180deg);
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
.q-btn-actions__panel {
|
|
2466
|
+
position: absolute;
|
|
2467
|
+
top: calc(100% + 4px);
|
|
2468
|
+
z-index: 1200;
|
|
2469
|
+
padding: 4px;
|
|
2470
|
+
background-color: #fff;
|
|
2471
|
+
border: 1px solid rgb(0 0 0 / 0.12);
|
|
2472
|
+
border-radius: var(--q-radius, 6px);
|
|
2473
|
+
box-shadow: 0 4px 16px rgb(0 0 0 / 0.15);
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
.q-btn-actions--align-left .q-btn-actions__panel {
|
|
2477
|
+
left: 0;
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
.q-btn-actions--align-right .q-btn-actions__panel {
|
|
2481
|
+
right: 0;
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
.q-btn-actions__item {
|
|
2485
|
+
display: flex;
|
|
2486
|
+
align-items: center;
|
|
2487
|
+
gap: 8px;
|
|
2488
|
+
width: 100%;
|
|
2489
|
+
padding: 7px 8px;
|
|
2490
|
+
border: none;
|
|
2491
|
+
border-radius: 4px;
|
|
2492
|
+
background: transparent;
|
|
2493
|
+
color: inherit;
|
|
2494
|
+
font: inherit;
|
|
2495
|
+
font-size: 14px;
|
|
2496
|
+
line-height: 1.35;
|
|
2497
|
+
text-align: left;
|
|
2498
|
+
cursor: pointer;
|
|
2499
|
+
user-select: none;
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
.q-btn-actions__item:hover,
|
|
2503
|
+
.q-btn-actions__item:focus-visible {
|
|
2504
|
+
background-color: rgb(0 0 0 / 0.06);
|
|
2505
|
+
outline: none;
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
.q-btn-actions__item--disabled {
|
|
2509
|
+
opacity: 0.5;
|
|
2510
|
+
pointer-events: none;
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
.q-btn-actions__item-icon {
|
|
2514
|
+
width: 16px;
|
|
2515
|
+
height: 16px;
|
|
2516
|
+
flex-shrink: 0;
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
.q-btn-actions__item-text {
|
|
2520
|
+
flex: 1;
|
|
2521
|
+
min-width: 0;
|
|
2522
|
+
display: flex;
|
|
2523
|
+
flex-direction: column;
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
.q-btn-actions__item-label {
|
|
2527
|
+
overflow: hidden;
|
|
2528
|
+
text-overflow: ellipsis;
|
|
2529
|
+
white-space: nowrap;
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
.q-btn-actions__item-desc {
|
|
2533
|
+
font-size: 12px;
|
|
2534
|
+
opacity: 0.6;
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
.q-btn-actions__separator {
|
|
2538
|
+
height: 1px;
|
|
2539
|
+
margin: 4px 6px;
|
|
2540
|
+
background-color: rgb(0 0 0 / 0.1);
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
.q-popup-enter-active {
|
|
2544
|
+
animation: q-popup-in 0.12s ease;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
.q-popup-leave-active {
|
|
2548
|
+
animation: q-popup-in 0.1s ease reverse;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2437
2551
|
/* ===== QImg ===== */
|
|
2438
2552
|
.q-img {
|
|
2439
2553
|
position: relative;
|