@dnax/ui 0.0.2 → 0.0.4
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/QBackHeader.vue +8 -2
- package/components/QHeader.vue +10 -1
- package/components/QPage.vue +16 -1
- package/components/QSelect.vue +15 -2
- package/lib/fixedLayout.ts +106 -0
- package/package.json +1 -1
- package/styles/main.css +20 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
// QBackHeader — barre de retour : <q-back-header title="Détails" fixed @back="router.back()" />
|
|
3
3
|
// Bouton retour + titre + actions à droite ; safe-area top intégrée.
|
|
4
|
-
import { computed } from "vue"
|
|
4
|
+
import { computed, ref } from "vue"
|
|
5
5
|
import { Icon } from "@iconify/vue"
|
|
6
6
|
import { icons } from "../lib/icons"
|
|
7
7
|
import { cn } from "../lib/utils"
|
|
8
|
+
import { useFixedBarOffset } from "../lib/fixedLayout"
|
|
8
9
|
|
|
9
10
|
interface Props {
|
|
10
11
|
/** Titre de la page */
|
|
@@ -32,6 +33,11 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
32
33
|
|
|
33
34
|
const emit = defineEmits<{ back: [] }>()
|
|
34
35
|
|
|
36
|
+
const rootEl = ref<HTMLElement | null>(null)
|
|
37
|
+
|
|
38
|
+
// Empilement : top = hauteur cumulée des barres fixed précédentes (si fixed)
|
|
39
|
+
useFixedBarOffset(rootEl, "bar", () => props.fixed)
|
|
40
|
+
|
|
35
41
|
const translucentStyle = computed<Record<string, string> | undefined>(() =>
|
|
36
42
|
props.translucent === true || typeof props.translucent === "number"
|
|
37
43
|
? { "--q-translucent-opacity": `${typeof props.translucent === "number" ? props.translucent : 70}%` }
|
|
@@ -49,7 +55,7 @@ const headerClasses = computed(() =>
|
|
|
49
55
|
</script>
|
|
50
56
|
|
|
51
57
|
<template>
|
|
52
|
-
<header class="q-back-header" :class="headerClasses" :style="translucentStyle">
|
|
58
|
+
<header ref="rootEl" class="q-back-header" :class="headerClasses" :style="translucentStyle">
|
|
53
59
|
<button
|
|
54
60
|
v-if="showBack"
|
|
55
61
|
type="button"
|
package/components/QHeader.vue
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
// QHeader — barre haute. fixed = position: fixed en haut (opt-in).
|
|
3
3
|
// Safe-area top/left/right appliquées toujours (styles/main.css).
|
|
4
|
-
|
|
4
|
+
// Quand plusieurs barres fixed sont empilées (q-back-header + q-header), chaque
|
|
5
|
+
// barre se décale sous la précédente automatiquement.
|
|
6
|
+
import { computed, ref } from "vue"
|
|
7
|
+
import { useFixedBarOffset } from "../lib/fixedLayout"
|
|
5
8
|
|
|
6
9
|
interface Props {
|
|
7
10
|
/** Fixe la barre en haut de l'écran (sort du flux) */
|
|
@@ -16,6 +19,11 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
16
19
|
fixed: false,
|
|
17
20
|
})
|
|
18
21
|
|
|
22
|
+
const rootEl = ref<HTMLElement | null>(null)
|
|
23
|
+
|
|
24
|
+
// Empilement : top = hauteur cumulée des barres fixed précédentes (si fixed)
|
|
25
|
+
useFixedBarOffset(rootEl, "bar", () => props.fixed)
|
|
26
|
+
|
|
19
27
|
const translucentStyle = computed<Record<string, string> | undefined>(() =>
|
|
20
28
|
props.translucent === true || typeof props.translucent === "number"
|
|
21
29
|
? { "--q-translucent-opacity": `${typeof props.translucent === "number" ? props.translucent : 70}%` }
|
|
@@ -25,6 +33,7 @@ const translucentStyle = computed<Record<string, string> | undefined>(() =>
|
|
|
25
33
|
|
|
26
34
|
<template>
|
|
27
35
|
<div
|
|
36
|
+
ref="rootEl"
|
|
28
37
|
class="q-header"
|
|
29
38
|
:class="{
|
|
30
39
|
'q-header--fixed': fixed,
|
package/components/QPage.vue
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
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é).
|
|
6
|
+
import { onBeforeUnmount, onMounted, ref } from "vue"
|
|
7
|
+
import { useFixedBarOffset } from "../lib/fixedLayout"
|
|
4
8
|
import QVirtualScroll from "./QVirtualScroll.vue"
|
|
5
9
|
|
|
6
10
|
interface Props {
|
|
@@ -25,11 +29,22 @@ interface Props {
|
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
defineProps<Props>()
|
|
32
|
+
|
|
33
|
+
const rootEl = ref<HTMLElement | null>(null)
|
|
34
|
+
// ref fonctionnel : sur un élément → l'élément ; sur q-virtual-scroll (composant) → .$el
|
|
35
|
+
const setRoot = (el: unknown) => {
|
|
36
|
+
const node = el as (HTMLElement & { $el?: HTMLElement }) | null
|
|
37
|
+
rootEl.value = node?.$el ?? node ?? null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Offset automatique : padding-top = hauteur des barres fixed précédentes
|
|
41
|
+
useFixedBarOffset(rootEl, "page")
|
|
28
42
|
</script>
|
|
29
43
|
|
|
30
44
|
<template>
|
|
31
45
|
<q-virtual-scroll
|
|
32
46
|
v-if="virtual"
|
|
47
|
+
:ref="setRoot"
|
|
33
48
|
class="q-page"
|
|
34
49
|
:items="items"
|
|
35
50
|
:item-key="itemKey"
|
|
@@ -50,7 +65,7 @@ defineProps<Props>()
|
|
|
50
65
|
<slot name="after" />
|
|
51
66
|
</template>
|
|
52
67
|
</q-virtual-scroll>
|
|
53
|
-
<div v-else class="q-page">
|
|
68
|
+
<div v-else :ref="setRoot" class="q-page">
|
|
54
69
|
<slot />
|
|
55
70
|
</div>
|
|
56
71
|
</template>
|
package/components/QSelect.vue
CHANGED
|
@@ -300,6 +300,12 @@ const inputDisplay = computed(() => {
|
|
|
300
300
|
return selectedOptions.value[0] ? getOptionLabel(selectedOptions.value[0]) : ""
|
|
301
301
|
})
|
|
302
302
|
|
|
303
|
+
// En mode multiple + use-chips, le texte du display est remplacé par les chips ;
|
|
304
|
+
// on ne garde le display que pour le placeholder quand la liste est vide.
|
|
305
|
+
const showDisplay = computed(
|
|
306
|
+
() => !(props.multiple && props.useChips) || selectedOptions.value.length === 0,
|
|
307
|
+
)
|
|
308
|
+
|
|
303
309
|
const floatActive = computed(
|
|
304
310
|
() => focused.value || query.value !== "" || selectedOptions.value.length > 0 || props.stackLabel,
|
|
305
311
|
)
|
|
@@ -497,7 +503,14 @@ const sheetClass = computed(() => [`q-select__sheet--${props.mode}`, modeOptions
|
|
|
497
503
|
<div class="q-field__control" @click="onControlClick">
|
|
498
504
|
<slot name="prepend" />
|
|
499
505
|
|
|
500
|
-
|
|
506
|
+
<!-- Chips : uniquement en multiple + use-chips (comme Quasar — un select
|
|
507
|
+
simple avec use-chips affiche juste le texte du display) -->
|
|
508
|
+
<span
|
|
509
|
+
v-for="(opt, i) in selectedOptions"
|
|
510
|
+
v-if="multiple && useChips"
|
|
511
|
+
:key="`${String(getOptionValue(opt))}-${i}`"
|
|
512
|
+
class="q-select__chip"
|
|
513
|
+
>
|
|
501
514
|
<span class="q-select__chip-label">{{ getOptionLabel(opt) }}</span>
|
|
502
515
|
<button
|
|
503
516
|
v-if="!disable && !readonly"
|
|
@@ -527,7 +540,7 @@ const sheetClass = computed(() => [`q-select__sheet--${props.mode}`, modeOptions
|
|
|
527
540
|
@keydown="onKeydown"
|
|
528
541
|
/>
|
|
529
542
|
<span
|
|
530
|
-
v-else
|
|
543
|
+
v-else-if="showDisplay"
|
|
531
544
|
class="q-select__display"
|
|
532
545
|
:class="{ 'q-select__display--placeholder': !displayText }"
|
|
533
546
|
>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// fixedLayout — offset automatique des barres fixed (q-header, q-back-header)
|
|
2
|
+
// et du q-page qui les suit.
|
|
3
|
+
//
|
|
4
|
+
// Problème : les barres fixed sortent du flux (position: fixed) → le contenu de
|
|
5
|
+
// q-page passe dessous. Ce module :
|
|
6
|
+
// - q-page (mode "page") reçoit un padding-top = hauteur cumulée des barres
|
|
7
|
+
// fixed frères qui le précèdent (le contenu ne se retrouve plus sous le header)
|
|
8
|
+
// - chaque barre fixed suivante (mode "bar") s'empile sous la précédente (top)
|
|
9
|
+
//
|
|
10
|
+
// Les barres fixed sont détectées par le sélecteur FIXED_BAR_SELECTOR parmi les
|
|
11
|
+
// frères du même parent ; la hauteur est observée (ResizeObserver) pour suivre
|
|
12
|
+
// les changements de taille (contenu, safe-area).
|
|
13
|
+
import { onBeforeUnmount, onMounted, watch } from "vue"
|
|
14
|
+
import type { Ref } from "vue"
|
|
15
|
+
|
|
16
|
+
export const FIXED_BAR_SELECTOR = ".q-header--fixed, .q-back-header--fixed"
|
|
17
|
+
|
|
18
|
+
/** Hauteur cumulée des barres fixed qui précèdent `el` (frères du même parent) */
|
|
19
|
+
export function fixedBarsHeightBefore(el: HTMLElement): number {
|
|
20
|
+
const parent = el.parentElement
|
|
21
|
+
if (!parent) return 0
|
|
22
|
+
let total = 0
|
|
23
|
+
for (const child of parent.children) {
|
|
24
|
+
if (child === el) break
|
|
25
|
+
if (child instanceof HTMLElement && child.matches(FIXED_BAR_SELECTOR)) {
|
|
26
|
+
total += child.offsetHeight
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return total
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Barres fixed frères de `el` (pour l'observation ResizeObserver) */
|
|
33
|
+
function fixedBarSiblings(el: HTMLElement): HTMLElement[] {
|
|
34
|
+
const parent = el.parentElement
|
|
35
|
+
if (!parent) return []
|
|
36
|
+
return Array.from(parent.children).filter(
|
|
37
|
+
(c): c is HTMLElement => c instanceof HTMLElement && c.matches(FIXED_BAR_SELECTOR),
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Applique l'offset lié aux barres fixed qui précèdent `el` :
|
|
43
|
+
* - mode "page" : padding-top = hauteur cumulée (q-page) + variable --q-page-offset
|
|
44
|
+
* - mode "bar" : top = hauteur cumulée (empilement des barres fixed)
|
|
45
|
+
*
|
|
46
|
+
* `enabled` permet de n'activer que dans certaines conditions (ex. props.fixed).
|
|
47
|
+
*/
|
|
48
|
+
export function useFixedBarOffset(
|
|
49
|
+
el: Ref<HTMLElement | null>,
|
|
50
|
+
mode: "page" | "bar",
|
|
51
|
+
enabled: () => boolean = () => true,
|
|
52
|
+
) {
|
|
53
|
+
let ro: ResizeObserver | null = null
|
|
54
|
+
|
|
55
|
+
const apply = () => {
|
|
56
|
+
const node = el.value
|
|
57
|
+
if (!node || !enabled()) return
|
|
58
|
+
const h = fixedBarsHeightBefore(node)
|
|
59
|
+
if (mode === "page") {
|
|
60
|
+
node.style.setProperty("--q-page-offset", h ? `${h}px` : "0px")
|
|
61
|
+
node.style.paddingTop = h ? `${h}px` : ""
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
node.style.top = h ? `${h}px` : ""
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const clear = () => {
|
|
69
|
+
const node = el.value
|
|
70
|
+
if (!node) return
|
|
71
|
+
if (mode === "page") {
|
|
72
|
+
node.style.removeProperty("--q-page-offset")
|
|
73
|
+
node.style.paddingTop = ""
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
node.style.top = ""
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const observe = () => {
|
|
81
|
+
const node = el.value
|
|
82
|
+
if (!node || typeof ResizeObserver === "undefined") return
|
|
83
|
+
const bars = fixedBarSiblings(node)
|
|
84
|
+
if (!bars.length) return
|
|
85
|
+
ro = new ResizeObserver(apply)
|
|
86
|
+
bars.forEach((b) => ro!.observe(b))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
onMounted(() => {
|
|
90
|
+
apply()
|
|
91
|
+
observe()
|
|
92
|
+
window.addEventListener("resize", apply)
|
|
93
|
+
})
|
|
94
|
+
onBeforeUnmount(() => {
|
|
95
|
+
ro?.disconnect()
|
|
96
|
+
window.removeEventListener("resize", apply)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// Ré-applique/efface quand la condition change (ex. bascule fixed)
|
|
100
|
+
watch(enabled, (v) => {
|
|
101
|
+
if (v) apply()
|
|
102
|
+
else clear()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
return apply
|
|
106
|
+
}
|
package/package.json
CHANGED
package/styles/main.css
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
/* ===== Reset global (Preflight Tailwind) =====
|
|
2
|
+
box-sizing: border-box partout : width:100% + padding (q-container, q-btn
|
|
3
|
+
stretch, q-toolbar…) ne débordent plus du viewport (scroll horizontal).
|
|
4
|
+
Sans ce reset, les composants en content-box débordaient. */
|
|
5
|
+
*,
|
|
6
|
+
::before,
|
|
7
|
+
::after {
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
html {
|
|
12
|
+
-webkit-text-size-adjust: 100%;
|
|
13
|
+
tab-size: 4;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
body {
|
|
17
|
+
margin: 0;
|
|
18
|
+
line-height: inherit;
|
|
19
|
+
}
|
|
20
|
+
|
|
1
21
|
/* ===== Tokens globaux ===== */
|
|
2
22
|
/* Rayon de base des composants. Surchargé localement par la prop `rounded`
|
|
3
23
|
(échelle none|xs|sm|md|lg via --q-radius) ou les composantProps du thème. */
|