@dnax/ui 0.0.23 → 0.0.25
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/QBottomSheet.vue +28 -2
- package/components/QBottomSheetProvider.vue +2 -0
- package/components/QDialog.vue +7 -0
- package/components/QDialogHeader.vue +3 -3
- package/components/QPage.vue +1 -14
- package/components/QTabs.vue +23 -5
- package/index.ts +1 -0
- package/lib/closeOverlay.ts +48 -0
- package/lib/q.ts +4 -0
- package/module.ts +14 -0
- package/package.json +1 -1
- package/scripts/generate-exports.ts +1 -0
- package/styles/main.css +45 -10
|
@@ -16,6 +16,7 @@ export const qBottomSheetKey: InjectionKey<BottomSheetContext> = Symbol("q-botto
|
|
|
16
16
|
import { computed, onBeforeUnmount, onMounted, provide, ref, watch } from "vue"
|
|
17
17
|
import { cn } from "../lib/utils"
|
|
18
18
|
import { useOverlayBack } from "../lib/overlayBack"
|
|
19
|
+
import { markOverlayClose } from "../lib/closeOverlay"
|
|
19
20
|
|
|
20
21
|
interface Props {
|
|
21
22
|
/** Ouvert (v-model) */
|
|
@@ -33,6 +34,10 @@ interface Props {
|
|
|
33
34
|
translucent?: boolean | number
|
|
34
35
|
/** Seuil de drag (px) au-delà duquel on ferme */
|
|
35
36
|
dragThreshold?: number
|
|
37
|
+
/** Animation d'ouverture : slide-up (défaut) | fade | zoom */
|
|
38
|
+
transition?: "slide-up" | "fade" | "zoom"
|
|
39
|
+
/** Durée des transitions d'entrée/sortie en ms */
|
|
40
|
+
transitionDuration?: number
|
|
36
41
|
/** Style du panneau (variables de thème pour les contenus téléportés) */
|
|
37
42
|
contentStyle?: Record<string, string>
|
|
38
43
|
}
|
|
@@ -63,6 +68,11 @@ provide<BottomSheetContext>(qBottomSheetKey, {
|
|
|
63
68
|
// « Retour » navigateur → ferme le bottom sheet au lieu de naviguer
|
|
64
69
|
useOverlayBack(open, () => { open.value = false }, "QBottomSheet")
|
|
65
70
|
|
|
71
|
+
// v-close : marque l'overlay comme fermable (la directive remonte jusqu'ici)
|
|
72
|
+
const markOverlay = (el: unknown) => {
|
|
73
|
+
markOverlayClose(el as HTMLElement | null, () => { open.value = false })
|
|
74
|
+
}
|
|
75
|
+
|
|
66
76
|
// Échap + verrouillage du scroll du body
|
|
67
77
|
const onDocKeydown = (e: KeyboardEvent) => {
|
|
68
78
|
if (e.key === "Escape" && open.value && !props.persistent) open.value = false
|
|
@@ -120,6 +130,21 @@ const radiusStyle = computed<Record<string, string> | undefined>(() => {
|
|
|
120
130
|
return undefined
|
|
121
131
|
})
|
|
122
132
|
|
|
133
|
+
// Nom de transition : q-bs (défaut slide-up) | q-bs-fade | q-bs-zoom
|
|
134
|
+
const transitionName = computed(() =>
|
|
135
|
+
props.transition ? `q-bs-${props.transition}` : "q-bs",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
// Durée (ms) → variables consommées par les transitions/animations CSS
|
|
139
|
+
const transitionStyle = computed<Record<string, string> | undefined>(() =>
|
|
140
|
+
props.transitionDuration
|
|
141
|
+
? {
|
|
142
|
+
"--q-bs-duration-enter": `${props.transitionDuration}ms`,
|
|
143
|
+
"--q-bs-duration-leave": `${props.transitionDuration}ms`,
|
|
144
|
+
}
|
|
145
|
+
: undefined,
|
|
146
|
+
)
|
|
147
|
+
|
|
123
148
|
const panelClasses = computed(() =>
|
|
124
149
|
cn(
|
|
125
150
|
"q-bottom-sheet__panel",
|
|
@@ -143,9 +168,10 @@ const translucentStyle = computed<Record<string, string> | undefined>(() =>
|
|
|
143
168
|
</span>
|
|
144
169
|
|
|
145
170
|
<Teleport to="body">
|
|
146
|
-
<Transition name="
|
|
171
|
+
<Transition :name="transitionName" :duration="transitionDuration">
|
|
147
172
|
<div
|
|
148
173
|
v-if="open"
|
|
174
|
+
:ref="markOverlay"
|
|
149
175
|
class="q-bottom-sheet__overlay"
|
|
150
176
|
@click="!persistent && (open = false)"
|
|
151
177
|
>
|
|
@@ -153,7 +179,7 @@ const translucentStyle = computed<Record<string, string> | undefined>(() =>
|
|
|
153
179
|
ref="panelRef"
|
|
154
180
|
class="q-bottom-sheet__panel"
|
|
155
181
|
:class="panelClasses"
|
|
156
|
-
:style="[panelStyle, radiusStyle, translucentStyle, props.contentStyle]"
|
|
182
|
+
:style="[panelStyle, radiusStyle, translucentStyle, transitionStyle, props.contentStyle]"
|
|
157
183
|
role="dialog"
|
|
158
184
|
aria-modal="true"
|
|
159
185
|
@click.stop
|
|
@@ -42,6 +42,8 @@ const handleDismiss = (ctrl: BottomSheetController) => {
|
|
|
42
42
|
:dark="sheet._opts.dark"
|
|
43
43
|
:translucent="sheet._opts.translucent"
|
|
44
44
|
:drag-threshold="sheet._opts.dragThreshold"
|
|
45
|
+
:transition="sheet._opts.transition"
|
|
46
|
+
:transition-duration="sheet._opts.transitionDuration"
|
|
45
47
|
:content-style="sheetThemeStyle"
|
|
46
48
|
@update:model-value="(v: boolean) => !v && handleDismiss(sheet)"
|
|
47
49
|
>
|
package/components/QDialog.vue
CHANGED
|
@@ -17,6 +17,7 @@ import { cn } from "../lib/utils"
|
|
|
17
17
|
import { radiusStyle, useRadius } from "../lib/useComponentProps"
|
|
18
18
|
import type { RadiusProp } from "../lib/useComponentProps"
|
|
19
19
|
import { useOverlayBack } from "../lib/overlayBack"
|
|
20
|
+
import { markOverlayClose } from "../lib/closeOverlay"
|
|
20
21
|
|
|
21
22
|
type DialogPosition = "standard" | "top" | "right" | "bottom" | "left"
|
|
22
23
|
|
|
@@ -88,6 +89,11 @@ const onDocKeydown = (e: KeyboardEvent) => {
|
|
|
88
89
|
// « Retour » navigateur → ferme le dialog au lieu de naviguer
|
|
89
90
|
useOverlayBack(open, () => { open.value = false }, "QDialog")
|
|
90
91
|
|
|
92
|
+
// v-close : marque l'overlay comme fermable (la directive remonte jusqu'ici)
|
|
93
|
+
const markOverlay = (el: unknown) => {
|
|
94
|
+
markOverlayClose(el as HTMLElement | null, () => { open.value = false })
|
|
95
|
+
}
|
|
96
|
+
|
|
91
97
|
// — Verrou du scroll de la page derrière le dialog —
|
|
92
98
|
// Compteur module-level + suivi par instance : chaque dialog verrouille au plus
|
|
93
99
|
// une fois et déverrouille au plus une fois → pile de dialogs ($q.dialog) sans
|
|
@@ -169,6 +175,7 @@ const durationStyle = computed<StyleValue>(() =>
|
|
|
169
175
|
<Transition :name="transitionName" @after-enter="emit('show')" @after-leave="emit('hide')">
|
|
170
176
|
<div
|
|
171
177
|
v-if="open"
|
|
178
|
+
:ref="markOverlay"
|
|
172
179
|
class="q-dialog__overlay"
|
|
173
180
|
:class="overlayClasses"
|
|
174
181
|
:style="durationStyle"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
// QDialogHeader — header de la modale façon barre d'app (équivalent QHeader) :
|
|
3
3
|
// le contenu est embarqué dans un <q-toolbar> (min-height 50px, padding 0 12px),
|
|
4
|
-
// avec titre + description + bouton fermer (
|
|
4
|
+
// avec titre + description + bouton fermer OPT-IN (show-close).
|
|
5
5
|
// no-padding retire le padding horizontal du toolbar.
|
|
6
6
|
import { inject } from "vue"
|
|
7
7
|
import { Icon } from "@iconify/vue"
|
|
@@ -13,14 +13,14 @@ import QSpace from "./QSpace.vue"
|
|
|
13
13
|
interface Props {
|
|
14
14
|
title?: string
|
|
15
15
|
description?: string
|
|
16
|
-
/**
|
|
16
|
+
/** Affiche le bouton fermer (opt-in — sans la prop, aucun ×) */
|
|
17
17
|
showClose?: boolean
|
|
18
18
|
/** Supprime le padding du toolbar (contenu collé aux bords) */
|
|
19
19
|
noPadding?: boolean
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const props = withDefaults(defineProps<Props>(), {
|
|
23
|
-
showClose:
|
|
23
|
+
showClose: false,
|
|
24
24
|
noPadding: false,
|
|
25
25
|
})
|
|
26
26
|
|
package/components/QPage.vue
CHANGED
|
@@ -31,19 +31,6 @@ interface Props {
|
|
|
31
31
|
|
|
32
32
|
defineProps<Props>()
|
|
33
33
|
|
|
34
|
-
// Slots typés (mode virtual) : même contrat que QVirtualScroll
|
|
35
|
-
// (item, index, ref pour la mesure ; before/after pour les zones sticky)
|
|
36
|
-
defineSlots<{
|
|
37
|
-
default?: (props: { item: any; index: number; ref: (el: unknown) => void }) => any
|
|
38
|
-
before?: () => any
|
|
39
|
-
after?: () => any
|
|
40
|
-
}>()
|
|
41
|
-
|
|
42
|
-
// L'inférence du scope ne traverse pas la branche v-if → cast explicite
|
|
43
|
-
// (le scope reçu EST le scope du QVirtualScroll)
|
|
44
|
-
type VirtualScope = { item: any; index: number; ref: (el: unknown) => void }
|
|
45
|
-
const forwardScope = (scope: unknown) => scope as VirtualScope
|
|
46
|
-
|
|
47
34
|
const rootEl = ref<HTMLElement | null>(null)
|
|
48
35
|
// ref fonctionnel : sur un élément → l'élément ; sur q-virtual-scroll (composant) → .$el
|
|
49
36
|
const setRoot = (el: unknown) => {
|
|
@@ -71,7 +58,7 @@ useFixedBarOffset(rootEl, "page")
|
|
|
71
58
|
v-bind="$attrs"
|
|
72
59
|
>
|
|
73
60
|
<template #default="scope">
|
|
74
|
-
<slot v-bind="
|
|
61
|
+
<slot v-bind="scope" />
|
|
75
62
|
</template>
|
|
76
63
|
<template #before>
|
|
77
64
|
<slot name="before" />
|
package/components/QTabs.vue
CHANGED
|
@@ -24,7 +24,7 @@ export const qTabsKey: InjectionKey<QTabContext> = Symbol("q-tabs")
|
|
|
24
24
|
</script>
|
|
25
25
|
|
|
26
26
|
<script setup lang="ts">
|
|
27
|
-
import { computed, nextTick, onBeforeUnmount, onMounted, provide, ref, shallowRef, watch } from "vue"
|
|
27
|
+
import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, provide, ref, shallowRef, watch } from "vue"
|
|
28
28
|
import { cva } from "class-variance-authority"
|
|
29
29
|
import { cn } from "../lib/utils"
|
|
30
30
|
import { colorValue } from "../lib/colors"
|
|
@@ -117,6 +117,24 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
117
117
|
|
|
118
118
|
const emit = defineEmits<{ "update:modelValue": [value: string | number | null] }>()
|
|
119
119
|
|
|
120
|
+
// — Mode non-contrôlé : sans v-model (pas de listener update:modelValue), QTabs
|
|
121
|
+
// maintient son propre état interne — indispensable pour QRouteTab (le tab
|
|
122
|
+
// actif suit la route sans v-model parent) et pour tout usage sans v-model.
|
|
123
|
+
const instance = getCurrentInstance()
|
|
124
|
+
const controlled = computed(
|
|
125
|
+
() => !!instance && "onUpdate:modelValue" in (instance.vnode.props ?? {}),
|
|
126
|
+
)
|
|
127
|
+
const internal = ref<string | number | null>(null)
|
|
128
|
+
|
|
129
|
+
const activeName = computed<string | number | null>(() =>
|
|
130
|
+
controlled.value ? (props.modelValue ?? null) : internal.value,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
const setActive = (name: string | number) => {
|
|
134
|
+
if (controlled.value) emit("update:modelValue", name)
|
|
135
|
+
else internal.value = name
|
|
136
|
+
}
|
|
137
|
+
|
|
120
138
|
// Position effective de l'indicateur : "top" | "bottom" (défaut) | "none" (masqué)
|
|
121
139
|
const indicatorPos = computed<"top" | "bottom" | "none">(() => {
|
|
122
140
|
const p = props.switchIndicatorPosition
|
|
@@ -145,7 +163,7 @@ const onCollapseEnd = (e: Event) => {
|
|
|
145
163
|
measure()
|
|
146
164
|
}
|
|
147
165
|
|
|
148
|
-
watch([
|
|
166
|
+
watch([activeName, tabRegs], measure)
|
|
149
167
|
|
|
150
168
|
onMounted(() => {
|
|
151
169
|
measure()
|
|
@@ -158,8 +176,8 @@ onBeforeUnmount(() => {
|
|
|
158
176
|
})
|
|
159
177
|
|
|
160
178
|
const context: QTabContext = {
|
|
161
|
-
activeName
|
|
162
|
-
setActive
|
|
179
|
+
activeName,
|
|
180
|
+
setActive,
|
|
163
181
|
register: (tab) => {
|
|
164
182
|
tabRegs.value = [...tabRegs.value, tab]
|
|
165
183
|
},
|
|
@@ -175,7 +193,7 @@ const context: QTabContext = {
|
|
|
175
193
|
provide(qTabsKey, context)
|
|
176
194
|
|
|
177
195
|
const activeTabEl = computed(
|
|
178
|
-
() => tabRegs.value.find((t) => t.name ===
|
|
196
|
+
() => tabRegs.value.find((t) => t.name === activeName.value)?.el.value ?? null,
|
|
179
197
|
)
|
|
180
198
|
|
|
181
199
|
const indicatorStyle = computed<Record<string, string>>(() => {
|
package/index.ts
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// v-close — directive : ferme l'overlay (dialog, bottom sheet, …) le plus
|
|
2
|
+
// proche au clic (équivalent Quasar v-close-popup).
|
|
3
|
+
// Les overlays s'enregistrent en posant el.__qClose sur leur élément racine
|
|
4
|
+
// (markOverlayClose) ; la directive remonte depuis l'élément cliqué.
|
|
5
|
+
|
|
6
|
+
interface QCloseMarker extends HTMLElement {
|
|
7
|
+
__qClose?: () => void
|
|
8
|
+
__qVClose?: () => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Marque un élément comme « overlay fermable » (posé par QDialog, QBottomSheet…) */
|
|
12
|
+
export function markOverlayClose(el: HTMLElement | null, close: () => void) {
|
|
13
|
+
if (!el) return
|
|
14
|
+
;(el as QCloseMarker).__qClose = close
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Remonte jusqu'à l'overlay le plus proche portant __qClose */
|
|
18
|
+
function closestOverlay(el: Element): QCloseMarker | null {
|
|
19
|
+
let node: Element | null = el
|
|
20
|
+
while (node) {
|
|
21
|
+
const marker = node as QCloseMarker
|
|
22
|
+
if (typeof marker.__qClose === "function") return marker
|
|
23
|
+
node = node.parentElement
|
|
24
|
+
}
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Ferme l'overlay parent le plus proche de `el` */
|
|
29
|
+
export function closeParentOverlay(el: Element) {
|
|
30
|
+
closestOverlay(el)?.__qClose?.()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Directive : <q-btn v-close> → ferme le dialog/sheet parent au clic.
|
|
34
|
+
* v-close="false" désactive la fermeture. */
|
|
35
|
+
export const vClose = {
|
|
36
|
+
mounted(el: HTMLElement, binding: { value?: unknown }) {
|
|
37
|
+
const onClick = () => {
|
|
38
|
+
if (binding.value === false) return
|
|
39
|
+
closeParentOverlay(el)
|
|
40
|
+
}
|
|
41
|
+
el.addEventListener("click", onClick)
|
|
42
|
+
;(el as QCloseMarker).__qVClose = onClick
|
|
43
|
+
},
|
|
44
|
+
unmounted(el: HTMLElement) {
|
|
45
|
+
const fn = (el as QCloseMarker).__qVClose
|
|
46
|
+
if (fn) el.removeEventListener("click", fn)
|
|
47
|
+
},
|
|
48
|
+
}
|
package/lib/q.ts
CHANGED
|
@@ -128,6 +128,10 @@ export interface BottomSheetOptions {
|
|
|
128
128
|
persistent?: boolean
|
|
129
129
|
/** Seuil de drag (px) au-delà duquel on ferme */
|
|
130
130
|
dragThreshold?: number
|
|
131
|
+
/** Animation d'ouverture : slide-up (défaut) | fade | zoom */
|
|
132
|
+
transition?: "slide-up" | "fade" | "zoom"
|
|
133
|
+
/** Durée des transitions d'entrée/sortie en ms */
|
|
134
|
+
transitionDuration?: number
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
export interface BottomSheetController {
|
package/module.ts
CHANGED
|
@@ -85,5 +85,19 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
85
85
|
`,
|
|
86
86
|
})
|
|
87
87
|
}
|
|
88
|
+
|
|
89
|
+
// Directive globale v-close : <q-btn v-close> ferme le dialog/sheet parent
|
|
90
|
+
addPluginTemplate({
|
|
91
|
+
filename: "dnax-ui-directives.mjs",
|
|
92
|
+
mode: "client",
|
|
93
|
+
getContents: () => `
|
|
94
|
+
import { defineNuxtPlugin } from "#imports"
|
|
95
|
+
import { vClose } from "@dnax/ui/runtime"
|
|
96
|
+
|
|
97
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
98
|
+
nuxtApp.vueApp.directive("close", vClose)
|
|
99
|
+
})
|
|
100
|
+
`,
|
|
101
|
+
})
|
|
88
102
|
},
|
|
89
103
|
})
|
package/package.json
CHANGED
|
@@ -50,6 +50,7 @@ export {
|
|
|
50
50
|
installOverlayBackHandler,
|
|
51
51
|
} from "./lib/overlayBack"
|
|
52
52
|
export type { OverlayHandle } from "./lib/overlayBack"
|
|
53
|
+
export { markOverlayClose, closeParentOverlay, vClose } from "./lib/closeOverlay"
|
|
53
54
|
`
|
|
54
55
|
|
|
55
56
|
await Bun.write(indexFile, `${exports}\n${manualExports}`)
|
package/styles/main.css
CHANGED
|
@@ -4868,25 +4868,37 @@ html.dark body {
|
|
|
4868
4868
|
border-color: rgb(255 255 255 / 0.12);
|
|
4869
4869
|
}
|
|
4870
4870
|
|
|
4871
|
-
/* — transitions (slide vertical type vaul) —
|
|
4871
|
+
/* — transitions (slide vertical type vaul) — durées configurables via
|
|
4872
|
+
transition-duration (vars --q-bs-duration-enter/leave) */
|
|
4872
4873
|
.q-bs-enter-active,
|
|
4873
|
-
.q-bs-leave-active
|
|
4874
|
-
|
|
4874
|
+
.q-bs-leave-active,
|
|
4875
|
+
.q-bs-fade-enter-active,
|
|
4876
|
+
.q-bs-fade-leave-active,
|
|
4877
|
+
.q-bs-zoom-enter-active,
|
|
4878
|
+
.q-bs-zoom-leave-active {
|
|
4879
|
+
transition: opacity var(--q-bs-duration-enter, 0.25s) ease;
|
|
4880
|
+
}
|
|
4881
|
+
.q-bs-leave-active,
|
|
4882
|
+
.q-bs-fade-leave-active,
|
|
4883
|
+
.q-bs-zoom-leave-active {
|
|
4884
|
+
transition: opacity var(--q-bs-duration-leave, 0.2s) ease;
|
|
4875
4885
|
}
|
|
4876
|
-
|
|
4877
4886
|
.q-bs-enter-from,
|
|
4878
|
-
.q-bs-leave-to
|
|
4887
|
+
.q-bs-leave-to,
|
|
4888
|
+
.q-bs-fade-enter-from,
|
|
4889
|
+
.q-bs-fade-leave-to,
|
|
4890
|
+
.q-bs-zoom-enter-from,
|
|
4891
|
+
.q-bs-zoom-leave-to {
|
|
4879
4892
|
opacity: 0;
|
|
4880
4893
|
}
|
|
4881
4894
|
|
|
4895
|
+
/* slide-up (défaut) : le panneau glisse depuis le bas */
|
|
4882
4896
|
.q-bs-enter-active .q-bottom-sheet__panel {
|
|
4883
|
-
animation: q-bs-in 0.25s ease;
|
|
4897
|
+
animation: q-bs-in var(--q-bs-duration-enter, 0.25s) ease;
|
|
4884
4898
|
}
|
|
4885
|
-
|
|
4886
4899
|
.q-bs-leave-active .q-bottom-sheet__panel {
|
|
4887
|
-
animation: q-bs-out 0.2s ease forwards;
|
|
4900
|
+
animation: q-bs-out var(--q-bs-duration-leave, 0.2s) ease forwards;
|
|
4888
4901
|
}
|
|
4889
|
-
|
|
4890
4902
|
@keyframes q-bs-in {
|
|
4891
4903
|
from {
|
|
4892
4904
|
transform: translateY(100%);
|
|
@@ -4895,13 +4907,36 @@ html.dark body {
|
|
|
4895
4907
|
transform: none;
|
|
4896
4908
|
}
|
|
4897
4909
|
}
|
|
4898
|
-
|
|
4899
4910
|
@keyframes q-bs-out {
|
|
4900
4911
|
to {
|
|
4901
4912
|
transform: translateY(100%);
|
|
4902
4913
|
}
|
|
4903
4914
|
}
|
|
4904
4915
|
|
|
4916
|
+
/* zoom : le panneau scale légèrement depuis le centre-bas */
|
|
4917
|
+
.q-bs-zoom-enter-active .q-bottom-sheet__panel {
|
|
4918
|
+
animation: q-bs-zoom-in var(--q-bs-duration-enter, 0.25s) ease;
|
|
4919
|
+
}
|
|
4920
|
+
.q-bs-zoom-leave-active .q-bottom-sheet__panel {
|
|
4921
|
+
animation: q-bs-zoom-out var(--q-bs-duration-leave, 0.2s) ease forwards;
|
|
4922
|
+
}
|
|
4923
|
+
@keyframes q-bs-zoom-in {
|
|
4924
|
+
from {
|
|
4925
|
+
opacity: 0;
|
|
4926
|
+
transform: scale(0.96) translateY(8px);
|
|
4927
|
+
}
|
|
4928
|
+
to {
|
|
4929
|
+
opacity: 1;
|
|
4930
|
+
transform: none;
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
@keyframes q-bs-zoom-out {
|
|
4934
|
+
to {
|
|
4935
|
+
opacity: 0;
|
|
4936
|
+
transform: scale(0.96) translateY(8px);
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
|
|
4905
4940
|
/* ===== QBackHeader ===== */
|
|
4906
4941
|
.q-back-header {
|
|
4907
4942
|
position: relative;
|