@dnax/ui 0.0.11 → 0.0.13

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.
@@ -1,9 +1,27 @@
1
1
  <script setup lang="ts">
2
- // QBottomSheetFooter — zone d'actions en bas du panneau (équivalent DrawerFooter).
2
+ // QBottomSheetFooter — pied du panneau façon barre d'app (équivalent QFooter) :
3
+ // les actions sont embarquées dans un <q-toolbar> (min-height 50px, padding
4
+ // 0 12px) et alignées à droite (justify-content: flex-end — styles/main.css).
5
+ // no-padding retire le padding horizontal du toolbar.
6
+ import QToolbar from "./QToolbar.vue"
7
+
8
+ interface Props {
9
+ /** Supprime le padding du toolbar (actions collées aux bords) */
10
+ noPadding?: boolean
11
+ }
12
+
13
+ withDefaults(defineProps<Props>(), {
14
+ noPadding: false,
15
+ })
3
16
  </script>
4
17
 
5
18
  <template>
6
- <div class="q-bottom-sheet__footer">
7
- <slot />
19
+ <div
20
+ class="q-bottom-sheet__footer"
21
+ :class="{ 'q-bottom-sheet__footer--no-padding': noPadding }"
22
+ >
23
+ <q-toolbar>
24
+ <slot />
25
+ </q-toolbar>
8
26
  </div>
9
27
  </template>
@@ -1,35 +1,51 @@
1
1
  <script setup lang="ts">
2
- // QBottomSheetHeader — header du panneau : titre + description + bouton fermer (équivalent DrawerHeader/DrawerTitle).
2
+ // QBottomSheetHeader — header du panneau façon barre d'app (équivalent QHeader) :
3
+ // le contenu est embarqué dans un <q-toolbar> (min-height 50px, padding 0 12px),
4
+ // avec titre + description + bouton fermer (équivalent DrawerHeader/DrawerTitle).
5
+ // no-padding retire le padding horizontal du toolbar.
3
6
  import { inject } from "vue"
4
7
  import { Icon } from "@iconify/vue"
5
8
  import { icons } from "../lib/icons"
6
9
  import { qBottomSheetKey } from "./QBottomSheet.vue"
10
+ import QToolbar from "./QToolbar.vue"
11
+ import QSpace from "./QSpace.vue"
7
12
 
8
13
  interface Props {
9
14
  title?: string
10
15
  description?: string
16
+ /** Supprime le padding du toolbar (contenu collé aux bords) */
17
+ noPadding?: boolean
11
18
  }
12
19
 
13
- const props = defineProps<Props>()
20
+ withDefaults(defineProps<Props>(), {
21
+ noPadding: false,
22
+ })
23
+
14
24
  const sheet = inject(qBottomSheetKey, null)
15
25
  </script>
16
26
 
17
27
  <template>
18
- <div class="q-bottom-sheet__header">
19
- <div class="q-bottom-sheet__header-text">
20
- <h2 v-if="title" class="q-bottom-sheet__title">{{ title }}</h2>
21
- <slot name="title" />
22
- <p v-if="description" class="q-bottom-sheet__description">{{ description }}</p>
23
- <slot name="description" />
24
- </div>
25
- <button
26
- type="button"
27
- class="q-bottom-sheet__close"
28
- aria-label="Fermer"
29
- @click="sheet?.setOpen(false)"
30
- >
31
- <Icon :icon="icons.x" aria-hidden="true" />
32
- </button>
33
- <slot />
28
+ <div
29
+ class="q-bottom-sheet__header"
30
+ :class="{ 'q-bottom-sheet__header--no-padding': noPadding }"
31
+ >
32
+ <q-toolbar>
33
+ <div class="q-bottom-sheet__header-text">
34
+ <h2 v-if="title" class="q-bottom-sheet__title">{{ title }}</h2>
35
+ <slot name="title" />
36
+ <p v-if="description" class="q-bottom-sheet__description">{{ description }}</p>
37
+ <slot name="description" />
38
+ </div>
39
+ <q-space />
40
+ <button
41
+ type="button"
42
+ class="q-bottom-sheet__close"
43
+ aria-label="Fermer"
44
+ @click="sheet?.setOpen(false)"
45
+ >
46
+ <Icon :icon="icons.x" aria-hidden="true" />
47
+ </button>
48
+ <slot />
49
+ </q-toolbar>
34
50
  </div>
