@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.
@@ -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"
@@ -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
- import { computed } from "vue"
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,
@@ -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>
@@ -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
- <span v-for="(opt, i) in selectedOptions" :key="`${String(getOptionValue(opt))}-${i}`" class="q-select__chip">
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dnax/ui",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "module": "index.ts",
5
5
  "description": "UI design System",
6
6
  "type": "module",
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. */