35
51
  </template>
@@ -38,6 +38,19 @@ interface Props {
38
38
  waveColor?: string
39
39
  /** Couleur de la seconde vague (background-effect="wave", défaut violet) */
40
40
  waveColor2?: string
41
+ /** Style glassmorphism : fond translucide + flou d'arrière-plan + bordure claire */
42
+ glass?: boolean
43
+ /** URL d'une image de fond — rendue en couche derrière le contenu (cover) */
44
+ backgroundImage?: string
45
+ /** Taille de l'image : cover | contain | fill | none | scale-down, ou valeur
46
+ * CSS ("50%", "400px" → boîte centrée, image entière visible) */
47
+ backgroundImageSize?: string
48
+ /** Anime l'image de fond (Ken Burns indéterminé : zoom + panoramique lents en boucle) */
49
+ backgroundAnimated?: boolean
50
+ /** Direction de la boucle Ken Burns : alternate (défaut) | alternate-reverse | normal | reverse */
51
+ backgroundAnimationDirection?: "alternate" | "alternate-reverse" | "normal" | "reverse"
52
+ /** Durée d'une itération du Ken Burns (défaut 24s) */
53
+ backgroundAnimationDuration?: string
41
54
  class?: string
42
55
  }
43
56
 
@@ -47,6 +60,13 @@ const props = withDefaults(defineProps<Props>(), {
47
60
  padding: "16px",
48
61
  })
49
62
 
63
+ const IMG_FIT = new Set(["cover", "contain", "fill", "none", "scale-down"])
64
+
65
+ // Taille CSS libre ("50%", "400px"…) → boîte centrée (object-fit: contain)
66
+ const imgSized = computed(
67
+ () => !!props.backgroundImageSize && !IMG_FIT.has(props.backgroundImageSize),
68
+ )
69
+
50
70
  const containerStyle = computed<Record<string, string>>(() => {
51
71
  const style: Record<string, string> = {
52
72
  "--q-container-max": typeof props.maxWidth === "number" ? `${props.maxWidth}px` : props.maxWidth,
@@ -61,6 +81,18 @@ const containerStyle = computed<Record<string, string>>(() => {
61
81
  if (props.auroraColor2) style["--q-aurora-color-2"] = props.auroraColor2
62
82
  if (props.waveColor) style["--q-wave-color"] = props.waveColor
63
83
  if (props.waveColor2) style["--q-wave-color-2"] = props.waveColor2
84
+ if (props.backgroundImageSize && IMG_FIT.has(props.backgroundImageSize)) {
85
+ style["--q-container-img-fit"] = props.backgroundImageSize
86
+ }
87
+ if (imgSized.value && props.backgroundImageSize) {
88
+ style["--q-container-img-size"] = props.backgroundImageSize
89
+ }
90
+ if (props.backgroundAnimationDirection) {
91
+ style["--q-container-kenburns-direction"] = props.backgroundAnimationDirection
92
+ }
93
+ if (props.backgroundAnimationDuration) {
94
+ style["--q-container-kenburns-duration"] = props.backgroundAnimationDuration
95
+ }
64
96
  return style
65
97
  })
66
98
 
@@ -68,7 +100,8 @@ const containerClasses = computed(() =>
68
100
  cn(
69
101
  "q-container",
70
102
  props.fluid && "q-container--fluid",
71
- props.backgroundEffect && "q-container--bg",
103
+ (props.backgroundEffect || props.backgroundImage) && "q-container--bg",
104
+ props.glass && "q-container--glass",
72
105
  props.class,
73
106
  ),
74
107
  )
@@ -145,6 +178,19 @@ onBeforeUnmount(() => rootEl.value?.removeEventListener("mousemove", onMouseMove
145
178
 
146
179
  <template>
147
180
  <div ref="rootEl" class="q-container" :class="containerClasses" :style="containerStyle">
181
+ <img
182
+ v-if="backgroundImage"
183
+ :src="backgroundImage"
184
+ class="q-container__img"
185
+ :class="{
186
+ 'q-container__img--glass': glass,
187
+ 'q-container__img--animated': backgroundAnimated,
188
+ 'q-container__img--sized': imgSized,
189
+ }"
190
+ alt=""
191
+ aria-hidden="true"
192
+ draggable="false"
193
+ />
148
194
  <div
149
195
  v-if="backgroundEffect"
150
196
  class="q-container__bg"
@@ -88,8 +88,36 @@ const onDocKeydown = (e: KeyboardEvent) => {
88
88
  // « Retour » navigateur → ferme le dialog au lieu de naviguer
89
89
  useOverlayBack(open, () => { open.value = false }, "QDialog")
90
90
 
91
- watch(open, (v) => {
92
- if (typeof document !== "undefined") document.body.style.overflow = v ? "hidden" : ""
91
+ // — Verrou du scroll de la page derrière le dialog —
92
+ // Compteur module-level + suivi par instance : chaque dialog verrouille au plus
93
+ // une fois et déverrouille au plus une fois → pile de dialogs ($q.dialog) sans
94
+ // déverrouillage prématuré. Verrouille html + body (iOS Safari ignore
95
+ // overflow:hidden sur body seul).
96
+ let scrollLockCount = 0
97
+ function setBodyScrollLock(locked: boolean) {
98
+ if (typeof document === "undefined") return
99
+ scrollLockCount = Math.max(0, scrollLockCount + (locked ? 1 : -1))
100
+ const on = scrollLockCount > 0
101
+ document.documentElement.style.overflow = on ? "hidden" : ""
102
+ document.body.style.overflow = on ? "hidden" : ""
103
+ }
104
+
105
+ let didLock = false
106
+ const lockIf = (v: boolean) => {
107
+ if (v && !didLock) {
108
+ didLock = true
109
+ setBodyScrollLock(true)
110
+ }
111
+ else if (!v && didLock) {
112
+ didLock = false
113
+ setBodyScrollLock(false)
114
+ }
115
+ }
116
+
117
+ onMounted(() => lockIf(props.modelValue))
118
+ watch(open, lockIf)
119
+ onBeforeUnmount(() => {
120
+ if (didLock) setBodyScrollLock(false)
93
121
  })
94
122
 
95
123
  onMounted(() => {
@@ -98,8 +126,8 @@ onMounted(() => {
98
126
  onBeforeUnmount(() => {
99
127
  if (typeof document !== "undefined") {
100
128
  document.removeEventListener("keydown", onDocKeydown)
101
- document.body.style.overflow = ""
102
129
  }
130
+ if (didLock) setBodyScrollLock(false)
103
131
  })
104
132
 
105
133
  const overlayClasses = computed(() => cn("q-dialog__overlay", `q-dialog__overlay--${props.position}`))
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ // QDialogContent — section de corps de la modale (équivalent QCardSection).
3
+ // Le panneau <q-dialog> est en flex-column (max-height 90vh) : avec `scrollable`,
4
+ // le corps prend l'espace restant et défile en interne, entre un header et un
5
+ // footer fixes (essentiel en mode maximized, où le panneau est overflow: hidden).
6
+ interface Props {
7
+ /** Rendu scrollable : le corps défile entre header et footer fixes */
8
+ scrollable?: boolean
9
+ }
10
+
11
+ withDefaults(defineProps<Props>(), {
12
+ scrollable: false,
13
+ })
14
+ </script>
15
+
16
+ <template>
17
+ <div class="q-dialog__body" :class="{ 'q-dialog__body--scrollable': scrollable }">
18
+ <slot />
19
+ </div>
20
+ </template>
@@ -1,9 +1,27 @@
1
1
  <script setup lang="ts">
2
- // QDialogFooter — zone d'actions en bas de la modale (équivalent DialogFooter).
2
+ // QDialogFooter — pied de la modale façon barre d'app (équivalent QFooter) :
3
+ // les actions sont embarquées dans un <q-toolbar> (min-height 50px, padding
4
+ // 0 12px) et alignées à droite (justify-content: flex-end — styles/main.css).
5
+ // no-padding retire le padding horizontal du toolbar.
6
+ import QToolbar from "./QToolbar.vue"
7
+
8
+ interface Props {
9
+ /** Supprime le padding du toolbar (actions collées aux bords) */
10
+ noPadding?: boolean
11
+ }
12
+
13
+ withDefaults(defineProps<Props>(), {
14
+ noPadding: false,
15
+ })
3
16
  </script>
4
17
 
5
18
  <template>
6
- <div class="q-dialog__footer">
7
- <slot />
19
+ <div
20
+ class="q-dialog__footer"
21
+ :class="{ 'q-dialog__footer--no-padding': noPadding }"
22
+ >
23
+ <q-toolbar>
24
+ <slot />
25
+ </q-toolbar>
8
26
  </div>
9
27
  </template>
@@ -1,41 +1,55 @@
1
1
  <script setup lang="ts">
2
- // QDialogHeader — header de la modale : titre + description + bouton fermer (équivalent DialogHeader/Title/Close).
2
+ // QDialogHeader — header de la modale façon barre d'app (équivalent QHeader) :
3
+ // le contenu est embarqué dans un <q-toolbar> (min-height 50px, padding 0 12px),
4
+ // avec titre + description + bouton fermer (équivalent DialogHeader/Title/Close).
5
+ // no-padding retire le padding horizontal du toolbar.
3
6
  import { inject } from "vue"
4
7
  import { Icon } from "@iconify/vue"
5
8
  import { icons } from "../lib/icons"
6
9
  import { qDialogKey } from "./QDialog.vue"
10
+ import QToolbar from "./QToolbar.vue"
11
+ import QSpace from "./QSpace.vue"
7
12
 
8
13
  interface Props {
9
14
  title?: string
10
15
  description?: string
11
16
  /** Bouton fermer */
12
17
  showClose?: boolean
18
+ /** Supprime le padding du toolbar (contenu collé aux bords) */
19
+ noPadding?: boolean
13
20
  }
14
21
 
15
22
  const props = withDefaults(defineProps<Props>(), {
16
23
  showClose: true,
24
+ noPadding: false,
17
25
  })
18
26
 
19
27
  const dialog = inject(qDialogKey, null)
20
28
  </script>
21
29
 
22
30
  <template>
23
- <div class="q-dialog__header">
24
- <div class="q-dialog__header-text">
25
- <h2 v-if="title" class="q-dialog__title">{{ title }}</h2>
26
- <slot name="title" />
27
- <p v-if="description" class="q-dialog__description">{{ description }}</p>
28
- <slot name="description" />
29
- </div>
30
- <button
31
- v-if="showClose"
32
- type="button"
33
- class="q-dialog__close"
34
- aria-label="Fermer"
35
- @click="dialog?.setOpen(false)"
36
- >
37
- <Icon :icon="icons.x" aria-hidden="true" />
38
- </button>
39
- <slot />
31
+ <div
32
+ class="q-dialog__header"
33
+ :class="{ 'q-dialog__header--no-padding': noPadding }"
34
+ >
35
+ <q-toolbar>
36
+ <div class="q-dialog__header-text">
37
+ <h2 v-if="title" class="q-dialog__title">{{ title }}</h2>
38
+ <slot name="title" />
39
+ <p v-if="description" class="q-dialog__description">{{ description }}</p>
40
+ <slot name="description" />
41
+ </div>
42
+ <q-space />
43
+ <button
44
+ v-if="showClose"
45
+ type="button"
46
+ class="q-dialog__close"
47
+ aria-label="Fermer"
48
+ @click="dialog?.setOpen(false)"
49
+ >
50
+ <Icon :icon="icons.x" aria-hidden="true" />
51
+ </button>
52
+ <slot />
53
+ </q-toolbar>
40
54
  </div>
41
55
  </template>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  // QFooter — barre basse (safe-area bottom appliquée par styles/main.css : .q-app .q-footer)
3
3
  import { computed, onBeforeUnmount, onMounted, ref } from "vue"
4
+ import { useFixedBarOffset } from "../lib/fixedLayout"
4
5
 
5
6
  interface Props {
6
7
  /** Fixe la barre en bas de l'écran (sort du flux) */
@@ -15,6 +16,8 @@ interface Props {
15
16
  translucent?: boolean | number
16
17
  /** Glassmorphism marqué : fond très translucide + flou fort + bordure claire */
17
18
  glass?: boolean
19
+ /** Supprime le padding horizontal (éléments collés aux bords, safe-area conservée) */
20
+ noPadding?: boolean
18
21
  }
19
22
 
20
23
  const props = withDefaults(defineProps<Props>(), {
@@ -22,8 +25,14 @@ const props = withDefaults(defineProps<Props>(), {
22
25
  elevated: false,
23
26
  reveal: false,
24
27
  bordered: false,
28
+ noPadding: false,
25
29
  })
26
30
 
31
+ const rootEl = ref<HTMLElement | null>(null)
32
+
33
+ // Empilement : bottom = hauteur cumulée des footers fixed suivants (si fixed)
34
+ useFixedBarOffset(rootEl, "bar-bottom", () => props.fixed)
35
+
27
36
  const translucentStyle = computed<Record<string, string> | undefined>(() =>
28
37
  props.translucent === true || typeof props.translucent === "number"
29
38
  ? { "--q-translucent-opacity": `${typeof props.translucent === "number" ? props.translucent : 70}%` }
@@ -55,6 +64,7 @@ onBeforeUnmount(() => {
55
64
 
56
65
  <template>
57
66
  <footer
67
+ ref="rootEl"
58
68
  class="q-footer"
59
69
  :class="{
60
70
  'q-footer--fixed': fixed,
@@ -64,6 +74,7 @@ onBeforeUnmount(() => {
64
74
  'q-footer--reveal-hidden': props.reveal && !visible,
65
75
  'q-footer--translucent': props.translucent === true || typeof props.translucent === 'number',
66
76
  'q-footer--glass': props.glass,
77
+ 'q-footer--no-padding': props.noPadding,
67
78
  }"
68
79
  :style="translucentStyle"
69
80
  >
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  // QPage — zone de contenu. Prop `virtual` : active le virtual scroll
3
3
  // (rendu fenêtré via QVirtualScroll) quand on a beaucoup d'items.
4
- // Padding-top automatique quand des barres fixed (q-header / q-back-header)
5
- // précèdent la page : le contenu reste sous les barres (jamais masqué).
4
+ // Padding-top/padding-bottom automatiques quand des barres fixed
5
+ // (q-header / q-back-header avant, q-footer après) entourent la page : le
6
+ // contenu reste visible, jamais masqué par les barres au scroll.
6
7
  import { onBeforeUnmount, onMounted, ref } from "vue"
7
8
  import { useFixedBarOffset } from "../lib/fixedLayout"
8
9
  import QVirtualScroll from "./QVirtualScroll.vue"
@@ -95,9 +95,11 @@ const tabClasses = computed(() =>
95
95
  isActive.value && tabs?.activeClass.value,
96
96
  (props.noCaps || tabs?.noCaps.value) && "q-tab--no-caps",
97
97
  props.disable && "q-tab--disabled",
98
- tabs?.inlineLabel.value && "q-tab--inline-label",
98
+ // Mode replié : le label s'étend à droite de l'icône → forcer l'inline-label
99
+ // (et neutraliser le padding stacked-icon + le fallback :has) */
100
+ (tabs?.inlineLabel.value || tabs?.collapseInactive.value) && "q-tab--inline-label",
99
101
  // Icône empilée au-dessus du label : padding vertical pour l'aérer du cadre
100
- props.icon && !tabs?.inlineLabel.value && "q-tab--stacked-icon",
102
+ props.icon && !tabs?.inlineLabel.value && !tabs?.collapseInactive.value && "q-tab--stacked-icon",
101
103
  ),
102
104
  )
103
105
 
@@ -16,6 +16,8 @@ export interface QTabContext {
16
16
  inlineLabel: Readonly<Ref<boolean>>
17
17
  noCaps: Readonly<Ref<boolean>>
18
18
  activeClass: Readonly<Ref<string | undefined>>
19
+ /** Mode replié : seul le tab actif montre icône + label (les autres : icône seule) */
20
+ collapseInactive: Readonly<Ref<boolean>>
19
21
  }
20
22
 
21
23
  export const qTabsKey: InjectionKey<QTabContext> = Symbol("q-tabs")
@@ -49,6 +51,8 @@ interface Props {
49
51
  activeColor?: string
50
52
  /** Couleur de fond du tab actif */
51
53
  activeBgColor?: string
54
+ /** Couleur des tabs inactifs (token ou hex — défaut : couleur héritée) */
55
+ inactiveColor?: string
52
56
  /** Classe ajoutée au tab actif */
53
57
  activeClass?: string
54
58
  /** Couleur de l'indicateur (défaut : activeColor → primary) */
@@ -74,6 +78,19 @@ interface Props {
74
78
  contentClass?: string
75
79
  /** Anime les tabs au clic (appui scale + pop du tab actif) */
76
80
  animated?: boolean
81
+ /**
82
+ * Transition du changement de tab (requiert `animated`) :
83
+ * spring (défaut, rebond léger) | smooth (expo-out velouté, label qui monte)
84
+ * | elastic (rebond marqué).
85
+ */
86
+ transition?: "spring" | "smooth" | "elastic"
87
+ /** Durée des transitions en ms (indicateur, labels collapse, pop/rise) */
88
+ transitionDuration?: number
89
+ /**
90
+ * Mode replié (pattern mobile) : les tabs inactifs n'affichent que l'icône ;
91
+ * le tab actif s'étend (icône + label) avec une largeur animée.
92
+ */
93
+ collapseInactive?: boolean
77
94
  }
78
95
 
79
96
  const props = withDefaults(defineProps<Props>(), {
@@ -81,6 +98,7 @@ const props = withDefaults(defineProps<Props>(), {
81
98
  align: "center",
82
99
  activeColor: "",
83
100
  activeBgColor: "",
101
+ inactiveColor: "",
84
102
  activeClass: "",
85
103
  indicatorColor: "",
86
104
  dense: false,
@@ -92,6 +110,9 @@ const props = withDefaults(defineProps<Props>(), {
92
110
  stretch: false,
93
111
  contentClass: "",
94
112
  animated: false,
113
+ transition: "spring",
114
+ transitionDuration: undefined,
115
+ collapseInactive: false,
95
116
  })
96
117
 
97
118
  const emit = defineEmits<{ "update:modelValue": [value: string | number | null] }>()
@@ -107,6 +128,7 @@ const indicatorPos = computed<"top" | "bottom" | "none">(() => {
107
128
  // shallowRef : on ne veut PAS que Vue dé-unwrappe les Ref contenues dans les enregistrements
108
129
  const tabRegs = shallowRef<QTabRegistration[]>([])
109
130
  const tick = ref(0)
131
+ const rootEl = ref<HTMLElement | null>(null)
110
132
 
111
133
  // Recalcule la position de l'indicateur après chaque changement de DOM
112
134
  const measure = async () => {
@@ -114,14 +136,25 @@ const measure = async () => {
114
136
  tick.value++
115
137
  }
116
138
 
139
+ // collapse-inactive : le label s'étend/retombe via une transition max-width
140
+ // (0.3s) — à la fin, la largeur du tab actif change encore → re-mesurer,
141
+ // sinon l'indicateur reste calé sur la largeur repliée (mal positionné).
142
+ // transitionend bulle depuis le label jusqu'à la racine : un seul écouteur.
143
+ const onCollapseEnd = (e: Event) => {
144
+ if ((e as TransitionEvent).propertyName !== "max-width") return
145
+ measure()
146
+ }
147
+
117
148
  watch([() => props.modelValue, tabRegs], measure)
118
149
 
119
150
  onMounted(() => {
120
151
  measure()
121
152
  if (typeof window !== "undefined") window.addEventListener("resize", measure)
153
+ rootEl.value?.addEventListener("transitionend", onCollapseEnd)
122
154
  })
123
155
  onBeforeUnmount(() => {
124
156
  if (typeof window !== "undefined") window.removeEventListener("resize", measure)
157
+ rootEl.value?.removeEventListener("transitionend", onCollapseEnd)
125
158
  })
126
159
 
127
160
  const context: QTabContext = {
@@ -136,6 +169,7 @@ const context: QTabContext = {
136
169
  inlineLabel: computed(() => props.inlineLabel),
137
170
  noCaps: computed(() => props.noCaps),
138
171
  activeClass: computed(() => (props.activeClass ? props.activeClass : undefined)),
172
+ collapseInactive: computed(() => props.collapseInactive),
139
173
  }
140
174
 
141
175
  provide(qTabsKey, context)
@@ -170,6 +204,8 @@ const containerStyle = computed<Record<string, string>>(() => {
170
204
  // Token (primary…) → var(--primary) ; sinon valeur directe (hex, rgb…)
171
205
  if (props.activeColor) style["--q-tabs-active-color"] = colorValue(props.activeColor)
172
206
  if (props.activeBgColor) style["--q-tabs-active-bg"] = colorValue(props.activeBgColor)
207
+ if (props.inactiveColor) style["--q-tabs-inactive-color"] = colorValue(props.inactiveColor)
208
+ if (props.transitionDuration) style["--q-tabs-duration"] = `${props.transitionDuration}ms`
173
209
  return style
174
210
  })
175
211
 
@@ -184,12 +220,15 @@ const tabsClasses = computed(() =>
184
220
  props.shrink && "q-tabs--shrink",
185
221
  props.stretch && "q-tabs--stretch",
186
222
  props.animated && "q-tabs--animated",
223
+ props.animated && `q-tabs--anim-${props.transition}`,
224
+ props.collapseInactive && "q-tabs--collapse-inactive",
187
225
  ),
188
226
  )
189
227
  </script>
190
228
 
191
229
  <template>
192
230
  <div
231
+ ref="rootEl"
193
232
  class="q-tabs"
194
233
  :class="tabsClasses"
195
234
  :style="containerStyle"
package/index.ts CHANGED
@@ -39,6 +39,7 @@ export { default as QCountryPicker } from "./components/QCountryPicker.vue"
39
39
  export { default as QDataGrid } from "./components/QDataGrid.vue"
40
40
  export { default as QDatePicker } from "./components/QDatePicker.vue"
41
41
  export { default as QDialog } from "./components/QDialog.vue"
42
+ export { default as QDialogContent } from "./components/QDialogContent.vue"
42
43
  export { default as QDialogFooter } from "./components/QDialogFooter.vue"
43
44
  export { default as QDialogHeader } from "./components/QDialogHeader.vue"
44
45
  export { default as QDialogProvider } from "./components/QDialogProvider.vue"